@joebigelow / wix / commits / a5c63c90

Update to command-line parsing re-organization

Rob Mensching committed Oct 24, 2018 at 21:02 UTC a5c63c90a02665267c11c8bf2c653fd6db8d0107
11 files changed +824 -715
src/WixToolset.Core.TestPackage/WixRunner.cs
+1 -1
@@ -35,7 +35,7 @@ namespace WixToolset.Core.TestPackage
35 var arguments = serviceProvider.GetService<ICommandLineArguments>();
36 arguments.Populate(args);
37
38 - var commandLine = serviceProvider.GetService<ICommandLineParser>();
38 + var commandLine = serviceProvider.GetService<ICommandLine>();
39 commandLine.ExtensionManager = CreateExtensionManagerWithStandardBackends(serviceProvider, arguments.Extensions);
40 commandLine.Arguments = arguments;
41 var command = commandLine.ParseStandardCommandLine();
src/WixToolset.Core/CommandLine/BuildCommand.cs
+390 -88
@@ -1,4 +1,4 @@
1 -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.CommandLine
4 {
@@ -14,83 +14,84 @@ namespace WixToolset.Core.CommandLine
14
15 internal class BuildCommand : ICommandLineCommand
16 {
17 - public BuildCommand(IServiceProvider serviceProvider, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, IEnumerable<string> libraryFiles, IEnumerable<string> filterCultures, string outputPath, OutputType outputType, Platform platform, string cabCachePath, bool bindFiles, IEnumerable<BindPath> bindPaths, IEnumerable<string> includeSearchPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile)
17 + private readonly CommandLine commandLine;
18 +
19 + public BuildCommand(IServiceProvider serviceProvider)
20 {
21 this.ServiceProvider = serviceProvider;
22 this.Messaging = serviceProvider.GetService<IMessaging>();
23 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
22 - this.LocFiles = locFiles;
23 - this.LibraryFiles = libraryFiles;
24 - this.FilterCultures = filterCultures;
25 - this.PreprocessorVariables = preprocessorVariables;
26 - this.SourceFiles = sources;
27 - this.OutputPath = outputPath;
28 - this.OutputType = outputType;
29 - this.Platform = platform;
30 -
31 - this.CabCachePath = cabCachePath;
32 - this.BindFiles = bindFiles;
33 - this.BindPaths = bindPaths;
34 - this.IncludeSearchPaths = includeSearchPaths;
35 -
36 - this.IntermediateFolder = intermediateFolder ?? Path.GetTempPath();
37 - this.ContentsFile = contentsFile;
38 - this.OutputsFile = outputsFile;
39 - this.BuiltOutputsFile = builtOutputsFile;
24 + this.commandLine = new CommandLine(this.Messaging);
25 }
26
42 - public IServiceProvider ServiceProvider { get; }
27 + public bool ShowLogo => this.commandLine.ShowLogo;
28
44 - public IMessaging Messaging { get; }
29 + public bool StopParsing => this.commandLine.ShowHelp;
30
46 - public IExtensionManager ExtensionManager { get; }
31 + private IServiceProvider ServiceProvider { get; }
32
48 - public IEnumerable<string> FilterCultures { get; }
33 + private IMessaging Messaging { get; }
34
50 - public IEnumerable<string> IncludeSearchPaths { get; }
35 + private IExtensionManager ExtensionManager { get; }
36
52 - public IEnumerable<string> LocFiles { get; }
37 + private string IntermediateFolder { get; set; }
38
54 - public IEnumerable<string> LibraryFiles { get; }
39 + private OutputType OutputType { get; set; }
40
56 - private IEnumerable<SourceFile> SourceFiles { get; }
41 + private List<string> IncludeSearchPaths { get; set; }
42
58 - private IDictionary<string, string> PreprocessorVariables { get; }
43 + private Platform Platform { get; set; }
44
60 - private string OutputPath { get; }
45 + private string OutputFile { get; set; }
46
62 - private OutputType OutputType { get; }
47 + private string ContentsFile { get; set; }
48
64 - private Platform Platform { get; }
49 + private string OutputsFile { get; set; }
50
66 - public string CabCachePath { get; }
51 + private string BuiltOutputsFile { get; set; }
52
68 - public bool BindFiles { get; }
53 + public int Execute()
54 + {
55 + if (this.commandLine.ShowHelp)
56 + {
57 + Console.WriteLine("TODO: Show build command help");
58 + return -1;
59 + }
60
70 - public IEnumerable<BindPath> BindPaths { get; }
61 + this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder();
62
72 - public string IntermediateFolder { get; }
63 + this.OutputType = this.commandLine.CalculateOutputType();
64
74 - public string ContentsFile { get; }
65 + this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths;
66
76 - public string OutputsFile { get; }
67 + this.Platform = this.commandLine.Platform;
68
78 - public string BuiltOutputsFile { get; }
69 + this.OutputFile = this.commandLine.OutputFile;
70 +
71 + this.ContentsFile = this.commandLine.ContentsFile;
72 +
73 + this.OutputsFile = this.commandLine.OutputsFile;
74 +
75 + this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile;
76 +
77 + var preprocessorVariables = this.commandLine.GatherPreprocessorVariables();
78 +
79 + var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder);
80 +
81 + var filterCultures = this.commandLine.CalculateFilterCultures();
82
80 - public int Execute()
81 - {
83 var creator = this.ServiceProvider.GetService<ITupleDefinitionCreator>();
84
84 - this.EvaluateSourceFiles(creator, out var codeFiles, out var wixipl);
85 + this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl);
86
87 if (this.Messaging.EncounteredError)
88 {
89 return this.Messaging.LastErrorNumber;
90 }
91
91 - var wixobjs = this.CompilePhase(codeFiles);
92 + var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles);
93
93 - var wxls = this.LoadLocalizationFiles().ToList();
94 + var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables);
95
96 if (this.Messaging.EncounteredError)
97 {
@@ -99,29 +100,29 @@ namespace WixToolset.Core.CommandLine
100
101 if (this.OutputType == OutputType.Library)
102 {
102 - var wixlib = this.LibraryPhase(wixobjs, wxls);
103 + var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths);
104
105 if (!this.Messaging.EncounteredError)
106 {
106 - wixlib.Save(this.OutputPath);
107 + wixlib.Save(this.commandLine.OutputFile);
108 }
109 }
110 else
111 {
112 if (wixipl == null)
113 {
113 - wixipl = this.LinkPhase(wixobjs, creator);
114 + wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator);
115 }
116
117 if (!this.Messaging.EncounteredError)
118 {
119 if (this.OutputType == OutputType.IntermediatePostLink)
120 {
120 - wixipl.Save(this.OutputPath);
121 + wixipl.Save(this.commandLine.OutputFile);
122 }
123 else
124 {
124 - this.BindPhase(wixipl, wxls);
125 + this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths);
126 }
127 }
128 }
@@ -129,13 +130,18 @@ namespace WixToolset.Core.CommandLine
130 return this.Messaging.LastErrorNumber;
131 }
132
132 - private void EvaluateSourceFiles(ITupleDefinitionCreator creator, out List<SourceFile> codeFiles, out Intermediate wixipl)
133 + public bool TryParseArgument(ICommandLineParser parser, string argument)
134 + {
135 + return this.commandLine.TryParseArgument(argument, parser);
136 + }
137 +
138 + private void EvaluateSourceFiles(IEnumerable<SourceFile> sourceFiles, ITupleDefinitionCreator creator, out List<SourceFile> codeFiles, out Intermediate wixipl)
139 {
140 codeFiles = new List<SourceFile>();
141
142 wixipl = null;
143
138 - foreach (var sourceFile in this.SourceFiles)
144 + foreach (var sourceFile in sourceFiles)
145 {
146 var extension = Path.GetExtension(sourceFile.SourcePath);
147
@@ -167,13 +173,13 @@ namespace WixToolset.Core.CommandLine
173 }
174 }
175
170 - private IEnumerable<Intermediate> CompilePhase(IEnumerable<SourceFile> sourceFiles)
176 + private IEnumerable<Intermediate> CompilePhase(IDictionary<string, string> preprocessorVariables, IEnumerable<SourceFile> sourceFiles)
177 {
178 var intermediates = new List<Intermediate>();
179
180 foreach (var sourceFile in sourceFiles)
181 {
176 - var document = this.Preprocess(sourceFile.SourcePath);
182 + var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath);
183
184 if (this.Messaging.EncounteredError)
185 {
@@ -208,11 +214,11 @@ namespace WixToolset.Core.CommandLine
214 return intermediates;
215 }
216
211 - private Intermediate LibraryPhase(IEnumerable<Intermediate> intermediates, IEnumerable<Localization> localizations)
217 + private Intermediate LibraryPhase(IEnumerable<Intermediate> intermediates, IEnumerable<Localization> localizations, bool bindFiles, IEnumerable<BindPath> bindPaths)
218 {
219 var context = this.ServiceProvider.GetService<ILibraryContext>();
214 - context.BindFiles = this.BindFiles;
215 - context.BindPaths = this.BindPaths;
220 + context.BindFiles = bindFiles;
221 + context.BindPaths = bindPaths;
222 context.Extensions = this.ExtensionManager.Create<ILibrarianExtension>();
223 context.Localizations = localizations;
224 context.Intermediates = intermediates;
@@ -230,10 +236,10 @@ namespace WixToolset.Core.CommandLine
236
237 return library;
238 }
233 -
234 - private Intermediate LinkPhase(IEnumerable<Intermediate> intermediates, ITupleDefinitionCreator creator)
239 +
240 + private Intermediate LinkPhase(IEnumerable<Intermediate> intermediates, IEnumerable<string> libraryFiles, ITupleDefinitionCreator creator)
241 {
236 - var libraries = this.LoadLibraries(creator);
242 + var libraries = this.LoadLibraries(libraryFiles, creator);
243
244 if (this.Messaging.EncounteredError)
245 {
@@ -251,7 +257,7 @@ namespace WixToolset.Core.CommandLine
257 return linker.Link(context);
258 }
259
254 - private void BindPhase(Intermediate output, IEnumerable<Localization> localizations)
260 + private void BindPhase(Intermediate output, IEnumerable<Localization> localizations, IEnumerable<string> filterCultures, string cabCachePath, IEnumerable<BindPath> bindPaths)
261 {
262 var intermediateFolder = this.IntermediateFolder;
263 if (String.IsNullOrEmpty(intermediateFolder))
@@ -262,10 +268,10 @@ namespace WixToolset.Core.CommandLine
268 ResolveResult resolveResult;
269 {
270 var context = this.ServiceProvider.GetService<IResolveContext>();
265 - context.BindPaths = this.BindPaths;
271 + context.BindPaths = bindPaths;
272 context.Extensions = this.ExtensionManager.Create<IResolverExtension>();
273 context.ExtensionData = this.ExtensionManager.Create<IExtensionData>();
268 - context.FilterCultures = this.FilterCultures;
274 + context.FilterCultures = filterCultures;
275 context.IntermediateFolder = intermediateFolder;
276 context.IntermediateRepresentation = output;
277 context.Localizations = localizations;
@@ -284,7 +290,7 @@ namespace WixToolset.Core.CommandLine
290 {
291 var context = this.ServiceProvider.GetService<IBindContext>();
292 //context.CabbingThreadCount = this.CabbingThreadCount;
287 - context.CabCachePath = this.CabCachePath;
293 + context.CabCachePath = cabCachePath;
294 context.Codepage = resolveResult.Codepage;
295 //context.DefaultCompressionLevel = this.DefaultCompressionLevel;
296 context.DelayedFields = resolveResult.DelayedFields;
@@ -293,8 +299,8 @@ namespace WixToolset.Core.CommandLine
299 context.Ices = Array.Empty<string>(); // TODO: set this correctly
300 context.IntermediateFolder = intermediateFolder;
301 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
296 - context.OutputPath = this.OutputPath;
297 - context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb");
302 + context.OutputPath = this.OutputFile;
303 + context.OutputPdbPath = Path.ChangeExtension(this.OutputFile, ".wixpdb");
304 context.SuppressIces = Array.Empty<string>(); // TODO: set this correctly
305 context.SuppressValidation = true; // TODO: set this correctly
306
@@ -323,41 +329,39 @@ namespace WixToolset.Core.CommandLine
329 }
330 }
331
326 - private IEnumerable<Intermediate> LoadLibraries(ITupleDefinitionCreator creator)
332 + private IEnumerable<Intermediate> LoadLibraries(IEnumerable<string> libraryFiles, ITupleDefinitionCreator creator)
333 {
334 var libraries = new List<Intermediate>();
335
330 - if (this.LibraryFiles != null)
336 + foreach (var libraryFile in libraryFiles)
337 {
332 - foreach (var libraryFile in this.LibraryFiles)
338 + try
339 {
334 - try
335 - {
336 - var library = Intermediate.Load(libraryFile, creator);
340 + var library = Intermediate.Load(libraryFile, creator);
341
338 - libraries.Add(library);
339 - }
340 - catch (WixCorruptFileException e)
341 - {
342 - this.Messaging.Write(e.Error);
343 - }
344 - catch (WixUnexpectedFileFormatException e)
345 - {
346 - this.Messaging.Write(e.Error);
347 - }
342 + libraries.Add(library);
343 + }
344 + catch (WixCorruptFileException e)
345 + {
346 + this.Messaging.Write(e.Error);
347 + }
348 + catch (WixUnexpectedFileFormatException e)
349 + {
350 + this.Messaging.Write(e.Error);
351 }
352 }
353
354 return libraries;
355 }
356
354 - private IEnumerable<Localization> LoadLocalizationFiles()
357 + private IEnumerable<Localization> LoadLocalizationFiles(IEnumerable<string> locFiles, IDictionary<string, string> preprocessorVariables)
358 {
356 - var localizer = new Localizer(this.ServiceProvider);
359 + var localizations = new List<Localization>();
360 + var localizer = this.ServiceProvider.GetService<ILocalizer>();
361
358 - foreach (var loc in this.LocFiles)
362 + foreach (var loc in locFiles)
363 {
360 - var document = this.Preprocess(loc);
364 + var document = this.Preprocess(preprocessorVariables, loc);
365
366 if (this.Messaging.EncounteredError)
367 {
@@ -365,18 +369,20 @@ namespace WixToolset.Core.CommandLine
369 }
370
371 var localization = localizer.ParseLocalizationFile(document);
368 - yield return localization;
372 + localizations.Add(localization);
373 }
374 +
375 + return localizations;
376 }
377
372 - private XDocument Preprocess(string sourcePath)
378 + private XDocument Preprocess(IDictionary<string, string> preprocessorVariables, string sourcePath)
379 {
380 var context = this.ServiceProvider.GetService<IPreprocessContext>();
381 context.Extensions = this.ExtensionManager.Create<IPreprocessorExtension>();
382 context.Platform = this.Platform;
383 context.IncludeSearchPaths = this.IncludeSearchPaths;
384 context.SourcePath = sourcePath;
379 - context.Variables = this.PreprocessorVariables;
385 + context.Variables = preprocessorVariables;
386
387 XDocument document = null;
388 try
@@ -391,5 +397,301 @@ namespace WixToolset.Core.CommandLine
397
398 return document;
399 }
400 +
401 + private class CommandLine
402 + {
403 + private static readonly char[] BindPathSplit = { '=' };
404 +
405 + public bool BindFiles { get; private set; }
406 +
407 + public List<BindPath> BindPaths { get; } = new List<BindPath>();
408 +
409 + public string CabCachePath { get; private set; }
410 +
411 + public List<string> Cultures { get; } = new List<string>();
412 +
413 + public List<string> Defines { get; } = new List<string>();
414 +
415 + public List<string> IncludeSearchPaths { get; } = new List<string>();
416 +
417 + public List<string> LocalizationFilePaths { get; } = new List<string>();
418 +
419 + public List<string> LibraryFilePaths { get; } = new List<string>();
420 +
421 + public List<string> SourceFilePaths { get; } = new List<string>();
422 +
423 + public Platform Platform { get; private set; }
424 +
425 + public bool ShowLogo { get; private set; }
426 +
427 + public bool ShowHelp { get; private set; }
428 +
429 + public string IntermediateFolder { get; private set; }
430 +
431 + public string OutputFile { get; private set; }
432 +
433 + public string OutputType { get; private set; }
434 +
435 + public string ContentsFile { get; private set; }
436 +
437 + public string OutputsFile { get; private set; }
438 +
439 + public string BuiltOutputsFile { get; private set; }
440 +
441 + public CommandLine(IMessaging messaging)
442 + {
443 + this.Messaging = messaging;
444 + }
445 +
446 + private IMessaging Messaging { get; }
447 +
448 + public bool TryParseArgument(string arg, ICommandLineParser parser)
449 + {
450 + if (parser.IsSwitch(arg))
451 + {
452 + var parameter = arg.Substring(1);
453 + switch (parameter.ToLowerInvariant())
454 + {
455 + case "?":
456 + case "h":
457 + case "help":
458 + this.ShowHelp = true;
459 + return true;
460 +
461 + case "arch":
462 + case "platform":
463 + {
464 + var value = parser.GetNextArgumentOrError(arg);
465 + if (Enum.TryParse(value, true, out Platform platform))
466 + {
467 + this.Platform = platform;
468 + return true;
469 + }
470 + break;
471 + }
472 +
473 + case "bindfiles":
474 + this.BindFiles = true;
475 + return true;
476 +
477 + case "bindpath":
478 + {
479 + var value = parser.GetNextArgumentOrError(arg);
480 + if (this.TryParseBindPath(value, out var bindPath))
481 + {
482 + this.BindPaths.Add(bindPath);
483 + return true;
484 + }
485 + break;
486 + }
487 + case "cc":
488 + this.CabCachePath = parser.GetNextArgumentOrError(arg);
489 + return true;
490 +
491 + case "culture":
492 + parser.GetNextArgumentOrError(arg, this.Cultures);
493 + return true;
494 +
495 + case "contentsfile":
496 + this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg);
497 + return true;
498 + case "outputsfile":
499 + this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
500 + return true;
501 + case "builtoutputsfile":
502 + this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
503 + return true;
504 +
505 + case "d":
506 + case "define":
507 + parser.GetNextArgumentOrError(arg, this.Defines);
508 + return true;
509 +
510 + case "i":
511 + case "includepath":
512 + parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths);
513 + return true;
514 +
515 + case "intermediatefolder":
516 + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
517 + return true;
518 +
519 + case "loc":
520 + parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths);
521 + return true;
522 +
523 + case "lib":
524 + parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths);
525 + return true;
526 +
527 + case "o":
528 + case "out":
529 + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg);
530 + return true;
531 +
532 + case "outputtype":
533 + this.OutputType = parser.GetNextArgumentOrError(arg);
534 + return true;
535 +
536 + case "nologo":
537 + this.ShowLogo = false;
538 + return true;
539 +
540 + case "v":
541 + case "verbose":
542 + this.Messaging.ShowVerboseMessages = true;
543 + return true;
544 +
545 + case "sval":
546 + // todo: implement
547 + return true;
548 +
549 + case "sw":
550 + case "suppresswarning":
551 + var warning = parser.GetNextArgumentOrError(arg);
552 + if (!String.IsNullOrEmpty(warning))
553 + {
554 + var warningNumber = Convert.ToInt32(warning);
555 + this.Messaging.SuppressWarningMessage(warningNumber);
556 + }
557 + return true;
558 + }
559 +
560 + return false;
561 + }
562 + else
563 + {
564 + parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths);
565 + return true;
566 + }
567 + }
568 +
569 + public string CalculateIntermedateFolder()
570 + {
571 + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder;
572 + }
573 +
574 + public OutputType CalculateOutputType()
575 + {
576 + if (String.IsNullOrEmpty(this.OutputType))
577 + {
578 + this.OutputType = Path.GetExtension(this.OutputFile);
579 + }
580 +
581 + switch (this.OutputType.ToLowerInvariant())
582 + {
583 + case "bundle":
584 + case ".exe":
585 + return Data.OutputType.Bundle;
586 +
587 + case "library":
588 + case ".wixlib":
589 + return Data.OutputType.Library;
590 +
591 + case "module":
592 + case ".msm":
593 + return Data.OutputType.Module;
594 +
595 + case "patch":
596 + case ".msp":
597 + return Data.OutputType.Patch;
598 +
599 + case ".pcp":
600 + return Data.OutputType.PatchCreation;
601 +
602 + case "product":
603 + case "package":
604 + case ".msi":
605 + return Data.OutputType.Product;
606 +
607 + case "transform":
608 + case ".mst":
609 + return Data.OutputType.Transform;
610 +
611 + case "intermediatepostlink":
612 + case ".wixipl":
613 + return Data.OutputType.IntermediatePostLink;
614 + }
615 +
616 + return Data.OutputType.Unknown;
617 + }
618 +
619 + public IEnumerable<string> CalculateFilterCultures()
620 + {
621 + var result = new List<string>();
622 +
623 + if (this.Cultures == null)
624 + {
625 + }
626 + else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase))
627 + {
628 + // When null is used treat it as if cultures wasn't specified. This is
629 + // needed for batching in the MSBuild task since MSBuild doesn't support
630 + // empty items.
631 + }
632 + else
633 + {
634 + foreach (var culture in this.Cultures)
635 + {
636 + // Neutral is different from null. For neutral we still want to do culture filtering.
637 + // Set the culture to the empty string = identifier for the invariant culture.
638 + var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture;
639 + result.Add(filter);
640 + }
641 + }
642 +
643 + return result;
644 + }
645 +
646 + public IDictionary<string, string> GatherPreprocessorVariables()
647 + {
648 + var variables = new Dictionary<string, string>();
649 +
650 + foreach (var pair in this.Defines)
651 + {
652 + var value = pair.Split(new[] { '=' }, 2);
653 +
654 + if (variables.ContainsKey(value[0]))
655 + {
656 + this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
657 + continue;
658 + }
659 +
660 + variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
661 + }
662 +
663 + return variables;
664 + }
665 +
666 +
667 + public IEnumerable<SourceFile> GatherSourceFiles(string intermediateDirectory)
668 + {
669 + var files = new List<SourceFile>();
670 +
671 + foreach (var item in this.SourceFilePaths)
672 + {
673 + var sourcePath = item;
674 + var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
675 +
676 + files.Add(new SourceFile(sourcePath, outputPath));
677 + }
678 +
679 + return files;
680 + }
681 +
682 + private bool TryParseBindPath(string bindPath, out BindPath bp)
683 + {
684 + var namedPath = bindPath.Split(BindPathSplit, 2);
685 + bp = (1 == namedPath.Length) ? new BindPath(namedPath[0]) : new BindPath(namedPath[0], namedPath[1]);
686 +
687 + if (File.Exists(bp.Path))
688 + {
689 + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path));
690 + return false;
691 + }
692 +
693 + return true;
694 + }
695 + }
696 }
697 }
src/WixToolset.Core/CommandLine/CommandLine.cs new
+210
@@ -0,0 +1,210 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Core.CommandLine
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using WixToolset.Extensibility;
8 + using WixToolset.Extensibility.Data;
9 + using WixToolset.Extensibility.Services;
10 +
11 + internal enum CommandTypes
12 + {
13 + Unknown,
14 + Build,
15 + Preprocess,
16 + Compile,
17 + Link,
18 + Bind,
19 + Decompile,
20 + }
21 +
22 + internal class CommandLine : ICommandLine
23 + {
24 + private static readonly char[] BindPathSplit = { '=' };
25 +
26 + public CommandLine(IServiceProvider serviceProvider)
27 + {
28 + this.ServiceProvider = serviceProvider;
29 +
30 + this.Messaging = this.ServiceProvider.GetService<IMessaging>();
31 + }
32 +
33 + private IServiceProvider ServiceProvider { get; }
34 +
35 + private IMessaging Messaging { get; set; }
36 +
37 + public IExtensionManager ExtensionManager { get; set; }
38 +
39 + public ICommandLineArguments Arguments { get; set; }
40 +
41 + public static string ExpectedArgument { get; } = "expected argument";
42 +
43 + public bool ShowHelp { get; private set; }
44 +
45 + public ICommandLineCommand ParseStandardCommandLine()
46 + {
47 + var context = this.ServiceProvider.GetService<ICommandLineContext>();
48 + context.ExtensionManager = this.ExtensionManager ?? this.ServiceProvider.GetService<IExtensionManager>();
49 + context.Arguments = this.Arguments;
50 +
51 + var command = this.Parse(context);
52 +
53 + if (command.ShowLogo)
54 + {
55 + AppCommon.DisplayToolHeader();
56 + }
57 +
58 + return command;
59 + //switch (commandType)
60 + //{
61 + //case CommandTypes.Build:
62 + //{
63 + // var sourceFiles = GatherSourceFiles(files, outputFolder);
64 + // var variables = this.GatherPreprocessorVariables(defines);
65 + // var bindPathList = this.GatherBindPaths(bindPaths);
66 + // var filterCultures = CalculateFilterCultures(cultures);
67 + // var type = CalculateOutputType(outputType, outputFile);
68 + // var platform = CalculatePlatform(platformType);
69 + // return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, filterCultures, outputFile, type, platform, cabCachePath, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile);
70 + //}
71 +
72 + //case CommandTypes.Compile:
73 + //{
74 + // var sourceFiles = GatherSourceFiles(files, outputFolder);
75 + // var variables = this.GatherPreprocessorVariables(defines);
76 + // var platform = CalculatePlatform(platformType);
77 + // return new CompileCommand(this.ServiceProvider, sourceFiles, variables, platform);
78 + //}
79 +
80 + //case CommandTypes.Decompile:
81 + //{
82 + // var sourceFiles = GatherSourceFiles(files, outputFolder);
83 + // return new DecompileCommand(this.ServiceProvider, sourceFiles, outputFile);
84 + //}
85 + //}
86 +
87 + //return null;
88 + }
89 +
90 + private ICommandLineCommand Parse(ICommandLineContext context)
91 + {
92 + var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
93 +
94 + foreach (var extension in extensions)
95 + {
96 + extension.PreParse(context);
97 + }
98 +
99 + ICommandLineCommand command = null;
100 + var parser = context.Arguments.Parse();
101 +
102 + while (command?.StopParsing != true &&
103 + String.IsNullOrEmpty(parser.ErrorArgument) &&
104 + parser.TryGetNextSwitchOrArgument(out var arg))
105 + {
106 + if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
107 + {
108 + continue;
109 + }
110 +
111 + // First argument must be the command or global switch (that creates a command).
112 + if (command == null)
113 + {
114 + if (!this.TryParseUnknownCommandArg(arg, parser, out command, extensions))
115 + {
116 + parser.ErrorArgument = arg;
117 + }
118 + }
119 + else if (parser.IsSwitch(arg))
120 + {
121 + if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions))
122 + {
123 + parser.ErrorArgument = arg;
124 + }
125 + }
126 + else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && command?.TryParseArgument(parser, arg) == false)
127 + {
128 + parser.ErrorArgument = arg;
129 + }
130 + }
131 +
132 + foreach (var extension in extensions)
133 + {
134 + extension.PostParse();
135 + }
136 +
137 + return command ?? new HelpCommand();
138 + }
139 +
140 + private bool TryParseUnknownCommandArg(string arg, ICommandLineParser parser, out ICommandLineCommand command, IEnumerable<IExtensionCommandLine> extensions)
141 + {
142 + command = null;
143 +
144 + if (parser.IsSwitch(arg))
145 + {
146 + var parameter = arg.Substring(1);
147 + switch (parameter.ToLowerInvariant())
148 + {
149 + case "?":
150 + case "h":
151 + case "help":
152 + command = new HelpCommand();
153 + break;
154 +
155 + case "version":
156 + case "-version":
157 + command = new VersionCommand();
158 + break;
159 + }
160 + }
161 + else
162 + {
163 + if (Enum.TryParse(arg, true, out CommandTypes commandType))
164 + {
165 + switch (commandType)
166 + {
167 + case CommandTypes.Build:
168 + command = new BuildCommand(this.ServiceProvider);
169 + break;
170 +
171 + case CommandTypes.Compile:
172 + command = new CompileCommand(this.ServiceProvider);
173 + break;
174 +
175 + case CommandTypes.Decompile:
176 + command = new DecompileCommand(this.ServiceProvider);
177 + break;
178 + }
179 + }
180 + else
181 + {
182 + foreach (var extension in extensions)
183 + {
184 + if (extension.TryParseCommand(parser, out command))
185 + {
186 + break;
187 + }
188 +
189 + command = null;
190 + }
191 + }
192 + }
193 +
194 + return command != null;
195 + }
196 +
197 + private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable<IExtensionCommandLine> extensions)
198 + {
199 + foreach (var extension in extensions)
200 + {
201 + if (extension.TryParseArgument(parse, arg))
202 + {
203 + return true;
204 + }
205 + }
206 +
207 + return false;
208 + }
209 + }
210 +}
src/WixToolset.Core/CommandLine/CommandLineArguments.cs
+2 -2
@@ -41,11 +41,11 @@ namespace WixToolset.Core.CommandLine
41 this.ProcessArgumentsAndParseExtensions(this.OriginalArguments);
42 }
43
44 - public IParseCommandLine Parse()
44 + public ICommandLineParser Parse()
45 {
46 var messaging = (IMessaging)this.ServiceProvider.GetService(typeof(IMessaging));
47
48 - return new ParseCommandLine(messaging, this.Arguments, this.ErrorArgument);
48 + return new CommandLineParser(messaging, this.Arguments, this.ErrorArgument);
49 }
50
51 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
src/WixToolset.Core/CommandLine/CommandLineParser.cs
+173 -341
@@ -1,4 +1,4 @@
1 -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.CommandLine
4 {
@@ -6,437 +6,269 @@ namespace WixToolset.Core.CommandLine
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9 - using WixToolset.Extensibility;
10 - using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
13 - internal enum Commands
14 - {
15 - Unknown,
16 - Build,
17 - Preprocess,
18 - Compile,
19 - Link,
20 - Bind,
21 - }
22 -
11 internal class CommandLineParser : ICommandLineParser
12 {
25 - private static readonly char[] BindPathSplit = { '=' };
26 -
27 - public CommandLineParser(IServiceProvider serviceProvider)
28 - {
29 - this.ServiceProvider = serviceProvider;
30 -
31 - this.Messaging = this.ServiceProvider.GetService<IMessaging>();
32 - }
13 + private const string ExpectedArgument = "expected argument";
14
34 - private IServiceProvider ServiceProvider { get; }
15 + public string ErrorArgument { get; set; }
16
36 - private IMessaging Messaging { get; set; }
17 + private Queue<string> RemainingArguments { get; }
18
38 - public IExtensionManager ExtensionManager { get; set; }
19 + private IMessaging Messaging { get; }
20
40 - public ICommandLineArguments Arguments { get; set; }
41 -
42 - public static string ExpectedArgument { get; } = "expected argument";
43 -
44 - public string ActiveCommand { get; private set; }
21 + public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument)
22 + {
23 + this.Messaging = messaging;
24 + this.RemainingArguments = new Queue<string>(arguments);
25 + this.ErrorArgument = errorArgument;
26 + }
27
46 - public bool ShowHelp { get; private set; }
28 + public bool IsSwitch(string arg)
29 + {
30 + return !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]);
31 + }
32
48 - public ICommandLineCommand ParseStandardCommandLine()
33 + public string GetArgumentAsFilePathOrError(string argument, string fileType)
34 {
50 - var context = this.ServiceProvider.GetService<ICommandLineContext>();
51 - context.ExtensionManager = this.ExtensionManager ?? this.ServiceProvider.GetService<IExtensionManager>();
52 - context.Arguments = this.Arguments;
53 -
54 - var next = String.Empty;
55 -
56 - var command = Commands.Unknown;
57 - var showLogo = true;
58 - var showVersion = false;
59 - var outputFolder = String.Empty;
60 - var outputFile = String.Empty;
61 - var outputType = String.Empty;
62 - var platformType = String.Empty;
63 - var verbose = false;
64 - var files = new List<string>();
65 - var defines = new List<string>();
66 - var includePaths = new List<string>();
67 - var locFiles = new List<string>();
68 - var libraryFiles = new List<string>();
69 - var suppressedWarnings = new List<int>();
70 -
71 - var bindFiles = false;
72 - var bindPaths = new List<string>();
73 -
74 - var intermediateFolder = String.Empty;
75 -
76 - var cabCachePath = String.Empty;
77 - var cultures = new List<string>();
78 - var contentsFile = String.Empty;
79 - var outputsFile = String.Empty;
80 - var builtOutputsFile = String.Empty;
81 -
82 - this.Parse(context, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, parser, arg) =>
35 + if (!File.Exists(argument))
36 {
84 - if (parser.IsSwitch(arg))
85 - {
86 - var parameter = arg.Substring(1);
87 - switch (parameter.ToLowerInvariant())
88 - {
89 - case "?":
90 - case "h":
91 - case "help":
92 - cmdline.ShowHelp = true;
93 - return true;
94 -
95 - case "arch":
96 - case "platform":
97 - platformType = parser.GetNextArgumentOrError(arg);
98 - return true;
99 -
100 - case "bindfiles":
101 - bindFiles = true;
102 - return true;
103 -
104 - case "bindpath":
105 - parser.GetNextArgumentOrError(arg, bindPaths);
106 - return true;
107 -
108 - case "cc":
109 - cabCachePath = parser.GetNextArgumentOrError(arg);
110 - return true;
111 -
112 - case "culture":
113 - parser.GetNextArgumentOrError(arg, cultures);
114 - return true;
115 - case "contentsfile":
116 - contentsFile = parser.GetNextArgumentAsFilePathOrError(arg);
117 - return true;
118 - case "outputsfile":
119 - outputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
120 - return true;
121 - case "builtoutputsfile":
122 - builtOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
123 - return true;
124 -
125 - case "d":
126 - case "define":
127 - parser.GetNextArgumentOrError(arg, defines);
128 - return true;
129 -
130 - case "i":
131 - case "includepath":
132 - parser.GetNextArgumentOrError(arg, includePaths);
133 - return true;
134 -
135 - case "intermediatefolder":
136 - intermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
137 - return true;
138 -
139 - case "loc":
140 - parser.GetNextArgumentAsFilePathOrError(arg, "localization files", locFiles);
141 - return true;
142 -
143 - case "lib":
144 - parser.GetNextArgumentAsFilePathOrError(arg, "library files", libraryFiles);
145 - return true;
146 -
147 - case "o":
148 - case "out":
149 - outputFile = parser.GetNextArgumentAsFilePathOrError(arg);
150 - return true;
151 -
152 - case "outputtype":
153 - outputType = parser.GetNextArgumentOrError(arg);
154 - return true;
155 -
156 - case "nologo":
157 - showLogo = false;
158 - return true;
159 -
160 - case "v":
161 - case "verbose":
162 - verbose = true;
163 - return true;
164 -
165 - case "version":
166 - case "-version":
167 - showVersion = true;
168 - return true;
169 -
170 - case "sval":
171 - // todo: implement
172 - return true;
173 -
174 - case "sw":
175 - case "suppresswarning":
176 - var warning = parser.GetNextArgumentOrError(arg);
177 - if (!String.IsNullOrEmpty(warning))
178 - {
179 - var warningNumber = Convert.ToInt32(warning);
180 - this.Messaging.SuppressWarningMessage(warningNumber);
181 - }
182 - return true;
183 - }
184 -
185 - return false;
186 - }
187 - else
188 - {
189 - parser.GetArgumentAsFilePathOrError(arg, "source code", files);
190 - return true;
191 - }
192 - });
37 + this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType));
38 + return null;
39 + }
40
194 - this.Messaging.ShowVerboseMessages = verbose;
41 + return argument;
42 + }
43
196 - if (showVersion)
44 + public void GetArgumentAsFilePathOrError(string argument, string fileType, IList<string> paths)
45 + {
46 + foreach (var path in this.GetFiles(argument, fileType))
47 {
198 - return new VersionCommand();
48 + paths.Add(path);
49 }
50 + }
51
201 - if (showLogo)
52 + public string GetNextArgumentOrError(string commandLineSwitch)
53 + {
54 + if (this.TryGetNextNonSwitchArgumentOrError(out var argument))
55 {
203 - AppCommon.DisplayToolHeader();
56 + return argument;
57 }
58
206 - if (this.ShowHelp)
207 - {
208 - return new HelpCommand(command);
209 - }
59 + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
60 + return null;
61 + }
62
211 - switch (command)
212 - {
213 - case Commands.Build:
63 + public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args)
64 + {
65 + if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
66 {
215 - var sourceFiles = GatherSourceFiles(files, outputFolder);
216 - var variables = this.GatherPreprocessorVariables(defines);
217 - var bindPathList = this.GatherBindPaths(bindPaths);
218 - var filterCultures = CalculateFilterCultures(cultures);
219 - var type = CalculateOutputType(outputType, outputFile);
220 - var platform = CalculatePlatform(platformType);
221 - return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, filterCultures, outputFile, type, platform, cabCachePath, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile);
67 + args.Add(arg);
68 + return true;
69 }
70
224 - case Commands.Compile:
71 + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
72 + return false;
73 + }
74 +
75 + public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch)
76 + {
77 + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
78 {
226 - var sourceFiles = GatherSourceFiles(files, outputFolder);
227 - var variables = this.GatherPreprocessorVariables(defines);
228 - var platform = CalculatePlatform(platformType);
229 - return new CompileCommand(this.ServiceProvider, sourceFiles, variables, platform);
230 - }
79 + return directory;
80 }
81
82 + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
83 return null;
84 }
85
236 - private static IEnumerable<string> CalculateFilterCultures(List<string> cultures)
86 + public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories)
87 {
238 - var result = new List<string>();
239 -
240 - if (cultures == null)
241 - {
242 - }
243 - else if (cultures.Count == 1 && cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase))
88 + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
89 {
245 - // When null is used treat it as if cultures wasn't specified. This is
246 - // needed for batching in the MSBuild task since MSBuild doesn't support
247 - // empty items.
248 - }
249 - else
250 - {
251 - foreach (var culture in cultures)
252 - {
253 - // Neutral is different from null. For neutral we still want to do culture filtering.
254 - // Set the culture to the empty string = identifier for the invariant culture.
255 - var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture;
256 - result.Add(filter);
257 - }
90 + directories.Add(directory);
91 + return true;
92 }
93
260 - return result;
94 + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
95 + return false;
96 }
97
263 - private static OutputType CalculateOutputType(string outputType, string outputFile)
98 + public string GetNextArgumentAsFilePathOrError(string commandLineSwitch)
99 {
265 - if (String.IsNullOrEmpty(outputType))
100 + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path))
101 {
267 - outputType = Path.GetExtension(outputFile);
102 + return path;
103 }
104
270 - switch (outputType.ToLowerInvariant())
271 - {
272 - case "bundle":
273 - case ".exe":
274 - return OutputType.Bundle;
275 -
276 - case "library":
277 - case ".wixlib":
278 - return OutputType.Library;
279 -
280 - case "module":
281 - case ".msm":
282 - return OutputType.Module;
283 -
284 - case "patch":
285 - case ".msp":
286 - return OutputType.Patch;
287 -
288 - case ".pcp":
289 - return OutputType.PatchCreation;
290 -
291 - case "product":
292 - case "package":
293 - case ".msi":
294 - return OutputType.Product;
105 + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
106 + return null;
107 + }
108
296 - case "transform":
297 - case ".mst":
298 - return OutputType.Transform;
109 + public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths)
110 + {
111 + if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
112 + {
113 + foreach (var path in this.GetFiles(arg, fileType))
114 + {
115 + paths.Add(path);
116 + }
117
300 - case "intermediatepostlink":
301 - case ".wixipl":
302 - return OutputType.IntermediatePostLink;
118 + return true;
119 }
120
305 - return OutputType.Unknown;
121 + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
122 + return false;
123 }
124
308 - private static Platform CalculatePlatform(string platformType)
125 + public bool TryGetNextSwitchOrArgument(out string arg)
126 {
310 - return Enum.TryParse(platformType, true, out Platform platform) ? platform : Platform.X86;
127 + return TryDequeue(this.RemainingArguments, out arg);
128 }
129
313 - private ICommandLineParser Parse(ICommandLineContext context, Func<CommandLineParser, string, bool> parseCommand, Func<CommandLineParser, IParseCommandLine, string, bool> parseArgument)
130 + private bool TryGetNextNonSwitchArgumentOrError(out string arg)
131 {
315 - var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
132 + var result = this.TryGetNextSwitchOrArgument(out arg);
133
317 - foreach (var extension in extensions)
134 + if (!result && !this.IsSwitch(arg))
135 {
319 - extension.PreParse(context);
136 + this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument;
137 }
138
322 - var parser = context.Arguments.Parse();
323 -
324 - while (!this.ShowHelp &&
325 - String.IsNullOrEmpty(parser.ErrorArgument) &&
326 - parser.TryGetNextSwitchOrArgument(out var arg))
327 - {
328 - if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
329 - {
330 - continue;
331 - }
139 + return result;
140 + }
141
333 - if (parser.IsSwitch(arg))
334 - {
335 - if (!parseArgument(this, parser, arg) &&
336 - !this.TryParseCommandLineArgumentWithExtension(arg, parser, extensions))
337 - {
338 - parser.ErrorArgument = arg;
339 - }
340 - }
341 - else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
342 - {
343 - if (parseCommand(this, arg))
344 - {
345 - this.ActiveCommand = arg;
346 - }
347 - else
348 - {
349 - parser.ErrorArgument = arg;
350 - }
351 - }
352 - else if (!this.TryParseCommandLineArgumentWithExtension(arg, parser, extensions) &&
353 - !parseArgument(this, parser, arg))
354 - {
355 - parser.ErrorArgument = arg;
356 - }
357 - }
142 + private static bool IsValidArg(string arg)
143 + {
144 + return !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]);
145 + }
146
359 - foreach (var extension in extensions)
147 + private static bool TryDequeue(Queue<string> q, out string arg)
148 + {
149 + if (q.Count > 0)
150 {
361 - extension.PostParse();
151 + arg = q.Dequeue();
152 + return true;
153 }
154
364 - return this;
155 + arg = null;
156 + return false;
157 }
158
367 - private static IEnumerable<SourceFile> GatherSourceFiles(IEnumerable<string> sourceFiles, string intermediateDirectory)
159 + private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory)
160 {
369 - var files = new List<SourceFile>();
161 + directory = null;
162
371 - foreach (var item in sourceFiles)
163 + if (File.Exists(arg))
164 {
373 - var sourcePath = item;
374 - var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
375 -
376 - files.Add(new SourceFile(sourcePath, outputPath));
165 + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg));
166 + return false;
167 }
168
379 - return files;
169 + directory = this.VerifyPath(arg);
170 + return directory != null;
171 }
172
382 - private IDictionary<string, string> GatherPreprocessorVariables(IEnumerable<string> defineConstants)
173 + private bool TryGetFile(string commandlineSwitch, string arg, out string path)
174 {
384 - var variables = new Dictionary<string, string>();
175 + path = null;
176
386 - foreach (var pair in defineConstants)
177 + if (!IsValidArg(arg))
178 {
388 - var value = pair.Split(new[] { '=' }, 2);
389 -
390 - if (variables.ContainsKey(value[0]))
391 - {
392 - this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
393 - continue;
394 - }
395 -
396 - variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
179 + this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
180 + }
181 + else if (Directory.Exists(arg))
182 + {
183 + this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg));
184 + }
185 + else
186 + {
187 + path = this.VerifyPath(arg);
188 }
189
399 - return variables;
190 + return path != null;
191 }
192
402 - private IEnumerable<BindPath> GatherBindPaths(IEnumerable<string> bindPaths)
193 + /// <summary>
194 + /// Get a set of files that possibly have a search pattern in the path (such as '*').
195 + /// </summary>
196 + /// <param name="searchPath">Search path to find files in.</param>
197 + /// <param name="fileType">Type of file; typically "Source".</param>
198 + /// <returns>An array of files matching the search path.</returns>
199 + /// <remarks>
200 + /// This method is written in this verbose way because it needs to support ".." in the path.
201 + /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
202 + /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
203 + /// Path.GetDirectoryName does not support ".." in the path.
204 + /// </remarks>
205 + /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
206 + private string[] GetFiles(string searchPath, string fileType)
207 {
404 - var result = new List<BindPath>();
405 -
406 - foreach (var bindPath in bindPaths)
208 + if (null == searchPath)
209 {
408 - var bp = ParseBindPath(bindPath);
210 + throw new ArgumentNullException(nameof(searchPath));
211 + }
212
410 - if (File.Exists(bp.Path))
213 + // Convert alternate directory separators to the standard one.
214 + var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
215 + var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
216 + var files = new string[0];
217 +
218 + try
219 + {
220 + if (0 > lastSeparator)
221 {
412 - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path));
222 + files = Directory.GetFiles(".", filePath);
223 }
414 - else
224 + else // found directory separator
225 {
416 - result.Add(bp);
226 + files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
227 }
228 }
229 + catch (DirectoryNotFoundException)
230 + {
231 + // Don't let this function throw the DirectoryNotFoundException. This exception
232 + // occurs for non-existant directories and invalid characters in the searchPattern.
233 + }
234 + catch (ArgumentException)
235 + {
236 + // Don't let this function throw the ArgumentException. This exception
237 + // occurs in certain situations such as when passing a malformed UNC path.
238 + }
239 + catch (IOException)
240 + {
241 + }
242
420 - return result;
243 + if (0 == files.Length)
244 + {
245 + this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType));
246 + }
247 +
248 + return files;
249 }
250
423 - private bool TryParseCommandLineArgumentWithExtension(string arg, IParseCommandLine parse, IEnumerable<IExtensionCommandLine> extensions)
251 + private string VerifyPath(string path)
252 {
425 - foreach (var extension in extensions)
253 + string fullPath;
254 +
255 + if (0 <= path.IndexOf('\"'))
256 {
427 - if (extension.TryParseArgument(parse, arg))
428 - {
429 - return true;
430 - }
257 + this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path));
258 + return null;
259 }
260
433 - return false;
434 - }
261 + try
262 + {
263 + fullPath = Path.GetFullPath(path);
264 + }
265 + catch (Exception e)
266 + {
267 + this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
268 + return null;
269 + }
270
436 - public static BindPath ParseBindPath(string bindPath)
437 - {
438 - var namedPath = bindPath.Split(BindPathSplit, 2);
439 - return (1 == namedPath.Length) ? new BindPath(namedPath[0]) : new BindPath(namedPath[0], namedPath[1]);
271 + return fullPath;
272 }
273 }
274 }
src/WixToolset.Core/CommandLine/CompileCommand.cs
+17 -1
@@ -1,4 +1,4 @@
1 -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.CommandLine
4 {
@@ -12,6 +12,13 @@ namespace WixToolset.Core.CommandLine
12
13 internal class CompileCommand : ICommandLineCommand
14 {
15 + public CompileCommand(IServiceProvider serviceProvider)
16 + {
17 + this.ServiceProvider = serviceProvider;
18 + this.Messaging = serviceProvider.GetService<IMessaging>();
19 + this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
20 + }
21 +
22 public CompileCommand(IServiceProvider serviceProvider, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, Platform platform)
23 {
24 this.ServiceProvider = serviceProvider;
@@ -36,6 +43,15 @@ namespace WixToolset.Core.CommandLine
43
44 public IEnumerable<string> IncludeSearchPaths { get; }
45
46 + public bool ShowLogo => throw new NotImplementedException();
47 +
48 + public bool StopParsing => throw new NotImplementedException();
49 +
50 + public bool TryParseArgument(ICommandLineParser parseHelper, string argument)
51 + {
52 + throw new NotImplementedException();
53 + }
54 +
55 public int Execute()
56 {
57 foreach (var sourceFile in this.SourceFiles)
src/WixToolset.Core/CommandLine/HelpCommand.cs
+9 -13
@@ -4,28 +4,24 @@ namespace WixToolset.Core.CommandLine
4 {
5 using System;
6 using WixToolset.Extensibility.Data;
7 + using WixToolset.Extensibility.Services;
8
9 internal class HelpCommand : ICommandLineCommand
10 {
10 - public HelpCommand(Commands command)
11 - {
12 - this.Command = command;
13 - }
11 + public bool ShowLogo => true;
12
15 - public Commands Command { get; }
13 + public bool StopParsing => true;
14
15 public int Execute()
16 {
19 - if (this.Command == Commands.Unknown)
20 - {
21 - Console.WriteLine();
22 - }
23 - else
24 - {
25 - Console.WriteLine();
26 - }
17 + Console.WriteLine("TODO: Show list of available commands");
18
19 return -1;
20 }
21 +
22 + public bool TryParseArgument(ICommandLineParser parseHelper, string argument)
23 + {
24 + return true; // eat any arguments
25 + }
26 }
27 }
src/WixToolset.Core/CommandLine/ParseCommandLine.cs deleted
-263
@@ -1,263 +0,0 @@
1 -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 -
3 -namespace WixToolset.Core.CommandLine
4 -{
5 - using System;
6 - using System.Collections.Generic;
7 - using System.IO;
8 - using WixToolset.Data;
9 - using WixToolset.Extensibility.Services;
10 -
11 - internal class ParseCommandLine : IParseCommandLine
12 - {
13 - private const string ExpectedArgument = "expected argument";
14 -
15 - public string ErrorArgument { get; set; }
16 -
17 - private Queue<string> RemainingArguments { get; }
18 -
19 - private IMessaging Messaging { get; }
20 -
21 - public ParseCommandLine(IMessaging messaging, string[] arguments, string errorArgument)
22 - {
23 - this.Messaging = messaging;
24 - this.RemainingArguments = new Queue<string>(arguments);
25 - this.ErrorArgument = errorArgument;
26 - }
27 -
28 - public bool IsSwitch(string arg)
29 - {
30 - return !String.IsNullOrEmpty(arg) && ('/' == arg[0] || '-' == arg[0]);
31 - }
32 -
33 - public void GetArgumentAsFilePathOrError(string argument, string fileType, IList<string> paths)
34 - {
35 - foreach (var path in this.GetFiles(argument, fileType))
36 - {
37 - paths.Add(path);
38 - }
39 - }
40 -
41 - public string GetNextArgumentOrError(string commandLineSwitch)
42 - {
43 - if (this.TryGetNextNonSwitchArgumentOrError(out var argument))
44 - {
45 - return argument;
46 - }
47 -
48 - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
49 - return null;
50 - }
51 -
52 - public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args)
53 - {
54 - if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
55 - {
56 - args.Add(arg);
57 - return true;
58 - }
59 -
60 - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
61 - return false;
62 - }
63 -
64 - public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch)
65 - {
66 - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
67 - {
68 - return directory;
69 - }
70 -
71 - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
72 - return null;
73 - }
74 -
75 - public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories)
76 - {
77 - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, this.Messaging, arg, out var directory))
78 - {
79 - directories.Add(directory);
80 - return true;
81 - }
82 -
83 - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
84 - return false;
85 - }
86 -
87 - public string GetNextArgumentAsFilePathOrError(string commandLineSwitch)
88 - {
89 - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path))
90 - {
91 - return path;
92 - }
93 -
94 - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
95 - return null;
96 - }
97 -
98 - public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths)
99 - {
100 - if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
101 - {
102 - foreach (var path in this.GetFiles(arg, fileType))
103 - {
104 - paths.Add(path);
105 - }
106 -
107 - return true;
108 - }
109 -
110 - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
111 - return false;
112 - }
113 -
114 - public bool TryGetNextSwitchOrArgument(out string arg)
115 - {
116 - return TryDequeue(this.RemainingArguments, out arg);
117 - }
118 -
119 - private bool TryGetNextNonSwitchArgumentOrError(out string arg)
120 - {
121 - var result = this.TryGetNextSwitchOrArgument(out arg);
122 -
123 - if (!result && !this.IsSwitch(arg))
124 - {
125 - this.ErrorArgument = arg ?? ParseCommandLine.ExpectedArgument;
126 - }
127 -
128 - return result;
129 - }
130 -
131 - private static bool IsValidArg(string arg)
132 - {
133 - return !(String.IsNullOrEmpty(arg) || '/' == arg[0] || '-' == arg[0]);
134 - }
135 -
136 - private static bool TryDequeue(Queue<string> q, out string arg)
137 - {
138 - if (q.Count > 0)
139 - {
140 - arg = q.Dequeue();
141 - return true;
142 - }
143 -
144 - arg = null;
145 - return false;
146 - }
147 -
148 - private bool TryGetDirectory(string commandlineSwitch, IMessaging messageHandler, string arg, out string directory)
149 - {
150 - directory = null;
151 -
152 - if (File.Exists(arg))
153 - {
154 - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg));
155 - return false;
156 - }
157 -
158 - directory = this.VerifyPath(arg);
159 - return directory != null;
160 - }
161 -
162 - private bool TryGetFile(string commandlineSwitch, string arg, out string path)
163 - {
164 - path = null;
165 -
166 - if (!IsValidArg(arg))
167 - {
168 - this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
169 - }
170 - else if (Directory.Exists(arg))
171 - {
172 - this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg));
173 - }
174 - else
175 - {
176 - path = this.VerifyPath(arg);
177 - }
178 -
179 - return path != null;
180 - }
181 -
182 - /// <summary>
183 - /// Get a set of files that possibly have a search pattern in the path (such as '*').
184 - /// </summary>
185 - /// <param name="searchPath">Search path to find files in.</param>
186 - /// <param name="fileType">Type of file; typically "Source".</param>
187 - /// <returns>An array of files matching the search path.</returns>
188 - /// <remarks>
189 - /// This method is written in this verbose way because it needs to support ".." in the path.
190 - /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
191 - /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
192 - /// Path.GetDirectoryName does not support ".." in the path.
193 - /// </remarks>
194 - /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
195 - private string[] GetFiles(string searchPath, string fileType)
196 - {
197 - if (null == searchPath)
198 - {
199 - throw new ArgumentNullException(nameof(searchPath));
200 - }
201 -
202 - // Convert alternate directory separators to the standard one.
203 - var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
204 - var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
205 - var files = new string[0];
206 -
207 - try
208 - {
209 - if (0 > lastSeparator)
210 - {
211 - files = Directory.GetFiles(".", filePath);
212 - }
213 - else // found directory separator
214 - {
215 - files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
216 - }
217 - }
218 - catch (DirectoryNotFoundException)
219 - {
220 - // Don't let this function throw the DirectoryNotFoundException. This exception
221 - // occurs for non-existant directories and invalid characters in the searchPattern.
222 - }
223 - catch (ArgumentException)
224 - {
225 - // Don't let this function throw the ArgumentException. This exception
226 - // occurs in certain situations such as when passing a malformed UNC path.
227 - }
228 - catch (IOException)
229 - {
230 - }
231 -
232 - if (0 == files.Length)
233 - {
234 - this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType));
235 - }
236 -
237 - return files;
238 - }
239 -
240 - private string VerifyPath(string path)
241 - {
242 - string fullPath;
243 -
244 - if (0 <= path.IndexOf('\"'))
245 - {
246 - this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path));
247 - return null;
248 - }
249 -
250 - try
251 - {
252 - fullPath = Path.GetFullPath(path);
253 - }
254 - catch (Exception e)
255 - {
256 - this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
257 - return null;
258 - }
259 -
260 - return fullPath;
261 - }
262 - }
263 -}
src/WixToolset.Core/CommandLine/VersionCommand.cs
+10
@@ -4,9 +4,14 @@ namespace WixToolset.Core.CommandLine
4 {
5 using System;
6 using WixToolset.Extensibility.Data;
7 + using WixToolset.Extensibility.Services;
8
9 internal class VersionCommand : ICommandLineCommand
10 {
11 + public bool ShowLogo => true;
12 +
13 + public bool StopParsing => true;
14 +
15 public int Execute()
16 {
17 Console.WriteLine("wix version {0}", ThisAssembly.AssemblyInformationalVersion);
@@ -14,5 +19,10 @@ namespace WixToolset.Core.CommandLine
19
20 return 0;
21 }
22 +
23 + public bool TryParseArgument(ICommandLineParser parseHelper, string argument)
24 + {
25 + return true; // eat any arguments
26 + }
27 }
28 }
src/WixToolset.Core/WixToolsetServiceProvider.cs
+2 -2
@@ -1,4 +1,4 @@
1 -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core
4 {
@@ -29,7 +29,7 @@ namespace WixToolset.Core
29 // Transients.
30 this.AddService<ICommandLineArguments>((provider, singletons) => new CommandLineArguments(provider));
31 this.AddService<ICommandLineContext>((provider, singletons) => new CommandLineContext(provider));
32 - this.AddService<ICommandLineParser>((provider, singletons) => new CommandLineParser(provider));
32 + this.AddService<ICommandLine>((provider, singletons) => new CommandLine.CommandLine(provider));
33 this.AddService<IPreprocessContext>((provider, singletons) => new PreprocessContext(provider));
34 this.AddService<ICompileContext>((provider, singletons) => new CompileContext(provider));
35 this.AddService<ILibraryContext>((provider, singletons) => new LibraryContext(provider));
src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs
+10 -4
@@ -1,4 +1,4 @@
1 -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace Example.Extension
4 {
@@ -23,17 +23,23 @@ namespace Example.Extension
23 {
24 }
25
26 - public bool TryParseArgument(IParseCommandLine parseCommandLine, string arg)
26 + public bool TryParseArgument(ICommandLineParser parser, string argument)
27 {
28 - if (parseCommandLine.IsSwitch(arg) && arg.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase))
28 + if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase))
29 {
30 - this.exampleValueFromCommandLine = parseCommandLine.GetNextArgumentOrError(arg);
30 + this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument);
31 return true;
32 }
33
34 return false;
35 }
36
37 + public bool TryParseCommand(ICommandLineParser parser, out ICommandLineCommand command)
38 + {
39 + command = null;
40 + return false;
41 + }
42 +
43 public void PostParse()
44 {
45 }