<?xml version="1.0" encoding="utf-8"?> <!-- /**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt VS Tools. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ --> <!-- /////////////////////////////////////////////////////////////////////////////////////////////////// // Helper inline tasks used by the Qt/MSBuild targets // --> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK ListQrc ///////////////////////////////////////////////////////////////////////////////////////////////// // List resource paths in a QRC file. // Parameters: // in string QrcFilePath: path to QRC file // out string[] Result: paths to files referenced in QRC // --> <UsingTask TaskName="ListQrc" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <QrcFilePath ParameterType="System.String" Required="true" /> <Result ParameterType="System.String[]" Output="true" /> </ParameterGroup> <Task> <Reference Include="System.Xml"/> <Reference Include="System.Xml.Linq"/> <Using Namespace="System.Xml.Linq"/> <Code Type="Fragment" Language="cs"> <![CDATA[ XDocument qrc = XDocument.Load(QrcFilePath, LoadOptions.SetLineInfo); IEnumerable<XElement> files = qrc .Element("RCC") .Elements("qresource") .Elements("file"); Uri QrcPath = new Uri(QrcFilePath); Result = files .Select(x => new Uri(QrcPath, x.Value).LocalPath) .ToArray(); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK GetItemHash ///////////////////////////////////////////////////////////////////////////////////////////////// // Calculate an hash code (Deflate + Base64) for an item, given a list of metadata to use as key // Parameters: // in ITaskItem Item: item for which the hash will be calculated // in string[] Keys: list of names of the metadata to use as item key // out string Hash: hash code (Base64 representation of Deflate'd UTF-8 item key) // --> <UsingTask TaskName="GetItemHash" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <Item ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" /> <Keys ParameterType="System.String[]" Required="true" /> <Hash Output="true" ParameterType="System.String" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Text"/> <Using Namespace="System.IO"/> <Using Namespace="System.IO.Compression"/> <Code Type="Fragment" Language="cs"> <![CDATA[ var data = Encoding.UTF8.GetBytes(string.Concat(Keys.OrderBy(x => x) .Select(x => string.Format("[{0}={1}]", x, Item.GetMetadata(x)))) .ToUpper()); using (var dataZipped = new MemoryStream()) { using (var zip = new DeflateStream(dataZipped, CompressionLevel.Fastest)) zip.Write(data, 0, data.Length); Hash = Convert.ToBase64String(dataZipped.ToArray()); } ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK Expand ///////////////////////////////////////////////////////////////////////////////////////////////// // Expand a list of items, taking additional metadata from a base item and from a template item. // Parameters: // in ITaskItem[] Items: items to expand // in ITaskItem BaseItem: base item from which the expanded items derive // in ITaskItem Template = null: (optional) template containing metadata to add / update // out ITaskItem[] Result: list of new items // --> <UsingTask TaskName="Expand" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <BaseItem ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" /> <Template ParameterType="Microsoft.Build.Framework.ITaskItem" Required="false"/> <Result Output="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Text.RegularExpressions"/> <Code Type="Fragment" Language="cs"> <![CDATA[ Result = new ITaskItem[] { }; var reserved = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "AccessedTime", "CreatedTime", "DefiningProjectDirectory", "DefiningProjectExtension", "DefiningProjectFullPath", "DefiningProjectName", "Directory", "Extension", "Filename", "FullPath", "Identity", "ModifiedTime", "RecursiveDir", "RelativeDir", "RootDir", }; var newItems = new List<ITaskItem>(); foreach (var item in Items) { var newItem = new TaskItem(item); if (BaseItem != null) BaseItem.CopyMetadataTo(newItem); var itemExt = newItem.GetMetadata("Extension"); if (!string.IsNullOrEmpty(itemExt)) newItem.SetMetadata("Suffix", itemExt.Substring(1)); if (Template != null) { var metadataNames = Template.MetadataNames .Cast<string>().Where(x => !reserved.Contains(x)); foreach (var metadataName in metadataNames) { var metadataValue = Template.GetMetadata(metadataName); newItem.SetMetadata(metadataName, Regex.Replace(metadataValue, @"(%<)(\w+)(>)", match => newItem.GetMetadata(match.Groups[2].Value))); } } newItems.Add(newItem); } Result = newItems.ToArray(); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK DumpItems ///////////////////////////////////////////////////////////////////////////////////////////////// // Dump contents of items as a log message. The contents are formatted as XML. // Parameters: // in string ItemType: type of the items; this is used as the parent node of each // item dump // in ITaskItem[] Items: items to dump // in bool DumpReserved: include MSBuild reserved metadata in dump? // in string Metadata: list of names of metadata to include in dump; omit to // include all metadata // --> <UsingTask TaskName="DumpItems" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <ItemType ParameterType="System.String" Required="true" /> <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <DumpReserved ParameterType="System.Boolean" Required="false" /> <Metadata ParameterType="System.String" Required="false" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Diagnostics"/> <Using Namespace="System.Text"/> <Code Type="Fragment" Language="cs"> <![CDATA[ var reserved = new HashSet<string> { "AccessedTime", "CreatedTime", "DefiningProjectDirectory", "DefiningProjectExtension", "DefiningProjectFullPath", "DefiningProjectName", "Directory", "Extension", "Filename", "FullPath", "Identity", "ModifiedTime", "RecursiveDir", "RelativeDir", "RootDir", }; if (Metadata == null) Metadata = ""; var requestedNames = new HashSet<string>(Metadata.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); var itemXml = new StringBuilder(); if (Items.Any()) { foreach (var item in Items) { if (itemXml.Length > 0) itemXml.Append("\r\n"); itemXml.AppendFormat("<{0} Include=\"{1}\"", ItemType, item.ItemSpec); var names = item.MetadataNames.Cast<string>() .Where(x => (DumpReserved || !reserved.Contains(x)) && (!requestedNames.Any() || requestedNames.Contains(x))) .OrderBy(x => x); if (names.Any()) { itemXml.Append(">\r\n"); foreach (string name in names) { if (!DumpReserved && reserved.Contains(name)) continue; if (!item.MetadataNames.Cast<string>().Contains(name)) continue; var value = item.GetMetadata(name); if (!string.IsNullOrEmpty(value)) itemXml.AppendFormat(" <{0}>{1}</{0}>\r\n", name, value); else itemXml.AppendFormat(" <{0}/>\r\n", name); } itemXml.AppendFormat("</{0}>", ItemType); } else { itemXml.Append("/>"); } } } else { itemXml.AppendFormat("<{0}/>", ItemType); } Log.LogMessage(MessageImportance.High, itemXml.ToString()); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK QtRunWork ///////////////////////////////////////////////////////////////////////////////////////////////// // Run work items in parallel processes. // Parameters: // in ITaskItem[] QtWork: work items // in int QtMaxProcs: maximum number of processes to run in parallel // in bool QtDebug: generate debug messages // out ITaskItem[] Result: list of new items with the result of each work item // --> <UsingTask TaskName="QtRunWork" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <QtWork ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <QtMaxProcs ParameterType="System.Int32" Required="true" /> <QtDebug ParameterType="System.Boolean" Required="true" /> <Result Output="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.IO"/> <Using Namespace="System.Diagnostics"/> <Using Namespace="System.Collections.Generic"/> <Code Type="Fragment" Language="cs"> <![CDATA[ Result = new ITaskItem[] { }; bool ok = true; var Comparer = StringComparer.InvariantCultureIgnoreCase; var Comparison = StringComparison.InvariantCultureIgnoreCase; // Work item key = "%(WorkType)(%(Identity))" Func<string, string, string> KeyString = (x, y) => string.Format("{0}{{{1}}}", x, y); Func<ITaskItem, string> Key = (item) => KeyString(item.GetMetadata("WorkType"), item.ItemSpec); var workItemKeys = new HashSet<string>(QtWork.Select(x => Key(x)), Comparer); // Work items, indexed by %(Identity) var workItemsByIdentity = QtWork .GroupBy(x => x.ItemSpec, x => Key(x), Comparer) .ToDictionary(x => x.Key, x => new List<string>(x), Comparer); // Work items, indexed by work item key var workItems = QtWork.Select(x => new { Self = x, Key = Key(x), ToolPath = x.GetMetadata("ToolPath"), Message = x.GetMetadata("Message"), DependsOn = new HashSet<string>(comparer: Comparer, collection: x.GetMetadata("DependsOn") .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Where(y => workItemsByIdentity.ContainsKey(y)) .SelectMany(y => workItemsByIdentity[y]) .Union(x.GetMetadata("DependsOnWork") .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(y => KeyString(y, x.ItemSpec)) .Where(y => workItemKeys.Contains(y))) .GroupBy(y => y, Comparer).Select(y => y.Key) .Where(y => !y.Equals(Key(x), Comparison))), ProcessStartInfo = new ProcessStartInfo { FileName = x.GetMetadata("ToolPath"), Arguments = x.GetMetadata("Options"), CreateNoWindow = true, UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, }, }) // In case of items with duplicate keys, use only the first one .GroupBy(x => x.Key, Comparer) .ToDictionary(x => x.Key, x => x.First(), Comparer); // Result var result = workItems.Values .ToDictionary(x => x.Key, x => new TaskItem(x.Self)); // Dependency relation [item -> dependent items] var dependentsOf = workItems.Values .Where(x => x.DependsOn.Any()) .SelectMany(x => x.DependsOn.Select(y => new { Dependent = x.Key, Dependency = y })) .GroupBy(x => x.Dependency, x => x.Dependent, Comparer) .ToDictionary(x => x.Key, x => new List<string>(x), Comparer); // Work items that are ready to start; initially queue all independent items var workQueue = new Queue<string>(workItems.Values .Where(x => !x.DependsOn.Any()) .Select(x => x.Key)); if (QtDebug) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork queueing\r\n## {0}", string.Join("\r\n## ", workQueue))); } // Postponed items; save dependent items to queue later when ready var postponedItems = new HashSet<string>(workItems.Values .Where(x => x.DependsOn.Any()) .Select(x => x.Key)); if (QtDebug && postponedItems.Any()) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork postponed dependents\r\n## {0}", string.Join("\r\n## ", postponedItems .Select(x => string.Format("{0} <- {1}", x, string.Join(", ", workItems[x].DependsOn)))))); } // Work items that are running; must synchronize with the exit of all processes var running = new Queue<KeyValuePair<string, Process>>(); // Work items that have terminated var terminated = new HashSet<string>(Comparer); // While there are work items queued, start a process for each item while (ok && workQueue.Any()) { var workItem = workItems[workQueue.Dequeue()]; Log.LogMessage(MessageImportance.High, workItem.Message); try { var proc = Process.Start(workItem.ProcessStartInfo); proc.OutputDataReceived += (object sender, DataReceivedEventArgs e) => { if (!string.IsNullOrEmpty(e.Data)) Log.LogMessage(MessageImportance.High, string.Join(" ", new[] { (QtDebug ? "[" + (((Process)sender).Id.ToString()) + "]" : ""), e.Data })); }; proc.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => { if (!string.IsNullOrEmpty(e.Data)) Log.LogMessage(MessageImportance.High, string.Join(" ", new[] { (QtDebug ? "[" + (((Process)sender).Id.ToString()) + "]" : ""), e.Data })); }; proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); running.Enqueue(new KeyValuePair<string, Process>(workItem.Key, proc)); } catch (Exception e) { Log.LogError( string.Format("[QtRunWork] Error starting process {0}: {1}", workItem.ToolPath, e.Message)); ok = false; } string qtDebugRunning = ""; if (QtDebug) { qtDebugRunning = string.Format("## QtRunWork waiting {0}", string.Join(", ", running .Select(x => string.Format("{0} [{1}]", x.Key, x.Value.Id)))); } // Wait for process to terminate when there are processes running, and... while (ok && running.Any() // ...work is queued but already reached the maximum number of processes, or... && ((workQueue.Any() && running.Count >= QtMaxProcs) // ...work queue is empty but there are dependents that haven't yet been queued || (!workQueue.Any() && postponedItems.Any()))) { var itemProc = running.Dequeue(); workItem = workItems[itemProc.Key]; var proc = itemProc.Value; if (QtDebug && !string.IsNullOrEmpty(qtDebugRunning)) { Log.LogMessage(MessageImportance.High, qtDebugRunning); qtDebugRunning = ""; } if (proc.WaitForExit(100)) { if (QtDebug) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork exit {0} [{1}] = {2} ({3:0.00} msecs)", workItem.Key, proc.Id, proc.ExitCode, (proc.ExitTime - proc.StartTime).TotalMilliseconds)); } // Process terminated; check exit code and close terminated.Add(workItem.Key); result[workItem.Key].SetMetadata("ExitCode", proc.ExitCode.ToString()); ok &= (proc.ExitCode == 0); proc.Close(); // Add postponed dependent items to work queue if (ok && dependentsOf.ContainsKey(workItem.Key)) { // Dependents of workItem... var readyDependents = dependentsOf[workItem.Key] // ...that have not yet been queued... .Where(x => postponedItems.Contains(x) // ...and whose dependending items have all terminated. && workItems[x].DependsOn.All(y => terminated.Contains(y))); if (QtDebug && readyDependents.Any()) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork queueing\r\n## {0}", string.Join("\r\n## ", readyDependents))); } foreach (var dependent in readyDependents) { postponedItems.Remove(dependent); workQueue.Enqueue(dependent); } } } else { // Process is still running; feed it back into the running queue running.Enqueue(itemProc); } } } // If there are items still haven't been queued, that means a circular dependency exists if (ok && postponedItems.Any()) { ok = false; Log.LogError("[QtRunWork] Error: circular dependency"); if (QtDebug) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork circularity\r\n## {0}", string.Join("\r\n## ", postponedItems .Select(x => string.Format("{0} <- {1}", x, string.Join(", ", workItems[x].DependsOn)))))); } } if (ok && QtDebug) { Log.LogMessage(MessageImportance.High, "## QtRunWork all work queued"); if (running.Any()) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork waiting {0}", string.Join(", ", running .Select(x => string.Format("{0} [{1}]", x.Key, x.Value.Id))))); } } // Wait for all running processes to terminate while (running.Any()) { var itemProc = running.Dequeue(); var workItem = workItems[itemProc.Key]; var proc = itemProc.Value; if (proc.WaitForExit(100)) { if (QtDebug) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork exit {0} [{1}] = {2} ({3:0.00} msecs)", workItem.Key, proc.Id, proc.ExitCode, (proc.ExitTime - proc.StartTime).TotalMilliseconds)); } // Process terminated; check exit code and close result[workItem.Key].SetMetadata("ExitCode", proc.ExitCode.ToString()); ok &= (proc.ExitCode == 0); proc.Close(); } else { // Process is still running; feed it back into the running queue running.Enqueue(itemProc); } } if (QtDebug) { Log.LogMessage(MessageImportance.High, string.Format("## QtRunWork result {0}", (ok ? "ok" : "FAILED!"))); } Result = result.Values.ToArray(); if (!ok) return false; ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK ParseVarDefs ///////////////////////////////////////////////////////////////////////////////////////////////// // // --> <UsingTask TaskName="ParseVarDefs" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <QtVars Required="true" ParameterType="System.String"/> <OutVarDefs Output="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]"/> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Text"/> <Using Namespace="System.Text.RegularExpressions"/> <Code Type="Fragment" Language="cs"> <![CDATA[ OutVarDefs = Regex.Matches(QtVars, @"\s*(\w+)\s*(?:;|=\s*(\w*)\s*(?:\/((?:\\.|[^;\/])*)\/((?:\\.|[^;\/])*)\/)?)?") .Cast<Match>() .Where((Match x) => x.Groups.Count > 4 && !string.IsNullOrEmpty(x.Groups[1].Value)) .Select((Match x) => x.Groups .Cast<Group>() .Select((Group y) => !string.IsNullOrEmpty(y.Value) ? y.Value : null) .ToArray()) .Select((string[] x) => new TaskItem(x[1], new Dictionary<string,string> { { "Name" , x[2] ?? x[1] }, { "Pattern" , x[3] ?? ".*" }, { "Value" , x[4] ?? "$0" }, })) .ToArray(); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK GetVarsFromMakefile ///////////////////////////////////////////////////////////////////////////////////////////////// // // --> <UsingTask TaskName="GetVarsFromMakefile" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <Makefile Required="true" ParameterType="System.String"/> <VarDefs Required="false" ParameterType="Microsoft.Build.Framework.ITaskItem[]"/> <ExcludeValues Required="true" ParameterType="System.String[]"/> <OutVars Output="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]"/> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Text"/> <Using Namespace="System.Text.RegularExpressions"/> <Using Namespace="System.IO"/> <Code Type="Fragment" Language="cs"> <![CDATA[ var makefileVars = Regex.Matches( File.ReadAllText(Makefile), @"^(\w+)[^\=\r\n\S]*\=[^\r\n\S]*([^\r\n]+)[\r\n]", RegexOptions.Multiline).Cast<Match>() .Where(x => x.Groups.Count > 2 && x.Groups[1].Success && x.Groups[2].Success && !string.IsNullOrEmpty(x.Groups[1].Value)) .GroupBy(x => x.Groups[1].Value) .ToDictionary(g => g.Key, g => g.Last().Groups[2].Value); OutVars = VarDefs .Where(x => makefileVars.ContainsKey(x.GetMetadata("Name"))) .Select(x => new TaskItem(x.ItemSpec, new Dictionary<string,string> { { "Value", string.Join(";", Regex .Matches(makefileVars[x.GetMetadata("Name")], x.GetMetadata("Pattern")) .Cast<Match>() .Select(y => Regex .Replace(y.Value, x.GetMetadata("Pattern"), x.GetMetadata("Value"))) .Where(y => !string.IsNullOrEmpty(y) && !ExcludeValues.Contains(y, StringComparer.InvariantCultureIgnoreCase)) .ToHashSet()) } })) .Where(x => !string.IsNullOrEmpty(x.GetMetadata("Value"))) .ToArray(); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK Flatten ///////////////////////////////////////////////////////////////////////////////////////////////// // Destructure items into a "flat" list of metadata. The output is a list of (Name, Value) pairs, // each corresponding to one item metadata. Semi-colon-separated lists will also expand to many // items in the output list, with the metadata name shared among them. // Example: // INPUT: // <QtMoc> // <InputFile>foo.h</InputFile> // <IncludePath>C:\FOO;D:\BAR</IncludePath> // </QtMoc> // OUTPUT: // <Result> // <Name>InputFile</Name> // <Value>foo.h</Value> // </Result> // <Result> // <Name>IncludePath</Name> // <Value>C:\FOO</Value> // </Result> // <Result> // <Name>IncludePath</Name> // <Value>D:\BAR</Value> // </Result> // Parameters: // in ITaskItem[] Items: list of items to flatten // in string[] Metadata: names of metadata to look for; omit to include all metadata // out ITaskItem[] Result: list of metadata from all items // --> <UsingTask TaskName="Flatten" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <Metadata ParameterType="System.String[]" Required="false" /> <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.IO"/> <Using Namespace="System.Diagnostics"/> <Using Namespace="System.Collections.Generic"/> <Code Type="Fragment" Language="cs"> <![CDATA[ Result = new ITaskItem[] { }; var reserved = new HashSet<string> { "AccessedTime", "CreatedTime", "DefiningProjectDirectory", "DefiningProjectExtension", "DefiningProjectFullPath", "DefiningProjectName", "Directory", "Extension", "Filename", "FullPath", "Identity", "ModifiedTime", "RecursiveDir", "RelativeDir", "RootDir", }; if (Metadata == null) Metadata = new string[0]; var requestedNames = new HashSet<string>(Metadata.Where(x => !string.IsNullOrEmpty(x))); var newItems = new List<ITaskItem>(); foreach (var item in Items) { var itemName = item.ItemSpec; var names = item.MetadataNames.Cast<string>().Where(x => !reserved.Contains(x) && (!requestedNames.Any() || requestedNames.Contains(x))); foreach (string name in names) { var values = item.GetMetadata(name).Split(';'); foreach (string value in values.Where(v => !string.IsNullOrEmpty(v))) { newItems.Add(new TaskItem(string.Format("{0}={1}", name, value), new Dictionary<string, string> { { "Item", itemName }, { "Name", name }, { "Value", value }, })); } } } Result = newItems.ToArray(); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK HostTranslatePaths ///////////////////////////////////////////////////////////////////////////////////////////////// // Translate local (Windows) paths to build host paths. This could be a Linux host for cross // compilation, or a simple copy (i.e. "no-op") when building in Windows. // Input and output items are in the form: // <...> // <Item>...</Item> // <Name>...</Name> // <Value>...</Value> // </...> // where <Item> is the local path, <Name> is a filter criteria identifier matched with the Names // parameter, and <Value> is set to the host path in output items (for input items <Value> must // be equal to <Item>). // Parameters: // in ITaskItem[] Items: input items with local paths // in string[] Names: filter criteria; unmatched items will simply be copied (i.e. no-op) // out ITaskItem[] Result: output items with translated host paths // --> <!--// Linux build over WSL --> <UsingTask TaskName="HostTranslatePaths" TaskFactory="CodeTaskFactory" Condition="'$(ApplicationType)' == 'Linux' AND '$(PlatformToolset)' == 'WSL_1_0'" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <Names ParameterType="System.String[]" Required="false" /> <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> </ParameterGroup> <Task> <Reference Include="$(VCTargetsPath)\Application Type\Linux\1.0\liblinux.dll"/> <Using Namespace="System.IO"/> <Using Namespace="liblinux"/> <Using Namespace="liblinux.IO"/> <Code Type="Fragment" Language="cs"> <![CDATA[ Result = new ITaskItem[] { }; var newItems = new List<ITaskItem>(); foreach (var item in Items) { string itemName = item.GetMetadata("Name"); string itemValue = item.GetMetadata("Value"); if (Names.Contains(itemName)) { if (Path.IsPathRooted(itemValue) && !itemValue.StartsWith("/")) itemValue = PathUtils.TranslateWindowsPathToWSLPath(itemValue); else itemValue = itemValue.Replace(@"\", "/"); } newItems.Add(new TaskItem(item.ItemSpec, new Dictionary<string, string> { { "Item", item.GetMetadata("Item") }, { "Name", itemName }, { "Value", itemValue }, })); } Result = newItems.ToArray(); ]]> </Code> </Task> </UsingTask> <!--// Linux build over SSH --> <UsingTask TaskName="HostTranslatePaths" TaskFactory="CodeTaskFactory" Condition="'$(ApplicationType)' == 'Linux' AND '$(PlatformToolset)' != 'WSL_1_0'" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <Names ParameterType="System.String[]" Required="false" /> <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> </ParameterGroup> <Task> <Reference Include="$(VCTargetsPath)\Application Type\Linux\1.0\Microsoft.Build.Linux.Tasks.dll"/> <Code Type="Fragment" Language="cs"> <![CDATA[ Result = new ITaskItem[] { }; var newItems = new List<ITaskItem>(); foreach (var item in Items) { string itemName = item.GetMetadata("Name"); string itemValue = item.GetMetadata("Value"); if (Names.Contains(itemName)) { if (Path.IsPathRooted(itemValue) && !itemValue.StartsWith("/")) { var projectdir = new Uri(@"$(ProjectDir)"); var itemFileName = Path.GetFileName(itemValue); var itemDirName = Path.GetFullPath(Path.GetDirectoryName(itemValue)); if (!itemDirName.EndsWith(@"\")) itemDirName += @"\"; var itemDir = new Uri(itemDirName); if (projectdir.IsBaseOf(itemDir)) { itemValue = projectdir.MakeRelativeUri(itemDir).OriginalString + itemFileName; } else { Log.LogWarning("Unable to translate path: {0}", itemValue); } } else { itemValue = itemValue.Replace(@"\", "/"); } } newItems.Add(new TaskItem(item.ItemSpec, new Dictionary<string, string> { { "Item", item.GetMetadata("Item") }, { "Name", itemName }, { "Value", itemValue }, })); } Result = newItems.ToArray(); ]]> </Code> </Task> </UsingTask> <!--// Local (Windows) build --> <UsingTask TaskName="HostTranslatePaths" TaskFactory="CodeTaskFactory" Condition="'$(ApplicationType)' != 'Linux'" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> <Names ParameterType="System.String[]" Required="false" /> <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> </ParameterGroup> <Task> <Code Type="Fragment" Language="cs"> <![CDATA[ Result = Items.ToArray(); ]]> </Code> </Task> </UsingTask> <!-- ///////////////////////////////////////////////////////////////////////////////////////////////// /// TASK HostExec ///////////////////////////////////////////////////////////////////////////////////////////////// // Run command in build host. // Parameters: // in string Command: Command to run on the build host // in string RedirectStdOut: Path to file to receive redirected output messages // * can be NUL to discard messages // in string RedirectStdErr: Path to file to receive redirected error messages // * can be NUL to discard messages // * can be STDOUT to apply the same redirection as output messages // in string WorkingDirectory: Path to directory where command will be run // in ITaskItem[] Inputs: List of local -> host path mappings for command inputs // * item format: cf. HostTranslatePaths task // in ITaskItem[] Outputs: List of host -> local path mappings for command outputs // * item format: cf. HostTranslatePaths task // in string RemoteTarget: Set by ResolveRemoteDir in SSH mode; null otherwise // in string RemoteProjectDir: Set by ResolveRemoteDir in SSH mode; null otherwise // in bool IgnoreExitCode: Set flag to disable build error if command failed // out int ExitCode: status code at command exit // --> <!--// Linux build over WSL --> <UsingTask TaskName="HostExec" TaskFactory="CodeTaskFactory" Condition="'$(ApplicationType)' == 'Linux' AND '$(PlatformToolset)' == 'WSL_1_0'" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <!--IN--> <Message ParameterType="System.String" Required="false" /> <Command ParameterType="System.String" Required="true" /> <RedirectStdOut ParameterType="System.String" Required="false" /> <RedirectStdErr ParameterType="System.String" Required="false" /> <WorkingDirectory ParameterType="System.String" Required="false" /> <Inputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" /> <Outputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" /> <RemoteTarget ParameterType="System.String" Required="false" /> <RemoteProjectDir ParameterType="System.String" Required="false" /> <IgnoreExitCode ParameterType="System.Boolean" Required="false" /> <!--OUT--> <ExitCode ParameterType="System.Int32" Output="true" /> </ParameterGroup> <Task> <Reference Include="$(VCTargetsPath)\Application Type\Linux\1.0\Microsoft.Build.Linux.Tasks.dll"/> <Code Type="Fragment" Language="cs"> <![CDATA[ if (!string.IsNullOrEmpty(Message)) Log.LogMessage(MessageImportance.High, Message); Command = "(" + Command + ")"; if (RedirectStdOut == "NUL" || RedirectStdOut == "/dev/null") Command += " 1> /dev/null"; else if (!string.IsNullOrEmpty(RedirectStdOut)) Command += " 1> " + RedirectStdOut; if (RedirectStdErr == "NUL" || RedirectStdErr == "/dev/null") Command += " 2> /dev/null"; else if (RedirectStdErr == "STDOUT") Command += " 2>&1"; else if (!string.IsNullOrEmpty(RedirectStdErr)) Command += " 2> " + RedirectStdErr; var createDirs = new List<string>(); if (Inputs != null) { createDirs.AddRange(Inputs .Select(x => string.Format("\x24(dirname {0})", x.GetMetadata("Value")))); } if (Outputs != null) { createDirs.AddRange(Outputs .Select(x => string.Format("\x24(dirname {0})", x.GetMetadata("Value")))); } if (!string.IsNullOrEmpty(WorkingDirectory)) { createDirs.Add(WorkingDirectory); Command = string.Format("cd {0}; {1}", WorkingDirectory, Command); } if (createDirs.Any()) { Command = string.Format("{0}; {1}", string.Join("; ", createDirs.Select(x => string.Format("mkdir -p {0}", x))), Command); } var taskExec = new Microsoft.Build.Linux.WSL.Tasks.ExecuteCommand() { BuildEngine = BuildEngine, HostObject = HostObject, ProjectDir = @"$(ProjectDir)", IntermediateDir = @"$(IntDir)", WSLPath = @"$(WSLPath)", Command = Command, }; Log.LogMessage("\r\n==== HostExec: Microsoft.Build.Linux.WSL.Tasks.ExecuteCommand"); Log.LogMessage("ProjectDir: {0}", taskExec.ProjectDir); Log.LogMessage("IntermediateDir: {0}", taskExec.IntermediateDir); Log.LogMessage("WSLPath: {0}", taskExec.WSLPath); Log.LogMessage("Command: {0}", taskExec.Command); bool ok = taskExec.Execute(); Log.LogMessage("== {0} ExitCode: {1}\r\n", ok ? "OK" : "FAIL", taskExec.ExitCode); ExitCode = taskExec.ExitCode; if (!ok && !IgnoreExitCode) { Log.LogError("Host command failed."); return false; } ]]> </Code> </Task> </UsingTask> <!--// Linux build over SSH --> <UsingTask TaskName="HostExec" TaskFactory="CodeTaskFactory" Condition="'$(ApplicationType)' == 'Linux' AND '$(PlatformToolset)' != 'WSL_1_0'" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <!--IN--> <Message ParameterType="System.String" Required="false" /> <Command ParameterType="System.String" Required="true" /> <RedirectStdOut ParameterType="System.String" Required="false" /> <RedirectStdErr ParameterType="System.String" Required="false" /> <WorkingDirectory ParameterType="System.String" Required="false" /> <Inputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" /> <Outputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" /> <RemoteTarget ParameterType="System.String" Required="false" /> <RemoteProjectDir ParameterType="System.String" Required="false" /> <IgnoreExitCode ParameterType="System.Boolean" Required="false" /> <!--OUT--> <ExitCode ParameterType="System.Int32" Output="true" /> </ParameterGroup> <Task> <Reference Include="$(VCTargetsPath)\Application Type\Linux\1.0\Microsoft.Build.Linux.Tasks.dll"/> <Code Type="Fragment" Language="cs"> <![CDATA[ if (!string.IsNullOrEmpty(Message)) Log.LogMessage(MessageImportance.High, Message); var createDirs = new List<string> { string.Format("{0}/{1}", RemoteProjectDir, WorkingDirectory) }; var localFilesToCopyRemotelyMapping = new string[0]; if (Inputs != null) { localFilesToCopyRemotelyMapping = Inputs .Select(x => string.Format(@"{0}:={1}/{2}", x.GetMetadata("Item"), RemoteProjectDir, x.GetMetadata("Value"))) .ToArray(); createDirs.AddRange(Inputs .Select(x => string.Format("\x24(dirname {0})", x.GetMetadata("Value")))); } var remoteFilesToCopyLocallyMapping = new string[0]; if (Outputs != null) { remoteFilesToCopyLocallyMapping = Outputs .Select(x => string.Format(@"{0}/{1}:={2}", RemoteProjectDir, x.GetMetadata("Value"), x.GetMetadata("Item"))) .ToArray(); createDirs.AddRange(Outputs .Select(x => string.Format("\x24(dirname {0})", x.GetMetadata("Value")))); } Command = "(" + Command + ")"; if (RedirectStdOut == "NUL" || RedirectStdOut == "/dev/null") Command += " 1> /dev/null"; else if (!string.IsNullOrEmpty(RedirectStdOut)) Command += " 1> " + RedirectStdOut; if (RedirectStdErr == "NUL" || RedirectStdErr == "/dev/null") Command += " 2> /dev/null"; else if (RedirectStdErr == "STDOUT") Command += " 2>&1"; else if (!string.IsNullOrEmpty(RedirectStdErr)) Command += " 2> " + RedirectStdErr; Command = string.Format("cd {0}/{1}; {2}", RemoteProjectDir, WorkingDirectory, Command); var taskCopyFiles = new Microsoft.Build.Linux.Tasks.Execute() { BuildEngine = BuildEngine, HostObject = HostObject, ProjectDir = @"$(ProjectDir)", IntermediateDir = @"$(IntDir)", RemoteTarget = RemoteTarget, RemoteProjectDir = RemoteProjectDir, Command = string.Join("; ", createDirs.Select(x => string.Format("mkdir -p {0}", x))), LocalFilesToCopyRemotelyMapping = localFilesToCopyRemotelyMapping, }; var taskExec = new Microsoft.Build.Linux.Tasks.Execute() { BuildEngine = BuildEngine, HostObject = HostObject, ProjectDir = @"$(ProjectDir)", IntermediateDir = @"$(IntDir)", RemoteTarget = RemoteTarget, RemoteProjectDir = RemoteProjectDir, Command = Command, RemoteFilesToCopyLocallyMapping = remoteFilesToCopyLocallyMapping, }; Log.LogMessage("\r\n==== HostExec: Microsoft.Build.Linux.Tasks.Execute"); Log.LogMessage("ProjectDir: {0}", taskExec.ProjectDir); Log.LogMessage("IntermediateDir: {0}", taskExec.IntermediateDir); Log.LogMessage("RemoteTarget: {0}", taskExec.RemoteTarget); Log.LogMessage("RemoteProjectDir: {0}", taskExec.RemoteProjectDir); if (taskExec.LocalFilesToCopyRemotelyMapping.Any()) Log.LogMessage("LocalFilesToCopyRemotelyMapping: {0}", taskExec.LocalFilesToCopyRemotelyMapping); if (taskExec.RemoteFilesToCopyLocallyMapping.Any()) Log.LogMessage("RemoteFilesToCopyLocallyMapping: {0}", taskExec.RemoteFilesToCopyLocallyMapping); Log.LogMessage("CreateDirs: {0}", taskCopyFiles.Command); Log.LogMessage("Command: {0}", taskExec.Command); if (!taskCopyFiles.ExecuteTool()) return false; bool ok = taskExec.ExecuteTool(); Log.LogMessage("== {0} ExitCode: {1}\r\n", ok ? "OK" : "FAIL", taskExec.ExitCode); ExitCode = taskExec.ExitCode; if (!ok && !IgnoreExitCode) { Log.LogError("Host command failed."); return false; } ]]> </Code> </Task> </UsingTask> <!--// Local (Windows) build --> <UsingTask TaskName="HostExec" TaskFactory="CodeTaskFactory" Condition="'$(ApplicationType)' != 'Linux'" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <!--IN--> <Message ParameterType="System.String" Required="false" /> <Command ParameterType="System.String" Required="true" /> <RedirectStdOut ParameterType="System.String" Required="false" /> <RedirectStdErr ParameterType="System.String" Required="false" /> <WorkingDirectory ParameterType="System.String" Required="false" /> <Inputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" /> <Outputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" /> <RemoteTarget ParameterType="System.String" Required="false" /> <RemoteProjectDir ParameterType="System.String" Required="false" /> <IgnoreExitCode ParameterType="System.Boolean" Required="false" /> <!--OUT--> <ExitCode ParameterType="System.Int32" Output="true" /> </ParameterGroup> <Task> <Reference Include="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"/> <Reference Include="$(MSBuildToolsPath)\Microsoft.Build.Utilities.Core.dll"/> <Code Type="Fragment" Language="cs"> <![CDATA[ if (!string.IsNullOrEmpty(Message)) Log.LogMessage(MessageImportance.High, Message); Command = "(" + Command + ")"; if (RedirectStdOut == "NUL" || RedirectStdOut == "/dev/null") Command += " 1> NUL"; else if (!string.IsNullOrEmpty(RedirectStdOut)) Command += " 1> " + RedirectStdOut; if (RedirectStdErr == "NUL" || RedirectStdErr == "/dev/null") Command += " 2> NUL"; else if (RedirectStdErr == "STDOUT") Command += " 2>&1"; else if (!string.IsNullOrEmpty(RedirectStdErr)) Command += " 2> " + RedirectStdErr; var taskExec = new Microsoft.Build.Tasks.Exec() { BuildEngine = BuildEngine, HostObject = HostObject, WorkingDirectory = WorkingDirectory, Command = Command, IgnoreExitCode = IgnoreExitCode, }; Log.LogMessage("\r\n==== HostExec: Microsoft.Build.Tasks.Exec"); Log.LogMessage("WorkingDirectory: {0}", taskExec.WorkingDirectory); Log.LogMessage("Command: {0}", taskExec.Command); bool ok = taskExec.Execute(); Log.LogMessage("== {0} ExitCode: {1}\r\n", ok ? "OK" : "FAIL", taskExec.ExitCode); ExitCode = taskExec.ExitCode; if (!ok) return false; ]]> </Code> </Task> </UsingTask> </Project>