| 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 | } |