aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-10-14 09:34:30 -0700
committerRob Mensching <rob@firegiant.com>2022-10-14 20:13:50 -0700
commit08f53f409020b12dffaa2aeefa943b667a4b9328 (patch)
tree42bad414ff06c19fb881c7db1b145ec1d6885521
parent07b3d459ea0a45cbef29b98d283edafbab26462a (diff)
downloadwix-08f53f409020b12dffaa2aeefa943b667a4b9328.tar.gz
wix-08f53f409020b12dffaa2aeefa943b667a4b9328.tar.bz2
wix-08f53f409020b12dffaa2aeefa943b667a4b9328.zip
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
-rw-r--r--src/internal/WixToolset.BaseBuildTasks.Sources/FileSearchHelperMethods.cs57
-rw-r--r--src/internal/WixToolset.BaseBuildTasks.Sources/WixCommandLineBuilder.cs73
-rw-r--r--src/wix/WixToolset.BuildTasks/ResolveWixReferences.cs147
-rw-r--r--src/wix/WixToolset.BuildTasks/WixBuild.cs6
-rw-r--r--src/wix/WixToolset.Sdk/tools/wix.targets41
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs75
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/Library.wxs7
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/WixlibMissingExtension.wixproj18
8 files changed, 173 insertions, 251 deletions
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 @@
1// 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.
2
3namespace WixToolset.BaseBuildTasks
4{
5 using System;
6 using System.IO;
7
8 /// <summary>
9 /// Contains helper methods on searching for files
10 /// </summary>
11 public static class FileSearchHelperMethods
12 {
13 /// <summary>
14 /// Searches for the existence of a file in multiple directories.
15 /// Search is satisfied if default file path is valid and exists. If not,
16 /// file name is extracted from default path and combined with each of the directories
17 /// looking to see if it exists. If not found, input default path is returned.
18 /// </summary>
19 /// <param name="directories">Array of directories to look in, without filenames in them</param>
20 /// <param name="defaultFullPath">Default path - to use if not found</param>
21 /// <returns>File path if file found. Empty string if not found</returns>
22 public static string SearchFilePaths(string[] directories, string defaultFullPath)
23 {
24 if (String.IsNullOrEmpty(defaultFullPath))
25 {
26 return String.Empty;
27 }
28
29 if (File.Exists(defaultFullPath))
30 {
31 return defaultFullPath;
32 }
33
34 if (directories == null)
35 {
36 return String.Empty;
37 }
38
39 var fileName = Path.GetFileName(defaultFullPath);
40 foreach (var currentPath in directories)
41 {
42 if (String.IsNullOrWhiteSpace(currentPath))
43 {
44 continue;
45 }
46
47 var path = Path.Combine(currentPath, fileName);
48 if (File.Exists(path))
49 {
50 return path;
51 }
52 }
53
54 return String.Empty;
55 }
56 }
57}
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
@@ -76,79 +76,6 @@ namespace WixToolset.BaseBuildTasks
76 } 76 }
77 77
78 /// <summary> 78 /// <summary>
79 /// Build the extensions argument. Each extension is searched in the current folder, user defined search
80 /// directories (ReferencePath), HintPath, and under Wix Extension Directory in that order.
81 /// The order of precedence is based off of that described in Microsoft.Common.Targets's SearchPaths
82 /// property for the ResolveAssemblyReferences task.
83 /// </summary>
84 /// <param name="extensions">The list of extensions to include.</param>
85 /// <param name="wixExtensionDirectory">Evaluated default folder for Wix Extensions</param>
86 /// <param name="referencePaths">User defined reference directories to search in</param>
87 public void AppendExtensions(ITaskItem[] extensions, string wixExtensionDirectory, string [] referencePaths)
88 {
89 if (extensions == null)
90 {
91 return;
92 }
93
94 foreach (ITaskItem extension in extensions)
95 {
96 string className = extension.GetMetadata("Class");
97
98 string fileName = Path.GetFileName(extension.ItemSpec);
99
100 if (String.IsNullOrEmpty(Path.GetExtension(fileName)))
101 {
102 fileName += ".dll";
103 }
104
105 // First try reference paths
106 var resolvedPath = FileSearchHelperMethods.SearchFilePaths(referencePaths, fileName);
107
108 if (String.IsNullOrEmpty(resolvedPath))
109 {
110 // Now try HintPath
111 resolvedPath = extension.GetMetadata("HintPath");
112
113 if (!File.Exists(resolvedPath))
114 {
115 // Now try the item itself
116 resolvedPath = extension.ItemSpec;
117
118 if (String.IsNullOrEmpty(Path.GetExtension(resolvedPath)))
119 {
120 resolvedPath += ".dll";
121 }
122
123 if (!File.Exists(resolvedPath))
124 {
125 if (!String.IsNullOrEmpty(wixExtensionDirectory))
126 {
127 // Now try the extension directory
128 resolvedPath = Path.Combine(wixExtensionDirectory, Path.GetFileName(resolvedPath));
129 }
130
131 if (!File.Exists(resolvedPath))
132 {
133 // Extension wasn't found, just set it to the extension name passed in
134 resolvedPath = extension.ItemSpec;
135 }
136 }
137 }
138 }
139
140 if (String.IsNullOrEmpty(className))
141 {
142 this.AppendSwitchIfNotNull("-ext ", resolvedPath);
143 }
144 else
145 {
146 this.AppendSwitchIfNotNull("-ext ", className + ", " + resolvedPath);
147 }
148 }
149 }
150
151 /// <summary>
152 /// Append arbitrary text to the command-line if specified. 79 /// Append arbitrary text to the command-line if specified.
153 /// </summary> 80 /// </summary>
154 /// <param name="textToAppend">Text to append.</param> 81 /// <param name="textToAppend">Text to append.</param>
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
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using Microsoft.Build.Utilities;
8 using Microsoft.Build.Framework;
9 using System.IO; 7 using System.IO;
8 using System.Linq;
9 using Microsoft.Build.Framework;
10 using Microsoft.Build.Utilities;
10 11
11 /// <summary> 12 /// <summary>
12 /// This task searches for paths to references using the order specified in SearchPaths. 13 /// This task searches for paths to references using the order specified in SearchPaths.
@@ -15,14 +16,14 @@ namespace WixToolset.BuildTasks
15 { 16 {
16 /// <summary> 17 /// <summary>
17 /// Token value used in SearchPaths to indicate that the item's HintPath metadata should 18 /// Token value used in SearchPaths to indicate that the item's HintPath metadata should
18 /// be searched as a full file path to resolve the reference. 19 /// be searched as a full file path to resolve the reference.
19 /// Must match wix.targets, case sensitive. 20 /// Must match wix.targets, case sensitive.
20 /// </summary> 21 /// </summary>
21 private const string HintPathToken = "{HintPathFromItem}"; 22 private const string HintPathToken = "{HintPathFromItem}";
22 23
23 /// <summary> 24 /// <summary>
24 /// Token value used in SearchPaths to indicate that the item's Identity should 25 /// Token value used in SearchPaths to indicate that the item's Identity should
25 /// be searched as a full file path to resolve the reference. 26 /// be searched as a full file path to resolve the reference.
26 /// Must match wix.targets, case sensitive. 27 /// Must match wix.targets, case sensitive.
27 /// </summary> 28 /// </summary>
28 private const string RawFileNameToken = "{RawFileName}"; 29 private const string RawFileNameToken = "{RawFileName}";
@@ -34,24 +35,18 @@ namespace WixToolset.BuildTasks
34 public ITaskItem[] WixReferences { get; set; } 35 public ITaskItem[] WixReferences { get; set; }
35 36
36 /// <summary> 37 /// <summary>
37 /// The directories or special locations that are searched to find the files 38 /// The directories or special locations that are searched to find the files
38 /// on disk that represent the references. The order in which the search paths are listed 39 /// on disk that represent the references. The order in which the search paths are listed
39 /// is important. For each reference, the list of paths is searched from left to right. 40 /// is important. For each reference, the list of paths is searched from left to right.
40 /// When a file that represents the reference is found, that search stops and the search 41 /// When a file that represents the reference is found, that search stops and the search
41 /// for the next reference starts. 42 /// for the next reference starts.
42 /// 43 ///
43 /// This parameter accepts the following types of values: 44 /// This parameter accepts the following types of values:
44 /// A directory path. 45 /// A directory path.
45 /// {HintPathFromItem}: Specifies that the task will examine the HintPath metadata 46 /// {HintPathFromItem}: Specifies that the task will examine the HintPath metadata
46 /// of the base item. 47 /// of the base item.
47 /// TODO : {CandidateAssemblyFiles}: Specifies that the task will examine the files 48 /// {RawFileName}: Specifies the task will consider the Include value of the item to be
48 /// passed in through the CandidateAssemblyFiles parameter. 49 /// an exact path and file name.
49 /// TODO : {Registry:_AssemblyFoldersBase_, _RuntimeVersion_, _AssemblyFoldersSuffix_}:
50 /// TODO : {AssemblyFolders}: Specifies the task will use the Visual Studio.NET 2003
51 /// finding-assemblies-from-registry scheme.
52 /// TODO : {GAC}: Specifies the task will search in the GAC.
53 /// {RawFileName}: Specifies the task will consider the Include value of the item to be
54 /// an exact path and file name.
55 /// </summary> 50 /// </summary>
56 public string[] SearchPaths { get; set; } 51 public string[] SearchPaths { get; set; }
57 52
@@ -67,22 +62,37 @@ namespace WixToolset.BuildTasks
67 public ITaskItem[] ResolvedWixReferences { get; private set; } 62 public ITaskItem[] ResolvedWixReferences { get; private set; }
68 63
69 /// <summary> 64 /// <summary>
65 /// Output items that contain the same metadata as input references and cannot be found.
66 /// </summary>
67 [Output]
68 public ITaskItem[] UnresolvedWixReferences { get; private set; }
69
70 /// <summary>
70 /// Resolves reference paths by searching for referenced items using the specified SearchPaths. 71 /// Resolves reference paths by searching for referenced items using the specified SearchPaths.
71 /// </summary> 72 /// </summary>
72 /// <returns>True on success, or throws an exception on failure.</returns> 73 /// <returns>True on success, or throws an exception on failure.</returns>
73 public override bool Execute() 74 public override bool Execute()
74 { 75 {
75 var resolvedReferences = new List<ITaskItem>(); 76 var resolvedReferences = new List<ITaskItem>();
77 var unresolvedReferences = new List<ITaskItem>();
76 var uniqueReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase); 78 var uniqueReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
77 79
78 foreach (var reference in this.WixReferences) 80 foreach (var reference in this.WixReferences.Where(r => !String.IsNullOrWhiteSpace(r.ItemSpec)))
79 { 81 {
80 var resolvedReference = ResolveWixReferences.ResolveReference(reference, this.SearchPaths, this.SearchFilenameExtensions, this.Log); 82 (var resolvedReference, var found) = this.ResolveReference(reference, this.SearchPaths, this.SearchFilenameExtensions);
81 83
82 if (uniqueReferences.Add(resolvedReference.ItemSpec)) 84 if (uniqueReferences.Add(resolvedReference.ItemSpec))
83 { 85 {
84 this.Log.LogMessage(MessageImportance.Low, "Resolved path {0}", resolvedReference.ItemSpec); 86 if (found)
85 resolvedReferences.Add(resolvedReference); 87 {
88 this.Log.LogMessage(MessageImportance.Low, "Resolved path {0}", resolvedReference.ItemSpec);
89 resolvedReferences.Add(resolvedReference);
90 }
91 else
92 {
93 this.Log.LogWarning(null, "WXE0001", null, null, 0, 0, 0, 0, "Unable to find extension {0}.", resolvedReference.ItemSpec);
94 unresolvedReferences.Add(resolvedReference);
95 }
86 } 96 }
87 else 97 else
88 { 98 {
@@ -91,6 +101,7 @@ namespace WixToolset.BuildTasks
91 } 101 }
92 102
93 this.ResolvedWixReferences = resolvedReferences.ToArray(); 103 this.ResolvedWixReferences = resolvedReferences.ToArray();
104 this.UnresolvedWixReferences = unresolvedReferences.ToArray();
94 return true; 105 return true;
95 } 106 }
96 107
@@ -101,80 +112,76 @@ namespace WixToolset.BuildTasks
101 /// <param name="reference">The referenced item.</param> 112 /// <param name="reference">The referenced item.</param>
102 /// <param name="searchPaths">The paths to search.</param> 113 /// <param name="searchPaths">The paths to search.</param>
103 /// <param name="searchFilenameExtensions">Filename extensions to check.</param> 114 /// <param name="searchFilenameExtensions">Filename extensions to check.</param>
104 /// <param name="log">Logging helper.</param>
105 /// <returns>The resolved reference item, or the original reference if it could not be resolved.</returns> 115 /// <returns>The resolved reference item, or the original reference if it could not be resolved.</returns>
106 public static ITaskItem ResolveReference(ITaskItem reference, string[] searchPaths, string[] searchFilenameExtensions, TaskLoggingHelper log) 116 public (ITaskItem, bool) ResolveReference(ITaskItem reference, string[] searchPaths, string[] searchFilenameExtensions)
107 { 117 {
108 if (reference == null) 118 // Ensure we first check the reference without adding additional search filename extensions.
109 { 119 searchFilenameExtensions = searchFilenameExtensions == null ? new[] { String.Empty } : searchFilenameExtensions.Prepend(String.Empty).ToArray();
110 throw new ArgumentNullException("reference"); 120
111 } 121 // Copy all the metadata from the source
122 var resolvedReference = new TaskItem(reference);
123 this.Log.LogMessage(MessageImportance.Low, "WixReference: {0}", reference.ItemSpec);
124
125 var found = false;
112 126
127 // Nothing to search, so just resolve the original reference item.
113 if (searchPaths == null) 128 if (searchPaths == null)
114 { 129 {
115 // Nothing to search, so just return the original reference item. 130 if (this.ResolveFilenameExtensions(resolvedReference, resolvedReference.ItemSpec, searchFilenameExtensions))
116 return reference; 131 {
117 } 132 found = true;
133 }
118 134
119 if (searchFilenameExtensions == null) 135 return (resolvedReference, found);
120 {
121 searchFilenameExtensions = new string[] { };
122 } 136 }
123 137
124 // Copy all the metadata from the source 138 // Otherwise, now try to find the resolved path based on the order of precedence from search paths.
125 var resolvedReference = new TaskItem(reference);
126 log.LogMessage(MessageImportance.Low, "WixReference: {0}", reference.ItemSpec);
127
128 // Now find the resolved path based on our order of precedence
129 foreach (var searchPath in searchPaths) 139 foreach (var searchPath in searchPaths)
130 { 140 {
131 log.LogMessage(MessageImportance.Low, "Trying {0}", searchPath); 141 this.Log.LogMessage(MessageImportance.Low, "Trying {0}", searchPath);
132 if (searchPath.Equals(HintPathToken, StringComparison.Ordinal)) 142 if (HintPathToken.Equals(searchPath, StringComparison.Ordinal))
133 { 143 {
134 var path = reference.GetMetadata("HintPath"); 144 var path = reference.GetMetadata("HintPath");
135 log.LogMessage(MessageImportance.Low, "Trying path {0}", path); 145 if (String.IsNullOrWhiteSpace(path))
146 {
147 continue;
148 }
149
150 this.Log.LogMessage(MessageImportance.Low, "Trying path {0}", path);
136 if (File.Exists(path)) 151 if (File.Exists(path))
137 { 152 {
138 resolvedReference.ItemSpec = path; 153 resolvedReference.ItemSpec = path;
154 found = true;
139 break; 155 break;
140 } 156 }
141 } 157 }
142 else if (searchPath.Equals(RawFileNameToken, StringComparison.Ordinal)) 158 else if (RawFileNameToken.Equals(searchPath, StringComparison.Ordinal))
143 { 159 {
144 log.LogMessage(MessageImportance.Low, "Trying path {0}", resolvedReference.ItemSpec); 160 if (this.ResolveFilenameExtensions(resolvedReference, resolvedReference.ItemSpec, searchFilenameExtensions))
145 if (File.Exists(resolvedReference.ItemSpec))
146 {
147 break;
148 }
149
150 if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference,
151 resolvedReference.ItemSpec, searchFilenameExtensions, log))
152 { 161 {
162 found = true;
153 break; 163 break;
154 } 164 }
155 } 165 }
156 else 166 else
157 { 167 {
158 var path = Path.Combine(searchPath, Path.GetFileName(reference.ItemSpec)); 168 var path = Path.Combine(searchPath, reference.ItemSpec);
159 log.LogMessage(MessageImportance.Low, "Trying path {0}", path);
160 if (File.Exists(path))
161 {
162 resolvedReference.ItemSpec = path;
163 break;
164 }
165 169
166 if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference, 170 if (this.ResolveFilenameExtensions(resolvedReference, path, searchFilenameExtensions))
167 path, searchFilenameExtensions, log))
168 { 171 {
172 found = true;
169 break; 173 break;
170 } 174 }
171 } 175 }
172 } 176 }
173 177
174 // Normalize the item path 178 if (found)
175 resolvedReference.ItemSpec = resolvedReference.GetMetadata("FullPath"); 179 {
180 // Normalize the item spec to the full path.
181 resolvedReference.ItemSpec = resolvedReference.GetMetadata("FullPath");
182 }
176 183
177 return resolvedReference; 184 return (resolvedReference, found);
178 } 185 }
179 186
180 /// <summary> 187 /// <summary>
@@ -183,14 +190,14 @@ namespace WixToolset.BuildTasks
183 /// <param name="reference">The reference being resolved.</param> 190 /// <param name="reference">The reference being resolved.</param>
184 /// <param name="basePath">Full filename path without extension.</param> 191 /// <param name="basePath">Full filename path without extension.</param>
185 /// <param name="filenameExtensions">Filename extensions to check.</param> 192 /// <param name="filenameExtensions">Filename extensions to check.</param>
186 /// <param name="log">Logging helper.</param>
187 /// <returns>True if the item was resolved, else false.</returns> 193 /// <returns>True if the item was resolved, else false.</returns>
188 private static bool ResolveFilenameExtensions(ITaskItem reference, string basePath, string[] filenameExtensions, TaskLoggingHelper log) 194 private bool ResolveFilenameExtensions(ITaskItem reference, string basePath, string[] filenameExtensions)
189 { 195 {
190 foreach (var filenameExtension in filenameExtensions) 196 foreach (var filenameExtension in filenameExtensions)
191 { 197 {
192 var path = basePath + filenameExtension; 198 var path = basePath + filenameExtension;
193 log.LogMessage(MessageImportance.Low, "Trying path {0}", path); 199 this.Log.LogMessage(MessageImportance.Low, "Trying path {0}", path);
200
194 if (File.Exists(path)) 201 if (File.Exists(path))
195 { 202 {
196 reference.ItemSpec = path; 203 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
18 18
19 public ITaskItem[] Extensions { get; set; } 19 public ITaskItem[] Extensions { get; set; }
20 20
21 public string ExtensionDirectory { get; set; }
22
23 public string[] IncludeSearchPaths { get; set; } 21 public string[] IncludeSearchPaths { get; set; }
24 22
25 public string InstallerPlatform { get; set; } 23 public string InstallerPlatform { get; set; }
@@ -45,8 +43,6 @@ namespace WixToolset.BuildTasks
45 [Required] 43 [Required]
46 public ITaskItem[] SourceFiles { get; set; } 44 public ITaskItem[] SourceFiles { get; set; }
47 45
48 public string[] ReferencePaths { get; set; }
49
50 public ITaskItem[] BindInputPaths { get; set; } 46 public ITaskItem[] BindInputPaths { get; set; }
51 47
52 public bool BindFiles { get; set; } 48 public bool BindFiles { get; set; }
@@ -75,7 +71,7 @@ namespace WixToolset.BuildTasks
75 commandLineBuilder.AppendArrayIfNotNull("-culture ", this.Cultures); 71 commandLineBuilder.AppendArrayIfNotNull("-culture ", this.Cultures);
76 commandLineBuilder.AppendArrayIfNotNull("-d ", this.DefineConstants); 72 commandLineBuilder.AppendArrayIfNotNull("-d ", this.DefineConstants);
77 commandLineBuilder.AppendArrayIfNotNull("-I ", this.IncludeSearchPaths); 73 commandLineBuilder.AppendArrayIfNotNull("-I ", this.IncludeSearchPaths);
78 commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths); 74 commandLineBuilder.AppendArrayIfNotNull("-ext ", this.Extensions);
79 commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath); 75 commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath);
80 commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory); 76 commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory);
81 commandLineBuilder.AppendSwitchIfNotNull("-trackingfile ", this.BindTrackingFile); 77 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
@@ -129,10 +129,6 @@
129 </PropertyGroup> 129 </PropertyGroup>
130 130
131 <PropertyGroup> 131 <PropertyGroup>
132 <WixExtDir Condition=" '$(WixExtDir)' == ''">$(WixBinDir)</WixExtDir>
133 </PropertyGroup>
134
135 <PropertyGroup>
136 <BindTrackingFilePrefix Condition=" '$(BindTrackingFilePrefix)' == '' ">$(MSBuildProjectFile).BindTracking</BindTrackingFilePrefix> 132 <BindTrackingFilePrefix Condition=" '$(BindTrackingFilePrefix)' == '' ">$(MSBuildProjectFile).BindTracking</BindTrackingFilePrefix>
137 <BindTrackingFileExtension Condition=" '$(BindTrackingFileExtension)' == '' ">.txt</BindTrackingFileExtension> 133 <BindTrackingFileExtension Condition=" '$(BindTrackingFileExtension)' == '' ">.txt</BindTrackingFileExtension>
138 </PropertyGroup> 134 </PropertyGroup>
@@ -362,10 +358,9 @@
362 358
363 By default the WixLibrarySearchPaths property is set to find libraries in the following order: 359 By default the WixLibrarySearchPaths property is set to find libraries in the following order:
364 360
365 (1) $(ReferencePaths) - the reference paths property, which comes from the .USER file. 361 (1) $(ReferencePaths) - the reference paths property.
366 (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}. 362 (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
367 (3) Treat the reference's Include as if it were a real file name. 363 (3) Treat the reference's Include as if it were a real file name.
368 (4) Path specified by the WixExtDir property.
369 364
370 [IN] 365 [IN]
371 @(WixLibrary) - the list of .wixlib files. 366 @(WixLibrary) - the list of .wixlib files.
@@ -381,39 +376,33 @@
381 </PropertyGroup> 376 </PropertyGroup>
382 <Target 377 <Target
383 Name="ResolveWixLibraryReferences" 378 Name="ResolveWixLibraryReferences"
384 DependsOnTargets="$(ResolveWixLibraryReferencesDependsOn)"> 379 DependsOnTargets="$(ResolveWixLibraryReferencesDependsOn)"
380 Condition=" '@(WixLibrary)' != ''">
385 381
386 <PropertyGroup> 382 <PropertyGroup>
387 <WixLibrarySearchPaths Condition=" '$(WixLibrarySearchPaths)' == '' ">$(ReferencePaths);{HintPathFromItem};{RawFileName};$(WixExtDir)</WixLibrarySearchPaths> 383 <WixLibrarySearchPaths Condition=" '$(WixLibrarySearchPaths)' == '' ">$(ReferencePaths);{HintPathFromItem};{RawFileName}</WixLibrarySearchPaths>
388 </PropertyGroup> 384 </PropertyGroup>
389 385
390 <ResolveWixReferences 386 <ResolveWixReferences
391 WixReferences="@(WixLibrary)" 387 WixReferences="@(WixLibrary)"
392 SearchPaths="$(WixLibrarySearchPaths)" 388 SearchPaths="$(WixLibrarySearchPaths)"
393 SearchFilenameExtensions=".wixlib" 389 SearchFilenameExtensions=".wixlib">
394 Condition=" '@(WixLibrary)' != ''"> 390 <Output TaskParameter="ResolvedWixReferences" ItemName="_ResolvedWixLibraryPaths" />
395 <Output TaskParameter="ResolvedWixReferences" ItemName="_AllResolvedWixLibraryPaths" /> 391 <Output TaskParameter="UnresolvedWixReferences" ItemName="_UnresolvedWixLibraryPaths" />
396 </ResolveWixReferences> 392 </ResolveWixReferences>
397
398 <RemoveDuplicates Inputs="@(_AllResolvedWixLibraryPaths)">
399 <Output TaskParameter="Filtered" ItemName="_ResolvedWixLibraryPaths" />
400 </RemoveDuplicates>
401 </Target> 393 </Target>
402 394
403 <!-- 395 <!--
404 ================================================================================================ 396 ================================================================================================
405 ResolveWixExtensionReferences 397 ResolveWixExtensionReferences
406 398
407 Resolves WiX extension references to full paths. Any properties you use 399 Resolves WiX extension references to full paths.
408 to resolve paths to extensions must be defined before importing this
409 file or the extensions will be automatically resolved to $(WixExtDir).
410 400
411 By default the WixExtensionSearchPaths property is set to find extensions in the following order: 401 By default the WixExtensionSearchPaths property is set to find extensions in the following order:
412 402
413 (1) $(ReferencePaths) - the reference paths property, which comes from the .USER file. 403 (1) $(ReferencePaths) - the reference paths property.
414 (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}. 404 (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
415 (3) Treat the reference's Include as if it were a real file name. 405 (3) Treat the reference's Include as if it were a real file name.
416 (4) Path specified by the WixExtDir property.
417 406
418 [IN] 407 [IN]
419 @(WixExtension) - WixExtension item group 408 @(WixExtension) - WixExtension item group
@@ -431,20 +420,16 @@
431 Condition=" '@(WixExtension)' != ''"> 420 Condition=" '@(WixExtension)' != ''">
432 421
433 <PropertyGroup> 422 <PropertyGroup>
434 <WixExtensionSearchPaths Condition=" '$(WixExtensionSearchPaths)' == '' ">$(ReferencePaths);{HintPathFromItem};{RawFileName};$(WixExtDir)</WixExtensionSearchPaths> 423 <WixExtensionSearchPaths Condition=" '$(WixExtensionSearchPaths)' == '' ">$(ReferencePaths);{HintPathFromItem};{RawFileName}</WixExtensionSearchPaths>
435 </PropertyGroup> 424 </PropertyGroup>
436 425
437 <ResolveWixReferences 426 <ResolveWixReferences
438 WixReferences="@(WixExtension)" 427 WixReferences="@(WixExtension)"
439 SearchPaths="$(WixExtensionSearchPaths)" 428 SearchPaths="$(WixExtensionSearchPaths)"
440 SearchFilenameExtensions=".wixext.dll"> 429 SearchFilenameExtensions=".wixext.dll">
441 <Output TaskParameter="ResolvedWixReferences" ItemName="_AllResolvedWixExtensionPaths" /> 430 <Output TaskParameter="ResolvedWixReferences" ItemName="_ResolvedWixExtensionPaths" />
431 <Output TaskParameter="UnresolvedWixReferences" ItemName="_UnresolvedWixExtensionPaths" />
442 </ResolveWixReferences> 432 </ResolveWixReferences>
443
444 <!-- Remove duplicate extension items that would cause build errors -->
445 <RemoveDuplicates Inputs="@(_AllResolvedWixExtensionPaths)">
446 <Output TaskParameter="Filtered" ItemName="_ResolvedWixExtensionPaths" />
447 </RemoveDuplicates>
448 </Target> 433 </Target>
449 434
450 <!-- 435 <!--
@@ -599,7 +584,6 @@
599 584
600 Cultures="%(CultureGroup.Identity)" 585 Cultures="%(CultureGroup.Identity)"
601 586
602 ExtensionDirectory="$(WixExtDir)"
603 Extensions="@(_ResolvedWixExtensionPaths)" 587 Extensions="@(_ResolvedWixExtensionPaths)"
604 588
605 IntermediateDirectory="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)" 589 IntermediateDirectory="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)"
@@ -615,7 +599,6 @@
615 InstallerPlatform="$(InstallerPlatform)" 599 InstallerPlatform="$(InstallerPlatform)"
616 NoLogo="true" 600 NoLogo="true"
617 Pedantic="$(Pedantic)" 601 Pedantic="$(Pedantic)"
618 ReferencePaths="$(ReferencePaths)"
619 602
620 BindInputPaths="@(LinkerBindInputPaths)" 603 BindInputPaths="@(LinkerBindInputPaths)"
621 BindFiles="$(BindFiles)" 604 BindFiles="$(BindFiles)"
diff --git a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
index 1201620b..c1fdd548 100644
--- a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
+++ b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
@@ -29,7 +29,7 @@ namespace WixToolsetTest.Sdk
29 var binFolder = Path.Combine(baseFolder, @"bin\"); 29 var binFolder = Path.Combine(baseFolder, @"bin\");
30 var projectPath = Path.Combine(baseFolder, "SimpleBundle.wixproj"); 30 var projectPath = Path.Combine(baseFolder, "SimpleBundle.wixproj");
31 31
32 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] { 32 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] {
33 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath), 33 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath),
34 "-p:SignOutput=true", 34 "-p:SignOutput=true",
35 }); 35 });
@@ -162,7 +162,7 @@ namespace WixToolsetTest.Sdk
162 var platformSwitches = result.Output.Where(line => line.Contains("-platform x86")); 162 var platformSwitches = result.Output.Where(line => line.Contains("-platform x86"));
163 Assert.Single(platformSwitches); 163 Assert.Single(platformSwitches);
164 164
165 var warnings = result.Output.Where(line => line.Contains(": warning")).Select(ExtractWarningFromMessage).ToArray(); 165 var warnings = result.Output.Where(line => line.Contains(": warning")).Select(line => ExtractWarningFromMessage(line, baseFolder)).ToArray();
166 WixAssert.CompareLineByLine(new[] 166 WixAssert.CompareLineByLine(new[]
167 { 167 {
168 @"WIX1118: The variable 'Variable' with value 'DifferentValue' was previously declared with value 'Value'.", 168 @"WIX1118: The variable 'Variable' with value 'DifferentValue' was previously declared with value 'Value'.",
@@ -173,7 +173,7 @@ namespace WixToolsetTest.Sdk
173 @"WIX1122: The installer database '<basefolder>\obj\x86\Release\en-US\MsiPackage.msi' has external cabs, but at least one of them is not signed. Please ensure that all external cabs are signed, if you mean to sign them. If you don't mean to sign them, there is no need to inscribe the MSI as part of your build." 173 @"WIX1122: The installer database '<basefolder>\obj\x86\Release\en-US\MsiPackage.msi' has external cabs, but at least one of them is not signed. Please ensure that all external cabs are signed, if you mean to sign them. If you don't mean to sign them, there is no need to inscribe the MSI as part of your build."
174 }, warnings); 174 }, warnings);
175 175
176 var testMessages = result.Output.Where(line => line.Contains("TEST:")).Select(ReplacePathsInMessage).ToArray(); 176 var testMessages = result.Output.Where(line => line.Contains("TEST:")).Select(line => ReplacePathsInMessage(line, baseFolder)).ToArray();
177 WixAssert.CompareLineByLine(new[] 177 WixAssert.CompareLineByLine(new[]
178 { 178 {
179 @"TEST: SignCabs: <basefolder>\obj\x86\Release\en-US\cab1.cab", 179 @"TEST: SignCabs: <basefolder>\obj\x86\Release\en-US\cab1.cab",
@@ -191,20 +191,6 @@ namespace WixToolsetTest.Sdk
191 @"bin\x86\Release\en-US\MsiPackage.wixpdb", 191 @"bin\x86\Release\en-US\MsiPackage.wixpdb",
192 }, paths); 192 }, paths);
193 } 193 }
194
195 string ExtractWarningFromMessage(string message)
196 {
197 const string prefix = ": warning ";
198
199 var start = message.IndexOf(prefix) + prefix.Length;
200 var end = message.LastIndexOf("[");
201 return ReplacePathsInMessage(message.Substring(start, end - start));
202 }
203
204 string ReplacePathsInMessage(string message)
205 {
206 return message.Replace(baseFolder, "<basefolder>").Trim();
207 }
208 } 194 }
209 195
210 [Theory] 196 [Theory]
@@ -585,6 +571,46 @@ namespace WixToolsetTest.Sdk
585 } 571 }
586 } 572 }
587 573
574
575 [Theory]
576 [InlineData(BuildSystem.DotNetCoreSdk)]
577 [InlineData(BuildSystem.MSBuild)]
578 [InlineData(BuildSystem.MSBuild64)]
579 public void CanBuildWithWarningWhenExtensionIsMissing(BuildSystem buildSystem)
580 {
581 var sourceFolder = TestData.Get(@"TestData", "WixlibMissingExtension");
582
583 using (var fs = new TestDataFolderFileSystem())
584 {
585 fs.Initialize(sourceFolder);
586 var baseFolder = fs.BaseFolder;
587 var binFolder = Path.Combine(baseFolder, @"bin\");
588 var projectPath = Path.Combine(baseFolder, "WixlibMissingExtension.wixproj");
589
590 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] {
591 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath),
592 "-p:SignOutput=true",
593 });
594 result.AssertSuccess();
595
596 var warnings = result.Output.Where(line => line.Contains(": warning")).Select(line => ExtractWarningFromMessage(line, baseFolder)).ToArray();
597 WixAssert.CompareLineByLine(new[]
598 {
599 "WXE0001: Unable to find extension DoesNotExist.wixext.dll.",
600 "WXE0001: Unable to find extension DoesNotExist.wixext.dll.",
601 }, warnings);
602
603 var paths = Directory.EnumerateFiles(binFolder, @"*.*", SearchOption.AllDirectories)
604 .Select(s => s.Substring(baseFolder.Length + 1))
605 .OrderBy(s => s)
606 .ToArray();
607 WixAssert.CompareLineByLine(new[]
608 {
609 @"bin\Release\WixlibMissingExtension.wixlib",
610 }, paths);
611 }
612 }
613
588 [Theory(Skip = "Depends on creating broken publish which is not supported at this time")] 614 [Theory(Skip = "Depends on creating broken publish which is not supported at this time")]
589 [InlineData(BuildSystem.DotNetCoreSdk)] 615 [InlineData(BuildSystem.DotNetCoreSdk)]
590 [InlineData(BuildSystem.MSBuild)] 616 [InlineData(BuildSystem.MSBuild)]
@@ -610,5 +636,20 @@ namespace WixToolsetTest.Sdk
610 Assert.Contains(result.Output, m => m.Contains(expectedMessage)); 636 Assert.Contains(result.Output, m => m.Contains(expectedMessage));
611 } 637 }
612 } 638 }
639
640 private static string ExtractWarningFromMessage(string message, string baseFolder)
641 {
642 const string prefix = ": warning ";
643
644 var start = message.IndexOf(prefix) + prefix.Length;
645 var end = message.LastIndexOf("[");
646
647 return ReplacePathsInMessage(message.Substring(start, end - start), baseFolder);
648 }
649
650 private static string ReplacePathsInMessage(string message, string baseFolder)
651 {
652 return message.Replace(baseFolder, "<basefolder>").Trim();
653 }
613 } 654 }
614} 655}
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/Library.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/Library.wxs
new file mode 100644
index 00000000..bcf79a1f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/Library.wxs
@@ -0,0 +1,7 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <StandardDirectory Id="ProgramFilesFolder">
4 <Directory Id="WixLibFolder" />
5 </StandardDirectory>
6 </Fragment>
7</Wix>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/WixlibMissingExtension.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/WixlibMissingExtension.wixproj
new file mode 100644
index 00000000..7a21c293
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/WixlibMissingExtension/WixlibMissingExtension.wixproj
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project>
3 <Import Project="$(WixMSBuildProps)" />
4
5 <PropertyGroup>
6 <OutputType>Library</OutputType>
7 </PropertyGroup>
8
9 <ItemGroup>
10 <Compile Include="Library.wxs" />
11 </ItemGroup>
12
13 <ItemGroup>
14 <WixExtension Include="DoesNotExist.wixext.dll" />
15 </ItemGroup>
16
17 <Import Project="$(WixTargetsPath)" />
18</Project>