From a5c63c90a02665267c11c8bf2c653fd6db8d0107 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 24 Oct 2018 21:02:24 -0700 Subject: Update to command-line parsing re-organization --- src/WixToolset.Core/CommandLine/BuildCommand.cs | 478 +++++++++++++++---- src/WixToolset.Core/CommandLine/CommandLine.cs | 210 +++++++++ .../CommandLine/CommandLineArguments.cs | 4 +- .../CommandLine/CommandLineParser.cs | 514 +++++++-------------- src/WixToolset.Core/CommandLine/CompileCommand.cs | 18 +- src/WixToolset.Core/CommandLine/HelpCommand.cs | 22 +- .../CommandLine/ParseCommandLine.cs | 263 ----------- src/WixToolset.Core/CommandLine/VersionCommand.cs | 10 + src/WixToolset.Core/WixToolsetServiceProvider.cs | 4 +- 9 files changed, 813 insertions(+), 710 deletions(-) create mode 100644 src/WixToolset.Core/CommandLine/CommandLine.cs delete mode 100644 src/WixToolset.Core/CommandLine/ParseCommandLine.cs (limited to 'src/WixToolset.Core') diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 6052d979..87a3cd30 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -1,4 +1,4 @@ -// 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. +// 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.CommandLine { @@ -14,83 +14,84 @@ namespace WixToolset.Core.CommandLine internal class BuildCommand : ICommandLineCommand { - public BuildCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, IEnumerable locFiles, IEnumerable libraryFiles, IEnumerable filterCultures, string outputPath, OutputType outputType, Platform platform, string cabCachePath, bool bindFiles, IEnumerable bindPaths, IEnumerable includeSearchPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile) + private readonly CommandLine commandLine; + + public BuildCommand(IServiceProvider serviceProvider) { this.ServiceProvider = serviceProvider; this.Messaging = serviceProvider.GetService(); this.ExtensionManager = serviceProvider.GetService(); - this.LocFiles = locFiles; - this.LibraryFiles = libraryFiles; - this.FilterCultures = filterCultures; - this.PreprocessorVariables = preprocessorVariables; - this.SourceFiles = sources; - this.OutputPath = outputPath; - this.OutputType = outputType; - this.Platform = platform; - - this.CabCachePath = cabCachePath; - this.BindFiles = bindFiles; - this.BindPaths = bindPaths; - this.IncludeSearchPaths = includeSearchPaths; - - this.IntermediateFolder = intermediateFolder ?? Path.GetTempPath(); - this.ContentsFile = contentsFile; - this.OutputsFile = outputsFile; - this.BuiltOutputsFile = builtOutputsFile; + this.commandLine = new CommandLine(this.Messaging); } - public IServiceProvider ServiceProvider { get; } + public bool ShowLogo => this.commandLine.ShowLogo; - public IMessaging Messaging { get; } + public bool StopParsing => this.commandLine.ShowHelp; - public IExtensionManager ExtensionManager { get; } + private IServiceProvider ServiceProvider { get; } - public IEnumerable FilterCultures { get; } + private IMessaging Messaging { get; } - public IEnumerable IncludeSearchPaths { get; } + private IExtensionManager ExtensionManager { get; } - public IEnumerable LocFiles { get; } + private string IntermediateFolder { get; set; } - public IEnumerable LibraryFiles { get; } + private OutputType OutputType { get; set; } - private IEnumerable SourceFiles { get; } + private List IncludeSearchPaths { get; set; } - private IDictionary PreprocessorVariables { get; } + private Platform Platform { get; set; } - private string OutputPath { get; } + private string OutputFile { get; set; } - private OutputType OutputType { get; } + private string ContentsFile { get; set; } - private Platform Platform { get; } + private string OutputsFile { get; set; } - public string CabCachePath { get; } + private string BuiltOutputsFile { get; set; } - public bool BindFiles { get; } + public int Execute() + { + if (this.commandLine.ShowHelp) + { + Console.WriteLine("TODO: Show build command help"); + return -1; + } - public IEnumerable BindPaths { get; } + this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); - public string IntermediateFolder { get; } + this.OutputType = this.commandLine.CalculateOutputType(); - public string ContentsFile { get; } + this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths; - public string OutputsFile { get; } + this.Platform = this.commandLine.Platform; - public string BuiltOutputsFile { get; } + this.OutputFile = this.commandLine.OutputFile; + + this.ContentsFile = this.commandLine.ContentsFile; + + this.OutputsFile = this.commandLine.OutputsFile; + + this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; + + var preprocessorVariables = this.commandLine.GatherPreprocessorVariables(); + + var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder); + + var filterCultures = this.commandLine.CalculateFilterCultures(); - public int Execute() - { var creator = this.ServiceProvider.GetService(); - this.EvaluateSourceFiles(creator, out var codeFiles, out var wixipl); + this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl); if (this.Messaging.EncounteredError) { return this.Messaging.LastErrorNumber; } - var wixobjs = this.CompilePhase(codeFiles); + var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles); - var wxls = this.LoadLocalizationFiles().ToList(); + var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables); if (this.Messaging.EncounteredError) { @@ -99,29 +100,29 @@ namespace WixToolset.Core.CommandLine if (this.OutputType == OutputType.Library) { - var wixlib = this.LibraryPhase(wixobjs, wxls); + var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths); if (!this.Messaging.EncounteredError) { - wixlib.Save(this.OutputPath); + wixlib.Save(this.commandLine.OutputFile); } } else { if (wixipl == null) { - wixipl = this.LinkPhase(wixobjs, creator); + wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator); } if (!this.Messaging.EncounteredError) { if (this.OutputType == OutputType.IntermediatePostLink) { - wixipl.Save(this.OutputPath); + wixipl.Save(this.commandLine.OutputFile); } else { - this.BindPhase(wixipl, wxls); + this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths); } } } @@ -129,13 +130,18 @@ namespace WixToolset.Core.CommandLine return this.Messaging.LastErrorNumber; } - private void EvaluateSourceFiles(ITupleDefinitionCreator creator, out List codeFiles, out Intermediate wixipl) + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + return this.commandLine.TryParseArgument(argument, parser); + } + + private void EvaluateSourceFiles(IEnumerable sourceFiles, ITupleDefinitionCreator creator, out List codeFiles, out Intermediate wixipl) { codeFiles = new List(); wixipl = null; - foreach (var sourceFile in this.SourceFiles) + foreach (var sourceFile in sourceFiles) { var extension = Path.GetExtension(sourceFile.SourcePath); @@ -167,13 +173,13 @@ namespace WixToolset.Core.CommandLine } } - private IEnumerable CompilePhase(IEnumerable sourceFiles) + private IEnumerable CompilePhase(IDictionary preprocessorVariables, IEnumerable sourceFiles) { var intermediates = new List(); foreach (var sourceFile in sourceFiles) { - var document = this.Preprocess(sourceFile.SourcePath); + var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath); if (this.Messaging.EncounteredError) { @@ -208,11 +214,11 @@ namespace WixToolset.Core.CommandLine return intermediates; } - private Intermediate LibraryPhase(IEnumerable intermediates, IEnumerable localizations) + private Intermediate LibraryPhase(IEnumerable intermediates, IEnumerable localizations, bool bindFiles, IEnumerable bindPaths) { var context = this.ServiceProvider.GetService(); - context.BindFiles = this.BindFiles; - context.BindPaths = this.BindPaths; + context.BindFiles = bindFiles; + context.BindPaths = bindPaths; context.Extensions = this.ExtensionManager.Create(); context.Localizations = localizations; context.Intermediates = intermediates; @@ -230,10 +236,10 @@ namespace WixToolset.Core.CommandLine return library; } - - private Intermediate LinkPhase(IEnumerable intermediates, ITupleDefinitionCreator creator) + + private Intermediate LinkPhase(IEnumerable intermediates, IEnumerable libraryFiles, ITupleDefinitionCreator creator) { - var libraries = this.LoadLibraries(creator); + var libraries = this.LoadLibraries(libraryFiles, creator); if (this.Messaging.EncounteredError) { @@ -251,7 +257,7 @@ namespace WixToolset.Core.CommandLine return linker.Link(context); } - private void BindPhase(Intermediate output, IEnumerable localizations) + private void BindPhase(Intermediate output, IEnumerable localizations, IEnumerable filterCultures, string cabCachePath, IEnumerable bindPaths) { var intermediateFolder = this.IntermediateFolder; if (String.IsNullOrEmpty(intermediateFolder)) @@ -262,10 +268,10 @@ namespace WixToolset.Core.CommandLine ResolveResult resolveResult; { var context = this.ServiceProvider.GetService(); - context.BindPaths = this.BindPaths; + context.BindPaths = bindPaths; context.Extensions = this.ExtensionManager.Create(); context.ExtensionData = this.ExtensionManager.Create(); - context.FilterCultures = this.FilterCultures; + context.FilterCultures = filterCultures; context.IntermediateFolder = intermediateFolder; context.IntermediateRepresentation = output; context.Localizations = localizations; @@ -284,7 +290,7 @@ namespace WixToolset.Core.CommandLine { var context = this.ServiceProvider.GetService(); //context.CabbingThreadCount = this.CabbingThreadCount; - context.CabCachePath = this.CabCachePath; + context.CabCachePath = cabCachePath; context.Codepage = resolveResult.Codepage; //context.DefaultCompressionLevel = this.DefaultCompressionLevel; context.DelayedFields = resolveResult.DelayedFields; @@ -293,8 +299,8 @@ namespace WixToolset.Core.CommandLine context.Ices = Array.Empty(); // TODO: set this correctly context.IntermediateFolder = intermediateFolder; context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; - context.OutputPath = this.OutputPath; - context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb"); + context.OutputPath = this.OutputFile; + context.OutputPdbPath = Path.ChangeExtension(this.OutputFile, ".wixpdb"); context.SuppressIces = Array.Empty(); // TODO: set this correctly context.SuppressValidation = true; // TODO: set this correctly @@ -323,41 +329,39 @@ namespace WixToolset.Core.CommandLine } } - private IEnumerable LoadLibraries(ITupleDefinitionCreator creator) + private IEnumerable LoadLibraries(IEnumerable libraryFiles, ITupleDefinitionCreator creator) { var libraries = new List(); - if (this.LibraryFiles != null) + foreach (var libraryFile in libraryFiles) { - foreach (var libraryFile in this.LibraryFiles) + try { - try - { - var library = Intermediate.Load(libraryFile, creator); + var library = Intermediate.Load(libraryFile, creator); - libraries.Add(library); - } - catch (WixCorruptFileException e) - { - this.Messaging.Write(e.Error); - } - catch (WixUnexpectedFileFormatException e) - { - this.Messaging.Write(e.Error); - } + libraries.Add(library); + } + catch (WixCorruptFileException e) + { + this.Messaging.Write(e.Error); + } + catch (WixUnexpectedFileFormatException e) + { + this.Messaging.Write(e.Error); } } return libraries; } - private IEnumerable LoadLocalizationFiles() + private IEnumerable LoadLocalizationFiles(IEnumerable locFiles, IDictionary preprocessorVariables) { - var localizer = new Localizer(this.ServiceProvider); + var localizations = new List(); + var localizer = this.ServiceProvider.GetService(); - foreach (var loc in this.LocFiles) + foreach (var loc in locFiles) { - var document = this.Preprocess(loc); + var document = this.Preprocess(preprocessorVariables, loc); if (this.Messaging.EncounteredError) { @@ -365,18 +369,20 @@ namespace WixToolset.Core.CommandLine } var localization = localizer.ParseLocalizationFile(document); - yield return localization; + localizations.Add(localization); } + + return localizations; } - private XDocument Preprocess(string sourcePath) + private XDocument Preprocess(IDictionary preprocessorVariables, string sourcePath) { var context = this.ServiceProvider.GetService(); context.Extensions = this.ExtensionManager.Create(); context.Platform = this.Platform; context.IncludeSearchPaths = this.IncludeSearchPaths; context.SourcePath = sourcePath; - context.Variables = this.PreprocessorVariables; + context.Variables = preprocessorVariables; XDocument document = null; try @@ -391,5 +397,301 @@ namespace WixToolset.Core.CommandLine return document; } + + private class CommandLine + { + private static readonly char[] BindPathSplit = { '=' }; + + public bool BindFiles { get; private set; } + + public List BindPaths { get; } = new List(); + + public string CabCachePath { get; private set; } + + public List Cultures { get; } = new List(); + + public List Defines { get; } = new List(); + + public List IncludeSearchPaths { get; } = new List(); + + public List LocalizationFilePaths { get; } = new List(); + + public List LibraryFilePaths { get; } = new List(); + + public List SourceFilePaths { get; } = new List(); + + public Platform Platform { get; private set; } + + public bool ShowLogo { get; private set; } + + public bool ShowHelp { get; private set; } + + public string IntermediateFolder { get; private set; } + + public string OutputFile { get; private set; } + + public string OutputType { get; private set; } + + public string ContentsFile { get; private set; } + + public string OutputsFile { get; private set; } + + public string BuiltOutputsFile { get; private set; } + + public CommandLine(IMessaging messaging) + { + this.Messaging = messaging; + } + + private IMessaging Messaging { get; } + + public bool TryParseArgument(string arg, ICommandLineParser parser) + { + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + this.ShowHelp = true; + return true; + + case "arch": + case "platform": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out Platform platform)) + { + this.Platform = platform; + return true; + } + break; + } + + case "bindfiles": + this.BindFiles = true; + return true; + + case "bindpath": + { + var value = parser.GetNextArgumentOrError(arg); + if (this.TryParseBindPath(value, out var bindPath)) + { + this.BindPaths.Add(bindPath); + return true; + } + break; + } + case "cc": + this.CabCachePath = parser.GetNextArgumentOrError(arg); + return true; + + case "culture": + parser.GetNextArgumentOrError(arg, this.Cultures); + return true; + + case "contentsfile": + this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + case "outputsfile": + this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + case "builtoutputsfile": + this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "d": + case "define": + parser.GetNextArgumentOrError(arg, this.Defines); + return true; + + case "i": + case "includepath": + parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); + return true; + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); + return true; + + case "loc": + parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths); + return true; + + case "lib": + parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths); + return true; + + case "o": + case "out": + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "outputtype": + this.OutputType = parser.GetNextArgumentOrError(arg); + return true; + + case "nologo": + this.ShowLogo = false; + return true; + + case "v": + case "verbose": + this.Messaging.ShowVerboseMessages = true; + return true; + + case "sval": + // todo: implement + return true; + + case "sw": + case "suppresswarning": + var warning = parser.GetNextArgumentOrError(arg); + if (!String.IsNullOrEmpty(warning)) + { + var warningNumber = Convert.ToInt32(warning); + this.Messaging.SuppressWarningMessage(warningNumber); + } + return true; + } + + return false; + } + else + { + parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths); + return true; + } + } + + public string CalculateIntermedateFolder() + { + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; + } + + public OutputType CalculateOutputType() + { + if (String.IsNullOrEmpty(this.OutputType)) + { + this.OutputType = Path.GetExtension(this.OutputFile); + } + + switch (this.OutputType.ToLowerInvariant()) + { + case "bundle": + case ".exe": + return Data.OutputType.Bundle; + + case "library": + case ".wixlib": + return Data.OutputType.Library; + + case "module": + case ".msm": + return Data.OutputType.Module; + + case "patch": + case ".msp": + return Data.OutputType.Patch; + + case ".pcp": + return Data.OutputType.PatchCreation; + + case "product": + case "package": + case ".msi": + return Data.OutputType.Product; + + case "transform": + case ".mst": + return Data.OutputType.Transform; + + case "intermediatepostlink": + case ".wixipl": + return Data.OutputType.IntermediatePostLink; + } + + return Data.OutputType.Unknown; + } + + public IEnumerable CalculateFilterCultures() + { + var result = new List(); + + if (this.Cultures == null) + { + } + else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) + { + // When null is used treat it as if cultures wasn't specified. This is + // needed for batching in the MSBuild task since MSBuild doesn't support + // empty items. + } + else + { + foreach (var culture in this.Cultures) + { + // Neutral is different from null. For neutral we still want to do culture filtering. + // Set the culture to the empty string = identifier for the invariant culture. + var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; + result.Add(filter); + } + } + + return result; + } + + public IDictionary GatherPreprocessorVariables() + { + var variables = new Dictionary(); + + foreach (var pair in this.Defines) + { + var value = pair.Split(new[] { '=' }, 2); + + if (variables.ContainsKey(value[0])) + { + this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); + continue; + } + + variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); + } + + return variables; + } + + + public IEnumerable GatherSourceFiles(string intermediateDirectory) + { + var files = new List(); + + foreach (var item in this.SourceFilePaths) + { + var sourcePath = item; + var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); + + files.Add(new SourceFile(sourcePath, outputPath)); + } + + return files; + } + + private bool TryParseBindPath(string bindPath, out BindPath bp) + { + var namedPath = bindPath.Split(BindPathSplit, 2); + bp = (1 == namedPath.Length) ? new BindPath(namedPath[0]) : new BindPath(namedPath[0], namedPath[1]); + + if (File.Exists(bp.Path)) + { + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); + return false; + } + + return true; + } + } } } diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs new file mode 100644 index 00000000..9aefc50a --- /dev/null +++ b/src/WixToolset.Core/CommandLine/CommandLine.cs @@ -0,0 +1,210 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal enum CommandTypes + { + Unknown, + Build, + Preprocess, + Compile, + Link, + Bind, + Decompile, + } + + internal class CommandLine : ICommandLine + { + private static readonly char[] BindPathSplit = { '=' }; + + public CommandLine(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; set; } + + public IExtensionManager ExtensionManager { get; set; } + + public ICommandLineArguments Arguments { get; set; } + + public static string ExpectedArgument { get; } = "expected argument"; + + public bool ShowHelp { get; private set; } + + public ICommandLineCommand ParseStandardCommandLine() + { + var context = this.ServiceProvider.GetService(); + context.ExtensionManager = this.ExtensionManager ?? this.ServiceProvider.GetService(); + context.Arguments = this.Arguments; + + var command = this.Parse(context); + + if (command.ShowLogo) + { + AppCommon.DisplayToolHeader(); + } + + return command; + //switch (commandType) + //{ + //case CommandTypes.Build: + //{ + // var sourceFiles = GatherSourceFiles(files, outputFolder); + // var variables = this.GatherPreprocessorVariables(defines); + // var bindPathList = this.GatherBindPaths(bindPaths); + // var filterCultures = CalculateFilterCultures(cultures); + // var type = CalculateOutputType(outputType, outputFile); + // var platform = CalculatePlatform(platformType); + // return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, filterCultures, outputFile, type, platform, cabCachePath, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile); + //} + + //case CommandTypes.Compile: + //{ + // var sourceFiles = GatherSourceFiles(files, outputFolder); + // var variables = this.GatherPreprocessorVariables(defines); + // var platform = CalculatePlatform(platformType); + // return new CompileCommand(this.ServiceProvider, sourceFiles, variables, platform); + //} + + //case CommandTypes.Decompile: + //{ + // var sourceFiles = GatherSourceFiles(files, outputFolder); + // return new DecompileCommand(this.ServiceProvider, sourceFiles, outputFile); + //} + //} + + //return null; + } + + private ICommandLineCommand Parse(ICommandLineContext context) + { + var extensions = this.ExtensionManager.Create(); + + foreach (var extension in extensions) + { + extension.PreParse(context); + } + + ICommandLineCommand command = null; + var parser = context.Arguments.Parse(); + + while (command?.StopParsing != true && + String.IsNullOrEmpty(parser.ErrorArgument) && + parser.TryGetNextSwitchOrArgument(out var arg)) + { + if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. + { + continue; + } + + // First argument must be the command or global switch (that creates a command). + if (command == null) + { + if (!this.TryParseUnknownCommandArg(arg, parser, out command, extensions)) + { + parser.ErrorArgument = arg; + } + } + else if (parser.IsSwitch(arg)) + { + if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) + { + parser.ErrorArgument = arg; + } + } + else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && command?.TryParseArgument(parser, arg) == false) + { + parser.ErrorArgument = arg; + } + } + + foreach (var extension in extensions) + { + extension.PostParse(); + } + + return command ?? new HelpCommand(); + } + + private bool TryParseUnknownCommandArg(string arg, ICommandLineParser parser, out ICommandLineCommand command, IEnumerable extensions) + { + command = null; + + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + command = new HelpCommand(); + break; + + case "version": + case "-version": + command = new VersionCommand(); + break; + } + } + else + { + if (Enum.TryParse(arg, true, out CommandTypes commandType)) + { + switch (commandType) + { + case CommandTypes.Build: + command = new BuildCommand(this.ServiceProvider); + break; + + case CommandTypes.Compile: + command = new CompileCommand(this.ServiceProvider); + break; + + case CommandTypes.Decompile: + command = new DecompileCommand(this.ServiceProvider); + break; + } + } + else + { + foreach (var extension in extensions) + { + if (extension.TryParseCommand(parser, out command)) + { + break; + } + + command = null; + } + } + } + + return command != null; + } + + private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable extensions) + { + foreach (var extension in extensions) + { + if (extension.TryParseArgument(parse, arg)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs index 2f8226df..5fa547b4 100644 --- a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs +++ b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs @@ -41,11 +41,11 @@ namespace WixToolset.Core.CommandLine this.ProcessArgumentsAndParseExtensions(this.OriginalArguments); } - public IParseCommandLine Parse() + public ICommandLineParser Parse() { var messaging = (IMessaging)this.ServiceProvider.GetService(typeof(IMessaging)); - return new ParseCommandLine(messaging, this.Arguments, this.ErrorArgument); + return new CommandLineParser(messaging, this.Arguments, this.ErrorArgument); } private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) diff --git a/src/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs index d0484e45..11e5751d 100644 --- a/src/WixToolset.Core/CommandLine/CommandLineParser.cs +++ b/src/WixToolset.Core/CommandLine/CommandLineParser.cs @@ -1,4 +1,4 @@ -// 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. +// 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.CommandLine { @@ -6,437 +6,269 @@ namespace WixToolset.Core.CommandLine using System.Collections.Generic; using System.IO; using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; - internal enum Commands - { - Unknown, - Build, - Preprocess, - Compile, - Link, - Bind, - } - internal class CommandLineParser : ICommandLineParser { - private static readonly char[] BindPathSplit = { '=' }; - - public CommandLineParser(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } + private const string ExpectedArgument = "expected argument"; - private IServiceProvider ServiceProvider { get; } + public string ErrorArgument { get; set; } - private IMessaging Messaging { get; set; } + private Queue RemainingArguments { get; } - public IExtensionManager ExtensionManager { get; set; } + private IMessaging Messaging { get; } - public ICommandLineArguments Arguments { get; set; } - - public static string ExpectedArgument { get; } = "expected argument"; - - public string ActiveCommand { get; private set; } + public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument) + { + this.Messaging = messaging; + this.RemainingArguments = new Queue(arguments); + this.ErrorArgument = errorArgument; + } - public bool ShowHelp { get; private set; } + public bool IsSwitch(string arg) + { + return !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]); + } - public ICommandLineCommand ParseStandardCommandLine() + public string GetArgumentAsFilePathOrError(string argument, string fileType) { - var context = this.ServiceProvider.GetService(); - context.ExtensionManager = this.ExtensionManager ?? this.ServiceProvider.GetService(); - context.Arguments = this.Arguments; - - var next = String.Empty; - - var command = Commands.Unknown; - var showLogo = true; - var showVersion = false; - var outputFolder = String.Empty; - var outputFile = String.Empty; - var outputType = String.Empty; - var platformType = String.Empty; - var verbose = false; - var files = new List(); - var defines = new List(); - var includePaths = new List(); - var locFiles = new List(); - var libraryFiles = new List(); - var suppressedWarnings = new List(); - - var bindFiles = false; - var bindPaths = new List(); - - var intermediateFolder = String.Empty; - - var cabCachePath = String.Empty; - var cultures = new List(); - var contentsFile = String.Empty; - var outputsFile = String.Empty; - var builtOutputsFile = String.Empty; - - this.Parse(context, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, parser, arg) => + if (!File.Exists(argument)) { - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "help": - cmdline.ShowHelp = true; - return true; - - case "arch": - case "platform": - platformType = parser.GetNextArgumentOrError(arg); - return true; - - case "bindfiles": - bindFiles = true; - return true; - - case "bindpath": - parser.GetNextArgumentOrError(arg, bindPaths); - return true; - - case "cc": - cabCachePath = parser.GetNextArgumentOrError(arg); - return true; - - case "culture": - parser.GetNextArgumentOrError(arg, cultures); - return true; - case "contentsfile": - contentsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - case "outputsfile": - outputsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - case "builtoutputsfile": - builtOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "d": - case "define": - parser.GetNextArgumentOrError(arg, defines); - return true; - - case "i": - case "includepath": - parser.GetNextArgumentOrError(arg, includePaths); - return true; - - case "intermediatefolder": - intermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); - return true; - - case "loc": - parser.GetNextArgumentAsFilePathOrError(arg, "localization files", locFiles); - return true; - - case "lib": - parser.GetNextArgumentAsFilePathOrError(arg, "library files", libraryFiles); - return true; - - case "o": - case "out": - outputFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "outputtype": - outputType = parser.GetNextArgumentOrError(arg); - return true; - - case "nologo": - showLogo = false; - return true; - - case "v": - case "verbose": - verbose = true; - return true; - - case "version": - case "-version": - showVersion = true; - return true; - - case "sval": - // todo: implement - return true; - - case "sw": - case "suppresswarning": - var warning = parser.GetNextArgumentOrError(arg); - if (!String.IsNullOrEmpty(warning)) - { - var warningNumber = Convert.ToInt32(warning); - this.Messaging.SuppressWarningMessage(warningNumber); - } - return true; - } - - return false; - } - else - { - parser.GetArgumentAsFilePathOrError(arg, "source code", files); - return true; - } - }); + this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType)); + return null; + } - this.Messaging.ShowVerboseMessages = verbose; + return argument; + } - if (showVersion) + public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) + { + foreach (var path in this.GetFiles(argument, fileType)) { - return new VersionCommand(); + paths.Add(path); } + } - if (showLogo) + public string GetNextArgumentOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) { - AppCommon.DisplayToolHeader(); + return argument; } - if (this.ShowHelp) - { - return new HelpCommand(command); - } + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } - switch (command) - { - case Commands.Build: + public bool GetNextArgumentOrError(string commandLineSwitch, IList args) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) { - var sourceFiles = GatherSourceFiles(files, outputFolder); - var variables = this.GatherPreprocessorVariables(defines); - var bindPathList = this.GatherBindPaths(bindPaths); - var filterCultures = CalculateFilterCultures(cultures); - var type = CalculateOutputType(outputType, outputFile); - var platform = CalculatePlatform(platformType); - return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, filterCultures, outputFile, type, platform, cabCachePath, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile); + args.Add(arg); + return true; } - case Commands.Compile: + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory)) { - var sourceFiles = GatherSourceFiles(files, outputFolder); - var variables = this.GatherPreprocessorVariables(defines); - var platform = CalculatePlatform(platformType); - return new CompileCommand(this.ServiceProvider, sourceFiles, variables, platform); - } + return directory; } + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); return null; } - private static IEnumerable CalculateFilterCultures(List cultures) + public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) { - var result = new List(); - - if (cultures == null) - { - } - else if (cultures.Count == 1 && cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory)) { - // When null is used treat it as if cultures wasn't specified. This is - // needed for batching in the MSBuild task since MSBuild doesn't support - // empty items. - } - else - { - foreach (var culture in cultures) - { - // Neutral is different from null. For neutral we still want to do culture filtering. - // Set the culture to the empty string = identifier for the invariant culture. - var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; - result.Add(filter); - } + directories.Add(directory); + return true; } - return result; + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; } - private static OutputType CalculateOutputType(string outputType, string outputFile) + public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) { - if (String.IsNullOrEmpty(outputType)) + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) { - outputType = Path.GetExtension(outputFile); + return path; } - switch (outputType.ToLowerInvariant()) - { - case "bundle": - case ".exe": - return OutputType.Bundle; - - case "library": - case ".wixlib": - return OutputType.Library; - - case "module": - case ".msm": - return OutputType.Module; - - case "patch": - case ".msp": - return OutputType.Patch; - - case ".pcp": - return OutputType.PatchCreation; - - case "product": - case "package": - case ".msi": - return OutputType.Product; + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } - case "transform": - case ".mst": - return OutputType.Transform; + public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + foreach (var path in this.GetFiles(arg, fileType)) + { + paths.Add(path); + } - case "intermediatepostlink": - case ".wixipl": - return OutputType.IntermediatePostLink; + return true; } - return OutputType.Unknown; + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; } - private static Platform CalculatePlatform(string platformType) + public bool TryGetNextSwitchOrArgument(out string arg) { - return Enum.TryParse(platformType, true, out Platform platform) ? platform : Platform.X86; + return TryDequeue(this.RemainingArguments, out arg); } - private ICommandLineParser Parse(ICommandLineContext context, Func parseCommand, Func parseArgument) + private bool TryGetNextNonSwitchArgumentOrError(out string arg) { - var extensions = this.ExtensionManager.Create(); + var result = this.TryGetNextSwitchOrArgument(out arg); - foreach (var extension in extensions) + if (!result && !this.IsSwitch(arg)) { - extension.PreParse(context); + this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; } - var parser = context.Arguments.Parse(); - - while (!this.ShowHelp && - String.IsNullOrEmpty(parser.ErrorArgument) && - parser.TryGetNextSwitchOrArgument(out var arg)) - { - if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. - { - continue; - } + return result; + } - if (parser.IsSwitch(arg)) - { - if (!parseArgument(this, parser, arg) && - !this.TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) - { - parser.ErrorArgument = arg; - } - } - else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported. - { - if (parseCommand(this, arg)) - { - this.ActiveCommand = arg; - } - else - { - parser.ErrorArgument = arg; - } - } - else if (!this.TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && - !parseArgument(this, parser, arg)) - { - parser.ErrorArgument = arg; - } - } + private static bool IsValidArg(string arg) + { + return !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]); + } - foreach (var extension in extensions) + private static bool TryDequeue(Queue q, out string arg) + { + if (q.Count > 0) { - extension.PostParse(); + arg = q.Dequeue(); + return true; } - return this; + arg = null; + return false; } - private static IEnumerable GatherSourceFiles(IEnumerable sourceFiles, string intermediateDirectory) + private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory) { - var files = new List(); + directory = null; - foreach (var item in sourceFiles) + if (File.Exists(arg)) { - var sourcePath = item; - var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); - - files.Add(new SourceFile(sourcePath, outputPath)); + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); + return false; } - return files; + directory = this.VerifyPath(arg); + return directory != null; } - private IDictionary GatherPreprocessorVariables(IEnumerable defineConstants) + private bool TryGetFile(string commandlineSwitch, string arg, out string path) { - var variables = new Dictionary(); + path = null; - foreach (var pair in defineConstants) + if (!IsValidArg(arg)) { - var value = pair.Split(new[] { '=' }, 2); - - if (variables.ContainsKey(value[0])) - { - this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); - continue; - } - - variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); + this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); + } + else if (Directory.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); + } + else + { + path = this.VerifyPath(arg); } - return variables; + return path != null; } - private IEnumerable GatherBindPaths(IEnumerable bindPaths) + /// + /// Get a set of files that possibly have a search pattern in the path (such as '*'). + /// + /// Search path to find files in. + /// Type of file; typically "Source". + /// An array of files matching the search path. + /// + /// This method is written in this verbose way because it needs to support ".." in the path. + /// It needs the directory path isolated from the file name in order to use Directory.GetFiles + /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since + /// Path.GetDirectoryName does not support ".." in the path. + /// + /// Throws WixFileNotFoundException if no file matching the pattern can be found. + private string[] GetFiles(string searchPath, string fileType) { - var result = new List(); - - foreach (var bindPath in bindPaths) + if (null == searchPath) { - var bp = ParseBindPath(bindPath); + throw new ArgumentNullException(nameof(searchPath)); + } - if (File.Exists(bp.Path)) + // Convert alternate directory separators to the standard one. + var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); + var files = new string[0]; + + try + { + if (0 > lastSeparator) { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); + files = Directory.GetFiles(".", filePath); } - else + else // found directory separator { - result.Add(bp); + files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); } } + catch (DirectoryNotFoundException) + { + // Don't let this function throw the DirectoryNotFoundException. This exception + // occurs for non-existant directories and invalid characters in the searchPattern. + } + catch (ArgumentException) + { + // Don't let this function throw the ArgumentException. This exception + // occurs in certain situations such as when passing a malformed UNC path. + } + catch (IOException) + { + } - return result; + if (0 == files.Length) + { + this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); + } + + return files; } - private bool TryParseCommandLineArgumentWithExtension(string arg, IParseCommandLine parse, IEnumerable extensions) + private string VerifyPath(string path) { - foreach (var extension in extensions) + string fullPath; + + if (0 <= path.IndexOf('\"')) { - if (extension.TryParseArgument(parse, arg)) - { - return true; - } + this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); + return null; } - return false; - } + try + { + fullPath = Path.GetFullPath(path); + } + catch (Exception e) + { + this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); + return null; + } - public static BindPath ParseBindPath(string bindPath) - { - var namedPath = bindPath.Split(BindPathSplit, 2); - return (1 == namedPath.Length) ? new BindPath(namedPath[0]) : new BindPath(namedPath[0], namedPath[1]); + return fullPath; } } } diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs index 4007c263..69e35cab 100644 --- a/src/WixToolset.Core/CommandLine/CompileCommand.cs +++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs @@ -1,4 +1,4 @@ -// 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. +// 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.CommandLine { @@ -12,6 +12,13 @@ namespace WixToolset.Core.CommandLine internal class CompileCommand : ICommandLineCommand { + public CompileCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + } + public CompileCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, Platform platform) { this.ServiceProvider = serviceProvider; @@ -36,6 +43,15 @@ namespace WixToolset.Core.CommandLine public IEnumerable IncludeSearchPaths { get; } + public bool ShowLogo => throw new NotImplementedException(); + + public bool StopParsing => throw new NotImplementedException(); + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) + { + throw new NotImplementedException(); + } + public int Execute() { foreach (var sourceFile in this.SourceFiles) diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs index b1298e46..224b154c 100644 --- a/src/WixToolset.Core/CommandLine/HelpCommand.cs +++ b/src/WixToolset.Core/CommandLine/HelpCommand.cs @@ -4,28 +4,24 @@ namespace WixToolset.Core.CommandLine { using System; using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; internal class HelpCommand : ICommandLineCommand { - public HelpCommand(Commands command) - { - this.Command = command; - } + public bool ShowLogo => true; - public Commands Command { get; } + public bool StopParsing => true; public int Execute() { - if (this.Command == Commands.Unknown) - { - Console.WriteLine(); - } - else - { - Console.WriteLine(); - } + Console.WriteLine("TODO: Show list of available commands"); return -1; } + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) + { + return true; // eat any arguments + } } } diff --git a/src/WixToolset.Core/CommandLine/ParseCommandLine.cs b/src/WixToolset.Core/CommandLine/ParseCommandLine.cs deleted file mode 100644 index 3cf6e032..00000000 --- a/src/WixToolset.Core/CommandLine/ParseCommandLine.cs +++ /dev/null @@ -1,263 +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.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class ParseCommandLine : IParseCommandLine - { - private const string ExpectedArgument = "expected argument"; - - public string ErrorArgument { get; set; } - - private Queue RemainingArguments { get; } - - private IMessaging Messaging { get; } - - public ParseCommandLine(IMessaging messaging, string[] arguments, string errorArgument) - { - this.Messaging = messaging; - this.RemainingArguments = new Queue(arguments); - this.ErrorArgument = errorArgument; - } - - public bool IsSwitch(string arg) - { - return !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]); - } - - public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) - { - foreach (var path in this.GetFiles(argument, fileType)) - { - paths.Add(path); - } - } - - public string GetNextArgumentOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) - { - return argument; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentOrError(string commandLineSwitch, IList args) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) - { - args.Add(arg); - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory)) - { - return directory; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory)) - { - directories.Add(directory); - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) - { - return path; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) - { - foreach (var path in this.GetFiles(arg, fileType)) - { - paths.Add(path); - } - - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public bool TryGetNextSwitchOrArgument(out string arg) - { - return TryDequeue(this.RemainingArguments, out arg); - } - - private bool TryGetNextNonSwitchArgumentOrError(out string arg) - { - var result = this.TryGetNextSwitchOrArgument(out arg); - - if (!result && !this.IsSwitch(arg)) - { - this.ErrorArgument = arg ?? ParseCommandLine.ExpectedArgument; - } - - return result; - } - - private static bool IsValidArg(string arg) - { - return !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]); - } - - private static bool TryDequeue(Queue q, out string arg) - { - if (q.Count > 0) - { - arg = q.Dequeue(); - return true; - } - - arg = null; - return false; - } - - private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory) - { - directory = null; - - if (File.Exists(arg)) - { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); - return false; - } - - directory = this.VerifyPath(arg); - return directory != null; - } - - private bool TryGetFile(string commandlineSwitch, string arg, out string path) - { - path = null; - - if (!IsValidArg(arg)) - { - this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); - } - else if (Directory.Exists(arg)) - { - this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); - } - else - { - path = this.VerifyPath(arg); - } - - return path != null; - } - - /// - /// Get a set of files that possibly have a search pattern in the path (such as '*'). - /// - /// Search path to find files in. - /// Type of file; typically "Source". - /// An array of files matching the search path. - /// - /// This method is written in this verbose way because it needs to support ".." in the path. - /// It needs the directory path isolated from the file name in order to use Directory.GetFiles - /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since - /// Path.GetDirectoryName does not support ".." in the path. - /// - /// Throws WixFileNotFoundException if no file matching the pattern can be found. - private string[] GetFiles(string searchPath, string fileType) - { - if (null == searchPath) - { - throw new ArgumentNullException(nameof(searchPath)); - } - - // Convert alternate directory separators to the standard one. - var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); - var files = new string[0]; - - try - { - if (0 > lastSeparator) - { - files = Directory.GetFiles(".", filePath); - } - else // found directory separator - { - files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); - } - } - catch (DirectoryNotFoundException) - { - // Don't let this function throw the DirectoryNotFoundException. This exception - // occurs for non-existant directories and invalid characters in the searchPattern. - } - catch (ArgumentException) - { - // Don't let this function throw the ArgumentException. This exception - // occurs in certain situations such as when passing a malformed UNC path. - } - catch (IOException) - { - } - - if (0 == files.Length) - { - this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); - } - - return files; - } - - private string VerifyPath(string path) - { - string fullPath; - - if (0 <= path.IndexOf('\"')) - { - this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); - return null; - } - - try - { - fullPath = Path.GetFullPath(path); - } - catch (Exception e) - { - this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); - return null; - } - - return fullPath; - } - } -} diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs index e67aafb8..1baee72d 100644 --- a/src/WixToolset.Core/CommandLine/VersionCommand.cs +++ b/src/WixToolset.Core/CommandLine/VersionCommand.cs @@ -4,9 +4,14 @@ namespace WixToolset.Core.CommandLine { using System; using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; internal class VersionCommand : ICommandLineCommand { + public bool ShowLogo => true; + + public bool StopParsing => true; + public int Execute() { Console.WriteLine("wix version {0}", ThisAssembly.AssemblyInformationalVersion); @@ -14,5 +19,10 @@ namespace WixToolset.Core.CommandLine return 0; } + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) + { + return true; // eat any arguments + } } } diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs index 0337f771..7216ae2a 100644 --- a/src/WixToolset.Core/WixToolsetServiceProvider.cs +++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -29,7 +29,7 @@ namespace WixToolset.Core // Transients. this.AddService((provider, singletons) => new CommandLineArguments(provider)); this.AddService((provider, singletons) => new CommandLineContext(provider)); - this.AddService((provider, singletons) => new CommandLineParser(provider)); + this.AddService((provider, singletons) => new CommandLine.CommandLine(provider)); this.AddService((provider, singletons) => new PreprocessContext(provider)); this.AddService((provider, singletons) => new CompileContext(provider)); this.AddService((provider, singletons) => new LibraryContext(provider)); -- cgit v1.2.3-55-g6feb