// 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; } } }