From d3d3649a68cb1fa589fdd987a6690dbd5d671f0d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 17 Sep 2017 15:35:20 -0700 Subject: Initial code commit --- src/WixToolset.BuildTasks/AssemblyInfo.cs | 7 + src/WixToolset.BuildTasks/BuildException.cs | 26 + src/WixToolset.BuildTasks/Candle.cs | 199 +++ src/WixToolset.BuildTasks/Common.cs | 41 + src/WixToolset.BuildTasks/ConvertReferences.cs | 93 ++ .../CreateItemAvoidingInference.cs | 60 + .../CreateProjectReferenceDefineConstants.cs | 270 ++++ src/WixToolset.BuildTasks/DoIt-Compile.cs | 192 +++ src/WixToolset.BuildTasks/DoIt.cs | 233 ++++ .../FileSearchHelperMethods.cs | 60 + .../GenerateCompileWithObjectPath.cs | 146 +++ src/WixToolset.BuildTasks/GetCabList.cs | 87 ++ src/WixToolset.BuildTasks/GetLooseFileList.cs | 230 ++++ src/WixToolset.BuildTasks/GlobalSuppressions.cs | 8 + src/WixToolset.BuildTasks/Insignia.cs | 118 ++ src/WixToolset.BuildTasks/Light.cs | 488 +++++++ src/WixToolset.BuildTasks/Lit.cs | 178 +++ src/WixToolset.BuildTasks/Pyro.cs | 140 ++ .../RefreshBundleGeneratedFile.cs | 132 ++ src/WixToolset.BuildTasks/RefreshGeneratedFile.cs | 118 ++ src/WixToolset.BuildTasks/ReplaceString.cs | 54 + src/WixToolset.BuildTasks/ResolveWixReferences.cs | 212 +++ src/WixToolset.BuildTasks/TaskBase.cs | 65 + src/WixToolset.BuildTasks/Torch.cs | 159 +++ src/WixToolset.BuildTasks/WixAssignCulture.cs | 231 ++++ src/WixToolset.BuildTasks/WixCommandLineBuilder.cs | 180 +++ src/WixToolset.BuildTasks/WixToolTask.cs | 406 ++++++ .../WixToolset.BuildTasks.csproj | 38 + src/WixToolset.BuildTasks/heatdirectory.cs | 103 ++ src/WixToolset.BuildTasks/heatfile.cs | 95 ++ src/WixToolset.BuildTasks/heatproject.cs | 108 ++ src/WixToolset.BuildTasks/heattask.cs | 121 ++ src/WixToolset.BuildTasks/redirect.wix.ca.targets | 11 + src/WixToolset.BuildTasks/redirect.wix.targets | 11 + src/WixToolset.BuildTasks/wix.ca.targets | 123 ++ src/WixToolset.BuildTasks/wix.harvest.targets | 511 ++++++++ src/WixToolset.BuildTasks/wix.signing.targets | 378 ++++++ src/WixToolset.BuildTasks/wix.targets | 1353 ++++++++++++++++++++ 38 files changed, 6985 insertions(+) create mode 100644 src/WixToolset.BuildTasks/AssemblyInfo.cs create mode 100644 src/WixToolset.BuildTasks/BuildException.cs create mode 100644 src/WixToolset.BuildTasks/Candle.cs create mode 100644 src/WixToolset.BuildTasks/Common.cs create mode 100644 src/WixToolset.BuildTasks/ConvertReferences.cs create mode 100644 src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs create mode 100644 src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs create mode 100644 src/WixToolset.BuildTasks/DoIt-Compile.cs create mode 100644 src/WixToolset.BuildTasks/DoIt.cs create mode 100644 src/WixToolset.BuildTasks/FileSearchHelperMethods.cs create mode 100644 src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs create mode 100644 src/WixToolset.BuildTasks/GetCabList.cs create mode 100644 src/WixToolset.BuildTasks/GetLooseFileList.cs create mode 100644 src/WixToolset.BuildTasks/GlobalSuppressions.cs create mode 100644 src/WixToolset.BuildTasks/Insignia.cs create mode 100644 src/WixToolset.BuildTasks/Light.cs create mode 100644 src/WixToolset.BuildTasks/Lit.cs create mode 100644 src/WixToolset.BuildTasks/Pyro.cs create mode 100644 src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs create mode 100644 src/WixToolset.BuildTasks/RefreshGeneratedFile.cs create mode 100644 src/WixToolset.BuildTasks/ReplaceString.cs create mode 100644 src/WixToolset.BuildTasks/ResolveWixReferences.cs create mode 100644 src/WixToolset.BuildTasks/TaskBase.cs create mode 100644 src/WixToolset.BuildTasks/Torch.cs create mode 100644 src/WixToolset.BuildTasks/WixAssignCulture.cs create mode 100644 src/WixToolset.BuildTasks/WixCommandLineBuilder.cs create mode 100644 src/WixToolset.BuildTasks/WixToolTask.cs create mode 100644 src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj create mode 100644 src/WixToolset.BuildTasks/heatdirectory.cs create mode 100644 src/WixToolset.BuildTasks/heatfile.cs create mode 100644 src/WixToolset.BuildTasks/heatproject.cs create mode 100644 src/WixToolset.BuildTasks/heattask.cs create mode 100644 src/WixToolset.BuildTasks/redirect.wix.ca.targets create mode 100644 src/WixToolset.BuildTasks/redirect.wix.targets create mode 100644 src/WixToolset.BuildTasks/wix.ca.targets create mode 100644 src/WixToolset.BuildTasks/wix.harvest.targets create mode 100644 src/WixToolset.BuildTasks/wix.signing.targets create mode 100644 src/WixToolset.BuildTasks/wix.targets (limited to 'src/WixToolset.BuildTasks') diff --git a/src/WixToolset.BuildTasks/AssemblyInfo.cs b/src/WixToolset.BuildTasks/AssemblyInfo.cs new file mode 100644 index 00000000..ae52fce8 --- /dev/null +++ b/src/WixToolset.BuildTasks/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/src/WixToolset.BuildTasks/BuildException.cs b/src/WixToolset.BuildTasks/BuildException.cs new file mode 100644 index 00000000..953134ba --- /dev/null +++ b/src/WixToolset.BuildTasks/BuildException.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Globalization; + + class BuildException : Exception + { + public BuildException() + { + } + + public BuildException(string message) : base(message) + { + } + + public BuildException(string message, Exception innerException) : base(message, innerException) + { + } + + public BuildException(string format, params string[] args) : this(String.Format(CultureInfo.CurrentCulture, format, args)) + { + } + } +} diff --git a/src/WixToolset.BuildTasks/Candle.cs b/src/WixToolset.BuildTasks/Candle.cs new file mode 100644 index 00000000..82b15838 --- /dev/null +++ b/src/WixToolset.BuildTasks/Candle.cs @@ -0,0 +1,199 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// An MSBuild task to run the WiX compiler. + /// + public sealed class CandleOld : WixToolTask + { + private const string CandleToolName = "candle.exe"; + + private string[] defineConstants; + private ITaskItem[] extensions; + private string[] includeSearchPaths; + private ITaskItem outputFile; + private bool pedantic; + private string installerPlatform; + private string preprocessToFile; + private bool preprocessToStdOut; + private ITaskItem[] sourceFiles; + private string extensionDirectory; + private string[] referencePaths; + + public string[] DefineConstants + { + get { return this.defineConstants; } + set { this.defineConstants = value; } + } + + public ITaskItem[] Extensions + { + get { return this.extensions; } + set { this.extensions = value; } + } + + public string[] IncludeSearchPaths + { + get { return this.includeSearchPaths; } + set { this.includeSearchPaths = value; } + } + + public string InstallerPlatform + { + get { return this.installerPlatform; } + set { this.installerPlatform = value; } + } + + [Output] + [Required] + public ITaskItem OutputFile + { + get { return this.outputFile; } + set { this.outputFile = value; } + } + + public bool Pedantic + { + get { return this.pedantic; } + set { this.pedantic = value; } + } + + public string PreprocessToFile + { + get { return this.preprocessToFile; } + set { this.preprocessToFile = value; } + } + + public bool PreprocessToStdOut + { + get { return this.preprocessToStdOut; } + set { this.preprocessToStdOut = value; } + } + + [Required] + public ITaskItem[] SourceFiles + { + get { return this.sourceFiles; } + set { this.sourceFiles = value; } + } + + public string ExtensionDirectory + { + get { return this.extensionDirectory; } + set { this.extensionDirectory = value; } + } + + public string[] ReferencePaths + { + get { return this.referencePaths; } + set { this.referencePaths = value; } + } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of candle.exe. + /// The name of the executable. + protected override string ToolName + { + get { return CandleToolName; } + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply candle.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return CandleToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), CandleToolName); + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendIfTrue("-p", this.PreprocessToStdOut); + commandLineBuilder.AppendSwitchIfNotNull("-p", this.PreprocessToFile); + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendArrayIfNotNull("-d", this.DefineConstants); + commandLineBuilder.AppendArrayIfNotNull("-I", this.IncludeSearchPaths); + commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic); + commandLineBuilder.AppendSwitchIfNotNull("-arch ", this.InstallerPlatform); + commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + + // Support per-source-file output by looking at the SourceFiles items to + // see if there is any "CandleOutput" metadata. If there is, we do our own + // appending, otherwise we fall back to the built-in "append file names" code. + // Note also that the wix.targets "Compile" target does *not* automagically + // fix the "@(CompileObjOutput)" list to include these new output names. + // If you really want to use this, you're going to have to clone the target + // in your own .targets file and create the output list yourself. + bool usePerSourceOutput = false; + if (this.SourceFiles != null) + { + foreach (ITaskItem item in this.SourceFiles) + { + if (!String.IsNullOrEmpty(item.GetMetadata("CandleOutput"))) + { + usePerSourceOutput = true; + break; + } + } + } + + if (usePerSourceOutput) + { + string[] newSourceNames = new string[this.SourceFiles.Length]; + for (int iSource = 0; iSource < this.SourceFiles.Length; ++iSource) + { + ITaskItem item = this.SourceFiles[iSource]; + if (null == item) + { + newSourceNames[iSource] = null; + } + else + { + string output = item.GetMetadata("CandleOutput"); + + if (!String.IsNullOrEmpty(output)) + { + newSourceNames[iSource] = String.Concat(item.ItemSpec, ";", output); + } + else + { + newSourceNames[iSource] = item.ItemSpec; + } + } + } + + commandLineBuilder.AppendFileNamesIfNotNull(newSourceNames, " "); + } + else + { + commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " "); + } + } + } +} diff --git a/src/WixToolset.BuildTasks/Common.cs b/src/WixToolset.BuildTasks/Common.cs new file mode 100644 index 00000000..803e9d14 --- /dev/null +++ b/src/WixToolset.BuildTasks/Common.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset +{ + using System; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + + /// + /// Common WixTasks utility methods and types. + /// + internal static class Common + { + /// Metadata key name to turn off harvesting of project references. + public const string DoNotHarvest = "DoNotHarvest"; + + private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); + private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters + + /// + /// Return an identifier based on passed file/directory name + /// + /// File/directory name to generate identifer from + /// A version of the name that is a legal identifier. + /// This is duplicated from WiX's Common class. + internal static string GetIdentifierFromName(string name) + { + string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". + + // MSI identifiers must begin with an alphabetic character or an + // underscore. Prefix all other values with an underscore. + if (AddPrefix.IsMatch(name)) + { + result = String.Concat("_", result); + } + + return result; + } + } +} diff --git a/src/WixToolset.BuildTasks/ConvertReferences.cs b/src/WixToolset.BuildTasks/ConvertReferences.cs new file mode 100644 index 00000000..fe137633 --- /dev/null +++ b/src/WixToolset.BuildTasks/ConvertReferences.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task assigns Culture metadata to files based on the value of the Culture attribute on the + /// WixLocalization element inside the file. + /// + public class ConvertReferences : Task + { + private string projectOutputGroups; + private ITaskItem[] projectReferences; + private ITaskItem[] harvestItems; + + /// + /// The total list of cabs in this database + /// + [Output] + public ITaskItem[] HarvestItems + { + get { return this.harvestItems; } + } + + /// + /// The project output groups to harvest. + /// + [Required] + public string ProjectOutputGroups + { + get { return this.projectOutputGroups; } + set { this.projectOutputGroups = value; } + } + + /// + /// All the project references in the project. + /// + [Required] + public ITaskItem[] ProjectReferences + { + get { return this.projectReferences; } + set { this.projectReferences = value; } + } + + /// + /// Gets a complete list of external cabs referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + List newItems = new List(); + + foreach(ITaskItem item in this.ProjectReferences) + { + Dictionary newItemMetadeta = new Dictionary(); + + if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest))) + { + continue; + } + + string refTargetDir = item.GetMetadata("RefTargetDir"); + if (!String.IsNullOrEmpty(refTargetDir)) + { + newItemMetadeta.Add("DirectoryIds", refTargetDir); + } + + string refName = item.GetMetadata("Name"); + if (!String.IsNullOrEmpty(refName)) + { + newItemMetadeta.Add("ProjectName", refName); + } + + newItemMetadeta.Add("ProjectOutputGroups", this.ProjectOutputGroups); + + ITaskItem newItem = new TaskItem(item.ItemSpec, newItemMetadeta); + newItems.Add(newItem); + } + + this.harvestItems = newItems.ToArray(); + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs b/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs new file mode 100644 index 00000000..84816cac --- /dev/null +++ b/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task assigns Culture metadata to files based on the value of the Culture attribute on the + /// WixLocalization element inside the file. + /// + public class CreateItemAvoidingInference : Task + { + private string inputProperties; + private ITaskItem[] outputItems; + + /// + /// The output items. + /// + [Output] + public ITaskItem[] OuputItems + { + get { return this.outputItems; } + } + + /// + /// The properties to converty to items. + /// + [Required] + public string InputProperties + { + get { return this.inputProperties; } + set { this.inputProperties = value; } + } + + /// + /// Gets a complete list of external cabs referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + List newItems = new List(); + + foreach (string property in this.inputProperties.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + newItems.Add(new TaskItem(property)); + } + + this.outputItems = newItems.ToArray(); + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs b/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs new file mode 100644 index 00000000..5eae0850 --- /dev/null +++ b/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs @@ -0,0 +1,270 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// An MSBuild task to create a list of preprocessor defines to be passed to candle from the + /// list of referenced projects. + /// + public sealed class CreateProjectReferenceDefineConstants : Task + { + private ITaskItem[] defineConstants; + private ITaskItem[] projectConfigurations; + private ITaskItem[] projectReferencePaths; + + [Output] + public ITaskItem[] DefineConstants + { + get { return this.defineConstants; } + } + + [Required] + public ITaskItem[] ProjectReferencePaths + { + get { return this.projectReferencePaths; } + set { this.projectReferencePaths = value; } + } + + public ITaskItem[] ProjectConfigurations + { + get { return this.projectConfigurations; } + set { this.projectConfigurations = value; } + } + + public override bool Execute() + { + List outputItems = new List(); + Dictionary defineConstants = new Dictionary(); + + for (int i = 0; i < this.ProjectReferencePaths.Length; i++) + { + ITaskItem item = this.ProjectReferencePaths[i]; + + string configuration = item.GetMetadata("Configuration"); + string fullConfiguration = item.GetMetadata("FullConfiguration"); + string platform = item.GetMetadata("Platform"); + + string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i); + string projectDir = Path.GetDirectoryName(projectPath) + Path.DirectorySeparatorChar; + string projectExt = Path.GetExtension(projectPath); + string projectFileName = Path.GetFileName(projectPath); + string projectName = Path.GetFileNameWithoutExtension(projectPath); + + string referenceName = CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName); + + string targetPath = item.GetMetadata("FullPath"); + string targetDir = Path.GetDirectoryName(targetPath) + Path.DirectorySeparatorChar; + string targetExt = Path.GetExtension(targetPath); + string targetFileName = Path.GetFileName(targetPath); + string targetName = Path.GetFileNameWithoutExtension(targetPath); + + // If there is no configuration metadata on the project reference task item, + // check for any additional configuration data provided in the optional task property. + if (String.IsNullOrEmpty(fullConfiguration)) + { + fullConfiguration = this.FindProjectConfiguration(projectName); + if (!String.IsNullOrEmpty(fullConfiguration)) + { + string[] typeAndPlatform = fullConfiguration.Split('|'); + configuration = typeAndPlatform[0]; + platform = (typeAndPlatform.Length > 1 ? typeAndPlatform[1] : String.Empty); + } + } + + // write out the platform/configuration defines + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.Configuration", referenceName)] = configuration; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.FullConfiguration", referenceName)] = fullConfiguration; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.Platform", referenceName)] = platform; + + // write out the ProjectX defines + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectDir", referenceName)] = projectDir; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectExt", referenceName)] = projectExt; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectFileName", referenceName)] = projectFileName; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectName", referenceName)] = projectName; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectPath", referenceName)] = projectPath; + + // write out the TargetX defines + string targetDirDefine = String.Format(CultureInfo.InvariantCulture, "{0}.TargetDir", referenceName); + if (defineConstants.ContainsKey(targetDirDefine)) + { + //if target dir was already defined, redefine it as the common root shared by multiple references from the same project + string commonDir = FindCommonRoot(targetDir, defineConstants[targetDirDefine]); + if (!String.IsNullOrEmpty(commonDir)) + { + targetDir = commonDir; + } + } + defineConstants[targetDirDefine] = CreateProjectReferenceDefineConstants.EnsureEndsWithBackslash(targetDir); + + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetExt", referenceName)] = targetExt; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetFileName", referenceName)] = targetFileName; + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetName", referenceName)] = targetName; + + //if target path was already defined, append to it creating a list of multiple references from the same project + string targetPathDefine = String.Format(CultureInfo.InvariantCulture, "{0}.TargetPath", referenceName); + if (defineConstants.ContainsKey(targetPathDefine)) + { + string oldTargetPath = defineConstants[targetPathDefine]; + if (!targetPath.Equals(oldTargetPath, StringComparison.OrdinalIgnoreCase)) + { + defineConstants[targetPathDefine] += ";" + targetPath; + } + + //If there was only one targetpath we need to create its culture specific define + if (!oldTargetPath.Contains(";")) + { + string oldSubFolder = FindSubfolder(oldTargetPath, targetDir, targetFileName); + if (!String.IsNullOrEmpty(oldSubFolder)) + { + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.{1}.TargetPath", referenceName, oldSubFolder.Replace('\\', '_'))] = oldTargetPath; + } + } + + // Create a culture specific define + string subFolder = FindSubfolder(targetPath, targetDir, targetFileName); + if (!String.IsNullOrEmpty(subFolder)) + { + defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.{1}.TargetPath", referenceName, subFolder.Replace('\\', '_'))] = targetPath; + } + + } + else + { + defineConstants[targetPathDefine] = targetPath; + } + } + + foreach (KeyValuePair define in defineConstants) + { + outputItems.Add(new TaskItem(String.Format(CultureInfo.InvariantCulture, "{0}={1}", define.Key, define.Value))); + } + this.defineConstants = outputItems.ToArray(); + + return true; + } + + public static string GetProjectPath(ITaskItem[] projectReferencePaths, int i) + { + return projectReferencePaths[i].GetMetadata("MSBuildSourceProjectFile"); + } + + public static string GetReferenceName(ITaskItem item, string projectName) + { + string referenceName = item.GetMetadata("Name"); + if (String.IsNullOrEmpty(referenceName)) + { + referenceName = projectName; + } + + // We cannot have an equals sign in the variable name because it + // messes with the preprocessor definitions on the command line. + referenceName = referenceName.Replace('=', '_'); + + // We cannot have a double quote on the command line because it + // there is no way to escape it on the command line. + referenceName = referenceName.Replace('\"', '_'); + + // We cannot have parens in the variable name because the WiX + // preprocessor will not be able to parse it. + referenceName = referenceName.Replace('(', '_'); + referenceName = referenceName.Replace(')', '_'); + + return referenceName; + } + + /// + /// Look through the configuration data in the ProjectConfigurations property + /// to find the configuration for a project, if available. + /// + /// Name of the project that is being searched for. + /// Full configuration spec, for example "Release|Win32". + private string FindProjectConfiguration(string projectName) + { + string configuration = String.Empty; + + if (this.ProjectConfigurations != null) + { + foreach (ITaskItem configItem in this.ProjectConfigurations) + { + string configProject = configItem.ItemSpec; + if (configProject.Length > projectName.Length && + configProject.StartsWith(projectName) && + configProject[projectName.Length] == '=') + { + configuration = configProject.Substring(projectName.Length + 1); + break; + } + } + } + + return configuration; + } + + /// + /// Finds the common root between two paths + /// + /// + /// + /// common root on success, empty string on failure + private static string FindCommonRoot(string path1, string path2) + { + path1 = path1.TrimEnd(Path.DirectorySeparatorChar); + path2 = path2.TrimEnd(Path.DirectorySeparatorChar); + + while (!String.IsNullOrEmpty(path1)) + { + for (string searchPath = path2; !String.IsNullOrEmpty(searchPath); searchPath = Path.GetDirectoryName(searchPath)) + { + if (path1.Equals(searchPath, StringComparison.OrdinalIgnoreCase)) + { + return searchPath; + } + } + + path1 = Path.GetDirectoryName(path1); + } + + return path1; + } + + /// + /// Finds the subfolder of a path, excluding a root and filename. + /// + /// Path to examine + /// Root that must be present + /// + /// + private static string FindSubfolder(string path, string rootPath, string fileName) + { + if (Path.GetFileName(path).Equals(fileName, StringComparison.OrdinalIgnoreCase)) + { + path = Path.GetDirectoryName(path); + } + + if (path.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) + { + // cut out the root and return the subpath + return path.Substring(rootPath.Length).Trim(Path.DirectorySeparatorChar); + } + + return String.Empty; + } + + private static string EnsureEndsWithBackslash(string dir) + { + if (dir[dir.Length - 1] != Path.DirectorySeparatorChar) + { + dir += Path.DirectorySeparatorChar; + } + + return dir; + } + } +} diff --git a/src/WixToolset.BuildTasks/DoIt-Compile.cs b/src/WixToolset.BuildTasks/DoIt-Compile.cs new file mode 100644 index 00000000..f89078fe --- /dev/null +++ b/src/WixToolset.BuildTasks/DoIt-Compile.cs @@ -0,0 +1,192 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#if false +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.IO; + using Microsoft.Build.Framework; + using WixToolset.Data; + + /// + /// An MSBuild task to run the WiX compiler. + /// + public sealed class Candle : TaskBase + { + public string[] DefineConstants { get; set; } + + public ITaskItem[] Extensions { get; set; } + + public string[] IncludeSearchPaths { get; set; } + + public string InstallerPlatform { get; set; } + + [Output] + [Required] + public ITaskItem OutputFile { get; set; } + + public bool Pedantic { get; set; } + + public string PreprocessToFile { get; set; } + + public bool PreprocessToStdOut { get; set; } + + [Required] + public ITaskItem IntermediateDirectory { get; set; } + + [Required] + public ITaskItem[] SourceFiles { get; set; } + + public string ExtensionDirectory { get; set; } + + public string[] ReferencePaths { get; set; } + + protected override void ExecuteCore() + { + Messaging.Instance.InitializeAppName("WIX", "wix.exe"); + + Messaging.Instance.Display += this.DisplayMessage; + + var preprocessor = new Preprocessor(); + + var compiler = new Compiler(); + + var sourceFiles = this.GatherSourceFiles(); + + var preprocessorVariables = this.GatherPreprocessorVariables(); + + foreach (var sourceFile in sourceFiles) + { + var document = preprocessor.Process(sourceFile.SourcePath, preprocessorVariables); + + var intermediate = compiler.Compile(document); + + intermediate.Save(sourceFile.OutputPath); + } + } + + private void DisplayMessage(object sender, DisplayEventArgs e) + { + this.Log.LogMessageFromText(e.Message, MessageImportance.Normal); + } + + private IEnumerable GatherSourceFiles() + { + var files = new List(); + + foreach (var item in this.SourceFiles) + { + var sourcePath = item.ItemSpec; + var outputPath = item.GetMetadata("CandleOutput") ?? this.OutputFile?.ItemSpec; + + if (String.IsNullOrEmpty(outputPath)) + { + outputPath = Path.Combine(this.IntermediateDirectory.ItemSpec, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); + } + + files.Add(new SourceFile(sourcePath, outputPath)); + } + + return files; + } + + private IDictionary GatherPreprocessorVariables() + { + var variables = new Dictionary(); + + foreach (var pair in this.DefineConstants) + { + string[] value = pair.Split(new[] { '=' }, 2); + + if (variables.ContainsKey(value[0])) + { + //Messaging.Instance.OnMessage(WixErrors.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], this.PreprocessorVariables[value[0]])); + break; + } + + if (1 == value.Length) + { + variables.Add(value[0], String.Empty); + } + else + { + variables.Add(value[0], value[1]); + } + } + + return variables; + } + + ///// + ///// Builds a command line from options in this task. + ///// + //protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + //{ + // base.BuildCommandLine(commandLineBuilder); + + // commandLineBuilder.AppendIfTrue("-p", this.PreprocessToStdOut); + // commandLineBuilder.AppendSwitchIfNotNull("-p", this.PreprocessToFile); + // commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + // commandLineBuilder.AppendArrayIfNotNull("-d", this.DefineConstants); + // commandLineBuilder.AppendArrayIfNotNull("-I", this.IncludeSearchPaths); + // commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic); + // commandLineBuilder.AppendSwitchIfNotNull("-arch ", this.InstallerPlatform); + // commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths); + // commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + + // // Support per-source-file output by looking at the SourceFiles items to + // // see if there is any "CandleOutput" metadata. If there is, we do our own + // // appending, otherwise we fall back to the built-in "append file names" code. + // // Note also that the wix.targets "Compile" target does *not* automagically + // // fix the "@(CompileObjOutput)" list to include these new output names. + // // If you really want to use this, you're going to have to clone the target + // // in your own .targets file and create the output list yourself. + // bool usePerSourceOutput = false; + // if (this.SourceFiles != null) + // { + // foreach (ITaskItem item in this.SourceFiles) + // { + // if (!String.IsNullOrEmpty(item.GetMetadata("CandleOutput"))) + // { + // usePerSourceOutput = true; + // break; + // } + // } + // } + + // if (usePerSourceOutput) + // { + // string[] newSourceNames = new string[this.SourceFiles.Length]; + // for (int iSource = 0; iSource < this.SourceFiles.Length; ++iSource) + // { + // ITaskItem item = this.SourceFiles[iSource]; + // if (null == item) + // { + // newSourceNames[iSource] = null; + // } + // else + // { + // string output = item.GetMetadata("CandleOutput"); + + // if (!String.IsNullOrEmpty(output)) + // { + // newSourceNames[iSource] = String.Concat(item.ItemSpec, ";", output); + // } + // else + // { + // newSourceNames[iSource] = item.ItemSpec; + // } + // } + // } + + // commandLineBuilder.AppendFileNamesIfNotNull(newSourceNames, " "); + // } + // else + // { + // commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " "); + // } + //} + } +} +#endif diff --git a/src/WixToolset.BuildTasks/DoIt.cs b/src/WixToolset.BuildTasks/DoIt.cs new file mode 100644 index 00000000..7688342c --- /dev/null +++ b/src/WixToolset.BuildTasks/DoIt.cs @@ -0,0 +1,233 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using WixToolset.Core; + using WixToolset.Data; + + /// + /// An MSBuild task to run the WiX compiler. + /// + public sealed class DoIt : Task + { + public DoIt() + { + Messaging.Instance.InitializeAppName("WIX", "wix.exe"); + + Messaging.Instance.Display += this.DisplayMessage; + } + + public string AdditionalOptions { get; set; } + + public string Cultures { get; set; } + + public string[] DefineConstants { get; set; } + + public ITaskItem[] Extensions { get; set; } + + public string ExtensionDirectory { get; set; } + + public string[] IncludeSearchPaths { get; set; } + + public string InstallerPlatform { get; set; } + + [Required] + public ITaskItem IntermediateDirectory { get; set; } + + public ITaskItem[] LocalizationFiles { get; set; } + + public bool NoLogo { get; set; } + + public ITaskItem[] ObjectFiles { get; set; } + + [Output] + [Required] + public ITaskItem OutputFile { get; set; } + + public string PdbOutputFile { get; set; } + + public bool Pedantic { get; set; } + + [Required] + public ITaskItem[] SourceFiles { get; set; } + + public string[] ReferencePaths { get; set; } + + + /// + /// Gets or sets whether all warnings should be suppressed. + /// + public bool SuppressAllWarnings { get; set; } + + /// + /// Gets or sets a list of specific warnings to be suppressed. + /// + public string[] SuppressSpecificWarnings { get; set; } + + /// + /// Gets or sets whether all warnings should be treated as errors. + /// + public bool TreatWarningsAsErrors { get; set; } + + /// + /// Gets or sets a list of specific warnings to treat as errors. + /// + public string[] TreatSpecificWarningsAsErrors { get; set; } + + /// + /// Gets or sets whether to display verbose output. + /// + public bool VerboseOutput { get; set; } + + + public ITaskItem[] BindInputPaths { get; set; } + public bool BindFiles { get; set; } + public ITaskItem BindContentsFile{ get; set; } + public ITaskItem BindOutputsFile { get; set; } + public ITaskItem BindBuiltOutputsFile { get; set; } + + public string CabinetCachePath { get; set; } + public int CabinetCreationThreadCount { get; set; } + public string DefaultCompressionLevel { get; set; } + + [Output] + public ITaskItem UnreferencedSymbolsFile { get; set; } + + public ITaskItem WixProjectFile { get; set; } + public string[] WixVariables { get; set; } + + public bool SuppressValidation { get; set; } + public string[] SuppressIces { get; set; } + public string AdditionalCub { get; set; } + + + + public override bool Execute() + { + try + { + this.ExecuteCore(); + } + catch (BuildException e) + { + this.Log.LogErrorFromException(e); + } + catch (WixException e) + { + this.Log.LogErrorFromException(e); + } + + return !this.Log.HasLoggedErrors; + } + + private void ExecuteCore() + { + var commandLineBuilder = new WixCommandLineBuilder(); + + commandLineBuilder.AppendTextUnquoted("build"); + + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendSwitchIfNotNull("-cultures ", this.Cultures); + commandLineBuilder.AppendArrayIfNotNull("-d ", this.DefineConstants); + commandLineBuilder.AppendArrayIfNotNull("-I ", this.IncludeSearchPaths); + commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths); + commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo); + commandLineBuilder.AppendIfTrue("-sval", this.SuppressValidation); + commandLineBuilder.AppendArrayIfNotNull("-sice ", this.SuppressIces); + commandLineBuilder.AppendSwitchIfNotNull("-usf ", this.UnreferencedSymbolsFile); + commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath); + commandLineBuilder.AppendSwitchIfNotNull("-contentsfile ", this.BindContentsFile); + commandLineBuilder.AppendSwitchIfNotNull("-outputsfile ", this.BindOutputsFile); + commandLineBuilder.AppendSwitchIfNotNull("-builtoutputsfile ", this.BindBuiltOutputsFile); + commandLineBuilder.AppendSwitchIfNotNull("-wixprojectfile ", this.WixProjectFile); + commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions); + + commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles); + commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " "); + + var commandLineString = commandLineBuilder.ToString(); + + this.Log.LogMessage(MessageImportance.Normal, commandLineString); + + var command = CommandLine.ParseStandardCommandLine(commandLineString); + command?.Execute(); + } + + private void DisplayMessage(object sender, DisplayEventArgs e) + { + this.Log.LogMessageFromText(e.Message, MessageImportance.Normal); + } + + ///// + ///// Builds a command line from options in this task. + ///// + //protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + //{ + // base.BuildCommandLine(commandLineBuilder); + + // commandLineBuilder.AppendIfTrue("-p", this.PreprocessToStdOut); + // commandLineBuilder.AppendSwitchIfNotNull("-p", this.PreprocessToFile); + // commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + // commandLineBuilder.AppendArrayIfNotNull("-d", this.DefineConstants); + // commandLineBuilder.AppendArrayIfNotNull("-I", this.IncludeSearchPaths); + // commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic); + // commandLineBuilder.AppendSwitchIfNotNull("-arch ", this.InstallerPlatform); + // commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths); + // commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + + // // Support per-source-file output by looking at the SourceFiles items to + // // see if there is any "CandleOutput" metadata. If there is, we do our own + // // appending, otherwise we fall back to the built-in "append file names" code. + // // Note also that the wix.targets "Compile" target does *not* automagically + // // fix the "@(CompileObjOutput)" list to include these new output names. + // // If you really want to use this, you're going to have to clone the target + // // in your own .targets file and create the output list yourself. + // bool usePerSourceOutput = false; + // if (this.SourceFiles != null) + // { + // foreach (ITaskItem item in this.SourceFiles) + // { + // if (!String.IsNullOrEmpty(item.GetMetadata("CandleOutput"))) + // { + // usePerSourceOutput = true; + // break; + // } + // } + // } + + // if (usePerSourceOutput) + // { + // string[] newSourceNames = new string[this.SourceFiles.Length]; + // for (int iSource = 0; iSource < this.SourceFiles.Length; ++iSource) + // { + // ITaskItem item = this.SourceFiles[iSource]; + // if (null == item) + // { + // newSourceNames[iSource] = null; + // } + // else + // { + // string output = item.GetMetadata("CandleOutput"); + + // if (!String.IsNullOrEmpty(output)) + // { + // newSourceNames[iSource] = String.Concat(item.ItemSpec, ";", output); + // } + // else + // { + // newSourceNames[iSource] = item.ItemSpec; + // } + // } + // } + + // commandLineBuilder.AppendFileNamesIfNotNull(newSourceNames, " "); + // } + // else + // { + // commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " "); + // } + //} + } +} diff --git a/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs b/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs new file mode 100644 index 00000000..6cc804eb --- /dev/null +++ b/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text; + using Microsoft.Build.Framework; + + /// + /// Contains helper methods on searching for files + /// + public static class FileSearchHelperMethods + { + /// + /// Searches for the existence of a file in multiple directories. + /// Search is satisfied if default file path is valid and exists. If not, + /// file name is extracted from default path and combined with each of the directories + /// looking to see if it exists. If not found, input default path is returned. + /// + /// Array of directories to look in, without filenames in them + /// Default path - to use if not found + /// File path if file found. Empty string if not found + public static string SearchFilePaths(string[] directories, string defaultFullPath) + { + if (String.IsNullOrEmpty(defaultFullPath)) + { + return String.Empty; + } + + if (File.Exists(defaultFullPath)) + { + return defaultFullPath; + } + + if (directories == null) + { + return string.Empty; + } + + string fileName = Path.GetFileName(defaultFullPath); + foreach (string currentPath in directories) + { + if (String.IsNullOrEmpty(currentPath) || String.IsNullOrEmpty(currentPath.Trim())) + { + continue; + } + + if (File.Exists(Path.Combine(currentPath, fileName))) + { + return Path.Combine(currentPath, fileName); + } + } + + return String.Empty; + } + } +} diff --git a/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs b/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs new file mode 100644 index 00000000..06c8b98a --- /dev/null +++ b/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task generates metadata on the for compile output objects. + /// + public class GenerateCompileWithObjectPath : Task + { + /// + /// The list of files to generate outputs for. + /// + [Required] + public ITaskItem[] Compile + { + get; + set; + } + + /// + /// The list of files with ObjectPath metadata. + /// + [Output] + public ITaskItem[] CompileWithObjectPath + { + get; + private set; + } + + /// + /// The folder under which all ObjectPaths should reside. + /// + [Required] + public string IntermediateOutputPath + { + get; + set; + } + + /// + /// Generate an identifier by hashing data from the row. + /// + /// Three letter or less prefix for generated row identifier. + /// Information to hash. + /// The generated identifier. + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] + public static string GenerateIdentifier(string prefix, params string[] args) + { + string stringData = String.Join("|", args); + byte[] data = Encoding.Unicode.GetBytes(stringData); + + // hash the data + byte[] hash; + + using (MD5 md5 = new MD5CryptoServiceProvider()) + { + hash = md5.ComputeHash(data); + } + + // build up the identifier + StringBuilder identifier = new StringBuilder(35, 35); + identifier.Append(prefix); + + // hard coded to 16 as that is the most bytes that can be used to meet the length requirements. SHA1 is 20 bytes. + for (int i = 0; i < 16; i++) + { + identifier.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture.NumberFormat)); + } + + return identifier.ToString(); + } + + /// + /// Gets the full path of the directory in which the file is found. + /// + /// The file from which to extract the directory. + /// The generated identifier. + private static string GetDirectory(ITaskItem file) + { + return file.GetMetadata("RootDir") + file.GetMetadata("Directory"); + } + + /// + /// Sets the object path to use for the file. + /// + /// The file on which to set the ObjectPath metadata. + /// + /// For the same input path it will return the same ObjectPath. Case is not ignored, however that isn't a problem. + /// + private void SetObjectPath(ITaskItem file) + { + // If the source file is in the project directory or in the intermediate directory, use the intermediate directory. + if (string.IsNullOrEmpty(file.GetMetadata("RelativeDir")) || string.Compare(file.GetMetadata("RelativeDir"), this.IntermediateOutputPath, StringComparison.OrdinalIgnoreCase) == 0) + { + file.SetMetadata("ObjectPath", this.IntermediateOutputPath); + } + // Otherwise use a subdirectory of the intermediate directory. The subfolder's name is based on the full path of the folder containing the source file. + else + { + file.SetMetadata("ObjectPath", Path.Combine(this.IntermediateOutputPath, GenerateIdentifier("pth", GetDirectory(file))) + Path.DirectorySeparatorChar); + } + } + + /// + /// Gets a complete list of external cabs referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + if (string.IsNullOrEmpty(this.IntermediateOutputPath)) + { + this.Log.LogError("IntermediateOutputPath parameter is required and cannot be empty"); + return false; + } + + if (this.Compile == null || this.Compile.Length == 0) + { + return true; + } + + this.CompileWithObjectPath = new ITaskItem[this.Compile.Length]; + for (int i = 0; i < this.Compile.Length; ++i) + { + this.CompileWithObjectPath[i] = new TaskItem(this.Compile[i].ItemSpec, this.Compile[i].CloneCustomMetadata()); + + // Do not overwrite the ObjectPath metadata if it already was set. + if (string.IsNullOrEmpty(this.CompileWithObjectPath[i].GetMetadata("ObjectPath"))) + { + SetObjectPath(this.CompileWithObjectPath[i]); + } + } + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/GetCabList.cs b/src/WixToolset.BuildTasks/GetCabList.cs new file mode 100644 index 00000000..e97538af --- /dev/null +++ b/src/WixToolset.BuildTasks/GetCabList.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Reflection; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using WixToolset.Dtf.WindowsInstaller; + using Microsoft.Win32; + + /// + /// This task assigns Culture metadata to files based on the value of the Culture attribute on the + /// WixLocalization element inside the file. + /// + public class GetCabList : Task + { + private ITaskItem database; + private ITaskItem[] cabList; + + /// + /// The list of database files to find cabs in + /// + [Required] + public ITaskItem Database + { + get { return this.database; } + set { this.database = value; } + } + + /// + /// The total list of cabs in this database + /// + [Output] + public ITaskItem[] CabList + { + get { return this.cabList; } + } + + /// + /// Gets a complete list of external cabs referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + string databaseFile = this.database.ItemSpec; + Object []args = { }; + System.Collections.Generic.List cabNames = new System.Collections.Generic.List(); + + // If the file doesn't exist, no cabs to return, so exit now + if (!File.Exists(databaseFile)) + { + return true; + } + + using (Database database = new Database(databaseFile)) + { + // If the media table doesn't exist, no cabs to return, so exit now + if (null == database.Tables["Media"]) + { + return true; + } + + System.Collections.IList records = database.ExecuteQuery("SELECT `Cabinet` FROM `Media`", args); + + foreach (string cabName in records) + { + if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + cabNames.Add(new TaskItem(Path.Combine(Path.GetDirectoryName(databaseFile), cabName))); + } + } + + this.cabList = cabNames.ToArray(); + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/GetLooseFileList.cs b/src/WixToolset.BuildTasks/GetLooseFileList.cs new file mode 100644 index 00000000..bd403426 --- /dev/null +++ b/src/WixToolset.BuildTasks/GetLooseFileList.cs @@ -0,0 +1,230 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Reflection; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using WixToolset.Dtf.WindowsInstaller; + using Microsoft.Win32; + + /// + /// This task assigns Culture metadata to files based on the value of the Culture attribute on the + /// WixLocalization element inside the file. + /// + public class GetLooseFileList : Task + { + private ITaskItem database; + private ITaskItem[] looseFileList; + + internal const int MsidbFileAttributesNoncompressed = 8192; + internal const int MsidbFileAttributesCompressed = 16384; + + /// + /// The list of database files to find Loose Files in + /// + [Required] + public ITaskItem Database + { + get { return this.database; } + set { this.database = value; } + } + + /// + /// The total list of Loose Files in this database + /// + [Output] + public ITaskItem[] LooseFileList + { + get { return this.looseFileList; } + } + + /// + /// Takes the "defaultDir" column + /// + /// Returns the corresponding sourceDir. + public string SourceDirFromDefaultDir(string defaultDir) + { + string sourceDir; + + string[] splitted = defaultDir.Split(':'); + + if (1 == splitted.Length) + { + sourceDir = splitted[0]; + } + else + { + sourceDir = splitted[1]; + } + + splitted = sourceDir.Split('|'); + + if (1 == splitted.Length) + { + sourceDir = splitted[0]; + } + else + { + sourceDir = splitted[1]; + } + + return sourceDir; + } + + /// + /// Takes the "FileName" column + /// + /// Returns the corresponding source file name. + public string SourceFileFromFileName(string fileName) + { + string sourceFile; + + string[] splitted = fileName.Split('|'); + + if (1 == splitted.Length) + { + sourceFile = splitted[0]; + } + else + { + sourceFile = splitted[1]; + } + + return sourceFile; + } + + /// + /// Gets a complete list of external Loose Files referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + string databaseFile = this.database.ItemSpec; + Object []emptyArgs = { }; + System.Collections.Generic.List looseFileNames = new System.Collections.Generic.List(); + Dictionary ComponentFullDirectory = new Dictionary(); + Dictionary DirectoryIdDefaultDir = new Dictionary(); + Dictionary DirectoryIdParent = new Dictionary(); + Dictionary DirectoryIdFullSource = new Dictionary(); + int i; + string databaseDir = Path.GetDirectoryName(databaseFile); + + // If the file doesn't exist, no Loose Files to return, so exit now + if (!File.Exists(databaseFile)) + { + return true; + } + + using (Database database = new Database(databaseFile)) + { + bool compressed = false; + if (2 == (database.SummaryInfo.WordCount & 2)) + { + compressed = true; + } + + // If the media table doesn't exist, no Loose Files to return, so exit now + if (null == database.Tables["File"]) + { + return true; + } + + // Only setup all these helpful indexes if the database is marked as uncompressed. If it's marked as compressed, files are stored at the root, + // so none of these indexes will be used + if (!compressed) + { + if (null == database.Tables["Directory"] || null == database.Tables["Component"]) + { + return true; + } + + System.Collections.IList directoryRecords = database.ExecuteQuery("SELECT `Directory`,`Directory_Parent`,`DefaultDir` FROM `Directory`", emptyArgs); + + // First setup a simple index from DirectoryId to DefaultDir + for (i = 0; i < directoryRecords.Count; i += 3) + { + string directoryId = (string)(directoryRecords[i]); + string directoryParent = (string)(directoryRecords[i + 1]); + string defaultDir = (string)(directoryRecords[i + 2]); + + string sourceDir = SourceDirFromDefaultDir(defaultDir); + + DirectoryIdDefaultDir[directoryId] = sourceDir; + DirectoryIdParent[directoryId] = directoryParent; + } + + // Setup an index from directory Id to the full source path + for (i = 0; i < directoryRecords.Count; i += 3) + { + string directoryId = (string)(directoryRecords[i]); + string directoryParent = (string)(directoryRecords[i + 1]); + string defaultDir = (string)(directoryRecords[i + 2]); + + string sourceDir = DirectoryIdDefaultDir[directoryId]; + + // The TARGETDIR case + if (String.IsNullOrEmpty(directoryParent)) + { + DirectoryIdFullSource[directoryId] = databaseDir; + } + else + { + string tempDirectoryParent = directoryParent; + + while (!String.IsNullOrEmpty(tempDirectoryParent) && !String.IsNullOrEmpty(DirectoryIdParent[tempDirectoryParent])) + { + sourceDir = Path.Combine(DirectoryIdDefaultDir[tempDirectoryParent], sourceDir); + + tempDirectoryParent = DirectoryIdParent[tempDirectoryParent]; + } + + DirectoryIdFullSource[directoryId] = Path.Combine(databaseDir, sourceDir); + } + } + + // Setup an index from component Id to full directory path + System.Collections.IList componentRecords = database.ExecuteQuery("SELECT `Component`,`Directory_` FROM `Component`", emptyArgs); + + for (i = 0; i < componentRecords.Count; i += 2) + { + string componentId = (string)(componentRecords[i]); + string componentDir = (string)(componentRecords[i + 1]); + + ComponentFullDirectory[componentId] = DirectoryIdFullSource[componentDir]; + } + } + + System.Collections.IList fileRecords = database.ExecuteQuery("SELECT `Component_`,`FileName`,`Attributes` FROM `File`", emptyArgs); + + for (i = 0; i < fileRecords.Count; i += 3) + { + string componentId = (string)(fileRecords[i]); + string fileName = SourceFileFromFileName((string)(fileRecords[i + 1])); + int attributes = (int)(fileRecords[i + 2]); + + // If the whole database is marked uncompressed, use the directory layout made above + if ((!compressed && MsidbFileAttributesCompressed != (attributes & MsidbFileAttributesCompressed))) + { + looseFileNames.Add(new TaskItem(Path.GetFullPath(Path.Combine(ComponentFullDirectory[componentId], fileName)))); + } + // If the database is marked as compressed, put files at the root + else if (compressed && (MsidbFileAttributesNoncompressed == (attributes & MsidbFileAttributesNoncompressed))) + { + looseFileNames.Add(new TaskItem(Path.GetFullPath(Path.Combine(databaseDir, fileName)))); + } + } + } + + this.looseFileList = looseFileNames.ToArray(); + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/GlobalSuppressions.cs b/src/WixToolset.BuildTasks/GlobalSuppressions.cs new file mode 100644 index 00000000..65c34c13 --- /dev/null +++ b/src/WixToolset.BuildTasks/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Msi", Scope = "namespace", Target = "WixToolset.BuildTasks")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wix")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wix")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "wix")] diff --git a/src/WixToolset.BuildTasks/Insignia.cs b/src/WixToolset.BuildTasks/Insignia.cs new file mode 100644 index 00000000..ba30963a --- /dev/null +++ b/src/WixToolset.BuildTasks/Insignia.cs @@ -0,0 +1,118 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// An MSBuild task to run the WiX transform generator. + /// + public sealed class Insignia : WixToolTask + { + private const string InsigniaToolName = "insignia.exe"; + + /// + /// Gets or sets the path to the database to inscribe. + /// + public ITaskItem DatabaseFile { get; set; } + + /// + /// Gets or sets the path to the bundle to inscribe. + /// + public ITaskItem BundleFile { get; set; } + + /// + /// Gets or sets the path to the original bundle that contains the attached container. + /// + public ITaskItem OriginalBundleFile { get; set; } + + /// + /// Gets or sets the path to output the inscribed result. + /// + [Required] + public ITaskItem OutputFile { get; set; } + + /// + /// Gets or sets the output. Only set if insignia does work. + /// + [Output] + public ITaskItem Output { get; set; } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of Insignia.exe. + /// The name of the executable. + protected override string ToolName + { + get { return InsigniaToolName; } + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply Insignia.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return InsigniaToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), InsigniaToolName); + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendSwitchIfNotNull("-im ", this.DatabaseFile); + if (null != this.OriginalBundleFile) + { + commandLineBuilder.AppendSwitchIfNotNull("-ab ", this.BundleFile); + commandLineBuilder.AppendFileNameIfNotNull(this.OriginalBundleFile); + } + else + { + commandLineBuilder.AppendSwitchIfNotNull("-ib ", this.BundleFile); + } + + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + } + + /// + /// Executes a tool in-process by loading the tool assembly and invoking its entrypoint. + /// + /// Path to the tool to be executed; must be a managed executable. + /// Commands to be written to a response file. + /// Commands to be passed directly on the command-line. + /// The tool exit code. + protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) + { + int returnCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); + if (0 == returnCode) // successfully did work. + { + this.Output = this.OutputFile; + } + else if (-1 == returnCode) // no work done. + { + returnCode = 0; + } + + return returnCode; + } + } +} diff --git a/src/WixToolset.BuildTasks/Light.cs b/src/WixToolset.BuildTasks/Light.cs new file mode 100644 index 00000000..b7d0b4f7 --- /dev/null +++ b/src/WixToolset.BuildTasks/Light.cs @@ -0,0 +1,488 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// An MSBuild task to run the WiX linker. + /// + public sealed class Light : WixToolTask + { + private const string LightToolName = "Light.exe"; + + private string additionalCub; + private bool allowIdenticalRows; + private bool allowUnresolvedReferences; + private string[] baseInputPaths; + private ITaskItem[] bindInputPaths; + private bool backwardsCompatibleGuidGeneration; + private bool bindFiles; + private ITaskItem builtOutputsFile; + private string cabinetCachePath; + private int cabinetCreationThreadCount = WixCommandLineBuilder.Unspecified; + private ITaskItem contentsFile; + private string cultures; + private string customBinder; + private string defaultCompressionLevel; + private ITaskItem[] extensions; + private string[] ices; + private bool leaveTemporaryFiles; + private ITaskItem[] localizationFiles; + private ITaskItem[] objectFiles; + private bool outputAsXml; + private ITaskItem outputsFile; + private ITaskItem outputFile; + private ITaskItem pdbOutputFile; + private ITaskItem wixProjectFile; + private bool pedantic; + private bool reuseCabinetCache; + private bool suppressAclReset; + private bool suppressAssemblies; + private bool suppressDefaultAdminSequenceActions; + private bool suppressDefaultAdvSequenceActions; + private bool suppressDefaultUISequenceActions; + private bool dropUnrealTables; + private bool exactAssemblyVersions; + private bool suppressFileHashAndInfo; + private bool suppressFiles; + private bool suppressIntermediateFileVersionMatching; + private string[] suppressIces; + private bool suppressLayout; + private bool suppressLocalization; + private bool suppressMsiAssemblyTableProcessing; + private bool suppressPdbOutput; + private bool suppressSchemaValidation; + private bool suppressValidation; + private bool suppressTagSectionIdAttributeOnTuples; + private ITaskItem unreferencedSymbolsFile; + private string[] wixVariables; + private string extensionDirectory; + private string[] referencePaths; + + /// + /// Creates a new light task. + /// + /// + /// Defaults to running the task as a separate process, instead of in-proc + /// which is the default for WixToolTasks. This allows the Win32 manifest file + /// embedded in light.exe to enable reg-free COM interop with mergemod.dll. + /// + public Light() + { + } + + public string AdditionalCub + { + get { return this.additionalCub; } + set { this.additionalCub = value; } + } + + public bool AllowIdenticalRows + { + get { return this.allowIdenticalRows; } + set { this.allowIdenticalRows = value; } + } + + public bool AllowUnresolvedReferences + { + get { return this.allowUnresolvedReferences; } + set { this.allowUnresolvedReferences = value; } + } + + // TODO: remove this property entirely in v4.0 + [Obsolete("Use BindInputPaths instead of BaseInputPaths.")] + public string[] BaseInputPaths + { + get { return this.baseInputPaths; } + set { this.baseInputPaths = value; } + } + + public ITaskItem[] BindInputPaths + { + get { return this.bindInputPaths; } + set { this.bindInputPaths = value; } + } + + public bool BackwardsCompatibleGuidGeneration + { + get { return this.backwardsCompatibleGuidGeneration; } + set { this.backwardsCompatibleGuidGeneration = value; } + } + + public bool BindFiles + { + get { return this.bindFiles; } + set { this.bindFiles = value; } + } + + public string CabinetCachePath + { + get { return this.cabinetCachePath; } + set { this.cabinetCachePath = value; } + } + + public int CabinetCreationThreadCount + { + get { return this.cabinetCreationThreadCount; } + set { this.cabinetCreationThreadCount = value; } + } + + public ITaskItem BindBuiltOutputsFile + { + get { return this.builtOutputsFile; } + set { this.builtOutputsFile = value; } + } + + public ITaskItem BindContentsFile + { + get { return this.contentsFile; } + set { this.contentsFile = value; } + } + + public ITaskItem BindOutputsFile + { + get { return this.outputsFile; } + set { this.outputsFile = value; } + } + + public string Cultures + { + get { return this.cultures; } + set { this.cultures = value; } + } + + public string CustomBinder + { + get { return this.customBinder; } + set { this.customBinder = value; } + } + + public string DefaultCompressionLevel + { + get { return this.defaultCompressionLevel; } + set { this.defaultCompressionLevel = value; } + } + + public bool DropUnrealTables + { + get { return this.dropUnrealTables; } + set { this.dropUnrealTables = value; } + } + + public bool ExactAssemblyVersions + { + get { return this.exactAssemblyVersions; } + set { this.exactAssemblyVersions = value; } + } + + public ITaskItem[] Extensions + { + get { return this.extensions; } + set { this.extensions = value; } + } + + public string[] Ices + { + get { return this.ices; } + set { this.ices = value; } + } + + public bool LeaveTemporaryFiles + { + get { return this.leaveTemporaryFiles; } + set { this.leaveTemporaryFiles = value; } + } + + public ITaskItem[] LocalizationFiles + { + get { return this.localizationFiles; } + set { this.localizationFiles = value; } + } + + [Required] + public ITaskItem[] ObjectFiles + { + get { return this.objectFiles; } + set { this.objectFiles = value; } + } + + public bool OutputAsXml + { + get { return this.outputAsXml; } + set { this.outputAsXml = value; } + } + + [Required] + [Output] + public ITaskItem OutputFile + { + get { return this.outputFile; } + set { this.outputFile = value; } + } + + [Output] + public ITaskItem PdbOutputFile + { + get { return this.pdbOutputFile; } + set { this.pdbOutputFile = value; } + } + + public bool Pedantic + { + get { return this.pedantic; } + set { this.pedantic = value; } + } + + public bool ReuseCabinetCache + { + get { return this.reuseCabinetCache; } + set { this.reuseCabinetCache = value; } + } + + public bool SuppressAclReset + { + get { return this.suppressAclReset; } + set { this.suppressAclReset = value; } + } + + public bool SuppressAssemblies + { + get { return this.suppressAssemblies; } + set { this.suppressAssemblies = value; } + } + + public bool SuppressDefaultAdminSequenceActions + { + get { return this.suppressDefaultAdminSequenceActions; } + set { this.suppressDefaultAdminSequenceActions = value; } + } + + public bool SuppressDefaultAdvSequenceActions + { + get { return this.suppressDefaultAdvSequenceActions; } + set { this.suppressDefaultAdvSequenceActions = value; } + } + + public bool SuppressDefaultUISequenceActions + { + get { return this.suppressDefaultUISequenceActions; } + set { this.suppressDefaultUISequenceActions = value; } + } + + public bool SuppressFileHashAndInfo + { + get { return this.suppressFileHashAndInfo; } + set { this.suppressFileHashAndInfo = value; } + } + + public bool SuppressFiles + { + get { return this.suppressFiles; } + set { this.suppressFiles = value; } + } + + public bool SuppressIntermediateFileVersionMatching + { + get { return this.suppressIntermediateFileVersionMatching; } + set { this.suppressIntermediateFileVersionMatching = value; } + } + + public string[] SuppressIces + { + get { return this.suppressIces; } + set { this.suppressIces = value; } + } + + public bool SuppressLayout + { + get { return this.suppressLayout; } + set { this.suppressLayout = value; } + } + + public bool SuppressLocalization + { + get { return this.suppressLocalization; } + set { this.suppressLocalization = value; } + } + + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + public bool SuppressMsiAssemblyTableProcessing + { + get { return this.suppressMsiAssemblyTableProcessing; } + set { this.suppressMsiAssemblyTableProcessing = value; } + } + + public bool SuppressPdbOutput + { + get { return this.suppressPdbOutput; } + set { this.suppressPdbOutput = value; } + } + + public bool SuppressSchemaValidation + { + get { return this.suppressSchemaValidation; } + set { this.suppressSchemaValidation = value; } + } + + public bool SuppressValidation + { + get { return this.suppressValidation; } + set { this.suppressValidation = value; } + } + + public bool SuppressTagSectionIdAttributeOnTuples + { + get { return this.suppressTagSectionIdAttributeOnTuples; } + set { this.suppressTagSectionIdAttributeOnTuples = value; } + } + + [Output] + public ITaskItem UnreferencedSymbolsFile + { + get { return this.unreferencedSymbolsFile; } + set { this.unreferencedSymbolsFile = value; } + } + + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + public ITaskItem WixProjectFile + { + get { return this.wixProjectFile; } + set { this.wixProjectFile = value; } + } + + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + public string[] WixVariables + { + get { return this.wixVariables; } + set { this.wixVariables = value; } + } + + public string ExtensionDirectory + { + get { return this.extensionDirectory; } + set { this.extensionDirectory = value; } + } + + public string[] ReferencePaths + { + get { return this.referencePaths; } + set { this.referencePaths = value; } + } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of light.exe. + /// The name of the executable. + protected override string ToolName + { + get { return LightToolName; } + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply light.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return LightToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), LightToolName); + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + // Always put the output first so it is easy to find in the log. + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendSwitchIfNotNull("-pdbout ", this.PdbOutputFile); + + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendIfTrue("-ai", this.AllowIdenticalRows); + commandLineBuilder.AppendIfTrue("-au", this.AllowUnresolvedReferences); + commandLineBuilder.AppendArrayIfNotNull("-b ", this.baseInputPaths); + + if (null != this.BindInputPaths) + { + Queue formattedBindInputPaths = new Queue(); + foreach (ITaskItem item in this.BindInputPaths) + { + String formattedPath = string.Empty; + String bindName = item.GetMetadata("BindName"); + if (!String.IsNullOrEmpty(bindName)) + { + formattedPath = String.Concat(bindName, "=", item.GetMetadata("FullPath")); + } + else + { + formattedPath = item.GetMetadata("FullPath"); + } + formattedBindInputPaths.Enqueue(formattedPath); + } + commandLineBuilder.AppendArrayIfNotNull("-b ", formattedBindInputPaths.ToArray()); + } + + commandLineBuilder.AppendIfTrue("-bcgg", this.BackwardsCompatibleGuidGeneration); + commandLineBuilder.AppendIfTrue("-bf", this.BindFiles); + commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath); + commandLineBuilder.AppendIfSpecified("-ct ", this.CabinetCreationThreadCount); + commandLineBuilder.AppendSwitchIfNotNull("-cub ", this.AdditionalCub); + commandLineBuilder.AppendSwitchIfNotNull("-cultures:", this.Cultures); + commandLineBuilder.AppendSwitchIfNotNull("-binder ", this.CustomBinder); + commandLineBuilder.AppendArrayIfNotNull("-d", this.WixVariables); + commandLineBuilder.AppendSwitchIfNotNull("-dcl:", this.DefaultCompressionLevel); + commandLineBuilder.AppendIfTrue("-dut", this.DropUnrealTables); + commandLineBuilder.AppendIfTrue("-eav", this.ExactAssemblyVersions); + commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths); + commandLineBuilder.AppendArrayIfNotNull("-ice:", this.Ices); + commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles); + commandLineBuilder.AppendIfTrue("-notidy", this.LeaveTemporaryFiles); + commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic); + commandLineBuilder.AppendIfTrue("-reusecab", this.ReuseCabinetCache); + commandLineBuilder.AppendIfTrue("-sa", this.SuppressAssemblies); + commandLineBuilder.AppendIfTrue("-sacl", this.SuppressAclReset); + commandLineBuilder.AppendIfTrue("-sadmin", this.SuppressDefaultAdminSequenceActions); + commandLineBuilder.AppendIfTrue("-sadv", this.SuppressDefaultAdvSequenceActions); + commandLineBuilder.AppendArrayIfNotNull("-sice:", this.SuppressIces); + commandLineBuilder.AppendIfTrue("-sma", this.SuppressMsiAssemblyTableProcessing); + commandLineBuilder.AppendIfTrue("-sf", this.SuppressFiles); + commandLineBuilder.AppendIfTrue("-sh", this.SuppressFileHashAndInfo); + commandLineBuilder.AppendIfTrue("-sl", this.SuppressLayout); + commandLineBuilder.AppendIfTrue("-sloc", this.SuppressLocalization); + commandLineBuilder.AppendIfTrue("-spdb", this.SuppressPdbOutput); + commandLineBuilder.AppendIfTrue("-ss", this.SuppressSchemaValidation); + commandLineBuilder.AppendIfTrue("-sts", this.SuppressTagSectionIdAttributeOnTuples); + commandLineBuilder.AppendIfTrue("-sui", this.SuppressDefaultUISequenceActions); + commandLineBuilder.AppendIfTrue("-sv", this.SuppressIntermediateFileVersionMatching); + commandLineBuilder.AppendIfTrue("-sval", this.SuppressValidation); + commandLineBuilder.AppendSwitchIfNotNull("-usf ", this.UnreferencedSymbolsFile); + commandLineBuilder.AppendIfTrue("-xo", this.OutputAsXml); + commandLineBuilder.AppendSwitchIfNotNull("-contentsfile ", this.BindContentsFile); + commandLineBuilder.AppendSwitchIfNotNull("-outputsfile ", this.BindOutputsFile); + commandLineBuilder.AppendSwitchIfNotNull("-builtoutputsfile ", this.BindBuiltOutputsFile); + commandLineBuilder.AppendSwitchIfNotNull("-wixprojectfile ", this.WixProjectFile); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + + List objectFilePaths = AdjustFilePaths(this.objectFiles, this.ReferencePaths); + commandLineBuilder.AppendFileNamesIfNotNull(objectFilePaths.ToArray(), " "); + } + } +} diff --git a/src/WixToolset.BuildTasks/Lit.cs b/src/WixToolset.BuildTasks/Lit.cs new file mode 100644 index 00000000..1df964ae --- /dev/null +++ b/src/WixToolset.BuildTasks/Lit.cs @@ -0,0 +1,178 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// An MSBuild task to run the WiX lib tool. + /// + public sealed class Lit : WixToolTask + { + private const string LitToolName = "lit.exe"; + + private string[] baseInputPaths; + private ITaskItem[] bindInputPaths; + private bool bindFiles; + private ITaskItem[] extensions; + private ITaskItem[] localizationFiles; + private ITaskItem[] objectFiles; + private ITaskItem outputFile; + private bool pedantic; + private bool suppressIntermediateFileVersionMatching; + private bool suppressSchemaValidation; + private string extensionDirectory; + private string[] referencePaths; + + // TODO: remove this property entirely in v4.0 + [Obsolete("Use BindInputPaths instead of BaseInputPaths.")] + public string[] BaseInputPaths + { + get { return this.baseInputPaths; } + set { this.baseInputPaths = value; } + } + + public ITaskItem[] BindInputPaths + { + get { return this.bindInputPaths; } + set { this.bindInputPaths = value; } + } + + public bool BindFiles + { + get { return this.bindFiles; } + set { this.bindFiles = value; } + } + + public ITaskItem[] Extensions + { + get { return this.extensions; } + set { this.extensions = value; } + } + + public ITaskItem[] LocalizationFiles + { + get { return this.localizationFiles; } + set { this.localizationFiles = value; } + } + + [Required] + public ITaskItem[] ObjectFiles + { + get { return this.objectFiles; } + set { this.objectFiles = value; } + } + + [Required] + [Output] + public ITaskItem OutputFile + { + get { return this.outputFile; } + set { this.outputFile = value; } + } + + public bool Pedantic + { + get { return this.pedantic; } + set { this.pedantic = value; } + } + + public bool SuppressIntermediateFileVersionMatching + { + get { return this.suppressIntermediateFileVersionMatching; } + set { this.suppressIntermediateFileVersionMatching = value; } + } + + public bool SuppressSchemaValidation + { + get { return this.suppressSchemaValidation; } + set { this.suppressSchemaValidation = value; } + } + + public string ExtensionDirectory + { + get { return this.extensionDirectory; } + set { this.extensionDirectory = value; } + } + + public string[] ReferencePaths + { + get { return this.referencePaths; } + set { this.referencePaths = value; } + } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of lit.exe + /// The name of the executable. + protected override string ToolName + { + get { return LitToolName; } + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply lit.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return LitToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), LitToolName); + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendArrayIfNotNull("-b ", this.baseInputPaths); + if (null != this.BindInputPaths) + { + Queue formattedBindInputPaths = new Queue(); + foreach (ITaskItem item in this.BindInputPaths) + { + String formattedPath = string.Empty; + String bindName = item.GetMetadata("BindName"); + if (!String.IsNullOrEmpty(item.GetMetadata("BindName"))) + { + formattedPath = String.Concat(bindName, "=", item.GetMetadata("FullPath")); + } + else + { + formattedPath = item.GetMetadata("FullPath"); + } + formattedBindInputPaths.Enqueue(formattedPath); + } + commandLineBuilder.AppendArrayIfNotNull("-b ", formattedBindInputPaths.ToArray()); + } + commandLineBuilder.AppendIfTrue("-bf", this.BindFiles); + commandLineBuilder.AppendExtensions(this.extensions, this.ExtensionDirectory, this.referencePaths); + commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles); + commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic); + commandLineBuilder.AppendIfTrue("-ss", this.SuppressSchemaValidation); + commandLineBuilder.AppendIfTrue("-sv", this.SuppressIntermediateFileVersionMatching); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + + List objectFilePaths = AdjustFilePaths(this.objectFiles, this.ReferencePaths); + commandLineBuilder.AppendFileNamesIfNotNull(objectFilePaths.ToArray(), " "); + } + } +} diff --git a/src/WixToolset.BuildTasks/Pyro.cs b/src/WixToolset.BuildTasks/Pyro.cs new file mode 100644 index 00000000..f6b069da --- /dev/null +++ b/src/WixToolset.BuildTasks/Pyro.cs @@ -0,0 +1,140 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.IO; + using Microsoft.Build.Framework; + + /// + /// An MSBuild task to run the WiX patch builder. + /// + public sealed class Pyro : WixToolTask + { + private const string PyroToolName = "pyro.exe"; + + public bool BinaryDeltaPatch { get; set; } + public string CabinetCachePath { get; set; } + public string ExtensionDirectory { get; set; } + public ITaskItem[] Extensions { get; set; } + public bool LeaveTemporaryFiles { get; set; } + public string[] ReferencePaths { get; set; } + public bool ReuseCabinetCache { get; set; } + public bool SuppressAssemblies { get; set; } + public bool SuppressFiles { get; set; } + public bool SuppressFileHashAndInfo { get; set; } + public bool SuppressPdbOutput { get; set; } + + [Required] + public string DefaultBaselineId { get; set; } + + public ITaskItem[] BindInputPathsForTarget { get; set; } + public ITaskItem[] BindInputPathsForUpdated { get; set; } + + [Required] + public ITaskItem InputFile { get; set; } + + [Required] + [Output] + public ITaskItem OutputFile { get; set; } + + [Output] + public ITaskItem PdbOutputFile { get; set; } + + [Required] + public ITaskItem[] Transforms { get; set; } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of pyro.exe. + /// The name of the executable. + protected override string ToolName + { + get { return PyroToolName; } + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply torch.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return PyroToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), PyroToolName); + } + + /// + /// Builds a command line for bind-input paths (-bt and -bu switches). + /// + private void AppendBindInputPaths(WixCommandLineBuilder commandLineBuilder, IEnumerable bindInputPaths, string switchName) + { + if (null != bindInputPaths) + { + Queue formattedBindInputPaths = new Queue(); + foreach (ITaskItem item in bindInputPaths) + { + String formattedPath = string.Empty; + String bindName = item.GetMetadata("BindName"); + if (!String.IsNullOrEmpty(bindName)) + { + formattedPath = String.Concat(bindName, "=", item.GetMetadata("FullPath")); + } + else + { + formattedPath = item.GetMetadata("FullPath"); + } + formattedBindInputPaths.Enqueue(formattedPath); + } + + commandLineBuilder.AppendArrayIfNotNull(switchName, formattedBindInputPaths.ToArray()); + } + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + // Always put the output first so it is easy to find in the log. + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendSwitchIfNotNull("-pdbout ", this.PdbOutputFile); + + base.BuildCommandLine(commandLineBuilder); + + this.AppendBindInputPaths(commandLineBuilder, this.BindInputPathsForTarget, "-bt "); + this.AppendBindInputPaths(commandLineBuilder, this.BindInputPathsForUpdated, "-bu "); + + commandLineBuilder.AppendFileNameIfNotNull(this.InputFile); + commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath); + commandLineBuilder.AppendIfTrue("-delta", this.BinaryDeltaPatch); + commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths); + commandLineBuilder.AppendIfTrue("-notidy", this.LeaveTemporaryFiles); + commandLineBuilder.AppendIfTrue("-reusecab", this.ReuseCabinetCache); + commandLineBuilder.AppendIfTrue("-sa", this.SuppressAssemblies); + commandLineBuilder.AppendIfTrue("-sf", this.SuppressFiles); + commandLineBuilder.AppendIfTrue("-sh", this.SuppressFileHashAndInfo); + commandLineBuilder.AppendIfTrue("-spdb", this.SuppressPdbOutput); + foreach (ITaskItem transform in this.Transforms) + { + string transformPath = transform.ItemSpec; + string baselineId = transform.GetMetadata("OverrideBaselineId"); + if (String.IsNullOrEmpty(baselineId)) + { + baselineId = this.DefaultBaselineId; + } + + commandLineBuilder.AppendTextIfNotNull(String.Format("-t {0} {1}", baselineId, transformPath)); + } + + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + } + } +} diff --git a/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs new file mode 100644 index 00000000..5445e0cd --- /dev/null +++ b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs @@ -0,0 +1,132 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections; + using System.Globalization; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task refreshes the generated file for bundle projects. + /// + public class RefreshBundleGeneratedFile : Task + { + private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); + private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters + + private ITaskItem[] generatedFiles; + private ITaskItem[] projectReferencePaths; + + /// + /// The list of files to generate. + /// + [Required] + public ITaskItem[] GeneratedFiles + { + get { return this.generatedFiles; } + set { this.generatedFiles = value; } + } + + /// + /// All the project references in the project. + /// + [Required] + public ITaskItem[] ProjectReferencePaths + { + get { return this.projectReferencePaths; } + set { this.projectReferencePaths = value; } + } + + /// + /// Gets a complete list of external cabs referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + ArrayList payloadGroupRefs = new ArrayList(); + ArrayList packageGroupRefs = new ArrayList(); + for (int i = 0; i < this.ProjectReferencePaths.Length; i++) + { + ITaskItem item = this.ProjectReferencePaths[i]; + + if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest))) + { + continue; + } + + string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i); + string projectName = Path.GetFileNameWithoutExtension(projectPath); + string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName)); + + string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';'); + foreach (string pog in pogs) + { + if (!String.IsNullOrEmpty(pog)) + { + // TODO: Add payload group references and package group references once heat is generating them + ////payloadGroupRefs.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", referenceName, pog)); + packageGroupRefs.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", referenceName, pog)); + } + } + } + + XmlDocument doc = new XmlDocument(); + + XmlProcessingInstruction head = doc.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'"); + doc.AppendChild(head); + + XmlElement rootElement = doc.CreateElement("Wix"); + rootElement.SetAttribute("xmlns", "http://wixtoolset.org/schemas/v4/wxs"); + doc.AppendChild(rootElement); + + XmlElement fragment = doc.CreateElement("Fragment"); + rootElement.AppendChild(fragment); + + XmlElement payloadGroup = doc.CreateElement("PayloadGroup"); + payloadGroup.SetAttribute("Id", "Bundle.Generated.Payloads"); + fragment.AppendChild(payloadGroup); + + XmlElement packageGroup = doc.CreateElement("PackageGroup"); + packageGroup.SetAttribute("Id", "Bundle.Generated.Packages"); + fragment.AppendChild(packageGroup); + + foreach (string payloadGroupRef in payloadGroupRefs) + { + XmlElement payloadGroupRefElement = doc.CreateElement("PayloadGroupRef"); + payloadGroupRefElement.SetAttribute("Id", payloadGroupRef); + payloadGroup.AppendChild(payloadGroupRefElement); + } + + foreach (string packageGroupRef in packageGroupRefs) + { + XmlElement packageGroupRefElement = doc.CreateElement("PackageGroupRef"); + packageGroupRefElement.SetAttribute("Id", packageGroupRef); + packageGroup.AppendChild(packageGroupRefElement); + } + + foreach (ITaskItem item in this.GeneratedFiles) + { + string fullPath = item.GetMetadata("FullPath"); + + payloadGroup.SetAttribute("Id", Path.GetFileNameWithoutExtension(fullPath) + ".Payloads"); + packageGroup.SetAttribute("Id", Path.GetFileNameWithoutExtension(fullPath) + ".Packages"); + try + { + doc.Save(fullPath); + } + catch (Exception e) + { + // e.Message will be something like: "Access to the path 'fullPath' is denied." + this.Log.LogMessage(MessageImportance.High, "Unable to save generated file to '{0}'. {1}", fullPath, e.Message); + } + } + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs new file mode 100644 index 00000000..fdfc4774 --- /dev/null +++ b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs @@ -0,0 +1,118 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections; + using System.Globalization; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task refreshes the generated file that contains ComponentGroupRefs + /// to harvested output. + /// + public class RefreshGeneratedFile : Task + { + private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); + private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters + + private ITaskItem[] generatedFiles; + private ITaskItem[] projectReferencePaths; + + /// + /// The list of files to generate. + /// + [Required] + public ITaskItem[] GeneratedFiles + { + get { return this.generatedFiles; } + set { this.generatedFiles = value; } + } + + /// + /// All the project references in the project. + /// + [Required] + public ITaskItem[] ProjectReferencePaths + { + get { return this.projectReferencePaths; } + set { this.projectReferencePaths = value; } + } + + /// + /// Gets a complete list of external cabs referenced by the given installer database file. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + ArrayList componentGroupRefs = new ArrayList(); + for (int i = 0; i < this.ProjectReferencePaths.Length; i++) + { + ITaskItem item = this.ProjectReferencePaths[i]; + + if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest))) + { + continue; + } + + string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i); + string projectName = Path.GetFileNameWithoutExtension(projectPath); + string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName)); + + string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';'); + foreach (string pog in pogs) + { + if (!String.IsNullOrEmpty(pog)) + { + componentGroupRefs.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", referenceName, pog)); + } + } + } + + XmlDocument doc = new XmlDocument(); + + XmlProcessingInstruction head = doc.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'"); + doc.AppendChild(head); + + XmlElement rootElement = doc.CreateElement("Wix"); + rootElement.SetAttribute("xmlns", "http://wixtoolset.org/schemas/v4/wxs"); + doc.AppendChild(rootElement); + + XmlElement fragment = doc.CreateElement("Fragment"); + rootElement.AppendChild(fragment); + + XmlElement componentGroup = doc.CreateElement("ComponentGroup"); + componentGroup.SetAttribute("Id", "Product.Generated"); + fragment.AppendChild(componentGroup); + + foreach (string componentGroupRef in componentGroupRefs) + { + XmlElement componentGroupRefElement = doc.CreateElement("ComponentGroupRef"); + componentGroupRefElement.SetAttribute("Id", componentGroupRef); + componentGroup.AppendChild(componentGroupRefElement); + } + + foreach (ITaskItem item in this.GeneratedFiles) + { + string fullPath = item.GetMetadata("FullPath"); + + componentGroup.SetAttribute("Id", Path.GetFileNameWithoutExtension(fullPath)); + try + { + doc.Save(fullPath); + } + catch (Exception e) + { + // e.Message will be something like: "Access to the path 'fullPath' is denied." + this.Log.LogMessage(MessageImportance.High, "Unable to save generated file to '{0}'. {1}", fullPath, e.Message); + } + } + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/ReplaceString.cs b/src/WixToolset.BuildTasks/ReplaceString.cs new file mode 100644 index 00000000..e5041923 --- /dev/null +++ b/src/WixToolset.BuildTasks/ReplaceString.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// Replaces occurances of OldValues with NewValues in String. + /// + public class ReplaceString : Task + { + /// + /// Text to operate on. + /// + [Output] + [Required] + public string Text { get; set; } + + /// + /// List of old values to replace. + /// + [Required] + public string OldValue { get; set; } + + /// + /// List of new values to replace old values with. If not specified, occurances of OldValue will be removed. + /// + public string NewValue { get; set; } + + /// + /// Does the string replacement. + /// + /// + public override bool Execute() + { + if (String.IsNullOrEmpty(this.Text)) + { + return true; + } + + if (String.IsNullOrEmpty(this.OldValue)) + { + Log.LogError("OldValue must be specified"); + return false; + } + + this.Text = this.Text.Replace(this.OldValue, this.NewValue); + + return true; + } + } +} diff --git a/src/WixToolset.BuildTasks/ResolveWixReferences.cs b/src/WixToolset.BuildTasks/ResolveWixReferences.cs new file mode 100644 index 00000000..9b8cfe6f --- /dev/null +++ b/src/WixToolset.BuildTasks/ResolveWixReferences.cs @@ -0,0 +1,212 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + using System.IO; + + /// + /// This task searches for paths to references using the order specified in SearchPaths. + /// + public class ResolveWixReferences : Task + { + /// + /// Token value used in SearchPaths to indicate that the item's HintPath metadata should + /// be searched as a full file path to resolve the reference. + /// Must match wix.targets, case sensitive. + /// + private const string HintPathToken = "{HintPathFromItem}"; + + /// + /// Token value used in SearchPaths to indicate that the item's Identity should + /// be searched as a full file path to resolve the reference. + /// Must match wix.targets, case sensitive. + /// + private const string RawFileNameToken = "{RawFileName}"; + + /// + /// The list of references to resolve. + /// + [Required] + public ITaskItem[] WixReferences + { + get; + set; + } + + /// + /// The directories or special locations that are searched to find the files + /// on disk that represent the references. The order in which the search paths are listed + /// is important. For each reference, the list of paths is searched from left to right. + /// When a file that represents the reference is found, that search stops and the search + /// for the next reference starts. + /// + /// This parameter accepts the following types of values: + /// A directory path. + /// {HintPathFromItem}: Specifies that the task will examine the HintPath metadata + /// of the base item. + /// TODO : {CandidateAssemblyFiles}: Specifies that the task will examine the files + /// passed in through the CandidateAssemblyFiles parameter. + /// TODO : {Registry:_AssemblyFoldersBase_, _RuntimeVersion_, _AssemblyFoldersSuffix_}: + /// TODO : {AssemblyFolders}: Specifies the task will use the Visual Studio.NET 2003 + /// finding-assemblies-from-registry scheme. + /// TODO : {GAC}: Specifies the task will search in the GAC. + /// {RawFileName}: Specifies the task will consider the Include value of the item to be + /// an exact path and file name. + /// + public string[] SearchPaths + { + get; + set; + } + + /// + /// The filename extension(s) to be checked when searching. + /// + public string[] SearchFilenameExtensions + { + get; + set; + } + + /// + /// Output items that contain the same metadata as input references and have been resolved to full paths. + /// + [Output] + public ITaskItem[] ResolvedWixReferences + { + get; + private set; + } + + /// + /// Resolves reference paths by searching for referenced items using the specified SearchPaths. + /// + /// True on success, or throws an exception on failure. + public override bool Execute() + { + List resolvedReferences = new List(); + + foreach (ITaskItem reference in this.WixReferences) + { + ITaskItem resolvedReference = ResolveWixReferences.ResolveReference(reference, this.SearchPaths, this.SearchFilenameExtensions, this.Log); + + this.Log.LogMessage(MessageImportance.Low, "Resolved path {0}", resolvedReference.ItemSpec); + resolvedReferences.Add(resolvedReference); + } + + this.ResolvedWixReferences = resolvedReferences.ToArray(); + return true; + } + + /// + /// Resolves a single reference item by searcheing for referenced items using the specified SearchPaths. + /// This method is made public so the resolution logic can be reused by other tasks. + /// + /// The referenced item. + /// The paths to search. + /// Filename extensions to check. + /// Logging helper. + /// The resolved reference item, or the original reference if it could not be resolved. + public static ITaskItem ResolveReference(ITaskItem reference, string[] searchPaths, string[] searchFilenameExtensions, TaskLoggingHelper log) + { + if (reference == null) + { + throw new ArgumentNullException("reference"); + } + + if (searchPaths == null) + { + // Nothing to search, so just return the original reference item. + return reference; + } + + if (searchFilenameExtensions == null) + { + searchFilenameExtensions = new string[] { }; + } + + // Copy all the metadata from the source + TaskItem resolvedReference = new TaskItem(reference); + log.LogMessage(MessageImportance.Low, "WixReference: {0}", reference.ItemSpec); + + // Now find the resolved path based on our order of precedence + foreach (string searchPath in searchPaths) + { + log.LogMessage(MessageImportance.Low, "Trying {0}", searchPath); + if (searchPath.Equals(HintPathToken, StringComparison.Ordinal)) + { + string path = reference.GetMetadata("HintPath"); + log.LogMessage(MessageImportance.Low, "Trying path {0}", path); + if (File.Exists(path)) + { + resolvedReference.ItemSpec = path; + break; + } + } + else if (searchPath.Equals(RawFileNameToken, StringComparison.Ordinal)) + { + log.LogMessage(MessageImportance.Low, "Trying path {0}", resolvedReference.ItemSpec); + if (File.Exists(resolvedReference.ItemSpec)) + { + break; + } + + if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference, + resolvedReference.ItemSpec, searchFilenameExtensions, log)) + { + break; + } + } + else + { + string path = Path.Combine(searchPath, Path.GetFileName(reference.ItemSpec)); + log.LogMessage(MessageImportance.Low, "Trying path {0}", path); + if (File.Exists(path)) + { + resolvedReference.ItemSpec = path; + break; + } + + if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference, + path, searchFilenameExtensions, log)) + { + break; + } + } + } + + // Normalize the item path + resolvedReference.ItemSpec = resolvedReference.GetMetadata("FullPath"); + + return resolvedReference; + } + + /// + /// Helper method for checking filename extensions when resolving references. + /// + /// The reference being resolved. + /// Full filename path without extension. + /// Filename extensions to check. + /// Logging helper. + /// True if the item was resolved, else false. + private static bool ResolveFilenameExtensions(ITaskItem reference, string basePath, string[] filenameExtensions, TaskLoggingHelper log) + { + foreach (string filenameExtension in filenameExtensions) + { + string path = basePath + filenameExtension; + log.LogMessage(MessageImportance.Low, "Trying path {0}", path); + if (File.Exists(path)) + { + reference.ItemSpec = path; + return true; + } + } + + return false; + } + } +} diff --git a/src/WixToolset.BuildTasks/TaskBase.cs b/src/WixToolset.BuildTasks/TaskBase.cs new file mode 100644 index 00000000..3d58fc06 --- /dev/null +++ b/src/WixToolset.BuildTasks/TaskBase.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using Microsoft.Build.Utilities; + + public abstract class TaskBase : Task + { + public string ToolPath { get; set; } + + public string AdditionalOptions { get; set; } + + public bool RunAsSeparateProcess { get; set; } + + /// + /// Gets or sets whether all warnings should be suppressed. + /// + public bool SuppressAllWarnings { get; set; } + + /// + /// Gets or sets a list of specific warnings to be suppressed. + /// + public string[] SuppressSpecificWarnings { get; set; } + + /// + /// Gets or sets whether all warnings should be treated as errors. + /// + public bool TreatWarningsAsErrors { get; set; } + + /// + /// Gets or sets a list of specific warnings to treat as errors. + /// + public string[] TreatSpecificWarningsAsErrors { get; set; } + + /// + /// Gets or sets whether to display verbose output. + /// + public bool VerboseOutput { get; set; } + + /// + /// Gets or sets whether to display the logo. + /// + public bool NoLogo { get; set; } + + public override bool Execute() + { + try + { + this.ExecuteCore(); + } + catch (BuildException e) + { + this.Log.LogErrorFromException(e); + } + catch (Data.WixException e) + { + this.Log.LogErrorFromException(e); + } + + return !this.Log.HasLoggedErrors; + } + + protected abstract void ExecuteCore(); + } +} diff --git a/src/WixToolset.BuildTasks/Torch.cs b/src/WixToolset.BuildTasks/Torch.cs new file mode 100644 index 00000000..e18ed315 --- /dev/null +++ b/src/WixToolset.BuildTasks/Torch.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// An MSBuild task to run the WiX transform generator. + /// + public sealed class Torch : WixToolTask + { + private const string TorchToolName = "Torch.exe"; + + private bool adminImage; + private ITaskItem baselineFile; + private string binaryExtractionPath; + private bool inputIsXml; + private bool leaveTemporaryFiles; + private bool outputAsXml; + private ITaskItem outputFile; + private bool preserveUnmodifiedContent; + private string suppressTransformErrorFlags; + private string transformValidationFlags; + private string transformValidationType; + private ITaskItem updateFile; + + public bool AdminImage + { + get { return this.adminImage; } + set { this.adminImage = value; } + } + + + [Required] + public ITaskItem BaselineFile + { + get { return this.baselineFile; } + set { this.baselineFile = value; } + } + + public string BinaryExtractionPath + { + get { return this.binaryExtractionPath; } + set { this.binaryExtractionPath = value; } + } + + public bool LeaveTemporaryFiles + { + get { return this.leaveTemporaryFiles; } + set { this.leaveTemporaryFiles = value; } + } + + public bool InputIsXml + { + get { return this.inputIsXml; } + set { this.inputIsXml = value; } + } + + public bool OutputAsXml + { + get { return this.outputAsXml; } + set { this.outputAsXml = value; } + } + + public bool PreserveUnmodifiedContent + { + get { return this.preserveUnmodifiedContent; } + set { this.preserveUnmodifiedContent = value; } + } + + [Required] + [Output] + public ITaskItem OutputFile + { + get { return this.outputFile; } + set { this.outputFile = value; } + } + + public string SuppressTransformErrorFlags + { + get { return this.suppressTransformErrorFlags; } + set { this.suppressTransformErrorFlags = value; } + } + + public string TransformValidationType + { + get { return this.transformValidationType; } + set { this.transformValidationType = value; } + } + + public string TransformValidationFlags + { + get { return this.transformValidationFlags; } + set { this.transformValidationFlags = value; } + } + + [Required] + public ITaskItem UpdateFile + { + get { return this.updateFile; } + set { this.updateFile = value; } + } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of torch.exe. + /// The name of the executable. + protected override string ToolName + { + get { return TorchToolName; } + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply torch.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return TorchToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), TorchToolName); + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendIfTrue("-notidy", this.LeaveTemporaryFiles); + commandLineBuilder.AppendIfTrue("-xo", this.OutputAsXml); + commandLineBuilder.AppendIfTrue("-xi", this.InputIsXml); + commandLineBuilder.AppendIfTrue("-p", this.PreserveUnmodifiedContent); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + commandLineBuilder.AppendFileNameIfNotNull(this.BaselineFile); + commandLineBuilder.AppendFileNameIfNotNull(this.UpdateFile); + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + commandLineBuilder.AppendIfTrue("-a", this.adminImage); + commandLineBuilder.AppendSwitchIfNotNull("-x ", this.BinaryExtractionPath); + commandLineBuilder.AppendSwitchIfNotNull("-serr ", this.SuppressTransformErrorFlags); + commandLineBuilder.AppendSwitchIfNotNull("-t ", this.TransformValidationType); + commandLineBuilder.AppendSwitchIfNotNull("-val ", this.TransformValidationFlags); + } + } +} diff --git a/src/WixToolset.BuildTasks/WixAssignCulture.cs b/src/WixToolset.BuildTasks/WixAssignCulture.cs new file mode 100644 index 00000000..7a03dc47 --- /dev/null +++ b/src/WixToolset.BuildTasks/WixAssignCulture.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Xml; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task assigns Culture metadata to files based on the value of the Culture attribute on the + /// WixLocalization element inside the file. + /// + public class WixAssignCulture : Task + { + private const string CultureAttributeName = "Culture"; + private const string OutputFolderMetadataName = "OutputFolder"; + private const string InvariantCultureIdentifier = "neutral"; + private const string NullCultureIdentifier = "null"; + + /// + /// The list of cultures to build. Cultures are specified in the following form: + /// primary culture,first fallback culture, second fallback culture;... + /// Culture groups are seperated by semi-colons + /// Culture precedence within a culture group is evaluated from left to right where fallback cultures are + /// separated with commas. + /// The first (primary) culture in a culture group will be used as the output sub-folder. + /// + public string Cultures { get; set; } + + /// + /// The list of files to apply culture information to. + /// + [Required] + public ITaskItem[] Files + { + get; + set; + } + + /// + /// The files that had culture information applied + /// + [Output] + public ITaskItem[] CultureGroups + { + get; + private set; + } + + /// + /// Applies culture information to the files specified by the Files property. + /// This task intentionally does not validate that strings are valid Cultures so that we can support + /// psuedo-loc. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + // First, process the culture group list the user specified in the cultures property + List cultureGroups = new List(); + + if (!String.IsNullOrEmpty(this.Cultures)) + { + // Get rid of extra quotes + this.Cultures = this.Cultures.Trim('\"'); + + foreach (string cultureGroupString in this.Cultures.Split(';')) + { + if (0 == cultureGroupString.Length) + { + // MSBuild v2.0.50727 cannnot handle "" items + // for the invariant culture we require the neutral keyword + continue; + } + CultureGroup cultureGroup = new CultureGroup(cultureGroupString); + cultureGroups.Add(cultureGroup); + } + } + else + { + // Only process the EmbeddedResource items if cultures was unspecified + foreach (ITaskItem file in this.Files) + { + // Ignore non-wxls + if (!String.Equals(file.GetMetadata("Extension"), ".wxl", StringComparison.OrdinalIgnoreCase)) + { + Log.LogError("Unable to retrieve the culture for EmbeddedResource {0}. The file type is not supported.", file.ItemSpec); + return false; + } + XmlDocument wxlFile = new XmlDocument(); + + try + { + wxlFile.Load(file.ItemSpec); + } + catch (FileNotFoundException) + { + Log.LogError("Unable to retrieve the culture for EmbeddedResource {0}. The file was not found.", file.ItemSpec); + return false; + } + catch (Exception e) + { + Log.LogError("Unable to retrieve the culture for EmbeddedResource {0}: {1}", file.ItemSpec, e.Message); + return false; + } + + // Take the culture value and try using it to create a culture. + XmlAttribute cultureAttr = wxlFile.DocumentElement.Attributes[WixAssignCulture.CultureAttributeName]; + string wxlCulture = null == cultureAttr ? String.Empty : cultureAttr.Value; + if (0 == wxlCulture.Length) + { + // We use a keyword for the invariant culture because MSBuild v2.0.50727 cannnot handle "" items + wxlCulture = InvariantCultureIdentifier; + } + + // We found the culture for the WXL, we now need to determine if it maps to a culture group specified + // in the Cultures property or if we need to create a new one. + Log.LogMessage(MessageImportance.Low, "Culture \"{0}\" from EmbeddedResource {1}.", wxlCulture, file.ItemSpec); + + bool cultureGroupExists = false; + foreach (CultureGroup cultureGroup in cultureGroups) + { + foreach (string culture in cultureGroup.Cultures) + { + if (String.Equals(wxlCulture, culture, StringComparison.OrdinalIgnoreCase)) + { + cultureGroupExists = true; + break; + } + } + } + + // The WXL didn't match a culture group we already have so create a new one. + if (!cultureGroupExists) + { + cultureGroups.Add(new CultureGroup(wxlCulture)); + } + } + } + + // If we didn't create any culture groups the culture was unspecificed and no WXLs were included + // Build an unlocalized target in the output folder + if (cultureGroups.Count == 0) + { + cultureGroups.Add(new CultureGroup()); + } + + List cultureGroupItems = new List(); + + if (1 == cultureGroups.Count && 0 == this.Files.Length) + { + // Maintain old behavior, if only one culturegroup is specified and no WXL, output to the default folder + TaskItem cultureGroupItem = new TaskItem(cultureGroups[0].ToString()); + cultureGroupItem.SetMetadata(OutputFolderMetadataName, CultureGroup.DefaultFolder); + cultureGroupItems.Add(cultureGroupItem); + } + else + { + foreach (CultureGroup cultureGroup in cultureGroups) + { + TaskItem cultureGroupItem = new TaskItem(cultureGroup.ToString()); + cultureGroupItem.SetMetadata(OutputFolderMetadataName, cultureGroup.OutputFolder); + cultureGroupItems.Add(cultureGroupItem); + Log.LogMessage("Culture: {0}", cultureGroup.ToString()); + } + } + + this.CultureGroups = cultureGroupItems.ToArray(); + return true; + } + + private class CultureGroup + { + private List cultures = new List(); + + /// + /// TargetPath already has a '\', do not double it! + /// + public const string DefaultFolder = ""; + + /// + /// Initialize a null culture group + /// + public CultureGroup() + { + } + + public CultureGroup(string cultureGroupString) + { + Debug.Assert(!String.IsNullOrEmpty(cultureGroupString)); + foreach (string cultureString in cultureGroupString.Split(',')) + { + this.cultures.Add(cultureString); + } + } + + public List Cultures { get { return cultures; } } + + public string OutputFolder + { + get + { + string result = DefaultFolder; + if (this.Cultures.Count > 0 && + !this.Cultures[0].Equals(InvariantCultureIdentifier, StringComparison.OrdinalIgnoreCase)) + { + result = this.Cultures[0] + "\\"; + } + + return result; + } + } + + public override string ToString() + { + if (this.Cultures.Count > 0) + { + return String.Join(",", this.Cultures.ToArray()); + } + + // We use a keyword for a null culture because MSBuild cannnot handle "" items + // Null is different from neutral. For neutral we still want to do WXL + // filtering in Light. + return NullCultureIdentifier; + } + } + } +} diff --git a/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs b/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs new file mode 100644 index 00000000..9a6a005d --- /dev/null +++ b/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs @@ -0,0 +1,180 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// Helper class for appending the command line arguments. + /// + public class WixCommandLineBuilder : CommandLineBuilder + { + internal const int Unspecified = -1; + + /// + /// Append a switch to the command line if the value has been specified. + /// + /// Switch to append. + /// Value specified by the user. + public void AppendIfSpecified(string switchName, int value) + { + if (value != Unspecified) + { + this.AppendSwitchIfNotNull(switchName, value.ToString(CultureInfo.InvariantCulture)); + } + } + + /// + /// Append a switch to the command line if the condition is true. + /// + /// Switch to append. + /// Condition specified by the user. + public void AppendIfTrue(string switchName, bool condition) + { + if (condition) + { + this.AppendSwitch(switchName); + } + } + + /// + /// Append a switch to the command line if any values in the array have been specified. + /// + /// Switch to append. + /// Values specified by the user. + public void AppendArrayIfNotNull(string switchName, ITaskItem[] values) + { + if (values != null) + { + foreach (ITaskItem value in values) + { + this.AppendSwitchIfNotNull(switchName, value); + } + } + } + + /// + /// Append a switch to the command line if any values in the array have been specified. + /// + /// Switch to append. + /// Values specified by the user. + public void AppendArrayIfNotNull(string switchName, string[] values) + { + if (values != null) + { + foreach (string value in values) + { + this.AppendSwitchIfNotNull(switchName, value); + } + } + } + + /// + /// Build the extensions argument. Each extension is searched in the current folder, user defined search + /// directories (ReferencePath), HintPath, and under Wix Extension Directory in that order. + /// The order of precednce is based off of that described in Microsoft.Common.Targets's SearchPaths + /// property for the ResolveAssemblyReferences task. + /// + /// The list of extensions to include. + /// Evaluated default folder for Wix Extensions + /// User defined reference directories to search in + public void AppendExtensions(ITaskItem[] extensions, string wixExtensionDirectory, string [] referencePaths) + { + if (extensions == null) + { + return; + } + + string resolvedPath; + + foreach (ITaskItem extension in extensions) + { + string className = extension.GetMetadata("Class"); + + string fileName = Path.GetFileName(extension.ItemSpec); + + if (Path.GetExtension(fileName).Length == 0) + { + fileName += ".dll"; + } + + // First try reference paths + resolvedPath = FileSearchHelperMethods.SearchFilePaths(referencePaths, fileName); + + if (String.IsNullOrEmpty(resolvedPath)) + { + // Now try HintPath + resolvedPath = extension.GetMetadata("HintPath"); + + if (!File.Exists(resolvedPath)) + { + // Now try the item itself + resolvedPath = extension.ItemSpec; + + if (Path.GetExtension(resolvedPath).Length == 0) + { + resolvedPath += ".dll"; + } + + if (!File.Exists(resolvedPath)) + { + if (!String.IsNullOrEmpty(wixExtensionDirectory)) + { + // Now try the extension directory + resolvedPath = Path.Combine(wixExtensionDirectory, Path.GetFileName(resolvedPath)); + } + + if (!File.Exists(resolvedPath)) + { + // Extesnion wasn't found, just set it to the extension name passed in + resolvedPath = extension.ItemSpec; + } + } + } + } + + if (String.IsNullOrEmpty(className)) + { + this.AppendSwitchIfNotNull("-ext ", resolvedPath); + } + else + { + this.AppendSwitchIfNotNull("-ext ", className + ", " + resolvedPath); + } + } + } + + /// + /// Append arbitrary text to the command-line if specified. + /// + /// Text to append. + public void AppendTextIfNotNull(string textToAppend) + { + if (!String.IsNullOrEmpty(textToAppend)) + { + this.AppendSpaceIfNotEmpty(); + this.AppendTextUnquoted(textToAppend); + } + } + + /// + /// Append arbitrary text to the command-line if specified. + /// + /// Text to append. + public void AppendTextIfNotWhitespace(string textToAppend) + { + if (!String.IsNullOrWhiteSpace(textToAppend)) + { + this.AppendSpaceIfNotEmpty(); + this.AppendTextUnquoted(textToAppend); + } + } + } +} diff --git a/src/WixToolset.BuildTasks/WixToolTask.cs b/src/WixToolset.BuildTasks/WixToolTask.cs new file mode 100644 index 00000000..2e5e8705 --- /dev/null +++ b/src/WixToolset.BuildTasks/WixToolTask.cs @@ -0,0 +1,406 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Reflection; + using System.Text; + using System.Threading; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// Base class for WiX tool tasks; executes tools in-process + /// so that repeated invocations are much faster. + /// + public abstract class WixToolTask : ToolTask, IDisposable + { + private string additionalOptions; + private bool disposed; + private bool noLogo; + private bool runAsSeparateProcess; + private bool suppressAllWarnings; + private string[] suppressSpecificWarnings; + private string[] treatSpecificWarningsAsErrors; + private bool treatWarningsAsErrors; + private bool verboseOutput; + private Queue messageQueue; + private ManualResetEvent messagesAvailable; + private ManualResetEvent toolExited; + private int exitCode; + + /// + /// Gets or sets additional options that are appended the the tool command-line. + /// + /// + /// This allows the task to support extended options in the tool which are not + /// explicitly implemented as properties on the task. + /// + public string AdditionalOptions + { + get { return this.additionalOptions; } + set { this.additionalOptions = value; } + } + + /// + /// Gets or sets a flag indicating whether the task should be run as separate + /// process instead of in-proc with MSBuild which is the default. + /// + public bool RunAsSeparateProcess + { + get { return this.runAsSeparateProcess; } + set { this.runAsSeparateProcess = value; } + } + +#region Common Options + /// + /// Gets or sets whether all warnings should be suppressed. + /// + public bool SuppressAllWarnings + { + get { return this.suppressAllWarnings; } + set { this.suppressAllWarnings = value; } + } + + /// + /// Gets or sets a list of specific warnings to be suppressed. + /// + public string[] SuppressSpecificWarnings + { + get { return this.suppressSpecificWarnings; } + set { this.suppressSpecificWarnings = value; } + } + + /// + /// Gets or sets whether all warnings should be treated as errors. + /// + public bool TreatWarningsAsErrors + { + get { return this.treatWarningsAsErrors; } + set { this.treatWarningsAsErrors = value; } + } + + /// + /// Gets or sets a list of specific warnings to treat as errors. + /// + public string[] TreatSpecificWarningsAsErrors + { + get { return this.treatSpecificWarningsAsErrors; } + set { this.treatSpecificWarningsAsErrors = value; } + } + + /// + /// Gets or sets whether to display verbose output. + /// + public bool VerboseOutput + { + get { return this.verboseOutput; } + set { this.verboseOutput = value; } + } + + /// + /// Gets or sets whether to display the logo. + /// + public bool NoLogo + { + get { return this.noLogo; } + set { this.noLogo = value; } + } +#endregion + + /// + /// Cleans up the ManualResetEvent members + /// + public void Dispose() + { + if (!this.disposed) + { + this.Dispose(true); + GC.SuppressFinalize(this); + disposed = true; + } + } + + /// + /// Cleans up the ManualResetEvent members + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + messagesAvailable.Close(); + toolExited.Close(); + } + } + + /// + /// Generate the command line arguments to write to the response file from the properties. + /// + /// Command line string. + protected override string GenerateResponseFileCommands() + { + WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder(); + this.BuildCommandLine(commandLineBuilder); + return commandLineBuilder.ToString(); + } + + /// + /// Builds a command line from options in this and derivative tasks. + /// + /// + /// Derivative classes should call BuildCommandLine() on the base class to ensure that common command line options are added to the command. + /// + protected virtual void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo); + commandLineBuilder.AppendArrayIfNotNull("-sw", this.SuppressSpecificWarnings); + commandLineBuilder.AppendIfTrue("-sw", this.SuppressAllWarnings); + commandLineBuilder.AppendIfTrue("-v", this.VerboseOutput); + commandLineBuilder.AppendArrayIfNotNull("-wx", this.TreatSpecificWarningsAsErrors); + commandLineBuilder.AppendIfTrue("-wx", this.TreatWarningsAsErrors); + } + + /// + /// Executes a tool in-process by loading the tool assembly and invoking its entrypoint. + /// + /// Path to the tool to be executed; must be a managed executable. + /// Commands to be written to a response file. + /// Commands to be passed directly on the command-line. + /// The tool exit code. + protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) + { + if (this.RunAsSeparateProcess) + { + return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); + } + + this.messageQueue = new Queue(); + this.messagesAvailable = new ManualResetEvent(false); + this.toolExited = new ManualResetEvent(false); + + Util.RunningInMsBuild = true; + + WixToolTaskLogger logger = new WixToolTaskLogger(this.messageQueue, this.messagesAvailable); + TextWriter saveConsoleOut = Console.Out; + TextWriter saveConsoleError = Console.Error; + Console.SetOut(logger); + Console.SetError(logger); + + string responseFile = null; + try + { + string responseFileSwitch; + responseFile = this.GetTemporaryResponseFile(responseFileCommands, out responseFileSwitch); + if (!String.IsNullOrEmpty(responseFileSwitch)) + { + commandLineCommands = commandLineCommands + " " + responseFileSwitch; + } + + string[] arguments = CommandLineResponseFile.ParseArgumentsToArray(commandLineCommands); + + Thread toolThread = new Thread(new ParameterizedThreadStart(this.ExecuteToolThread)); + toolThread.Start(new object[] { pathToTool, arguments }); + + this.HandleToolMessages(); + + if (this.exitCode == 0 && this.Log.HasLoggedErrors) + { + this.exitCode = -1; + } + + return this.exitCode; + } + finally + { + if (responseFile != null) + { + File.Delete(responseFile); + } + + Console.SetOut(saveConsoleOut); + Console.SetError(saveConsoleError); + } + } + + /// + /// Called by a new thread to execute the tool in that thread. + /// + /// Tool path and arguments array. + private void ExecuteToolThread(object parameters) + { + try + { + object[] pathAndArguments = (object[])parameters; + Assembly toolAssembly = Assembly.LoadFrom((string)pathAndArguments[0]); + this.exitCode = (int)toolAssembly.EntryPoint.Invoke(null, new object[] { pathAndArguments[1] }); + } + catch (FileNotFoundException fnfe) + { + Log.LogError("Unable to load tool from path {0}. Consider setting the ToolPath parameter to $(WixToolPath).", fnfe.FileName); + this.exitCode = -1; + } + catch (Exception ex) + { + this.exitCode = -1; + this.LogEventsFromTextOutput(ex.Message, MessageImportance.High); + foreach (string stackTraceLine in ex.StackTrace.Split('\n')) + { + this.LogEventsFromTextOutput(stackTraceLine.TrimEnd(), MessageImportance.High); + } + + throw; + } + finally + { + this.toolExited.Set(); + } + } + + /// + /// Waits for messages from the tool thread and sends them to the MSBuild logger on the original thread. + /// Returns when the tool thread exits. + /// + private void HandleToolMessages() + { + WaitHandle[] waitHandles = new WaitHandle[] { this.messagesAvailable, this.toolExited }; + while (WaitHandle.WaitAny(waitHandles) == 0) + { + lock (this.messageQueue) + { + while (this.messageQueue.Count > 0) + { + this.LogEventsFromTextOutput(messageQueue.Dequeue(), MessageImportance.Normal); + } + + this.messagesAvailable.Reset(); + } + } + } + + /// + /// Creates a temporary response file for tool execution. + /// + /// Path to the response file. + /// + /// The temporary file should be deleted after the tool execution is finished. + /// + private string GetTemporaryResponseFile(string responseFileCommands, out string responseFileSwitch) + { + string responseFile = null; + responseFileSwitch = null; + + if (!String.IsNullOrEmpty(responseFileCommands)) + { + responseFile = Path.GetTempFileName(); + using (StreamWriter writer = new StreamWriter(responseFile, false, this.ResponseFileEncoding)) + { + writer.Write(responseFileCommands); + } + responseFileSwitch = this.GetResponseFileSwitch(responseFile); + } + return responseFile; + } + + /// + /// Cycles thru each task to find correct path of the file in question. + /// Looks at item spec, hintpath and then in user defined Reference Paths + /// + /// Input task array + /// SemiColon delimited directories to search + /// List of task item file paths + [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] + protected static List AdjustFilePaths(ITaskItem[] tasks, string[] referencePaths) + { + List sourceFilePaths = new List(); + + if (tasks == null) + { + return sourceFilePaths; + } + + foreach (ITaskItem task in tasks) + { + string filePath = task.ItemSpec; + if (!File.Exists(filePath)) + { + filePath = task.GetMetadata("HintPath"); + if (!File.Exists(filePath)) + { + string searchPath = FileSearchHelperMethods.SearchFilePaths(referencePaths, filePath); + if (!String.IsNullOrEmpty(searchPath)) + { + filePath = searchPath; + } + } + } + sourceFilePaths.Add(filePath); + } + + return sourceFilePaths; + } + + /// + /// Used as a replacement for Console.Out to capture output from a tool + /// and redirect it to the MSBuild logging system. + /// + private class WixToolTaskLogger : TextWriter + { + private StringBuilder buffer; + private Queue messageQueue; + private ManualResetEvent messagesAvailable; + + /// + /// Creates a new logger that sends tool output to the tool task's log handler. + /// + public WixToolTaskLogger(Queue messageQueue, ManualResetEvent messagesAvailable) : base(CultureInfo.CurrentCulture) + { + this.messageQueue = messageQueue; + this.messagesAvailable = messagesAvailable; + this.buffer = new StringBuilder(); + } + + /// + /// Gets the encoding of the logger. + /// + public override Encoding Encoding + { + get { return Encoding.Unicode; } + } + + /// + /// Redirects output to a buffer; watches for newlines and sends each line to the + /// MSBuild logging system. + /// + /// Character being written. + /// All other Write() variants eventually call into this one. + public override void Write(char value) + { + lock (this.messageQueue) + { + if (value == '\n') + { + if (this.buffer.Length > 0 && this.buffer[this.buffer.Length - 1] == '\r') + { + this.buffer.Length = this.buffer.Length - 1; + } + + this.messageQueue.Enqueue(this.buffer.ToString()); + this.messagesAvailable.Set(); + + this.buffer.Length = 0; + } + else + { + this.buffer.Append(value); + } + } + } + } + } +} diff --git a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj new file mode 100644 index 00000000..34a1a9f5 --- /dev/null +++ b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj @@ -0,0 +1,38 @@ + + + + + + net462 + + WiX Toolset MSBuild Tasks + + + + + NU1701 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.BuildTasks/heatdirectory.cs b/src/WixToolset.BuildTasks/heatdirectory.cs new file mode 100644 index 00000000..1d5f104a --- /dev/null +++ b/src/WixToolset.BuildTasks/heatdirectory.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using Microsoft.Build.Framework; + + public sealed class HeatDirectory : HeatTask + { + private string directory; + private bool keepEmptyDirectories; + private bool suppressCom; + private bool suppressRootDirectory; + private bool suppressRegistry; + private string template; + private string componentGroupName; + private string directoryRefId; + private string preprocessorVariable; + + public string ComponentGroupName + { + get { return this.componentGroupName; } + set { this.componentGroupName = value; } + } + + [Required] + public string Directory + { + get { return this.directory; } + set { this.directory = value; } + } + + public string DirectoryRefId + { + get { return this.directoryRefId; } + set { this.directoryRefId = value; } + } + + public bool KeepEmptyDirectories + { + get { return this.keepEmptyDirectories; } + set { this.keepEmptyDirectories = value; } + } + + public string PreprocessorVariable + { + get { return this.preprocessorVariable; } + set { this.preprocessorVariable = value; } + } + + public bool SuppressCom + { + get { return this.suppressCom; } + set { this.suppressCom = value; } + } + + public bool SuppressRootDirectory + { + get { return this.suppressRootDirectory; } + set { this.suppressRootDirectory = value; } + } + + public bool SuppressRegistry + { + get { return this.suppressRegistry; } + set { this.suppressRegistry = value; } + } + + public string Template + { + get { return this.template; } + set { this.template = value; } + } + + protected override string OperationName + { + get { return "dir"; } + } + + /// + /// Generate the command line arguments to write to the response file from the properties. + /// + /// Command line string. + protected override string GenerateResponseFileCommands() + { + WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder(); + + commandLineBuilder.AppendSwitch(this.OperationName); + commandLineBuilder.AppendFileNameIfNotNull(this.Directory); + + commandLineBuilder.AppendSwitchIfNotNull("-cg ", this.ComponentGroupName); + commandLineBuilder.AppendSwitchIfNotNull("-dr ", this.DirectoryRefId); + commandLineBuilder.AppendIfTrue("-ke", this.KeepEmptyDirectories); + commandLineBuilder.AppendIfTrue("-scom", this.SuppressCom); + commandLineBuilder.AppendIfTrue("-sreg", this.SuppressRegistry); + commandLineBuilder.AppendIfTrue("-srd", this.SuppressRootDirectory); + commandLineBuilder.AppendSwitchIfNotNull("-template ", this.Template); + commandLineBuilder.AppendSwitchIfNotNull("-var ", this.PreprocessorVariable); + + base.BuildCommandLine(commandLineBuilder); + return commandLineBuilder.ToString(); + } + } +} diff --git a/src/WixToolset.BuildTasks/heatfile.cs b/src/WixToolset.BuildTasks/heatfile.cs new file mode 100644 index 00000000..69e11b88 --- /dev/null +++ b/src/WixToolset.BuildTasks/heatfile.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using Microsoft.Build.Framework; + + public sealed class HeatFile : HeatTask + { + private string file; + private bool suppressCom; + private bool suppressRegistry; + private bool suppressRootDirectory; + private string template; + private string componentGroupName; + private string directoryRefId; + private string preprocessorVariable; + + public string ComponentGroupName + { + get { return this.componentGroupName; } + set { this.componentGroupName = value; } + } + + public string DirectoryRefId + { + get { return this.directoryRefId; } + set { this.directoryRefId = value; } + } + + [Required] + public string File + { + get { return this.file; } + set { this.file = value; } + } + + public string PreprocessorVariable + { + get { return this.preprocessorVariable; } + set { this.preprocessorVariable = value; } + } + + public bool SuppressCom + { + get { return this.suppressCom; } + set { this.suppressCom = value; } + } + + public bool SuppressRegistry + { + get { return this.suppressRegistry; } + set { this.suppressRegistry = value; } + } + + public bool SuppressRootDirectory + { + get { return this.suppressRootDirectory; } + set { this.suppressRootDirectory = value; } + } + + public string Template + { + get { return this.template; } + set { this.template = value; } + } + + protected override string OperationName + { + get { return "file"; } + } + + /// + /// Generate the command line arguments to write to the response file from the properties. + /// + /// Command line string. + protected override string GenerateResponseFileCommands() + { + WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder(); + + commandLineBuilder.AppendSwitch(this.OperationName); + commandLineBuilder.AppendFileNameIfNotNull(this.File); + + commandLineBuilder.AppendSwitchIfNotNull("-cg ", this.ComponentGroupName); + commandLineBuilder.AppendSwitchIfNotNull("-dr ", this.DirectoryRefId); + commandLineBuilder.AppendIfTrue("-scom", this.SuppressCom); + commandLineBuilder.AppendIfTrue("-srd", this.SuppressRootDirectory); + commandLineBuilder.AppendIfTrue("-sreg", this.SuppressRegistry); + commandLineBuilder.AppendSwitchIfNotNull("-template ", this.Template); + commandLineBuilder.AppendSwitchIfNotNull("-var ", this.PreprocessorVariable); + + base.BuildCommandLine(commandLineBuilder); + return commandLineBuilder.ToString(); + } + } +} diff --git a/src/WixToolset.BuildTasks/heatproject.cs b/src/WixToolset.BuildTasks/heatproject.cs new file mode 100644 index 00000000..8620ffa3 --- /dev/null +++ b/src/WixToolset.BuildTasks/heatproject.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using Microsoft.Build.Framework; + + public sealed class HeatProject : HeatTask + { + private string configuration; + private string directoryIds; + private string generateType; + private bool generateWixVariables; + private string platform; + private string project; + private string projectName; + private string[] projectOutputGroups; + + public string Configuration + { + get { return this.configuration; } + set { this.configuration = value; } + } + + public string DirectoryIds + { + get { return this.directoryIds; } + set { this.directoryIds = value; } + } + + public bool GenerateWixVariables + { + get { return this.generateWixVariables; } + set { this.generateWixVariables = value; } + } + + public string GenerateType + { + get { return this.generateType; } + set { this.generateType = value; } + } + + public string Platform + { + get { return this.platform; } + set { this.platform = value; } + } + + [Required] + public string Project + { + get { return this.project; } + set { this.project = value; } + } + + public string ProjectName + { + get { return this.projectName; } + set { this.projectName = value; } + } + + public string[] ProjectOutputGroups + { + get + { + return this.projectOutputGroups; + } + set + { + this.projectOutputGroups = value; + + // If it's just one string and it contains semicolons, let's + // split it into separate items. + if (this.projectOutputGroups.Length == 1) + { + this.projectOutputGroups = this.projectOutputGroups[0].Split(new char[] { ';' }); + } + } + } + + protected override string OperationName + { + get { return "project"; } + } + + /// + /// Generate the command line arguments to write to the response file from the properties. + /// + /// Command line string. + protected override string GenerateResponseFileCommands() + { + WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder(); + + commandLineBuilder.AppendSwitch(this.OperationName); + commandLineBuilder.AppendFileNameIfNotNull(this.Project); + + commandLineBuilder.AppendSwitchIfNotNull("-configuration ", this.Configuration); + commandLineBuilder.AppendSwitchIfNotNull("-directoryid ", this.DirectoryIds); + commandLineBuilder.AppendSwitchIfNotNull("-generate ", this.GenerateType); + commandLineBuilder.AppendSwitchIfNotNull("-platform ", this.Platform); + commandLineBuilder.AppendArrayIfNotNull("-pog ", this.ProjectOutputGroups); + commandLineBuilder.AppendSwitchIfNotNull("-projectname ", this.ProjectName); + commandLineBuilder.AppendIfTrue("-wixvar", this.GenerateWixVariables); + + base.BuildCommandLine(commandLineBuilder); + return commandLineBuilder.ToString(); + } + } +} diff --git a/src/WixToolset.BuildTasks/heattask.cs b/src/WixToolset.BuildTasks/heattask.cs new file mode 100644 index 00000000..bf0a2ad3 --- /dev/null +++ b/src/WixToolset.BuildTasks/heattask.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.BuildTasks +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// A base MSBuild task to run the WiX harvester. + /// Specific harvester tasks should extend this class. + /// + public abstract class HeatTask : WixToolTask + { + private const string HeatToolName = "Heat.exe"; + + private bool autogenerageGuids; + private bool generateGuidsNow; + private ITaskItem outputFile; + private bool suppressFragments; + private bool suppressUniqueIds; + private string[] transforms; + + public bool AutogenerateGuids + { + get { return this.autogenerageGuids; } + set { this.autogenerageGuids = value; } + } + + public bool GenerateGuidsNow + { + get { return this.generateGuidsNow; } + set { this.generateGuidsNow = value; } + } + + [Required] + [Output] + public ITaskItem OutputFile + { + get { return this.outputFile; } + set { this.outputFile = value; } + } + + public bool SuppressFragments + { + get { return this.suppressFragments; } + set { this.suppressFragments = value; } + } + + public bool SuppressUniqueIds + { + get { return this.suppressUniqueIds; } + set { this.suppressUniqueIds = value; } + } + + public string[] Transforms + { + get { return this.transforms; } + set { this.transforms = value; } + } + + /// + /// Get the name of the executable. + /// + /// The ToolName is used with the ToolPath to get the location of heat.exe. + /// The name of the executable. + protected override string ToolName + { + get { return HeatToolName; } + } + + /// + /// Gets the name of the heat operation performed by the task. + /// + /// This is the first parameter passed on the heat.exe command-line. + /// The name of the heat operation performed by the task. + protected abstract string OperationName + { + get; + } + + /// + /// Get the path to the executable. + /// + /// GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above). + /// The full path to the executable or simply heat.exe if it's expected to be in the system path. + protected override string GenerateFullPathToTool() + { + // If there's not a ToolPath specified, it has to be in the system path. + if (String.IsNullOrEmpty(this.ToolPath)) + { + return HeatToolName; + } + + return Path.Combine(Path.GetFullPath(this.ToolPath), HeatToolName); + } + + /// + /// Builds a command line from options in this task. + /// + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendIfTrue("-ag", this.AutogenerateGuids); + commandLineBuilder.AppendIfTrue("-gg", this.GenerateGuidsNow); + commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo); + commandLineBuilder.AppendIfTrue("-sfrag", this.SuppressFragments); + commandLineBuilder.AppendIfTrue("-suid", this.SuppressUniqueIds); + commandLineBuilder.AppendArrayIfNotNull("-sw", this.SuppressSpecificWarnings); + commandLineBuilder.AppendArrayIfNotNull("-t ", this.Transforms); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile); + } + } +} diff --git a/src/WixToolset.BuildTasks/redirect.wix.ca.targets b/src/WixToolset.BuildTasks/redirect.wix.ca.targets new file mode 100644 index 00000000..74e6ec3c --- /dev/null +++ b/src/WixToolset.BuildTasks/redirect.wix.ca.targets @@ -0,0 +1,11 @@ + + + + + + + $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\WiX Toolset\v4', 'InstallFolder', null, RegistryView.Registry32)) + + + + diff --git a/src/WixToolset.BuildTasks/redirect.wix.targets b/src/WixToolset.BuildTasks/redirect.wix.targets new file mode 100644 index 00000000..b40c4c36 --- /dev/null +++ b/src/WixToolset.BuildTasks/redirect.wix.targets @@ -0,0 +1,11 @@ + + + + + + + $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\WiX Toolset\v4', 'InstallFolder', null, RegistryView.Registry32)) + + + + diff --git a/src/WixToolset.BuildTasks/wix.ca.targets b/src/WixToolset.BuildTasks/wix.ca.targets new file mode 100644 index 00000000..4578c2d8 --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.ca.targets @@ -0,0 +1,123 @@ + + + + + + + + + + true + + $(TargetName).CA$(TargetExt) + + $(MSBuildThisFileDirectory) + $(WixSdkPath)x86\ + $(WixSdkPath)x64\ + + $(WixSdkPath)MakeSfxCA.exe + $(WixSdkX64Path)SfxCA.dll + $(WixSdkX86Path)SfxCA.dll + + + + + + + + + + + + + + + + + + + @(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.BuildTasks/wix.harvest.targets b/src/WixToolset.BuildTasks/wix.harvest.targets new file mode 100644 index 00000000..e94dfcea --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.harvest.targets @@ -0,0 +1,511 @@ + + + + + + + + + $(MSBuildThisFileFullPath) + $(WixTargetsPath)WixTasks.dll + + + + + + + + + + + + + $(NoLogo) + $(SuppressAllWarnings) + $(SuppressSpecificWarnings) + $(TreatWarningsAsErrors) + $(TreatSpecificWarningsAsErrors) + $(VerboseOutput) + true + false + true + false + + + + + + false + + $(HarvestNoLogo) + $(HarvestSuppressAllWarnings) + $(HarvestSuppressSpecificWarnings) + $(HarvestTreatWarningsAsErrors) + $(HarvestTreatSpecificWarningsAsErrors) + $(HarvestVerboseOutput) + $(HarvestAutogenerateGuids) + $(HarvestGenerateGuidsNow) + $(HarvestSuppressFragments) + $(HarvestSuppressUniqueIds) + $(HarvestTransforms) + $(IntermediateOutputPath)Product.Generated.wxs + $(IntermediateOutputPath)Bundle.Generated.wxs + + + + + $(HarvestNoLogo) + $(HarvestSuppressAllWarnings) + $(HarvestSuppressSpecificWarnings) + $(HarvestTreatWarningsAsErrors) + $(HarvestTreatSpecificWarningsAsErrors) + $(HarvestVerboseOutput) + $(HarvestAutogenerateGuids) + $(HarvestGenerateGuidsNow) + $(HarvestSuppressFragments) + $(HarvestSuppressUniqueIds) + $(HarvestTransforms) + + + + + $(HarvestNoLogo) + $(HarvestSuppressAllWarnings) + $(HarvestSuppressSpecificWarnings) + $(HarvestTreatWarningsAsErrors) + $(HarvestTreatSpecificWarningsAsErrors) + $(HarvestVerboseOutput) + $(HarvestAutogenerateGuids) + $(HarvestGenerateGuidsNow) + $(HarvestSuppressFragments) + $(HarvestSuppressUniqueIds) + $(HarvestTransforms) + + + + + + ConvertReferences; + ConvertBundleReferences; + HarvestProjects; + HarvestDirectory; + HarvestFile; + GenerateCode; + + + + + + + + RefreshGeneratedFile; + RefreshBundleGeneratedFile + + + + + + + + + <_HeatProjectReference Include="@(_MSBuildProjectReferenceExistent)" Condition=" '%(_MSBuildProjectReferenceExistent.DoNotHarvest)' == '' "> + %(_MSBuildProjectReferenceExistent.RefTargetDir) + Binaries;Symbols;Sources;Content;Satellites;Documents + %(_MSBuildProjectReferenceExistent.Name) + $(IntermediateOutputPath)_%(_MSBuildProjectReferenceExistent.Filename).wxs + + + + + + + + + + <_GeneratedFiles Include="$(HarvestProjectsGeneratedFile)" /> + + + + + + + + + <_HeatProjectReference Include="@(_MSBuildProjectReferenceExistent)" Condition=" '%(_MSBuildProjectReferenceExistent.DoNotHarvest)' == '' "> + Binaries;Symbols;Sources;Content;Satellites;Documents + payloadgroup + $(IntermediateOutputPath)_%(_MSBuildProjectReferenceExistent.Filename).wxs + + + + + + + + + + <_GeneratedFiles Include="$(HarvestProjectsGeneratedFile)" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(HarvestProjectsTransforms) + $(HarvestProjectsProjectOutputGroups) + $(HarvestProjectsDirectoryIds) + + + $(HarvestProjectsTransforms) + $(HarvestProjectsProjectOutputGroups) + $(HarvestProjectsDirectoryIds) + + + + + CombineHarvestProjects + + + + + + + + + + + + + + + + + $(HarvestDirectoryTransforms) + $(HarvestDirectoryComponentGroupName) + $(HarvestDirectoryDirectoryRefId) + $(HarvestDirectoryKeepEmptyDirectories) + $(HarvestDirectoryPreprocessorVariable) + $(HarvestDirectorySuppressCom) + $(HarvestDirectorySuppressRootDirectory) + $(HarvestDirectorySuppressRegistry) + + + + + + GetHarvestDirectoryContent + + + + + + + + + + + + + + + + + + + + + + + + + + $(HarvestFileTransforms) + $(HarvestFileComponentGroupName) + $(HarvestFileDirectoryRefId) + $(HarvestFilePreprocessorVariable) + $(HarvestFileSuppressCom) + $(HarvestFileSuppressRegistry) + $(HarvestFileSuppressRootDirectory) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.BuildTasks/wix.signing.targets b/src/WixToolset.BuildTasks/wix.signing.targets new file mode 100644 index 00000000..6351cc8b --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.signing.targets @@ -0,0 +1,378 @@ + + + + + + + + + $(MSBuildThisFileFullPath) + $(WixTargetsPath)WixTasks.dll + + $(MSBuildProjectFile).Signed.txt + + + + + + + $(NoLogo) + $(SuppressAllWarnings) + $(SuppressSpecificWarnings) + $(TreatWarningsAsErrors) + $(TreatSpecificWarningsAsErrors) + $(VerboseOutput) + + + + + + GetMsmsToSign; + InternalSignMsm; + + + GetCabsToSign; + GetMsiToSign; + InternalSignCabs; + InscribeMsi; + InternalSignMsi; + + + GetContainersToSign; + InternalSignContainers; + InscribeBundleEngine; + InternalSignBundleEngine; + InscribeBundle; + InternalSignBundle; + + + + CompileAndLink; + BeforeSigning; + $(InternalSignDependsOn); + AfterSigning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PrepareForBuild; + ResolveWixExtensionReferences; + CompileAndLink; + InternalSignCabs + + + + + + + + + + + PrepareForBuild; + ResolveWixExtensionReferences; + CompileAndLink; + InternalSignContainers + + + + + + + + + + + + + + + + + + + PrepareForBuild; + ResolveWixExtensionReferences; + CompileAndLink; + InternalSignBundleEngine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.BuildTasks/wix.targets b/src/WixToolset.BuildTasks/wix.targets new file mode 100644 index 00000000..eadd33ec --- /dev/null +++ b/src/WixToolset.BuildTasks/wix.targets @@ -0,0 +1,1353 @@ + + + + + + + true + + + + + + + + + + $(MSBuildThisFileDirectory) + $(WixBinDir)WixToolset.BuildTasks.dll + $(WixBinDir)wix.harvest.targets + $(WixBinDir)wix.signing.targets + $(WixBinDir)lux.targets + $(WixBinDir)LuxTasks.dll + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildAllProjects);$(WixHarvestTargetsPath) + $(MSBuildAllProjects);$(WixSigningTargetsPath) + $(MSBuildAllProjects);$(LuxTargetsPath) + $(MSBuildAllProjects);$(CustomBeforeWixTargets) + $(MSBuildAllProjects);$(CustomAfterWixTargets) + + + + + + .wxs + wix + Managed + + + $(MSBuildProjectName) + $(OutputName) + + + <_OriginalOutputType>$(OutputType) + Package + + + + + .msi + .msm + .pcp + .wixlib + .exe + + + + + <_DebugSymbolsIntermediatePath Include="$(PdbOutputDir)$(TargetPdbName)" Condition=" '@(_DebugSymbolsIntermediatePath)' == '' " /> + + + + + + + $(IntermediateOutputPath) + $(PdbOutputDir)\ + + + $([System.IO.Path]::GetFullPath(`$([System.IO.Path]::Combine(`$(MSBuildProjectDirectory)`, `$(PdbOutputDir)`))`)) + + + $(TargetName).wixpdb + + + $(TargetPdbDir)$(TargetPdbName) + + + + + + + + + + + + + + + + + + + + + + + + true + + + + $(MSBuildProjectFile).BindContentsFileList.txt + $(MSBuildProjectFile).BindOutputsFileList.txt + $(MSBuildProjectFile).BindBuiltOutputsFileList.txt + + + + $(IntermediateOutputPath)cabcache\ + + + + $(WixBinDir) + $(WixToolDir) + + + + + + + + + + + + + + + + + $(NoLogo) + $(SuppressAllWarnings) + $(SuppressSpecificWarnings) + $(TreatWarningsAsErrors) + $(TreatSpecificWarningsAsErrors) + $(VerboseOutput) + + $(Platform) + + + + + $(NoLogo) + $(BindFiles) + $(Pedantic) + $(SuppressAllWarnings) + $(SuppressSpecificWarnings) + $(SuppressSchemaValidation) + $(SuppressIntermediateFileVersionMatching) + $(TreatWarningsAsErrors) + $(TreatSpecificWarningsAsErrors) + $(VerboseOutput) + + + + + $(NoLogo) + $(BindFiles) + $(Pedantic) + $(SuppressAllWarnings) + $(SuppressSpecificWarnings) + $(SuppressSchemaValidation) + $(SuppressIntermediateFileVersionMatching) + $(TreatWarningsAsErrors) + $(TreatSpecificWarningsAsErrors) + $(VerboseOutput) + + + + + + + + + + + + + + + + + + <_PleaseSetThisInProjectFile>Please set this in the project file before the <Import> of the wix.targets file. + <_OutputTypeDescription>The OutputType defines whether a Windows Installer package (.msi), PatchCreation (.pcp), merge module (.msm), wix library (.wixlib), or self-extracting executable (.exe) is being built. $(_PleaseSetThisInProjectFile) Possible values are 'Package', 'Module', 'Library', and 'Bundle'. + + + + + + + + + + + + + + + + + + + BuildOnlySettings; + PrepareForBuild; + PreBuildEvent; + ResolveReferences; + + + DoIt; + Signing; + + GetTargetPath; + PrepareForRun; + IncrementalClean; + PostBuildEvent + + + + + + + + + + BeforeResolveReferences; + AssignProjectConfiguration; + ResolveProjectReferences; + ResolveWixLibraryReferences; + ResolveWixExtensionReferences; + AfterResolveReferences + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AssignCultures + + + + + + + + + PrepareForBuild; + ResolveReferences; + BeforeCompile; + _TimeStampBeforeCompile; + Harvest; + + CalculateDefineConstants; + GenerateCompileWithObjectPath; + + AssignCultures; + ReadPreviousBindInputsAndBuiltOutputs; + + ActuallyDoIt; + + UpdateLinkFileWrites; + _TimeStampAfterCompile; + AfterCompile + + + + + + + + + $(TargetPdbDir)%(CultureGroup.OutputFolder)$(TargetPdbName) + + + + + + + + + + + + + + + ResolveReferences; + BeforeCompile; + _TimeStampBeforeCompile; + Harvest; + Compile; + Lib; + Link; + UpdateLinkFileWrites; + _TimeStampAfterCompile; + AfterCompile + + + + + + + ResolveReferences + + + + + + Configuration=$(ConfigurationName); + OutDir=$(OutDir); + Platform=$(PlatformName); + ProjectDir=$(ProjectDir); + ProjectExt=$(ProjectExt); + ProjectFileName=$(ProjectFileName); + ProjectName=$(ProjectName); + ProjectPath=$(ProjectPath); + TargetDir=$(TargetDir); + TargetExt=$(TargetExt); + TargetFileName=$(TargetFileName); + TargetName=$(TargetName); + TargetPath=$(TargetPath); + + + + + $(SolutionDefineConstants);DevEnvDir=$(DevEnvDir) + $(SolutionDefineConstants);SolutionDir=$(SolutionDir) + $(SolutionDefineConstants);SolutionExt=$(SolutionExt) + $(SolutionDefineConstants);SolutionFileName=$(SolutionFileName) + $(SolutionDefineConstants);SolutionName=$(SolutionName) + $(SolutionDefineConstants);SolutionPath=$(SolutionPath) + + + + + + + + + + + + + + + + + + + + + + + + PrepareForBuild; + ResolveReferences; + CalculateDefineConstants; + GenerateCompileWithObjectPath + + + + + + + + + + + + + + + + + + PrepareForBuild; + ResolveReferences + + + + + + + + + + + + + + + + + + <_TargetPathItems Include="$(TargetDir)%(CultureGroup.OutputFolder)$(TargetName)$(TargetExt)" /> + <_TargetPdbPathItems Include="$(TargetPdbDir)%(CultureGroup.OutputFolder)$(TargetPdbName)" /> + + + + + @(_TargetPathItems) + @(_TargetPdbPathItems) + + + + + + + + + + + + + + + + + + + + + + + + + + + PrepareForBuild; + ResolveReferences; + AssignCultures; + ReadPreviousBindInputsAndBuiltOutputs; + + + + + + $(TargetPdbDir)%(CultureGroup.OutputFolder)$(TargetPdbName) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(TargetPath) + $(TargetFileName) + + + + + + PrepareForBuild;AssignCultures + + + + + + + + + + + + + <_BuiltProjectOutputGroupOutputIntermediate Include="$(TargetPath)" /> + + + + + + + %(_BuiltProjectOutputGroupOutputIntermediate.FullPath) + + + + + + + AssignCultures + + + + + + + + + + + + + + + + true + + + + true + true + $([System.IO.Path]::GetFullPath($(IntermediateOutputPath))) + + + + + + + + + <_FullPathToCopy Include="$(TargetPath)" Condition=" '@(_FullPathToCopy)'=='' " /> + <_RelativePath Include="$([MSBuild]::MakeRelative($(FullIntermediateOutputPath), %(_FullPathToCopy.Identity)))" /> + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb