main
cs 1,006 lines 42.8 KB
Raw
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 System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using System.Xml.Linq;
12 using WixToolset.Data;
13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16
17 internal class BuildCommand : BaseCommandLineCommand
18 {
19 private readonly CommandLine commandLine;
20
21 public BuildCommand(IServiceProvider serviceProvider)
22 {
23 this.ServiceProvider = serviceProvider;
24 this.Messaging = serviceProvider.GetService<IMessaging>();
25 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
26 this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging);
27 }
28
29 private IServiceProvider ServiceProvider { get; }
30
31 private IMessaging Messaging { get; }
32
33 private IExtensionManager ExtensionManager { get; }
34
35 private string IntermediateFolder { get; set; }
36
37 private string OutputPath { get; set; }
38
39 private Platform Platform { get; set; }
40
41 private CompressionLevel? DefaultCompressionLevel { get; set; }
42
43 private string TrackingFile { get; set; }
44
45 public override CommandLineHelp GetCommandLineHelp()
46 {
47 return new CommandLineHelp("", "build [options] sourcefile [sourcefile ...] -out output.ext", new[]
48 {
49 new CommandLineHelpSwitch("-arch", "Set the architecture of the output."),
50 new CommandLineHelpSwitch("-bindfiles", "-bf", "Bind files into an output .wixlib. Ignored if not building a .wixlib."),
51 new CommandLineHelpSwitch("-bindpath", "-b", "Bind path to search for content files."),
52 new CommandLineHelpSwitch("-bindpath:target", "-bt", "Bind path to search for target package's content files. Only used when building a patch."),
53 new CommandLineHelpSwitch("-bindpath:update", "-bu", "Bind path to search for update package's content files. Only used when building a patch."),
54 new CommandLineHelpSwitch("-cabcache", "-cc", "Set a folder to cache cabinets across builds."),
55 new CommandLineHelpSwitch("-cabthreads", "-ct", "Override the number of threads used to create cabinets."),
56 new CommandLineHelpSwitch("-culture", "Adds a culture to filter localization files."),
57 new CommandLineHelpSwitch("-define", "-d", "Sets a preprocessor variable."),
58 new CommandLineHelpSwitch("-defaultcompressionlevel", "-dcl", "Default compression level; see Compression levels below."),
59 new CommandLineHelpSwitch("-includepath", "-i", "Folder to search for include files."),
60 new CommandLineHelpSwitch("-intermediatefolder", "Optional working folder. If not specified a folder in %TMP% will be created."),
61 new CommandLineHelpSwitch("-loc", "Localization file to use in the build. By default, .wxl files are recognized as localization."),
62 new CommandLineHelpSwitch("-lib", "Library file to use in the build. By default, .wixlib files are recognized as libraries."),
63 new CommandLineHelpSwitch("-src", "Source file to use in the build. By default, .wxs files are recognized as source code."),
64 new CommandLineHelpSwitch("-nostdlib", "Skip use of WiX standard wixlib."),
65 new CommandLineHelpSwitch("-out", "-o", "Path to output the build to."),
66 new CommandLineHelpSwitch("-outputtype", "Explicitly set the output type if it cannot be determined from the output."),
67 new CommandLineHelpSwitch("-pdb", "Optional path to output .wixpdb. Default will write .wixpdb beside output path."),
68 new CommandLineHelpSwitch("-pdbtype", "Switch to disable creation of .wixpdb. Types: full or none."),
69 })
70 {
71 Notes = String.Join(Environment.NewLine,
72 "Compression levels:",
73 " none Use no compression",
74 " low Use low compression",
75 " medium Use medium compression",
76 " high Use high compression",
77 " mszip Use ms-zip compression")
78 };
79 }
80
81 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
82 {
83 this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder();
84
85 this.Platform = this.commandLine.Platform;
86
87 this.TrackingFile = this.commandLine.TrackingFile;
88
89 this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel;
90
91 var preprocessorVariables = this.commandLine.CalculatePreprocessorVariables();
92
93 var filterCultures = this.commandLine.CalculateFilterCultures();
94
95 var creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
96
97 var inputsOutputs = this.commandLine.CalculateInputsAndOutputs(creator);
98
99 this.OutputPath = inputsOutputs.OutputPath;
100
101 if (this.Messaging.EncounteredError)
102 {
103 return Task.FromResult(this.Messaging.LastErrorNumber);
104 }
105
106 var wixobjs = this.CompilePhase(preprocessorVariables, inputsOutputs.SourcePaths, this.commandLine.IncludeSearchPaths, cancellationToken);
107
108 var wxls = this.LoadLocalizationFiles(inputsOutputs.LocalizationPaths, preprocessorVariables, this.commandLine.IncludeSearchPaths, cancellationToken);
109
110 if (this.Messaging.EncounteredError)
111 {
112 return Task.FromResult(this.Messaging.LastErrorNumber);
113 }
114
115 this.OptimizePhase(wixobjs, wxls, this.commandLine.BindPaths, this.commandLine.BindVariables, cancellationToken);
116
117 if (inputsOutputs.OutputType == OutputType.Library)
118 {
119 using (new IntermediateFieldContext("wix.lib"))
120 {
121 this.LibraryPhase(wixobjs, wxls, inputsOutputs.LibraryPaths, creator, this.commandLine.BindFiles, this.commandLine.BindPaths, this.commandLine.BindVariables, inputsOutputs.OutputPath, cancellationToken);
122 }
123 }
124 else
125 {
126 using (new IntermediateFieldContext("wix.link"))
127 {
128 var wixipl = inputsOutputs.Wixipls.SingleOrDefault()
129 ?? this.LinkPhase(wixobjs, inputsOutputs, creator, cancellationToken);
130
131 if (!this.Messaging.EncounteredError)
132 {
133 var outputExtension = Path.GetExtension(inputsOutputs.OutputPath);
134 if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension)
135 {
136 var entrySectionType = wixipl.Sections.Single().Type;
137
138 inputsOutputs.OutputPath = Path.ChangeExtension(inputsOutputs.OutputPath, DefaultExtensionForSectionType(entrySectionType));
139 this.OutputPath = inputsOutputs.OutputPath;
140 }
141
142 if (inputsOutputs.OutputType == OutputType.IntermediatePostLink)
143 {
144 wixipl.Save(inputsOutputs.OutputPath);
145 }
146 else
147 {
148 using (new IntermediateFieldContext("wix.bind"))
149 {
150 this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.CabbingThreadCount, this.commandLine.BindPaths, this.commandLine.BindVariables, inputsOutputs, cancellationToken);
151 }
152 }
153 }
154 }
155 }
156
157 return Task.FromResult(this.Messaging.LastErrorNumber);
158 }
159
160 public override bool TryParseArgument(ICommandLineParser parser, string argument)
161 {
162 return this.commandLine.TryParseArgument(argument, parser);
163 }
164
165 private IReadOnlyList<Intermediate> CompilePhase(IDictionary<string, string> preprocessorVariables, IEnumerable<string> sourceFiles, IReadOnlyCollection<string> includeSearchPaths, CancellationToken cancellationToken)
166 {
167 var intermediates = new List<Intermediate>();
168
169 foreach (var sourceFile in sourceFiles)
170 {
171 var document = this.Preprocess(preprocessorVariables, sourceFile, includeSearchPaths, cancellationToken);
172
173 if (document == null)
174 {
175 continue;
176 }
177
178 var context = this.ServiceProvider.GetService<ICompileContext>();
179 context.Extensions = this.ExtensionManager.GetServices<ICompilerExtension>();
180 context.IntermediateFolder = this.IntermediateFolder;
181 context.OutputPath = this.OutputPath;
182 context.Platform = this.Platform;
183 context.Source = document;
184 context.CancellationToken = cancellationToken;
185
186 Intermediate intermediate = null;
187 try
188 {
189 var compiler = this.ServiceProvider.GetService<ICompiler>();
190 intermediate = compiler.Compile(context);
191 }
192 catch (WixException e)
193 {
194 this.Messaging.Write(e.Error);
195 }
196
197 if (this.Messaging.EncounteredError)
198 {
199 continue;
200 }
201
202 intermediates.Add(intermediate);
203 }
204
205 return intermediates;
206 }
207
208 private void OptimizePhase(IReadOnlyCollection<Intermediate> intermediates, IReadOnlyCollection<Localization> localizations, IReadOnlyCollection<IBindPath> bindPaths, Dictionary<string, string> bindVariables, CancellationToken cancellationToken)
209 {
210 var context = this.ServiceProvider.GetService<IOptimizeContext>();
211 context.Extensions = this.ExtensionManager.GetServices<IOptimizerExtension>();
212 context.IntermediateFolder = this.IntermediateFolder;
213 context.BindPaths = bindPaths;
214 context.BindVariables = bindVariables;
215 context.Platform = this.Platform;
216 context.Intermediates = intermediates;
217 context.Localizations = localizations;
218 context.CancellationToken = cancellationToken;
219
220 var optimizer = this.ServiceProvider.GetService<IOptimizer>();
221 optimizer.Optimize(context);
222 }
223
224 private void LibraryPhase(IReadOnlyCollection<Intermediate> intermediates, IReadOnlyCollection<Localization> localizations, IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator, bool bindFiles, IReadOnlyCollection<IBindPath> bindPaths, Dictionary<string, string> bindVariables, string outputPath, CancellationToken cancellationToken)
225 {
226 var libraries = this.LoadLibraries(libraryFiles, creator);
227
228 if (this.Messaging.EncounteredError)
229 {
230 return;
231 }
232
233 var context = this.ServiceProvider.GetService<ILibraryContext>();
234 context.BindFiles = bindFiles;
235 context.BindPaths = bindPaths;
236 context.BindVariables = bindVariables;
237 context.Extensions = this.ExtensionManager.GetServices<ILibrarianExtension>();
238 context.Localizations = localizations;
239 context.IntermediateFolder = this.IntermediateFolder;
240 context.Intermediates = intermediates.Concat(libraries).ToList();
241 context.OutputPath = this.OutputPath;
242 context.CancellationToken = cancellationToken;
243
244 try
245 {
246 var librarian = this.ServiceProvider.GetService<ILibrarian>();
247 var result = librarian.Combine(context);
248
249 if (!this.Messaging.EncounteredError)
250 {
251 result.Library.SaveNew(outputPath);
252
253 this.LayoutFiles(result.TrackedFiles, null, cancellationToken);
254 }
255 }
256 catch (WixException e)
257 {
258 this.Messaging.Write(e.Error);
259 }
260 }
261
262 private Intermediate LinkPhase(IEnumerable<Intermediate> intermediates, InputsAndOutputs inputsOutputs, ISymbolDefinitionCreator creator, CancellationToken cancellationToken)
263 {
264 var libraries = this.LoadLibraries(inputsOutputs.LibraryPaths, creator);
265
266 if (this.Messaging.EncounteredError)
267 {
268 return null;
269 }
270
271 var context = this.ServiceProvider.GetService<ILinkContext>();
272 context.Extensions = this.ExtensionManager.GetServices<ILinkerExtension>();
273 context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>();
274 context.ExpectedOutputType = inputsOutputs.OutputType;
275 context.IntermediateFolder = this.IntermediateFolder;
276 context.Intermediates = intermediates.Concat(libraries).ToList();
277 context.OutputPath = this.OutputPath;
278 context.Platform = this.Platform;
279 context.SkipStdWixlib = this.commandLine.SkipStdWixlib;
280 context.SymbolDefinitionCreator = creator;
281 context.CancellationToken = cancellationToken;
282
283 var linker = this.ServiceProvider.GetService<ILinker>();
284 return linker.Link(context);
285 }
286
287 private void BindPhase(Intermediate output, IReadOnlyCollection<Localization> localizations, IReadOnlyCollection<string> filterCultures, string cabCachePath, int cabbingThreadCount, IReadOnlyCollection<IBindPath> bindPaths, Dictionary<string, string> bindVariables, InputsAndOutputs inputsOutputs, CancellationToken cancellationToken)
288 {
289 IResolveResult resolveResult;
290 {
291 var context = this.ServiceProvider.GetService<IResolveContext>();
292 context.BindPaths = bindPaths;
293 context.BindVariables = bindVariables;
294 context.Extensions = this.ExtensionManager.GetServices<IResolverExtension>();
295 context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>();
296 context.FilterCultures = filterCultures;
297 context.IntermediateFolder = this.IntermediateFolder;
298 context.IntermediateRepresentation = output;
299 context.Localizations = localizations;
300 context.OutputPath = inputsOutputs.OutputPath;
301 context.CancellationToken = cancellationToken;
302
303 var resolver = this.ServiceProvider.GetService<IResolver>();
304 resolveResult = resolver.Resolve(context);
305 }
306
307 if (this.Messaging.EncounteredError)
308 {
309 return;
310 }
311
312 IBindResult bindResult = null;
313 try
314 {
315 {
316 var context = this.ServiceProvider.GetService<IBindContext>();
317 context.BindPaths = bindPaths;
318 context.CabbingThreadCount = cabbingThreadCount;
319 context.CabCachePath = cabCachePath;
320 context.ResolvedCodepage = resolveResult.Codepage;
321 context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage;
322 context.ResolvedLcid = resolveResult.PackageLcid;
323 context.DefaultCompressionLevel = this.DefaultCompressionLevel;
324 context.DelayedFields = resolveResult.DelayedFields;
325 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
326 context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>();
327 context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
328 context.IntermediateFolder = this.IntermediateFolder;
329 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
330 context.OutputPath = this.OutputPath;
331 context.OutputType = this.commandLine.OutputType;
332 context.PdbType = inputsOutputs.PdbType;
333 context.PdbPath = inputsOutputs.PdbPath;
334 context.CancellationToken = cancellationToken;
335
336 if (String.IsNullOrEmpty(context.OutputType))
337 {
338 var entrySection = context.IntermediateRepresentation.Sections.First();
339
340 context.OutputType = entrySection.Type.ToString();
341 }
342
343 var binder = this.ServiceProvider.GetService<IBinder>();
344 bindResult = binder.Bind(context);
345 }
346
347 if (this.Messaging.EncounteredError)
348 {
349 return;
350 }
351
352 this.LayoutFiles(bindResult.TrackedFiles, bindResult.FileTransfers, cancellationToken);
353 }
354 finally
355 {
356 bindResult?.Dispose();
357 }
358 }
359
360 private void LayoutFiles(IReadOnlyCollection<ITrackedFile> trackedFiles, IReadOnlyCollection<IFileTransfer> fileTransfers, CancellationToken cancellationToken)
361 {
362 var context = this.ServiceProvider.GetService<ILayoutContext>();
363 context.Extensions = this.ExtensionManager.GetServices<ILayoutExtension>();
364 context.TrackedFiles = trackedFiles;
365 context.FileTransfers = fileTransfers;
366 context.IntermediateFolder = this.IntermediateFolder;
367 context.OutputPath = this.OutputPath;
368 context.TrackingFile = this.TrackingFile;
369 context.ResetAcls = this.commandLine.ResetAcls;
370 context.CancellationToken = cancellationToken;
371
372 var layout = this.ServiceProvider.GetService<ILayoutCreator>();
373 layout.Layout(context);
374 }
375
376 private IEnumerable<Intermediate> LoadLibraries(IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator)
377 {
378 try
379 {
380 return Intermediate.Load(libraryFiles, creator);
381 }
382 catch (WixCorruptFileException e)
383 {
384 this.Messaging.Write(e.Error);
385 }
386 catch (WixUnexpectedFileFormatException e)
387 {
388 this.Messaging.Write(e.Error);
389 }
390
391 return Array.Empty<Intermediate>();
392 }
393
394 private IReadOnlyList<Localization> LoadLocalizationFiles(IEnumerable<string> locFiles, IDictionary<string, string> preprocessorVariables, IReadOnlyCollection<string> includeSearchPaths, CancellationToken cancellationToken)
395 {
396 var localizations = new List<Localization>();
397 var parser = this.ServiceProvider.GetService<ILocalizationParser>();
398
399 foreach (var loc in locFiles)
400 {
401 var document = this.Preprocess(preprocessorVariables, loc, includeSearchPaths, cancellationToken);
402
403 if (document == null)
404 {
405 continue;
406 }
407
408 var localization = parser.ParseLocalization(document);
409 localizations.Add(localization);
410 }
411
412 return localizations;
413 }
414
415 private XDocument Preprocess(IDictionary<string, string> preprocessorVariables, string sourcePath, IReadOnlyCollection<string> includeSearchPaths, CancellationToken cancellationToken)
416 {
417 var context = this.ServiceProvider.GetService<IPreprocessContext>();
418 context.Extensions = this.ExtensionManager.GetServices<IPreprocessorExtension>();
419 context.Platform = this.Platform;
420 context.IncludeSearchPaths = includeSearchPaths;
421 context.IntermediateFolder = this.IntermediateFolder;
422 context.OutputPath = this.OutputPath;
423 context.SourcePath = sourcePath;
424 context.Variables = preprocessorVariables;
425 context.CancellationToken = cancellationToken;
426
427 IPreprocessResult result = null;
428 try
429 {
430 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>();
431 result = preprocessor.Preprocess(context);
432 }
433 catch (WixException e)
434 {
435 this.Messaging.Write(e.Error);
436 }
437
438 return result?.Document;
439 }
440
441 private static string DefaultExtensionForSectionType(SectionType sectionType)
442 {
443 switch (sectionType)
444 {
445 case SectionType.Bundle:
446 return ".exe";
447 case SectionType.Module:
448 return ".msm";
449 case SectionType.Package:
450 return ".msi";
451 case SectionType.PatchCreation:
452 return ".pcp";
453 case SectionType.Patch:
454 return ".msp";
455 case SectionType.Fragment:
456 case SectionType.Unknown:
457 default:
458 return ".wix";
459 }
460 }
461
462 private static string DefaultExtensionForOutputType(OutputType outputType)
463 {
464 switch (outputType)
465 {
466 case OutputType.Bundle:
467 return ".exe";
468 case OutputType.Library:
469 return ".wixlib";
470 case OutputType.Module:
471 return ".msm";
472 case OutputType.Patch:
473 return ".msp";
474 case OutputType.PatchCreation:
475 return ".pcp";
476 case OutputType.Package:
477 return ".msi";
478 case OutputType.Transform:
479 return ".mst";
480 case OutputType.IntermediatePostLink:
481 return ".wixipl";
482 case OutputType.Unknown:
483 default:
484 return ".wix";
485 }
486 }
487
488 private class CommandLine
489 {
490 private static readonly char[] BindPathSplit = { '=' };
491
492 public bool BindFiles { get; private set; }
493
494 public List<IBindPath> BindPaths { get; } = new List<IBindPath>();
495
496 public Dictionary<string, string> BindVariables { get; } = new Dictionary<string, string>();
497
498 public string CabCachePath { get; private set; }
499
500 public int CabbingThreadCount { get; private set; }
501
502 public List<string> Cultures { get; } = new List<string>();
503
504 public List<string> Defines { get; } = new List<string>();
505
506 public List<string> IncludeSearchPaths { get; } = new List<string>();
507
508 public List<string> LocalizationFilePaths { get; } = new List<string>();
509
510 public List<string> LibraryFilePaths { get; } = new List<string>();
511
512 public List<string> SourceFilePaths { get; } = new List<string>();
513
514 public List<string> UnclassifiedInputFilePaths { get; } = new List<string>();
515
516 public Platform Platform { get; private set; }
517
518 public string PdbFile { get; private set; }
519
520 public PdbType PdbType { get; private set; }
521
522 public string IntermediateFolder { get; private set; }
523
524 public string OutputFile { get; private set; }
525
526 public string OutputType { get; private set; }
527
528 public CompressionLevel? DefaultCompressionLevel { get; private set; }
529
530 public string TrackingFile { get; private set; }
531
532 public bool SkipStdWixlib { get; set; }
533
534 public bool ResetAcls { get; set; }
535
536 public CommandLine(IServiceProvider serviceProvider, IMessaging messaging)
537 {
538 this.ServiceProvider = serviceProvider;
539 this.Messaging = messaging;
540 }
541
542 private IServiceProvider ServiceProvider { get; }
543
544 private IMessaging Messaging { get; }
545
546 public bool TryParseArgument(string arg, ICommandLineParser parser)
547 {
548 if (parser.IsSwitch(arg))
549 {
550 var parameter = arg.Substring(1).ToLowerInvariant();
551 switch (parameter)
552 {
553 case "arch":
554 case "platform":
555 {
556 var value = parser.GetNextArgumentOrError(arg);
557 if (Enum.TryParse(value, true, out Platform platform))
558 {
559 this.Platform = platform;
560 }
561 else if (!String.IsNullOrEmpty(value))
562 {
563 parser.ReportErrorArgument(arg, ErrorMessages.IllegalCommandLineArgumentValue(arg, value, Enum.GetNames(typeof(Platform)).Select(s => s.ToLowerInvariant())));
564 }
565
566 return true;
567 }
568
569 case "bf":
570 case "bindfiles":
571 this.BindFiles = true;
572 return true;
573
574 case "b":
575 case "bindpath":
576 case "bt":
577 case "bindpath:target":
578 case "bu":
579 case "bindpath:update":
580 {
581 var value = parser.GetNextArgumentOrError(arg);
582 if (value != null && this.TryParseBindPath(arg, value, out var bindPath))
583 {
584 if (parameter == "bt" || parameter.EndsWith("target"))
585 {
586 bindPath.Stage = BindStage.Target;
587 }
588 else if (parameter == "bu" || parameter.EndsWith("update"))
589 {
590 bindPath.Stage = BindStage.Updated;
591 }
592
593 this.BindPaths.Add(bindPath);
594 }
595 return true;
596 }
597
598 case "bv":
599 case "bindvariable":
600 {
601 var value = parser.GetNextArgumentOrError(arg);
602 if (value != null && TryParseNameValuePair(value, out var parsedName, out var parsedValue))
603 {
604 if (this.BindVariables.TryGetValue(parsedName, out var collisionValue))
605 {
606 parser.ReportErrorArgument(arg, LinkerErrors.DuplicateBindPathVariableOnCommandLine(arg, parsedName, parsedValue, collisionValue));
607 }
608 else
609 {
610 this.BindVariables.Add(parsedName, parsedValue);
611 }
612 }
613 return true;
614 }
615
616 case "cc":
617 case "cabcache":
618 this.CabCachePath = parser.GetNextArgumentOrError(arg);
619 return true;
620
621 case "ct":
622 case "cabthreads":
623 {
624 var value = parser.GetNextArgumentOrError(arg);
625 if (Int32.TryParse(value, out var cabbingThreads))
626 {
627 this.CabbingThreadCount = cabbingThreads;
628 }
629 else if (!String.IsNullOrEmpty(value))
630 {
631 var processorCount = Environment.ProcessorCount == 0 ? 1 : Environment.ProcessorCount;
632 var range = Enumerable.Range(1, processorCount * 2).Select(i => i.ToString());
633 parser.ReportErrorArgument(arg, ErrorMessages.IllegalCommandLineArgumentValue(arg, value, range));
634 }
635
636 return true;
637 }
638
639 case "culture":
640 parser.GetNextArgumentOrError(arg, this.Cultures);
641 return true;
642
643 case "trackingfile":
644 this.TrackingFile = parser.GetNextArgumentAsFilePathOrError(arg, "tracking file");
645 return true;
646
647 case "d":
648 case "define":
649 parser.GetNextArgumentOrError(arg, this.Defines);
650 return true;
651
652 case "dcl":
653 case "defaultcompressionlevel":
654 {
655 var value = parser.GetNextArgumentOrError(arg);
656 if (Enum.TryParse(value, true, out CompressionLevel compressionLevel))
657 {
658 this.DefaultCompressionLevel = compressionLevel;
659 }
660 else if (!String.IsNullOrEmpty(value))
661 {
662 parser.ReportErrorArgument(arg, ErrorMessages.IllegalCommandLineArgumentValue(arg, value, Enum.GetNames(typeof(CompressionLevel)).Select(s => s.ToLowerInvariant())));
663 }
664
665 return true;
666 }
667
668 case "i":
669 case "includepath":
670 parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths);
671 return true;
672
673 case "intermediatefolder":
674 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
675 return true;
676
677 case "loc":
678 parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths);
679 return true;
680
681 case "lib":
682 parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths);
683 return true;
684
685 case "src":
686 parser.GetNextArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths);
687 return true;
688
689 case "nostdlib":
690 this.SkipStdWixlib = true;
691 return true;
692
693 case "o":
694 case "out":
695 this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg, "output file");
696 return true;
697
698 case "outputtype":
699 this.OutputType = parser.GetNextArgumentOrError(arg);
700 return true;
701
702 case "pdb":
703 this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg, "wixpdb file");
704 return true;
705
706 case "pdbtype":
707 {
708 var value = parser.GetNextArgumentOrError(arg);
709 if (Enum.TryParse(value, true, out PdbType pdbType))
710 {
711 this.PdbType = pdbType;
712 }
713 else if (!String.IsNullOrEmpty(value))
714 {
715 if (value.Equals("embedded", StringComparison.OrdinalIgnoreCase))
716 {
717 this.Messaging.Write(WarningMessages.UnsupportedCommandLineArgumentValue(arg, value, "full"));
718
719 this.PdbType = PdbType.Full;
720 }
721 else
722 {
723 parser.ReportErrorArgument(arg, ErrorMessages.IllegalCommandLineArgumentValue(arg, value, Enum.GetNames(typeof(PdbType)).Select(s => s.ToLowerInvariant())));
724 }
725 }
726
727 return true;
728 }
729
730 case "resetacls":
731 this.ResetAcls = true;
732 return true;
733 }
734
735 return false;
736 }
737 else
738 {
739 parser.GetArgumentAsFilePathOrError(arg, "input", this.UnclassifiedInputFilePaths);
740 return true;
741 }
742 }
743
744 public string CalculateIntermedateFolder()
745 {
746 var intermediateFolder = this.IntermediateFolder;
747
748 if (String.IsNullOrEmpty(intermediateFolder))
749 {
750 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
751 }
752
753 return intermediateFolder;
754 }
755
756 public OutputType CalculateOutputType()
757 {
758 if (String.IsNullOrEmpty(this.OutputType))
759 {
760 this.OutputType = Path.GetExtension(this.OutputFile);
761 }
762
763 switch (this.OutputType?.ToLowerInvariant())
764 {
765 case "bundle":
766 case ".exe":
767 return Data.OutputType.Bundle;
768
769 case "library":
770 case ".wixlib":
771 return Data.OutputType.Library;
772
773 case "module":
774 case ".msm":
775 return Data.OutputType.Module;
776
777 case "patch":
778 case ".msp":
779 return Data.OutputType.Patch;
780
781 case ".pcp":
782 return Data.OutputType.PatchCreation;
783
784 case "product":
785 case "package":
786 case ".msi":
787 return Data.OutputType.Package;
788
789 case "transform":
790 case ".mst":
791 return Data.OutputType.Transform;
792
793 case "intermediatepostlink":
794 case ".wixipl":
795 return Data.OutputType.IntermediatePostLink;
796 }
797
798 return Data.OutputType.Unknown;
799 }
800
801 public IReadOnlyList<string> CalculateFilterCultures()
802 {
803 var result = new List<string>();
804
805 if (this.Cultures == null)
806 {
807 }
808 else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase))
809 {
810 // When null is used treat it as if cultures wasn't specified. This is
811 // needed for batching in the MSBuild task since MSBuild doesn't support
812 // empty items.
813 }
814 else
815 {
816 foreach (var culture in this.Cultures)
817 {
818 // Neutral is different from null. For neutral we still want to do culture filtering.
819 // Set the culture to the empty string = identifier for the invariant culture.
820 var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture;
821 result.Add(filter);
822 }
823 }
824
825 return result;
826 }
827
828 public IDictionary<string, string> CalculatePreprocessorVariables()
829 {
830 var variables = new Dictionary<string, string>();
831
832 foreach (var pair in this.Defines)
833 {
834 var value = pair.Split(new[] { '=' }, 2);
835
836 if (variables.ContainsKey(value[0]))
837 {
838 this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
839 continue;
840 }
841
842 variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
843 }
844
845 return variables;
846 }
847
848 public InputsAndOutputs CalculateInputsAndOutputs(ISymbolDefinitionCreator creator)
849 {
850 var codePaths = new List<string>(this.SourceFilePaths);
851 var localizationPaths = new List<string>(this.LocalizationFilePaths);
852 var libraryPaths = new List<string>(this.LibraryFilePaths);
853 var wixipls = new List<Intermediate>();
854 string lastWixiplPath = null;
855
856 var outputPath = this.OutputFile;
857 var outputType = this.CalculateOutputType();
858
859 foreach (var path in this.UnclassifiedInputFilePaths)
860 {
861 var extension = Path.GetExtension(path);
862
863 if (".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase))
864 {
865 codePaths.Add(path);
866 }
867 else if (".wxl".Equals(extension, StringComparison.OrdinalIgnoreCase))
868 {
869 localizationPaths.Add(path);
870 }
871 else if (".wixlib".Equals(extension, StringComparison.OrdinalIgnoreCase))
872 {
873 libraryPaths.Add(path);
874 }
875 else
876 {
877 try
878 {
879 // Try to load the file as an intermediate to determine whether it is a
880 // .wixipl or a .wixlib.
881 var intermediate = Intermediate.Load(path, creator);
882
883 if (intermediate.HasLevel(IntermediateLevels.Linked))
884 {
885 wixipls.Add(intermediate);
886 lastWixiplPath = path;
887 }
888 else
889 {
890 libraryPaths.Add(path);
891 }
892 }
893 catch (WixException)
894 {
895 // We'll assume anything that isn't a valid intermediate is source code to compile.
896 codePaths.Add(path);
897 }
898 }
899 }
900
901 if (wixipls.Count > 0)
902 {
903 if (wixipls.Count > 1 || codePaths.Count > 0 || libraryPaths.Count > 0)
904 {
905 this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive());
906 }
907 }
908 else if (codePaths.Count == 0 && libraryPaths.Count == 0)
909 {
910 this.Messaging.Write(ErrorMessages.NoSourceFiles());
911 }
912
913 if (!this.Messaging.EncounteredError && String.IsNullOrEmpty(outputPath))
914 {
915 var singleInputPath = codePaths.Count == 1 ? codePaths[0] : lastWixiplPath;
916
917 if (String.IsNullOrEmpty(singleInputPath))
918 {
919 this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput());
920 }
921 else
922 {
923 // If output type is unknown, the extension will be replaced with the right default based on output type.
924 outputPath = Path.ChangeExtension(singleInputPath, DefaultExtensionForOutputType(outputType));
925 }
926 }
927
928 var pdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(outputPath ?? "error.above", ".wixpdb");
929
930 return new InputsAndOutputs(codePaths, localizationPaths, libraryPaths, wixipls, outputPath, outputType, pdbPath, this.PdbType);
931 }
932
933 private bool TryParseBindPath(string argument, string bindPath, out IBindPath bp)
934 {
935 bp = this.ServiceProvider.GetService<IBindPath>();
936
937 if (TryParseNameValuePair(bindPath, out var name, out var path))
938 {
939 bp.Name = name;
940 bp.Path = path;
941 }
942 else
943 {
944 bp.Path = bindPath;
945 }
946
947 if (File.Exists(bp.Path))
948 {
949 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(argument, bp.Path));
950 return false;
951 }
952
953 return true;
954 }
955
956 private static bool TryParseNameValuePair(string nameValuePair, out string key, out string value)
957 {
958 var split = nameValuePair.Split(BindPathSplit, 2);
959
960 if (1 == split.Length)
961 {
962 key = null;
963 value = null;
964
965 return false;
966 }
967
968 key = split[0];
969 value = split[1];
970
971 return true;
972 }
973 }
974
975 private class InputsAndOutputs
976 {
977 public InputsAndOutputs(IReadOnlyCollection<string> sourcePaths, IReadOnlyCollection<string> localizationPaths, IReadOnlyCollection<string> libraryPaths, IReadOnlyCollection<Intermediate> wixipls, string outputPath, OutputType outputType, string pdbPath, PdbType pdbType)
978 {
979 this.SourcePaths = sourcePaths;
980 this.LocalizationPaths = localizationPaths;
981 this.LibraryPaths = libraryPaths;
982 this.Wixipls = wixipls;
983 this.OutputPath = outputPath;
984 this.OutputType = outputType;
985 this.PdbPath = pdbPath;
986 this.PdbType = pdbType;
987 }
988
989 public IReadOnlyCollection<string> SourcePaths { get; }
990
991 public IReadOnlyCollection<string> LocalizationPaths { get; }
992
993 public IReadOnlyCollection<string> LibraryPaths { get; }
994
995 public IReadOnlyCollection<Intermediate> Wixipls { get; }
996
997 public string OutputPath { get; set; }
998
999 public OutputType OutputType { get; }
1000
1001 public string PdbPath { get; }
1002
1003 public PdbType PdbType { get; }
1004 }
1005 }
1006 }