From ecf3a0cca5a424a91ab98557d963d2535963d582 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 22 Dec 2017 15:53:01 -0800 Subject: Reintroduce binder extensions and light.exe for binding .wixouts --- src/light/light.cs | 595 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 595 insertions(+) create mode 100644 src/light/light.cs (limited to 'src/light/light.cs') diff --git a/src/light/light.cs b/src/light/light.cs new file mode 100644 index 00000000..c0967caa --- /dev/null +++ b/src/light/light.cs @@ -0,0 +1,595 @@ +// 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"); + } + } + } + } +} -- cgit v1.2.3-55-g6feb