aboutsummaryrefslogtreecommitdiff
path: root/src/light/light.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-12-22 15:53:01 -0800
committerRob Mensching <rob@firegiant.com>2017-12-22 15:53:01 -0800
commitecf3a0cca5a424a91ab98557d963d2535963d582 (patch)
tree06355e906e5c404480dc6eac342b9b4d2ec9d122 /src/light/light.cs
parentdc9f4c329e6f55ce7595970463e0caf148096f4b (diff)
downloadwix-ecf3a0cca5a424a91ab98557d963d2535963d582.tar.gz
wix-ecf3a0cca5a424a91ab98557d963d2535963d582.tar.bz2
wix-ecf3a0cca5a424a91ab98557d963d2535963d582.zip
Reintroduce binder extensions and light.exe for binding .wixouts
Diffstat (limited to 'src/light/light.cs')
-rw-r--r--src/light/light.cs595
1 files changed, 595 insertions, 0 deletions
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Tools
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Text;
12 using System.Threading;
13 using WixToolset.Core;
14 using WixToolset.Data;
15 using WixToolset.Data.Bind;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Services;
18
19 /// <summary>
20 /// The main entry point for light.
21 /// </summary>
22 public sealed class Light
23 {
24 LightCommandLine commandLine;
25 private IEnumerable<IExtensionData> extensionData;
26 //private IEnumerable<IBinderExtension> binderExtensions;
27 //private IEnumerable<IBinderFileManager> fileManagers;
28
29 /// <summary>
30 /// The main entry point for light.
31 /// </summary>
32 /// <param name="args">Commandline arguments for the application.</param>
33 /// <returns>Returns the application error code.</returns>
34 [MTAThread]
35 public static int Main(string[] args)
36 {
37 var serviceProvider = new WixToolsetServiceProvider();
38
39 var listener = new ConsoleMessageListener("WIX", "light.exe");
40
41 Light light = new Light();
42 return light.Run(serviceProvider, listener, args);
43 }
44
45 /// <summary>
46 /// Main running method for the application.
47 /// </summary>
48 /// <param name="args">Commandline arguments to the application.</param>
49 /// <returns>Returns the application error code.</returns>
50 public int Run(IServiceProvider serviceProvider, IMessageListener listener, string[] args)
51 {
52 var messaging = serviceProvider.GetService<IMessaging>();
53 messaging.SetListener(listener);
54
55 try
56 {
57 var unparsed = this.ParseCommandLineAndLoadExtensions(serviceProvider, messaging, args);
58
59 if (!messaging.EncounteredError)
60 {
61 if (this.commandLine.ShowLogo)
62 {
63 AppCommon.DisplayToolHeader();
64 }
65
66 if (this.commandLine.ShowHelp)
67 {
68 PrintHelp();
69 AppCommon.DisplayToolFooter();
70 }
71 else
72 {
73 foreach (string arg in unparsed)
74 {
75 messaging.Write(WarningMessages.UnsupportedCommandLineArgument(arg));
76 }
77
78 this.Bind(serviceProvider, messaging);
79 }
80 }
81 }
82 catch (WixException we)
83 {
84 messaging.Write(we.Error);
85 }
86 catch (Exception e)
87 {
88 messaging.Write(ErrorMessages.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
89 if (e is NullReferenceException || e is SEHException)
90 {
91 throw;
92 }
93 }
94
95 return messaging.LastErrorNumber;
96 }
97
98 /// <summary>
99 /// Parse command line and load all the extensions.
100 /// </summary>
101 /// <param name="args">Command line arguments to be parsed.</param>
102 private IEnumerable<string> ParseCommandLineAndLoadExtensions(IServiceProvider serviceProvider, IMessaging messaging, string[] args)
103 {
104 this.commandLine = new LightCommandLine(messaging);
105
106 string[] unprocessed = this.commandLine.Parse(args);
107 if (messaging.EncounteredError)
108 {
109 return unprocessed;
110 }
111
112 // Load extensions.
113 var extensionManager = CreateExtensionManagerWithStandardBackends(serviceProvider);
114 foreach (string extension in this.commandLine.Extensions)
115 {
116 extensionManager.Load(extension);
117 }
118
119 // Extension data command line processing.
120 var context = serviceProvider.GetService<ICommandLineContext>();
121 context.Arguments = null;
122 context.ExtensionManager = extensionManager;
123 context.Messaging = messaging;
124 context.ParsedArguments = args;
125
126 var commandLineExtensions = extensionManager.Create<IExtensionCommandLine>();
127 foreach (var extension in commandLineExtensions)
128 {
129 extension.PreParse(context);
130 }
131
132 // Process unproccessed arguments.
133 List<string> actuallyUnprocessed = new List<string>();
134 foreach (var arg in unprocessed)
135 {
136 if (!this.TryParseCommandLineArgumentWithExtension(arg, commandLineExtensions))
137 {
138 actuallyUnprocessed.Add(arg);
139 }
140 }
141
142 return this.commandLine.ParsePostExtensions(actuallyUnprocessed.ToArray());
143 }
144
145 private void Bind(IServiceProvider serviceProvider, IMessaging messaging)
146 {
147 var output = this.LoadWixout(messaging);
148
149 if (messaging.EncounteredError)
150 {
151 return;
152 }
153
154 var intermediateFolder = this.commandLine.IntermediateFolder;
155 if (String.IsNullOrEmpty(intermediateFolder))
156 {
157 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
158 }
159
160 var localizations = this.LoadLocalizationFiles(messaging, this.commandLine.LocalizationFiles);
161
162 if (messaging.EncounteredError)
163 {
164 return;
165 }
166
167 ResolveResult resolveResult;
168 {
169 var resolver = new Resolver(serviceProvider);
170 resolver.BindPaths = this.commandLine.BindPaths;
171 resolver.IntermediateFolder = intermediateFolder;
172 resolver.IntermediateRepresentation = output;
173 resolver.Localizations = localizations;
174
175 resolveResult = resolver.Execute();
176 }
177
178 if (messaging.EncounteredError)
179 {
180 return;
181 }
182
183 BindResult bindResult;
184 {
185 var binder = new Binder(serviceProvider);
186 binder.CabbingThreadCount = this.commandLine.CabbingThreadCount;
187 binder.CabCachePath = this.commandLine.CabCachePath;
188 binder.Codepage = resolveResult.Codepage;
189 binder.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel;
190 binder.DelayedFields = resolveResult.DelayedFields;
191 binder.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
192 binder.Ices = this.commandLine.Ices;
193 binder.IntermediateFolder = intermediateFolder;
194 binder.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
195 binder.OutputPath = this.commandLine.OutputFile;
196 binder.OutputPdbPath = Path.ChangeExtension(this.commandLine.OutputFile, ".wixpdb");
197 binder.SuppressIces = this.commandLine.SuppressIces;
198 binder.SuppressValidation = this.commandLine.SuppressValidation;
199
200 bindResult = binder.Execute();
201 }
202
203 if (messaging.EncounteredError)
204 {
205 return;
206 }
207
208 {
209 var layout = new Layout(serviceProvider);
210 layout.FileTransfers = bindResult.FileTransfers;
211 layout.ContentFilePaths = bindResult.ContentFilePaths;
212 layout.ContentsFile = this.commandLine.ContentsFile;
213 layout.OutputsFile = this.commandLine.OutputsFile;
214 layout.BuiltOutputsFile = this.commandLine.BuiltOutputsFile;
215 layout.SuppressAclReset = this.commandLine.SuppressAclReset;
216
217 layout.Execute();
218 }
219 }
220
221 private void Run(IMessaging messaging)
222 {
223#if false
224 // Initialize the variable resolver from the command line.
225 WixVariableResolver wixVariableResolver = new WixVariableResolver();
226 foreach (var wixVar in this.commandLine.Variables)
227 {
228 wixVariableResolver.AddVariable(wixVar.Key, wixVar.Value);
229 }
230
231 // Initialize the linker from the command line.
232 Linker linker = new Linker();
233 linker.UnreferencedSymbolsFile = this.commandLine.UnreferencedSymbolsFile;
234 linker.ShowPedanticMessages = this.commandLine.ShowPedanticMessages;
235 linker.WixVariableResolver = wixVariableResolver;
236
237 foreach (IExtensionData data in this.extensionData)
238 {
239 linker.AddExtensionData(data);
240 }
241
242 // Initialize the binder from the command line.
243 WixToolset.Binder binder = new WixToolset.Binder();
244 binder.CabCachePath = this.commandLine.CabCachePath;
245 binder.ContentsFile = this.commandLine.ContentsFile;
246 binder.BuiltOutputsFile = this.commandLine.BuiltOutputsFile;
247 binder.OutputsFile = this.commandLine.OutputsFile;
248 binder.WixprojectFile = this.commandLine.WixprojectFile;
249 binder.BindPaths.AddRange(this.commandLine.BindPaths);
250 binder.CabbingThreadCount = this.commandLine.CabbingThreadCount;
251 if (this.commandLine.DefaultCompressionLevel.HasValue)
252 {
253 binder.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel.Value;
254 }
255 binder.Ices.AddRange(this.commandLine.Ices);
256 binder.SuppressIces.AddRange(this.commandLine.SuppressIces);
257 binder.SuppressAclReset = this.commandLine.SuppressAclReset;
258 binder.SuppressLayout = this.commandLine.SuppressLayout;
259 binder.SuppressValidation = this.commandLine.SuppressValidation;
260 binder.PdbFile = this.commandLine.SuppressWixPdb ? null : this.commandLine.PdbFile;
261 binder.TempFilesLocation = AppCommon.GetTempLocation();
262 binder.WixVariableResolver = wixVariableResolver;
263
264 foreach (IBinderExtension extension in this.binderExtensions)
265 {
266 binder.AddExtension(extension);
267 }
268
269 foreach (IBinderFileManager fileManager in this.fileManagers)
270 {
271 binder.AddExtension(fileManager);
272 }
273
274 // Initialize the localizer.
275 Localizer localizer = this.InitializeLocalization(linker.TableDefinitions);
276 if (messaging.EncounteredError)
277 {
278 return;
279 }
280
281 wixVariableResolver.Localizer = localizer;
282 linker.Localizer = localizer;
283 binder.Localizer = localizer;
284
285 // Loop through all the believed object files.
286 List<Section> sections = new List<Section>();
287 Output output = null;
288 foreach (string inputFile in this.commandLine.Files)
289 {
290 string inputFileFullPath = Path.GetFullPath(inputFile);
291 FileFormat format = FileStructure.GuessFileFormatFromExtension(Path.GetExtension(inputFileFullPath));
292 bool retry;
293 do
294 {
295 retry = false;
296
297 try
298 {
299 switch (format)
300 {
301 case FileFormat.Wixobj:
302 Intermediate intermediate = Intermediate.Load(inputFileFullPath, linker.TableDefinitions, this.commandLine.SuppressVersionCheck);
303 sections.AddRange(intermediate.Sections);
304 break;
305
306 case FileFormat.Wixlib:
307 Library library = Library.Load(inputFileFullPath, linker.TableDefinitions, this.commandLine.SuppressVersionCheck);
308 AddLibraryLocalizationsToLocalizer(library, this.commandLine.Cultures, localizer);
309 sections.AddRange(library.Sections);
310 break;
311
312 default:
313 output = Output.Load(inputFileFullPath, this.commandLine.SuppressVersionCheck);
314 break;
315 }
316 }
317 catch (WixUnexpectedFileFormatException e)
318 {
319 format = e.FileFormat;
320 retry = (FileFormat.Wixobj == format || FileFormat.Wixlib == format || FileFormat.Wixout == format); // .wixobj, .wixout and .wixout are supported by light.
321 if (!retry)
322 {
323 messaging.OnMessage(e.Error);
324 }
325 }
326 } while (retry);
327 }
328
329 // Stop processing if any errors were found loading object files.
330 if (messaging.EncounteredError)
331 {
332 return;
333 }
334
335 // and now for the fun part
336 if (null == output)
337 {
338 OutputType expectedOutputType = OutputType.Unknown;
339 if (!String.IsNullOrEmpty(this.commandLine.OutputFile))
340 {
341 expectedOutputType = Output.GetOutputType(Path.GetExtension(this.commandLine.OutputFile));
342 }
343
344 output = linker.Link(sections, expectedOutputType);
345
346 // If an error occurred during linking, stop processing.
347 if (null == output)
348 {
349 return;
350 }
351 }
352 else if (0 != sections.Count)
353 {
354 throw new InvalidOperationException(LightStrings.EXP_CannotLinkObjFilesWithOutpuFile);
355 }
356
357 bool tidy = true; // clean up after ourselves by default.
358 try
359 {
360 // only output the xml if its a patch build or user specfied to only output wixout
361 string outputFile = this.commandLine.OutputFile;
362 string outputExtension = Path.GetExtension(outputFile);
363 if (this.commandLine.OutputXml || OutputType.Patch == output.Type)
364 {
365 if (String.IsNullOrEmpty(outputExtension) || outputExtension.Equals(".wix", StringComparison.Ordinal))
366 {
367 outputExtension = (OutputType.Patch == output.Type) ? ".wixmsp" : ".wixout";
368 outputFile = Path.ChangeExtension(outputFile, outputExtension);
369 }
370
371 output.Save(outputFile);
372 }
373 else // finish creating the MSI/MSM
374 {
375 if (String.IsNullOrEmpty(outputExtension) || outputExtension.Equals(".wix", StringComparison.Ordinal))
376 {
377 outputExtension = Output.GetExtension(output.Type);
378 outputFile = Path.ChangeExtension(outputFile, outputExtension);
379 }
380
381 binder.Bind(output, outputFile);
382 }
383 }
384 catch (WixException we) // keep files around for debugging IDT issues.
385 {
386 if (we is WixInvalidIdtException)
387 {
388 tidy = false;
389 }
390
391 throw;
392 }
393 catch (Exception) // keep files around for debugging unexpected exceptions.
394 {
395 tidy = false;
396 throw;
397 }
398 finally
399 {
400 if (null != binder)
401 {
402 binder.Cleanup(tidy);
403 }
404 }
405
406 return;
407#endif
408 }
409
410#if false
411 private Localizer InitializeLocalization(TableDefinitionCollection tableDefinitions)
412 {
413 Localizer localizer = null;
414
415 // Instantiate the localizer and load any localization files.
416 if (!this.commandLine.SuppressLocalization || 0 < this.commandLine.LocalizationFiles.Count || null != this.commandLine.Cultures || !this.commandLine.OutputXml)
417 {
418 List<Localization> localizations = new List<Localization>();
419
420 // Load each localization file.
421 foreach (string localizationFile in this.commandLine.LocalizationFiles)
422 {
423 Localization localization = Localizer.ParseLocalizationFile(localizationFile, tableDefinitions);
424 if (null != localization)
425 {
426 localizations.Add(localization);
427 }
428 }
429
430 localizer = new Localizer();
431 if (null != this.commandLine.Cultures)
432 {
433 // Alocalizations in order specified in cultures.
434 foreach (string culture in this.commandLine.Cultures)
435 {
436 foreach (Localization localization in localizations)
437 {
438 if (culture.Equals(localization.Culture, StringComparison.OrdinalIgnoreCase))
439 {
440 localizer.AddLocalization(localization);
441 }
442 }
443 }
444 }
445 else // no cultures specified, so try neutral culture and if none of those add all loc files.
446 {
447 bool neutralFound = false;
448 foreach (Localization localization in localizations)
449 {
450 if (String.IsNullOrEmpty(localization.Culture))
451 {
452 // If a neutral wxl was provided use it.
453 localizer.AddLocalization(localization);
454 neutralFound = true;
455 }
456 }
457
458 if (!neutralFound)
459 {
460 // No cultures were specified and no neutral wxl are available, include all of the loc files.
461 foreach (Localization localization in localizations)
462 {
463 localizer.AddLocalization(localization);
464 }
465 }
466 }
467
468 // Load localizations provided by extensions with data.
469 foreach (IExtensionData data in this.extensionData)
470 {
471 Library library = data.GetLibrary(tableDefinitions);
472 if (null != library)
473 {
474 // Load the extension's default culture if it provides one and no cultures were specified.
475 string[] extensionCultures = this.commandLine.Cultures;
476 if (null == extensionCultures && null != data.DefaultCulture)
477 {
478 extensionCultures = new string[] { data.DefaultCulture };
479 }
480
481 AddLibraryLocalizationsToLocalizer(library, extensionCultures, localizer);
482 }
483 }
484 }
485
486 return localizer;
487 }
488
489 private void AddLibraryLocalizationsToLocalizer(Library library, string[] cultures, Localizer localizer)
490 {
491 foreach (Localization localization in library.GetLocalizations(cultures))
492 {
493 localizer.AddLocalization(localization);
494 }
495 }
496#endif
497
498 private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions)
499 {
500 foreach (var extension in extensions)
501 {
502 // TODO: decide what to do with "IParseCommandLine" argument.
503 if (extension.TryParseArgument(null, arg))
504 {
505 return true;
506 }
507 }
508
509 return false;
510 }
511
512 private IEnumerable<Localization> LoadLocalizationFiles(IMessaging messaging, IEnumerable<string> locFiles)
513 {
514 foreach (var loc in locFiles)
515 {
516 var localization = Localizer.ParseLocalizationFile(messaging, loc);
517
518 yield return localization;
519 }
520 }
521
522 private Intermediate LoadWixout(IMessaging messaging)
523 {
524 var path = this.commandLine.Files.Single();
525
526 return Intermediate.Load(path);
527 }
528
529 private static IExtensionManager CreateExtensionManagerWithStandardBackends(IServiceProvider serviceProvider)
530 {
531 var extensionManager = serviceProvider.GetService<IExtensionManager>();
532
533 foreach (var type in new[] { typeof(WixToolset.Core.Burn.WixToolsetStandardBackend), typeof(WixToolset.Core.WindowsInstaller.WixToolsetStandardBackend) })
534 {
535 extensionManager.Add(type.Assembly);
536 }
537
538 return extensionManager;
539 }
540
541 private static void PrintHelp()
542 {
543 string lightArgs = LightStrings.CommandLineArguments;
544
545 Console.WriteLine(String.Format(LightStrings.HelpMessage, lightArgs));
546 }
547
548 private class ConsoleMessageListener : IMessageListener
549 {
550 public ConsoleMessageListener(string shortName, string longName)
551 {
552 this.ShortAppName = shortName;
553 this.LongAppName = longName;
554
555 PrepareConsoleForLocalization();
556 }
557
558 public string LongAppName { get; }
559
560 public string ShortAppName { get; }
561
562 public void Write(Message message)
563 {
564 var filename = message.SourceLineNumbers?.FileName ?? this.LongAppName;
565 var line = message.SourceLineNumbers?.LineNumber ?? -1;
566 var type = message.Level.ToString().ToLowerInvariant();
567 var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error;
568
569 if (line > 0)
570 {
571 filename = String.Concat(filename, "(", line, ")");
572 }
573
574 output.WriteLine("{0} : {1} {2}{3:0000}: {4}", filename, type, this.ShortAppName, message.Id, message.ToString());
575 }
576
577 public void Write(string message)
578 {
579 Console.Out.WriteLine(message);
580 }
581
582 private static void PrepareConsoleForLocalization()
583 {
584 Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture();
585
586 if (Console.OutputEncoding.CodePage != Encoding.UTF8.CodePage &&
587 Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage &&
588 Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.ANSICodePage)
589 {
590 Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
591 }
592 }
593 }
594 }
595}