aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/CommandLine
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/CommandLine')
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs100
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLine.cs592
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineHelper.cs255
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineOption.cs27
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs137
-rw-r--r--src/WixToolset.Core/CommandLine/CompileCommand.cs39
-rw-r--r--src/WixToolset.Core/CommandLine/HelpCommand.cs30
-rw-r--r--src/WixToolset.Core/CommandLine/ICommand.cs9
-rw-r--r--src/WixToolset.Core/CommandLine/VersionCommand.cs17
9 files changed, 1206 insertions, 0 deletions
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
new file mode 100644
index 00000000..ffb48305
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -0,0 +1,100 @@
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 WixToolset.Data;
10
11 internal class BuildCommand : ICommand
12 {
13 public BuildCommand(IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, string outputPath, IEnumerable<string> cultures, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile)
14 {
15 this.LocFiles = locFiles;
16 this.PreprocessorVariables = preprocessorVariables;
17 this.SourceFiles = sources;
18 this.OutputPath = outputPath;
19
20 this.Cultures = cultures;
21 this.ContentsFile = contentsFile;
22 this.OutputsFile = outputsFile;
23 this.BuiltOutputsFile = builtOutputsFile;
24 this.WixProjectFile = wixProjectFile;
25 }
26
27 public IEnumerable<string> LocFiles { get; }
28
29 private IEnumerable<SourceFile> SourceFiles { get; }
30
31 private IDictionary<string, string> PreprocessorVariables { get; }
32
33 private string OutputPath { get; }
34
35 public IEnumerable<string> Cultures { get; }
36
37 public string ContentsFile { get; }
38
39 public string OutputsFile { get; }
40
41 public string BuiltOutputsFile { get; }
42
43 public string WixProjectFile { get; }
44
45 public int Execute()
46 {
47 var intermediates = CompilePhase();
48
49 var sections = intermediates.SelectMany(i => i.Sections).ToList();
50
51 var linker = new Linker();
52
53 var output = linker.Link(sections, OutputType.Product);
54
55 var localizer = new Localizer();
56
57 var binder = new Binder();
58 binder.TempFilesLocation = Path.GetTempPath();
59 binder.WixVariableResolver = new WixVariableResolver();
60 binder.WixVariableResolver.Localizer = localizer;
61 binder.AddExtension(new BinderFileManager());
62 binder.SuppressValidation = true;
63
64 binder.ContentsFile = this.ContentsFile;
65 binder.OutputsFile = this.OutputsFile;
66 binder.BuiltOutputsFile = this.BuiltOutputsFile;
67 binder.WixprojectFile = this.WixProjectFile;
68
69 foreach (var loc in this.LocFiles)
70 {
71 var localization = Localizer.ParseLocalizationFile(loc, linker.TableDefinitions);
72 binder.WixVariableResolver.Localizer.AddLocalization(localization);
73 }
74
75 binder.Bind(output, this.OutputPath);
76
77 return 0;
78 }
79
80 private IEnumerable<Intermediate> CompilePhase()
81 {
82 var intermediates = new List<Intermediate>();
83
84 var preprocessor = new Preprocessor();
85
86 var compiler = new Compiler();
87
88 foreach (var sourceFile in this.SourceFiles)
89 {
90 var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables);
91
92 var intermediate = compiler.Compile(document);
93
94 intermediates.Add(intermediate);
95 }
96
97 return intermediates;
98 }
99 }
100}
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs
new file mode 100644
index 00000000..440ae9ef
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLine.cs
@@ -0,0 +1,592 @@
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
14 internal enum Commands
15 {
16 Unknown,
17 Build,
18 Preprocess,
19 Compile,
20 Link,
21 Bind,
22 }
23
24 public class CommandLine
25 {
26 private CommandLine()
27 {
28 }
29
30 public static string ExpectedArgument { get; } = "expected argument";
31
32 public string ActiveCommand { get; private set; }
33
34 public string[] OriginalArguments { get; private set; }
35
36 public Queue<string> RemainingArguments { get; } = new Queue<string>();
37
38 public ExtensionManager ExtensionManager { get; } = new ExtensionManager();
39
40 public string ErrorArgument { get; set; }
41
42 public bool ShowHelp { get; set; }
43
44 public static ICommand ParseStandardCommandLine(string commandLineString)
45 {
46 var args = CommandLine.ParseArgumentsToArray(commandLineString).ToArray();
47
48 return ParseStandardCommandLine(args);
49 }
50
51 public static ICommand ParseStandardCommandLine(string[] args)
52 {
53 var next = String.Empty;
54
55 var command = Commands.Unknown;
56 var showLogo = true;
57 var showVersion = false;
58 var outputFolder = String.Empty;
59 var outputFile = String.Empty;
60 var sourceFile = String.Empty;
61 var verbose = false;
62 var files = new List<string>();
63 var defines = new List<string>();
64 var includePaths = new List<string>();
65 var locFiles = new List<string>();
66 var suppressedWarnings = new List<int>();
67
68 var cultures = new List<string>();
69 var contentsFile = String.Empty;
70 var outputsFile = String.Empty;
71 var builtOutputsFile = String.Empty;
72 var wixProjectFile = String.Empty;
73
74 var cli = CommandLine.Parse(args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) =>
75 {
76 if (cmdline.IsSwitch(arg))
77 {
78 var parameter = arg.TrimStart(new[] { '-', '/' });
79 switch (parameter.ToLowerInvariant())
80 {
81 case "?":
82 case "h":
83 case "help":
84 cmdline.ShowHelp = true;
85 return true;
86
87 case "cultures":
88 cmdline.GetNextArgumentOrError(cultures);
89 return true;
90 case "contentsfile":
91 cmdline.GetNextArgumentOrError(ref contentsFile);
92 return true;
93 case "outputsfile":
94 cmdline.GetNextArgumentOrError(ref outputsFile);
95 return true;
96 case "builtoutputsfile":
97 cmdline.GetNextArgumentOrError(ref builtOutputsFile);
98 return true;
99 case "wixprojectfile":
100 cmdline.GetNextArgumentOrError(ref wixProjectFile);
101 return true;
102
103 case "d":
104 case "define":
105 cmdline.GetNextArgumentOrError(defines);
106 return true;
107
108 case "i":
109 case "includepath":
110 cmdline.GetNextArgumentOrError(includePaths);
111 return true;
112
113 case "loc":
114 cmdline.GetNextArgumentAsFilePathOrError(locFiles, "localization files");
115 return true;
116
117 case "o":
118 case "out":
119 cmdline.GetNextArgumentOrError(ref outputFile);
120 return true;
121
122 case "nologo":
123 showLogo = false;
124 return true;
125
126 case "v":
127 case "verbose":
128 verbose = true;
129 return true;
130
131 case "version":
132 case "-version":
133 showVersion = true;
134 return true;
135 }
136
137 return false;
138 }
139 else
140 {
141 files.AddRange(cmdline.GetFiles(arg, "source code"));
142 return true;
143 }
144 });
145
146 if (showVersion)
147 {
148 return new VersionCommand();
149 }
150
151 if (showLogo)
152 {
153 AppCommon.DisplayToolHeader();
154 }
155
156 if (cli.ShowHelp)
157 {
158 return new HelpCommand(command);
159 }
160
161 switch (command)
162 {
163 case Commands.Build:
164 {
165 var sourceFiles = GatherSourceFiles(files, outputFolder);
166 var variables = GatherPreprocessorVariables(defines);
167 var extensions = cli.ExtensionManager;
168 return new BuildCommand(sourceFiles, variables, locFiles, outputFile, cultures, contentsFile, outputsFile, builtOutputsFile, wixProjectFile);
169 }
170
171 case Commands.Compile:
172 {
173 var sourceFiles = GatherSourceFiles(files, outputFolder);
174 var variables = GatherPreprocessorVariables(defines);
175 return new CompileCommand(sourceFiles, variables);
176 }
177 }
178
179 return null;
180 }
181
182 private static CommandLine Parse(string commandLineString, Func<CommandLine, string, bool> parseArgument)
183 {
184 var arguments = CommandLine.ParseArgumentsToArray(commandLineString).ToArray();
185
186 return CommandLine.Parse(arguments, null, parseArgument);
187 }
188
189 private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseArgument)
190 {
191 return CommandLine.Parse(commandLineArguments, null, parseArgument);
192 }
193
194 private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseCommand, Func<CommandLine, string, bool> parseArgument)
195 {
196 var cmdline = new CommandLine();
197
198 cmdline.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
199
200 cmdline.QueueArgumentsAndLoadExtensions(cmdline.OriginalArguments);
201
202 cmdline.ProcessRemainingArguments(parseArgument, parseCommand);
203
204 return cmdline;
205 }
206
207 private static IEnumerable<SourceFile> GatherSourceFiles(IEnumerable<string> sourceFiles, string intermediateDirectory)
208 {
209 var files = new List<SourceFile>();
210
211 foreach (var item in sourceFiles)
212 {
213 var sourcePath = item;
214 var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
215
216 files.Add(new SourceFile(sourcePath, outputPath));
217 }
218
219 return files;
220 }
221
222 private static IDictionary<string, string> GatherPreprocessorVariables(IEnumerable<string> defineConstants)
223 {
224 var variables = new Dictionary<string, string>();
225
226 foreach (var pair in defineConstants)
227 {
228 string[] value = pair.Split(new[] { '=' }, 2);
229
230 if (variables.ContainsKey(value[0]))
231 {
232 Messaging.Instance.OnMessage(WixErrors.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
233 continue;
234 }
235
236 variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
237 }
238
239 return variables;
240 }
241
242
243 /// <summary>
244 /// Get a set of files that possibly have a search pattern in the path (such as '*').
245 /// </summary>
246 /// <param name="searchPath">Search path to find files in.</param>
247 /// <param name="fileType">Type of file; typically "Source".</param>
248 /// <returns>An array of files matching the search path.</returns>
249 /// <remarks>
250 /// This method is written in this verbose way because it needs to support ".." in the path.
251 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
252 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
253 /// Path.GetDirectoryName does not support ".." in the path.
254 /// </remarks>
255 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
256 public string[] GetFiles(string searchPath, string fileType)
257 {
258 if (null == searchPath)
259 {
260 throw new ArgumentNullException(nameof(searchPath));
261 }
262
263 // Convert alternate directory separators to the standard one.
264 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
265 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
266 string[] files = null;
267
268 try
269 {
270 if (0 > lastSeparator)
271 {
272 files = Directory.GetFiles(".", filePath);
273 }
274 else // found directory separator
275 {
276 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
277 }
278 }
279 catch (DirectoryNotFoundException)
280 {
281 // Don't let this function throw the DirectoryNotFoundException. This exception
282 // occurs for non-existant directories and invalid characters in the searchPattern.
283 }
284 catch (ArgumentException)
285 {
286 // Don't let this function throw the ArgumentException. This exception
287 // occurs in certain situations such as when passing a malformed UNC path.
288 }
289 catch (IOException)
290 {
291 throw new WixFileNotFoundException(searchPath, fileType);
292 }
293
294 if (null == files || 0 == files.Length)
295 {
296 throw new WixFileNotFoundException(searchPath, fileType);
297 }
298
299 return files;
300 }
301
302 /// <summary>
303 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
304 /// </summary>
305 /// <param name="args">The list of strings to check.</param>
306 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
307 /// <returns>True if a valid switch exists there, false if not.</returns>
308 public bool IsSwitch(string arg)
309 {
310 return arg != null && ('/' == arg[0] || '-' == arg[0]);
311 }
312
313 /// <summary>
314 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
315 /// </summary>
316 /// <param name="args">The list of strings to check.</param>
317 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
318 /// <returns>True if a valid switch exists there, false if not.</returns>
319 public bool IsSwitchAt(IEnumerable<string> args, int index)
320 {
321 var arg = args.ElementAtOrDefault(index);
322 return IsSwitch(arg);
323 }
324
325 public void GetNextArgumentOrError(ref string arg)
326 {
327 this.TryGetNextArgumentOrError(out arg);
328 }
329
330 public void GetNextArgumentOrError(IList<string> args)
331 {
332 if (this.TryGetNextArgumentOrError(out var arg))
333 {
334 args.Add(arg);
335 }
336 }
337
338 public void GetNextArgumentAsFilePathOrError(IList<string> args, string fileType)
339 {
340 if (this.TryGetNextArgumentOrError(out var arg))
341 {
342 foreach (var path in this.GetFiles(arg, fileType))
343 {
344 args.Add(path);
345 }
346 }
347 }
348
349 public bool TryGetNextArgumentOrError(out string arg)
350 {
351 //if (this.RemainingArguments.TryDequeue(out arg) && !this.IsSwitch(arg))
352 if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg))
353 {
354 return true;
355 }
356
357 this.ErrorArgument = arg ?? CommandLine.ExpectedArgument;
358
359 return false;
360 }
361
362 private static bool TryDequeue(Queue<string> q, out string arg)
363 {
364 if (q.Count> 0)
365 {
366 arg = q.Dequeue();
367 return true;
368 }
369
370 arg = null;
371 return false;
372 }
373
374 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
375 {
376 List<string> args = new List<string>();
377
378 foreach (var arg in commandLineArguments)
379 {
380 if ('@' == arg[0])
381 {
382 var responseFileArguments = CommandLine.ParseResponseFile(arg.Substring(1));
383 args.AddRange(responseFileArguments);
384 }
385 else
386 {
387 args.Add(arg);
388 }
389 }
390
391 this.OriginalArguments = args.ToArray();
392 }
393
394 private void QueueArgumentsAndLoadExtensions(string[] args)
395 {
396 for (var i = 0; i < args.Length; ++i)
397 {
398 var arg = args[i];
399
400 if ("-ext" == arg || "/ext" == arg)
401 {
402 if (!this.IsSwitchAt(args, ++i))
403 {
404 this.ExtensionManager.Load(args[i]);
405 }
406 else
407 {
408 this.ErrorArgument = arg;
409 break;
410 }
411 }
412 else
413 {
414 this.RemainingArguments.Enqueue(arg);
415 }
416 }
417 }
418
419 private void ProcessRemainingArguments(Func<CommandLine, string, bool> parseArgument, Func<CommandLine, string, bool> parseCommand)
420 {
421 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
422
423 while (!this.ShowHelp &&
424 String.IsNullOrEmpty(this.ErrorArgument) &&
425 TryDequeue(this.RemainingArguments, out var arg))
426 {
427 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
428 {
429 continue;
430 }
431
432 if ('-' == arg[0] || '/' == arg[0])
433 {
434 if (!parseArgument(this, arg) &&
435 !this.TryParseCommandLineArgumentWithExtension(arg, extensions))
436 {
437 this.ErrorArgument = arg;
438 }
439 }
440 else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
441 {
442 if (parseCommand(this, arg))
443 {
444 this.ActiveCommand = arg;
445 }
446 else
447 {
448 this.ErrorArgument = arg;
449 }
450 }
451 else if (!this.TryParseCommandLineArgumentWithExtension(arg, extensions) &&
452 !parseArgument(this, arg))
453 {
454 this.ErrorArgument = arg;
455 }
456 }
457 }
458
459 private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions)
460 {
461 foreach (var extension in extensions)
462 {
463 //if (extension.ParseArgument(this, arg))
464 //{
465 // return true;
466 //}
467 }
468
469 return false;
470 }
471
472 /// <summary>
473 /// Parses a response file.
474 /// </summary>
475 /// <param name="responseFile">The file to parse.</param>
476 /// <returns>The array of arguments.</returns>
477 private static List<string> ParseResponseFile(string responseFile)
478 {
479 string arguments;
480
481 using (StreamReader reader = new StreamReader(responseFile))
482 {
483 arguments = reader.ReadToEnd();
484 }
485
486 return CommandLine.ParseArgumentsToArray(arguments);
487 }
488
489 /// <summary>
490 /// Parses an argument string into an argument array based on whitespace and quoting.
491 /// </summary>
492 /// <param name="arguments">Argument string.</param>
493 /// <returns>Argument array.</returns>
494 private static List<string> ParseArgumentsToArray(string arguments)
495 {
496 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
497 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
498 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
499 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
500
501 // Collects the final list of arguments to be returned.
502 var argsList = new List<string>();
503
504 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
505 var insideQuote = false;
506
507 // Index of the start of the current argument substring; either the start of the argument
508 // or the start of a quoted or unquoted sequence within it.
509 var partStart = 0;
510
511 // The current argument string being built; when completed it will be added to the list.
512 var arg = new StringBuilder();
513
514 for (int i = 0; i <= arguments.Length; i++)
515 {
516 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
517 {
518 // Reached a whitespace separator or the end of the string.
519
520 // Finish building the current argument.
521 arg.Append(arguments.Substring(partStart, i - partStart));
522
523 // Skip over the whitespace character.
524 partStart = i + 1;
525
526 // Add the argument to the list if it's not empty.
527 if (arg.Length > 0)
528 {
529 argsList.Add(CommandLine.ExpandEnvVars(arg.ToString()));
530 arg.Length = 0;
531 }
532 }
533 else if (i > partStart && arguments[i - 1] == '\\')
534 {
535 // Check the character following an unprocessed backslash.
536 // Unescape quotes, and backslashes followed by a quote.
537 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
538 {
539 // Unescape the quote or backslash by skipping the preceeding backslash.
540 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
541 arg.Append(arguments[i]);
542 partStart = i + 1;
543 }
544 }
545 else if (arguments[i] == '"')
546 {
547 // Add the quoted or unquoted section to the argument string.
548 arg.Append(arguments.Substring(partStart, i - partStart));
549
550 // And skip over the quote character.
551 partStart = i + 1;
552
553 insideQuote = !insideQuote;
554 }
555 }
556
557 return argsList;
558 }
559
560 /// <summary>
561 /// Expand enxironment variables contained in the passed string
562 /// </summary>
563 /// <param name="arguments"></param>
564 /// <returns></returns>
565 private static string ExpandEnvVars(string arguments)
566 {
567 var id = Environment.GetEnvironmentVariables();
568
569 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
570 MatchCollection matches = regex.Matches(arguments);
571
572 string value = String.Empty;
573 for (int i = 0; i <= (matches.Count - 1); i++)
574 {
575 try
576 {
577 var key = matches[i].Value;
578 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
579 value = id[key].ToString();
580 arguments = regex.Replace(arguments, value);
581 }
582 catch (NullReferenceException)
583 {
584 // Collapse unresolved environment variables.
585 arguments = regex.Replace(arguments, value);
586 }
587 }
588
589 return arguments;
590 }
591 }
592}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineHelper.cs b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
new file mode 100644
index 00000000..86724603
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
@@ -0,0 +1,255 @@
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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11
12 /// <summary>
13 /// Common utilities for Wix command-line processing.
14 /// </summary>
15 public static class CommandLineHelper
16 {
17 /// <summary>
18 /// Get a set of files that possibly have a search pattern in the path (such as '*').
19 /// </summary>
20 /// <param name="searchPath">Search path to find files in.</param>
21 /// <param name="fileType">Type of file; typically "Source".</param>
22 /// <returns>An array of files matching the search path.</returns>
23 /// <remarks>
24 /// This method is written in this verbose way because it needs to support ".." in the path.
25 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
26 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
27 /// Path.GetDirectoryName does not support ".." in the path.
28 /// </remarks>
29 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
30 public static string[] GetFiles(string searchPath, string fileType)
31 {
32 if (null == searchPath)
33 {
34 throw new ArgumentNullException("searchPath");
35 }
36
37 // convert alternate directory separators to the standard one
38 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
39 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
40 string[] files = null;
41
42 try
43 {
44 if (0 > lastSeparator)
45 {
46 files = Directory.GetFiles(".", filePath);
47 }
48 else // found directory separator
49 {
50 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
51 }
52 }
53 catch (DirectoryNotFoundException)
54 {
55 // don't let this function throw the DirectoryNotFoundException. (this exception
56 // occurs for non-existant directories and invalid characters in the searchPattern)
57 }
58 catch (ArgumentException)
59 {
60 // don't let this function throw the ArgumentException. (this exception
61 // occurs in certain situations such as when passing a malformed UNC path)
62 }
63 catch (IOException)
64 {
65 throw new WixFileNotFoundException(searchPath, fileType);
66 }
67
68 // file could not be found or path is invalid in some way
69 if (null == files || 0 == files.Length)
70 {
71 throw new WixFileNotFoundException(searchPath, fileType);
72 }
73
74 return files;
75 }
76
77 /// <summary>
78 /// Validates that a valid string parameter (without "/" or "-"), and returns a bool indicating its validity
79 /// </summary>
80 /// <param name="args">The list of strings to check.</param>
81 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
82 /// <returns>True if a valid string parameter exists there, false if not.</returns>
83 public static bool IsValidArg(string[] args, int index)
84 {
85 if (args.Length <= index || String.IsNullOrEmpty(args[index]) || '/' == args[index][0] || '-' == args[index][0])
86 {
87 return false;
88 }
89 else
90 {
91 return true;
92 }
93 }
94
95 /// <summary>
96 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
97 /// </summary>
98 /// <param name="path">The path to test.</param>
99 /// <returns>The string if it is valid, null if it is invalid.</returns>
100 public static string VerifyPath(string path)
101 {
102 return VerifyPath(path, false);
103 }
104
105 /// <summary>
106 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
107 /// </summary>
108 /// <param name="path">The path to test.</param>
109 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
110 /// <returns>The full path if it is valid, null if it is invalid.</returns>
111 public static string VerifyPath(string path, bool allowPrefix)
112 {
113 string fullPath;
114
115 if (0 <= path.IndexOf('\"'))
116 {
117 Messaging.Instance.OnMessage(WixErrors.PathCannotContainQuote(path));
118 return null;
119 }
120
121 try
122 {
123 string prefix = null;
124 if (allowPrefix)
125 {
126 int prefixLength = path.IndexOf('=') + 1;
127 if (0 != prefixLength)
128 {
129 prefix = path.Substring(0, prefixLength);
130 path = path.Substring(prefixLength);
131 }
132 }
133
134 if (String.IsNullOrEmpty(prefix))
135 {
136 fullPath = Path.GetFullPath(path);
137 }
138 else
139 {
140 fullPath = String.Concat(prefix, Path.GetFullPath(path));
141 }
142 }
143 catch (Exception e)
144 {
145 Messaging.Instance.OnMessage(WixErrors.InvalidCommandLineFileName(path, e.Message));
146 return null;
147 }
148
149 return fullPath;
150 }
151
152 /// <summary>
153 /// Validates that a string is a valid bind path, and throws appropriate warnings/errors if not
154 /// </summary>
155 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
156 /// <param name="args">The list of strings to check.</param>
157 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
158 /// <returns>The bind path if it is valid, null if it is invalid.</returns>
159 public static BindPath GetBindPath(string commandlineSwitch, string[] args, int index)
160 {
161 commandlineSwitch = String.Concat("-", commandlineSwitch);
162
163 if (!IsValidArg(args, index))
164 {
165 Messaging.Instance.OnMessage(WixErrors.DirectoryPathRequired(commandlineSwitch));
166 return null;
167 }
168
169 BindPath bindPath = BindPath.Parse(args[index]);
170
171 if (File.Exists(bindPath.Path))
172 {
173 Messaging.Instance.OnMessage(WixErrors.ExpectedDirectoryGotFile(commandlineSwitch, bindPath.Path));
174 return null;
175 }
176
177 bindPath.Path = VerifyPath(bindPath.Path, true);
178 return String.IsNullOrEmpty(bindPath.Path) ? null : bindPath;
179 }
180
181 /// <summary>
182 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
183 /// </summary>
184 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
185 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
186 /// <param name="args">The list of strings to check.</param>
187 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
188 /// <returns>The string if it is valid, null if it is invalid.</returns>
189 public static string GetFileOrDirectory(string commandlineSwitch, string[] args, int index)
190 {
191 commandlineSwitch = String.Concat("-", commandlineSwitch);
192
193 if (!IsValidArg(args, index))
194 {
195 Messaging.Instance.OnMessage(WixErrors.FileOrDirectoryPathRequired(commandlineSwitch));
196 return null;
197 }
198
199 return VerifyPath(args[index]);
200 }
201
202 /// <summary>
203 /// Validates that a string is a valid directory name, and throws appropriate warnings/errors if not
204 /// </summary>
205 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
206 /// <param name="args">The list of strings to check.</param>
207 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
208 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
209 /// <returns>The string if it is valid, null if it is invalid.</returns>
210 public static string GetDirectory(string commandlineSwitch, string[] args, int index, bool allowPrefix = false)
211 {
212 commandlineSwitch = String.Concat("-", commandlineSwitch);
213
214 if (!IsValidArg(args, index))
215 {
216 Messaging.Instance.OnMessage(WixErrors.DirectoryPathRequired(commandlineSwitch));
217 return null;
218 }
219
220 if (File.Exists(args[index]))
221 {
222 Messaging.Instance.OnMessage(WixErrors.ExpectedDirectoryGotFile(commandlineSwitch, args[index]));
223 return null;
224 }
225
226 return VerifyPath(args[index], allowPrefix);
227 }
228
229 /// <summary>
230 /// Validates that a string is a valid filename, and throws appropriate warnings/errors if not
231 /// </summary>
232 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
233 /// <param name="args">The list of strings to check.</param>
234 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
235 /// <returns>The string if it is valid, null if it is invalid.</returns>
236 public static string GetFile(string commandlineSwitch, string[] args, int index)
237 {
238 commandlineSwitch = String.Concat("-", commandlineSwitch);
239
240 if (!IsValidArg(args, index))
241 {
242 Messaging.Instance.OnMessage(WixErrors.FilePathRequired(commandlineSwitch));
243 return null;
244 }
245
246 if (Directory.Exists(args[index]))
247 {
248 Messaging.Instance.OnMessage(WixErrors.ExpectedFileGotDirectory(commandlineSwitch, args[index]));
249 return null;
250 }
251
252 return VerifyPath(args[index]);
253 }
254 }
255}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineOption.cs b/src/WixToolset.Core/CommandLine/CommandLineOption.cs
new file mode 100644
index 00000000..85a654bf
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineOption.cs
@@ -0,0 +1,27 @@
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
4{
5 /// <summary>
6 /// A command line option.
7 /// </summary>
8 public struct CommandLineOption
9 {
10 public string Option;
11 public string Description;
12 public int AdditionalArguments;
13
14 /// <summary>
15 /// Instantiates a new BuilderCommandLineOption.
16 /// </summary>
17 /// <param name="option">The option name.</param>
18 /// <param name="description">The description of the option.</param>
19 /// <param name="additionalArguments">Count of additional arguments to require after this switch.</param>
20 public CommandLineOption(string option, string description, int additionalArguments)
21 {
22 this.Option = option;
23 this.Description = description;
24 this.AdditionalArguments = additionalArguments;
25 }
26 }
27}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs
new file mode 100644
index 00000000..f27296b7
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs
@@ -0,0 +1,137 @@
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
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Text;
10 using System.Text.RegularExpressions;
11
12 /// <summary>
13 /// Common utilities for Wix command-line processing.
14 /// </summary>
15 public static class CommandLineResponseFile
16 {
17 /// <summary>
18 /// Parses a response file.
19 /// </summary>
20 /// <param name="responseFile">The file to parse.</param>
21 /// <returns>The array of arguments.</returns>
22 public static string[] Parse(string responseFile)
23 {
24 string arguments;
25
26 using (StreamReader reader = new StreamReader(responseFile))
27 {
28 arguments = reader.ReadToEnd();
29 }
30
31 return CommandLineResponseFile.ParseArgumentsToArray(arguments);
32 }
33
34 /// <summary>
35 /// Parses an argument string into an argument array based on whitespace and quoting.
36 /// </summary>
37 /// <param name="arguments">Argument string.</param>
38 /// <returns>Argument array.</returns>
39 public static string[] ParseArgumentsToArray(string arguments)
40 {
41 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
42 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
43 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
44 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
45
46 // Collects the final list of arguments to be returned.
47 List<string> argsList = new List<string>();
48
49 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
50 bool insideQuote = false;
51
52 // Index of the start of the current argument substring; either the start of the argument
53 // or the start of a quoted or unquoted sequence within it.
54 int partStart = 0;
55
56 // The current argument string being built; when completed it will be added to the list.
57 StringBuilder arg = new StringBuilder();
58
59 for (int i = 0; i <= arguments.Length; i++)
60 {
61 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
62 {
63 // Reached a whitespace separator or the end of the string.
64
65 // Finish building the current argument.
66 arg.Append(arguments.Substring(partStart, i - partStart));
67
68 // Skip over the whitespace character.
69 partStart = i + 1;
70
71 // Add the argument to the list if it's not empty.
72 if (arg.Length > 0)
73 {
74 argsList.Add(CommandLineResponseFile.ExpandEnvVars(arg.ToString()));
75 arg.Length = 0;
76 }
77 }
78 else if (i > partStart && arguments[i - 1] == '\\')
79 {
80 // Check the character following an unprocessed backslash.
81 // Unescape quotes, and backslashes followed by a quote.
82 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
83 {
84 // Unescape the quote or backslash by skipping the preceeding backslash.
85 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
86 arg.Append(arguments[i]);
87 partStart = i + 1;
88 }
89 }
90 else if (arguments[i] == '"')
91 {
92 // Add the quoted or unquoted section to the argument string.
93 arg.Append(arguments.Substring(partStart, i - partStart));
94
95 // And skip over the quote character.
96 partStart = i + 1;
97
98 insideQuote = !insideQuote;
99 }
100 }
101
102 return argsList.ToArray();
103 }
104
105 /// <summary>
106 /// Expand enxironment variables contained in the passed string
107 /// </summary>
108 /// <param name="arguments"></param>
109 /// <returns></returns>
110 static private string ExpandEnvVars(string arguments)
111 {
112 IDictionary id = Environment.GetEnvironmentVariables();
113
114 Regex regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
115 MatchCollection matches = regex.Matches(arguments);
116
117 string value = String.Empty;
118 for (int i = 0; i <= (matches.Count - 1); i++)
119 {
120 try
121 {
122 string key = matches[i].Value;
123 regex = new Regex(String.Concat("(?i)(?:\\%)(?:" , key , ")(?:\\%)"));
124 value = id[key].ToString();
125 arguments = regex.Replace(arguments, value);
126 }
127 catch (NullReferenceException)
128 {
129 // Collapse unresolved environment variables.
130 arguments = regex.Replace(arguments, value);
131 }
132 }
133
134 return arguments;
135 }
136 }
137}
diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs
new file mode 100644
index 00000000..17847b57
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs
@@ -0,0 +1,39 @@
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 WixToolset.Data;
8
9 internal class CompileCommand : ICommand
10 {
11 public CompileCommand(IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables)
12 {
13 this.PreprocessorVariables = preprocessorVariables;
14 this.SourceFiles = sources;
15 }
16
17 private IEnumerable<SourceFile> SourceFiles { get; }
18
19 private IDictionary<string, string> PreprocessorVariables { get; }
20
21 public int Execute()
22 {
23 var preprocessor = new Preprocessor();
24
25 var compiler = new Compiler();
26
27 foreach (var sourceFile in this.SourceFiles)
28 {
29 var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables);
30
31 var intermediate = compiler.Compile(document);
32
33 intermediate.Save(sourceFile.OutputPath);
34 }
35
36 return 0;
37 }
38 }
39}
diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs
new file mode 100644
index 00000000..1c101781
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/HelpCommand.cs
@@ -0,0 +1,30 @@
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
7 internal class HelpCommand : ICommand
8 {
9 public HelpCommand(Commands command)
10 {
11 this.Command = command;
12 }
13
14 public Commands Command { get; }
15
16 public int Execute()
17 {
18 if (this.Command == Commands.Unknown)
19 {
20 Console.WriteLine();
21 }
22 else
23 {
24 Console.WriteLine();
25 }
26
27 return -1;
28 }
29 }
30}
diff --git a/src/WixToolset.Core/CommandLine/ICommand.cs b/src/WixToolset.Core/CommandLine/ICommand.cs
new file mode 100644
index 00000000..41abbbdc
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/ICommand.cs
@@ -0,0 +1,9 @@
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 public interface ICommand
6 {
7 int Execute();
8 }
9}
diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs
new file mode 100644
index 00000000..a1980a2b
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/VersionCommand.cs
@@ -0,0 +1,17 @@
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
3using System;
4
5namespace WixToolset.Core
6{
7 internal class VersionCommand : ICommand
8 {
9 public int Execute()
10 {
11 Console.WriteLine("wix version {0}", ThisAssembly.AssemblyInformationalVersion);
12 Console.WriteLine();
13
14 return 0;
15 }
16 }
17}