From 08f53f409020b12dffaa2aeefa943b667a4b9328 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 14 Oct 2022 09:34:30 -0700 Subject: Simplify reference resolution WiX v3 extension loading had options that were rarely if ever used and library paths modeled after C++. Given the new Sdk-style model in WiX v4, we can simplify reference resolution. Fixes 6945, 6946 --- .../FileSearchHelperMethods.cs | 57 -------- .../WixCommandLineBuilder.cs | 73 ---------- .../WixToolset.BuildTasks/ResolveWixReferences.cs | 147 +++++++++++---------- src/wix/WixToolset.BuildTasks/WixBuild.cs | 6 +- src/wix/WixToolset.Sdk/tools/wix.targets | 41 ++---- src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs | 75 ++++++++--- .../TestData/WixlibMissingExtension/Library.wxs | 7 + .../WixlibMissingExtension.wixproj | 18 +++ 8 files changed, 173 insertions(+), 251 deletions(-) delete mode 100644 src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/Library.wxs create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/WixlibMissingExtension.wixproj diff --git a/src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs b/src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs deleted file mode 100644 index 442fedd6..00000000 --- a/src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 index 152992dd..d950bca9 100644 --- a/src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs +++ b/src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs @@ -75,79 +75,6 @@ namespace WixToolset.BaseBuildTasks } } - /// - /// 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. /// diff --git a/src/wix/WixToolset.BuildTasks/ResolveWixReferences.cs b/src/wix/WixToolset.BuildTasks/ResolveWixReferences.cs index 8d8db428..d11b0399 100644 --- a/src/wix/WixToolset.BuildTasks/ResolveWixReferences.cs +++ b/src/wix/WixToolset.BuildTasks/ResolveWixReferences.cs @@ -4,9 +4,10 @@ namespace WixToolset.BuildTasks { using System; using System.Collections.Generic; - using Microsoft.Build.Utilities; - using Microsoft.Build.Framework; using System.IO; + using System.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; /// /// This task searches for paths to references using the order specified in SearchPaths. @@ -15,14 +16,14 @@ namespace WixToolset.BuildTasks { /// /// 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. + /// 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. + /// be searched as a full file path to resolve the reference. /// Must match wix.targets, case sensitive. /// private const string RawFileNameToken = "{RawFileName}"; @@ -34,24 +35,18 @@ namespace WixToolset.BuildTasks 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. + /// 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. + /// {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; } @@ -66,6 +61,12 @@ namespace WixToolset.BuildTasks [Output] public ITaskItem[] ResolvedWixReferences { get; private set; } + /// + /// Output items that contain the same metadata as input references and cannot be found. + /// + [Output] + public ITaskItem[] UnresolvedWixReferences { get; private set; } + /// /// Resolves reference paths by searching for referenced items using the specified SearchPaths. /// @@ -73,16 +74,25 @@ namespace WixToolset.BuildTasks public override bool Execute() { var resolvedReferences = new List(); + var unresolvedReferences = new List(); var uniqueReferences = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var reference in this.WixReferences) + foreach (var reference in this.WixReferences.Where(r => !String.IsNullOrWhiteSpace(r.ItemSpec))) { - var resolvedReference = ResolveWixReferences.ResolveReference(reference, this.SearchPaths, this.SearchFilenameExtensions, this.Log); + (var resolvedReference, var found) = this.ResolveReference(reference, this.SearchPaths, this.SearchFilenameExtensions); if (uniqueReferences.Add(resolvedReference.ItemSpec)) { - this.Log.LogMessage(MessageImportance.Low, "Resolved path {0}", resolvedReference.ItemSpec); - resolvedReferences.Add(resolvedReference); + if (found) + { + this.Log.LogMessage(MessageImportance.Low, "Resolved path {0}", resolvedReference.ItemSpec); + resolvedReferences.Add(resolvedReference); + } + else + { + this.Log.LogWarning(null, "WXE0001", null, null, 0, 0, 0, 0, "Unable to find extension {0}.", resolvedReference.ItemSpec); + unresolvedReferences.Add(resolvedReference); + } } else { @@ -91,6 +101,7 @@ namespace WixToolset.BuildTasks } this.ResolvedWixReferences = resolvedReferences.ToArray(); + this.UnresolvedWixReferences = unresolvedReferences.ToArray(); return true; } @@ -101,80 +112,76 @@ namespace WixToolset.BuildTasks /// 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) + public (ITaskItem, bool) ResolveReference(ITaskItem reference, string[] searchPaths, string[] searchFilenameExtensions) { - if (reference == null) - { - throw new ArgumentNullException("reference"); - } + // Ensure we first check the reference without adding additional search filename extensions. + searchFilenameExtensions = searchFilenameExtensions == null ? new[] { String.Empty } : searchFilenameExtensions.Prepend(String.Empty).ToArray(); + + // Copy all the metadata from the source + var resolvedReference = new TaskItem(reference); + this.Log.LogMessage(MessageImportance.Low, "WixReference: {0}", reference.ItemSpec); + + var found = false; + // Nothing to search, so just resolve the original reference item. if (searchPaths == null) { - // Nothing to search, so just return the original reference item. - return reference; - } + if (this.ResolveFilenameExtensions(resolvedReference, resolvedReference.ItemSpec, searchFilenameExtensions)) + { + found = true; + } - if (searchFilenameExtensions == null) - { - searchFilenameExtensions = new string[] { }; + return (resolvedReference, found); } - // Copy all the metadata from the source - var resolvedReference = new TaskItem(reference); - log.LogMessage(MessageImportance.Low, "WixReference: {0}", reference.ItemSpec); - - // Now find the resolved path based on our order of precedence + // Otherwise, now try to find the resolved path based on the order of precedence from search paths. foreach (var searchPath in searchPaths) { - log.LogMessage(MessageImportance.Low, "Trying {0}", searchPath); - if (searchPath.Equals(HintPathToken, StringComparison.Ordinal)) + this.Log.LogMessage(MessageImportance.Low, "Trying {0}", searchPath); + if (HintPathToken.Equals(searchPath, StringComparison.Ordinal)) { var path = reference.GetMetadata("HintPath"); - log.LogMessage(MessageImportance.Low, "Trying path {0}", path); + if (String.IsNullOrWhiteSpace(path)) + { + continue; + } + + this.Log.LogMessage(MessageImportance.Low, "Trying path {0}", path); if (File.Exists(path)) { resolvedReference.ItemSpec = path; + found = true; break; } } - else if (searchPath.Equals(RawFileNameToken, StringComparison.Ordinal)) + else if (RawFileNameToken.Equals(searchPath, 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)) + if (this.ResolveFilenameExtensions(resolvedReference, resolvedReference.ItemSpec, searchFilenameExtensions)) { + found = true; break; } } else { - var path = Path.Combine(searchPath, Path.GetFileName(reference.ItemSpec)); - log.LogMessage(MessageImportance.Low, "Trying path {0}", path); - if (File.Exists(path)) - { - resolvedReference.ItemSpec = path; - break; - } + var path = Path.Combine(searchPath, reference.ItemSpec); - if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference, - path, searchFilenameExtensions, log)) + if (this.ResolveFilenameExtensions(resolvedReference, path, searchFilenameExtensions)) { + found = true; break; } } } - // Normalize the item path - resolvedReference.ItemSpec = resolvedReference.GetMetadata("FullPath"); + if (found) + { + // Normalize the item spec to the full path. + resolvedReference.ItemSpec = resolvedReference.GetMetadata("FullPath"); + } - return resolvedReference; + return (resolvedReference, found); } /// @@ -183,14 +190,14 @@ namespace WixToolset.BuildTasks /// 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) + private bool ResolveFilenameExtensions(ITaskItem reference, string basePath, string[] filenameExtensions) { foreach (var filenameExtension in filenameExtensions) { var path = basePath + filenameExtension; - log.LogMessage(MessageImportance.Low, "Trying path {0}", path); + this.Log.LogMessage(MessageImportance.Low, "Trying path {0}", path); + if (File.Exists(path)) { reference.ItemSpec = path; diff --git a/src/wix/WixToolset.BuildTasks/WixBuild.cs b/src/wix/WixToolset.BuildTasks/WixBuild.cs index 8990d0b7..923567ed 100644 --- a/src/wix/WixToolset.BuildTasks/WixBuild.cs +++ b/src/wix/WixToolset.BuildTasks/WixBuild.cs @@ -18,8 +18,6 @@ namespace WixToolset.BuildTasks public ITaskItem[] Extensions { get; set; } - public string ExtensionDirectory { get; set; } - public string[] IncludeSearchPaths { get; set; } public string InstallerPlatform { get; set; } @@ -45,8 +43,6 @@ namespace WixToolset.BuildTasks [Required] public ITaskItem[] SourceFiles { get; set; } - public string[] ReferencePaths { get; set; } - public ITaskItem[] BindInputPaths { get; set; } public bool BindFiles { get; set; } @@ -75,7 +71,7 @@ namespace WixToolset.BuildTasks commandLineBuilder.AppendArrayIfNotNull("-culture ", this.Cultures); commandLineBuilder.AppendArrayIfNotNull("-d ", this.DefineConstants); commandLineBuilder.AppendArrayIfNotNull("-I ", this.IncludeSearchPaths); - commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths); + commandLineBuilder.AppendArrayIfNotNull("-ext ", this.Extensions); commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath); commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory); commandLineBuilder.AppendSwitchIfNotNull("-trackingfile ", this.BindTrackingFile); diff --git a/src/wix/WixToolset.Sdk/tools/wix.targets b/src/wix/WixToolset.Sdk/tools/wix.targets index 340fb91f..6a8b6055 100644 --- a/src/wix/WixToolset.Sdk/tools/wix.targets +++ b/src/wix/WixToolset.Sdk/tools/wix.targets @@ -128,10 +128,6 @@ $(TargetPdbDir)$(TargetPdbFileName) - - $(WixBinDir) - - $(MSBuildProjectFile).BindTracking .txt @@ -362,10 +358,9 @@ By default the WixLibrarySearchPaths property is set to find libraries in the following order: - (1) $(ReferencePaths) - the reference paths property, which comes from the .USER file. + (1) $(ReferencePaths) - the reference paths property. (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}. (3) Treat the reference's Include as if it were a real file name. - (4) Path specified by the WixExtDir property. [IN] @(WixLibrary) - the list of .wixlib files. @@ -381,39 +376,33 @@ + DependsOnTargets="$(ResolveWixLibraryReferencesDependsOn)" + Condition=" '@(WixLibrary)' != ''"> - $(ReferencePaths);{HintPathFromItem};{RawFileName};$(WixExtDir) + $(ReferencePaths);{HintPathFromItem};{RawFileName} - + SearchFilenameExtensions=".wixlib"> + + - - - - - - -