// 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. namespace WixToolset.Tools { using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using WixToolset.Core; using WixToolset.Data; using WixToolset.Data.Bind; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; /// /// The main entry point for light. /// public sealed class Light { LightCommandLine commandLine; private IEnumerable extensionData; //private IEnumerable binderExtensions; //private IEnumerable fileManagers; /// /// The main entry point for light. /// /// Commandline arguments for the application. /// Returns the application error code. [MTAThread] public static int Main(string[] args) { var serviceProvider = new WixToolsetServiceProvider(); var listener = new ConsoleMessageListener("WIX", "light.exe"); Light light = new Light(); return light.Run(serviceProvider, listener, args); } /// /// Main running method for the application. /// /// Commandline arguments to the application. /// Returns the application error code. public int Run(IServiceProvider serviceProvider, IMessageListener listener, string[] args) { var messaging = serviceProvider.GetService(); messaging.SetListener(listener); try { var unparsed = this.ParseCommandLineAndLoadExtensions(serviceProvider, messaging, args); if (!messaging.EncounteredError) { if (this.commandLine.ShowLogo) { AppCommon.DisplayToolHeader(); } if (this.commandLine.ShowHelp) { PrintHelp(); AppCommon.DisplayToolFooter(); } else { foreach (string arg in unparsed) { messaging.Write(WarningMessages.UnsupportedCommandLineArgument(arg)); } this.Bind(serviceProvider, messaging); } } } catch (WixException we) { messaging.Write(we.Error); } catch (Exception e) { messaging.Write(ErrorMessages.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace)); if (e is NullReferenceException || e is SEHException) { throw; } } return messaging.LastErrorNumber; } /// /// Parse command line and load all the extensions. /// /// Command line arguments to be parsed. private IEnumerable ParseCommandLineAndLoadExtensions(IServiceProvider serviceProvider, IMessaging messaging, string[] args) { this.commandLine = new LightCommandLine(messaging); string[] unprocessed = this.commandLine.Parse(args); if (messaging.EncounteredError) { return unprocessed; } // Load extensions. var extensionManager = CreateExtensionManagerWithStandardBackends(serviceProvider); foreach (string extension in this.commandLine.Extensions) { extensionManager.Load(extension); } // Extension data command line processing. var context = serviceProvider.GetService(); context.Arguments = null; context.ExtensionManager = extensionManager; context.Messaging = messaging; context.ParsedArguments = args; var commandLineExtensions = extensionManager.Create(); foreach (var extension in commandLineExtensions) { extension.PreParse(context); } // Process unproccessed arguments. List actuallyUnprocessed = new List(); foreach (var arg in unprocessed) { if (!this.TryParseCommandLineArgumentWithExtension(arg, commandLineExtensions)) { actuallyUnprocessed.Add(arg); } } return this.commandLine.ParsePostExtensions(actuallyUnprocessed.ToArray()); } private void Bind(IServiceProvider serviceProvider, IMessaging messaging) { var output = this.LoadWixout(messaging); if (messaging.EncounteredError) { return; } var intermediateFolder = this.commandLine.IntermediateFolder; if (String.IsNullOrEmpty(intermediateFolder)) { intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); } var localizations = this.LoadLocalizationFiles(messaging, this.commandLine.LocalizationFiles); if (messaging.EncounteredError) { return; } ResolveResult resolveResult; { var resolver = new Resolver(serviceProvider); resolver.BindPaths = this.commandLine.BindPaths; resolver.IntermediateFolder = intermediateFolder; resolver.IntermediateRepresentation = output; resolver.Localizations = localizations; resolveResult = resolver.Execute(); } if (messaging.EncounteredError) { return; } BindResult bindResult; { var binder = new Binder(serviceProvider); binder.CabbingThreadCount = this.commandLine.CabbingThreadCount; binder.CabCachePath = this.commandLine.CabCachePath; binder.Codepage = resolveResult.Codepage; binder.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel; binder.DelayedFields = resolveResult.DelayedFields; binder.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; binder.Ices = this.commandLine.Ices; binder.IntermediateFolder = intermediateFolder; binder.IntermediateRepresentation = resolveResult.IntermediateRepresentation; binder.OutputPath = this.commandLine.OutputFile; binder.OutputPdbPath = Path.ChangeExtension(this.commandLine.OutputFile, ".wixpdb"); binder.SuppressIces = this.commandLine.SuppressIces; binder.SuppressValidation = this.commandLine.SuppressValidation; bindResult = binder.Execute(); } if (messaging.EncounteredError) { return; } { var layout = new Layout(serviceProvider); layout.FileTransfers = bindResult.FileTransfers; layout.ContentFilePaths = bindResult.ContentFilePaths; layout.ContentsFile = this.commandLine.ContentsFile; layout.OutputsFile = this.commandLine.OutputsFile; layout.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; layout.SuppressAclReset = this.commandLine.SuppressAclReset; layout.Execute(); } } private void Run(IMessaging messaging) { #if false // Initialize the variable resolver from the command line. WixVariableResolver wixVariableResolver = new WixVariableResolver(); foreach (var wixVar in this.commandLine.Variables) { wixVariableResolver.AddVariable(wixVar.Key, wixVar.Value); } // Initialize the linker from the command line. Linker linker = new Linker(); linker.UnreferencedSymbolsFile = this.commandLine.UnreferencedSymbolsFile; linker.ShowPedanticMessages = this.commandLine.ShowPedanticMessages; linker.WixVariableResolver = wixVariableResolver; foreach (IExtensionData data in this.extensionData) { linker.AddExtensionData(data); } // Initialize the binder from the command line. WixToolset.Binder binder = new WixToolset.Binder(); binder.CabCachePath = this.commandLine.CabCachePath; binder.ContentsFile = this.commandLine.ContentsFile; binder.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; binder.OutputsFile = this.commandLine.OutputsFile; binder.WixprojectFile = this.commandLine.WixprojectFile; binder.BindPaths.AddRange(this.commandLine.BindPaths); binder.CabbingThreadCount = this.commandLine.CabbingThreadCount; if (this.commandLine.DefaultCompressionLevel.HasValue) { binder.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel.Value; } binder.Ices.AddRange(this.commandLine.Ices); binder.SuppressIces.AddRange(this.commandLine.SuppressIces); binder.SuppressAclReset = this.commandLine.SuppressAclReset; binder.SuppressLayout = this.commandLine.SuppressLayout; binder.SuppressValidation = this.commandLine.SuppressValidation; binder.PdbFile = this.commandLine.SuppressWixPdb ? null : this.commandLine.PdbFile; binder.TempFilesLocation = AppCommon.GetTempLocation(); binder.WixVariableResolver = wixVariableResolver; foreach (IBinderExtension extension in this.binderExtensions) { binder.AddExtension(extension); } foreach (IBinderFileManager fileManager in this.fileManagers) { binder.AddExtension(fileManager); } // Initialize the localizer. Localizer localizer = this.InitializeLocalization(linker.TableDefinitions); if (messaging.EncounteredError) { return; } wixVariableResolver.Localizer = localizer; linker.Localizer = localizer; binder.Localizer = localizer; // Loop through all the believed object files. List
sections = new List
(); Output output = null; foreach (string inputFile in this.commandLine.Files) { string inputFileFullPath = Path.GetFullPath(inputFile); FileFormat format = FileStructure.GuessFileFormatFromExtension(Path.GetExtension(inputFileFullPath)); bool retry; do { retry = false; try { switch (format) { case FileFormat.Wixobj: Intermediate intermediate = Intermediate.Load(inputFileFullPath, linker.TableDefinitions, this.commandLine.SuppressVersionCheck); sections.AddRange(intermediate.Sections); break; case FileFormat.Wixlib: Library library = Library.Load(inputFileFullPath, linker.TableDefinitions, this.commandLine.SuppressVersionCheck); AddLibraryLocalizationsToLocalizer(library, this.commandLine.Cultures, localizer); sections.AddRange(library.Sections); break; default: output = Output.Load(inputFileFullPath, this.commandLine.SuppressVersionCheck); break; } } catch (WixUnexpectedFileFormatException e) { format = e.FileFormat; retry = (FileFormat.Wixobj == format || FileFormat.Wixlib == format || FileFormat.Wixout == format); // .wixobj, .wixout and .wixout are supported by light. if (!retry) { messaging.OnMessage(e.Error); } } } while (retry); } // Stop processing if any errors were found loading object files. if (messaging.EncounteredError) { return; } // and now for the fun part if (null == output) { OutputType expectedOutputType = OutputType.Unknown; if (!String.IsNullOrEmpty(this.commandLine.OutputFile)) { expectedOutputType = Output.GetOutputType(Path.GetExtension(this.commandLine.OutputFile)); } output = linker.Link(sections, expectedOutputType); // If an error occurred during linking, stop processing. if (null == output) { return; } } else if (0 != sections.Count) { throw new InvalidOperationException(LightStrings.EXP_CannotLinkObjFilesWithOutpuFile); } bool tidy = true; // clean up after ourselves by default. try { // only output the xml if its a patch build or user specfied to only output wixout string outputFile = this.commandLine.OutputFile; string outputExtension = Path.GetExtension(outputFile); if (this.commandLine.OutputXml || OutputType.Patch == output.Type) { if (String.IsNullOrEmpty(outputExtension) || outputExtension.Equals(".wix", StringComparison.Ordinal)) { outputExtension = (OutputType.Patch == output.Type) ? ".wixmsp" : ".wixout"; outputFile = Path.ChangeExtension(outputFile, outputExtension); } output.Save(outputFile); } else // finish creating the MSI/MSM { if (String.IsNullOrEmpty(outputExtension) || outputExtension.Equals(".wix", StringComparison.Ordinal)) { outputExtension = Output.GetExtension(output.Type); outputFile = Path.ChangeExtension(outputFile, outputExtension); } binder.Bind(output, outputFile); } } catch (WixException we) // keep files around for debugging IDT issues. { if (we is WixInvalidIdtException) { tidy = false; } throw; } catch (Exception) // keep files around for debugging unexpected exceptions. { tidy = false; throw; } finally { if (null != binder) { binder.Cleanup(tidy); } } return; #endif } #if false private Localizer InitializeLocalization(TableDefinitionCollection tableDefinitions) { Localizer localizer = null; // Instantiate the localizer and load any localization files. if (!this.commandLine.SuppressLocalization || 0 < this.commandLine.LocalizationFiles.Count || null != this.commandLine.Cultures || !this.commandLine.OutputXml) { List localizations = new List(); // Load each localization file. foreach (string localizationFile in this.commandLine.LocalizationFiles) { Localization localization = Localizer.ParseLocalizationFile(localizationFile, tableDefinitions); if (null != localization) { localizations.Add(localization); } } localizer = new Localizer(); if (null != this.commandLine.Cultures) { // Alocalizations in order specified in cultures. foreach (string culture in this.commandLine.Cultures) { foreach (Localization localization in localizations) { if (culture.Equals(localization.Culture, StringComparison.OrdinalIgnoreCase)) { localizer.AddLocalization(localization); } } } } else // no cultures specified, so try neutral culture and if none of those add all loc files. { bool neutralFound = false; foreach (Localization localization in localizations) { if (String.IsNullOrEmpty(localization.Culture)) { // If a neutral wxl was provided use it. localizer.AddLocalization(localization); neutralFound = true; } } if (!neutralFound) { // No cultures were specified and no neutral wxl are available, include all of the loc files. foreach (Localization localization in localizations) { localizer.AddLocalization(localization); } } } // Load localizations provided by extensions with data. foreach (IExtensionData data in this.extensionData) { Library library = data.GetLibrary(tableDefinitions); if (null != library) { // Load the extension's default culture if it provides one and no cultures were specified. string[] extensionCultures = this.commandLine.Cultures; if (null == extensionCultures && null != data.DefaultCulture) { extensionCultures = new string[] { data.DefaultCulture }; } AddLibraryLocalizationsToLocalizer(library, extensionCultures, localizer); } } } return localizer; } private void AddLibraryLocalizationsToLocalizer(Library library, string[] cultures, Localizer localizer) { foreach (Localization localization in library.GetLocalizations(cultures)) { localizer.AddLocalization(localization); } } #endif private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable extensions) { foreach (var extension in extensions) { // TODO: decide what to do with "IParseCommandLine" argument. if (extension.TryParseArgument(null, arg)) { return true; } } return false; } private IEnumerable LoadLocalizationFiles(IMessaging messaging, IEnumerable locFiles) { foreach (var loc in locFiles) { var localization = Localizer.ParseLocalizationFile(messaging, loc); yield return localization; } } private Intermediate LoadWixout(IMessaging messaging) { var path = this.commandLine.Files.Single(); return Intermediate.Load(path); } private static IExtensionManager CreateExtensionManagerWithStandardBackends(IServiceProvider serviceProvider) { var extensionManager = serviceProvider.GetService(); foreach (var type in new[] { typeof(WixToolset.Core.Burn.WixToolsetStandardBackend), typeof(WixToolset.Core.WindowsInstaller.WixToolsetStandardBackend) }) { extensionManager.Add(type.Assembly); } return extensionManager; } private static void PrintHelp() { string lightArgs = LightStrings.CommandLineArguments; Console.WriteLine(String.Format(LightStrings.HelpMessage, lightArgs)); } private class ConsoleMessageListener : IMessageListener { public ConsoleMessageListener(string shortName, string longName) { this.ShortAppName = shortName; this.LongAppName = longName; PrepareConsoleForLocalization(); } public string LongAppName { get; } public string ShortAppName { get; } public void Write(Message message) { var filename = message.SourceLineNumbers?.FileName ?? this.LongAppName; var line = message.SourceLineNumbers?.LineNumber ?? -1; var type = message.Level.ToString().ToLowerInvariant(); var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error; if (line > 0) { filename = String.Concat(filename, "(", line, ")"); } output.WriteLine("{0} : {1} {2}{3:0000}: {4}", filename, type, this.ShortAppName, message.Id, message.ToString()); } public void Write(string message) { Console.Out.WriteLine(message); } private static void PrepareConsoleForLocalization() { Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture(); if (Console.OutputEncoding.CodePage != Encoding.UTF8.CodePage && Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage && Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.ANSICodePage) { Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); } } } } }