aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs15
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLine.cs29
-rw-r--r--src/WixToolset.Core/CommandLine/CompileCommand.cs13
-rw-r--r--src/WixToolset.Core/Compiler.cs7
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs479
-rw-r--r--src/WixToolset.Core/Preprocess/PreprocessorOperation.cs19
-rw-r--r--src/WixToolset.Core/PreprocessContext.cs36
-rw-r--r--src/WixToolset.Core/Preprocessor.cs453
-rw-r--r--src/WixToolset.Core/PreprocessorCore.cs560
-rw-r--r--src/WixToolset.Core/WixToolsetServiceProvider.cs11
-rw-r--r--src/test/Example.Extension/ExampleExtensionFactory.cs11
-rw-r--r--src/test/Example.Extension/ExamplePreprocessorExtension.cs55
-rw-r--r--src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs46
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs43
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs2
15 files changed, 842 insertions, 937 deletions
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
index 54bf688d..79bacd22 100644
--- a/src/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -40,6 +40,8 @@ namespace WixToolset.Core
40 40
41 public IExtensionManager ExtensionManager { get; } 41 public IExtensionManager ExtensionManager { get; }
42 42
43 public IEnumerable<string> IncludeSearchPaths { get; }
44
43 public IEnumerable<string> LocFiles { get; } 45 public IEnumerable<string> LocFiles { get; }
44 46
45 public IEnumerable<string> LibraryFiles { get; } 47 public IEnumerable<string> LibraryFiles { get; }
@@ -102,15 +104,18 @@ namespace WixToolset.Core
102 { 104 {
103 var intermediates = new List<Intermediate>(); 105 var intermediates = new List<Intermediate>();
104 106
105
106 foreach (var sourceFile in this.SourceFiles) 107 foreach (var sourceFile in this.SourceFiles)
107 { 108 {
108 //var preprocessContext = this.ServiceProvider.GetService<IPreprocessContext>(); 109 var preprocessContext = this.ServiceProvider.GetService<IPreprocessContext>();
109 //preprocessContext.SourcePath = sourceFile.SourcePath; 110 preprocessContext.Messaging = Messaging.Instance;
110 //preprocessContext.Variables = this.PreprocessorVariables; 111 preprocessContext.Extensions = this.ExtensionManager.Create<IPreprocessorExtension>();
112 preprocessContext.Platform = Platform.X86; // TODO: set this correctly
113 preprocessContext.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List<string>();
114 preprocessContext.SourceFile = sourceFile.SourcePath;
115 preprocessContext.Variables = new Dictionary<string, string>(this.PreprocessorVariables);
111 116
112 var preprocessor = new Preprocessor(); 117 var preprocessor = new Preprocessor();
113 var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables); 118 var document = preprocessor.Process(preprocessContext);
114 119
115 var compileContext = this.ServiceProvider.GetService<ICompileContext>(); 120 var compileContext = this.ServiceProvider.GetService<ICompileContext>();
116 compileContext.Messaging = Messaging.Instance; 121 compileContext.Messaging = Messaging.Instance;
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs
index c6fe11b7..9bedca9a 100644
--- a/src/WixToolset.Core/CommandLine/CommandLine.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLine.cs
@@ -22,7 +22,7 @@ namespace WixToolset.Core
22 Bind, 22 Bind,
23 } 23 }
24 24
25 internal class CommandLine : ICommandLine 25 internal class CommandLine : ICommandLine, IParseCommandLine
26 { 26 {
27 public CommandLine() 27 public CommandLine()
28 { 28 {
@@ -57,10 +57,10 @@ namespace WixToolset.Core
57 args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray(); 57 args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray();
58 } 58 }
59 59
60 return this.ParseStandardCommandLine(args); 60 return this.ParseStandardCommandLine(context, args);
61 } 61 }
62 62
63 private ICommandLineCommand ParseStandardCommandLine(string[] args) 63 private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args)
64 { 64 {
65 var next = String.Empty; 65 var next = String.Empty;
66 66
@@ -90,7 +90,7 @@ namespace WixToolset.Core
90 var builtOutputsFile = String.Empty; 90 var builtOutputsFile = String.Empty;
91 var wixProjectFile = String.Empty; 91 var wixProjectFile = String.Empty;
92 92
93 this.Parse(args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => 93 this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) =>
94 { 94 {
95 if (cmdline.IsSwitch(arg)) 95 if (cmdline.IsSwitch(arg))
96 { 96 {
@@ -279,13 +279,13 @@ namespace WixToolset.Core
279 } 279 }
280#endif 280#endif
281 281
282 private ICommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseCommand, Func<CommandLine, string, bool> parseArgument) 282 private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func<CommandLine, string, bool> parseCommand, Func<CommandLine, string, bool> parseArgument)
283 { 283 {
284 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments); 284 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
285 285
286 this.QueueArgumentsAndLoadExtensions(this.OriginalArguments); 286 this.QueueArgumentsAndLoadExtensions(this.OriginalArguments);
287 287
288 this.ProcessRemainingArguments(parseArgument, parseCommand); 288 this.ProcessRemainingArguments(context, parseArgument, parseCommand);
289 289
290 return this; 290 return this;
291 } 291 }
@@ -413,7 +413,7 @@ namespace WixToolset.Core
413 /// <returns>True if a valid switch exists there, false if not.</returns> 413 /// <returns>True if a valid switch exists there, false if not.</returns>
414 public bool IsSwitch(string arg) 414 public bool IsSwitch(string arg)
415 { 415 {
416 return arg != null && ('/' == arg[0] || '-' == arg[0]); 416 return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]);
417 } 417 }
418 418
419 /// <summary> 419 /// <summary>
@@ -522,10 +522,15 @@ namespace WixToolset.Core
522 } 522 }
523 } 523 }
524 524
525 private void ProcessRemainingArguments(Func<CommandLine, string, bool> parseArgument, Func<CommandLine, string, bool> parseCommand) 525 private void ProcessRemainingArguments(ICommandLineContext context, Func<CommandLine, string, bool> parseArgument, Func<CommandLine, string, bool> parseCommand)
526 { 526 {
527 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>(); 527 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
528 528
529 foreach (var extension in extensions)
530 {
531 extension.PreParse(context);
532 }
533
529 while (!this.ShowHelp && 534 while (!this.ShowHelp &&
530 String.IsNullOrEmpty(this.ErrorArgument) && 535 String.IsNullOrEmpty(this.ErrorArgument) &&
531 TryDequeue(this.RemainingArguments, out var arg)) 536 TryDequeue(this.RemainingArguments, out var arg))
@@ -566,10 +571,10 @@ namespace WixToolset.Core
566 { 571 {
567 foreach (var extension in extensions) 572 foreach (var extension in extensions)
568 { 573 {
569 //if (extension.ParseArgument(this, arg)) 574 if (extension.TryParseArgument(this, arg))
570 //{ 575 {
571 // return true; 576 return true;
572 //} 577 }
573 } 578 }
574 579
575 return false; 580 return false;
diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs
index 58ba9d29..e7fcdd4d 100644
--- a/src/WixToolset.Core/CommandLine/CompileCommand.cs
+++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs
@@ -4,6 +4,7 @@ namespace WixToolset.Core
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Linq;
7 using WixToolset.Data; 8 using WixToolset.Data;
8 using WixToolset.Extensibility; 9 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services; 10 using WixToolset.Extensibility.Services;
@@ -22,6 +23,8 @@ namespace WixToolset.Core
22 23
23 private IExtensionManager ExtensionManager { get; } 24 private IExtensionManager ExtensionManager { get; }
24 25
26 public IEnumerable<string> IncludeSearchPaths { get; }
27
25 private IEnumerable<SourceFile> SourceFiles { get; } 28 private IEnumerable<SourceFile> SourceFiles { get; }
26 29
27 private IDictionary<string, string> PreprocessorVariables { get; } 30 private IDictionary<string, string> PreprocessorVariables { get; }
@@ -30,8 +33,16 @@ namespace WixToolset.Core
30 { 33 {
31 foreach (var sourceFile in this.SourceFiles) 34 foreach (var sourceFile in this.SourceFiles)
32 { 35 {
36 var preprocessContext = this.ServiceProvider.GetService<IPreprocessContext>();
37 preprocessContext.Messaging = Messaging.Instance;
38 preprocessContext.Extensions = this.ExtensionManager.Create<IPreprocessorExtension>();
39 preprocessContext.Platform = Platform.X86; // TODO: set this correctly
40 preprocessContext.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List<string>();
41 preprocessContext.SourceFile = sourceFile.SourcePath;
42 preprocessContext.Variables = new Dictionary<string, string>(this.PreprocessorVariables);
43
33 var preprocessor = new Preprocessor(); 44 var preprocessor = new Preprocessor();
34 var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables); 45 var document = preprocessor.Process(preprocessContext);
35 46
36 var compileContext = this.ServiceProvider.GetService<ICompileContext>(); 47 var compileContext = this.ServiceProvider.GetService<ICompileContext>();
37 compileContext.Messaging = Messaging.Instance; 48 compileContext.Messaging = Messaging.Instance;
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs
index 406bc46a..1c1c2f0a 100644
--- a/src/WixToolset.Core/Compiler.cs
+++ b/src/WixToolset.Core/Compiler.cs
@@ -10825,9 +10825,10 @@ namespace WixToolset.Core
10825 switch (installScopeType) 10825 switch (installScopeType)
10826 { 10826 {
10827 case Wix.Package.InstallScopeType.perMachine: 10827 case Wix.Package.InstallScopeType.perMachine:
10828 row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property); 10828 {
10829 row.Set(0, "ALLUSERS"); 10829 row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property, new Identifier("ALLUSERS", AccessModifier.Public));
10830 row.Set(1, "1"); 10830 row.Set(1, "1");
10831 }
10831 break; 10832 break;
10832 case Wix.Package.InstallScopeType.perUser: 10833 case Wix.Package.InstallScopeType.perUser:
10833 sourceBits = sourceBits | 8; 10834 sourceBits = sourceBits | 8;
diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs
new file mode 100644
index 00000000..3b8011c4
--- /dev/null
+++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs
@@ -0,0 +1,479 @@
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.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13
14 internal class PreprocessHelper : IPreprocessHelper
15 {
16 private static readonly char[] VariableSplitter = new char[] { '.' };
17 private static readonly char[] ArgumentSplitter = new char[] { ',' };
18
19 public PreprocessHelper(IServiceProvider serviceProvider)
20 {
21 this.ServiceProvider = serviceProvider;
22 }
23
24 private IServiceProvider ServiceProvider { get; }
25
26 private Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; set; }
27
28 public void AddVariable(IPreprocessContext context, string name, string value)
29 {
30 this.AddVariable(context, name, value, true);
31 }
32
33 public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning)
34 {
35 var currentValue = this.GetVariableValue(context, "var", name);
36
37 if (null == currentValue)
38 {
39 context.Variables.Add(name, value);
40 }
41 else
42 {
43 if (showWarning)
44 {
45 context.Messaging.OnMessage(WixWarnings.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue));
46 }
47
48 context.Variables[name] = value;
49 }
50 }
51
52 public string EvaluateFunction(IPreprocessContext context, string function)
53 {
54 var prefixParts = function.Split(VariableSplitter, 2);
55
56 // Check to make sure there are 2 parts and neither is an empty string.
57 if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length)
58 {
59 throw new WixException(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function));
60 }
61
62 var prefix = prefixParts[0];
63 var functionParts = prefixParts[1].Split(new char[] { '(' }, 2);
64
65 // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren.
66 if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal))
67 {
68 throw new WixException(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function));
69 }
70
71 var functionName = functionParts[0];
72
73 // Remove the trailing closing paren.
74 var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1);
75
76 // Parse the arguments and preprocess them.
77 var args = allArgs.Split(ArgumentSplitter);
78 for (var i = 0; i < args.Length; i++)
79 {
80 args[i] = this.PreprocessString(context, args[i].Trim());
81 }
82
83 var result = this.EvaluateFunction(context, prefix, functionName, args);
84
85 // If the function didn't evaluate, try to evaluate the original value as a variable to support
86 // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve.
87 if (result == null)
88 {
89 result = this.GetVariableValue(context, function, false);
90 }
91
92 return result;
93 }
94
95 public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args)
96 {
97 if (String.IsNullOrEmpty(prefix))
98 {
99 throw new ArgumentNullException("prefix");
100 }
101
102 if (String.IsNullOrEmpty(function))
103 {
104 throw new ArgumentNullException("function");
105 }
106
107 switch (prefix)
108 {
109 case "fun":
110 switch (function)
111 {
112 case "AutoVersion":
113 // Make sure the base version is specified
114 if (args.Length == 0 || String.IsNullOrEmpty(args[0]))
115 {
116 throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber));
117 }
118
119 // Build = days since 1/1/2000; Revision = seconds since midnight / 2
120 var now = DateTime.UtcNow;
121 var build = now - new DateTime(2000, 1, 1);
122 var revision = now - new DateTime(now.Year, now.Month, now.Day);
123
124 return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2));
125
126 default:
127 return null;
128 }
129
130 default:
131 var extensionsByPrefix = this.GetExtensionsByPrefix(context);
132 if (extensionsByPrefix.TryGetValue(prefix, out var extension))
133 {
134 try
135 {
136 return extension.EvaluateFunction(prefix, function, args);
137 }
138 catch (Exception e)
139 {
140 throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message));
141 }
142 }
143 else
144 {
145 return null;
146 }
147 }
148 }
149
150 public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix)
151 {
152 // Strip the "$(" off the front.
153 if (variable.StartsWith("$(", StringComparison.Ordinal))
154 {
155 variable = variable.Substring(2);
156 }
157
158 var parts = variable.Split(VariableSplitter, 2);
159
160 if (1 == parts.Length) // missing prefix
161 {
162 if (allowMissingPrefix)
163 {
164 return this.GetVariableValue(context, "var", parts[0]);
165 }
166 else
167 {
168 throw new WixException(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable));
169 }
170 }
171 else
172 {
173 // check for empty variable name
174 if (0 < parts[1].Length)
175 {
176 string result = this.GetVariableValue(context, parts[0], parts[1]);
177
178 // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix
179 if (null == result && allowMissingPrefix && variable.Contains("."))
180 {
181 result = this.GetVariableValue(context, "var", variable);
182 }
183
184 return result;
185 }
186 else
187 {
188 throw new WixException(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable));
189 }
190 }
191 }
192
193 public string GetVariableValue(IPreprocessContext context, string prefix, string name)
194 {
195 if (String.IsNullOrEmpty(prefix))
196 {
197 throw new ArgumentNullException("prefix");
198 }
199
200 if (String.IsNullOrEmpty(name))
201 {
202 throw new ArgumentNullException("name");
203 }
204
205 switch (prefix)
206 {
207 case "env":
208 return Environment.GetEnvironmentVariable(name);
209
210 case "sys":
211 switch (name)
212 {
213 case "CURRENTDIR":
214 return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar);
215
216 case "SOURCEFILEDIR":
217 return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar);
218
219 case "SOURCEFILEPATH":
220 return context.CurrentSourceLineNumber.FileName;
221
222 case "PLATFORM":
223 context.Messaging.OnMessage(WixWarnings.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)"));
224
225 goto case "BUILDARCH";
226
227 case "BUILDARCH":
228 switch (context.Platform)
229 {
230 case Platform.X86:
231 return "x86";
232
233 case Platform.X64:
234 return "x64";
235
236 case Platform.IA64:
237 return "ia64";
238
239 case Platform.ARM:
240 return "arm";
241
242 default:
243 throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, context.Platform.ToString());
244 }
245
246 default:
247 return null;
248 }
249
250 case "var":
251 return context.Variables.TryGetValue(name, out var result) ? result : null;
252
253 default:
254 var extensionsByPrefix = this.GetExtensionsByPrefix(context);
255 if (extensionsByPrefix.TryGetValue(prefix, out var extension))
256 {
257 try
258 {
259 return extension.GetVariableValue(prefix, name);
260 }
261 catch (Exception e)
262 {
263 throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message));
264 }
265 }
266 else
267 {
268 return null;
269 }
270 }
271 }
272
273 public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent)
274 {
275 var prefixParts = pragmaName.Split(VariableSplitter, 2);
276
277 // Check to make sure there are 2 parts and neither is an empty string.
278 if (2 != prefixParts.Length)
279 {
280 throw new WixException(WixErrors.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName));
281 }
282
283 var prefix = prefixParts[0];
284 var pragma = prefixParts[1];
285
286 if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma))
287 {
288 throw new WixException(WixErrors.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName));
289 }
290
291 switch (prefix)
292 {
293 case "wix":
294 switch (pragma)
295 {
296 // Add any core defined pragmas here
297 default:
298 context.Messaging.OnMessage(WixWarnings.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName));
299 break;
300 }
301 break;
302
303 default:
304 var extensionsByPrefix = this.GetExtensionsByPrefix(context);
305 if (extensionsByPrefix.TryGetValue(prefix, out var extension))
306 {
307 if (!extension.ProcessPragma(prefix, pragma, args, parent))
308 {
309 context.Messaging.OnMessage(WixWarnings.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName));
310 }
311 }
312 break;
313 }
314 }
315
316 public string PreprocessString(IPreprocessContext context, string value)
317 {
318 var sb = new StringBuilder();
319 var currentPosition = 0;
320 var end = 0;
321
322 while (-1 != (currentPosition = value.IndexOf('$', end)))
323 {
324 if (end < currentPosition)
325 {
326 sb.Append(value, end, currentPosition - end);
327 }
328
329 end = currentPosition + 1;
330
331 var remainder = value.Substring(end);
332 if (remainder.StartsWith("$", StringComparison.Ordinal))
333 {
334 sb.Append("$");
335 end++;
336 }
337 else if (remainder.StartsWith("(loc.", StringComparison.Ordinal))
338 {
339 currentPosition = remainder.IndexOf(')');
340 if (-1 == currentPosition)
341 {
342 context.Messaging.OnMessage(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder));
343 break;
344 }
345
346 sb.Append("$"); // just put the resource reference back as was
347 sb.Append(remainder, 0, currentPosition + 1);
348
349 end += currentPosition + 1;
350 }
351 else if (remainder.StartsWith("(", StringComparison.Ordinal))
352 {
353 var openParenCount = 1;
354 var closingParenCount = 0;
355 var isFunction = false;
356 var foundClosingParen = false;
357
358 // find the closing paren
359 int closingParenPosition;
360 for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++)
361 {
362 switch (remainder[closingParenPosition])
363 {
364 case '(':
365 openParenCount++;
366 isFunction = true;
367 break;
368
369 case ')':
370 closingParenCount++;
371 break;
372 }
373
374 if (openParenCount == closingParenCount)
375 {
376 foundClosingParen = true;
377 break;
378 }
379 }
380
381 // move the currentPosition to the closing paren
382 currentPosition += closingParenPosition;
383
384 if (!foundClosingParen)
385 {
386 if (isFunction)
387 {
388 context.Messaging.OnMessage(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder));
389 break;
390 }
391 else
392 {
393 context.Messaging.OnMessage(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder));
394 break;
395 }
396 }
397
398 var subString = remainder.Substring(1, closingParenPosition - 1);
399 string result = null;
400 if (isFunction)
401 {
402 result = this.EvaluateFunction(context, subString);
403 }
404 else
405 {
406 result = this.GetVariableValue(context, subString, false);
407 }
408
409 if (null == result)
410 {
411 if (isFunction)
412 {
413 context.Messaging.OnMessage(WixErrors.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString));
414 break;
415 }
416 else
417 {
418 context.Messaging.OnMessage(WixErrors.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString));
419 break;
420 }
421 }
422 else
423 {
424 if (!isFunction)
425 {
426 //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result));
427 }
428 }
429
430 sb.Append(result);
431 end += closingParenPosition + 1;
432 }
433 else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing
434 {
435 sb.Append('$');
436 }
437 }
438
439 if (end < value.Length)
440 {
441 sb.Append(value.Substring(end));
442 }
443
444 return sb.ToString();
445 }
446
447 public void RemoveVariable(IPreprocessContext context, string name)
448 {
449 if (!context.Variables.Remove(name))
450 {
451 context.Messaging.OnMessage(WixErrors.CannotReundefineVariable(context.CurrentSourceLineNumber, name));
452 }
453 }
454
455 private Dictionary<string, IPreprocessorExtension> GetExtensionsByPrefix(IPreprocessContext context)
456 {
457 if (this.ExtensionsByPrefix == null)
458 {
459 this.ExtensionsByPrefix = new Dictionary<string, IPreprocessorExtension>();
460
461 foreach (var extension in context.Extensions)
462 {
463 if (null != extension.Prefixes)
464 {
465 foreach (string prefix in extension.Prefixes)
466 {
467 if (!this.ExtensionsByPrefix.ContainsKey(prefix))
468 {
469 this.ExtensionsByPrefix.Add(prefix, extension);
470 }
471 }
472 }
473 }
474 }
475
476 return this.ExtensionsByPrefix;
477 }
478 }
479}
diff --git a/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs
new file mode 100644
index 00000000..086a0f1a
--- /dev/null
+++ b/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs
@@ -0,0 +1,19 @@
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.Core.Preprocess
4{
5 /// <summary>
6 /// Enumeration for preprocessor operations in if statements.
7 /// </summary>
8 internal enum PreprocessorOperation
9 {
10 /// <summary>The and operator.</summary>
11 And,
12
13 /// <summary>The or operator.</summary>
14 Or,
15
16 /// <summary>The not operator.</summary>
17 Not
18 }
19}
diff --git a/src/WixToolset.Core/PreprocessContext.cs b/src/WixToolset.Core/PreprocessContext.cs
new file mode 100644
index 00000000..c0acc31e
--- /dev/null
+++ b/src/WixToolset.Core/PreprocessContext.cs
@@ -0,0 +1,36 @@
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.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9
10 /// <summary>
11 /// The preprocessor core.
12 /// </summary>
13 internal class PreprocessContext : IPreprocessContext
14 {
15 internal PreprocessContext(IServiceProvider serviceProvider)
16 {
17 this.ServiceProvider = serviceProvider;
18 }
19
20 public IServiceProvider ServiceProvider { get; }
21
22 public Messaging Messaging { get; set; }
23
24 public IEnumerable<IPreprocessorExtension> Extensions { get; set; }
25
26 public Platform Platform { get; set; }
27
28 public IList<string> IncludeSearchPaths { get; set; }
29
30 public string SourceFile { get; set; }
31
32 public IDictionary<string, string> Variables { get; set; }
33
34 public SourceLineNumber CurrentSourceLineNumber { get; set; }
35 }
36}
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs
index 3aed0735..195ede9e 100644
--- a/src/WixToolset.Core/Preprocessor.cs
+++ b/src/WixToolset.Core/Preprocessor.cs
@@ -4,7 +4,6 @@ namespace WixToolset.Core
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization; 7 using System.Globalization;
9 using System.IO; 8 using System.IO;
10 using System.Text; 9 using System.Text;
@@ -14,6 +13,7 @@ namespace WixToolset.Core
14 using WixToolset.Data; 13 using WixToolset.Data;
15 using WixToolset.Extensibility; 14 using WixToolset.Extensibility;
16 using WixToolset.Core.Preprocess; 15 using WixToolset.Core.Preprocess;
16 using WixToolset.Extensibility.Services;
17 17
18 /// <summary> 18 /// <summary>
19 /// Preprocessor object 19 /// Preprocessor object
@@ -30,42 +30,22 @@ namespace WixToolset.Core
30 }; 30 };
31 private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() 31 private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings()
32 { 32 {
33 ConformanceLevel = System.Xml.ConformanceLevel.Fragment, 33 ConformanceLevel = ConformanceLevel.Fragment,
34 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, 34 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
35 XmlResolver = null, 35 XmlResolver = null,
36 }; 36 };
37 37
38 private List<IPreprocessorExtension> extensions; 38 private IPreprocessContext Context { get; set; }
39 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
40 39
41 private SourceLineNumber currentLineNumber; 40 private Stack<string> CurrentFileStack { get; } = new Stack<string>();
42 private Stack<SourceLineNumber> sourceStack;
43 41
44 private PreprocessorCore core; 42 private Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; } = new Dictionary<string, IPreprocessorExtension>();
45 private TextWriter preprocessOut;
46 43
47 private Stack<bool> includeNextStack; 44 private Stack<bool> IncludeNextStack { get; } = new Stack<bool>();
48 private Stack<string> currentFileStack;
49 45
50 private Platform currentPlatform; 46 private Stack<SourceLineNumber> SourceStack { get; } = new Stack<SourceLineNumber>();
51 47
52 /// <summary> 48 private IPreprocessHelper Helper { get; set; }
53 /// Creates a new preprocesor.
54 /// </summary>
55 public Preprocessor()
56 {
57 this.IncludeSearchPaths = new List<string>();
58
59 this.extensions = new List<IPreprocessorExtension>();
60 this.extensionsByPrefix = new Dictionary<string, IPreprocessorExtension>();
61
62 this.sourceStack = new Stack<SourceLineNumber>();
63
64 this.includeNextStack = new Stack<bool>();
65 this.currentFileStack = new Stack<string>();
66
67 this.currentPlatform = Platform.X86;
68 }
69 49
70 /// <summary> 50 /// <summary>
71 /// Event for ifdef/ifndef directives. 51 /// Event for ifdef/ifndef directives.
@@ -88,62 +68,6 @@ namespace WixToolset.Core
88 public event ResolvedVariableEventHandler ResolvedVariable; 68 public event ResolvedVariableEventHandler ResolvedVariable;
89 69
90 /// <summary> 70 /// <summary>
91 /// Enumeration for preprocessor operations in if statements.
92 /// </summary>
93 private enum PreprocessorOperation
94 {
95 /// <summary>The and operator.</summary>
96 And,
97
98 /// <summary>The or operator.</summary>
99 Or,
100
101 /// <summary>The not operator.</summary>
102 Not
103 }
104
105 /// <summary>
106 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
107 /// </summary>
108 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
109 public Platform CurrentPlatform
110 {
111 get { return this.currentPlatform; }
112 set { this.currentPlatform = value; }
113 }
114
115 /// <summary>
116 /// Ordered list of search paths that the precompiler uses to find included files.
117 /// </summary>
118 /// <value>List of ordered search paths to use during precompiling.</value>
119 public IList<string> IncludeSearchPaths { get; private set; }
120
121 /// <summary>
122 /// Specifies the text stream to display the postprocessed data to.
123 /// </summary>
124 /// <value>TextWriter to write preprocessed xml to.</value>
125 public TextWriter PreprocessOut
126 {
127 get { return this.preprocessOut; }
128 set { this.preprocessOut = value; }
129 }
130
131 /// <summary>
132 /// Get the source line information for the current element. The precompiler will insert
133 /// special source line number processing instructions before each element that it
134 /// encounters. This is where those line numbers are read and processed. This function
135 /// may return an array of source line numbers because the element may have come from
136 /// an included file, in which case the chain of imports is expressed in the array.
137 /// </summary>
138 /// <param name="node">Element to get source line information for.</param>
139 /// <returns>Returns the stack of imports used to author the element being processed.</returns>
140 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
141 public static SourceLineNumber GetSourceLineNumbers(XmlNode node)
142 {
143 return null;
144 }
145
146 /// <summary>
147 /// Get the source line information for the current element. The precompiler will insert 71 /// Get the source line information for the current element. The precompiler will insert
148 /// special source line number information for each element that it encounters. 72 /// special source line number information for each element that it encounters.
149 /// </summary> 73 /// </summary>
@@ -159,122 +83,94 @@ namespace WixToolset.Core
159 } 83 }
160 84
161 /// <summary> 85 /// <summary>
162 /// Adds an extension. 86 /// Preprocesses a file.
163 /// </summary> 87 /// </summary>
164 /// <param name="extension">The extension to add.</param> 88 /// <param name="context">The preprocessing context.</param>
165 public void AddExtension(IPreprocessorExtension extension) 89 /// <returns>XDocument with the postprocessed data.</returns>
90 public XDocument Process(IPreprocessContext context)
166 { 91 {
167 this.extensions.Add(extension); 92 this.Context = context ?? throw new ArgumentNullException(nameof(context));
168 93
169 if (null != extension.Prefixes) 94 using (XmlReader reader = XmlReader.Create(context.SourceFile, DocumentXmlReaderSettings))
170 { 95 {
171 foreach (string prefix in extension.Prefixes) 96 return Process(context, reader);
172 {
173 IPreprocessorExtension collidingExtension;
174 if (!this.extensionsByPrefix.TryGetValue(prefix, out collidingExtension))
175 {
176 this.extensionsByPrefix.Add(prefix, extension);
177 }
178 else
179 {
180 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString()));
181 }
182 }
183 } 97 }
184
185 //if (null != extension.InspectorExtension)
186 //{
187 // this.inspectorExtensions.Add(extension.InspectorExtension);
188 //}
189 } 98 }
190 99
191 /// <summary> 100 /// <summary>
192 /// Preprocesses a file. 101 /// Preprocesses a file.
193 /// </summary> 102 /// </summary>
194 /// <param name="sourceFile">The file to preprocess.</param> 103 /// <param name="context">The preprocessing context.</param>
195 /// <param name="variables">The variables defined prior to preprocessing.</param> 104 /// <param name="reader">XmlReader to processing the context.</param>
196 /// <returns>XDocument with the postprocessed data.</returns> 105 /// <returns>XDocument with the postprocessed data.</returns>
197 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] 106 public XDocument Process(IPreprocessContext context, XmlReader reader)
198 public XDocument Process(string sourceFile, IDictionary<string, string> variables)
199 { 107 {
200 using (Stream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read)) 108 if (this.Context == null)
201 using (XmlReader reader = XmlReader.Create(sourceFile, DocumentXmlReaderSettings))
202 { 109 {
203 return Process(reader, variables, sourceFile); 110 this.Context = context ?? throw new ArgumentNullException(nameof(context));
111 }
112 else if (this.Context != context)
113 {
114 throw new ArgumentException(nameof(context));
204 } 115 }
205 }
206 116
207 /// <summary> 117 if (String.IsNullOrEmpty(this.Context.SourceFile) && !String.IsNullOrEmpty(reader.BaseURI))
208 /// Preprocesses a file.
209 /// </summary>
210 /// <param name="sourceFile">The file to preprocess.</param>
211 /// <param name="variables">The variables defined prior to preprocessing.</param>
212 /// <returns>XDocument with the postprocessed data.</returns>
213 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
214 public XDocument Process(XmlReader reader, IDictionary<string, string> variables, string sourceFile = null)
215 {
216 if (String.IsNullOrEmpty(sourceFile) && !String.IsNullOrEmpty(reader.BaseURI))
217 { 118 {
218 Uri uri = new Uri(reader.BaseURI); 119 var uri = new Uri(reader.BaseURI);
219 sourceFile = uri.AbsolutePath; 120 this.Context.SourceFile = uri.AbsolutePath;
220 } 121 }
221 122
222 this.core = new PreprocessorCore(this.extensionsByPrefix, sourceFile, variables); 123 this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.SourceFile);
223 this.core.ResolvedVariableHandler = this.ResolvedVariable;
224 this.core.CurrentPlatform = this.currentPlatform;
225 this.currentLineNumber = new SourceLineNumber(sourceFile);
226 this.currentFileStack.Clear();
227 this.currentFileStack.Push(this.core.GetVariableValue(this.currentLineNumber, "sys", "SOURCEFILEDIR"));
228 124
229 // Process the reader into the output. 125 this.Helper = this.Context.ServiceProvider.GetService<IPreprocessHelper>();
230 XDocument output = new XDocument(); 126
231 try 127 foreach (var extension in this.Context.Extensions)
232 { 128 {
233 foreach (PreprocessorExtension extension in this.extensions) 129 if (null != extension.Prefixes)
234 { 130 {
235 extension.Core = this.core; 131 foreach (string prefix in extension.Prefixes)
236 extension.Initialize(); 132 {
133 if (!this.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension))
134 {
135 this.ExtensionsByPrefix.Add(prefix, extension);
136 }
137 else
138 {
139 this.Context.Messaging.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString()));
140 }
141 }
237 } 142 }
238 143
239 this.PreprocessReader(false, reader, output, 0); 144 extension.PrePreprocess(context);
240 }
241 catch (XmlException e)
242 {
243 this.UpdateCurrentLineNumber(reader, 0);
244 throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message));
245 } 145 }
246 146
247 // Fire event with post-processed document. 147 this.CurrentFileStack.Clear();
248 ProcessedStreamEventArgs args = new ProcessedStreamEventArgs(sourceFile, output); 148 this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR"));
249 this.OnProcessedStream(args);
250 149
251 // preprocess the generated XML Document 150 // Process the reader into the output.
252 foreach (PreprocessorExtension extension in this.extensions) 151 XDocument output = new XDocument();
152 try
253 { 153 {
254 extension.PreprocessDocument(output); 154 this.PreprocessReader(false, reader, output, 0);
255 }
256 155
257 // finalize the preprocessing 156 // Fire event with post-processed document.
258 foreach (PreprocessorExtension extension in this.extensions) 157 this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourceFile, output));
259 {
260 extension.Finish();
261 extension.Core = null;
262 } 158 }
263 159 catch (XmlException e)
264 if (this.core.EncounteredError)
265 { 160 {
266 return null; 161 this.UpdateCurrentLineNumber(reader, 0);
162 throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message));
267 } 163 }
268 else 164 finally
269 { 165 {
270 if (null != this.preprocessOut) 166 // Finalize the preprocessing.
167 foreach (var extension in this.Context.Extensions)
271 { 168 {
272 output.Save(this.preprocessOut); 169 extension.PostPreprocess(output);
273 this.preprocessOut.Flush();
274 } 170 }
275
276 return output;
277 } 171 }
172
173 return this.Context.Messaging.EncounteredError ? null : output;
278 } 174 }
279 175
280 /// <summary> 176 /// <summary>
@@ -340,42 +236,6 @@ namespace WixToolset.Core
340 } 236 }
341 237
342 /// <summary> 238 /// <summary>
343 /// Fires an event when an ifdef/ifndef directive is processed.
344 /// </summary>
345 /// <param name="ea">ifdef/ifndef event arguments.</param>
346 private void OnIfDef(IfDefEventArgs ea)
347 {
348 if (null != this.IfDef)
349 {
350 this.IfDef(this, ea);
351 }
352 }
353
354 /// <summary>
355 /// Fires an event when an included file is processed.
356 /// </summary>
357 /// <param name="ea">Included file event arguments.</param>
358 private void OnIncludedFile(IncludedFileEventArgs ea)
359 {
360 if (null != this.IncludedFile)
361 {
362 this.IncludedFile(this, ea);
363 }
364 }
365
366 /// <summary>
367 /// Fires an event after the file is preprocessed.
368 /// </summary>
369 /// <param name="ea">Included file event arguments.</param>
370 private void OnProcessedStream(ProcessedStreamEventArgs ea)
371 {
372 if (null != this.ProcessedStream)
373 {
374 this.ProcessedStream(this, ea);
375 }
376 }
377
378 /// <summary>
379 /// Tests expression to see if it starts with a keyword. 239 /// Tests expression to see if it starts with a keyword.
380 /// </summary> 240 /// </summary>
381 /// <param name="expression">Expression to test.</param> 241 /// <param name="expression">Expression to test.</param>
@@ -383,7 +243,7 @@ namespace WixToolset.Core
383 /// <returns>true if expression starts with a keyword.</returns> 243 /// <returns>true if expression starts with a keyword.</returns>
384 private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) 244 private static bool StartsWithKeyword(string expression, PreprocessorOperation operation)
385 { 245 {
386 expression = expression.ToUpper(CultureInfo.InvariantCulture); 246 expression = expression.ToUpperInvariant();
387 switch (operation) 247 switch (operation)
388 { 248 {
389 case PreprocessorOperation.Not: 249 case PreprocessorOperation.Not:
@@ -431,7 +291,7 @@ namespace WixToolset.Core
431 // update information here in case an error occurs before the next read 291 // update information here in case an error occurs before the next read
432 this.UpdateCurrentLineNumber(reader, offset); 292 this.UpdateCurrentLineNumber(reader, offset);
433 293
434 SourceLineNumber sourceLineNumbers = this.currentLineNumber; 294 var sourceLineNumbers = this.Context.CurrentSourceLineNumber;
435 295
436 // check for changes in conditional processing 296 // check for changes in conditional processing
437 if (XmlNodeType.ProcessingInstruction == reader.NodeType) 297 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
@@ -459,14 +319,14 @@ namespace WixToolset.Core
459 name = reader.Value.Trim(); 319 name = reader.Value.Trim();
460 if (ifContext.IsTrue) 320 if (ifContext.IsTrue)
461 { 321 {
462 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); 322 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.Helper.GetVariableValue(this.Context, name, true)), IfState.If);
463 } 323 }
464 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 324 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
465 { 325 {
466 ifContext = new IfContext(); 326 ifContext = new IfContext();
467 } 327 }
468 ignore = true; 328 ignore = true;
469 OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); 329 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
470 break; 330 break;
471 331
472 case "ifndef": 332 case "ifndef":
@@ -474,25 +334,25 @@ namespace WixToolset.Core
474 name = reader.Value.Trim(); 334 name = reader.Value.Trim();
475 if (ifContext.IsTrue) 335 if (ifContext.IsTrue)
476 { 336 {
477 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); 337 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.Helper.GetVariableValue(this.Context, name, true)), IfState.If);
478 } 338 }
479 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 339 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
480 { 340 {
481 ifContext = new IfContext(); 341 ifContext = new IfContext();
482 } 342 }
483 ignore = true; 343 ignore = true;
484 OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); 344 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
485 break; 345 break;
486 346
487 case "elseif": 347 case "elseif":
488 if (0 == ifStack.Count) 348 if (0 == ifStack.Count)
489 { 349 {
490 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif")); 350 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
491 } 351 }
492 352
493 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) 353 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
494 { 354 {
495 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif")); 355 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
496 } 356 }
497 357
498 ifContext.IfState = IfState.ElseIf; // we're now in an elseif 358 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
@@ -510,12 +370,12 @@ namespace WixToolset.Core
510 case "else": 370 case "else":
511 if (0 == ifStack.Count) 371 if (0 == ifStack.Count)
512 { 372 {
513 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else")); 373 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
514 } 374 }
515 375
516 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) 376 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
517 { 377 {
518 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else")); 378 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
519 } 379 }
520 380
521 ifContext.IfState = IfState.Else; // we're now in an else 381 ifContext.IfState = IfState.Else; // we're now in an else
@@ -526,7 +386,7 @@ namespace WixToolset.Core
526 case "endif": 386 case "endif":
527 if (0 == ifStack.Count) 387 if (0 == ifStack.Count)
528 { 388 {
529 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "endif")); 389 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif"));
530 } 390 }
531 391
532 ifContext = (IfContext)ifStack.Pop(); 392 ifContext = (IfContext)ifStack.Pop();
@@ -606,7 +466,7 @@ namespace WixToolset.Core
606 break; 466 break;
607 467
608 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error 468 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
609 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "foreach", "endforeach")); 469 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach"));
610 470
611 case "pragma": 471 case "pragma":
612 this.PreprocessPragma(reader.Value, currentContainer); 472 this.PreprocessPragma(reader.Value, currentContainer);
@@ -619,31 +479,33 @@ namespace WixToolset.Core
619 break; 479 break;
620 480
621 case XmlNodeType.Element: 481 case XmlNodeType.Element:
622 if (0 < this.includeNextStack.Count && this.includeNextStack.Peek()) 482 if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek())
623 { 483 {
624 if ("Include" != reader.LocalName) 484 if ("Include" != reader.LocalName)
625 { 485 {
626 this.core.OnMessage(WixErrors.InvalidDocumentElement(this.currentLineNumber, reader.Name, "include", "Include")); 486 this.Context.Messaging.OnMessage(WixErrors.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include"));
627 } 487 }
628 488
629 this.includeNextStack.Pop(); 489 this.IncludeNextStack.Pop();
630 this.includeNextStack.Push(false); 490 this.IncludeNextStack.Push(false);
631 break; 491 break;
632 } 492 }
633 493
634 bool empty = reader.IsEmptyElement; 494 var empty = reader.IsEmptyElement;
635 XNamespace ns = XNamespace.Get(reader.NamespaceURI); 495 var ns = XNamespace.Get(reader.NamespaceURI);
636 XElement element = new XElement(ns + reader.LocalName); 496 var element = new XElement(ns + reader.LocalName);
637 currentContainer.Add(element); 497 currentContainer.Add(element);
638 498
639 this.UpdateCurrentLineNumber(reader, offset); 499 this.UpdateCurrentLineNumber(reader, offset);
640 element.AddAnnotation(this.currentLineNumber); 500 element.AddAnnotation(sourceLineNumbers);
641 501
642 while (reader.MoveToNextAttribute()) 502 while (reader.MoveToNextAttribute())
643 { 503 {
644 string value = this.core.PreprocessString(this.currentLineNumber, reader.Value); 504 var value = this.Helper.PreprocessString(this.Context, reader.Value);
645 XNamespace attribNamespace = XNamespace.Get(reader.NamespaceURI); 505
506 var attribNamespace = XNamespace.Get(reader.NamespaceURI);
646 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; 507 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
508
647 element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); 509 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
648 } 510 }
649 511
@@ -662,12 +524,12 @@ namespace WixToolset.Core
662 break; 524 break;
663 525
664 case XmlNodeType.Text: 526 case XmlNodeType.Text:
665 string postprocessedText = this.core.PreprocessString(this.currentLineNumber, reader.Value); 527 string postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value);
666 currentContainer.Add(postprocessedText); 528 currentContainer.Add(postprocessedText);
667 break; 529 break;
668 530
669 case XmlNodeType.CDATA: 531 case XmlNodeType.CDATA:
670 string postprocessedValue = this.core.PreprocessString(this.currentLineNumber, reader.Value); 532 string postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value);
671 currentContainer.Add(new XCData(postprocessedValue)); 533 currentContainer.Add(new XCData(postprocessedValue));
672 break; 534 break;
673 535
@@ -678,13 +540,13 @@ namespace WixToolset.Core
678 540
679 if (0 != ifStack.Count) 541 if (0 != ifStack.Count)
680 { 542 {
681 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "if", "endif")); 543 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "if", "endif"));
682 } 544 }
683 545
684 // TODO: can this actually happen? 546 // TODO: can this actually happen?
685 if (0 != containerStack.Count) 547 if (0 != containerStack.Count)
686 { 548 {
687 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "nodes", "nodes")); 549 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "nodes", "nodes"));
688 } 550 }
689 } 551 }
690 552
@@ -694,12 +556,10 @@ namespace WixToolset.Core
694 /// <param name="errorMessage">Text from source.</param> 556 /// <param name="errorMessage">Text from source.</param>
695 private void PreprocessError(string errorMessage) 557 private void PreprocessError(string errorMessage)
696 { 558 {
697 SourceLineNumber sourceLineNumbers = this.currentLineNumber; 559 // Resolve other variables in the error message.
698 560 errorMessage = this.Helper.PreprocessString(this.Context, errorMessage);
699 // resolve other variables in the error message
700 errorMessage = this.core.PreprocessString(sourceLineNumbers, errorMessage);
701 561
702 throw new WixException(WixErrors.PreprocessorError(sourceLineNumbers, errorMessage)); 562 throw new WixException(WixErrors.PreprocessorError(this.Context.CurrentSourceLineNumber, errorMessage));
703 } 563 }
704 564
705 /// <summary> 565 /// <summary>
@@ -708,12 +568,10 @@ namespace WixToolset.Core
708 /// <param name="warningMessage">Text from source.</param> 568 /// <param name="warningMessage">Text from source.</param>
709 private void PreprocessWarning(string warningMessage) 569 private void PreprocessWarning(string warningMessage)
710 { 570 {
711 SourceLineNumber sourceLineNumbers = this.currentLineNumber; 571 // Resolve other variables in the warning message.
712 572 warningMessage = this.Helper.PreprocessString(this.Context, warningMessage);
713 // resolve other variables in the warning message
714 warningMessage = this.core.PreprocessString(sourceLineNumbers, warningMessage);
715 573
716 this.core.OnMessage(WixWarnings.PreprocessorWarning(sourceLineNumbers, warningMessage)); 574 this.Context.Messaging.OnMessage(WixWarnings.PreprocessorWarning(this.Context.CurrentSourceLineNumber, warningMessage));
717 } 575 }
718 576
719 /// <summary> 577 /// <summary>
@@ -722,16 +580,15 @@ namespace WixToolset.Core
722 /// <param name="originalDefine">Text from source.</param> 580 /// <param name="originalDefine">Text from source.</param>
723 private void PreprocessDefine(string originalDefine) 581 private void PreprocessDefine(string originalDefine)
724 { 582 {
725 Match match = defineRegex.Match(originalDefine); 583 var match = defineRegex.Match(originalDefine);
726 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
727 584
728 if (!match.Success) 585 if (!match.Success)
729 { 586 {
730 throw new WixException(WixErrors.IllegalDefineStatement(sourceLineNumbers, originalDefine)); 587 throw new WixException(WixErrors.IllegalDefineStatement(this.Context.CurrentSourceLineNumber, originalDefine));
731 } 588 }
732 589
733 string defineName = match.Groups["varName"].Value; 590 var defineName = match.Groups["varName"].Value;
734 string defineValue = match.Groups["varValue"].Value; 591 var defineValue = match.Groups["varValue"].Value;
735 592
736 // strip off the optional quotes 593 // strip off the optional quotes
737 if (1 < defineValue.Length && 594 if (1 < defineValue.Length &&
@@ -742,15 +599,15 @@ namespace WixToolset.Core
742 } 599 }
743 600
744 // resolve other variables in the variable value 601 // resolve other variables in the variable value
745 defineValue = this.core.PreprocessString(sourceLineNumbers, defineValue); 602 defineValue = this.Helper.PreprocessString(this.Context, defineValue);
746 603
747 if (defineName.StartsWith("var.", StringComparison.Ordinal)) 604 if (defineName.StartsWith("var.", StringComparison.Ordinal))
748 { 605 {
749 this.core.AddVariable(sourceLineNumbers, defineName.Substring(4), defineValue); 606 this.Helper.AddVariable(this.Context, defineName.Substring(4), defineValue);
750 } 607 }
751 else 608 else
752 { 609 {
753 this.core.AddVariable(sourceLineNumbers, defineName, defineValue); 610 this.Helper.AddVariable(this.Context, defineName, defineValue);
754 } 611 }
755 } 612 }
756 613
@@ -760,16 +617,15 @@ namespace WixToolset.Core
760 /// <param name="originalDefine">Text from source.</param> 617 /// <param name="originalDefine">Text from source.</param>
761 private void PreprocessUndef(string originalDefine) 618 private void PreprocessUndef(string originalDefine)
762 { 619 {
763 SourceLineNumber sourceLineNumbers = this.currentLineNumber; 620 var name = this.Helper.PreprocessString(this.Context, originalDefine.Trim());
764 string name = this.core.PreprocessString(sourceLineNumbers, originalDefine.Trim());
765 621
766 if (name.StartsWith("var.", StringComparison.Ordinal)) 622 if (name.StartsWith("var.", StringComparison.Ordinal))
767 { 623 {
768 this.core.RemoveVariable(sourceLineNumbers, name.Substring(4)); 624 this.Helper.RemoveVariable(this.Context, name.Substring(4));
769 } 625 }
770 else 626 else
771 { 627 {
772 this.core.RemoveVariable(sourceLineNumbers, name); 628 this.Helper.RemoveVariable(this.Context, name);
773 } 629 }
774 } 630 }
775 631
@@ -780,12 +636,12 @@ namespace WixToolset.Core
780 /// <param name="parent">Parent container for included content.</param> 636 /// <param name="parent">Parent container for included content.</param>
781 private void PreprocessInclude(string includePath, XContainer parent) 637 private void PreprocessInclude(string includePath, XContainer parent)
782 { 638 {
783 SourceLineNumber sourceLineNumbers = this.currentLineNumber; 639 var sourceLineNumbers = this.Context.CurrentSourceLineNumber;
784 640
785 // preprocess variables in the path 641 // Preprocess variables in the path.
786 includePath = this.core.PreprocessString(sourceLineNumbers, includePath); 642 includePath = this.Helper.PreprocessString(this.Context, includePath);
787 643
788 string includeFile = this.GetIncludeFile(includePath); 644 var includeFile = this.GetIncludeFile(includePath);
789 645
790 if (null == includeFile) 646 if (null == includeFile)
791 { 647 {
@@ -807,7 +663,7 @@ namespace WixToolset.Core
807 throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message)); 663 throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message));
808 } 664 }
809 665
810 this.OnIncludedFile(new IncludedFileEventArgs(sourceLineNumbers, includeFile)); 666 this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile));
811 667
812 this.PopInclude(); 668 this.PopInclude();
813 } 669 }
@@ -821,11 +677,11 @@ namespace WixToolset.Core
821 /// <param name="offset">Offset for the line numbers.</param> 677 /// <param name="offset">Offset for the line numbers.</param>
822 private void PreprocessForeach(XmlReader reader, XContainer container, int offset) 678 private void PreprocessForeach(XmlReader reader, XContainer container, int offset)
823 { 679 {
824 // find the "in" token 680 // Find the "in" token.
825 int indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); 681 var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
826 if (0 > indexOfInToken) 682 if (0 > indexOfInToken)
827 { 683 {
828 throw new WixException(WixErrors.IllegalForeach(this.currentLineNumber, reader.Value)); 684 throw new WixException(WixErrors.IllegalForeach(this.Context.CurrentSourceLineNumber, reader.Value));
829 } 685 }
830 686
831 // parse out the variable name 687 // parse out the variable name
@@ -833,7 +689,7 @@ namespace WixToolset.Core
833 string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); 689 string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
834 690
835 // preprocess the variable values string because it might be a variable itself 691 // preprocess the variable values string because it might be a variable itself
836 varValuesString = this.core.PreprocessString(this.currentLineNumber, varValuesString); 692 varValuesString = this.Helper.PreprocessString(this.Context, varValuesString);
837 693
838 string[] varValues = varValuesString.Split(';'); 694 string[] varValues = varValuesString.Split(';');
839 695
@@ -895,20 +751,20 @@ namespace WixToolset.Core
895 } 751 }
896 else if (reader.NodeType == XmlNodeType.None) 752 else if (reader.NodeType == XmlNodeType.None)
897 { 753 {
898 throw new WixException(WixErrors.ExpectedEndforeach(this.currentLineNumber)); 754 throw new WixException(WixErrors.ExpectedEndforeach(this.Context.CurrentSourceLineNumber));
899 } 755 }
900 756
901 reader.Read(); 757 reader.Read();
902 } 758 }
903 759
904 using (MemoryStream fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) 760 using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
905 using (XmlReader loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) 761 using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
906 { 762 {
907 // process each iteration, updating the variable's value each time 763 // process each iteration, updating the variable's value each time
908 foreach (string varValue in varValues) 764 foreach (string varValue in varValues)
909 { 765 {
910 // Always overwrite foreach variables. 766 // Always overwrite foreach variables.
911 this.core.AddVariable(this.currentLineNumber, varName, varValue, false); 767 this.Helper.AddVariable(this.Context, varName, varValue, false);
912 768
913 try 769 try
914 { 770 {
@@ -917,7 +773,7 @@ namespace WixToolset.Core
917 catch (XmlException e) 773 catch (XmlException e)
918 { 774 {
919 this.UpdateCurrentLineNumber(loopReader, offset); 775 this.UpdateCurrentLineNumber(loopReader, offset);
920 throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message)); 776 throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message));
921 } 777 }
922 778
923 fragmentStream.Position = 0; // seek back to the beginning for the next loop. 779 fragmentStream.Position = 0; // seek back to the beginning for the next loop.
@@ -931,24 +787,23 @@ namespace WixToolset.Core
931 /// <param name="pragmaText">Text from source.</param> 787 /// <param name="pragmaText">Text from source.</param>
932 private void PreprocessPragma(string pragmaText, XContainer parent) 788 private void PreprocessPragma(string pragmaText, XContainer parent)
933 { 789 {
934 Match match = pragmaRegex.Match(pragmaText); 790 var match = pragmaRegex.Match(pragmaText);
935 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
936 791
937 if (!match.Success) 792 if (!match.Success)
938 { 793 {
939 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaText)); 794 throw new WixException(WixErrors.InvalidPreprocessorPragma(this.Context.CurrentSourceLineNumber, pragmaText));
940 } 795 }
941 796
942 // resolve other variables in the pragma argument(s) 797 // resolve other variables in the pragma argument(s)
943 string pragmaArgs = this.core.PreprocessString(sourceLineNumbers, match.Groups["pragmaValue"].Value).Trim(); 798 string pragmaArgs = this.Helper.PreprocessString(this.Context, match.Groups["pragmaValue"].Value).Trim();
944 799
945 try 800 try
946 { 801 {
947 this.core.PreprocessPragma(sourceLineNumbers, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); 802 this.Helper.PreprocessPragma(this.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent);
948 } 803 }
949 catch (Exception e) 804 catch (Exception e)
950 { 805 {
951 throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(sourceLineNumbers, pragmaText, e.Message)); 806 throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(this.Context.CurrentSourceLineNumber, pragmaText, e.Message));
952 } 807 }
953 } 808 }
954 809
@@ -975,11 +830,11 @@ namespace WixToolset.Core
975 int endingQuotes = expression.IndexOf('\"', 1); 830 int endingQuotes = expression.IndexOf('\"', 1);
976 if (-1 == endingQuotes) 831 if (-1 == endingQuotes)
977 { 832 {
978 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); 833 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
979 } 834 }
980 835
981 // cut the quotes off the string 836 // cut the quotes off the string
982 token = this.core.PreprocessString(this.currentLineNumber, expression.Substring(1, endingQuotes - 1)); 837 token = this.Helper.PreprocessString(this.Context, expression.Substring(1, endingQuotes - 1));
983 838
984 // advance past this string 839 // advance past this string
985 expression = expression.Substring(endingQuotes + 1).Trim(); 840 expression = expression.Substring(endingQuotes + 1).Trim();
@@ -1009,7 +864,7 @@ namespace WixToolset.Core
1009 864
1010 if (-1 == endingParen) 865 if (-1 == endingParen)
1011 { 866 {
1012 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); 867 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1013 } 868 }
1014 token = expression.Substring(0, endingParen + 1); 869 token = expression.Substring(0, endingParen + 1);
1015 870
@@ -1115,7 +970,7 @@ namespace WixToolset.Core
1115 { 970 {
1116 try 971 try
1117 { 972 {
1118 varValue = this.core.PreprocessString(this.currentLineNumber, variable); 973 varValue = this.Helper.PreprocessString(this.Context, variable);
1119 } 974 }
1120 catch (ArgumentNullException) 975 catch (ArgumentNullException)
1121 { 976 {
@@ -1126,12 +981,12 @@ namespace WixToolset.Core
1126 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) 981 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1)
1127 { 982 {
1128 // make sure it doesn't contain parenthesis 983 // make sure it doesn't contain parenthesis
1129 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); 984 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1130 } 985 }
1131 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) 986 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1)
1132 { 987 {
1133 // shouldn't contain quotes 988 // shouldn't contain quotes
1134 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); 989 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1135 } 990 }
1136 991
1137 return varValue; 992 return varValue;
@@ -1162,7 +1017,7 @@ namespace WixToolset.Core
1162 { 1017 {
1163 if (stringLiteral) 1018 if (stringLiteral)
1164 { 1019 {
1165 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); 1020 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1166 } 1021 }
1167 1022
1168 rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral); 1023 rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
@@ -1213,7 +1068,7 @@ namespace WixToolset.Core
1213 { 1068 {
1214 if (operation.Length > 0) 1069 if (operation.Length > 0)
1215 { 1070 {
1216 throw new WixException(WixErrors.ExpectedVariable(this.currentLineNumber, originalExpression)); 1071 throw new WixException(WixErrors.ExpectedVariable(this.Context.CurrentSourceLineNumber, originalExpression));
1217 } 1072 }
1218 1073
1219 // false expression 1074 // false expression
@@ -1228,7 +1083,7 @@ namespace WixToolset.Core
1228 } 1083 }
1229 else 1084 else
1230 { 1085 {
1231 throw new WixException(WixErrors.UnexpectedLiteral(this.currentLineNumber, originalExpression)); 1086 throw new WixException(WixErrors.UnexpectedLiteral(this.Context.CurrentSourceLineNumber, originalExpression));
1232 } 1087 }
1233 } 1088 }
1234 else 1089 else
@@ -1268,11 +1123,11 @@ namespace WixToolset.Core
1268 } 1123 }
1269 catch (FormatException) 1124 catch (FormatException)
1270 { 1125 {
1271 throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression)); 1126 throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1272 } 1127 }
1273 catch (OverflowException) 1128 catch (OverflowException)
1274 { 1129 {
1275 throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression)); 1130 throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1276 } 1131 }
1277 1132
1278 // Compare the numbers 1133 // Compare the numbers
@@ -1314,7 +1169,7 @@ namespace WixToolset.Core
1314 closeParenIndex = expression.IndexOf(')', closeParenIndex); 1169 closeParenIndex = expression.IndexOf(')', closeParenIndex);
1315 if (closeParenIndex == -1) 1170 if (closeParenIndex == -1)
1316 { 1171 {
1317 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); 1172 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1318 } 1173 }
1319 1174
1320 if (InsideQuotes(expression, closeParenIndex)) 1175 if (InsideQuotes(expression, closeParenIndex))
@@ -1363,7 +1218,7 @@ namespace WixToolset.Core
1363 currentValue = !currentValue; 1218 currentValue = !currentValue;
1364 break; 1219 break;
1365 default: 1220 default:
1366 throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.currentLineNumber, operation.ToString())); 1221 throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString()));
1367 } 1222 }
1368 } 1223 }
1369 1224
@@ -1412,7 +1267,7 @@ namespace WixToolset.Core
1412 expression = expression.Trim(); 1267 expression = expression.Trim();
1413 if (expression.Length == 0) 1268 if (expression.Length == 0)
1414 { 1269 {
1415 throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression)); 1270 throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.Context.CurrentSourceLineNumber, originalExpression));
1416 } 1271 }
1417 1272
1418 // If the expression starts with parenthesis, evaluate it 1273 // If the expression starts with parenthesis, evaluate it
@@ -1433,7 +1288,7 @@ namespace WixToolset.Core
1433 expression = expression.Substring(3).Trim(); 1288 expression = expression.Substring(3).Trim();
1434 if (expression.Length == 0) 1289 if (expression.Length == 0)
1435 { 1290 {
1436 throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression)); 1291 throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.Context.CurrentSourceLineNumber, originalExpression));
1437 } 1292 }
1438 1293
1439 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true); 1294 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true);
@@ -1462,7 +1317,7 @@ namespace WixToolset.Core
1462 } 1317 }
1463 else 1318 else
1464 { 1319 {
1465 throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression)); 1320 throw new WixException(WixErrors.InvalidSubExpression(this.Context.CurrentSourceLineNumber, expression, originalExpression));
1466 } 1321 }
1467 } 1322 }
1468 1323
@@ -1481,9 +1336,9 @@ namespace WixToolset.Core
1481 { 1336 {
1482 int newLine = lineInfoReader.LineNumber + offset; 1337 int newLine = lineInfoReader.LineNumber + offset;
1483 1338
1484 if (this.currentLineNumber.LineNumber != newLine) 1339 if (this.Context.CurrentSourceLineNumber.LineNumber != newLine)
1485 { 1340 {
1486 this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine); 1341 this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.CurrentSourceLineNumber.FileName, newLine);
1487 } 1342 }
1488 } 1343 }
1489 } 1344 }
@@ -1494,15 +1349,15 @@ namespace WixToolset.Core
1494 /// <param name="fileName">Name to push on to the stack of included files.</param> 1349 /// <param name="fileName">Name to push on to the stack of included files.</param>
1495 private void PushInclude(string fileName) 1350 private void PushInclude(string fileName)
1496 { 1351 {
1497 if (1023 < this.currentFileStack.Count) 1352 if (1023 < this.CurrentFileStack.Count)
1498 { 1353 {
1499 throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count)); 1354 throw new WixException(WixErrors.TooDeeplyIncluded(this.Context.CurrentSourceLineNumber, this.CurrentFileStack.Count));
1500 } 1355 }
1501 1356
1502 this.currentFileStack.Push(fileName); 1357 this.CurrentFileStack.Push(fileName);
1503 this.sourceStack.Push(this.currentLineNumber); 1358 this.SourceStack.Push(this.Context.CurrentSourceLineNumber);
1504 this.currentLineNumber = new SourceLineNumber(fileName); 1359 this.Context.CurrentSourceLineNumber = new SourceLineNumber(fileName);
1505 this.includeNextStack.Push(true); 1360 this.IncludeNextStack.Push(true);
1506 } 1361 }
1507 1362
1508 /// <summary> 1363 /// <summary>
@@ -1510,10 +1365,10 @@ namespace WixToolset.Core
1510 /// </summary> 1365 /// </summary>
1511 private void PopInclude() 1366 private void PopInclude()
1512 { 1367 {
1513 this.currentLineNumber = this.sourceStack.Pop(); 1368 this.Context.CurrentSourceLineNumber = this.SourceStack.Pop();
1514 1369
1515 this.currentFileStack.Pop(); 1370 this.CurrentFileStack.Pop();
1516 this.includeNextStack.Pop(); 1371 this.IncludeNextStack.Pop();
1517 } 1372 }
1518 1373
1519 /// <summary> 1374 /// <summary>
@@ -1548,8 +1403,8 @@ namespace WixToolset.Core
1548 else // relative path 1403 else // relative path
1549 { 1404 {
1550 // build a string to test the directory containing the source file first 1405 // build a string to test the directory containing the source file first
1551 string currentFolder = this.currentFileStack.Peek(); 1406 var currentFolder = this.CurrentFileStack.Peek();
1552 string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath); 1407 var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) , includePath);
1553 1408
1554 // test the source file directory 1409 // test the source file directory
1555 if (File.Exists(includeTestPath)) 1410 if (File.Exists(includeTestPath))
@@ -1558,7 +1413,7 @@ namespace WixToolset.Core
1558 } 1413 }
1559 else // test all search paths in the order specified on the command line 1414 else // test all search paths in the order specified on the command line
1560 { 1415 {
1561 foreach (string includeSearchPath in this.IncludeSearchPaths) 1416 foreach (var includeSearchPath in this.Context.IncludeSearchPaths)
1562 { 1417 {
1563 // if the path exists, we have found the final string 1418 // if the path exists, we have found the final string
1564 includeTestPath = Path.Combine(includeSearchPath, includePath); 1419 includeTestPath = Path.Combine(includeSearchPath, includePath);
diff --git a/src/WixToolset.Core/PreprocessorCore.cs b/src/WixToolset.Core/PreprocessorCore.cs
deleted file mode 100644
index b58fc80c..00000000
--- a/src/WixToolset.Core/PreprocessorCore.cs
+++ /dev/null
@@ -1,560 +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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// The preprocessor core.
15 /// </summary>
16 internal class PreprocessorCore : IPreprocessorCore
17 {
18 private static readonly char[] variableSplitter = new char[] { '.' };
19 private static readonly char[] argumentSplitter = new char[] { ',' };
20
21 private Platform currentPlatform;
22 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
23 private string sourceFile;
24 private IDictionary<string, string> variables;
25
26 /// <summary>
27 /// Instantiate a new PreprocessorCore.
28 /// </summary>
29 /// <param name="extensionsByPrefix">The extensions indexed by their prefixes.</param>
30 /// <param name="messageHandler">The message handler.</param>
31 /// <param name="sourceFile">The source file being preprocessed.</param>
32 /// <param name="variables">The variables defined prior to preprocessing.</param>
33 internal PreprocessorCore(Dictionary<string, IPreprocessorExtension> extensionsByPrefix, string sourceFile, IDictionary<string, string> variables)
34 {
35 this.extensionsByPrefix = extensionsByPrefix;
36 this.sourceFile = String.IsNullOrEmpty(sourceFile) ? null : Path.GetFullPath(sourceFile);
37
38 this.variables = new Dictionary<string, string>();
39 foreach (var entry in variables)
40 {
41 this.AddVariable(null, entry.Key, entry.Value);
42 }
43 }
44
45 /// <summary>
46 /// Event for resolved variables.
47 /// </summary>
48 private event ResolvedVariableEventHandler ResolvedVariable;
49
50 /// <summary>
51 /// Sets event for ResolvedVariableEventHandler.
52 /// </summary>
53 public ResolvedVariableEventHandler ResolvedVariableHandler
54 {
55 set { this.ResolvedVariable = value; }
56 }
57
58 /// <summary>
59 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
60 /// </summary>
61 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
62 public Platform CurrentPlatform
63 {
64 get { return this.currentPlatform; }
65 set { this.currentPlatform = value; }
66 }
67
68 /// <summary>
69 /// Gets whether the core encountered an error while processing.
70 /// </summary>
71 /// <value>Flag if core encountered an error during processing.</value>
72 public bool EncounteredError
73 {
74 get { return Messaging.Instance.EncounteredError; }
75 }
76
77 /// <summary>
78 /// Replaces parameters in the source text.
79 /// </summary>
80 /// <param name="sourceLineNumbers">The source line information for the function.</param>
81 /// <param name="value">Text that may contain parameters to replace.</param>
82 /// <returns>Text after parameters have been replaced.</returns>
83 public string PreprocessString(SourceLineNumber sourceLineNumbers, string value)
84 {
85 StringBuilder sb = new StringBuilder();
86 int currentPosition = 0;
87 int end = 0;
88
89 while (-1 != (currentPosition = value.IndexOf('$', end)))
90 {
91 if (end < currentPosition)
92 {
93 sb.Append(value, end, currentPosition - end);
94 }
95
96 end = currentPosition + 1;
97 string remainder = value.Substring(end);
98 if (remainder.StartsWith("$", StringComparison.Ordinal))
99 {
100 sb.Append("$");
101 end++;
102 }
103 else if (remainder.StartsWith("(loc.", StringComparison.Ordinal))
104 {
105 currentPosition = remainder.IndexOf(')');
106 if (-1 == currentPosition)
107 {
108 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
109 break;
110 }
111
112 sb.Append("$"); // just put the resource reference back as was
113 sb.Append(remainder, 0, currentPosition + 1);
114
115 end += currentPosition + 1;
116 }
117 else if (remainder.StartsWith("(", StringComparison.Ordinal))
118 {
119 int openParenCount = 1;
120 int closingParenCount = 0;
121 bool isFunction = false;
122 bool foundClosingParen = false;
123
124 // find the closing paren
125 int closingParenPosition;
126 for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++)
127 {
128 switch (remainder[closingParenPosition])
129 {
130 case '(':
131 openParenCount++;
132 isFunction = true;
133 break;
134 case ')':
135 closingParenCount++;
136 break;
137 }
138 if (openParenCount == closingParenCount)
139 {
140 foundClosingParen = true;
141 break;
142 }
143 }
144
145 // move the currentPosition to the closing paren
146 currentPosition += closingParenPosition;
147
148 if (!foundClosingParen)
149 {
150 if (isFunction)
151 {
152 this.OnMessage(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, remainder));
153 break;
154 }
155 else
156 {
157 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
158 break;
159 }
160 }
161
162 string subString = remainder.Substring(1, closingParenPosition - 1);
163 string result = null;
164 if (isFunction)
165 {
166 result = this.EvaluateFunction(sourceLineNumbers, subString);
167 }
168 else
169 {
170 result = this.GetVariableValue(sourceLineNumbers, subString, false);
171 }
172
173 if (null == result)
174 {
175 if (isFunction)
176 {
177 this.OnMessage(WixErrors.UndefinedPreprocessorFunction(sourceLineNumbers, subString));
178 break;
179 }
180 else
181 {
182 this.OnMessage(WixErrors.UndefinedPreprocessorVariable(sourceLineNumbers, subString));
183 break;
184 }
185 }
186 else
187 {
188 if (!isFunction)
189 {
190 this.OnResolvedVariable(new ResolvedVariableEventArgs(sourceLineNumbers, subString, result));
191 }
192 }
193 sb.Append(result);
194 end += closingParenPosition + 1;
195 }
196 else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing
197 {
198 sb.Append('$');
199 }
200 }
201
202 if (end < value.Length)
203 {
204 sb.Append(value.Substring(end));
205 }
206
207 return sb.ToString();
208 }
209
210 /// <summary>
211 /// Evaluate a Pragma.
212 /// </summary>
213 /// <param name="sourceLineNumbers">The source line information for the function.</param>
214 /// <param name="pragmaName">The pragma's full name (<prefix>.<pragma>).</param>
215 /// <param name="args">The arguments to the pragma.</param>
216 /// <param name="parent">The parent element of the pragma.</param>
217 public void PreprocessPragma(SourceLineNumber sourceLineNumbers, string pragmaName, string args, XContainer parent)
218 {
219 string[] prefixParts = pragmaName.Split(variableSplitter, 2);
220 // Check to make sure there are 2 parts and neither is an empty string.
221 if (2 != prefixParts.Length)
222 {
223 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
224 }
225 string prefix = prefixParts[0];
226 string pragma = prefixParts[1];
227
228 if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma))
229 {
230 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
231 }
232
233 switch (prefix)
234 {
235 case "wix":
236 switch (pragma)
237 {
238 // Add any core defined pragmas here
239 default:
240 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
241 break;
242 }
243 break;
244 default:
245 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
246 if (null == extension || !extension.ProcessPragma(sourceLineNumbers, prefix, pragma, args, parent))
247 {
248 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
249 }
250 break;
251 }
252 }
253
254 /// <summary>
255 /// Evaluate a function.
256 /// </summary>
257 /// <param name="sourceLineNumbers">The source line information for the function.</param>
258 /// <param name="function">The function expression including the prefix and name.</param>
259 /// <returns>The function value.</returns>
260 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string function)
261 {
262 string[] prefixParts = function.Split(variableSplitter, 2);
263 // Check to make sure there are 2 parts and neither is an empty string.
264 if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length)
265 {
266 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
267 }
268 string prefix = prefixParts[0];
269
270 string[] functionParts = prefixParts[1].Split(new char[] { '(' }, 2);
271 // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren.
272 if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal))
273 {
274 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
275 }
276 string functionName = functionParts[0];
277
278 // Remove the trailing closing paren.
279 string allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1);
280
281 // Parse the arguments and preprocess them.
282 string[] args = allArgs.Split(argumentSplitter);
283 for (int i = 0; i < args.Length; i++)
284 {
285 args[i] = this.PreprocessString(sourceLineNumbers, args[i].Trim());
286 }
287
288 string result = this.EvaluateFunction(sourceLineNumbers, prefix, functionName, args);
289
290 // If the function didn't evaluate, try to evaluate the original value as a variable to support
291 // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve.
292 if (null == result)
293 {
294 result = this.GetVariableValue(sourceLineNumbers, function, false);
295 }
296
297 return result;
298 }
299
300 /// <summary>
301 /// Evaluate a function.
302 /// </summary>
303 /// <param name="sourceLineNumbers">The source line information for the function.</param>
304 /// <param name="prefix">The function prefix.</param>
305 /// <param name="function">The function name.</param>
306 /// <param name="args">The arguments for the function.</param>
307 /// <returns>The function value or null if the function is not defined.</returns>
308 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string prefix, string function, string[] args)
309 {
310 if (String.IsNullOrEmpty(prefix))
311 {
312 throw new ArgumentNullException("prefix");
313 }
314
315 if (String.IsNullOrEmpty(function))
316 {
317 throw new ArgumentNullException("function");
318 }
319
320 switch (prefix)
321 {
322 case "fun":
323 switch (function)
324 {
325 case "AutoVersion":
326 // Make sure the base version is specified
327 if (args.Length == 0 || String.IsNullOrEmpty(args[0]))
328 {
329 throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(sourceLineNumbers));
330 }
331
332 // Build = days since 1/1/2000; Revision = seconds since midnight / 2
333 DateTime now = DateTime.UtcNow;
334 TimeSpan build = now - new DateTime(2000, 1, 1);
335 TimeSpan revision = now - new DateTime(now.Year, now.Month, now.Day);
336
337 return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2));
338
339 default:
340 return null;
341 }
342 default:
343 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
344 if (null != extension)
345 {
346 try
347 {
348 return extension.EvaluateFunction(prefix, function, args);
349 }
350 catch (Exception e)
351 {
352 throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(sourceLineNumbers, prefix, function, String.Join(",", args), e.Message));
353 }
354 }
355 else
356 {
357 return null;
358 }
359 }
360 }
361
362 /// <summary>
363 /// Get the value of a variable expression like var.name.
364 /// </summary>
365 /// <param name="sourceLineNumbers">The source line information for the variable.</param>
366 /// <param name="variable">The variable expression including the optional prefix and name.</param>
367 /// <param name="allowMissingPrefix">true to allow the variable prefix to be missing.</param>
368 /// <returns>The variable value.</returns>
369 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string variable, bool allowMissingPrefix)
370 {
371 // Strip the "$(" off the front.
372 if (variable.StartsWith("$(", StringComparison.Ordinal))
373 {
374 variable = variable.Substring(2);
375 }
376
377 string[] parts = variable.Split(variableSplitter, 2);
378
379 if (1 == parts.Length) // missing prefix
380 {
381 if (allowMissingPrefix)
382 {
383 return this.GetVariableValue(sourceLineNumbers, "var", parts[0]);
384 }
385 else
386 {
387 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
388 }
389 }
390 else
391 {
392 // check for empty variable name
393 if (0 < parts[1].Length)
394 {
395 string result = this.GetVariableValue(sourceLineNumbers, parts[0], parts[1]);
396
397 // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix
398 if (null == result && allowMissingPrefix && variable.Contains("."))
399 {
400 result = this.GetVariableValue(sourceLineNumbers, "var", variable);
401 }
402
403 return result;
404 }
405 else
406 {
407 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
408 }
409 }
410 }
411
412 /// <summary>
413 /// Get the value of a variable.
414 /// </summary>
415 /// <param name="sourceLineNumbers">The source line information for the function.</param>
416 /// <param name="prefix">The variable prefix.</param>
417 /// <param name="name">The variable name.</param>
418 /// <returns>The variable value or null if the variable is not set.</returns>
419 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string prefix, string name)
420 {
421 if (String.IsNullOrEmpty(prefix))
422 {
423 throw new ArgumentNullException("prefix");
424 }
425
426 if (String.IsNullOrEmpty(name))
427 {
428 throw new ArgumentNullException("name");
429 }
430
431 switch (prefix)
432 {
433 case "env":
434 return Environment.GetEnvironmentVariable(name);
435 case "sys":
436 switch (name)
437 {
438 case "CURRENTDIR":
439 return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar);
440 case "SOURCEFILEDIR":
441 return String.Concat(Path.GetDirectoryName(sourceLineNumbers.FileName), Path.DirectorySeparatorChar);
442 case "SOURCEFILEPATH":
443 return sourceLineNumbers.FileName;
444 case "PLATFORM":
445 this.OnMessage(WixWarnings.DeprecatedPreProcVariable(sourceLineNumbers, "$(sys.PLATFORM)", "$(sys.BUILDARCH)"));
446
447 goto case "BUILDARCH";
448
449 case "BUILDARCH":
450 switch (this.currentPlatform)
451 {
452 case Platform.X86:
453 return "x86";
454 case Platform.X64:
455 return "x64";
456 case Platform.IA64:
457 return "ia64";
458 case Platform.ARM:
459 return "arm";
460 default:
461 throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.currentPlatform.ToString());
462 }
463 default:
464 return null;
465 }
466 case "var":
467 string result = null;
468 return this.variables.TryGetValue(name, out result) ? result : null;
469 default:
470 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
471 if (null != extension)
472 {
473 try
474 {
475 return extension.GetVariableValue(prefix, name);
476 }
477 catch (Exception e)
478 {
479 throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(sourceLineNumbers, prefix, name, e.Message));
480 }
481 }
482 else
483 {
484 return null;
485 }
486 }
487 }
488
489 /// <summary>
490 /// Sends a message to the message delegate if there is one.
491 /// </summary>
492 /// <param name="mea">Message event arguments.</param>
493 public void OnMessage(MessageEventArgs e)
494 {
495 Messaging.Instance.OnMessage(e);
496 }
497
498 /// <summary>
499 /// Sends resolved variable to delegate if there is one.
500 /// </summary>
501 /// <param name="mea">Message event arguments.</param>
502 public void OnResolvedVariable(ResolvedVariableEventArgs mea)
503 {
504 if (null != this.ResolvedVariable)
505 {
506 this.ResolvedVariable(this, mea);
507 }
508 }
509
510 /// <summary>
511 /// Add a variable.
512 /// </summary>
513 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
514 /// <param name="name">The variable name.</param>
515 /// <param name="value">The variable value.</param>
516 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value)
517 {
518 this.AddVariable(sourceLineNumbers, name, value, true);
519 }
520
521 /// <summary>
522 /// Add a variable.
523 /// </summary>
524 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
525 /// <param name="name">The variable name.</param>
526 /// <param name="value">The variable value.</param>
527 /// <param name="overwrite">Set to true to show variable overwrite warning.</param>
528 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value, bool showWarning)
529 {
530 string currentValue = this.GetVariableValue(sourceLineNumbers, "var", name);
531
532 if (null == currentValue)
533 {
534 this.variables.Add(name, value);
535 }
536 else
537 {
538 if (showWarning)
539 {
540 this.OnMessage(WixWarnings.VariableDeclarationCollision(sourceLineNumbers, name, value, currentValue));
541 }
542
543 this.variables[name] = value;
544 }
545 }
546
547 /// <summary>
548 /// Remove a variable.
549 /// </summary>
550 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
551 /// <param name="name">The variable name.</param>
552 internal void RemoveVariable(SourceLineNumber sourceLineNumbers, string name)
553 {
554 if (!this.variables.Remove(name))
555 {
556 this.OnMessage(WixErrors.CannotReundefineVariable(sourceLineNumbers, name));
557 }
558 }
559 }
560}
diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs
index 8693461b..d31d7355 100644
--- a/src/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -12,6 +12,7 @@ namespace WixToolset.Core
12 { 12 {
13 private ExtensionManager extensionManager; 13 private ExtensionManager extensionManager;
14 private ParseHelper parseHelper; 14 private ParseHelper parseHelper;
15 private PreprocessHelper preprocessHelper;
15 private TupleDefinitionCreator tupleDefinitionCreator; 16 private TupleDefinitionCreator tupleDefinitionCreator;
16 17
17 public object GetService(Type serviceType) 18 public object GetService(Type serviceType)
@@ -19,6 +20,11 @@ namespace WixToolset.Core
19 if (serviceType == null) throw new ArgumentNullException(nameof(serviceType)); 20 if (serviceType == null) throw new ArgumentNullException(nameof(serviceType));
20 21
21 // Transients. 22 // Transients.
23 if (serviceType == typeof(IPreprocessContext))
24 {
25 return new PreprocessContext(this);
26 }
27
22 if (serviceType == typeof(ICompileContext)) 28 if (serviceType == typeof(ICompileContext))
23 { 29 {
24 return new CompileContext(this); 30 return new CompileContext(this);
@@ -65,6 +71,11 @@ namespace WixToolset.Core
65 return this.parseHelper = this.parseHelper ?? new ParseHelper(this); 71 return this.parseHelper = this.parseHelper ?? new ParseHelper(this);
66 } 72 }
67 73
74 if (serviceType == typeof(IPreprocessHelper))
75 {
76 return this.preprocessHelper = this.preprocessHelper ?? new PreprocessHelper(this);
77 }
78
68 throw new ArgumentException($"Unknown service type: {serviceType.Name}", nameof(serviceType)); 79 throw new ArgumentException($"Unknown service type: {serviceType.Name}", nameof(serviceType));
69 } 80 }
70 } 81 }
diff --git a/src/test/Example.Extension/ExampleExtensionFactory.cs b/src/test/Example.Extension/ExampleExtensionFactory.cs
index 9539ee85..b91d06e9 100644
--- a/src/test/Example.Extension/ExampleExtensionFactory.cs
+++ b/src/test/Example.Extension/ExampleExtensionFactory.cs
@@ -7,11 +7,18 @@ namespace Example.Extension
7 7
8 public class ExampleExtensionFactory : IExtensionFactory 8 public class ExampleExtensionFactory : IExtensionFactory
9 { 9 {
10 private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension;
11
10 public bool TryCreateExtension(Type extensionType, out object extension) 12 public bool TryCreateExtension(Type extensionType, out object extension)
11 { 13 {
12 if (extensionType == typeof(IPreprocessorExtension)) 14 if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension))
13 { 15 {
14 extension = new ExamplePreprocessorExtension(); 16 if (preprocessorExtension == null)
17 {
18 preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine();
19 }
20
21 extension = preprocessorExtension;
15 } 22 }
16 else if (extensionType == typeof(ICompilerExtension)) 23 else if (extensionType == typeof(ICompilerExtension))
17 { 24 {
diff --git a/src/test/Example.Extension/ExamplePreprocessorExtension.cs b/src/test/Example.Extension/ExamplePreprocessorExtension.cs
deleted file mode 100644
index c16c8b5a..00000000
--- a/src/test/Example.Extension/ExamplePreprocessorExtension.cs
+++ /dev/null
@@ -1,55 +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 Example.Extension
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9
10 internal class ExamplePreprocessorExtension : IPreprocessorExtension
11 {
12 public ExamplePreprocessorExtension()
13 {
14 }
15
16 public IPreprocessorCore Core { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
17
18 public string[] Prefixes => throw new NotImplementedException();
19
20 public string EvaluateFunction(string prefix, string function, string[] args)
21 {
22 throw new NotImplementedException();
23 }
24
25 public void Finish()
26 {
27 throw new NotImplementedException();
28 }
29
30 public string GetVariableValue(string prefix, string name)
31 {
32 throw new NotImplementedException();
33 }
34
35 public void Initialize()
36 {
37 throw new NotImplementedException();
38 }
39
40 public void PreprocessDocument(XDocument document)
41 {
42 throw new NotImplementedException();
43 }
44
45 public string PreprocessParameter(string name)
46 {
47 throw new NotImplementedException();
48 }
49
50 public bool ProcessPragma(SourceLineNumber sourceLineNumbers, string prefix, string pragma, string args, XContainer parent)
51 {
52 throw new NotImplementedException();
53 }
54 }
55} \ No newline at end of file
diff --git a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs
new file mode 100644
index 00000000..53394ea3
--- /dev/null
+++ b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs
@@ -0,0 +1,46 @@
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 Example.Extension
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Services;
9
10 internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine
11 {
12 private string exampleValueFromCommandLine;
13
14 public IEnumerable<ExtensionCommandLineSwitch> CommandLineSwitches => throw new NotImplementedException();
15
16 public ExamplePreprocessorExtensionAndCommandLine()
17 {
18 this.Prefixes = new[] { "ex" };
19 }
20
21 public void PreParse(ICommandLineContext context)
22 {
23 }
24
25 public bool TryParseArgument(IParseCommandLine parseCommandLine, string arg)
26 {
27 if (parseCommandLine.IsSwitch(arg) && arg.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase))
28 {
29 parseCommandLine.GetNextArgumentOrError(ref this.exampleValueFromCommandLine);
30 return true;
31 }
32
33 return false;
34 }
35
36 public override string GetVariableValue(string prefix, string name)
37 {
38 if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase))
39 {
40 return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine;
41 }
42
43 return null;
44 }
45 }
46} \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
index 5181c748..6acf3472 100644
--- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
@@ -56,5 +56,48 @@ namespace WixToolsetTest.CoreIntegration
56 Assert.Equal("Bar", example[1].AsString()); 56 Assert.Equal("Bar", example[1].AsString());
57 } 57 }
58 } 58 }
59
60 [Fact]
61 public void CanParseCommandLineWithExtension()
62 {
63 var folder = TestData.Get(@"TestData\ExampleExtension");
64 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
65
66 using (var fs = new DisposableFileSystem())
67 {
68 var intermediateFolder = fs.GetFolder();
69
70 var program = new Program();
71 var result = program.Run(new WixToolsetServiceProvider(), new[]
72 {
73 "build",
74 Path.Combine(folder, "Package.wxs"),
75 Path.Combine(folder, "PackageComponents.wxs"),
76 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
77 "-ext", extensionPath,
78 "-bindpath", Path.Combine(folder, "data"),
79 "-intermediateFolder", intermediateFolder,
80 "-example", "test",
81 "-o", Path.Combine(intermediateFolder, @"bin\extest.msi")
82 });
83
84 Assert.Equal(0, result);
85
86 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi")));
87 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")));
88 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt")));
89
90 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir"));
91 var section = intermediate.Sections.Single();
92
93 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
94 Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path);
95 Assert.Equal(@"example.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path);
96
97 var property = section.Tuples.OfType<PropertyTuple>().Where(p => p.Id.Id == "ExampleProperty").Single();
98 Assert.Equal("ExampleProperty", property.Property);
99 Assert.Equal("test", property.Value);
100 }
101 }
59 } 102 }
60} 103}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs
index cdc323ec..9fd42214 100644
--- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs
@@ -6,6 +6,8 @@
6 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> 6 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
7 <MediaTemplate /> 7 <MediaTemplate />
8 8
9 <Property Id="ExampleProperty" Value="$(ex.Test)" />
10
9 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> 11 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
10 <ComponentGroupRef Id="ProductComponents" /> 12 <ComponentGroupRef Id="ProductComponents" />
11 </Feature> 13 </Feature>