@joebigelow / wix-1 / commits / 95f2f442

Implement support for IExtensionCommandLine and IPreprocessorExtension

Rob Mensching committed Dec 2, 2017 at 00:46 UTC 95f2f4425b900374c7d7b583ae810b096121b3c4
15 files changed +842 -937
src/WixToolset.Core/CommandLine/BuildCommand.cs
+10 -5
@@ -40,6 +40,8 @@ namespace WixToolset.Core
40
41 public IExtensionManager ExtensionManager { get; }
42
43 + public IEnumerable<string> IncludeSearchPaths { get; }
44 +
45 public IEnumerable<string> LocFiles { get; }
46
47 public IEnumerable<string> LibraryFiles { get; }
@@ -102,15 +104,18 @@ namespace WixToolset.Core
104 {
105 var intermediates = new List<Intermediate>();
106
105 -
107 foreach (var sourceFile in this.SourceFiles)
108 {
108 - //var preprocessContext = this.ServiceProvider.GetService<IPreprocessContext>();
109 - //preprocessContext.SourcePath = sourceFile.SourcePath;
110 - //preprocessContext.Variables = this.PreprocessorVariables;
109 + var preprocessContext = this.ServiceProvider.GetService<IPreprocessContext>();
110 + preprocessContext.Messaging = Messaging.Instance;
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);
116
117 var preprocessor = new Preprocessor();
113 - var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables);
118 + var document = preprocessor.Process(preprocessContext);
119
120 var compileContext = this.ServiceProvider.GetService<ICompileContext>();
121 compileContext.Messaging = Messaging.Instance;
src/WixToolset.Core/CommandLine/CommandLine.cs
+17 -12
@@ -22,7 +22,7 @@ namespace WixToolset.Core
22 Bind,
23 }
24
25 - internal class CommandLine : ICommandLine
25 + internal class CommandLine : ICommandLine, IParseCommandLine
26 {
27 public CommandLine()
28 {
@@ -57,10 +57,10 @@ namespace WixToolset.Core
57 args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray();
58 }
59
60 - return this.ParseStandardCommandLine(args);
60 + return this.ParseStandardCommandLine(context, args);
61 }
62
63 - private ICommandLineCommand ParseStandardCommandLine(string[] args)
63 + private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args)
64 {
65 var next = String.Empty;
66
@@ -90,7 +90,7 @@ namespace WixToolset.Core
90 var builtOutputsFile = String.Empty;
91 var wixProjectFile = String.Empty;
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 {
95 if (cmdline.IsSwitch(arg))
96 {
@@ -279,13 +279,13 @@ namespace WixToolset.Core
279 }
280 #endif
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 {
284 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
285
286 this.QueueArgumentsAndLoadExtensions(this.OriginalArguments);
287
288 - this.ProcessRemainingArguments(parseArgument, parseCommand);
288 + this.ProcessRemainingArguments(context, parseArgument, parseCommand);
289
290 return this;
291 }
@@ -413,7 +413,7 @@ namespace WixToolset.Core
413 /// <returns>True if a valid switch exists there, false if not.</returns>
414 public bool IsSwitch(string arg)
415 {
416 - return arg != null && ('/' == arg[0] || '-' == arg[0]);
416 + return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]);
417 }
418
419 /// <summary>
@@ -522,10 +522,15 @@ namespace WixToolset.Core
522 }
523 }
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 {
527 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
528
529 + foreach (var extension in extensions)
530 + {
531 + extension.PreParse(context);
532 + }
533 +
534 while (!this.ShowHelp &&
535 String.IsNullOrEmpty(this.ErrorArgument) &&
536 TryDequeue(this.RemainingArguments, out var arg))
@@ -566,10 +571,10 @@ namespace WixToolset.Core
571 {
572 foreach (var extension in extensions)
573 {
569 - //if (extension.ParseArgument(this, arg))
570 - //{
571 - // return true;
572 - //}
574 + if (extension.TryParseArgument(this, arg))
575 + {
576 + return true;
577 + }
578 }
579
580 return false;
src/WixToolset.Core/CommandLine/CompileCommand.cs
+12 -1
@@ -4,6 +4,7 @@ namespace WixToolset.Core
4 {
5 using System;
6 using System.Collections.Generic;
7 + using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Services;
@@ -22,6 +23,8 @@ namespace WixToolset.Core
23
24 private IExtensionManager ExtensionManager { get; }
25
26 + public IEnumerable<string> IncludeSearchPaths { get; }
27 +
28 private IEnumerable<SourceFile> SourceFiles { get; }
29
30 private IDictionary<string, string> PreprocessorVariables { get; }
@@ -30,8 +33,16 @@ namespace WixToolset.Core
33 {
34 foreach (var sourceFile in this.SourceFiles)
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 +
44 var preprocessor = new Preprocessor();
34 - var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables);
45 + var document = preprocessor.Process(preprocessContext);
46
47 var compileContext = this.ServiceProvider.GetService<ICompileContext>();
48 compileContext.Messaging = Messaging.Instance;
src/WixToolset.Core/Compiler.cs
+4 -3
@@ -10825,9 +10825,10 @@ namespace WixToolset.Core
10825 switch (installScopeType)
10826 {
10827 case Wix.Package.InstallScopeType.perMachine:
10828 - row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property);
10829 - row.Set(0, "ALLUSERS");
10830 - row.Set(1, "1");
10828 + {
10829 + row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property, new Identifier("ALLUSERS", AccessModifier.Public));
10830 + row.Set(1, "1");
10831 + }
10832 break;
10833 case Wix.Package.InstallScopeType.perUser:
10834 sourceBits = sourceBits | 8;
src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs new
+479
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Preprocess/PreprocessorOperation.cs new
+19
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/PreprocessContext.cs new
+36
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Preprocessor.cs
+154 -299
@@ -4,7 +4,6 @@ namespace WixToolset.Core
4 {
5 using System;
6 using System.Collections.Generic;
7 - using System.Diagnostics.CodeAnalysis;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
@@ -14,6 +13,7 @@ namespace WixToolset.Core
13 using WixToolset.Data;
14 using WixToolset.Extensibility;
15 using WixToolset.Core.Preprocess;
16 + using WixToolset.Extensibility.Services;
17
18 /// <summary>
19 /// Preprocessor object
@@ -30,42 +30,22 @@ namespace WixToolset.Core
30 };
31 private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings()
32 {
33 - ConformanceLevel = System.Xml.ConformanceLevel.Fragment,
33 + ConformanceLevel = ConformanceLevel.Fragment,
34 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
35 XmlResolver = null,
36 };
37
38 - private List<IPreprocessorExtension> extensions;
39 - private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
38 + private IPreprocessContext Context { get; set; }
39
41 - private SourceLineNumber currentLineNumber;
42 - private Stack<SourceLineNumber> sourceStack;
40 + private Stack<string> CurrentFileStack { get; } = new Stack<string>();
41
44 - private PreprocessorCore core;
45 - private TextWriter preprocessOut;
42 + private Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; } = new Dictionary<string, IPreprocessorExtension>();
43
47 - private Stack<bool> includeNextStack;
48 - private Stack<string> currentFileStack;
44 + private Stack<bool> IncludeNextStack { get; } = new Stack<bool>();
45
50 - private Platform currentPlatform;
46 + private Stack<SourceLineNumber> SourceStack { get; } = new Stack<SourceLineNumber>();
47
52 - /// <summary>
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 - }
48 + private IPreprocessHelper Helper { get; set; }
49
50 /// <summary>
51 /// Event for ifdef/ifndef directives.
@@ -87,62 +67,6 @@ namespace WixToolset.Core
67 /// </summary>
68 public event ResolvedVariableEventHandler ResolvedVariable;
69
90 - /// <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 -
70 /// <summary>
71 /// Get the source line information for the current element. The precompiler will insert
72 /// special source line number information for each element that it encounters.
@@ -159,122 +83,94 @@ namespace WixToolset.Core
83 }
84
85 /// <summary>
162 - /// Adds an extension.
86 + /// Preprocesses a file.
87 /// </summary>
164 - /// <param name="extension">The extension to add.</param>
165 - public void AddExtension(IPreprocessorExtension extension)
88 + /// <param name="context">The preprocessing context.</param>
89 + /// <returns>XDocument with the postprocessed data.</returns>
90 + public XDocument Process(IPreprocessContext context)
91 {
167 - this.extensions.Add(extension);
92 + this.Context = context ?? throw new ArgumentNullException(nameof(context));
93
169 - if (null != extension.Prefixes)
94 + using (XmlReader reader = XmlReader.Create(context.SourceFile, DocumentXmlReaderSettings))
95 {
171 - foreach (string prefix in extension.Prefixes)
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 - }
96 + return Process(context, reader);
97 }
184 -
185 - //if (null != extension.InspectorExtension)
186 - //{
187 - // this.inspectorExtensions.Add(extension.InspectorExtension);
188 - //}
98 }
99
100 /// <summary>
101 /// Preprocesses a file.
102 /// </summary>
194 - /// <param name="sourceFile">The file to preprocess.</param>
195 - /// <param name="variables">The variables defined prior to preprocessing.</param>
103 + /// <param name="context">The preprocessing context.</param>
104 + /// <param name="reader">XmlReader to processing the context.</param>
105 /// <returns>XDocument with the postprocessed data.</returns>
197 - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
198 - public XDocument Process(string sourceFile, IDictionary<string, string> variables)
106 + public XDocument Process(IPreprocessContext context, XmlReader reader)
107 {
200 - using (Stream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
201 - using (XmlReader reader = XmlReader.Create(sourceFile, DocumentXmlReaderSettings))
108 + if (this.Context == null)
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));
115 }
205 - }
116
207 - /// <summary>
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))
117 + if (String.IsNullOrEmpty(this.Context.SourceFile) && !String.IsNullOrEmpty(reader.BaseURI))
118 {
218 - Uri uri = new Uri(reader.BaseURI);
219 - sourceFile = uri.AbsolutePath;
119 + var uri = new Uri(reader.BaseURI);
120 + this.Context.SourceFile = uri.AbsolutePath;
121 }
122
222 - this.core = new PreprocessorCore(this.extensionsByPrefix, sourceFile, variables);
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"));
123 + this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.SourceFile);
124
229 - // Process the reader into the output.
230 - XDocument output = new XDocument();
231 - try
125 + this.Helper = this.Context.ServiceProvider.GetService<IPreprocessHelper>();
126 +
127 + foreach (var extension in this.Context.Extensions)
128 {
233 - foreach (PreprocessorExtension extension in this.extensions)
129 + if (null != extension.Prefixes)
130 {
235 - extension.Core = this.core;
236 - extension.Initialize();
131 + foreach (string prefix in extension.Prefixes)
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 + }
142 }
143
239 - this.PreprocessReader(false, reader, output, 0);
240 - }
241 - catch (XmlException e)
242 - {
243 - this.UpdateCurrentLineNumber(reader, 0);
244 - throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message));
144 + extension.PrePreprocess(context);
145 }
146
247 - // Fire event with post-processed document.
248 - ProcessedStreamEventArgs args = new ProcessedStreamEventArgs(sourceFile, output);
249 - this.OnProcessedStream(args);
147 + this.CurrentFileStack.Clear();
148 + this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR"));
149
251 - // preprocess the generated XML Document
252 - foreach (PreprocessorExtension extension in this.extensions)
150 + // Process the reader into the output.
151 + XDocument output = new XDocument();
152 + try
153 {
254 - extension.PreprocessDocument(output);
255 - }
154 + this.PreprocessReader(false, reader, output, 0);
155
257 - // finalize the preprocessing
258 - foreach (PreprocessorExtension extension in this.extensions)
259 - {
260 - extension.Finish();
261 - extension.Core = null;
156 + // Fire event with post-processed document.
157 + this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourceFile, output));
158 }
263 -
264 - if (this.core.EncounteredError)
159 + catch (XmlException e)
160 {
266 - return null;
161 + this.UpdateCurrentLineNumber(reader, 0);
162 + throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message));
163 }
268 - else
164 + finally
165 {
270 - if (null != this.preprocessOut)
166 + // Finalize the preprocessing.
167 + foreach (var extension in this.Context.Extensions)
168 {
272 - output.Save(this.preprocessOut);
273 - this.preprocessOut.Flush();
169 + extension.PostPreprocess(output);
170 }
275 -
276 - return output;
171 }
172 +
173 + return this.Context.Messaging.EncounteredError ? null : output;
174 }
175
176 /// <summary>
@@ -339,42 +235,6 @@ namespace WixToolset.Core
235 return true;
236 }
237
342 - /// <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 -
238 /// <summary>
239 /// Tests expression to see if it starts with a keyword.
240 /// </summary>
@@ -383,7 +243,7 @@ namespace WixToolset.Core
243 /// <returns>true if expression starts with a keyword.</returns>
244 private static bool StartsWithKeyword(string expression, PreprocessorOperation operation)
245 {
386 - expression = expression.ToUpper(CultureInfo.InvariantCulture);
246 + expression = expression.ToUpperInvariant();
247 switch (operation)
248 {
249 case PreprocessorOperation.Not:
@@ -431,7 +291,7 @@ namespace WixToolset.Core
291 // update information here in case an error occurs before the next read
292 this.UpdateCurrentLineNumber(reader, offset);
293
434 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
294 + var sourceLineNumbers = this.Context.CurrentSourceLineNumber;
295
296 // check for changes in conditional processing
297 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
@@ -459,14 +319,14 @@ namespace WixToolset.Core
319 name = reader.Value.Trim();
320 if (ifContext.IsTrue)
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);
323 }
324 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
325 {
326 ifContext = new IfContext();
327 }
328 ignore = true;
469 - OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
329 + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
330 break;
331
332 case "ifndef":
@@ -474,25 +334,25 @@ namespace WixToolset.Core
334 name = reader.Value.Trim();
335 if (ifContext.IsTrue)
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);
338 }
339 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
340 {
341 ifContext = new IfContext();
342 }
343 ignore = true;
484 - OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
344 + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
345 break;
346
347 case "elseif":
348 if (0 == ifStack.Count)
349 {
490 - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif"));
350 + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
351 }
352
353 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
354 {
495 - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif"));
355 + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
356 }
357
358 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
@@ -510,12 +370,12 @@ namespace WixToolset.Core
370 case "else":
371 if (0 == ifStack.Count)
372 {
513 - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else"));
373 + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
374 }
375
376 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
377 {
518 - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else"));
378 + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
379 }
380
381 ifContext.IfState = IfState.Else; // we're now in an else
@@ -526,7 +386,7 @@ namespace WixToolset.Core
386 case "endif":
387 if (0 == ifStack.Count)
388 {
529 - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "endif"));
389 + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif"));
390 }
391
392 ifContext = (IfContext)ifStack.Pop();
@@ -606,7 +466,7 @@ namespace WixToolset.Core
466 break;
467
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"));
470
471 case "pragma":
472 this.PreprocessPragma(reader.Value, currentContainer);
@@ -619,31 +479,33 @@ namespace WixToolset.Core
479 break;
480
481 case XmlNodeType.Element:
622 - if (0 < this.includeNextStack.Count && this.includeNextStack.Peek())
482 + if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek())
483 {
484 if ("Include" != reader.LocalName)
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"));
487 }
488
629 - this.includeNextStack.Pop();
630 - this.includeNextStack.Push(false);
489 + this.IncludeNextStack.Pop();
490 + this.IncludeNextStack.Push(false);
491 break;
492 }
493
634 - bool empty = reader.IsEmptyElement;
635 - XNamespace ns = XNamespace.Get(reader.NamespaceURI);
636 - XElement element = new XElement(ns + reader.LocalName);
494 + var empty = reader.IsEmptyElement;
495 + var ns = XNamespace.Get(reader.NamespaceURI);
496 + var element = new XElement(ns + reader.LocalName);
497 currentContainer.Add(element);
498
499 this.UpdateCurrentLineNumber(reader, offset);
640 - element.AddAnnotation(this.currentLineNumber);
500 + element.AddAnnotation(sourceLineNumbers);
501
502 while (reader.MoveToNextAttribute())
503 {
644 - string value = this.core.PreprocessString(this.currentLineNumber, reader.Value);
645 - XNamespace attribNamespace = XNamespace.Get(reader.NamespaceURI);
504 + var value = this.Helper.PreprocessString(this.Context, reader.Value);
505 +
506 + var attribNamespace = XNamespace.Get(reader.NamespaceURI);
507 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
508 +
509 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
510 }
511
@@ -662,12 +524,12 @@ namespace WixToolset.Core
524 break;
525
526 case XmlNodeType.Text:
665 - string postprocessedText = this.core.PreprocessString(this.currentLineNumber, reader.Value);
527 + string postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value);
528 currentContainer.Add(postprocessedText);
529 break;
530
531 case XmlNodeType.CDATA:
670 - string postprocessedValue = this.core.PreprocessString(this.currentLineNumber, reader.Value);
532 + string postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value);
533 currentContainer.Add(new XCData(postprocessedValue));
534 break;
535
@@ -678,13 +540,13 @@ namespace WixToolset.Core
540
541 if (0 != ifStack.Count)
542 {
681 - throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "if", "endif"));
543 + throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "if", "endif"));
544 }
545
546 // TODO: can this actually happen?
547 if (0 != containerStack.Count)
548 {
687 - throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "nodes", "nodes"));
549 + throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "nodes", "nodes"));
550 }
551 }
552
@@ -694,12 +556,10 @@ namespace WixToolset.Core
556 /// <param name="errorMessage">Text from source.</param>
557 private void PreprocessError(string errorMessage)
558 {
697 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
698 -
699 - // resolve other variables in the error message
700 - errorMessage = this.core.PreprocessString(sourceLineNumbers, errorMessage);
559 + // Resolve other variables in the error message.
560 + errorMessage = this.Helper.PreprocessString(this.Context, errorMessage);
561
702 - throw new WixException(WixErrors.PreprocessorError(sourceLineNumbers, errorMessage));
562 + throw new WixException(WixErrors.PreprocessorError(this.Context.CurrentSourceLineNumber, errorMessage));
563 }
564
565 /// <summary>
@@ -708,12 +568,10 @@ namespace WixToolset.Core
568 /// <param name="warningMessage">Text from source.</param>
569 private void PreprocessWarning(string warningMessage)
570 {
711 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
712 -
713 - // resolve other variables in the warning message
714 - warningMessage = this.core.PreprocessString(sourceLineNumbers, warningMessage);
571 + // Resolve other variables in the warning message.
572 + warningMessage = this.Helper.PreprocessString(this.Context, warningMessage);
573
716 - this.core.OnMessage(WixWarnings.PreprocessorWarning(sourceLineNumbers, warningMessage));
574 + this.Context.Messaging.OnMessage(WixWarnings.PreprocessorWarning(this.Context.CurrentSourceLineNumber, warningMessage));
575 }
576
577 /// <summary>
@@ -722,16 +580,15 @@ namespace WixToolset.Core
580 /// <param name="originalDefine">Text from source.</param>
581 private void PreprocessDefine(string originalDefine)
582 {
725 - Match match = defineRegex.Match(originalDefine);
726 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
583 + var match = defineRegex.Match(originalDefine);
584
585 if (!match.Success)
586 {
730 - throw new WixException(WixErrors.IllegalDefineStatement(sourceLineNumbers, originalDefine));
587 + throw new WixException(WixErrors.IllegalDefineStatement(this.Context.CurrentSourceLineNumber, originalDefine));
588 }
589
733 - string defineName = match.Groups["varName"].Value;
734 - string defineValue = match.Groups["varValue"].Value;
590 + var defineName = match.Groups["varName"].Value;
591 + var defineValue = match.Groups["varValue"].Value;
592
593 // strip off the optional quotes
594 if (1 < defineValue.Length &&
@@ -742,15 +599,15 @@ namespace WixToolset.Core
599 }
600
601 // resolve other variables in the variable value
745 - defineValue = this.core.PreprocessString(sourceLineNumbers, defineValue);
602 + defineValue = this.Helper.PreprocessString(this.Context, defineValue);
603
604 if (defineName.StartsWith("var.", StringComparison.Ordinal))
605 {
749 - this.core.AddVariable(sourceLineNumbers, defineName.Substring(4), defineValue);
606 + this.Helper.AddVariable(this.Context, defineName.Substring(4), defineValue);
607 }
608 else
609 {
753 - this.core.AddVariable(sourceLineNumbers, defineName, defineValue);
610 + this.Helper.AddVariable(this.Context, defineName, defineValue);
611 }
612 }
613
@@ -760,16 +617,15 @@ namespace WixToolset.Core
617 /// <param name="originalDefine">Text from source.</param>
618 private void PreprocessUndef(string originalDefine)
619 {
763 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
764 - string name = this.core.PreprocessString(sourceLineNumbers, originalDefine.Trim());
620 + var name = this.Helper.PreprocessString(this.Context, originalDefine.Trim());
621
622 if (name.StartsWith("var.", StringComparison.Ordinal))
623 {
768 - this.core.RemoveVariable(sourceLineNumbers, name.Substring(4));
624 + this.Helper.RemoveVariable(this.Context, name.Substring(4));
625 }
626 else
627 {
772 - this.core.RemoveVariable(sourceLineNumbers, name);
628 + this.Helper.RemoveVariable(this.Context, name);
629 }
630 }
631
@@ -780,12 +636,12 @@ namespace WixToolset.Core
636 /// <param name="parent">Parent container for included content.</param>
637 private void PreprocessInclude(string includePath, XContainer parent)
638 {
783 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
639 + var sourceLineNumbers = this.Context.CurrentSourceLineNumber;
640
785 - // preprocess variables in the path
786 - includePath = this.core.PreprocessString(sourceLineNumbers, includePath);
641 + // Preprocess variables in the path.
642 + includePath = this.Helper.PreprocessString(this.Context, includePath);
643
788 - string includeFile = this.GetIncludeFile(includePath);
644 + var includeFile = this.GetIncludeFile(includePath);
645
646 if (null == includeFile)
647 {
@@ -807,7 +663,7 @@ namespace WixToolset.Core
663 throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message));
664 }
665
810 - this.OnIncludedFile(new IncludedFileEventArgs(sourceLineNumbers, includeFile));
666 + this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile));
667
668 this.PopInclude();
669 }
@@ -821,11 +677,11 @@ namespace WixToolset.Core
677 /// <param name="offset">Offset for the line numbers.</param>
678 private void PreprocessForeach(XmlReader reader, XContainer container, int offset)
679 {
824 - // find the "in" token
825 - int indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
680 + // Find the "in" token.
681 + var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
682 if (0 > indexOfInToken)
683 {
828 - throw new WixException(WixErrors.IllegalForeach(this.currentLineNumber, reader.Value));
684 + throw new WixException(WixErrors.IllegalForeach(this.Context.CurrentSourceLineNumber, reader.Value));
685 }
686
687 // parse out the variable name
@@ -833,7 +689,7 @@ namespace WixToolset.Core
689 string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
690
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);
693
694 string[] varValues = varValuesString.Split(';');
695
@@ -895,20 +751,20 @@ namespace WixToolset.Core
751 }
752 else if (reader.NodeType == XmlNodeType.None)
753 {
898 - throw new WixException(WixErrors.ExpectedEndforeach(this.currentLineNumber));
754 + throw new WixException(WixErrors.ExpectedEndforeach(this.Context.CurrentSourceLineNumber));
755 }
756
757 reader.Read();
758 }
759
904 - using (MemoryStream fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
905 - using (XmlReader loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
760 + using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
761 + using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
762 {
763 // process each iteration, updating the variable's value each time
764 foreach (string varValue in varValues)
765 {
766 // Always overwrite foreach variables.
911 - this.core.AddVariable(this.currentLineNumber, varName, varValue, false);
767 + this.Helper.AddVariable(this.Context, varName, varValue, false);
768
769 try
770 {
@@ -917,7 +773,7 @@ namespace WixToolset.Core
773 catch (XmlException e)
774 {
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));
777 }
778
779 fragmentStream.Position = 0; // seek back to the beginning for the next loop.
@@ -931,24 +787,23 @@ namespace WixToolset.Core
787 /// <param name="pragmaText">Text from source.</param>
788 private void PreprocessPragma(string pragmaText, XContainer parent)
789 {
934 - Match match = pragmaRegex.Match(pragmaText);
935 - SourceLineNumber sourceLineNumbers = this.currentLineNumber;
790 + var match = pragmaRegex.Match(pragmaText);
791
792 if (!match.Success)
793 {
939 - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaText));
794 + throw new WixException(WixErrors.InvalidPreprocessorPragma(this.Context.CurrentSourceLineNumber, pragmaText));
795 }
796
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();
799
800 try
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);
803 }
804 catch (Exception e)
805 {
951 - throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(sourceLineNumbers, pragmaText, e.Message));
806 + throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(this.Context.CurrentSourceLineNumber, pragmaText, e.Message));
807 }
808 }
809
@@ -975,11 +830,11 @@ namespace WixToolset.Core
830 int endingQuotes = expression.IndexOf('\"', 1);
831 if (-1 == endingQuotes)
832 {
978 - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
833 + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
834 }
835
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));
838
839 // advance past this string
840 expression = expression.Substring(endingQuotes + 1).Trim();
@@ -1009,7 +864,7 @@ namespace WixToolset.Core
864
865 if (-1 == endingParen)
866 {
1012 - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
867 + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
868 }
869 token = expression.Substring(0, endingParen + 1);
870
@@ -1115,7 +970,7 @@ namespace WixToolset.Core
970 {
971 try
972 {
1118 - varValue = this.core.PreprocessString(this.currentLineNumber, variable);
973 + varValue = this.Helper.PreprocessString(this.Context, variable);
974 }
975 catch (ArgumentNullException)
976 {
@@ -1126,12 +981,12 @@ namespace WixToolset.Core
981 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1)
982 {
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));
985 }
986 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1)
987 {
988 // shouldn't contain quotes
1134 - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
989 + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
990 }
991
992 return varValue;
@@ -1162,7 +1017,7 @@ namespace WixToolset.Core
1017 {
1018 if (stringLiteral)
1019 {
1165 - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1020 + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1021 }
1022
1023 rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
@@ -1213,7 +1068,7 @@ namespace WixToolset.Core
1068 {
1069 if (operation.Length > 0)
1070 {
1216 - throw new WixException(WixErrors.ExpectedVariable(this.currentLineNumber, originalExpression));
1071 + throw new WixException(WixErrors.ExpectedVariable(this.Context.CurrentSourceLineNumber, originalExpression));
1072 }
1073
1074 // false expression
@@ -1228,7 +1083,7 @@ namespace WixToolset.Core
1083 }
1084 else
1085 {
1231 - throw new WixException(WixErrors.UnexpectedLiteral(this.currentLineNumber, originalExpression));
1086 + throw new WixException(WixErrors.UnexpectedLiteral(this.Context.CurrentSourceLineNumber, originalExpression));
1087 }
1088 }
1089 else
@@ -1268,11 +1123,11 @@ namespace WixToolset.Core
1123 }
1124 catch (FormatException)
1125 {
1271 - throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression));
1126 + throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1127 }
1128 catch (OverflowException)
1129 {
1275 - throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression));
1130 + throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1131 }
1132
1133 // Compare the numbers
@@ -1314,7 +1169,7 @@ namespace WixToolset.Core
1169 closeParenIndex = expression.IndexOf(')', closeParenIndex);
1170 if (closeParenIndex == -1)
1171 {
1317 - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1172 + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
1173 }
1174
1175 if (InsideQuotes(expression, closeParenIndex))
@@ -1363,7 +1218,7 @@ namespace WixToolset.Core
1218 currentValue = !currentValue;
1219 break;
1220 default:
1366 - throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.currentLineNumber, operation.ToString()));
1221 + throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString()));
1222 }
1223 }
1224
@@ -1412,7 +1267,7 @@ namespace WixToolset.Core
1267 expression = expression.Trim();
1268 if (expression.Length == 0)
1269 {
1415 - throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression));
1270 + throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.Context.CurrentSourceLineNumber, originalExpression));
1271 }
1272
1273 // If the expression starts with parenthesis, evaluate it
@@ -1433,7 +1288,7 @@ namespace WixToolset.Core
1288 expression = expression.Substring(3).Trim();
1289 if (expression.Length == 0)
1290 {
1436 - throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression));
1291 + throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.Context.CurrentSourceLineNumber, originalExpression));
1292 }
1293
1294 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true);
@@ -1462,7 +1317,7 @@ namespace WixToolset.Core
1317 }
1318 else
1319 {
1465 - throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression));
1320 + throw new WixException(WixErrors.InvalidSubExpression(this.Context.CurrentSourceLineNumber, expression, originalExpression));
1321 }
1322 }
1323
@@ -1481,9 +1336,9 @@ namespace WixToolset.Core
1336 {
1337 int newLine = lineInfoReader.LineNumber + offset;
1338
1484 - if (this.currentLineNumber.LineNumber != newLine)
1339 + if (this.Context.CurrentSourceLineNumber.LineNumber != newLine)
1340 {
1486 - this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine);
1341 + this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.CurrentSourceLineNumber.FileName, newLine);
1342 }
1343 }
1344 }
@@ -1494,15 +1349,15 @@ namespace WixToolset.Core
1349 /// <param name="fileName">Name to push on to the stack of included files.</param>
1350 private void PushInclude(string fileName)
1351 {
1497 - if (1023 < this.currentFileStack.Count)
1352 + if (1023 < this.CurrentFileStack.Count)
1353 {
1499 - throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count));
1354 + throw new WixException(WixErrors.TooDeeplyIncluded(this.Context.CurrentSourceLineNumber, this.CurrentFileStack.Count));
1355 }
1356
1502 - this.currentFileStack.Push(fileName);
1503 - this.sourceStack.Push(this.currentLineNumber);
1504 - this.currentLineNumber = new SourceLineNumber(fileName);
1505 - this.includeNextStack.Push(true);
1357 + this.CurrentFileStack.Push(fileName);
1358 + this.SourceStack.Push(this.Context.CurrentSourceLineNumber);
1359 + this.Context.CurrentSourceLineNumber = new SourceLineNumber(fileName);
1360 + this.IncludeNextStack.Push(true);
1361 }
1362
1363 /// <summary>
@@ -1510,10 +1365,10 @@ namespace WixToolset.Core
1365 /// </summary>
1366 private void PopInclude()
1367 {
1513 - this.currentLineNumber = this.sourceStack.Pop();
1368 + this.Context.CurrentSourceLineNumber = this.SourceStack.Pop();
1369
1515 - this.currentFileStack.Pop();
1516 - this.includeNextStack.Pop();
1370 + this.CurrentFileStack.Pop();
1371 + this.IncludeNextStack.Pop();
1372 }
1373
1374 /// <summary>
@@ -1548,8 +1403,8 @@ namespace WixToolset.Core
1403 else // relative path
1404 {
1405 // build a string to test the directory containing the source file first
1551 - string currentFolder = this.currentFileStack.Peek();
1552 - string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath);
1406 + var currentFolder = this.CurrentFileStack.Peek();
1407 + var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) , includePath);
1408
1409 // test the source file directory
1410 if (File.Exists(includeTestPath))
@@ -1558,7 +1413,7 @@ namespace WixToolset.Core
1413 }
1414 else // test all search paths in the order specified on the command line
1415 {
1561 - foreach (string includeSearchPath in this.IncludeSearchPaths)
1416 + foreach (var includeSearchPath in this.Context.IncludeSearchPaths)
1417 {
1418 // if the path exists, we have found the final string
1419 includeTestPath = Path.Combine(includeSearchPath, includePath);
src/WixToolset.Core/PreprocessorCore.cs deleted
-560
@@ -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 -
3 -namespace 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 -}
src/WixToolset.Core/WixToolsetServiceProvider.cs
+11
@@ -12,6 +12,7 @@ namespace WixToolset.Core
12 {
13 private ExtensionManager extensionManager;
14 private ParseHelper parseHelper;
15 + private PreprocessHelper preprocessHelper;
16 private TupleDefinitionCreator tupleDefinitionCreator;
17
18 public object GetService(Type serviceType)
@@ -19,6 +20,11 @@ namespace WixToolset.Core
20 if (serviceType == null) throw new ArgumentNullException(nameof(serviceType));
21
22 // Transients.
23 + if (serviceType == typeof(IPreprocessContext))
24 + {
25 + return new PreprocessContext(this);
26 + }
27 +
28 if (serviceType == typeof(ICompileContext))
29 {
30 return new CompileContext(this);
@@ -65,6 +71,11 @@ namespace WixToolset.Core
71 return this.parseHelper = this.parseHelper ?? new ParseHelper(this);
72 }
73
74 + if (serviceType == typeof(IPreprocessHelper))
75 + {
76 + return this.preprocessHelper = this.preprocessHelper ?? new PreprocessHelper(this);
77 + }
78 +
79 throw new ArgumentException($"Unknown service type: {serviceType.Name}", nameof(serviceType));
80 }
81 }
src/test/Example.Extension/ExampleExtensionFactory.cs
+9 -2
@@ -7,11 +7,18 @@ namespace Example.Extension
7
8 public class ExampleExtensionFactory : IExtensionFactory
9 {
10 + private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension;
11 +
12 public bool TryCreateExtension(Type extensionType, out object extension)
13 {
12 - if (extensionType == typeof(IPreprocessorExtension))
14 + if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension))
15 {
14 - extension = new ExamplePreprocessorExtension();
16 + if (preprocessorExtension == null)
17 + {
18 + preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine();
19 + }
20 +
21 + extension = preprocessorExtension;
22 }
23 else if (extensionType == typeof(ICompilerExtension))
24 {
src/test/Example.Extension/ExamplePreprocessorExtension.cs deleted
-55
@@ -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 -
3 -namespace 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
src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs new
+46
@@ -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 +
3 +namespace 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
src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
+43
@@ -56,5 +56,48 @@ namespace WixToolsetTest.CoreIntegration
56 Assert.Equal("Bar", example[1].AsString());
57 }
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 + }
102 }
103 }
src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs
+2
@@ -6,6 +6,8 @@
6 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
7 <MediaTemplate />
8
9 + <Property Id="ExampleProperty" Value="$(ex.Test)" />
10 +
11 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
12 <ComponentGroupRef Id="ProductComponents" />
13 </Feature>