From 07b3d459ea0a45cbef29b98d283edafbab26462a Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 12 Oct 2022 22:01:55 -0700 Subject: Normalize ToolsetTask implementation to call wix.exe and heat.exe Share the ToolsetTask implementation that can find .NET Core and .NET Framework with multiple architectures. Fixes 6951 --- .../BaseToolsetTask.cs | 178 +++++++++++++++++++++ .../FileSearchHelperMethods.cs | 57 +++++++ .../WixCommandLineBuilder.cs | 177 ++++++++++++++++++++ .../WixToolset.BaseBuildTasks.Sources.csproj | 36 +++++ 4 files changed, 448 insertions(+) create mode 100644 src/internal/WixToolset.BaseBuildTasks.Sources/BaseToolsetTask.cs create mode 100644 src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs create mode 100644 src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs create mode 100644 src/internal/WixToolset.BaseBuildTasks.Sources/WixToolset.BaseBuildTasks.Sources.csproj (limited to 'src/internal/WixToolset.BaseBuildTasks.Sources') diff --git a/src/internal/WixToolset.BaseBuildTasks.Sources/BaseToolsetTask.cs b/src/internal/WixToolset.BaseBuildTasks.Sources/BaseToolsetTask.cs new file mode 100644 index 00000000..d9e3b5e8 --- /dev/null +++ b/src/internal/WixToolset.BaseBuildTasks.Sources/BaseToolsetTask.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.BaseBuildTasks +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + using Microsoft.Build.Utilities; + + public abstract class BaseToolsetTask : ToolTask + { + /// + /// 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; set; } + + /// + /// Gets or sets whether to display the logo. + /// + public bool NoLogo { 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; } + + /// + /// Get the path to the executable. + /// + /// + /// ToolTask only calls GenerateFullPathToTool when the ToolPath property is not set. + /// WiX never sets the ToolPath property, but the user can through $(WixToolDir). + /// If we return only a file name, ToolTask will search the system paths for it. + /// + protected sealed override string GenerateFullPathToTool() + { + var defaultToolFullPath = this.GetDefaultToolFullPath(); + +#if NETCOREAPP + // If we're pointing at an executable use that. + if (IsSelfExecutable(defaultToolFullPath, out var finalToolFullPath)) + { + return finalToolFullPath; + } + + // Otherwise, use "dotnet.exe" to run an assembly dll. + return Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet"; +#else + return defaultToolFullPath; +#endif + } + + /// + /// 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); + commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions); + } + + protected sealed override string GenerateResponseFileCommands() + { + var commandLineBuilder = new WixCommandLineBuilder(); + this.BuildCommandLine(commandLineBuilder); + return commandLineBuilder.ToString(); + } + +#if NETCOREAPP + protected override string GenerateCommandLineCommands() + { + // If the target tool path is an executable, we don't need to add anything to the command-line. + var toolFullPath = this.GetToolFullPath(); + + if (IsSelfExecutable(toolFullPath, out var finalToolFullPath)) + { + return null; + } + else // we're using "dotnet.exe" to run the assembly so add "exec" plus path to the command-line. + { + return $"exec \"{finalToolFullPath}\""; + } + } + + private static bool IsSelfExecutable(string proposedToolFullPath, out string finalToolFullPath) + { + var toolFullPathWithoutExtension = Path.Combine(Path.GetDirectoryName(proposedToolFullPath), Path.GetFileNameWithoutExtension(proposedToolFullPath)); + var exeExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : String.Empty; + var exeToolFullPath = $"{toolFullPathWithoutExtension}{exeExtension}"; + if (File.Exists(exeToolFullPath)) + { + finalToolFullPath = exeToolFullPath; + return true; + } + + finalToolFullPath = $"{toolFullPathWithoutExtension}.dll"; + return false; + } +#else + private static string GetArchitectureFolder(string baseFolder) + { + // First try to find a folder that matches this task's architecture. + var folder = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + + if (Directory.Exists(Path.Combine(baseFolder, folder))) + { + return folder; + } + + // Try to fallback to "x86" folder. + if (folder != "x86" && Directory.Exists(Path.Combine(baseFolder, "x86"))) + { + return "x86"; + } + + // Return empty, even though this isn't likely to be useful. + return String.Empty; + } +#endif + + private string GetDefaultToolFullPath() + { +#if NETCOREAPP + var thisTaskFolder = Path.GetDirectoryName(typeof(BaseToolsetTask).Assembly.Location); + + return Path.Combine(thisTaskFolder, this.ToolExe); +#else + var thisTaskFolder = Path.GetDirectoryName(new Uri(typeof(BaseToolsetTask).Assembly.CodeBase).AbsolutePath); + + var archFolder = GetArchitectureFolder(thisTaskFolder); + + return Path.Combine(thisTaskFolder, archFolder, this.ToolExe); +#endif + } + + private string GetToolFullPath() + { + if (String.IsNullOrEmpty(this.ToolPath)) + { + return this.GetDefaultToolFullPath(); + } + + return Path.Combine(this.ToolPath, this.ToolExe); + } + } +} diff --git a/src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs b/src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs new file mode 100644 index 00000000..442fedd6 --- /dev/null +++ b/src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs @@ -0,0 +1,57 @@ +// 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.BaseBuildTasks +{ + using System; + using System.IO; + + /// + /// 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; + } + + var fileName = Path.GetFileName(defaultFullPath); + foreach (var currentPath in directories) + { + if (String.IsNullOrWhiteSpace(currentPath)) + { + continue; + } + + var path = Path.Combine(currentPath, fileName); + if (File.Exists(path)) + { + return path; + } + } + + return String.Empty; + } + } +} diff --git a/src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs b/src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs new file mode 100644 index 00000000..152992dd --- /dev/null +++ b/src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs @@ -0,0 +1,177 @@ +// 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.BaseBuildTasks +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + + 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, IEnumerable 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, IEnumerable 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 precedence 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; + } + + foreach (ITaskItem extension in extensions) + { + string className = extension.GetMetadata("Class"); + + string fileName = Path.GetFileName(extension.ItemSpec); + + if (String.IsNullOrEmpty(Path.GetExtension(fileName))) + { + fileName += ".dll"; + } + + // First try reference paths + var 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 (String.IsNullOrEmpty(Path.GetExtension(resolvedPath))) + { + 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)) + { + // Extension 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/internal/WixToolset.BaseBuildTasks.Sources/WixToolset.BaseBuildTasks.Sources.csproj b/src/internal/WixToolset.BaseBuildTasks.Sources/WixToolset.BaseBuildTasks.Sources.csproj new file mode 100644 index 00000000..6ae5165e --- /dev/null +++ b/src/internal/WixToolset.BaseBuildTasks.Sources/WixToolset.BaseBuildTasks.Sources.csproj @@ -0,0 +1,36 @@ + + + + + + + + netstandard2.0 + WiX Toolset BuildTasks Foundation Sources + true + false + false + contentFiles + true + false + false + CS8021 + true + true + true + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb