From 95f2f4425b900374c7d7b583ae810b096121b3c4 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 2 Dec 2017 00:46:11 -0800 Subject: Implement support for IExtensionCommandLine and IPreprocessorExtension --- src/WixToolset.Core/CommandLine/BuildCommand.cs | 15 +- src/WixToolset.Core/CommandLine/CommandLine.cs | 29 +- src/WixToolset.Core/CommandLine/CompileCommand.cs | 13 +- src/WixToolset.Core/Compiler.cs | 7 +- .../ExtensibilityServices/PreprocessHelper.cs | 479 ++++++++++++++++++ .../Preprocess/PreprocessorOperation.cs | 19 + src/WixToolset.Core/PreprocessContext.cs | 36 ++ src/WixToolset.Core/Preprocessor.cs | 453 ++++++----------- src/WixToolset.Core/PreprocessorCore.cs | 560 --------------------- src/WixToolset.Core/WixToolsetServiceProvider.cs | 11 + .../Example.Extension/ExampleExtensionFactory.cs | 11 +- .../ExamplePreprocessorExtension.cs | 55 -- .../ExamplePreprocessorExtensionAndCommandLine.cs | 46 ++ .../ExtensionFixture.cs | 43 ++ .../TestData/ExampleExtension/Package.wxs | 2 + 15 files changed, 842 insertions(+), 937 deletions(-) create mode 100644 src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs create mode 100644 src/WixToolset.Core/Preprocess/PreprocessorOperation.cs create mode 100644 src/WixToolset.Core/PreprocessContext.cs delete mode 100644 src/WixToolset.Core/PreprocessorCore.cs delete mode 100644 src/test/Example.Extension/ExamplePreprocessorExtension.cs create mode 100644 src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs 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 public IExtensionManager ExtensionManager { get; } + public IEnumerable IncludeSearchPaths { get; } + public IEnumerable LocFiles { get; } public IEnumerable LibraryFiles { get; } @@ -102,15 +104,18 @@ namespace WixToolset.Core { var intermediates = new List(); - foreach (var sourceFile in this.SourceFiles) { - //var preprocessContext = this.ServiceProvider.GetService(); - //preprocessContext.SourcePath = sourceFile.SourcePath; - //preprocessContext.Variables = this.PreprocessorVariables; + var preprocessContext = this.ServiceProvider.GetService(); + preprocessContext.Messaging = Messaging.Instance; + preprocessContext.Extensions = this.ExtensionManager.Create(); + preprocessContext.Platform = Platform.X86; // TODO: set this correctly + preprocessContext.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List(); + preprocessContext.SourceFile = sourceFile.SourcePath; + preprocessContext.Variables = new Dictionary(this.PreprocessorVariables); var preprocessor = new Preprocessor(); - var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables); + var document = preprocessor.Process(preprocessContext); var compileContext = this.ServiceProvider.GetService(); 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 Bind, } - internal class CommandLine : ICommandLine + internal class CommandLine : ICommandLine, IParseCommandLine { public CommandLine() { @@ -57,10 +57,10 @@ namespace WixToolset.Core args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray(); } - return this.ParseStandardCommandLine(args); + return this.ParseStandardCommandLine(context, args); } - private ICommandLineCommand ParseStandardCommandLine(string[] args) + private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args) { var next = String.Empty; @@ -90,7 +90,7 @@ namespace WixToolset.Core var builtOutputsFile = String.Empty; var wixProjectFile = String.Empty; - this.Parse(args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => + this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => { if (cmdline.IsSwitch(arg)) { @@ -279,13 +279,13 @@ namespace WixToolset.Core } #endif - private ICommandLine Parse(string[] commandLineArguments, Func parseCommand, Func parseArgument) + private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func parseCommand, Func parseArgument) { this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments); this.QueueArgumentsAndLoadExtensions(this.OriginalArguments); - this.ProcessRemainingArguments(parseArgument, parseCommand); + this.ProcessRemainingArguments(context, parseArgument, parseCommand); return this; } @@ -413,7 +413,7 @@ namespace WixToolset.Core /// True if a valid switch exists there, false if not. public bool IsSwitch(string arg) { - return arg != null && ('/' == arg[0] || '-' == arg[0]); + return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]); } /// @@ -522,10 +522,15 @@ namespace WixToolset.Core } } - private void ProcessRemainingArguments(Func parseArgument, Func parseCommand) + private void ProcessRemainingArguments(ICommandLineContext context, Func parseArgument, Func parseCommand) { var extensions = this.ExtensionManager.Create(); + foreach (var extension in extensions) + { + extension.PreParse(context); + } + while (!this.ShowHelp && String.IsNullOrEmpty(this.ErrorArgument) && TryDequeue(this.RemainingArguments, out var arg)) @@ -566,10 +571,10 @@ namespace WixToolset.Core { foreach (var extension in extensions) { - //if (extension.ParseArgument(this, arg)) - //{ - // return true; - //} + if (extension.TryParseArgument(this, arg)) + { + return true; + } } 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 { using System; using System.Collections.Generic; + using System.Linq; using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; @@ -22,6 +23,8 @@ namespace WixToolset.Core private IExtensionManager ExtensionManager { get; } + public IEnumerable IncludeSearchPaths { get; } + private IEnumerable SourceFiles { get; } private IDictionary PreprocessorVariables { get; } @@ -30,8 +33,16 @@ namespace WixToolset.Core { foreach (var sourceFile in this.SourceFiles) { + var preprocessContext = this.ServiceProvider.GetService(); + preprocessContext.Messaging = Messaging.Instance; + preprocessContext.Extensions = this.ExtensionManager.Create(); + preprocessContext.Platform = Platform.X86; // TODO: set this correctly + preprocessContext.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List(); + preprocessContext.SourceFile = sourceFile.SourcePath; + preprocessContext.Variables = new Dictionary(this.PreprocessorVariables); + var preprocessor = new Preprocessor(); - var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables); + var document = preprocessor.Process(preprocessContext); var compileContext = this.ServiceProvider.GetService(); 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 switch (installScopeType) { case Wix.Package.InstallScopeType.perMachine: - row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property); - row.Set(0, "ALLUSERS"); - row.Set(1, "1"); + { + row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property, new Identifier("ALLUSERS", AccessModifier.Public)); + row.Set(1, "1"); + } break; case Wix.Package.InstallScopeType.perUser: 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 @@ +// 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. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class PreprocessHelper : IPreprocessHelper + { + private static readonly char[] VariableSplitter = new char[] { '.' }; + private static readonly char[] ArgumentSplitter = new char[] { ',' }; + + public PreprocessHelper(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + private Dictionary ExtensionsByPrefix { get; set; } + + public void AddVariable(IPreprocessContext context, string name, string value) + { + this.AddVariable(context, name, value, true); + } + + public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) + { + var currentValue = this.GetVariableValue(context, "var", name); + + if (null == currentValue) + { + context.Variables.Add(name, value); + } + else + { + if (showWarning) + { + context.Messaging.OnMessage(WixWarnings.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); + } + + context.Variables[name] = value; + } + } + + public string EvaluateFunction(IPreprocessContext context, string function) + { + var prefixParts = function.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) + { + throw new WixException(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var prefix = prefixParts[0]; + var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); + + // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. + if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) + { + throw new WixException(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var functionName = functionParts[0]; + + // Remove the trailing closing paren. + var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); + + // Parse the arguments and preprocess them. + var args = allArgs.Split(ArgumentSplitter); + for (var i = 0; i < args.Length; i++) + { + args[i] = this.PreprocessString(context, args[i].Trim()); + } + + var result = this.EvaluateFunction(context, prefix, functionName, args); + + // If the function didn't evaluate, try to evaluate the original value as a variable to support + // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. + if (result == null) + { + result = this.GetVariableValue(context, function, false); + } + + return result; + } + + public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(function)) + { + throw new ArgumentNullException("function"); + } + + switch (prefix) + { + case "fun": + switch (function) + { + case "AutoVersion": + // Make sure the base version is specified + if (args.Length == 0 || String.IsNullOrEmpty(args[0])) + { + throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); + } + + // Build = days since 1/1/2000; Revision = seconds since midnight / 2 + var now = DateTime.UtcNow; + var build = now - new DateTime(2000, 1, 1); + var revision = now - new DateTime(now.Year, now.Month, now.Day); + + return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); + + default: + return null; + } + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(context); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.EvaluateFunction(prefix, function, args); + } + catch (Exception e) + { + throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); + } + } + else + { + return null; + } + } + } + + public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) + { + // Strip the "$(" off the front. + if (variable.StartsWith("$(", StringComparison.Ordinal)) + { + variable = variable.Substring(2); + } + + var parts = variable.Split(VariableSplitter, 2); + + if (1 == parts.Length) // missing prefix + { + if (allowMissingPrefix) + { + return this.GetVariableValue(context, "var", parts[0]); + } + else + { + throw new WixException(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + else + { + // check for empty variable name + if (0 < parts[1].Length) + { + string result = this.GetVariableValue(context, parts[0], parts[1]); + + // 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 + if (null == result && allowMissingPrefix && variable.Contains(".")) + { + result = this.GetVariableValue(context, "var", variable); + } + + return result; + } + else + { + throw new WixException(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + } + + public string GetVariableValue(IPreprocessContext context, string prefix, string name) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + switch (prefix) + { + case "env": + return Environment.GetEnvironmentVariable(name); + + case "sys": + switch (name) + { + case "CURRENTDIR": + return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); + + case "SOURCEFILEDIR": + return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); + + case "SOURCEFILEPATH": + return context.CurrentSourceLineNumber.FileName; + + case "PLATFORM": + context.Messaging.OnMessage(WixWarnings.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); + + goto case "BUILDARCH"; + + case "BUILDARCH": + switch (context.Platform) + { + case Platform.X86: + return "x86"; + + case Platform.X64: + return "x64"; + + case Platform.IA64: + return "ia64"; + + case Platform.ARM: + return "arm"; + + default: + throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, context.Platform.ToString()); + } + + default: + return null; + } + + case "var": + return context.Variables.TryGetValue(name, out var result) ? result : null; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(context); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.GetVariableValue(prefix, name); + } + catch (Exception e) + { + throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); + } + } + else + { + return null; + } + } + } + + public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) + { + var prefixParts = pragmaName.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length) + { + throw new WixException(WixErrors.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + var prefix = prefixParts[0]; + var pragma = prefixParts[1]; + + if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) + { + throw new WixException(WixErrors.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + switch (prefix) + { + case "wix": + switch (pragma) + { + // Add any core defined pragmas here + default: + context.Messaging.OnMessage(WixWarnings.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + break; + } + break; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(context); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + if (!extension.ProcessPragma(prefix, pragma, args, parent)) + { + context.Messaging.OnMessage(WixWarnings.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + } + } + break; + } + } + + public string PreprocessString(IPreprocessContext context, string value) + { + var sb = new StringBuilder(); + var currentPosition = 0; + var end = 0; + + while (-1 != (currentPosition = value.IndexOf('$', end))) + { + if (end < currentPosition) + { + sb.Append(value, end, currentPosition - end); + } + + end = currentPosition + 1; + + var remainder = value.Substring(end); + if (remainder.StartsWith("$", StringComparison.Ordinal)) + { + sb.Append("$"); + end++; + } + else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) + { + currentPosition = remainder.IndexOf(')'); + if (-1 == currentPosition) + { + context.Messaging.OnMessage(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + + sb.Append("$"); // just put the resource reference back as was + sb.Append(remainder, 0, currentPosition + 1); + + end += currentPosition + 1; + } + else if (remainder.StartsWith("(", StringComparison.Ordinal)) + { + var openParenCount = 1; + var closingParenCount = 0; + var isFunction = false; + var foundClosingParen = false; + + // find the closing paren + int closingParenPosition; + for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) + { + switch (remainder[closingParenPosition]) + { + case '(': + openParenCount++; + isFunction = true; + break; + + case ')': + closingParenCount++; + break; + } + + if (openParenCount == closingParenCount) + { + foundClosingParen = true; + break; + } + } + + // move the currentPosition to the closing paren + currentPosition += closingParenPosition; + + if (!foundClosingParen) + { + if (isFunction) + { + context.Messaging.OnMessage(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); + break; + } + else + { + context.Messaging.OnMessage(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + } + + var subString = remainder.Substring(1, closingParenPosition - 1); + string result = null; + if (isFunction) + { + result = this.EvaluateFunction(context, subString); + } + else + { + result = this.GetVariableValue(context, subString, false); + } + + if (null == result) + { + if (isFunction) + { + context.Messaging.OnMessage(WixErrors.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); + break; + } + else + { + context.Messaging.OnMessage(WixErrors.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); + break; + } + } + else + { + if (!isFunction) + { + //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); + } + } + + sb.Append(result); + end += closingParenPosition + 1; + } + else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing + { + sb.Append('$'); + } + } + + if (end < value.Length) + { + sb.Append(value.Substring(end)); + } + + return sb.ToString(); + } + + public void RemoveVariable(IPreprocessContext context, string name) + { + if (!context.Variables.Remove(name)) + { + context.Messaging.OnMessage(WixErrors.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); + } + } + + private Dictionary GetExtensionsByPrefix(IPreprocessContext context) + { + if (this.ExtensionsByPrefix == null) + { + this.ExtensionsByPrefix = new Dictionary(); + + foreach (var extension in context.Extensions) + { + if (null != extension.Prefixes) + { + foreach (string prefix in extension.Prefixes) + { + if (!this.ExtensionsByPrefix.ContainsKey(prefix)) + { + this.ExtensionsByPrefix.Add(prefix, extension); + } + } + } + } + } + + return this.ExtensionsByPrefix; + } + } +} 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 @@ +// 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. + +namespace WixToolset.Core.Preprocess +{ + /// + /// Enumeration for preprocessor operations in if statements. + /// + internal enum PreprocessorOperation + { + /// The and operator. + And, + + /// The or operator. + Or, + + /// The not operator. + Not + } +} 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 @@ +// 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. + +namespace WixToolset.Core +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + + /// + /// The preprocessor core. + /// + internal class PreprocessContext : IPreprocessContext + { + internal PreprocessContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public Messaging Messaging { get; set; } + + public IEnumerable Extensions { get; set; } + + public Platform Platform { get; set; } + + public IList IncludeSearchPaths { get; set; } + + public string SourceFile { get; set; } + + public IDictionary Variables { get; set; } + + public SourceLineNumber CurrentSourceLineNumber { get; set; } + } +} 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 { using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; @@ -14,6 +13,7 @@ namespace WixToolset.Core using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Core.Preprocess; + using WixToolset.Extensibility.Services; /// /// Preprocessor object @@ -30,42 +30,22 @@ namespace WixToolset.Core }; private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() { - ConformanceLevel = System.Xml.ConformanceLevel.Fragment, + ConformanceLevel = ConformanceLevel.Fragment, ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, XmlResolver = null, }; - private List extensions; - private Dictionary extensionsByPrefix; + private IPreprocessContext Context { get; set; } - private SourceLineNumber currentLineNumber; - private Stack sourceStack; + private Stack CurrentFileStack { get; } = new Stack(); - private PreprocessorCore core; - private TextWriter preprocessOut; + private Dictionary ExtensionsByPrefix { get; } = new Dictionary(); - private Stack includeNextStack; - private Stack currentFileStack; + private Stack IncludeNextStack { get; } = new Stack(); - private Platform currentPlatform; + private Stack SourceStack { get; } = new Stack(); - /// - /// Creates a new preprocesor. - /// - public Preprocessor() - { - this.IncludeSearchPaths = new List(); - - this.extensions = new List(); - this.extensionsByPrefix = new Dictionary(); - - this.sourceStack = new Stack(); - - this.includeNextStack = new Stack(); - this.currentFileStack = new Stack(); - - this.currentPlatform = Platform.X86; - } + private IPreprocessHelper Helper { get; set; } /// /// Event for ifdef/ifndef directives. @@ -87,62 +67,6 @@ namespace WixToolset.Core /// public event ResolvedVariableEventHandler ResolvedVariable; - /// - /// Enumeration for preprocessor operations in if statements. - /// - private enum PreprocessorOperation - { - /// The and operator. - And, - - /// The or operator. - Or, - - /// The not operator. - Not - } - - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public Platform CurrentPlatform - { - get { return this.currentPlatform; } - set { this.currentPlatform = value; } - } - - /// - /// Ordered list of search paths that the precompiler uses to find included files. - /// - /// List of ordered search paths to use during precompiling. - public IList IncludeSearchPaths { get; private set; } - - /// - /// Specifies the text stream to display the postprocessed data to. - /// - /// TextWriter to write preprocessed xml to. - public TextWriter PreprocessOut - { - get { return this.preprocessOut; } - set { this.preprocessOut = value; } - } - - /// - /// Get the source line information for the current element. The precompiler will insert - /// special source line number processing instructions before each element that it - /// encounters. This is where those line numbers are read and processed. This function - /// may return an array of source line numbers because the element may have come from - /// an included file, in which case the chain of imports is expressed in the array. - /// - /// Element to get source line information for. - /// Returns the stack of imports used to author the element being processed. - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] - public static SourceLineNumber GetSourceLineNumbers(XmlNode node) - { - return null; - } - /// /// Get the source line information for the current element. The precompiler will insert /// special source line number information for each element that it encounters. @@ -159,122 +83,94 @@ namespace WixToolset.Core } /// - /// Adds an extension. + /// Preprocesses a file. /// - /// The extension to add. - public void AddExtension(IPreprocessorExtension extension) + /// The preprocessing context. + /// XDocument with the postprocessed data. + public XDocument Process(IPreprocessContext context) { - this.extensions.Add(extension); + this.Context = context ?? throw new ArgumentNullException(nameof(context)); - if (null != extension.Prefixes) + using (XmlReader reader = XmlReader.Create(context.SourceFile, DocumentXmlReaderSettings)) { - foreach (string prefix in extension.Prefixes) - { - IPreprocessorExtension collidingExtension; - if (!this.extensionsByPrefix.TryGetValue(prefix, out collidingExtension)) - { - this.extensionsByPrefix.Add(prefix, extension); - } - else - { - Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); - } - } + return Process(context, reader); } - - //if (null != extension.InspectorExtension) - //{ - // this.inspectorExtensions.Add(extension.InspectorExtension); - //} } /// /// Preprocesses a file. /// - /// The file to preprocess. - /// The variables defined prior to preprocessing. + /// The preprocessing context. + /// XmlReader to processing the context. /// XDocument with the postprocessed data. - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] - public XDocument Process(string sourceFile, IDictionary variables) + public XDocument Process(IPreprocessContext context, XmlReader reader) { - using (Stream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (XmlReader reader = XmlReader.Create(sourceFile, DocumentXmlReaderSettings)) + if (this.Context == null) { - return Process(reader, variables, sourceFile); + this.Context = context ?? throw new ArgumentNullException(nameof(context)); + } + else if (this.Context != context) + { + throw new ArgumentException(nameof(context)); } - } - /// - /// Preprocesses a file. - /// - /// The file to preprocess. - /// The variables defined prior to preprocessing. - /// XDocument with the postprocessed data. - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] - public XDocument Process(XmlReader reader, IDictionary variables, string sourceFile = null) - { - if (String.IsNullOrEmpty(sourceFile) && !String.IsNullOrEmpty(reader.BaseURI)) + if (String.IsNullOrEmpty(this.Context.SourceFile) && !String.IsNullOrEmpty(reader.BaseURI)) { - Uri uri = new Uri(reader.BaseURI); - sourceFile = uri.AbsolutePath; + var uri = new Uri(reader.BaseURI); + this.Context.SourceFile = uri.AbsolutePath; } - this.core = new PreprocessorCore(this.extensionsByPrefix, sourceFile, variables); - this.core.ResolvedVariableHandler = this.ResolvedVariable; - this.core.CurrentPlatform = this.currentPlatform; - this.currentLineNumber = new SourceLineNumber(sourceFile); - this.currentFileStack.Clear(); - this.currentFileStack.Push(this.core.GetVariableValue(this.currentLineNumber, "sys", "SOURCEFILEDIR")); + this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.SourceFile); - // Process the reader into the output. - XDocument output = new XDocument(); - try + this.Helper = this.Context.ServiceProvider.GetService(); + + foreach (var extension in this.Context.Extensions) { - foreach (PreprocessorExtension extension in this.extensions) + if (null != extension.Prefixes) { - extension.Core = this.core; - extension.Initialize(); + foreach (string prefix in extension.Prefixes) + { + if (!this.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) + { + this.ExtensionsByPrefix.Add(prefix, extension); + } + else + { + this.Context.Messaging.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); + } + } } - this.PreprocessReader(false, reader, output, 0); - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(reader, 0); - throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message)); + extension.PrePreprocess(context); } - // Fire event with post-processed document. - ProcessedStreamEventArgs args = new ProcessedStreamEventArgs(sourceFile, output); - this.OnProcessedStream(args); + this.CurrentFileStack.Clear(); + this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR")); - // preprocess the generated XML Document - foreach (PreprocessorExtension extension in this.extensions) + // Process the reader into the output. + XDocument output = new XDocument(); + try { - extension.PreprocessDocument(output); - } + this.PreprocessReader(false, reader, output, 0); - // finalize the preprocessing - foreach (PreprocessorExtension extension in this.extensions) - { - extension.Finish(); - extension.Core = null; + // Fire event with post-processed document. + this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourceFile, output)); } - - if (this.core.EncounteredError) + catch (XmlException e) { - return null; + this.UpdateCurrentLineNumber(reader, 0); + throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); } - else + finally { - if (null != this.preprocessOut) + // Finalize the preprocessing. + foreach (var extension in this.Context.Extensions) { - output.Save(this.preprocessOut); - this.preprocessOut.Flush(); + extension.PostPreprocess(output); } - - return output; } + + return this.Context.Messaging.EncounteredError ? null : output; } /// @@ -339,42 +235,6 @@ namespace WixToolset.Core return true; } - /// - /// Fires an event when an ifdef/ifndef directive is processed. - /// - /// ifdef/ifndef event arguments. - private void OnIfDef(IfDefEventArgs ea) - { - if (null != this.IfDef) - { - this.IfDef(this, ea); - } - } - - /// - /// Fires an event when an included file is processed. - /// - /// Included file event arguments. - private void OnIncludedFile(IncludedFileEventArgs ea) - { - if (null != this.IncludedFile) - { - this.IncludedFile(this, ea); - } - } - - /// - /// Fires an event after the file is preprocessed. - /// - /// Included file event arguments. - private void OnProcessedStream(ProcessedStreamEventArgs ea) - { - if (null != this.ProcessedStream) - { - this.ProcessedStream(this, ea); - } - } - /// /// Tests expression to see if it starts with a keyword. /// @@ -383,7 +243,7 @@ namespace WixToolset.Core /// true if expression starts with a keyword. private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) { - expression = expression.ToUpper(CultureInfo.InvariantCulture); + expression = expression.ToUpperInvariant(); switch (operation) { case PreprocessorOperation.Not: @@ -431,7 +291,7 @@ namespace WixToolset.Core // update information here in case an error occurs before the next read this.UpdateCurrentLineNumber(reader, offset); - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var sourceLineNumbers = this.Context.CurrentSourceLineNumber; // check for changes in conditional processing if (XmlNodeType.ProcessingInstruction == reader.NodeType) @@ -459,14 +319,14 @@ namespace WixToolset.Core name = reader.Value.Trim(); if (ifContext.IsTrue) { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { ifContext = new IfContext(); } ignore = true; - OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); break; case "ifndef": @@ -474,25 +334,25 @@ namespace WixToolset.Core name = reader.Value.Trim(); if (ifContext.IsTrue) { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { ifContext = new IfContext(); } ignore = true; - OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); break; case "elseif": if (0 == ifStack.Count) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); } if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); } ifContext.IfState = IfState.ElseIf; // we're now in an elseif @@ -510,12 +370,12 @@ namespace WixToolset.Core case "else": if (0 == ifStack.Count) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); } if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); } ifContext.IfState = IfState.Else; // we're now in an else @@ -526,7 +386,7 @@ namespace WixToolset.Core case "endif": if (0 == ifStack.Count) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "endif")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); } ifContext = (IfContext)ifStack.Pop(); @@ -606,7 +466,7 @@ namespace WixToolset.Core break; case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "foreach", "endforeach")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); case "pragma": this.PreprocessPragma(reader.Value, currentContainer); @@ -619,31 +479,33 @@ namespace WixToolset.Core break; case XmlNodeType.Element: - if (0 < this.includeNextStack.Count && this.includeNextStack.Peek()) + if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek()) { if ("Include" != reader.LocalName) { - this.core.OnMessage(WixErrors.InvalidDocumentElement(this.currentLineNumber, reader.Name, "include", "Include")); + this.Context.Messaging.OnMessage(WixErrors.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); } - this.includeNextStack.Pop(); - this.includeNextStack.Push(false); + this.IncludeNextStack.Pop(); + this.IncludeNextStack.Push(false); break; } - bool empty = reader.IsEmptyElement; - XNamespace ns = XNamespace.Get(reader.NamespaceURI); - XElement element = new XElement(ns + reader.LocalName); + var empty = reader.IsEmptyElement; + var ns = XNamespace.Get(reader.NamespaceURI); + var element = new XElement(ns + reader.LocalName); currentContainer.Add(element); this.UpdateCurrentLineNumber(reader, offset); - element.AddAnnotation(this.currentLineNumber); + element.AddAnnotation(sourceLineNumbers); while (reader.MoveToNextAttribute()) { - string value = this.core.PreprocessString(this.currentLineNumber, reader.Value); - XNamespace attribNamespace = XNamespace.Get(reader.NamespaceURI); + var value = this.Helper.PreprocessString(this.Context, reader.Value); + + var attribNamespace = XNamespace.Get(reader.NamespaceURI); attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; + element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); } @@ -662,12 +524,12 @@ namespace WixToolset.Core break; case XmlNodeType.Text: - string postprocessedText = this.core.PreprocessString(this.currentLineNumber, reader.Value); + string postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value); currentContainer.Add(postprocessedText); break; case XmlNodeType.CDATA: - string postprocessedValue = this.core.PreprocessString(this.currentLineNumber, reader.Value); + string postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value); currentContainer.Add(new XCData(postprocessedValue)); break; @@ -678,13 +540,13 @@ namespace WixToolset.Core if (0 != ifStack.Count) { - throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "if", "endif")); + throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "if", "endif")); } // TODO: can this actually happen? if (0 != containerStack.Count) { - throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "nodes", "nodes")); + throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "nodes", "nodes")); } } @@ -694,12 +556,10 @@ namespace WixToolset.Core /// Text from source. private void PreprocessError(string errorMessage) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; - - // resolve other variables in the error message - errorMessage = this.core.PreprocessString(sourceLineNumbers, errorMessage); + // Resolve other variables in the error message. + errorMessage = this.Helper.PreprocessString(this.Context, errorMessage); - throw new WixException(WixErrors.PreprocessorError(sourceLineNumbers, errorMessage)); + throw new WixException(WixErrors.PreprocessorError(this.Context.CurrentSourceLineNumber, errorMessage)); } /// @@ -708,12 +568,10 @@ namespace WixToolset.Core /// Text from source. private void PreprocessWarning(string warningMessage) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; - - // resolve other variables in the warning message - warningMessage = this.core.PreprocessString(sourceLineNumbers, warningMessage); + // Resolve other variables in the warning message. + warningMessage = this.Helper.PreprocessString(this.Context, warningMessage); - this.core.OnMessage(WixWarnings.PreprocessorWarning(sourceLineNumbers, warningMessage)); + this.Context.Messaging.OnMessage(WixWarnings.PreprocessorWarning(this.Context.CurrentSourceLineNumber, warningMessage)); } /// @@ -722,16 +580,15 @@ namespace WixToolset.Core /// Text from source. private void PreprocessDefine(string originalDefine) { - Match match = defineRegex.Match(originalDefine); - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var match = defineRegex.Match(originalDefine); if (!match.Success) { - throw new WixException(WixErrors.IllegalDefineStatement(sourceLineNumbers, originalDefine)); + throw new WixException(WixErrors.IllegalDefineStatement(this.Context.CurrentSourceLineNumber, originalDefine)); } - string defineName = match.Groups["varName"].Value; - string defineValue = match.Groups["varValue"].Value; + var defineName = match.Groups["varName"].Value; + var defineValue = match.Groups["varValue"].Value; // strip off the optional quotes if (1 < defineValue.Length && @@ -742,15 +599,15 @@ namespace WixToolset.Core } // resolve other variables in the variable value - defineValue = this.core.PreprocessString(sourceLineNumbers, defineValue); + defineValue = this.Helper.PreprocessString(this.Context, defineValue); if (defineName.StartsWith("var.", StringComparison.Ordinal)) { - this.core.AddVariable(sourceLineNumbers, defineName.Substring(4), defineValue); + this.Helper.AddVariable(this.Context, defineName.Substring(4), defineValue); } else { - this.core.AddVariable(sourceLineNumbers, defineName, defineValue); + this.Helper.AddVariable(this.Context, defineName, defineValue); } } @@ -760,16 +617,15 @@ namespace WixToolset.Core /// Text from source. private void PreprocessUndef(string originalDefine) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; - string name = this.core.PreprocessString(sourceLineNumbers, originalDefine.Trim()); + var name = this.Helper.PreprocessString(this.Context, originalDefine.Trim()); if (name.StartsWith("var.", StringComparison.Ordinal)) { - this.core.RemoveVariable(sourceLineNumbers, name.Substring(4)); + this.Helper.RemoveVariable(this.Context, name.Substring(4)); } else { - this.core.RemoveVariable(sourceLineNumbers, name); + this.Helper.RemoveVariable(this.Context, name); } } @@ -780,12 +636,12 @@ namespace WixToolset.Core /// Parent container for included content. private void PreprocessInclude(string includePath, XContainer parent) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var sourceLineNumbers = this.Context.CurrentSourceLineNumber; - // preprocess variables in the path - includePath = this.core.PreprocessString(sourceLineNumbers, includePath); + // Preprocess variables in the path. + includePath = this.Helper.PreprocessString(this.Context, includePath); - string includeFile = this.GetIncludeFile(includePath); + var includeFile = this.GetIncludeFile(includePath); if (null == includeFile) { @@ -807,7 +663,7 @@ namespace WixToolset.Core throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message)); } - this.OnIncludedFile(new IncludedFileEventArgs(sourceLineNumbers, includeFile)); + this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); this.PopInclude(); } @@ -821,11 +677,11 @@ namespace WixToolset.Core /// Offset for the line numbers. private void PreprocessForeach(XmlReader reader, XContainer container, int offset) { - // find the "in" token - int indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); + // Find the "in" token. + var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); if (0 > indexOfInToken) { - throw new WixException(WixErrors.IllegalForeach(this.currentLineNumber, reader.Value)); + throw new WixException(WixErrors.IllegalForeach(this.Context.CurrentSourceLineNumber, reader.Value)); } // parse out the variable name @@ -833,7 +689,7 @@ namespace WixToolset.Core string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); // preprocess the variable values string because it might be a variable itself - varValuesString = this.core.PreprocessString(this.currentLineNumber, varValuesString); + varValuesString = this.Helper.PreprocessString(this.Context, varValuesString); string[] varValues = varValuesString.Split(';'); @@ -895,20 +751,20 @@ namespace WixToolset.Core } else if (reader.NodeType == XmlNodeType.None) { - throw new WixException(WixErrors.ExpectedEndforeach(this.currentLineNumber)); + throw new WixException(WixErrors.ExpectedEndforeach(this.Context.CurrentSourceLineNumber)); } reader.Read(); } - using (MemoryStream fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) - using (XmlReader loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) + using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) + using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) { // process each iteration, updating the variable's value each time foreach (string varValue in varValues) { // Always overwrite foreach variables. - this.core.AddVariable(this.currentLineNumber, varName, varValue, false); + this.Helper.AddVariable(this.Context, varName, varValue, false); try { @@ -917,7 +773,7 @@ namespace WixToolset.Core catch (XmlException e) { this.UpdateCurrentLineNumber(loopReader, offset); - throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message)); + throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); } fragmentStream.Position = 0; // seek back to the beginning for the next loop. @@ -931,24 +787,23 @@ namespace WixToolset.Core /// Text from source. private void PreprocessPragma(string pragmaText, XContainer parent) { - Match match = pragmaRegex.Match(pragmaText); - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var match = pragmaRegex.Match(pragmaText); if (!match.Success) { - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaText)); + throw new WixException(WixErrors.InvalidPreprocessorPragma(this.Context.CurrentSourceLineNumber, pragmaText)); } // resolve other variables in the pragma argument(s) - string pragmaArgs = this.core.PreprocessString(sourceLineNumbers, match.Groups["pragmaValue"].Value).Trim(); + string pragmaArgs = this.Helper.PreprocessString(this.Context, match.Groups["pragmaValue"].Value).Trim(); try { - this.core.PreprocessPragma(sourceLineNumbers, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); + this.Helper.PreprocessPragma(this.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); } catch (Exception e) { - throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(sourceLineNumbers, pragmaText, e.Message)); + throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(this.Context.CurrentSourceLineNumber, pragmaText, e.Message)); } } @@ -975,11 +830,11 @@ namespace WixToolset.Core int endingQuotes = expression.IndexOf('\"', 1); if (-1 == endingQuotes) { - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } // cut the quotes off the string - token = this.core.PreprocessString(this.currentLineNumber, expression.Substring(1, endingQuotes - 1)); + token = this.Helper.PreprocessString(this.Context, expression.Substring(1, endingQuotes - 1)); // advance past this string expression = expression.Substring(endingQuotes + 1).Trim(); @@ -1009,7 +864,7 @@ namespace WixToolset.Core if (-1 == endingParen) { - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } token = expression.Substring(0, endingParen + 1); @@ -1115,7 +970,7 @@ namespace WixToolset.Core { try { - varValue = this.core.PreprocessString(this.currentLineNumber, variable); + varValue = this.Helper.PreprocessString(this.Context, variable); } catch (ArgumentNullException) { @@ -1126,12 +981,12 @@ namespace WixToolset.Core else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) { // make sure it doesn't contain parenthesis - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) { // shouldn't contain quotes - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } return varValue; @@ -1162,7 +1017,7 @@ namespace WixToolset.Core { if (stringLiteral) { - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral); @@ -1213,7 +1068,7 @@ namespace WixToolset.Core { if (operation.Length > 0) { - throw new WixException(WixErrors.ExpectedVariable(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.ExpectedVariable(this.Context.CurrentSourceLineNumber, originalExpression)); } // false expression @@ -1228,7 +1083,7 @@ namespace WixToolset.Core } else { - throw new WixException(WixErrors.UnexpectedLiteral(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnexpectedLiteral(this.Context.CurrentSourceLineNumber, originalExpression)); } } else @@ -1268,11 +1123,11 @@ namespace WixToolset.Core } catch (FormatException) { - throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } catch (OverflowException) { - throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } // Compare the numbers @@ -1314,7 +1169,7 @@ namespace WixToolset.Core closeParenIndex = expression.IndexOf(')', closeParenIndex); if (closeParenIndex == -1) { - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } if (InsideQuotes(expression, closeParenIndex)) @@ -1363,7 +1218,7 @@ namespace WixToolset.Core currentValue = !currentValue; break; default: - throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.currentLineNumber, operation.ToString())); + throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString())); } } @@ -1412,7 +1267,7 @@ namespace WixToolset.Core expression = expression.Trim(); if (expression.Length == 0) { - throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.Context.CurrentSourceLineNumber, originalExpression)); } // If the expression starts with parenthesis, evaluate it @@ -1433,7 +1288,7 @@ namespace WixToolset.Core expression = expression.Substring(3).Trim(); if (expression.Length == 0) { - throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.Context.CurrentSourceLineNumber, originalExpression)); } expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true); @@ -1462,7 +1317,7 @@ namespace WixToolset.Core } else { - throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression)); + throw new WixException(WixErrors.InvalidSubExpression(this.Context.CurrentSourceLineNumber, expression, originalExpression)); } } @@ -1481,9 +1336,9 @@ namespace WixToolset.Core { int newLine = lineInfoReader.LineNumber + offset; - if (this.currentLineNumber.LineNumber != newLine) + if (this.Context.CurrentSourceLineNumber.LineNumber != newLine) { - this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine); + this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.CurrentSourceLineNumber.FileName, newLine); } } } @@ -1494,15 +1349,15 @@ namespace WixToolset.Core /// Name to push on to the stack of included files. private void PushInclude(string fileName) { - if (1023 < this.currentFileStack.Count) + if (1023 < this.CurrentFileStack.Count) { - throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count)); + throw new WixException(WixErrors.TooDeeplyIncluded(this.Context.CurrentSourceLineNumber, this.CurrentFileStack.Count)); } - this.currentFileStack.Push(fileName); - this.sourceStack.Push(this.currentLineNumber); - this.currentLineNumber = new SourceLineNumber(fileName); - this.includeNextStack.Push(true); + this.CurrentFileStack.Push(fileName); + this.SourceStack.Push(this.Context.CurrentSourceLineNumber); + this.Context.CurrentSourceLineNumber = new SourceLineNumber(fileName); + this.IncludeNextStack.Push(true); } /// @@ -1510,10 +1365,10 @@ namespace WixToolset.Core /// private void PopInclude() { - this.currentLineNumber = this.sourceStack.Pop(); + this.Context.CurrentSourceLineNumber = this.SourceStack.Pop(); - this.currentFileStack.Pop(); - this.includeNextStack.Pop(); + this.CurrentFileStack.Pop(); + this.IncludeNextStack.Pop(); } /// @@ -1548,8 +1403,8 @@ namespace WixToolset.Core else // relative path { // build a string to test the directory containing the source file first - string currentFolder = this.currentFileStack.Peek(); - string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath); + var currentFolder = this.CurrentFileStack.Peek(); + var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) , includePath); // test the source file directory if (File.Exists(includeTestPath)) @@ -1558,7 +1413,7 @@ namespace WixToolset.Core } else // test all search paths in the order specified on the command line { - foreach (string includeSearchPath in this.IncludeSearchPaths) + foreach (var includeSearchPath in this.Context.IncludeSearchPaths) { // if the path exists, we have found the final string 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 @@ -// 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. - -namespace WixToolset -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - /// - /// The preprocessor core. - /// - internal class PreprocessorCore : IPreprocessorCore - { - private static readonly char[] variableSplitter = new char[] { '.' }; - private static readonly char[] argumentSplitter = new char[] { ',' }; - - private Platform currentPlatform; - private Dictionary extensionsByPrefix; - private string sourceFile; - private IDictionary variables; - - /// - /// Instantiate a new PreprocessorCore. - /// - /// The extensions indexed by their prefixes. - /// The message handler. - /// The source file being preprocessed. - /// The variables defined prior to preprocessing. - internal PreprocessorCore(Dictionary extensionsByPrefix, string sourceFile, IDictionary variables) - { - this.extensionsByPrefix = extensionsByPrefix; - this.sourceFile = String.IsNullOrEmpty(sourceFile) ? null : Path.GetFullPath(sourceFile); - - this.variables = new Dictionary(); - foreach (var entry in variables) - { - this.AddVariable(null, entry.Key, entry.Value); - } - } - - /// - /// Event for resolved variables. - /// - private event ResolvedVariableEventHandler ResolvedVariable; - - /// - /// Sets event for ResolvedVariableEventHandler. - /// - public ResolvedVariableEventHandler ResolvedVariableHandler - { - set { this.ResolvedVariable = value; } - } - - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public Platform CurrentPlatform - { - get { return this.currentPlatform; } - set { this.currentPlatform = value; } - } - - /// - /// Gets whether the core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError - { - get { return Messaging.Instance.EncounteredError; } - } - - /// - /// Replaces parameters in the source text. - /// - /// The source line information for the function. - /// Text that may contain parameters to replace. - /// Text after parameters have been replaced. - public string PreprocessString(SourceLineNumber sourceLineNumbers, string value) - { - StringBuilder sb = new StringBuilder(); - int currentPosition = 0; - int end = 0; - - while (-1 != (currentPosition = value.IndexOf('$', end))) - { - if (end < currentPosition) - { - sb.Append(value, end, currentPosition - end); - } - - end = currentPosition + 1; - string remainder = value.Substring(end); - if (remainder.StartsWith("$", StringComparison.Ordinal)) - { - sb.Append("$"); - end++; - } - else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) - { - currentPosition = remainder.IndexOf(')'); - if (-1 == currentPosition) - { - this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder)); - break; - } - - sb.Append("$"); // just put the resource reference back as was - sb.Append(remainder, 0, currentPosition + 1); - - end += currentPosition + 1; - } - else if (remainder.StartsWith("(", StringComparison.Ordinal)) - { - int openParenCount = 1; - int closingParenCount = 0; - bool isFunction = false; - bool foundClosingParen = false; - - // find the closing paren - int closingParenPosition; - for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) - { - switch (remainder[closingParenPosition]) - { - case '(': - openParenCount++; - isFunction = true; - break; - case ')': - closingParenCount++; - break; - } - if (openParenCount == closingParenCount) - { - foundClosingParen = true; - break; - } - } - - // move the currentPosition to the closing paren - currentPosition += closingParenPosition; - - if (!foundClosingParen) - { - if (isFunction) - { - this.OnMessage(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, remainder)); - break; - } - else - { - this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder)); - break; - } - } - - string subString = remainder.Substring(1, closingParenPosition - 1); - string result = null; - if (isFunction) - { - result = this.EvaluateFunction(sourceLineNumbers, subString); - } - else - { - result = this.GetVariableValue(sourceLineNumbers, subString, false); - } - - if (null == result) - { - if (isFunction) - { - this.OnMessage(WixErrors.UndefinedPreprocessorFunction(sourceLineNumbers, subString)); - break; - } - else - { - this.OnMessage(WixErrors.UndefinedPreprocessorVariable(sourceLineNumbers, subString)); - break; - } - } - else - { - if (!isFunction) - { - this.OnResolvedVariable(new ResolvedVariableEventArgs(sourceLineNumbers, subString, result)); - } - } - sb.Append(result); - end += closingParenPosition + 1; - } - else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing - { - sb.Append('$'); - } - } - - if (end < value.Length) - { - sb.Append(value.Substring(end)); - } - - return sb.ToString(); - } - - /// - /// Evaluate a Pragma. - /// - /// The source line information for the function. - /// The pragma's full name (.). - /// The arguments to the pragma. - /// The parent element of the pragma. - public void PreprocessPragma(SourceLineNumber sourceLineNumbers, string pragmaName, string args, XContainer parent) - { - string[] prefixParts = pragmaName.Split(variableSplitter, 2); - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length) - { - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName)); - } - string prefix = prefixParts[0]; - string pragma = prefixParts[1]; - - if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) - { - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName)); - } - - switch (prefix) - { - case "wix": - switch (pragma) - { - // Add any core defined pragmas here - default: - this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName)); - break; - } - break; - default: - PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix]; - if (null == extension || !extension.ProcessPragma(sourceLineNumbers, prefix, pragma, args, parent)) - { - this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName)); - } - break; - } - } - - /// - /// Evaluate a function. - /// - /// The source line information for the function. - /// The function expression including the prefix and name. - /// The function value. - public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string function) - { - string[] prefixParts = function.Split(variableSplitter, 2); - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) - { - throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function)); - } - string prefix = prefixParts[0]; - - string[] functionParts = prefixParts[1].Split(new char[] { '(' }, 2); - // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. - if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) - { - throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function)); - } - string functionName = functionParts[0]; - - // Remove the trailing closing paren. - string allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); - - // Parse the arguments and preprocess them. - string[] args = allArgs.Split(argumentSplitter); - for (int i = 0; i < args.Length; i++) - { - args[i] = this.PreprocessString(sourceLineNumbers, args[i].Trim()); - } - - string result = this.EvaluateFunction(sourceLineNumbers, prefix, functionName, args); - - // If the function didn't evaluate, try to evaluate the original value as a variable to support - // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. - if (null == result) - { - result = this.GetVariableValue(sourceLineNumbers, function, false); - } - - return result; - } - - /// - /// Evaluate a function. - /// - /// The source line information for the function. - /// The function prefix. - /// The function name. - /// The arguments for the function. - /// The function value or null if the function is not defined. - public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string prefix, string function, string[] args) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(function)) - { - throw new ArgumentNullException("function"); - } - - switch (prefix) - { - case "fun": - switch (function) - { - case "AutoVersion": - // Make sure the base version is specified - if (args.Length == 0 || String.IsNullOrEmpty(args[0])) - { - throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(sourceLineNumbers)); - } - - // Build = days since 1/1/2000; Revision = seconds since midnight / 2 - DateTime now = DateTime.UtcNow; - TimeSpan build = now - new DateTime(2000, 1, 1); - TimeSpan revision = now - new DateTime(now.Year, now.Month, now.Day); - - return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); - - default: - return null; - } - default: - PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix]; - if (null != extension) - { - try - { - return extension.EvaluateFunction(prefix, function, args); - } - catch (Exception e) - { - throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(sourceLineNumbers, prefix, function, String.Join(",", args), e.Message)); - } - } - else - { - return null; - } - } - } - - /// - /// Get the value of a variable expression like var.name. - /// - /// The source line information for the variable. - /// The variable expression including the optional prefix and name. - /// true to allow the variable prefix to be missing. - /// The variable value. - public string GetVariableValue(SourceLineNumber sourceLineNumbers, string variable, bool allowMissingPrefix) - { - // Strip the "$(" off the front. - if (variable.StartsWith("$(", StringComparison.Ordinal)) - { - variable = variable.Substring(2); - } - - string[] parts = variable.Split(variableSplitter, 2); - - if (1 == parts.Length) // missing prefix - { - if (allowMissingPrefix) - { - return this.GetVariableValue(sourceLineNumbers, "var", parts[0]); - } - else - { - throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable)); - } - } - else - { - // check for empty variable name - if (0 < parts[1].Length) - { - string result = this.GetVariableValue(sourceLineNumbers, parts[0], parts[1]); - - // 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 - if (null == result && allowMissingPrefix && variable.Contains(".")) - { - result = this.GetVariableValue(sourceLineNumbers, "var", variable); - } - - return result; - } - else - { - throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable)); - } - } - } - - /// - /// Get the value of a variable. - /// - /// The source line information for the function. - /// The variable prefix. - /// The variable name. - /// The variable value or null if the variable is not set. - public string GetVariableValue(SourceLineNumber sourceLineNumbers, string prefix, string name) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(name)) - { - throw new ArgumentNullException("name"); - } - - switch (prefix) - { - case "env": - return Environment.GetEnvironmentVariable(name); - case "sys": - switch (name) - { - case "CURRENTDIR": - return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); - case "SOURCEFILEDIR": - return String.Concat(Path.GetDirectoryName(sourceLineNumbers.FileName), Path.DirectorySeparatorChar); - case "SOURCEFILEPATH": - return sourceLineNumbers.FileName; - case "PLATFORM": - this.OnMessage(WixWarnings.DeprecatedPreProcVariable(sourceLineNumbers, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); - - goto case "BUILDARCH"; - - case "BUILDARCH": - switch (this.currentPlatform) - { - case Platform.X86: - return "x86"; - case Platform.X64: - return "x64"; - case Platform.IA64: - return "ia64"; - case Platform.ARM: - return "arm"; - default: - throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.currentPlatform.ToString()); - } - default: - return null; - } - case "var": - string result = null; - return this.variables.TryGetValue(name, out result) ? result : null; - default: - PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix]; - if (null != extension) - { - try - { - return extension.GetVariableValue(prefix, name); - } - catch (Exception e) - { - throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(sourceLineNumbers, prefix, name, e.Message)); - } - } - else - { - return null; - } - } - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } - - /// - /// Sends resolved variable to delegate if there is one. - /// - /// Message event arguments. - public void OnResolvedVariable(ResolvedVariableEventArgs mea) - { - if (null != this.ResolvedVariable) - { - this.ResolvedVariable(this, mea); - } - } - - /// - /// Add a variable. - /// - /// The source line information of the variable. - /// The variable name. - /// The variable value. - internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value) - { - this.AddVariable(sourceLineNumbers, name, value, true); - } - - /// - /// Add a variable. - /// - /// The source line information of the variable. - /// The variable name. - /// The variable value. - /// Set to true to show variable overwrite warning. - internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value, bool showWarning) - { - string currentValue = this.GetVariableValue(sourceLineNumbers, "var", name); - - if (null == currentValue) - { - this.variables.Add(name, value); - } - else - { - if (showWarning) - { - this.OnMessage(WixWarnings.VariableDeclarationCollision(sourceLineNumbers, name, value, currentValue)); - } - - this.variables[name] = value; - } - } - - /// - /// Remove a variable. - /// - /// The source line information of the variable. - /// The variable name. - internal void RemoveVariable(SourceLineNumber sourceLineNumbers, string name) - { - if (!this.variables.Remove(name)) - { - this.OnMessage(WixErrors.CannotReundefineVariable(sourceLineNumbers, name)); - } - } - } -} 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 { private ExtensionManager extensionManager; private ParseHelper parseHelper; + private PreprocessHelper preprocessHelper; private TupleDefinitionCreator tupleDefinitionCreator; public object GetService(Type serviceType) @@ -19,6 +20,11 @@ namespace WixToolset.Core if (serviceType == null) throw new ArgumentNullException(nameof(serviceType)); // Transients. + if (serviceType == typeof(IPreprocessContext)) + { + return new PreprocessContext(this); + } + if (serviceType == typeof(ICompileContext)) { return new CompileContext(this); @@ -65,6 +71,11 @@ namespace WixToolset.Core return this.parseHelper = this.parseHelper ?? new ParseHelper(this); } + if (serviceType == typeof(IPreprocessHelper)) + { + return this.preprocessHelper = this.preprocessHelper ?? new PreprocessHelper(this); + } + throw new ArgumentException($"Unknown service type: {serviceType.Name}", nameof(serviceType)); } } 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 public class ExampleExtensionFactory : IExtensionFactory { + private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; + public bool TryCreateExtension(Type extensionType, out object extension) { - if (extensionType == typeof(IPreprocessorExtension)) + if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) { - extension = new ExamplePreprocessorExtension(); + if (preprocessorExtension == null) + { + preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); + } + + extension = preprocessorExtension; } else if (extensionType == typeof(ICompilerExtension)) { 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 @@ -// 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. - -namespace Example.Extension -{ - using System; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExamplePreprocessorExtension : IPreprocessorExtension - { - public ExamplePreprocessorExtension() - { - } - - public IPreprocessorCore Core { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public string[] Prefixes => throw new NotImplementedException(); - - public string EvaluateFunction(string prefix, string function, string[] args) - { - throw new NotImplementedException(); - } - - public void Finish() - { - throw new NotImplementedException(); - } - - public string GetVariableValue(string prefix, string name) - { - throw new NotImplementedException(); - } - - public void Initialize() - { - throw new NotImplementedException(); - } - - public void PreprocessDocument(XDocument document) - { - throw new NotImplementedException(); - } - - public string PreprocessParameter(string name) - { - throw new NotImplementedException(); - } - - public bool ProcessPragma(SourceLineNumber sourceLineNumbers, string prefix, string pragma, string args, XContainer parent) - { - throw new NotImplementedException(); - } - } -} \ 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 @@ +// 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. + +namespace Example.Extension +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine + { + private string exampleValueFromCommandLine; + + public IEnumerable CommandLineSwitches => throw new NotImplementedException(); + + public ExamplePreprocessorExtensionAndCommandLine() + { + this.Prefixes = new[] { "ex" }; + } + + public void PreParse(ICommandLineContext context) + { + } + + public bool TryParseArgument(IParseCommandLine parseCommandLine, string arg) + { + if (parseCommandLine.IsSwitch(arg) && arg.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) + { + parseCommandLine.GetNextArgumentOrError(ref this.exampleValueFromCommandLine); + return true; + } + + return false; + } + + public override string GetVariableValue(string prefix, string name) + { + if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; + } + + return null; + } + } +} \ 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 Assert.Equal("Bar", example[1].AsString()); } } + + [Fact] + public void CanParseCommandLineWithExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-example", "test", + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + + var property = section.Tuples.OfType().Where(p => p.Id.Id == "ExampleProperty").Single(); + Assert.Equal("ExampleProperty", property.Property); + Assert.Equal("test", property.Value); + } + } } } 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 @@ + + -- cgit v1.2.3-55-g6feb