diff options
| author | Rob Mensching <rob@firegiant.com> | 2017-12-02 00:46:11 -0800 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2017-12-02 00:46:11 -0800 |
| commit | 95f2f4425b900374c7d7b583ae810b096121b3c4 (patch) | |
| tree | 0ede0972e849bdc2c57e9535e31fbdd0df113f8d /src | |
| parent | 720c4a0db1a2fb2aa3e08e5c99d5198873e448ba (diff) | |
| download | wix-95f2f4425b900374c7d7b583ae810b096121b3c4.tar.gz wix-95f2f4425b900374c7d7b583ae810b096121b3c4.tar.bz2 wix-95f2f4425b900374c7d7b583ae810b096121b3c4.zip | |
Implement support for IExtensionCommandLine and IPreprocessorExtension
Diffstat (limited to 'src')
| -rw-r--r-- | src/WixToolset.Core/CommandLine/BuildCommand.cs | 15 | ||||
| -rw-r--r-- | src/WixToolset.Core/CommandLine/CommandLine.cs | 29 | ||||
| -rw-r--r-- | src/WixToolset.Core/CommandLine/CompileCommand.cs | 13 | ||||
| -rw-r--r-- | src/WixToolset.Core/Compiler.cs | 7 | ||||
| -rw-r--r-- | src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs | 479 | ||||
| -rw-r--r-- | src/WixToolset.Core/Preprocess/PreprocessorOperation.cs | 19 | ||||
| -rw-r--r-- | src/WixToolset.Core/PreprocessContext.cs | 36 | ||||
| -rw-r--r-- | src/WixToolset.Core/Preprocessor.cs | 453 | ||||
| -rw-r--r-- | src/WixToolset.Core/PreprocessorCore.cs | 560 | ||||
| -rw-r--r-- | src/WixToolset.Core/WixToolsetServiceProvider.cs | 11 | ||||
| -rw-r--r-- | src/test/Example.Extension/ExampleExtensionFactory.cs | 11 | ||||
| -rw-r--r-- | src/test/Example.Extension/ExamplePreprocessorExtension.cs | 55 | ||||
| -rw-r--r-- | src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs | 46 | ||||
| -rw-r--r-- | src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs | 43 | ||||
| -rw-r--r-- | src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs | 2 |
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 | |||
| 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 | } | ||
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 | |||
| 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 | } | ||
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 | |||
| 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 | } | ||
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 | |||
| 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 | } | ||
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 | |||
| 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 | ||
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 | |||
| 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 | ||
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> |
