aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/CommandLine/CommandLine.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/CommandLine/CommandLine.cs')
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLine.cs692
1 files changed, 0 insertions, 692 deletions
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs
deleted file mode 100644
index 97f79755..00000000
--- a/src/WixToolset.Core/CommandLine/CommandLine.cs
+++ /dev/null
@@ -1,692 +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
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Text.RegularExpressions;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Services;
14
15 internal enum Commands
16 {
17 Unknown,
18 Build,
19 Preprocess,
20 Compile,
21 Link,
22 Bind,
23 }
24
25 internal class CommandLine : ICommandLine, IParseCommandLine
26 {
27 private IServiceProvider ServiceProvider { get; set; }
28
29 private IMessaging Messaging { get; set; }
30
31 public static string ExpectedArgument { get; } = "expected argument";
32
33 public string ActiveCommand { get; private set; }
34
35 public string[] OriginalArguments { get; private set; }
36
37 public Queue<string> RemainingArguments { get; } = new Queue<string>();
38
39 public IExtensionManager ExtensionManager { get; private set; }
40
41 public string ErrorArgument { get; set; }
42
43 public bool ShowHelp { get; set; }
44
45 public ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context)
46 {
47 this.ServiceProvider = context.ServiceProvider;
48
49 this.Messaging = context.Messaging ?? this.ServiceProvider.GetService<IMessaging>();
50
51 this.ExtensionManager = context.ExtensionManager ?? this.ServiceProvider.GetService<IExtensionManager>();
52
53 var args = context.ParsedArguments ?? Array.Empty<string>();
54
55 if (!String.IsNullOrEmpty(context.Arguments))
56 {
57 args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray();
58 }
59
60 return this.ParseStandardCommandLine(context, args);
61 }
62
63 private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args)
64 {
65 var next = String.Empty;
66
67 var command = Commands.Unknown;
68 var showLogo = true;
69 var showVersion = false;
70 var outputFolder = String.Empty;
71 var outputFile = String.Empty;
72 var outputType = String.Empty;
73 var verbose = false;
74 var files = new List<string>();
75 var defines = new List<string>();
76 var includePaths = new List<string>();
77 var locFiles = new List<string>();
78 var libraryFiles = new List<string>();
79 var suppressedWarnings = new List<int>();
80
81 var bindFiles = false;
82 var bindPaths = new List<string>();
83
84 var intermediateFolder = String.Empty;
85
86 var cabCachePath = String.Empty;
87 var cultures = new List<string>();
88 var contentsFile = String.Empty;
89 var outputsFile = String.Empty;
90 var builtOutputsFile = String.Empty;
91 var wixProjectFile = String.Empty;
92
93 this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) =>
94 {
95 if (cmdline.IsSwitch(arg))
96 {
97 var parameter = arg.TrimStart(new[] { '-', '/' });
98 switch (parameter.ToLowerInvariant())
99 {
100 case "?":
101 case "h":
102 case "help":
103 cmdline.ShowHelp = true;
104 return true;
105
106 case "bindfiles":
107 bindFiles = true;
108 return true;
109
110 case "bindpath":
111 cmdline.GetNextArgumentOrError(bindPaths);
112 return true;
113
114 case "cc":
115 cmdline.GetNextArgumentOrError(ref cabCachePath);
116 return true;
117
118 case "cultures":
119 cmdline.GetNextArgumentOrError(cultures);
120 return true;
121 case "contentsfile":
122 cmdline.GetNextArgumentOrError(ref contentsFile);
123 return true;
124 case "outputsfile":
125 cmdline.GetNextArgumentOrError(ref outputsFile);
126 return true;
127 case "builtoutputsfile":
128 cmdline.GetNextArgumentOrError(ref builtOutputsFile);
129 return true;
130 case "wixprojectfile":
131 cmdline.GetNextArgumentOrError(ref wixProjectFile);
132 return true;
133
134 case "d":
135 case "define":
136 cmdline.GetNextArgumentOrError(defines);
137 return true;
138
139 case "i":
140 case "includepath":
141 cmdline.GetNextArgumentOrError(includePaths);
142 return true;
143
144 case "intermediatefolder":
145 cmdline.GetNextArgumentOrError(ref intermediateFolder);
146 return true;
147
148 case "loc":
149 cmdline.GetNextArgumentAsFilePathOrError(locFiles, "localization files");
150 return true;
151
152 case "lib":
153 cmdline.GetNextArgumentAsFilePathOrError(libraryFiles, "library files");
154 return true;
155
156 case "o":
157 case "out":
158 cmdline.GetNextArgumentOrError(ref outputFile);
159 return true;
160
161 case "outputtype":
162 cmdline.GetNextArgumentOrError(ref outputType);
163 return true;
164
165 case "nologo":
166 showLogo = false;
167 return true;
168
169 case "v":
170 case "verbose":
171 verbose = true;
172 return true;
173
174 case "version":
175 case "-version":
176 showVersion = true;
177 return true;
178 }
179
180 return false;
181 }
182 else
183 {
184 files.AddRange(cmdline.GetFiles(arg, "source code"));
185 return true;
186 }
187 });
188
189 this.Messaging.ShowVerboseMessages = verbose;
190
191 if (showVersion)
192 {
193 return new VersionCommand();
194 }
195
196 if (showLogo)
197 {
198 AppCommon.DisplayToolHeader();
199 }
200
201 if (this.ShowHelp)
202 {
203 return new HelpCommand(command);
204 }
205
206 switch (command)
207 {
208 case Commands.Build:
209 {
210 var sourceFiles = GatherSourceFiles(files, outputFolder);
211 var variables = this.GatherPreprocessorVariables(defines);
212 var bindPathList = this.GatherBindPaths(bindPaths);
213 var type = CalculateOutputType(outputType, outputFile);
214 return new BuildCommand(this.ServiceProvider, this.Messaging, this.ExtensionManager, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile, wixProjectFile);
215 }
216
217 case Commands.Compile:
218 {
219 var sourceFiles = GatherSourceFiles(files, outputFolder);
220 var variables = GatherPreprocessorVariables(defines);
221 return new CompileCommand(this.ServiceProvider, this.Messaging, this.ExtensionManager, sourceFiles, variables);
222 }
223 }
224
225 return null;
226 }
227
228 private static OutputType CalculateOutputType(string outputType, string outputFile)
229 {
230 if (String.IsNullOrEmpty(outputType))
231 {
232 outputType = Path.GetExtension(outputFile);
233 }
234
235 switch (outputType.ToLowerInvariant())
236 {
237 case "bundle":
238 case ".exe":
239 return OutputType.Bundle;
240
241 case "library":
242 case ".wixlib":
243 return OutputType.Library;
244
245 case "module":
246 case ".msm":
247 return OutputType.Module;
248
249 case "patch":
250 case ".msp":
251 return OutputType.Patch;
252
253 case ".pcp":
254 return OutputType.PatchCreation;
255
256 case "product":
257 case ".msi":
258 return OutputType.Product;
259
260 case "transform":
261 case ".mst":
262 return OutputType.Transform;
263
264 case "wixout":
265 case ".wixout":
266 return OutputType.Wixout;
267 }
268
269 return OutputType.Unknown;
270 }
271
272#if UNUSED
273 private static CommandLine Parse(string commandLineString, Func<CommandLine, string, bool> parseArgument)
274 {
275 var arguments = CommandLine.ParseArgumentsToArray(commandLineString).ToArray();
276
277 return CommandLine.Parse(arguments, null, parseArgument);
278 }
279
280 private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseArgument)
281 {
282 return CommandLine.Parse(commandLineArguments, null, parseArgument);
283 }
284#endif
285
286 private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func<CommandLine, string, bool> parseCommand, Func<CommandLine, string, bool> parseArgument)
287 {
288 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
289
290 this.QueueArgumentsAndLoadExtensions(this.OriginalArguments);
291
292 this.ProcessRemainingArguments(context, parseArgument, parseCommand);
293
294 return this;
295 }
296
297 private static IEnumerable<SourceFile> GatherSourceFiles(IEnumerable<string> sourceFiles, string intermediateDirectory)
298 {
299 var files = new List<SourceFile>();
300
301 foreach (var item in sourceFiles)
302 {
303 var sourcePath = item;
304 var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
305
306 files.Add(new SourceFile(sourcePath, outputPath));
307 }
308
309 return files;
310 }
311
312 private IDictionary<string, string> GatherPreprocessorVariables(IEnumerable<string> defineConstants)
313 {
314 var variables = new Dictionary<string, string>();
315
316 foreach (var pair in defineConstants)
317 {
318 string[] value = pair.Split(new[] { '=' }, 2);
319
320 if (variables.ContainsKey(value[0]))
321 {
322 this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
323 continue;
324 }
325
326 variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
327 }
328
329 return variables;
330 }
331
332 private IEnumerable<BindPath> GatherBindPaths(IEnumerable<string> bindPaths)
333 {
334 var result = new List<BindPath>();
335
336 foreach (var bindPath in bindPaths)
337 {
338 BindPath bp = BindPath.Parse(bindPath);
339
340 if (Directory.Exists(bp.Path))
341 {
342 result.Add(bp);
343 }
344 else if (File.Exists(bp.Path))
345 {
346 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path));
347 }
348 }
349
350 return result;
351 }
352
353 /// <summary>
354 /// Get a set of files that possibly have a search pattern in the path (such as '*').
355 /// </summary>
356 /// <param name="searchPath">Search path to find files in.</param>
357 /// <param name="fileType">Type of file; typically "Source".</param>
358 /// <returns>An array of files matching the search path.</returns>
359 /// <remarks>
360 /// This method is written in this verbose way because it needs to support ".." in the path.
361 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
362 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
363 /// Path.GetDirectoryName does not support ".." in the path.
364 /// </remarks>
365 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
366 public string[] GetFiles(string searchPath, string fileType)
367 {
368 if (null == searchPath)
369 {
370 throw new ArgumentNullException(nameof(searchPath));
371 }
372
373 // Convert alternate directory separators to the standard one.
374 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
375 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
376 string[] files = null;
377
378 try
379 {
380 if (0 > lastSeparator)
381 {
382 files = Directory.GetFiles(".", filePath);
383 }
384 else // found directory separator
385 {
386 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
387 }
388 }
389 catch (DirectoryNotFoundException)
390 {
391 // Don't let this function throw the DirectoryNotFoundException. This exception
392 // occurs for non-existant directories and invalid characters in the searchPattern.
393 }
394 catch (ArgumentException)
395 {
396 // Don't let this function throw the ArgumentException. This exception
397 // occurs in certain situations such as when passing a malformed UNC path.
398 }
399 catch (IOException)
400 {
401 throw new WixFileNotFoundException(searchPath, fileType);
402 }
403
404 if (null == files || 0 == files.Length)
405 {
406 throw new WixFileNotFoundException(searchPath, fileType);
407 }
408
409 return files;
410 }
411
412 /// <summary>
413 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
414 /// </summary>
415 /// <param name="args">The list of strings to check.</param>
416 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
417 /// <returns>True if a valid switch exists there, false if not.</returns>
418 public bool IsSwitch(string arg)
419 {
420 return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]);
421 }
422
423 /// <summary>
424 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
425 /// </summary>
426 /// <param name="args">The list of strings to check.</param>
427 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
428 /// <returns>True if a valid switch exists there, false if not.</returns>
429 public bool IsSwitchAt(IEnumerable<string> args, int index)
430 {
431 var arg = args.ElementAtOrDefault(index);
432 return IsSwitch(arg);
433 }
434
435 public void GetNextArgumentOrError(ref string arg)
436 {
437 this.TryGetNextArgumentOrError(out arg);
438 }
439
440 public void GetNextArgumentOrError(IList<string> args)
441 {
442 if (this.TryGetNextArgumentOrError(out var arg))
443 {
444 args.Add(arg);
445 }
446 }
447
448 public void GetNextArgumentAsFilePathOrError(IList<string> args, string fileType)
449 {
450 if (this.TryGetNextArgumentOrError(out var arg))
451 {
452 foreach (var path in this.GetFiles(arg, fileType))
453 {
454 args.Add(path);
455 }
456 }
457 }
458
459 public bool TryGetNextArgumentOrError(out string arg)
460 {
461 //if (this.RemainingArguments.TryDequeue(out arg) && !this.IsSwitch(arg))
462 if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg))
463 {
464 return true;
465 }
466
467 this.ErrorArgument = arg ?? CommandLine.ExpectedArgument;
468
469 return false;
470 }
471
472 private static bool TryDequeue(Queue<string> q, out string arg)
473 {
474 if (q.Count > 0)
475 {
476 arg = q.Dequeue();
477 return true;
478 }
479
480 arg = null;
481 return false;
482 }
483
484 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
485 {
486 List<string> args = new List<string>();
487
488 foreach (var arg in commandLineArguments)
489 {
490 if ('@' == arg[0])
491 {
492 var responseFileArguments = CommandLine.ParseResponseFile(arg.Substring(1));
493 args.AddRange(responseFileArguments);
494 }
495 else
496 {
497 args.Add(arg);
498 }
499 }
500
501 this.OriginalArguments = args.ToArray();
502 }
503
504 private void QueueArgumentsAndLoadExtensions(string[] args)
505 {
506 for (var i = 0; i < args.Length; ++i)
507 {
508 var arg = args[i];
509
510 if ("-ext" == arg || "/ext" == arg)
511 {
512 if (!this.IsSwitchAt(args, ++i))
513 {
514 this.ExtensionManager.Load(args[i]);
515 }
516 else
517 {
518 this.ErrorArgument = arg;
519 break;
520 }
521 }
522 else
523 {
524 this.RemainingArguments.Enqueue(arg);
525 }
526 }
527 }
528
529 private void ProcessRemainingArguments(ICommandLineContext context, Func<CommandLine, string, bool> parseArgument, Func<CommandLine, string, bool> parseCommand)
530 {
531 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
532
533 foreach (var extension in extensions)
534 {
535 extension.PreParse(context);
536 }
537
538 while (!this.ShowHelp &&
539 String.IsNullOrEmpty(this.ErrorArgument) &&
540 TryDequeue(this.RemainingArguments, out var arg))
541 {
542 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
543 {
544 continue;
545 }
546
547 if ('-' == arg[0] || '/' == arg[0])
548 {
549 if (!parseArgument(this, arg) &&
550 !this.TryParseCommandLineArgumentWithExtension(arg, extensions))
551 {
552 this.ErrorArgument = arg;
553 }
554 }
555 else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
556 {
557 if (parseCommand(this, arg))
558 {
559 this.ActiveCommand = arg;
560 }
561 else
562 {
563 this.ErrorArgument = arg;
564 }
565 }
566 else if (!this.TryParseCommandLineArgumentWithExtension(arg, extensions) &&
567 !parseArgument(this, arg))
568 {
569 this.ErrorArgument = arg;
570 }
571 }
572 }
573
574 private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions)
575 {
576 foreach (var extension in extensions)
577 {
578 if (extension.TryParseArgument(this, arg))
579 {
580 return true;
581 }
582 }
583
584 return false;
585 }
586
587 private static List<string> ParseResponseFile(string responseFile)
588 {
589 string arguments;
590
591 using (StreamReader reader = new StreamReader(responseFile))
592 {
593 arguments = reader.ReadToEnd();
594 }
595
596 return CommandLine.ParseArgumentsToArray(arguments);
597 }
598
599 private static List<string> ParseArgumentsToArray(string arguments)
600 {
601 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
602 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
603 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
604 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
605
606 // Collects the final list of arguments to be returned.
607 var argsList = new List<string>();
608
609 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
610 var insideQuote = false;
611
612 // Index of the start of the current argument substring; either the start of the argument
613 // or the start of a quoted or unquoted sequence within it.
614 var partStart = 0;
615
616 // The current argument string being built; when completed it will be added to the list.
617 var arg = new StringBuilder();
618
619 for (int i = 0; i <= arguments.Length; i++)
620 {
621 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
622 {
623 // Reached a whitespace separator or the end of the string.
624
625 // Finish building the current argument.
626 arg.Append(arguments.Substring(partStart, i - partStart));
627
628 // Skip over the whitespace character.
629 partStart = i + 1;
630
631 // Add the argument to the list if it's not empty.
632 if (arg.Length > 0)
633 {
634 argsList.Add(CommandLine.ExpandEnvironmentVariables(arg.ToString()));
635 arg.Length = 0;
636 }
637 }
638 else if (i > partStart && arguments[i - 1] == '\\')
639 {
640 // Check the character following an unprocessed backslash.
641 // Unescape quotes, and backslashes followed by a quote.
642 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
643 {
644 // Unescape the quote or backslash by skipping the preceeding backslash.
645 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
646 arg.Append(arguments[i]);
647 partStart = i + 1;
648 }
649 }
650 else if (arguments[i] == '"')
651 {
652 // Add the quoted or unquoted section to the argument string.
653 arg.Append(arguments.Substring(partStart, i - partStart));
654
655 // And skip over the quote character.
656 partStart = i + 1;
657
658 insideQuote = !insideQuote;
659 }
660 }
661
662 return argsList;
663 }
664
665 private static string ExpandEnvironmentVariables(string arguments)
666 {
667 var id = Environment.GetEnvironmentVariables();
668
669 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
670 MatchCollection matches = regex.Matches(arguments);
671
672 string value = String.Empty;
673 for (int i = 0; i <= (matches.Count - 1); i++)
674 {
675 try
676 {
677 var key = matches[i].Value;
678 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
679 value = id[key].ToString();
680 arguments = regex.Replace(arguments, value);
681 }
682 catch (NullReferenceException)
683 {
684 // Collapse unresolved environment variables.
685 arguments = regex.Replace(arguments, value);
686 }
687 }
688
689 return arguments;
690 }
691 }
692}