dolphin/Source/VSProps/qt_tasks.targets
Shawn Hoffman af55938538 msbuild: use the moc dispatcher from Qt's vstools
The new method supports build parallelism and cleaner
integration into msbuild dependency flows.
2020-08-22 16:17:51 -07:00

1111 lines
52 KiB
XML

<?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>