aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-11-15 23:38:46 -0800
committerRob Mensching <rob@firegiant.com>2022-11-17 07:48:19 -0800
commitdd3ab394ec6e37c24827294605857730f8e7a1a5 (patch)
tree7db5d1a5790f28f2a486358fa0b8e5a8617ab1fc
parent13292da793c9e0792d3ecd86974304b8b2a51d7a (diff)
downloadwix-dd3ab394ec6e37c24827294605857730f8e7a1a5.tar.gz
wix-dd3ab394ec6e37c24827294605857730f8e7a1a5.tar.bz2
wix-dd3ab394ec6e37c24827294605857730f8e7a1a5.zip
Expose bind variables from build command and via MSBuild WixVariables property
Also, resolves bind variables in path fields. Fixes 6995 and 7017
-rw-r--r--src/api/wix/WixToolset.Data/ErrorMessages.cs24
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/ILibraryContext.cs5
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/IResolveContext.cs5
-rw-r--r--src/wix/WixToolset.BuildTasks/WixBuild.cs22
-rw-r--r--src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs50
-rw-r--r--src/wix/WixToolset.Core/CommandLine/BuildCommand.cs81
-rw-r--r--src/wix/WixToolset.Core/Librarian.cs5
-rw-r--r--src/wix/WixToolset.Core/LibraryContext.cs2
-rw-r--r--src/wix/WixToolset.Core/Linker.cs2
-rw-r--r--src/wix/WixToolset.Core/LinkerErrors.cs6
-rw-r--r--src/wix/WixToolset.Core/ResolveContext.cs7
-rw-r--r--src/wix/WixToolset.Core/Resolver.cs5
-rw-r--r--src/wix/WixToolset.Core/VariableResolver.cs9
-rw-r--r--src/wix/WixToolset.Sdk/tools/wix.targets4
-rw-r--r--src/wix/test/WixToolsetTest.Core/VariableResolverFixture.cs (renamed from src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs)5
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/Bundle.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithBindVariableVersion.wxs33
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithReplaceableVersion.wxs22
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/WixVariableFixture.cs174
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs92
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/MsiPackageWithBindVariables.wixproj16
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/Library.wxs2
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/SimpleWixlib.wixproj29
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/data/subfolder/Library.txt (renamed from src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/data/Library.txt)0
27 files changed, 517 insertions, 123 deletions
diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs
index c447f89d..0c7511b9 100644
--- a/src/api/wix/WixToolset.Data/ErrorMessages.cs
+++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs
@@ -598,9 +598,9 @@ namespace WixToolset.Data
598 return Message(sourceLineNumbers, Ids.ExpectedVariable, "A required variable was missing in the expression '{0}'.", expression); 598 return Message(sourceLineNumbers, Ids.ExpectedVariable, "A required variable was missing in the expression '{0}'.", expression);
599 } 599 }
600 600
601 public static Message ExpectedWixVariableValue(string variableId) 601 public static Message ExpectedBindVariableValue(string variableId)
602 { 602 {
603 return Message(null, Ids.ExpectedWixVariableValue, "The WiX variable '{0}' was declared without a value. Please specify a value for the variable.", variableId); 603 return Message(null, Ids.ExpectedBindVariableValue, "The bind variable '{0}' was declared without a value. Please specify a value for the variable.", variableId);
604 } 604 }
605 605
606 public static Message FamilyNameTooLong(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value, int length) 606 public static Message FamilyNameTooLong(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value, int length)
@@ -1081,9 +1081,9 @@ namespace WixToolset.Data
1081 return Message(null, Ids.IllegalWarningIdAsError, "Illegal value '{0}' for the -wx<N> command line option. Specify a particular warning number, like '-wx6' to display the warning with ID 6 as an error, or '-wx' alone to suppress all warnings.", warningId); 1081 return Message(null, Ids.IllegalWarningIdAsError, "Illegal value '{0}' for the -wx<N> command line option. Specify a particular warning number, like '-wx6' to display the warning with ID 6 as an error, or '-wx' alone to suppress all warnings.", warningId);
1082 } 1082 }
1083 1083
1084 public static Message IllegalWixVariablePrefix(SourceLineNumber sourceLineNumbers, string variableId) 1084 public static Message IllegalBindVariablePrefix(SourceLineNumber sourceLineNumbers, string variableId)
1085 { 1085 {
1086 return Message(sourceLineNumbers, Ids.IllegalWixVariablePrefix, "The WiX variable $(wix.{0}) uses an illegal prefix '$'. Please use the '!' prefix instead.", variableId); 1086 return Message(sourceLineNumbers, Ids.IllegalBindVariablePrefix, "The bind variable $(wix.{0}) uses an illegal prefix '$'. Please use the '!' prefix instead.", variableId);
1087 } 1087 }
1088 1088
1089 public static Message IllegalYesNoAlwaysValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value) 1089 public static Message IllegalYesNoAlwaysValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value)
@@ -2231,14 +2231,14 @@ namespace WixToolset.Data
2231 return Message(null, Ids.WixFileNotFound, "The file '{0}' cannot be found.", file); 2231 return Message(null, Ids.WixFileNotFound, "The file '{0}' cannot be found.", file);
2232 } 2232 }
2233 2233
2234 public static Message WixVariableCollision(SourceLineNumber sourceLineNumbers, string variableId) 2234 public static Message BindVariableCollision(SourceLineNumber sourceLineNumbers, string variableId)
2235 { 2235 {
2236 return Message(sourceLineNumbers, Ids.WixVariableCollision, "The WiX variable '{0}' is declared in more than one location. Please remove one of the declarations.", variableId); 2236 return Message(sourceLineNumbers, Ids.BindVariableCollision, "The bind variable '{0}' is declared in more than one location. Please remove one of the declarations.", variableId);
2237 } 2237 }
2238 2238
2239 public static Message WixVariableUnknown(SourceLineNumber sourceLineNumbers, string variableId) 2239 public static Message BindVariableUnknown(SourceLineNumber sourceLineNumbers, string variableId)
2240 { 2240 {
2241 return Message(sourceLineNumbers, Ids.WixVariableUnknown, "The WiX variable !(wix.{0}) is unknown. Please ensure the variable is declared on the command line for light.exe, via a WixVariable element, or inline using the syntax !(wix.{0}=some value which doesn't contain parenthesis).", variableId); 2241 return Message(sourceLineNumbers, Ids.BindVariableUnknown, "The bind variable !(wix.{0}) is unknown. Please ensure the variable is declared on the command line for wix.exe, via a BindVariable element, or inline using the syntax !(wix.{0}=some value which doesn't contain parenthesis).", variableId);
2242 } 2242 }
2243 2243
2244 public static Message NoSourceFiles() 2244 public static Message NoSourceFiles()
@@ -2474,10 +2474,10 @@ namespace WixToolset.Data
2474 VersionIndependentProgIdsCannotHaveIcons = 192, 2474 VersionIndependentProgIdsCannotHaveIcons = 192,
2475 IllegalAttributeValueWithOtherAttribute = 193, 2475 IllegalAttributeValueWithOtherAttribute = 193,
2476 InvalidMergeLanguage = 194, 2476 InvalidMergeLanguage = 194,
2477 WixVariableCollision = 195, 2477 BindVariableCollision = 195,
2478 ExpectedWixVariableValue = 196, 2478 ExpectedBindVariableValue = 196,
2479 WixVariableUnknown = 197, 2479 BindVariableUnknown = 197,
2480 IllegalWixVariablePrefix = 198, 2480 IllegalBindVariablePrefix = 198,
2481 InvalidWixXmlNamespace = 199, 2481 InvalidWixXmlNamespace = 199,
2482 UnhandledExtensionElement = 200, 2482 UnhandledExtensionElement = 200,
2483 UnhandledExtensionAttribute = 201, 2483 UnhandledExtensionAttribute = 201,
diff --git a/src/api/wix/WixToolset.Extensibility/Data/ILibraryContext.cs b/src/api/wix/WixToolset.Extensibility/Data/ILibraryContext.cs
index f3c4db6e..9fc767f6 100644
--- a/src/api/wix/WixToolset.Extensibility/Data/ILibraryContext.cs
+++ b/src/api/wix/WixToolset.Extensibility/Data/ILibraryContext.cs
@@ -28,6 +28,11 @@ namespace WixToolset.Extensibility.Data
28 IReadOnlyCollection<IBindPath> BindPaths { get; set; } 28 IReadOnlyCollection<IBindPath> BindPaths { get; set; }
29 29
30 /// <summary> 30 /// <summary>
31 /// Bind variables used when binding files.
32 /// </summary>
33 IDictionary<string, string> BindVariables { get; set; }
34
35 /// <summary>
31 /// Collection of extensions used during creation of library. 36 /// Collection of extensions used during creation of library.
32 /// </summary> 37 /// </summary>
33 IReadOnlyCollection<ILibrarianExtension> Extensions { get; set; } 38 IReadOnlyCollection<ILibrarianExtension> Extensions { get; set; }
diff --git a/src/api/wix/WixToolset.Extensibility/Data/IResolveContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IResolveContext.cs
index bec689ba..a2158e4f 100644
--- a/src/api/wix/WixToolset.Extensibility/Data/IResolveContext.cs
+++ b/src/api/wix/WixToolset.Extensibility/Data/IResolveContext.cs
@@ -23,6 +23,11 @@ namespace WixToolset.Extensibility.Data
23 IReadOnlyCollection<IBindPath> BindPaths { get; set; } 23 IReadOnlyCollection<IBindPath> BindPaths { get; set; }
24 24
25 /// <summary> 25 /// <summary>
26 /// Bind variables used during resolution.
27 /// </summary>
28 IDictionary<string, string> BindVariables { get; set; }
29
30 /// <summary>
26 /// Resolve extensions. 31 /// Resolve extensions.
27 /// </summary> 32 /// </summary>
28 IReadOnlyCollection<IResolverExtension> Extensions { get; set; } 33 IReadOnlyCollection<IResolverExtension> Extensions { get; set; }
diff --git a/src/wix/WixToolset.BuildTasks/WixBuild.cs b/src/wix/WixToolset.BuildTasks/WixBuild.cs
index d725abff..4cadd7e0 100644
--- a/src/wix/WixToolset.BuildTasks/WixBuild.cs
+++ b/src/wix/WixToolset.BuildTasks/WixBuild.cs
@@ -45,6 +45,8 @@ namespace WixToolset.BuildTasks
45 45
46 public ITaskItem[] BindPaths { get; set; } 46 public ITaskItem[] BindPaths { get; set; }
47 47
48 public ITaskItem[] BindVariables { get; set; }
49
48 public bool BindFiles { get; set; } 50 public bool BindFiles { get; set; }
49 51
50 public ITaskItem BindTrackingFile { get; set; } 52 public ITaskItem BindTrackingFile { get; set; }
@@ -77,6 +79,7 @@ namespace WixToolset.BuildTasks
77 79
78 commandLineBuilder.AppendIfTrue("-bindFiles", this.BindFiles); 80 commandLineBuilder.AppendIfTrue("-bindFiles", this.BindFiles);
79 commandLineBuilder.AppendArrayIfNotNull("-bindPath ", this.CalculateBindPathStrings()); 81 commandLineBuilder.AppendArrayIfNotNull("-bindPath ", this.CalculateBindPathStrings());
82 commandLineBuilder.AppendArrayIfNotNull("-bindVariable ", this.CalculateBindVariableStrings());
80 commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles); 83 commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles);
81 commandLineBuilder.AppendArrayIfNotNull("-lib ", this.LibraryFiles); 84 commandLineBuilder.AppendArrayIfNotNull("-lib ", this.LibraryFiles);
82 commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions); 85 commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions);
@@ -103,5 +106,24 @@ namespace WixToolset.BuildTasks
103 } 106 }
104 } 107 }
105 } 108 }
109
110 private IEnumerable<string> CalculateBindVariableStrings()
111 {
112 if (null != this.BindVariables)
113 {
114 foreach (var item in this.BindVariables)
115 {
116 var value = item.ItemSpec;
117
118 var variableName = item.GetMetadata("Name");
119 if (!String.IsNullOrEmpty(variableName))
120 {
121 value = String.Concat(variableName, "=", value);
122 }
123
124 yield return value;
125 }
126 }
127 }
106 } 128 }
107} 129}
diff --git a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs
index acdf999a..1129d96f 100644
--- a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+++ b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs
@@ -88,25 +88,21 @@ namespace WixToolset.Core.Bind
88 88
89 var beforeErrorCount = this.Messaging.ErrorCount; 89 var beforeErrorCount = this.Messaging.ErrorCount;
90 90
91 // Check to make sure we're in a scenario where we can handle variable resolution. 91 // resolve localization and wix variables
92 if (null != delayedFields) 92 if (fieldType == IntermediateFieldType.String)
93 { 93 {
94 // resolve localization and wix variables 94 var original = field.AsString();
95 if (fieldType == IntermediateFieldType.String) 95 if (!String.IsNullOrEmpty(original))
96 { 96 {
97 var original = field.AsString(); 97 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables);
98 if (!String.IsNullOrEmpty(original)) 98 if (resolution.UpdatedValue)
99 { 99 {
100 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); 100 field.Set(resolution.Value);
101 if (resolution.UpdatedValue) 101 }
102 { 102
103 field.Set(resolution.Value); 103 if (resolution.DelayedResolve)
104 } 104 {
105 105 delayedFields.Add(new DelayedField(symbol, field));
106 if (resolution.DelayedResolve)
107 {
108 delayedFields.Add(new DelayedField(symbol, field));
109 }
110 } 106 }
111 } 107 }
112 } 108 }
@@ -136,13 +132,27 @@ namespace WixToolset.Core.Bind
136 // If the file is embedded and if the previous value has a bind variable in the path 132 // If the file is embedded and if the previous value has a bind variable in the path
137 // which gets modified by resolving the previous value again then switch to that newly 133 // which gets modified by resolving the previous value again then switch to that newly
138 // resolved path instead of using the embedded file. 134 // resolved path instead of using the embedded file.
139 if (fieldValue.Embed && field.PreviousValue != null) 135 if (fieldValue.Embed)
136 {
137 if (field.PreviousValue != null)
138 {
139 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false);
140
141 if (resolution.UpdatedValue && !resolution.IsDefault)
142 {
143 fieldValue = new IntermediateFieldPathValue { Path = resolution.Value };
144 }
145 }
146 }
147 else // resolve path field for bind variables.
140 { 148 {
141 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false); 149 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, fieldValue.Path, errorOnUnknown: false);
142 150
143 if (resolution.UpdatedValue && !resolution.IsDefault) 151 if (resolution.UpdatedValue)
144 { 152 {
145 fieldValue = new IntermediateFieldPathValue { Path = resolution.Value }; 153 field.Set(resolution.Value);
154
155 fieldValue = field.AsPath();
146 } 156 }
147 } 157 }
148 158
diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
index 53c4f798..aaa14396 100644
--- a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -10,6 +10,7 @@ namespace WixToolset.Core.CommandLine
10 using System.Threading.Tasks; 10 using System.Threading.Tasks;
11 using System.Xml.Linq; 11 using System.Xml.Linq;
12 using WixToolset.Data; 12 using WixToolset.Data;
13 using WixToolset.Data.Bind;
13 using WixToolset.Extensibility; 14 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Data; 15 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services; 16 using WixToolset.Extensibility.Services;
@@ -115,7 +116,7 @@ namespace WixToolset.Core.CommandLine
115 { 116 {
116 using (new IntermediateFieldContext("wix.lib")) 117 using (new IntermediateFieldContext("wix.lib"))
117 { 118 {
118 this.LibraryPhase(wixobjs, wxls, inputsOutputs.LibraryPaths, creator, this.commandLine.BindFiles, this.commandLine.BindPaths, inputsOutputs.OutputPath, cancellationToken); 119 this.LibraryPhase(wixobjs, wxls, inputsOutputs.LibraryPaths, creator, this.commandLine.BindFiles, this.commandLine.BindPaths, this.commandLine.BindVariables, inputsOutputs.OutputPath, cancellationToken);
119 } 120 }
120 } 121 }
121 else 122 else
@@ -148,7 +149,7 @@ namespace WixToolset.Core.CommandLine
148 { 149 {
149 using (new IntermediateFieldContext("wix.bind")) 150 using (new IntermediateFieldContext("wix.bind"))
150 { 151 {
151 this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.CabbingThreadCount, this.commandLine.BindPaths, inputsOutputs, cancellationToken); 152 this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.CabbingThreadCount, this.commandLine.BindPaths, this.commandLine.BindVariables, inputsOutputs, cancellationToken);
152 } 153 }
153 } 154 }
154 } 155 }
@@ -206,7 +207,7 @@ namespace WixToolset.Core.CommandLine
206 return intermediates; 207 return intermediates;
207 } 208 }
208 209
209 private void LibraryPhase(IReadOnlyCollection<Intermediate> intermediates, IReadOnlyCollection<Localization> localizations, IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator, bool bindFiles, IReadOnlyCollection<IBindPath> bindPaths, string outputPath, CancellationToken cancellationToken) 210 private void LibraryPhase(IReadOnlyCollection<Intermediate> intermediates, IReadOnlyCollection<Localization> localizations, IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator, bool bindFiles, IReadOnlyCollection<IBindPath> bindPaths, Dictionary<string, string> bindVariables, string outputPath, CancellationToken cancellationToken)
210 { 211 {
211 var libraries = this.LoadLibraries(libraryFiles, creator); 212 var libraries = this.LoadLibraries(libraryFiles, creator);
212 213
@@ -218,6 +219,7 @@ namespace WixToolset.Core.CommandLine
218 var context = this.ServiceProvider.GetService<ILibraryContext>(); 219 var context = this.ServiceProvider.GetService<ILibraryContext>();
219 context.BindFiles = bindFiles; 220 context.BindFiles = bindFiles;
220 context.BindPaths = bindPaths; 221 context.BindPaths = bindPaths;
222 context.BindVariables = bindVariables;
221 context.Extensions = this.ExtensionManager.GetServices<ILibrarianExtension>(); 223 context.Extensions = this.ExtensionManager.GetServices<ILibrarianExtension>();
222 context.Localizations = localizations; 224 context.Localizations = localizations;
223 context.IntermediateFolder = this.IntermediateFolder; 225 context.IntermediateFolder = this.IntermediateFolder;
@@ -266,12 +268,13 @@ namespace WixToolset.Core.CommandLine
266 return linker.Link(context); 268 return linker.Link(context);
267 } 269 }
268 270
269 private void BindPhase(Intermediate output, IReadOnlyCollection<Localization> localizations, IReadOnlyCollection<string> filterCultures, string cabCachePath, int cabbingThreadCount, IReadOnlyCollection<IBindPath> bindPaths, InputsAndOutputs inputsOutputs, CancellationToken cancellationToken) 271 private void BindPhase(Intermediate output, IReadOnlyCollection<Localization> localizations, IReadOnlyCollection<string> filterCultures, string cabCachePath, int cabbingThreadCount, IReadOnlyCollection<IBindPath> bindPaths, Dictionary<string, string> bindVariables, InputsAndOutputs inputsOutputs, CancellationToken cancellationToken)
270 { 272 {
271 IResolveResult resolveResult; 273 IResolveResult resolveResult;
272 { 274 {
273 var context = this.ServiceProvider.GetService<IResolveContext>(); 275 var context = this.ServiceProvider.GetService<IResolveContext>();
274 context.BindPaths = bindPaths; 276 context.BindPaths = bindPaths;
277 context.BindVariables = bindVariables;
275 context.Extensions = this.ExtensionManager.GetServices<IResolverExtension>(); 278 context.Extensions = this.ExtensionManager.GetServices<IResolverExtension>();
276 context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>(); 279 context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>();
277 context.FilterCultures = filterCultures; 280 context.FilterCultures = filterCultures;
@@ -466,6 +469,8 @@ namespace WixToolset.Core.CommandLine
466 469
467 public List<IBindPath> BindPaths { get; } = new List<IBindPath>(); 470 public List<IBindPath> BindPaths { get; } = new List<IBindPath>();
468 471
472 public Dictionary<string, string> BindVariables { get; } = new Dictionary<string, string>();
473
469 public string CabCachePath { get; private set; } 474 public string CabCachePath { get; private set; }
470 475
471 public int CabbingThreadCount { get; private set; } 476 public int CabbingThreadCount { get; private set; }
@@ -564,6 +569,24 @@ namespace WixToolset.Core.CommandLine
564 return true; 569 return true;
565 } 570 }
566 571
572 case "bv":
573 case "bindvariable":
574 {
575 var value = parser.GetNextArgumentOrError(arg);
576 if (value != null && TryParseNameValuePair(value, out var parsedName, out var parsedValue))
577 {
578 if (this.BindVariables.TryGetValue(parsedName, out var collisionValue))
579 {
580 parser.ReportErrorArgument(arg, LinkerErrors.DuplicateBindPathVariableOnCommandLine(arg, parsedName, parsedValue, collisionValue));
581 }
582 else
583 {
584 this.BindVariables.Add(parsedName, parsedValue);
585 }
586 }
587 return true;
588 }
589
567 case "cc": 590 case "cc":
568 case "cabcache": 591 case "cabcache":
569 this.CabCachePath = parser.GetNextArgumentOrError(arg); 592 this.CabCachePath = parser.GetNextArgumentOrError(arg);
@@ -651,20 +674,20 @@ namespace WixToolset.Core.CommandLine
651 return true; 674 return true;
652 675
653 case "pdbtype": 676 case "pdbtype":
677 {
678 var value = parser.GetNextArgumentOrError(arg);
679 if (Enum.TryParse(value, true, out PdbType pdbType))
654 { 680 {
655 var value = parser.GetNextArgumentOrError(arg); 681 this.PdbType = pdbType;
656 if (Enum.TryParse(value, true, out PdbType pdbType)) 682 }
657 { 683 else if (!String.IsNullOrEmpty(value))
658 this.PdbType = pdbType; 684 {
659 } 685 parser.ReportErrorArgument(arg, ErrorMessages.IllegalCommandLineArgumentValue(arg, value, Enum.GetNames(typeof(PdbType)).Select(s => s.ToLowerInvariant())));
660 else if (!String.IsNullOrEmpty(value))
661 {
662 parser.ReportErrorArgument(arg, ErrorMessages.IllegalCommandLineArgumentValue(arg, value, Enum.GetNames(typeof(PdbType)).Select(s => s.ToLowerInvariant())));
663 }
664
665 return true;
666 } 686 }
667 687
688 return true;
689 }
690
668 case "resetacls": 691 case "resetacls":
669 this.ResetAcls = true; 692 this.ResetAcls = true;
670 return true; 693 return true;
@@ -870,18 +893,16 @@ namespace WixToolset.Core.CommandLine
870 893
871 private bool TryParseBindPath(string argument, string bindPath, out IBindPath bp) 894 private bool TryParseBindPath(string argument, string bindPath, out IBindPath bp)
872 { 895 {
873 var namedPath = bindPath.Split(BindPathSplit, 2);
874
875 bp = this.ServiceProvider.GetService<IBindPath>(); 896 bp = this.ServiceProvider.GetService<IBindPath>();
876 897
877 if (1 == namedPath.Length) 898 if (TryParseNameValuePair(bindPath, out var name, out var path))
878 { 899 {
879 bp.Path = namedPath[0]; 900 bp.Name = name;
901 bp.Path = path;
880 } 902 }
881 else 903 else
882 { 904 {
883 bp.Name = namedPath[0]; 905 bp.Path = bindPath;
884 bp.Path = namedPath[1];
885 } 906 }
886 907
887 if (File.Exists(bp.Path)) 908 if (File.Exists(bp.Path))
@@ -892,6 +913,24 @@ namespace WixToolset.Core.CommandLine
892 913
893 return true; 914 return true;
894 } 915 }
916
917 private static bool TryParseNameValuePair(string nameValuePair, out string key, out string value)
918 {
919 var split = nameValuePair.Split(BindPathSplit, 2);
920
921 if (1 == split.Length)
922 {
923 key = null;
924 value = null;
925
926 return false;
927 }
928
929 key = split[0];
930 value = split[1];
931
932 return true;
933 }
895 } 934 }
896 935
897 private class InputsAndOutputs 936 private class InputsAndOutputs
diff --git a/src/wix/WixToolset.Core/Librarian.cs b/src/wix/WixToolset.Core/Librarian.cs
index 968dd946..1f366d55 100644
--- a/src/wix/WixToolset.Core/Librarian.cs
+++ b/src/wix/WixToolset.Core/Librarian.cs
@@ -99,6 +99,11 @@ namespace WixToolset.Core
99 { 99 {
100 var variableResolver = this.ServiceProvider.GetService<IVariableResolver>(); 100 var variableResolver = this.ServiceProvider.GetService<IVariableResolver>();
101 101
102 foreach (var bindVariable in context.BindVariables)
103 {
104 variableResolver.AddVariable(null, bindVariable.Key, bindVariable.Value, false);
105 }
106
102 var bindPaths = context.BindPaths.Where(b => b.Stage == BindStage.Normal).ToList(); 107 var bindPaths = context.BindPaths.Where(b => b.Stage == BindStage.Normal).ToList();
103 108
104 foreach (var symbol in sections.SelectMany(s => s.Symbols)) 109 foreach (var symbol in sections.SelectMany(s => s.Symbols))
diff --git a/src/wix/WixToolset.Core/LibraryContext.cs b/src/wix/WixToolset.Core/LibraryContext.cs
index a463844e..207e8e1b 100644
--- a/src/wix/WixToolset.Core/LibraryContext.cs
+++ b/src/wix/WixToolset.Core/LibraryContext.cs
@@ -25,6 +25,8 @@ namespace WixToolset.Core
25 25
26 public IReadOnlyCollection<IBindPath> BindPaths { get; set; } 26 public IReadOnlyCollection<IBindPath> BindPaths { get; set; }
27 27
28 public IDictionary<string, string> BindVariables { get; set; }
29
28 public IReadOnlyCollection<ILibrarianExtension> Extensions { get; set; } 30 public IReadOnlyCollection<ILibrarianExtension> Extensions { get; set; }
29 31
30 public string LibraryId { get; set; } 32 public string LibraryId { get; set; }
diff --git a/src/wix/WixToolset.Core/Linker.cs b/src/wix/WixToolset.Core/Linker.cs
index 9360c8d0..a3d99039 100644
--- a/src/wix/WixToolset.Core/Linker.cs
+++ b/src/wix/WixToolset.Core/Linker.cs
@@ -333,7 +333,7 @@ namespace WixToolset.Core
333 } 333 }
334 else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) 334 else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable))
335 { 335 {
336 this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); 336 this.Messaging.Write(ErrorMessages.BindVariableCollision(symbol.SourceLineNumbers, id));
337 } 337 }
338 } 338 }
339 else 339 else
diff --git a/src/wix/WixToolset.Core/LinkerErrors.cs b/src/wix/WixToolset.Core/LinkerErrors.cs
index 1d9a5a07..cb13a344 100644
--- a/src/wix/WixToolset.Core/LinkerErrors.cs
+++ b/src/wix/WixToolset.Core/LinkerErrors.cs
@@ -6,6 +6,11 @@ namespace WixToolset.Core
6 6
7 internal static class LinkerErrors 7 internal static class LinkerErrors
8 { 8 {
9 public static Message DuplicateBindPathVariableOnCommandLine(string argument, string bindName, string bindValue, string collisionValue)
10 {
11 return Message(null, Ids.DuplicateBindPathVariableOnCommandLine, "", argument, bindName, bindValue, collisionValue);
12 }
13
9 public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) 14 public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId)
10 { 15 {
11 return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); 16 return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId);
@@ -55,6 +60,7 @@ namespace WixToolset.Core
55 UnscheduledRollbackBoundary = 7004, 60 UnscheduledRollbackBoundary = 7004,
56 UncompressedPayloadInContainer = 7005, 61 UncompressedPayloadInContainer = 7005,
57 BAContainerCannotContainRemotePayload = 7006, 62 BAContainerCannotContainRemotePayload = 7006,
63 DuplicateBindPathVariableOnCommandLine = 7007,
58 } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. 64 } // last available is 7099. 7100 is WindowsInstallerBackendWarnings.
59 } 65 }
60} 66}
diff --git a/src/wix/WixToolset.Core/ResolveContext.cs b/src/wix/WixToolset.Core/ResolveContext.cs
index a56ce4c9..0d6d1925 100644
--- a/src/wix/WixToolset.Core/ResolveContext.cs
+++ b/src/wix/WixToolset.Core/ResolveContext.cs
@@ -8,8 +8,7 @@ namespace WixToolset.Core
8 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Extensibility; 9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data; 10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services; 11
12
13 internal class ResolveContext : IResolveContext 12 internal class ResolveContext : IResolveContext
14 { 13 {
15 internal ResolveContext(IServiceProvider serviceProvider) 14 internal ResolveContext(IServiceProvider serviceProvider)
@@ -21,6 +20,8 @@ namespace WixToolset.Core
21 20
22 public IReadOnlyCollection<IBindPath> BindPaths { get; set; } 21 public IReadOnlyCollection<IBindPath> BindPaths { get; set; }
23 22
23 public IDictionary<string, string> BindVariables { get; set; }
24
24 public IReadOnlyCollection<IResolverExtension> Extensions { get; set; } 25 public IReadOnlyCollection<IResolverExtension> Extensions { get; set; }
25 26
26 public IReadOnlyCollection<IExtensionData> ExtensionData { get; set; } 27 public IReadOnlyCollection<IExtensionData> ExtensionData { get; set; }
@@ -33,8 +34,6 @@ namespace WixToolset.Core
33 34
34 public IReadOnlyCollection<Localization> Localizations { get; set; } 35 public IReadOnlyCollection<Localization> Localizations { get; set; }
35 36
36 public IVariableResolver VariableResolver { get; set; }
37
38 public bool AllowUnresolvedVariables { get; set; } 37 public bool AllowUnresolvedVariables { get; set; }
39 38
40 public string OutputPath { get; set; } 39 public string OutputPath { get; set; }
diff --git a/src/wix/WixToolset.Core/Resolver.cs b/src/wix/WixToolset.Core/Resolver.cs
index f7aa6ff9..2eebc80a 100644
--- a/src/wix/WixToolset.Core/Resolver.cs
+++ b/src/wix/WixToolset.Core/Resolver.cs
@@ -187,6 +187,11 @@ namespace WixToolset.Core
187 variableResolver.AddLocalization(localization); 187 variableResolver.AddLocalization(localization);
188 } 188 }
189 189
190 foreach (var bindVariable in context.BindVariables)
191 {
192 variableResolver.AddVariable(null, bindVariable.Key, bindVariable.Value, false);
193 }
194
190 // Gather all the wix variables. 195 // Gather all the wix variables.
191 var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType<WixVariableSymbol>(); 196 var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType<WixVariableSymbol>();
192 foreach (var symbol in wixVariableSymbols) 197 foreach (var symbol in wixVariableSymbols)
diff --git a/src/wix/WixToolset.Core/VariableResolver.cs b/src/wix/WixToolset.Core/VariableResolver.cs
index 437cabb7..987a2290 100644
--- a/src/wix/WixToolset.Core/VariableResolver.cs
+++ b/src/wix/WixToolset.Core/VariableResolver.cs
@@ -23,7 +23,6 @@ namespace WixToolset.Core
23 /// </summary> 23 /// </summary>
24 internal VariableResolver(IServiceProvider serviceProvider) 24 internal VariableResolver(IServiceProvider serviceProvider)
25 { 25 {
26 this.ServiceProvider = serviceProvider;
27 this.Messaging = serviceProvider.GetService<IMessaging>(); 26 this.Messaging = serviceProvider.GetService<IMessaging>();
28 27
29 this.locVariables = new Dictionary<string, BindVariable>(); 28 this.locVariables = new Dictionary<string, BindVariable>();
@@ -31,8 +30,6 @@ namespace WixToolset.Core
31 this.localizedControls = new Dictionary<string, LocalizedControl>(); 30 this.localizedControls = new Dictionary<string, LocalizedControl>();
32 } 31 }
33 32
34 private IServiceProvider ServiceProvider { get; }
35
36 private IMessaging Messaging { get; } 33 private IMessaging Messaging { get; }
37 34
38 public int VariableCount => this.wixVariables.Count; 35 public int VariableCount => this.wixVariables.Count;
@@ -47,7 +44,7 @@ namespace WixToolset.Core
47 } 44 }
48 } 45 }
49 46
50 foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls) 47 foreach (var localizedControl in localization.LocalizedControls)
51 { 48 {
52 if (!this.localizedControls.ContainsKey(localizedControl.Key)) 49 if (!this.localizedControls.ContainsKey(localizedControl.Key))
53 { 50 {
@@ -62,7 +59,7 @@ namespace WixToolset.Core
62 59
63 if (!TryAddWixVariable(this.wixVariables, bindVariable)) 60 if (!TryAddWixVariable(this.wixVariables, bindVariable))
64 { 61 {
65 this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name)); 62 this.Messaging.Write(ErrorMessages.BindVariableCollision(sourceLineNumber, name));
66 } 63 }
67 } 64 }
68 65
@@ -166,7 +163,7 @@ namespace WixToolset.Core
166 } 163 }
167 else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable 164 else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
168 { 165 {
169 this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); 166 this.Messaging.Write(ErrorMessages.BindVariableUnknown(sourceLineNumbers, variableId));
170 } 167 }
171 168
172 start = parsed.Index + parsed.Length; 169 start = parsed.Index + parsed.Length;
diff --git a/src/wix/WixToolset.Sdk/tools/wix.targets b/src/wix/WixToolset.Sdk/tools/wix.targets
index 067fa412..ff9b2eb1 100644
--- a/src/wix/WixToolset.Sdk/tools/wix.targets
+++ b/src/wix/WixToolset.Sdk/tools/wix.targets
@@ -41,7 +41,7 @@
41 </ItemGroup> 41 </ItemGroup>
42 42
43 <ItemGroup> 43 <ItemGroup>
44 <AvailableItemName Include="BindPath;WixLibrary;WixExtension" /> 44 <AvailableItemName Include="BindPath;BindVariable;WixLibrary;WixExtension" />
45 </ItemGroup> 45 </ItemGroup>
46 46
47 <!-- 47 <!--
@@ -170,6 +170,7 @@
170 <LinkerBindInputPaths Include="$(LinkerBindInputPaths)" Condition=" '$(LinkerBindInputPaths)' != '' " /> 170 <LinkerBindInputPaths Include="$(LinkerBindInputPaths)" Condition=" '$(LinkerBindInputPaths)' != '' " />
171 <BindPath Include="$(BindPath)" Condition=" '$(BindPath)' != '' " /> 171 <BindPath Include="$(BindPath)" Condition=" '$(BindPath)' != '' " />
172 <BindPath Include="@(BindInputPaths);@(LinkerBindInputPaths)" /> 172 <BindPath Include="@(BindInputPaths);@(LinkerBindInputPaths)" />
173 <BindVariable Include="$(WixVariables)" Condition=" '$(WixVariables)' != '' " />
173 </ItemGroup> 174 </ItemGroup>
174 175
175 <!-- 176 <!--
@@ -601,6 +602,7 @@
601 Pedantic="$(Pedantic)" 602 Pedantic="$(Pedantic)"
602 603
603 BindPaths="@(BindPath)" 604 BindPaths="@(BindPath)"
605 BindVariables="@(BindVariable)"
604 BindFiles="$(BindFiles)" 606 BindFiles="$(BindFiles)"
605 BindTrackingFile="$(IntermediateOutputPath)$(BindTrackingFilePrefix)%(CultureGroup.Identity)$(BindTrackingFileExtension)" 607 BindTrackingFile="$(IntermediateOutputPath)$(BindTrackingFilePrefix)%(CultureGroup.Identity)$(BindTrackingFileExtension)"
606 608
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/wix/test/WixToolsetTest.Core/VariableResolverFixture.cs
index a40eb366..89951c2a 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs
+++ b/src/wix/test/WixToolsetTest.Core/VariableResolverFixture.cs
@@ -1,7 +1,6 @@
1
2// 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. 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.
3 2
4namespace WixToolsetTest.CoreIntegration 3namespace WixToolsetTest.Core
5{ 4{
6 using System.Collections.Generic; 5 using System.Collections.Generic;
7 using WixInternal.TestSupport; 6 using WixInternal.TestSupport;
@@ -26,7 +25,7 @@ namespace WixToolsetTest.CoreIntegration
26 { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, 25 { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } },
27 }; 26 };
28 27
29 var localization = new Localization(0, null, "x-none", variables, new Dictionary<string,LocalizedControl>()); 28 var localization = new Localization(0, null, "x-none", variables, new Dictionary<string, LocalizedControl>());
30 29
31 variableResolver.AddLocalization(localization); 30 variableResolver.AddLocalization(localization);
32 31
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/Bundle.wxs
new file mode 100644
index 00000000..bda708b0
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/Bundle.wxs
@@ -0,0 +1,11 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="MsiPackage-Bundle" Version="!(wix.VersionVar)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6
7 <Chain>
8 <MsiPackage SourceFile="test1.msi" />
9 </Chain>
10 </Bundle>
11</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithBindVariableVersion.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithBindVariableVersion.wxs
new file mode 100644
index 00000000..14c1cbcd
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithBindVariableVersion.wxs
@@ -0,0 +1,33 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Version="!(wix.VersionVar)"
3 Name="MsiPackage"
4 Manufacturer="Example Corporation"
5 UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"
6 Scope="perUser">
7 <MajorUpgrade DowngradeErrorMessage="Downgrade not allowed" />
8 <MediaTemplate EmbedCab="true" />
9
10 <Feature Id="ProductFeature" Title="Feature title">
11 <ComponentGroupRef Id="ProductComponents" />
12 </Feature>
13
14 <WixVariable Id="VersionVar" Value="v1.1.1.1" Overridable="true" />
15 </Package>
16
17 <Fragment>
18 <StandardDirectory Id="DesktopFolder">
19 <Directory Id="INSTALLFOLDER" Name="MsiPackage !(wix.VersionVar) and !(bind.property.ProductVersion)" />
20 </StandardDirectory>
21 </Fragment>
22
23 <Fragment>
24 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
25 <Component>
26 <File Source="test.txt" />
27 </Component>
28 <Component Id="Shared.dll" Shared="yes">
29 <File Name="Shared.dll" Source="test.txt" />
30 </Component>
31 </ComponentGroup>
32 </Fragment>
33</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithReplaceableVersion.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithReplaceableVersion.wxs
new file mode 100644
index 00000000..cf948131
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariable/PackageWithReplaceableVersion.wxs
@@ -0,0 +1,22 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Version="!(wix.VersionVar)"
3 Name="MsiPackage"
4 Manufacturer="Example Corporation"
5 UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"
6 Scope="perUser">
7 <MajorUpgrade DowngradeErrorMessage="Downgrade not allowed" />
8 <MediaTemplate EmbedCab="true" />
9
10 <Feature Id="ProductFeature" Title="Feature title">
11 <Component Directory="INSTALLFOLDER">
12 <File Source="!(wix.DataBindVariable)\test.txt" />
13 </Component>
14 </Feature>
15 </Package>
16
17 <Fragment>
18 <StandardDirectory Id="DesktopFolder">
19 <Directory Id="INSTALLFOLDER" Name="MsiPackage !(wix.VersionVar)" />
20 </StandardDirectory>
21 </Fragment>
22</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixVariableFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixVariableFixture.cs
new file mode 100644
index 00000000..50199b74
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixVariableFixture.cs
@@ -0,0 +1,174 @@
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 WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using System.Xml;
8 using WixInternal.Core.TestPackage;
9 using WixInternal.TestSupport;
10 using Xunit;
11
12 public class WixVariableFixture
13 {
14 [Fact]
15 public void CanBuildMsiWithBindVariable()
16 {
17 var folder = TestData.Get(@"TestData");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var msiPath = Path.Combine(baseFolder, "bin", "test1.msi");
24
25 var result = WixRunner.Execute(new[]
26 {
27 "build",
28 Path.Combine(folder, "WixVariable", "PackageWithReplaceableVersion.wxs"),
29 "-bindpath", Path.Combine(folder, "SingleFile"/*, this part of the path is added as a bind variable: "data"*/),
30 "-bindvariable", "DataBindVariable=data",
31 "-bindvariable", "VersionVar=4.3.2.1",
32 "-intermediateFolder", intermediateFolder,
33 "-o", msiPath
34 });
35
36 result.AssertSuccess();
37
38 var productVersion = GetProductVersionFromMsi(msiPath);
39 Assert.Equal("4.3.2.1", productVersion);
40 }
41 }
42
43 [Fact]
44 public void CanBuildMsiWithDefaultedBindVariable()
45 {
46 var folder = TestData.Get(@"TestData");
47
48 using (var fs = new DisposableFileSystem())
49 {
50 var baseFolder = fs.GetFolder();
51 var intermediateFolder = Path.Combine(baseFolder, "obj");
52 var msiPath = Path.Combine(baseFolder, "bin", "test1.msi");
53
54 var result = WixRunner.Execute(new[]
55 {
56 "build",
57 Path.Combine(folder, "WixVariable", "PackageWithBindVariableVersion.wxs"),
58 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
59 "-intermediateFolder", intermediateFolder,
60 "-o", msiPath
61 });
62
63 result.AssertSuccess();
64
65 var productVersion = GetProductVersionFromMsi(msiPath);
66 Assert.Equal("1.1.1.1", productVersion);
67
68 var directoryTable = Query.QueryDatabase(msiPath, new[] { "Directory" }).OrderBy(s => s).ToArray();
69 WixAssert.CompareLineByLine(new[]
70 {
71 "Directory:DesktopFolder\tTARGETDIR\tDesktop",
72 "Directory:INSTALLFOLDER\tDesktopFolder\tfcuah1wu|MsiPackage v1.1.1.1 and 1.1.1.1",
73 "Directory:TARGETDIR\t\tSourceDir"
74 }, directoryTable);
75 }
76 }
77
78 [Fact]
79 public void CanBuildMsiWithPrefixedVersionBindVariable()
80 {
81 var folder = TestData.Get(@"TestData");
82
83 using (var fs = new DisposableFileSystem())
84 {
85 var baseFolder = fs.GetFolder();
86 var intermediateFolder = Path.Combine(baseFolder, "obj");
87 var msiPath = Path.Combine(baseFolder, "bin", "test1.msi");
88
89 var result = WixRunner.Execute(new[]
90 {
91 "build",
92 Path.Combine(folder, "WixVariable", "PackageWithBindVariableVersion.wxs"),
93 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
94 "-intermediateFolder", intermediateFolder,
95 "-bindvariable", "VersionVar=v9.8.7.6",
96 "-o", msiPath
97 });
98
99 result.AssertSuccess();
100
101 var productVersion = GetProductVersionFromMsi(msiPath);
102 Assert.Equal("9.8.7.6", productVersion);
103
104 var directoryTable = Query.QueryDatabase(msiPath, new[] { "Directory" }).OrderBy(s => s).ToArray();
105 WixAssert.CompareLineByLine(new[]
106 {
107 "Directory:DesktopFolder\tTARGETDIR\tDesktop",
108 "Directory:INSTALLFOLDER\tDesktopFolder\tpja2bznq|MsiPackage v9.8.7.6 and 9.8.7.6",
109 "Directory:TARGETDIR\t\tSourceDir"
110 }, directoryTable);
111 }
112 }
113
114 [Fact]
115 public void CanBuildBundleWithBindVariable()
116 {
117 var folder = TestData.Get(@"TestData");
118
119 using (var fs = new DisposableFileSystem())
120 {
121 var baseFolder = fs.GetFolder();
122 var intermediateFolder = Path.Combine(baseFolder, "obj");
123 var msiPath = Path.Combine(baseFolder, "bin", "test1.msi");
124 var msi2Path = Path.Combine(baseFolder, "bin", "test2.msi");
125 var bundlePath = Path.Combine(baseFolder, "bin", "bundle.exe");
126
127 var result = WixRunner.Execute(new[]
128 {
129 "build",
130 Path.Combine(folder, "WixVariable", "PackageWithReplaceableVersion.wxs"),
131 "-bindpath", Path.Combine(folder, "SingleFile"/*, this part of the path is added as a bind variable: "data"*/),
132 "-bv", "DataBindVariable=data",
133 "-bv", "VersionVar=255.255.65535",
134 "-intermediateFolder", intermediateFolder,
135 "-o", msiPath
136 });
137
138 result.AssertSuccess();
139
140 var result3 = WixRunner.Execute(new[]
141{
142 "build",
143 Path.Combine(folder, "WixVariable", "Bundle.wxs"),
144 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
145 "-bindpath", Path.Combine(baseFolder, "bin"),
146 "-intermediateFolder", intermediateFolder,
147 "-bv", "VersionVar=2022.3.9-preview.0-build.5+0987654321abcdef1234567890",
148 "-o", bundlePath
149 });
150
151 result3.AssertSuccess();
152
153 var productVersion = GetProductVersionFromMsi(msiPath);
154 WixAssert.StringEqual("255.255.65535", productVersion);
155
156 var extractResult = BundleExtractor.ExtractAllContainers(null, bundlePath, Path.Combine(baseFolder, "ba"), Path.Combine(baseFolder, "attached"), Path.Combine(baseFolder, "extract"));
157 extractResult.AssertSuccess();
158
159 var bundleVersion = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration/@Version")
160 .Cast<XmlAttribute>()
161 .Single();
162 WixAssert.StringEqual("2022.3.9-preview.0-build.5+0987654321abcdef1234567890", bundleVersion.Value);
163 }
164 }
165
166 private static string GetProductVersionFromMsi(string msiPath)
167 {
168 var propertyTable = Query.QueryDatabase(msiPath, new[] { "Property" }).Select(r => r.Split('\t')).ToDictionary(r => r[0].Substring("Property:".Length), r => r[1]);
169 Assert.True(propertyTable.TryGetValue("ProductVersion", out var productVersion));
170
171 return productVersion;
172 }
173 }
174}
diff --git a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
index 64d05a59..05de1e2a 100644
--- a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
+++ b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
@@ -233,6 +233,42 @@ namespace WixToolsetTest.Sdk
233 [InlineData(BuildSystem.DotNetCoreSdk)] 233 [InlineData(BuildSystem.DotNetCoreSdk)]
234 [InlineData(BuildSystem.MSBuild)] 234 [InlineData(BuildSystem.MSBuild)]
235 [InlineData(BuildSystem.MSBuild64)] 235 [InlineData(BuildSystem.MSBuild64)]
236 public void CanBuildMsiPackageWithBindVariables(BuildSystem buildSystem)
237 {
238 var sourceFolder = TestData.Get("TestData", "MsiPackageWithBindVariables");
239
240 var baseFolder = String.Empty;
241
242 using (var fs = new TestDataFolderFileSystem())
243 {
244 fs.Initialize(sourceFolder);
245 baseFolder = fs.BaseFolder;
246
247 var binFolder = Path.Combine(baseFolder, @"bin\");
248 var projectPath = Path.Combine(baseFolder, "MsiPackageWithBindVariables.wixproj");
249
250 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] {
251 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath)
252 });
253 result.AssertSuccess();
254
255 var paths = Directory.EnumerateFiles(binFolder, @"*.*", SearchOption.AllDirectories)
256 .Select(s => s.Substring(baseFolder.Length + 1))
257 .OrderBy(s => s)
258 .ToArray();
259 WixAssert.CompareLineByLine(new[]
260 {
261 @"bin\Release\en-US\cab1.cab",
262 @"bin\Release\en-US\MsiPackageWithBindVariables.msi",
263 @"bin\Release\en-US\MsiPackageWithBindVariables.wixpdb",
264 }, paths);
265 }
266 }
267
268 [Theory]
269 [InlineData(BuildSystem.DotNetCoreSdk)]
270 [InlineData(BuildSystem.MSBuild)]
271 [InlineData(BuildSystem.MSBuild64)]
236 public void CanBuildWithDefaultAndExplicitlyFullWixpdbs(BuildSystem buildSystem) 272 public void CanBuildWithDefaultAndExplicitlyFullWixpdbs(BuildSystem buildSystem)
237 { 273 {
238 var expectedOutputs = new[] 274 var expectedOutputs = new[]
@@ -299,33 +335,6 @@ namespace WixToolsetTest.Sdk
299 } 335 }
300 } 336 }
301 337
302 private void AssertWixpdb(BuildSystem buildSystem, string debugType, string[] expectedOutputFiles)
303 {
304 var sourceFolder = TestData.Get(@"TestData\SimpleMsiPackage\MsiPackage");
305
306 using (var fs = new TestDataFolderFileSystem())
307 {
308 fs.Initialize(sourceFolder);
309 var baseFolder = fs.BaseFolder;
310 var binFolder = Path.Combine(baseFolder, @"bin\");
311 var projectPath = Path.Combine(baseFolder, "MsiPackage.wixproj");
312
313 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[]
314 {
315 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath),
316 debugType == null ? String.Empty : $"-p:DebugType={debugType}",
317 "-p:SuppressValidation=true"
318 });
319 result.AssertSuccess();
320
321 var paths = Directory.EnumerateFiles(binFolder, @"*.*", SearchOption.AllDirectories)
322 .Select(s => s.Substring(baseFolder.Length + 1))
323 .OrderBy(s => s)
324 .ToArray();
325 WixAssert.CompareLineByLine(expectedOutputFiles, paths);
326 }
327 }
328
329 [Theory] 338 [Theory]
330 [InlineData(BuildSystem.DotNetCoreSdk)] 339 [InlineData(BuildSystem.DotNetCoreSdk)]
331 [InlineData(BuildSystem.MSBuild)] 340 [InlineData(BuildSystem.MSBuild)]
@@ -474,7 +483,7 @@ namespace WixToolsetTest.Sdk
474 var path = Directory.EnumerateFiles(binFolder, @"*.*", SearchOption.AllDirectories) 483 var path = Directory.EnumerateFiles(binFolder, @"*.*", SearchOption.AllDirectories)
475 .Select(s => s.Substring(baseFolder.Length + 1)) 484 .Select(s => s.Substring(baseFolder.Length + 1))
476 .Single(); 485 .Single();
477 WixAssert.StringEqual(@"bin\x86\Release\SimpleWixlib.wixlib", path); 486 WixAssert.StringEqual(@"bin\Release\SimpleWixlib.wixlib", path);
478 } 487 }
479 } 488 }
480 489
@@ -637,6 +646,33 @@ namespace WixToolsetTest.Sdk
637 } 646 }
638 } 647 }
639 648
649 private void AssertWixpdb(BuildSystem buildSystem, string debugType, string[] expectedOutputFiles)
650 {
651 var sourceFolder = TestData.Get(@"TestData\SimpleMsiPackage\MsiPackage");
652
653 using (var fs = new TestDataFolderFileSystem())
654 {
655 fs.Initialize(sourceFolder);
656 var baseFolder = fs.BaseFolder;
657 var binFolder = Path.Combine(baseFolder, @"bin\");
658 var projectPath = Path.Combine(baseFolder, "MsiPackage.wixproj");
659
660 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[]
661 {
662 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath),
663 debugType == null ? String.Empty : $"-p:DebugType={debugType}",
664 "-p:SuppressValidation=true"
665 });
666 result.AssertSuccess();
667
668 var paths = Directory.EnumerateFiles(binFolder, @"*.*", SearchOption.AllDirectories)
669 .Select(s => s.Substring(baseFolder.Length + 1))
670 .OrderBy(s => s)
671 .ToArray();
672 WixAssert.CompareLineByLine(expectedOutputFiles, paths);
673 }
674 }
675
640 private static string ExtractWarningFromMessage(string message, string baseFolder) 676 private static string ExtractWarningFromMessage(string message, string baseFolder)
641 { 677 {
642 const string prefix = ": warning "; 678 const string prefix = ": warning ";
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/MsiPackageWithBindVariables.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/MsiPackageWithBindVariables.wixproj
new file mode 100644
index 00000000..e6eb8f88
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/MsiPackageWithBindVariables.wixproj
@@ -0,0 +1,16 @@
1<Project>
2 <Import Project="$(WixMSBuildProps)" />
3 <PropertyGroup>
4 <WixVariables>DataFolderVar=data</WixVariables>
5 </PropertyGroup>
6
7 <ItemGroup>
8 <Compile Include="Package.wxs" />
9
10 <EmbeddedResource Include="Package.en-us.wxl" />
11
12 <BindVariable Include="VersionVar=1.2.3" />
13 </ItemGroup>
14
15 <Import Project="$(WixTargetsPath)" />
16</Project>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.en-us.wxl b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.en-us.wxl
new file mode 100644
index 00000000..2bcb83ee
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.en-us.wxl
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError" Value="A newer version of [ProductName] is already installed." />
9 <String Id="FeatureTitle" Value="MsiPackage" />
10
11</WixLocalization>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.wxs
new file mode 100644
index 00000000..c11ef3ba
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/Package.wxs
@@ -0,0 +1,17 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="!(wix.VersionVar)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
4
5 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
6 <Component Directory="INSTALLFOLDER">
7 <File Source="!(wix.DataFolderVar)\test.txt" />
8 </Component>
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <StandardDirectory Id="ProgramFiles6432Folder">
14 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
15 </StandardDirectory>
16 </Fragment>
17</Wix>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/data/test.txt b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/data/test.txt
new file mode 100644
index 00000000..cd0db0e1
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithBindVariables/data/test.txt
@@ -0,0 +1 @@
This is test.txt. \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/Library.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/Library.wxs
index 8ffb3e8a..95cc8cb3 100644
--- a/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/Library.wxs
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/Library.wxs
@@ -8,7 +8,7 @@
8 <Fragment> 8 <Fragment>
9 <ComponentGroup Id="ProductComponents" Directory="WixLibFolder"> 9 <ComponentGroup Id="ProductComponents" Directory="WixLibFolder">
10 <Component Id="TextFile" Guid="2D93B748-4926-4185-BC84-9F1D6883AF20"> 10 <Component Id="TextFile" Guid="2D93B748-4926-4185-BC84-9F1D6883AF20">
11 <File Source="Library.txt" /> 11 <File Source="!(wix.SubfolderVar)\Library.txt" />
12 </Component> 12 </Component>
13 </ComponentGroup> 13 </ComponentGroup>
14 </Fragment> 14 </Fragment>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/SimpleWixlib.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/SimpleWixlib.wixproj
index 88dfdc39..415bacc7 100644
--- a/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/SimpleWixlib.wixproj
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/SimpleWixlib.wixproj
@@ -1,34 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 2<Project>
3 <Import Project="$(WixMSBuildProps)" /> 3 <Import Project="$(WixMSBuildProps)" />
4
4 <PropertyGroup> 5 <PropertyGroup>
5 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6 <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
7 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
8 <BindFiles>true</BindFiles> 7 <BindFiles>true</BindFiles>
9 </PropertyGroup> 8 <WixVariables>SubfolderVar=subfolder</WixVariables>
10
11 <PropertyGroup>
12 <ProjectGuid>{9F84998B-7F45-4CB3-8795-915801DBBB74}</ProjectGuid>
13 </PropertyGroup>
14
15 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
16 <PlatformName>$(Platform)</PlatformName>
17 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
18 <DefineConstants>Debug</DefineConstants>
19 </PropertyGroup>
20 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
21 <PlatformName>$(Platform)</PlatformName>
22 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
23 </PropertyGroup>
24 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
25 <PlatformName>$(Platform)</PlatformName>
26 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
27 <DefineConstants>Debug</DefineConstants>
28 </PropertyGroup>
29 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
30 <PlatformName>$(Platform)</PlatformName>
31 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
32 </PropertyGroup> 9 </PropertyGroup>
33 10
34 <ItemGroup> 11 <ItemGroup>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/data/Library.txt b/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/data/subfolder/Library.txt
index 19a811ae..19a811ae 100644
--- a/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/data/Library.txt
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/Wixlib/SimpleWixlib/data/subfolder/Library.txt