aboutsummaryrefslogtreecommitdiff
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
parentdc9f4c329e6f55ce7595970463e0caf148096f4b (diff)
downloadwix-ecf3a0cca5a424a91ab98557d963d2535963d582.tar.gz
wix-ecf3a0cca5a424a91ab98557d963d2535963d582.tar.bz2
wix-ecf3a0cca5a424a91ab98557d963d2535963d582.zip
Reintroduce binder extensions and light.exe for binding .wixouts
-rw-r--r--src/WixToolset.BuildTasks/WixToolTask.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs4
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs4
-rw-r--r--src/WixToolset.Core/BindContext.cs14
-rw-r--r--src/WixToolset.Core/Binder.cs461
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs62
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineContext.cs2
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineHelper.cs216
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineParser.cs (renamed from src/WixToolset.Core/CommandLine/CommandLine.cs)92
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs2
-rw-r--r--src/WixToolset.Core/CommandLine/CompileCommand.cs2
-rw-r--r--src/WixToolset.Core/CommandLine/HelpCommand.cs2
-rw-r--r--src/WixToolset.Core/CommandLine/VersionCommand.cs2
-rw-r--r--src/WixToolset.Core/Layout.cs26
-rw-r--r--src/WixToolset.Core/Resolver.cs29
-rw-r--r--src/WixToolset.Core/WixToolsetServiceProvider.cs3
-rw-r--r--src/light/App.icobin0 -> 1078 bytes
-rw-r--r--src/light/AssemblyInfo.cs9
-rw-r--r--src/light/LightCommandLine.cs485
-rw-r--r--src/light/LightStrings.Designer.cs104
-rw-r--r--src/light/LightStrings.resx174
-rw-r--r--src/light/app.config9
-rw-r--r--src/light/light.cs595
-rw-r--r--src/light/light.csproj21
-rw-r--r--src/test/WixToolsetTest.LightIntegration/LightFixture.cs48
-rw-r--r--src/test/WixToolsetTest.LightIntegration/TestData/Wixout/Package.en-us.wxl11
-rw-r--r--src/test/WixToolsetTest.LightIntegration/TestData/Wixout/data/test.txt1
-rw-r--r--src/test/WixToolsetTest.LightIntegration/TestData/Wixout/test.wixoutbin0 -> 10354 bytes
-rw-r--r--src/test/WixToolsetTest.LightIntegration/Utility/DisposableFileSystem.cs86
-rw-r--r--src/test/WixToolsetTest.LightIntegration/Utility/TestData.cs17
-rw-r--r--src/test/WixToolsetTest.LightIntegration/WixToolsetTest.LightIntegration.csproj33
31 files changed, 1956 insertions, 560 deletions
diff --git a/src/WixToolset.BuildTasks/WixToolTask.cs b/src/WixToolset.BuildTasks/WixToolTask.cs
index 7ccf0327..60305e00 100644
--- a/src/WixToolset.BuildTasks/WixToolTask.cs
+++ b/src/WixToolset.BuildTasks/WixToolTask.cs
@@ -4,7 +4,6 @@ namespace WixToolset.BuildTasks
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Diagnostics.CodeAnalysis; 7 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization; 8 using System.Globalization;
10 using System.IO; 9 using System.IO;
@@ -14,6 +13,7 @@ namespace WixToolset.BuildTasks
14 13
15 using Microsoft.Build.Framework; 14 using Microsoft.Build.Framework;
16 using Microsoft.Build.Utilities; 15 using Microsoft.Build.Utilities;
16 using WixToolset.Core.CommandLine;
17 17
18 /// <summary> 18 /// <summary>
19 /// Base class for WiX tool tasks; executes tools in-process 19 /// Base class for WiX tool tasks; executes tools in-process
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 2f161305..c47a1e56 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -22,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
22 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. 22 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
23 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); 23 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
24 24
25 public BindDatabaseCommand(WixToolset.Extensibility.IBindContext context, IEnumerable<IWindowsInstallerBackendExtension> backendExtension, Validator validator) 25 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendExtension> backendExtension, Validator validator)
26 { 26 {
27 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions(); 27 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions();
28 28
@@ -49,7 +49,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
49 49
50 private string CabCachePath { get; } 50 private string CabCachePath { get; }
51 51
52 private CompressionLevel DefaultCompressionLevel { get; } 52 private CompressionLevel? DefaultCompressionLevel { get; }
53 53
54 public IEnumerable<IDelayedField> DelayedFields { get; } 54 public IEnumerable<IDelayedField> DelayedFields { get; }
55 55
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index 28e7f6df..997ffa09 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -54,7 +54,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
54 /// Sets the default compression level to use for cabinets 54 /// Sets the default compression level to use for cabinets
55 /// that don't have their compression level explicitly set. 55 /// that don't have their compression level explicitly set.
56 /// </summary> 56 /// </summary>
57 public CompressionLevel DefaultCompressionLevel { private get; set; } 57 public CompressionLevel? DefaultCompressionLevel { private get; set; }
58 58
59 public IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { private get; set; } 59 public IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { private get; set; }
60 60
@@ -99,7 +99,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
99 { 99 {
100 var mediaTuple = entry.Key; 100 var mediaTuple = entry.Key;
101 IEnumerable<FileFacade> files = entry.Value; 101 IEnumerable<FileFacade> files = entry.Value;
102 CompressionLevel compressionLevel = this.DefaultCompressionLevel; 102 CompressionLevel compressionLevel = this.DefaultCompressionLevel ?? CompressionLevel.Medium;
103 103
104 string mediaLayoutFolder = null; 104 string mediaLayoutFolder = null;
105 105
diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs
index 8bdacf75..f423b731 100644
--- a/src/WixToolset.Core/BindContext.cs
+++ b/src/WixToolset.Core/BindContext.cs
@@ -27,13 +27,13 @@ namespace WixToolset.Core
27 27
28 public int Codepage { get; set; } 28 public int Codepage { get; set; }
29 29
30 public CompressionLevel DefaultCompressionLevel { get; set; } 30 public CompressionLevel? DefaultCompressionLevel { get; set; }
31 31
32 public IEnumerable<IDelayedField> DelayedFields { get; set; } 32 public IEnumerable<IDelayedField> DelayedFields { get; set; }
33 33
34 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; } 34 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; }
35 35
36 public IExtensionManager ExtensionManager { get; set; } 36 public IEnumerable<IBinderExtension> Extensions { get; set; }
37 37
38 public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; set; } 38 public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; set; }
39 39
@@ -50,15 +50,5 @@ namespace WixToolset.Core
50 public IEnumerable<string> SuppressIces { get; set; } 50 public IEnumerable<string> SuppressIces { get; set; }
51 51
52 public bool SuppressValidation { get; set; } 52 public bool SuppressValidation { get; set; }
53
54 public IBindVariableResolver WixVariableResolver { get; set; }
55
56 public string ContentsFile { get; set; }
57
58 public string OutputsFile { get; set; }
59
60 public string BuiltOutputsFile { get; set; }
61
62 public string WixprojectFile { get; set; }
63 } 53 }
64} 54}
diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs
index 9db27fec..c442c94d 100644
--- a/src/WixToolset.Core/Binder.cs
+++ b/src/WixToolset.Core/Binder.cs
@@ -5,10 +5,8 @@ namespace WixToolset.Core
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics; 7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq; 8 using System.Linq;
10 using System.Reflection; 9 using System.Reflection;
11 using WixToolset.Core.Bind;
12 using WixToolset.Data; 10 using WixToolset.Data;
13 using WixToolset.Data.Bind; 11 using WixToolset.Data.Bind;
14 using WixToolset.Data.Tuples; 12 using WixToolset.Data.Tuples;
@@ -20,334 +18,110 @@ namespace WixToolset.Core
20 /// </summary> 18 /// </summary>
21 public sealed class Binder 19 public sealed class Binder
22 { 20 {
23 //private BinderCore core; 21 public Binder(IServiceProvider serviceProvider)
24 //private List<IBinderExtension> extensions;
25 //private List<IBinderFileManager> fileManagers;
26
27 public Binder()
28 { 22 {
29 //this.DefaultCompressionLevel = CompressionLevel.High; 23 this.ServiceProvider = serviceProvider;
30
31 //this.BindPaths = new List<BindPath>();
32 //this.TargetBindPaths = new List<BindPath>();
33 //this.UpdatedBindPaths = new List<BindPath>();
34
35 //this.extensions = new List<IBinderExtension>();
36 //this.fileManagers = new List<IBinderFileManager>();
37 //this.inspectorExtensions = new List<InspectorExtension>();
38
39 //this.Ices = new List<string>();
40 //this.SuppressIces = new List<string>();
41 } 24 }
42 25
43 private IBindContext Context { get; set; } 26 public int CabbingThreadCount { get; set; }
44
45 //private TableDefinitionCollection TableDefinitions { get; }
46 27
47 //public IEnumerable<IBackendFactory> BackendFactories { get; set; } 28 public string CabCachePath { get; set; }
48 29
49 //public string ContentsFile { private get; set; } 30 public int Codepage { get; set; }
50 31
51 //public string OutputsFile { private get; set; } 32 public CompressionLevel? DefaultCompressionLevel { get; set; }
52 33
53 //public string BuiltOutputsFile { private get; set; } 34 public IEnumerable<IDelayedField> DelayedFields { get; set; }
54 35
55 //public string WixprojectFile { private get; set; } 36 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; }
56 37
57 /// <summary> 38 public IEnumerable<string> Ices { get; set; }
58 /// Gets the list of bindpaths.
59 /// </summary>
60 //public List<BindPath> BindPaths { get; private set; }
61 39
62 /// <summary> 40 public string IntermediateFolder { get; set; }
63 /// Gets the list of target bindpaths.
64 /// </summary>
65 //public List<BindPath> TargetBindPaths { get; private set; }
66 41
67 /// <summary> 42 public Intermediate IntermediateRepresentation { get; set; }
68 /// Gets the list of updated bindpaths.
69 /// </summary>
70 //public List<BindPath> UpdatedBindPaths { get; private set; }
71 43
72 /// <summary> 44 public string OutputPath { get; set; }
73 /// Gets or sets the option to enable building binary delta patches.
74 /// </summary>
75 /// <value>The option to enable building binary delta patches.</value>
76 public bool DeltaBinaryPatch { get; set; }
77 45
78 /// <summary> 46 public string OutputPdbPath { get; set; }
79 /// Gets or sets the cabinet cache location.
80 /// </summary>
81 public string CabCachePath { get; set; }
82 47
83 /// <summary> 48 public IEnumerable<string> SuppressIces { get; set; }
84 /// Gets or sets the number of threads to use for cabinet creation.
85 /// </summary>
86 /// <value>The number of threads to use for cabinet creation.</value>
87 public int CabbingThreadCount { get; set; }
88 49
89 /// <summary>
90 /// Gets or sets the default compression level to use for cabinets
91 /// that don't have their compression level explicitly set.
92 /// </summary>
93 //public CompressionLevel DefaultCompressionLevel { get; set; }
94
95 /// <summary>
96 /// Gets and sets the location to save the WixPdb.
97 /// </summary>
98 /// <value>The location in which to save the WixPdb. Null if the the WixPdb should not be output.</value>
99 //public string PdbFile { get; set; }
100
101 //public List<string> Ices { get; private set; }
102
103 //public List<string> SuppressIces { get; private set; }
104
105 /// <summary>
106 /// Gets and sets the option to suppress resetting ACLs by the binder.
107 /// </summary>
108 /// <value>The option to suppress resetting ACLs by the binder.</value>
109 public bool SuppressAclReset { get; set; }
110
111 /// <summary>
112 /// Gets and sets the option to suppress creating an image for MSI/MSM.
113 /// </summary>
114 /// <value>The option to suppress creating an image for MSI/MSM.</value>
115 public bool SuppressLayout { get; set; }
116
117 /// <summary>
118 /// Gets and sets the option to suppress MSI/MSM validation.
119 /// </summary>
120 /// <value>The option to suppress MSI/MSM validation.</value>
121 /// <remarks>This must be set before calling Bind.</remarks>
122 public bool SuppressValidation { get; set; } 50 public bool SuppressValidation { get; set; }
123 51
124 /// <summary> 52 public bool DeltaBinaryPatch { get; set; }
125 /// Gets and sets the option to suppress adding _Validation table rows.
126 /// </summary>
127 public bool SuppressAddingValidationRows { get; set; }
128
129 /// <summary>
130 /// Gets or sets the localizer.
131 /// </summary>
132 /// <value>The localizer.</value>
133 public Localizer Localizer { get; set; }
134
135 /// <summary>
136 /// Gets or sets the temporary path for the Binder. If left null, the binder
137 /// will use %TEMP% environment variable.
138 /// </summary>
139 /// <value>Path to temp files.</value>
140 public string TempFilesLocation { get; set; }
141
142 /// <summary>
143 /// Gets or sets the Wix variable resolver.
144 /// </summary>
145 /// <value>The Wix variable resolver.</value>
146 internal WixVariableResolver WixVariableResolver { get; set; }
147
148 public BindResult Bind(IBindContext context)
149 {
150 this.Context = context;
151 53
152 this.WriteBuildInfoTable(this.Context.IntermediateRepresentation, this.Context.OutputPath); 54 public IServiceProvider ServiceProvider { get; }
153 55
154 var bindResult = this.BackendBind(); 56 public BindResult Execute()
155 return bindResult;
156 }
157
158//// private ResolveResult Resolve()
159//// {
160//// var buildingPatch = this.Context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch);
161
162//// var filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
163
164//// IEnumerable<DelayedField> delayedFields;
165//// {
166//// var command = new ResolveFieldsCommand();
167//// command.Messaging = this.Context.Messaging;
168//// command.BuildingPatch = buildingPatch;
169//// command.BindVariableResolver = this.Context.WixVariableResolver;
170//// command.BindPaths = this.Context.BindPaths;
171//// command.Extensions = this.Context.Extensions;
172//// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
173//// command.IntermediateFolder = this.Context.IntermediateFolder;
174//// command.Intermediate = this.Context.IntermediateRepresentation;
175//// command.SupportDelayedResolution = true;
176//// command.Execute();
177
178//// delayedFields = command.DelayedFields;
179//// }
180
181////#if REVISIT_FOR_PATCHING
182//// if (this.Context.IntermediateRepresentation.SubStorages != null)
183//// {
184//// foreach (SubStorage transform in this.Context.IntermediateRepresentation.SubStorages)
185//// {
186//// var command = new ResolveFieldsCommand();
187//// command.BuildingPatch = buildingPatch;
188//// command.BindVariableResolver = this.Context.WixVariableResolver;
189//// command.BindPaths = this.Context.BindPaths;
190//// command.Extensions = this.Context.Extensions;
191//// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
192//// command.IntermediateFolder = this.Context.IntermediateFolder;
193//// command.Intermediate = this.Context.IntermediateRepresentation;
194//// command.SupportDelayedResolution = false;
195//// command.Execute();
196//// }
197//// }
198////#endif
199
200//// var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles();
201
202//// return new ResolveResult
203//// {
204//// ExpectedEmbeddedFiles = expectedEmbeddedFiles,
205//// DelayedFields = delayedFields,
206//// };
207//// }
208
209 private BindResult BackendBind()
210 { 57 {
211 var extensionManager = this.Context.ServiceProvider.GetService<IExtensionManager>(); 58 var context = this.ServiceProvider.GetService<IBindContext>();
59 context.Messaging = this.ServiceProvider.GetService<IMessaging>();
60 context.CabbingThreadCount = this.CabbingThreadCount;
61 context.CabCachePath = this.CabCachePath;
62 context.Codepage = this.Codepage;
63 context.DefaultCompressionLevel = this.DefaultCompressionLevel;
64 context.DelayedFields = this.DelayedFields;
65 context.ExpectedEmbeddedFiles = this.ExpectedEmbeddedFiles;
66 context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().Create<IBinderExtension>();
67 context.Ices = this.Ices;
68 context.IntermediateFolder = this.IntermediateFolder;
69 context.IntermediateRepresentation = this.IntermediateRepresentation;
70 context.OutputPath = this.OutputPath;
71 context.OutputPdbPath = this.OutputPdbPath;
72 context.SuppressIces = this.SuppressIces;
73 context.SuppressValidation = this.SuppressValidation;
74
75
76 // Prebind.
77 //
78 foreach (var extension in context.Extensions)
79 {
80 extension.PreBind(context);
81 }
212 82
213 var backendFactories = extensionManager.Create<IBackendFactory>(); 83 // Bind.
84 //
85 this.WriteBuildInfoTable(context.IntermediateRepresentation, context.OutputPath, context.OutputPdbPath);
214 86
215 var entrySection = this.Context.IntermediateRepresentation.Sections[0]; 87 var bindResult = this.BackendBind(context);
216 88
217 foreach (var factory in backendFactories) 89 if (bindResult != null)
218 { 90 {
219 if (factory.TryCreateBackend(entrySection.Type.ToString(), this.Context.OutputPath, null, out var backend)) 91 // Postbind.
92 //
93 foreach (var extension in context.Extensions)
220 { 94 {
221 var result = backend.Bind(this.Context); 95 extension.PostBind(bindResult);
222 return result;
223 } 96 }
224 } 97 }
225 98
226 // TODO: messaging that a backend could not be found to bind the output type? 99 return bindResult;
227
228 return null;
229 } 100 }
230 101
231 /// <summary> 102 private BindResult BackendBind(IBindContext context)
232 /// Binds an output.
233 /// </summary>
234 /// <param name="output">The output to bind.</param>
235 /// <param name="file">The Windows Installer file to create.</param>
236 /// <remarks>The Binder.DeleteTempFiles method should be called after calling this method.</remarks>
237 /// <returns>true if binding completed successfully; false otherwise</returns>
238#if false
239 public bool Bind(Output output, string file)
240 { 103 {
241 // Ensure the cabinet cache path exists if we are going to use it. 104 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
242 if (!String.IsNullOrEmpty(this.CabCachePath))
243 {
244 Directory.CreateDirectory(this.CabCachePath);
245 }
246
247 //var fileManagerCore = new BinderFileManagerCore();
248 //fileManagerCore.CabCachePath = this.CabCachePath;
249 //fileManagerCore.Output = output;
250 //fileManagerCore.TempFilesLocation = this.TempFilesLocation;
251 //fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal);
252 //fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target);
253 //fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated);
254 //foreach (IBinderFileManager fileManager in this.fileManagers)
255 //{
256 // fileManager.Core = fileManagerCore;
257 //}
258
259 this.core = new BinderCore();
260 this.core.FileManagerCore = fileManagerCore;
261
262 this.WriteBuildInfoTable(output, file);
263
264 // Initialize extensions.
265 foreach (IBinderExtension extension in this.extensions)
266 {
267 extension.Core = this.core;
268
269 extension.Initialize(output);
270 }
271
272 // Gather all the wix variables.
273 //Table wixVariableTable = output.Tables["WixVariable"];
274 //if (null != wixVariableTable)
275 //{
276 // foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows)
277 // {
278 // this.WixVariableResolver.AddVariable(wixVariableRow);
279 // }
280 //}
281
282 //BindContext context = new BindContext();
283 //context.CabbingThreadCount = this.CabbingThreadCount;
284 //context.DefaultCompressionLevel = this.DefaultCompressionLevel;
285 //context.Extensions = this.extensions;
286 //context.FileManagerCore = fileManagerCore;
287 //context.FileManagers = this.fileManagers;
288 //context.Ices = this.Ices;
289 //context.IntermediateFolder = this.TempFilesLocation;
290 //context.IntermediateRepresentation = output;
291 //context.Localizer = this.Localizer;
292 //context.OutputPath = file;
293 //context.OutputPdbPath = this.PdbFile;
294 //context.SuppressIces = this.SuppressIces;
295 //context.SuppressValidation = this.SuppressValidation;
296 //context.WixVariableResolver = this.WixVariableResolver;
297
298 BindResult result = null;
299
300 foreach (var factory in this.BackendFactories)
301 {
302 if (factory.TryCreateBackend(output.Type.ToString(), file, null, out var backend))
303 {
304 result = backend.Bind(context);
305 break;
306 }
307 }
308 105
309 if (result == null) 106 var backendFactories = extensionManager.Create<IBackendFactory>();
310 {
311 // TODO: messaging that a backend could not be found to bind the output type?
312 107
313 return false; 108 var entrySection = context.IntermediateRepresentation.Sections[0];
314 }
315 109
316 // Layout media 110 foreach (var factory in backendFactories)
317 try
318 {
319 this.LayoutMedia(result.FileTransfers);
320 }
321 finally
322 { 111 {
323 if (!String.IsNullOrEmpty(this.ContentsFile) && result.ContentFilePaths != null) 112 if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, null, out var backend))
324 {
325 this.CreateContentsFile(this.ContentsFile, result.ContentFilePaths);
326 }
327
328 if (!String.IsNullOrEmpty(this.OutputsFile) && result.FileTransfers != null)
329 { 113 {
330 this.CreateOutputsFile(this.OutputsFile, result.FileTransfers, this.PdbFile); 114 var result = backend.Bind(context);
331 } 115 return result;
332
333 if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && result.FileTransfers != null)
334 {
335 this.CreateBuiltOutputsFile(this.BuiltOutputsFile, result.FileTransfers, this.PdbFile);
336 } 116 }
337 } 117 }
338 118
339 this.core = null; 119 // TODO: messaging that a backend could not be found to bind the output type?
340 120
341 return Messaging.Instance.EncounteredError; 121 return null;
342 } 122 }
343#endif 123
344 124 private void WriteBuildInfoTable(Intermediate output, string outputFile, string outputPdbPath)
345 /// <summary>
346 /// Populates the WixBuildInfo table in an output.
347 /// </summary>
348 /// <param name="output">The output.</param>
349 /// <param name="databaseFile">The output file if OutputFile not set.</param>
350 private void WriteBuildInfoTable(Intermediate output, string outputFile)
351 { 125 {
352 var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); 126 var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment);
353 127
@@ -358,117 +132,12 @@ namespace WixToolset.Core
358 buildInfoRow.WixVersion = fileVersion.FileVersion; 132 buildInfoRow.WixVersion = fileVersion.FileVersion;
359 buildInfoRow.WixOutputFile = outputFile; 133 buildInfoRow.WixOutputFile = outputFile;
360 134
361 if (!String.IsNullOrEmpty(this.Context.WixprojectFile)) 135 if (!String.IsNullOrEmpty(outputPdbPath))
362 {
363 buildInfoRow.WixProjectFile = this.Context.WixprojectFile;
364 }
365
366 if (!String.IsNullOrEmpty(this.Context.OutputPdbPath))
367 { 136 {
368 buildInfoRow.WixPdbFile = this.Context.OutputPdbPath; 137 buildInfoRow.WixPdbFile = outputPdbPath;
369 } 138 }
370 139
371 entrySection.Tuples.Add(buildInfoRow); 140 entrySection.Tuples.Add(buildInfoRow);
372 } 141 }
373
374#if DELETE_THIS_CODE
375 /// <summary>
376 /// Binds a bundle.
377 /// </summary>
378 /// <param name="bundle">The bundle to bind.</param>
379 /// <param name="bundleFile">The bundle to create.</param>
380 private void BindBundle(Output bundle, string bundleFile, out IEnumerable<FileTransfer> fileTransfers, out IEnumerable<string> contentPaths)
381 {
382 BindBundleCommand command = new BindBundleCommand();
383 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
384 command.Extensions = this.extensions;
385 command.FileManagerCore = this.fileManagerCore;
386 command.FileManagers = this.fileManagers;
387 command.Output = bundle;
388 command.OutputPath = bundleFile;
389 command.PdbFile = this.PdbFile;
390 command.TableDefinitions = this.core.TableDefinitions;
391 command.TempFilesLocation = this.TempFilesLocation;
392 command.WixVariableResolver = this.WixVariableResolver;
393 command.Execute();
394
395 fileTransfers = command.FileTransfers;
396 contentPaths = command.ContentFilePaths;
397 }
398
399 /// <summary>
400 /// Binds a databse.
401 /// </summary>
402 /// <param name="output">The output to bind.</param>
403 /// <param name="databaseFile">The database file to create.</param>
404 private void BindDatabase(Output output, string databaseFile, out IEnumerable<FileTransfer> fileTransfers, out IEnumerable<string> contentPaths)
405 {
406 Validator validator = null;
407
408 // tell the binder about the validator if validation isn't suppressed
409 if (!this.SuppressValidation && (OutputType.Module == output.Type || OutputType.Product == output.Type))
410 {
411 validator = new Validator();
412 validator.TempFilesLocation = Path.Combine(this.TempFilesLocation, "validate");
413
414 // set the default cube file
415 string lightDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
416 string cubePath = (OutputType.Module == output.Type) ? Path.Combine(lightDirectory, "mergemod.cub") : Path.Combine(lightDirectory, "darice.cub");
417 validator.AddCubeFile(cubePath);
418
419 // by default, disable ICEs that have equivalent-or-better checks in WiX
420 this.SuppressIces.Add("ICE08");
421 this.SuppressIces.Add("ICE33");
422 this.SuppressIces.Add("ICE47");
423 this.SuppressIces.Add("ICE66");
424
425 // set the ICEs
426 validator.ICEs = this.Ices.ToArray();
427
428 // set the suppressed ICEs
429 validator.SuppressedICEs = this.SuppressIces.ToArray();
430 }
431
432 BindDatabaseCommand command = new BindDatabaseCommand();
433 command.CabbingThreadCount = this.CabbingThreadCount;
434 command.Codepage = this.Localizer == null ? -1 : this.Localizer.Codepage;
435 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
436 command.Extensions = this.extensions;
437 command.FileManagerCore = this.fileManagerCore;
438 command.FileManagers = this.fileManagers;
439 command.InspectorExtensions = this.inspectorExtensions;
440 command.Localizer = this.Localizer;
441 command.PdbFile = this.PdbFile;
442 command.Output = output;
443 command.OutputPath = databaseFile;
444 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
445 command.SuppressLayout = this.SuppressLayout;
446 command.TableDefinitions = this.core.TableDefinitions;
447 command.TempFilesLocation = this.TempFilesLocation;
448 command.Validator = validator;
449 command.WixVariableResolver = this.WixVariableResolver;
450 command.Execute();
451
452 fileTransfers = command.FileTransfers;
453 contentPaths = command.ContentFilePaths;
454 }
455
456 /// <summary>
457 /// Binds a transform.
458 /// </summary>
459 /// <param name="transform">The transform to bind.</param>
460 /// <param name="outputPath">The transform to create.</param>
461 private void BindTransform(Output transform, string outputPath)
462 {
463 BindTransformCommand command = new BindTransformCommand();
464 command.Extensions = this.extensions;
465 command.FileManagers = this.fileManagers;
466 command.TableDefinitions = this.core.TableDefinitions;
467 command.TempFilesLocation = this.TempFilesLocation;
468 command.Transform = transform;
469 command.OutputPath = outputPath;
470 command.Execute();
471 }
472#endif
473 } 142 }
474} 143}
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
index 92aa3343..5653afca 100644
--- a/src/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Core 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -13,7 +13,7 @@ namespace WixToolset.Core
13 13
14 internal class BuildCommand : ICommandLineCommand 14 internal class BuildCommand : ICommandLineCommand
15 { 15 {
16 public BuildCommand(IServiceProvider serviceProvider, IMessaging messaging, IExtensionManager extensions, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, IEnumerable<string> libraryFiles, string outputPath, OutputType outputType, string cabCachePath, IEnumerable<string> cultures, bool bindFiles, IEnumerable<BindPath> bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile) 16 public BuildCommand(IServiceProvider serviceProvider, IMessaging messaging, IExtensionManager extensions, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, IEnumerable<string> libraryFiles, string outputPath, OutputType outputType, string cabCachePath, IEnumerable<string> cultures, bool bindFiles, IEnumerable<BindPath> bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile)
17 { 17 {
18 this.ServiceProvider = serviceProvider; 18 this.ServiceProvider = serviceProvider;
19 this.Messaging = messaging; 19 this.Messaging = messaging;
@@ -34,7 +34,6 @@ namespace WixToolset.Core
34 this.ContentsFile = contentsFile; 34 this.ContentsFile = contentsFile;
35 this.OutputsFile = outputsFile; 35 this.OutputsFile = outputsFile;
36 this.BuiltOutputsFile = builtOutputsFile; 36 this.BuiltOutputsFile = builtOutputsFile;
37 this.WixProjectFile = wixProjectFile;
38 } 37 }
39 38
40 public IServiceProvider ServiceProvider { get; } 39 public IServiceProvider ServiceProvider { get; }
@@ -73,8 +72,6 @@ namespace WixToolset.Core
73 72
74 public string BuiltOutputsFile { get; } 73 public string BuiltOutputsFile { get; }
75 74
76 public string WixProjectFile { get; }
77
78 public int Execute() 75 public int Execute()
79 { 76 {
80 var intermediates = this.CompilePhase(); 77 var intermediates = this.CompilePhase();
@@ -207,7 +204,12 @@ namespace WixToolset.Core
207 204
208 ResolveResult resolveResult; 205 ResolveResult resolveResult;
209 { 206 {
210 var resolver = new Resolver(this.ServiceProvider, this.BindPaths, output, this.IntermediateFolder, localizations); 207 var resolver = new Resolver(this.ServiceProvider);
208 resolver.BindPaths = this.BindPaths;
209 resolver.IntermediateFolder = this.IntermediateFolder;
210 resolver.IntermediateRepresentation = output;
211 resolver.Localizations = localizations;
212
211 resolveResult = resolver.Execute(); 213 resolveResult = resolver.Execute();
212 } 214 }
213 215
@@ -224,28 +226,22 @@ namespace WixToolset.Core
224 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 226 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
225 } 227 }
226 228
227 var context = this.ServiceProvider.GetService<IBindContext>(); 229 var binder = new Binder(this.ServiceProvider);
228 context.Messaging = this.Messaging; 230 //binder.CabbingThreadCount = this.CabbingThreadCount;
229 //context.CabbingThreadCount = this.CabbingThreadCount; 231 binder.CabCachePath = this.CabCachePath;
230 context.CabCachePath = this.CabCachePath; 232 binder.Codepage = resolveResult.Codepage;
231 context.Codepage = resolveResult.Codepage; 233 //binder.DefaultCompressionLevel = this.DefaultCompressionLevel;
232 //context.DefaultCompressionLevel = this.DefaultCompressionLevel; 234 binder.DelayedFields = resolveResult.DelayedFields;
233 context.DelayedFields = resolveResult.DelayedFields; 235 binder.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
234 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; 236 binder.Ices = Array.Empty<string>(); // TODO: set this correctly
235 //context.Ices = this.Ices; 237 binder.IntermediateFolder = intermediateFolder;
236 context.IntermediateFolder = intermediateFolder; 238 binder.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
237 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; 239 binder.OutputPath = this.OutputPath;
238 context.OutputPath = this.OutputPath; 240 binder.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb");
239 context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb"); 241 binder.SuppressIces = Array.Empty<string>(); // TODO: set this correctly
240 //context.SuppressIces = this.SuppressIces; 242 binder.SuppressValidation = true; // TODO: set this correctly
241 context.SuppressValidation = true; // TODO: set this correctly 243
242 context.ContentsFile = this.ContentsFile; 244 bindResult = binder.Execute();
243 context.OutputsFile = this.OutputsFile;
244 context.BuiltOutputsFile = this.BuiltOutputsFile;
245 context.WixprojectFile = this.WixProjectFile;
246
247 var binder = new Binder();
248 bindResult = binder.Bind(context);
249 } 245 }
250 246
251 if (this.Messaging.EncounteredError) 247 if (this.Messaging.EncounteredError)
@@ -254,8 +250,14 @@ namespace WixToolset.Core
254 } 250 }
255 251
256 { 252 {
257 // TODO: correctly set SuppressAclReset bool at the end. 253 var layout = new Layout(this.ServiceProvider);
258 var layout = new Layout(this.ServiceProvider, bindResult.FileTransfers, bindResult.ContentFilePaths, this.ContentsFile, this.OutputsFile, this.BuiltOutputsFile, false); 254 layout.FileTransfers = bindResult.FileTransfers;
255 layout.ContentFilePaths = bindResult.ContentFilePaths;
256 layout.ContentsFile = this.ContentsFile;
257 layout.OutputsFile = this.OutputsFile;
258 layout.BuiltOutputsFile = this.BuiltOutputsFile;
259 layout.SuppressAclReset = false; // TODO: correctly set SuppressAclReset
260
259 layout.Execute(); 261 layout.Execute();
260 } 262 }
261 } 263 }
diff --git a/src/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/WixToolset.Core/CommandLine/CommandLineContext.cs
index cbb9af53..2ff2c1fd 100644
--- a/src/WixToolset.Core/CommandLine/CommandLineContext.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLineContext.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Core 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using WixToolset.Extensibility.Services; 6 using WixToolset.Extensibility.Services;
diff --git a/src/WixToolset.Core/CommandLine/CommandLineHelper.cs b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
new file mode 100644
index 00000000..51ece0f7
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
@@ -0,0 +1,216 @@
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.Core.CommandLine
4{
5 using System;
6 using System.IO;
7 using WixToolset.Data;
8 using WixToolset.Extensibility.Services;
9
10 public class CommandLineHelper
11 {
12 /// <summary>
13 /// Validates that a string is a valid directory name, and throws appropriate warnings/errors if not
14 /// </summary>
15 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
16 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
17 /// <param name="args">The list of strings to check.</param>
18 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
19 /// <returns>The string if it is valid, null if it is invalid.</returns>
20 public static string GetDirectory(string commandlineSwitch, IMessaging messageHandler, string[] args, int index)
21 {
22 return GetDirectory(commandlineSwitch, messageHandler, args, index, false);
23 }
24
25 /// <summary>
26 /// Validates that a string is a valid directory name, and throws appropriate warnings/errors if not
27 /// </summary>
28 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
29 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
30 /// <param name="args">The list of strings to check.</param>
31 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
32 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
33 /// <returns>The string if it is valid, null if it is invalid.</returns>
34 public static string GetDirectory(string commandlineSwitch, IMessaging messageHandler, string[] args, int index, bool allowPrefix)
35 {
36 commandlineSwitch = String.Concat("-", commandlineSwitch);
37
38 if (!IsValidArg(args, index))
39 {
40 messageHandler.Write(ErrorMessages.DirectoryPathRequired(commandlineSwitch));
41 return null;
42 }
43
44 if (File.Exists(args[index]))
45 {
46 messageHandler.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, args[index]));
47 return null;
48 }
49
50 return VerifyPath(messageHandler, args[index], allowPrefix);
51 }
52
53 /// <summary>
54 /// Validates that a string is a valid filename, and throws appropriate warnings/errors if not
55 /// </summary>
56 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
57 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
58 /// <param name="args">The list of strings to check.</param>
59 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
60 /// <returns>The string if it is valid, null if it is invalid.</returns>
61 public static string GetFile(string commandlineSwitch, IMessaging messageHandler, string[] args, int index)
62 {
63 commandlineSwitch = String.Concat("-", commandlineSwitch);
64
65 if (!IsValidArg(args, index))
66 {
67 messageHandler.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
68 return null;
69 }
70
71 if (Directory.Exists(args[index]))
72 {
73 messageHandler.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, args[index]));
74 return null;
75 }
76
77 return VerifyPath(messageHandler, args[index]);
78 }
79
80 /// <summary>
81 /// Get a set of files that possibly have a search pattern in the path (such as '*').
82 /// </summary>
83 /// <param name="searchPath">Search path to find files in.</param>
84 /// <param name="fileType">Type of file; typically "Source".</param>
85 /// <returns>An array of files matching the search path.</returns>
86 /// <remarks>
87 /// This method is written in this verbose way because it needs to support ".." in the path.
88 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
89 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
90 /// Path.GetDirectoryName does not support ".." in the path.
91 /// </remarks>
92 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
93 public static string[] GetFiles(string searchPath, string fileType)
94 {
95 if (null == searchPath)
96 {
97 throw new ArgumentNullException(nameof(searchPath));
98 }
99
100 // Convert alternate directory separators to the standard one.
101 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
102 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
103 string[] files = null;
104
105 try
106 {
107 if (0 > lastSeparator)
108 {
109 files = Directory.GetFiles(".", filePath);
110 }
111 else // found directory separator
112 {
113 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
114 }
115 }
116 catch (DirectoryNotFoundException)
117 {
118 // Don't let this function throw the DirectoryNotFoundException. This exception
119 // occurs for non-existant directories and invalid characters in the searchPattern.
120 }
121 catch (ArgumentException)
122 {
123 // Don't let this function throw the ArgumentException. This exception
124 // occurs in certain situations such as when passing a malformed UNC path.
125 }
126 catch (IOException)
127 {
128 throw new WixFileNotFoundException(searchPath, fileType);
129 }
130
131 if (null == files || 0 == files.Length)
132 {
133 throw new WixFileNotFoundException(searchPath, fileType);
134 }
135
136 return files;
137 }
138
139 /// <summary>
140 /// Validates that a valid string parameter (without "/" or "-"), and returns a bool indicating its validity
141 /// </summary>
142 /// <param name="args">The list of strings to check.</param>
143 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
144 /// <returns>True if a valid string parameter exists there, false if not.</returns>
145 public static bool IsValidArg(string[] args, int index)
146 {
147 if (args.Length <= index || String.IsNullOrEmpty(args[index]) || '/' == args[index][0] || '-' == args[index][0])
148 {
149 return false;
150 }
151 else
152 {
153 return true;
154 }
155 }
156
157 /// <summary>
158 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
159 /// </summary>
160 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
161 /// <param name="path">The path to test.</param>
162 /// <returns>The string if it is valid, null if it is invalid.</returns>
163 public static string VerifyPath(IMessaging messageHandler, string path)
164 {
165 return VerifyPath(messageHandler, path, false);
166 }
167
168 /// <summary>
169 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
170 /// </summary>
171 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
172 /// <param name="path">The path to test.</param>
173 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
174 /// <returns>The full path if it is valid, null if it is invalid.</returns>
175 public static string VerifyPath(IMessaging messageHandler, string path, bool allowPrefix)
176 {
177 string fullPath;
178
179 if (0 <= path.IndexOf('\"'))
180 {
181 messageHandler.Write(ErrorMessages.PathCannotContainQuote(path));
182 return null;
183 }
184
185 try
186 {
187 string prefix = null;
188 if (allowPrefix)
189 {
190 int prefixLength = path.IndexOf('=') + 1;
191 if (0 != prefixLength)
192 {
193 prefix = path.Substring(0, prefixLength);
194 path = path.Substring(prefixLength);
195 }
196 }
197
198 if (String.IsNullOrEmpty(prefix))
199 {
200 fullPath = Path.GetFullPath(path);
201 }
202 else
203 {
204 fullPath = String.Concat(prefix, Path.GetFullPath(path));
205 }
206 }
207 catch (Exception e)
208 {
209 messageHandler.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
210 return null;
211 }
212
213 return fullPath;
214 }
215 }
216}
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs
index 97f79755..0e7da42a 100644
--- a/src/WixToolset.Core/CommandLine/CommandLine.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLineParser.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Core 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -22,7 +22,7 @@ namespace WixToolset.Core
22 Bind, 22 Bind,
23 } 23 }
24 24
25 internal class CommandLine : ICommandLine, IParseCommandLine 25 internal class CommandLineParser : ICommandLine, IParseCommandLine
26 { 26 {
27 private IServiceProvider ServiceProvider { get; set; } 27 private IServiceProvider ServiceProvider { get; set; }
28 28
@@ -54,7 +54,7 @@ namespace WixToolset.Core
54 54
55 if (!String.IsNullOrEmpty(context.Arguments)) 55 if (!String.IsNullOrEmpty(context.Arguments))
56 { 56 {
57 args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray(); 57 args = CommandLineParser.ParseArgumentsToArray(context.Arguments).Union(args).ToArray();
58 } 58 }
59 59
60 return this.ParseStandardCommandLine(context, args); 60 return this.ParseStandardCommandLine(context, args);
@@ -88,13 +88,12 @@ namespace WixToolset.Core
88 var contentsFile = String.Empty; 88 var contentsFile = String.Empty;
89 var outputsFile = String.Empty; 89 var outputsFile = String.Empty;
90 var builtOutputsFile = String.Empty; 90 var builtOutputsFile = String.Empty;
91 var wixProjectFile = String.Empty;
92 91
93 this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => 92 this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) =>
94 { 93 {
95 if (cmdline.IsSwitch(arg)) 94 if (cmdline.IsSwitch(arg))
96 { 95 {
97 var parameter = arg.TrimStart(new[] { '-', '/' }); 96 var parameter = arg.Substring(1);
98 switch (parameter.ToLowerInvariant()) 97 switch (parameter.ToLowerInvariant())
99 { 98 {
100 case "?": 99 case "?":
@@ -127,9 +126,6 @@ namespace WixToolset.Core
127 case "builtoutputsfile": 126 case "builtoutputsfile":
128 cmdline.GetNextArgumentOrError(ref builtOutputsFile); 127 cmdline.GetNextArgumentOrError(ref builtOutputsFile);
129 return true; 128 return true;
130 case "wixprojectfile":
131 cmdline.GetNextArgumentOrError(ref wixProjectFile);
132 return true;
133 129
134 case "d": 130 case "d":
135 case "define": 131 case "define":
@@ -181,7 +177,7 @@ namespace WixToolset.Core
181 } 177 }
182 else 178 else
183 { 179 {
184 files.AddRange(cmdline.GetFiles(arg, "source code")); 180 files.AddRange(CommandLineHelper.GetFiles(arg, "source code"));
185 return true; 181 return true;
186 } 182 }
187 }); 183 });
@@ -211,7 +207,7 @@ namespace WixToolset.Core
211 var variables = this.GatherPreprocessorVariables(defines); 207 var variables = this.GatherPreprocessorVariables(defines);
212 var bindPathList = this.GatherBindPaths(bindPaths); 208 var bindPathList = this.GatherBindPaths(bindPaths);
213 var type = CalculateOutputType(outputType, outputFile); 209 var type = CalculateOutputType(outputType, outputFile);
214 return new BuildCommand(this.ServiceProvider, this.Messaging, this.ExtensionManager, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile, wixProjectFile); 210 return new BuildCommand(this.ServiceProvider, this.Messaging, this.ExtensionManager, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile);
215 } 211 }
216 212
217 case Commands.Compile: 213 case Commands.Compile:
@@ -283,7 +279,7 @@ namespace WixToolset.Core
283 } 279 }
284#endif 280#endif
285 281
286 private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func<CommandLine, string, bool> parseCommand, Func<CommandLine, string, bool> parseArgument) 282 private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func<CommandLineParser, string, bool> parseCommand, Func<CommandLineParser, string, bool> parseArgument)
287 { 283 {
288 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments); 284 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
289 285
@@ -335,7 +331,7 @@ namespace WixToolset.Core
335 331
336 foreach (var bindPath in bindPaths) 332 foreach (var bindPath in bindPaths)
337 { 333 {
338 BindPath bp = BindPath.Parse(bindPath); 334 var bp = BindPath.Parse(bindPath);
339 335
340 if (Directory.Exists(bp.Path)) 336 if (Directory.Exists(bp.Path))
341 { 337 {
@@ -351,65 +347,6 @@ namespace WixToolset.Core
351 } 347 }
352 348
353 /// <summary> 349 /// <summary>
354 /// Get a set of files that possibly have a search pattern in the path (such as '*').
355 /// </summary>
356 /// <param name="searchPath">Search path to find files in.</param>
357 /// <param name="fileType">Type of file; typically "Source".</param>
358 /// <returns>An array of files matching the search path.</returns>
359 /// <remarks>
360 /// This method is written in this verbose way because it needs to support ".." in the path.
361 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
362 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
363 /// Path.GetDirectoryName does not support ".." in the path.
364 /// </remarks>
365 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
366 public string[] GetFiles(string searchPath, string fileType)
367 {
368 if (null == searchPath)
369 {
370 throw new ArgumentNullException(nameof(searchPath));
371 }
372
373 // Convert alternate directory separators to the standard one.
374 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
375 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
376 string[] files = null;
377
378 try
379 {
380 if (0 > lastSeparator)
381 {
382 files = Directory.GetFiles(".", filePath);
383 }
384 else // found directory separator
385 {
386 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
387 }
388 }
389 catch (DirectoryNotFoundException)
390 {
391 // Don't let this function throw the DirectoryNotFoundException. This exception
392 // occurs for non-existant directories and invalid characters in the searchPattern.
393 }
394 catch (ArgumentException)
395 {
396 // Don't let this function throw the ArgumentException. This exception
397 // occurs in certain situations such as when passing a malformed UNC path.
398 }
399 catch (IOException)
400 {
401 throw new WixFileNotFoundException(searchPath, fileType);
402 }
403
404 if (null == files || 0 == files.Length)
405 {
406 throw new WixFileNotFoundException(searchPath, fileType);
407 }
408
409 return files;
410 }
411
412 /// <summary>
413 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity 350 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
414 /// </summary> 351 /// </summary>
415 /// <param name="args">The list of strings to check.</param> 352 /// <param name="args">The list of strings to check.</param>
@@ -449,7 +386,7 @@ namespace WixToolset.Core
449 { 386 {
450 if (this.TryGetNextArgumentOrError(out var arg)) 387 if (this.TryGetNextArgumentOrError(out var arg))
451 { 388 {
452 foreach (var path in this.GetFiles(arg, fileType)) 389 foreach (var path in CommandLineHelper.GetFiles(arg, fileType))
453 { 390 {
454 args.Add(path); 391 args.Add(path);
455 } 392 }
@@ -458,13 +395,12 @@ namespace WixToolset.Core
458 395
459 public bool TryGetNextArgumentOrError(out string arg) 396 public bool TryGetNextArgumentOrError(out string arg)
460 { 397 {
461 //if (this.RemainingArguments.TryDequeue(out arg) && !this.IsSwitch(arg))
462 if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg)) 398 if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg))
463 { 399 {
464 return true; 400 return true;
465 } 401 }
466 402
467 this.ErrorArgument = arg ?? CommandLine.ExpectedArgument; 403 this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument;
468 404
469 return false; 405 return false;
470 } 406 }
@@ -489,7 +425,7 @@ namespace WixToolset.Core
489 { 425 {
490 if ('@' == arg[0]) 426 if ('@' == arg[0])
491 { 427 {
492 var responseFileArguments = CommandLine.ParseResponseFile(arg.Substring(1)); 428 var responseFileArguments = CommandLineParser.ParseResponseFile(arg.Substring(1));
493 args.AddRange(responseFileArguments); 429 args.AddRange(responseFileArguments);
494 } 430 }
495 else 431 else
@@ -526,7 +462,7 @@ namespace WixToolset.Core
526 } 462 }
527 } 463 }
528 464
529 private void ProcessRemainingArguments(ICommandLineContext context, Func<CommandLine, string, bool> parseArgument, Func<CommandLine, string, bool> parseCommand) 465 private void ProcessRemainingArguments(ICommandLineContext context, Func<CommandLineParser, string, bool> parseArgument, Func<CommandLineParser, string, bool> parseCommand)
530 { 466 {
531 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>(); 467 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
532 468
@@ -593,7 +529,7 @@ namespace WixToolset.Core
593 arguments = reader.ReadToEnd(); 529 arguments = reader.ReadToEnd();
594 } 530 }
595 531
596 return CommandLine.ParseArgumentsToArray(arguments); 532 return CommandLineParser.ParseArgumentsToArray(arguments);
597 } 533 }
598 534
599 private static List<string> ParseArgumentsToArray(string arguments) 535 private static List<string> ParseArgumentsToArray(string arguments)
@@ -631,7 +567,7 @@ namespace WixToolset.Core
631 // Add the argument to the list if it's not empty. 567 // Add the argument to the list if it's not empty.
632 if (arg.Length > 0) 568 if (arg.Length > 0)
633 { 569 {
634 argsList.Add(CommandLine.ExpandEnvironmentVariables(arg.ToString())); 570 argsList.Add(CommandLineParser.ExpandEnvironmentVariables(arg.ToString()));
635 arg.Length = 0; 571 arg.Length = 0;
636 } 572 }
637 } 573 }
diff --git a/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs
index 578c3b22..6922b246 100644
--- a/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using System.Collections; 6 using System.Collections;
diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs
index 856dd29f..f0ff5b1a 100644
--- a/src/WixToolset.Core/CommandLine/CompileCommand.cs
+++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Core 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs
index 2a2eab24..6e547d60 100644
--- a/src/WixToolset.Core/CommandLine/HelpCommand.cs
+++ b/src/WixToolset.Core/CommandLine/HelpCommand.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Core 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using WixToolset.Extensibility.Services; 6 using WixToolset.Extensibility.Services;
diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs
index 12941bdc..a04aac31 100644
--- a/src/WixToolset.Core/CommandLine/VersionCommand.cs
+++ b/src/WixToolset.Core/CommandLine/VersionCommand.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Core 3namespace WixToolset.Core.CommandLine
4{ 4{
5 using System; 5 using System;
6 using WixToolset.Extensibility.Services; 6 using WixToolset.Extensibility.Services;
diff --git a/src/WixToolset.Core/Layout.cs b/src/WixToolset.Core/Layout.cs
index d7322a12..b2957fb9 100644
--- a/src/WixToolset.Core/Layout.cs
+++ b/src/WixToolset.Core/Layout.cs
@@ -9,7 +9,6 @@ namespace WixToolset.Core
9 using WixToolset.Core.Bind; 9 using WixToolset.Core.Bind;
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Data.Bind; 11 using WixToolset.Data.Bind;
12 using WixToolset.Data.Tuples;
13 using WixToolset.Extensibility; 12 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Services; 13 using WixToolset.Extensibility.Services;
15 14
@@ -18,36 +17,31 @@ namespace WixToolset.Core
18 /// </summary> 17 /// </summary>
19 public sealed class Layout 18 public sealed class Layout
20 { 19 {
21 public Layout(IServiceProvider serviceProvider, IEnumerable<FileTransfer> fileTransfers, IEnumerable<string> contentFilePaths, string contentsFile, string outputsFile, string builtOutputsFile, bool suppressAclReset) 20 public Layout(IServiceProvider serviceProvider)
22 { 21 {
23 this.ServiceProvider = serviceProvider; 22 this.ServiceProvider = serviceProvider;
24 this.FileTransfers = fileTransfers;
25 this.ContentFilePaths = contentFilePaths;
26 this.ContentsFile = contentsFile;
27 this.OutputsFile = outputsFile;
28 this.BuiltOutputsFile = builtOutputsFile;
29 this.SuppressAclReset = suppressAclReset;
30 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
31 } 23 }
32 24
33 private IServiceProvider ServiceProvider { get; } 25 private IServiceProvider ServiceProvider { get; }
34 26
35 private IEnumerable<FileTransfer> FileTransfers { get; } 27 public IEnumerable<FileTransfer> FileTransfers { get; set; }
36 28
37 private IEnumerable<string> ContentFilePaths { get; } 29 public IEnumerable<string> ContentFilePaths { get; set; }
38 30
39 private string ContentsFile { get; } 31 public string ContentsFile { get; set; }
40 32
41 private string OutputsFile { get; } 33 public string OutputsFile { get; set; }
42 34
43 private string BuiltOutputsFile { get; } 35 public string BuiltOutputsFile { get; set; }
44 36
45 private bool SuppressAclReset { get; } 37 public bool SuppressAclReset { get; set; }
46 38
47 private IMessaging Messaging { get; } 39 private IMessaging Messaging { get; set; }
48 40
49 public void Execute() 41 public void Execute()
50 { 42 {
43 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
44
51 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>(); 45 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
52 46
53 var context = this.ServiceProvider.GetService<ILayoutContext>(); 47 var context = this.ServiceProvider.GetService<ILayoutContext>();
diff --git a/src/WixToolset.Core/Resolver.cs b/src/WixToolset.Core/Resolver.cs
index b0d3a189..1b72e3d0 100644
--- a/src/WixToolset.Core/Resolver.cs
+++ b/src/WixToolset.Core/Resolver.cs
@@ -16,34 +16,31 @@ namespace WixToolset.Core
16 /// </summary> 16 /// </summary>
17 public sealed class Resolver 17 public sealed class Resolver
18 { 18 {
19 public Resolver(IServiceProvider serviceProvider, IEnumerable<BindPath> bindPaths, Intermediate intermediateRepresentation, string intermediateFolder, IEnumerable<Localization> localizations) 19 public Resolver(IServiceProvider serviceProvider)
20 { 20 {
21 this.ServiceProvider = serviceProvider; 21 this.ServiceProvider = serviceProvider;
22 this.BindPaths = bindPaths;
23 this.IntermediateRepresentation = intermediateRepresentation;
24 this.IntermediateFolder = intermediateFolder;
25 this.Localizations = localizations;
26
27 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
28 } 22 }
29 23
30 private IServiceProvider ServiceProvider { get; } 24 private IServiceProvider ServiceProvider { get; set; }
31 25
32 private IEnumerable<BindPath> BindPaths { get; } 26 public IEnumerable<BindPath> BindPaths { get; set; }
33 27
34 private Intermediate IntermediateRepresentation { get; } 28 public Intermediate IntermediateRepresentation { get; set; }
35 29
36 private string IntermediateFolder { get; } 30 public string IntermediateFolder { get; set; }
37 31
38 private IEnumerable<Localization> Localizations { get; } 32 public IEnumerable<Localization> Localizations { get; set; }
39 33
40 private IMessaging Messaging { get; } 34 private IMessaging Messaging { get; set; }
41 35
42 public ResolveResult Execute() 36 public ResolveResult Execute()
43 { 37 {
38 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
39
44 var localizer = new Localizer(this.Messaging, this.Localizations); 40 var localizer = new Localizer(this.Messaging, this.Localizations);
45 41
46 var variableResolver = new WixVariableResolver(this.Messaging, localizer); 42 var variableResolver = new WixVariableResolver(this.Messaging, localizer);
43 this.PopulateVariableResolver(variableResolver);
47 44
48 var context = this.ServiceProvider.GetService<IResolveContext>(); 45 var context = this.ServiceProvider.GetService<IResolveContext>();
49 context.Messaging = this.Messaging; 46 context.Messaging = this.Messaging;
@@ -51,7 +48,7 @@ namespace WixToolset.Core
51 context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().Create<IResolverExtension>(); 48 context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().Create<IResolverExtension>();
52 context.IntermediateFolder = this.IntermediateFolder; 49 context.IntermediateFolder = this.IntermediateFolder;
53 context.IntermediateRepresentation = this.IntermediateRepresentation; 50 context.IntermediateRepresentation = this.IntermediateRepresentation;
54 context.WixVariableResolver = this.PopulateVariableResolver(variableResolver); 51 context.WixVariableResolver = variableResolver;
55 52
56 // Preresolve. 53 // Preresolve.
57 // 54 //
@@ -212,7 +209,7 @@ namespace WixToolset.Core
212 } 209 }
213 } 210 }
214 211
215 private WixVariableResolver PopulateVariableResolver(WixVariableResolver resolver) 212 private void PopulateVariableResolver(WixVariableResolver resolver)
216 { 213 {
217 // Gather all the wix variables. 214 // Gather all the wix variables.
218 var wixVariableTuples = this.IntermediateRepresentation.Sections.SelectMany(s => s.Tuples).OfType<WixVariableTuple>(); 215 var wixVariableTuples = this.IntermediateRepresentation.Sections.SelectMany(s => s.Tuples).OfType<WixVariableTuple>();
@@ -227,8 +224,6 @@ namespace WixToolset.Core
227 this.Messaging.Write(ErrorMessages.WixVariableCollision(tuple.SourceLineNumbers, tuple.WixVariable)); 224 this.Messaging.Write(ErrorMessages.WixVariableCollision(tuple.SourceLineNumbers, tuple.WixVariable));
228 } 225 }
229 } 226 }
230
231 return resolver;
232 } 227 }
233 } 228 }
234} 229}
diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs
index 74b312c6..a018b8dc 100644
--- a/src/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -3,6 +3,7 @@
3namespace WixToolset.Core 3namespace WixToolset.Core
4{ 4{
5 using System; 5 using System;
6 using WixToolset.Core.CommandLine;
6 using WixToolset.Core.ExtensibilityServices; 7 using WixToolset.Core.ExtensibilityServices;
7 using WixToolset.Data; 8 using WixToolset.Data;
8 using WixToolset.Extensibility; 9 using WixToolset.Extensibility;
@@ -64,7 +65,7 @@ namespace WixToolset.Core
64 65
65 if (serviceType == typeof(ICommandLine)) 66 if (serviceType == typeof(ICommandLine))
66 { 67 {
67 return new CommandLine(); 68 return new CommandLineParser();
68 } 69 }
69 70
70 // Singletons. 71 // Singletons.
diff --git a/src/light/App.ico b/src/light/App.ico
new file mode 100644
index 00000000..3a5525fd
--- /dev/null
+++ b/src/light/App.ico
Binary files differ
diff --git a/src/light/AssemblyInfo.cs b/src/light/AssemblyInfo.cs
new file mode 100644
index 00000000..ab2fc0ab
--- /dev/null
+++ b/src/light/AssemblyInfo.cs
@@ -0,0 +1,9 @@
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
3using System;
4using System.Reflection;
5using System.Runtime.CompilerServices;
6using System.Runtime.InteropServices;
7
8[assembly: AssemblyCulture("")]
9[assembly: ComVisible(false)]
diff --git a/src/light/LightCommandLine.cs b/src/light/LightCommandLine.cs
new file mode 100644
index 00000000..9a90b9ce
--- /dev/null
+++ b/src/light/LightCommandLine.cs
@@ -0,0 +1,485 @@
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 WixToolset.Core.CommandLine;
10 using WixToolset.Data;
11 using WixToolset.Extensibility.Services;
12
13 public class LightCommandLine
14 {
15 public LightCommandLine(IMessaging messaging)
16 {
17 this.Messaging = messaging;
18 this.ShowLogo = true;
19 this.Tidy = true;
20
21 this.CubeFiles = new List<string>();
22 this.SuppressIces = new List<string>();
23 this.Ices = new List<string>();
24 this.BindPaths = new List<BindPath>();
25 this.Extensions = new List<string>();
26 this.Files = new List<string>();
27 this.LocalizationFiles = new List<string>();
28 this.Variables = new Dictionary<string, string>();
29 }
30
31 public IMessaging Messaging { get; }
32
33 public string PdbFile { get; private set; }
34
35 public CompressionLevel? DefaultCompressionLevel { get; set; }
36
37 public bool SuppressAclReset { get; private set; }
38
39 public bool SuppressLayout { get; private set; }
40
41 public bool SuppressWixPdb { get; private set; }
42
43 public bool SuppressValidation { get; private set; }
44
45 public string IntermediateFolder { get; private set; }
46
47 public string OutputsFile { get; private set; }
48
49 public string BuiltOutputsFile { get; private set; }
50
51 public string WixprojectFile { get; private set; }
52
53 public string ContentsFile { get; private set; }
54
55 public List<string> Ices { get; private set; }
56
57 public string CabCachePath { get; private set; }
58
59 public int CabbingThreadCount { get; private set; }
60
61 public List<string> CubeFiles { get; private set; }
62
63 public List<string> SuppressIces { get; private set; }
64
65 public bool ShowLogo { get; private set; }
66
67 public bool ShowHelp { get; private set; }
68
69 public bool ShowPedanticMessages { get; private set; }
70
71 public bool SuppressLocalization { get; private set; }
72
73 public bool SuppressVersionCheck { get; private set; }
74
75 public string[] Cultures { get; private set; }
76
77 public string OutputFile { get; private set; }
78
79 public bool OutputXml { get; private set; }
80
81 public List<BindPath> BindPaths { get; private set; }
82
83 public List<string> Extensions { get; private set; }
84
85 public List<string> Files { get; private set; }
86
87 public List<string> LocalizationFiles { get; private set; }
88
89 public bool Tidy { get; private set; }
90
91 public string UnreferencedSymbolsFile { get; private set; }
92
93 public IDictionary<string, string> Variables { get; private set; }
94
95 /// <summary>
96 /// Parse the commandline arguments.
97 /// </summary>
98 /// <param name="args">Commandline arguments.</param>
99 public string[] Parse(string[] args)
100 {
101 List<string> unprocessed = new List<string>();
102
103 for (int i = 0; i < args.Length; ++i)
104 {
105 string arg = args[i];
106 if (String.IsNullOrEmpty(arg)) // skip blank arguments
107 {
108 continue;
109 }
110
111 if (1 == arg.Length) // treat '-' and '@' as filenames when by themselves.
112 {
113 unprocessed.Add(arg);
114 }
115 else if ('-' == arg[0] || '/' == arg[0])
116 {
117 string parameter = arg.Substring(1);
118 if (parameter.Equals("b", StringComparison.Ordinal))
119 {
120 if (!CommandLineHelper.IsValidArg(args, ++i))
121 {
122 break;
123 }
124
125 var bindPath = BindPath.Parse(args[i]);
126
127 this.BindPaths.Add(bindPath);
128 }
129 else if (parameter.StartsWith("cultures:", StringComparison.Ordinal))
130 {
131 string culturesString = arg.Substring(10).ToLower(CultureInfo.InvariantCulture);
132
133 // When null is used treat it as if cultures wasn't specified.
134 // This is needed for batching over the light task when using MSBuild which doesn't
135 // support empty items
136 if (culturesString.Equals("null", StringComparison.OrdinalIgnoreCase))
137 {
138 this.Cultures = null;
139 }
140 else
141 {
142 this.Cultures = culturesString.Split(';', ',');
143
144 for (int c = 0; c < this.Cultures.Length; ++c)
145 {
146 // Neutral is different from null. For neutral we still want to do WXL filtering.
147 // Set the culture to the empty string = identifier for the invariant culture
148 if (this.Cultures[c].Equals("neutral", StringComparison.OrdinalIgnoreCase))
149 {
150 this.Cultures[c] = String.Empty;
151 }
152 }
153 }
154 }
155 else if (parameter.StartsWith("dcl:", StringComparison.Ordinal))
156 {
157 string defaultCompressionLevel = arg.Substring(5);
158
159 if (String.IsNullOrEmpty(defaultCompressionLevel))
160 {
161 break;
162 }
163 else if (Enum.TryParse(defaultCompressionLevel, true, out CompressionLevel compressionLevel))
164 {
165 this.DefaultCompressionLevel = compressionLevel;
166 }
167 }
168 else if (parameter.StartsWith("d", StringComparison.Ordinal))
169 {
170 parameter = arg.Substring(2);
171 string[] value = parameter.Split("=".ToCharArray(), 2);
172
173 string preexisting;
174 if (1 == value.Length)
175 {
176 this.Messaging.Write(ErrorMessages.ExpectedWixVariableValue(value[0]));
177 }
178 else if (this.Variables.TryGetValue(value[0], out preexisting))
179 {
180 this.Messaging.Write(ErrorMessages.WixVariableCollision(null, value[0]));
181 }
182 else
183 {
184 this.Variables.Add(value[0], value[1]);
185 }
186 }
187 else if (parameter.Equals("ext", StringComparison.Ordinal))
188 {
189 if (!CommandLineHelper.IsValidArg(args, ++i))
190 {
191 this.Messaging.Write(ErrorMessages.TypeSpecificationForExtensionRequired("-ext"));
192 break;
193 }
194
195 this.Extensions.Add(args[i]);
196 }
197 else if (parameter.Equals("loc", StringComparison.Ordinal))
198 {
199 string locFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
200 if (String.IsNullOrEmpty(locFile))
201 {
202 break;
203 }
204
205 this.LocalizationFiles.Add(locFile);
206 }
207 else if (parameter.Equals("nologo", StringComparison.Ordinal))
208 {
209 this.ShowLogo = false;
210 }
211 else if (parameter.Equals("notidy", StringComparison.Ordinal))
212 {
213 this.Tidy = false;
214 }
215 else if ("o" == parameter || "out" == parameter)
216 {
217 this.OutputFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
218 if (String.IsNullOrEmpty(this.OutputFile))
219 {
220 break;
221 }
222 }
223 else if (parameter.Equals("pedantic", StringComparison.Ordinal))
224 {
225 this.ShowPedanticMessages = true;
226 }
227 else if (parameter.Equals("sloc", StringComparison.Ordinal))
228 {
229 this.SuppressLocalization = true;
230 }
231 else if (parameter.Equals("usf", StringComparison.Ordinal))
232 {
233 this.UnreferencedSymbolsFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
234
235 if (String.IsNullOrEmpty(this.UnreferencedSymbolsFile))
236 {
237 break;
238 }
239 }
240 else if (parameter.Equals("xo", StringComparison.Ordinal))
241 {
242 this.OutputXml = true;
243 }
244 else if (parameter.Equals("cc", StringComparison.Ordinal))
245 {
246 this.CabCachePath = CommandLineHelper.GetDirectory(parameter, this.Messaging, args, ++i);
247
248 if (String.IsNullOrEmpty(this.CabCachePath))
249 {
250 break;
251 }
252 }
253 else if (parameter.Equals("ct", StringComparison.Ordinal))
254 {
255 if (!CommandLineHelper.IsValidArg(args, ++i))
256 {
257 this.Messaging.Write(ErrorMessages.IllegalCabbingThreadCount(String.Empty));
258 break;
259 }
260
261 int ct = 0;
262 if (!Int32.TryParse(args[i], out ct) || 0 >= ct)
263 {
264 this.Messaging.Write(ErrorMessages.IllegalCabbingThreadCount(args[i]));
265 break;
266 }
267
268 this.CabbingThreadCount = ct;
269 this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
270 }
271 else if (parameter.Equals("cub", StringComparison.Ordinal))
272 {
273 string cubeFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
274
275 if (String.IsNullOrEmpty(cubeFile))
276 {
277 break;
278 }
279
280 this.CubeFiles.Add(cubeFile);
281 }
282 else if (parameter.StartsWith("ice:", StringComparison.Ordinal))
283 {
284 this.Ices.Add(parameter.Substring(4));
285 }
286 else if (parameter.Equals("intermediatefolder", StringComparison.OrdinalIgnoreCase))
287 {
288 this.IntermediateFolder = CommandLineHelper.GetDirectory(parameter, this.Messaging, args, ++i);
289
290 if (String.IsNullOrEmpty(this.IntermediateFolder))
291 {
292 break;
293 }
294 }
295 else if (parameter.Equals("contentsfile", StringComparison.Ordinal))
296 {
297 this.ContentsFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
298
299 if (String.IsNullOrEmpty(this.ContentsFile))
300 {
301 break;
302 }
303 }
304 else if (parameter.Equals("outputsfile", StringComparison.Ordinal))
305 {
306 this.OutputsFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
307
308 if (String.IsNullOrEmpty(this.OutputsFile))
309 {
310 break;
311 }
312 }
313 else if (parameter.Equals("builtoutputsfile", StringComparison.Ordinal))
314 {
315 this.BuiltOutputsFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
316
317 if (String.IsNullOrEmpty(this.BuiltOutputsFile))
318 {
319 break;
320 }
321 }
322 else if (parameter.Equals("wixprojectfile", StringComparison.Ordinal))
323 {
324 this.WixprojectFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
325
326 if (String.IsNullOrEmpty(this.WixprojectFile))
327 {
328 break;
329 }
330 }
331 else if (parameter.Equals("pdbout", StringComparison.Ordinal))
332 {
333 this.PdbFile = CommandLineHelper.GetFile(parameter, this.Messaging, args, ++i);
334
335 if (String.IsNullOrEmpty(this.PdbFile))
336 {
337 break;
338 }
339 }
340 else if (parameter.StartsWith("sice:", StringComparison.Ordinal))
341 {
342 this.SuppressIces.Add(parameter.Substring(5));
343 }
344 else if (parameter.Equals("sl", StringComparison.Ordinal))
345 {
346 this.SuppressLayout = true;
347 }
348 else if (parameter.Equals("spdb", StringComparison.Ordinal))
349 {
350 this.SuppressWixPdb = true;
351 }
352 else if (parameter.Equals("sacl", StringComparison.Ordinal))
353 {
354 this.SuppressAclReset = true;
355 }
356 else if (parameter.Equals("sval", StringComparison.Ordinal))
357 {
358 this.SuppressValidation = true;
359 }
360 else if ("sv" == parameter)
361 {
362 this.SuppressVersionCheck = true;
363 }
364 else if (parameter.StartsWith("sw", StringComparison.Ordinal))
365 {
366 string paramArg = parameter.Substring(2);
367 if (0 == paramArg.Length)
368 {
369 this.Messaging.SuppressAllWarnings = true;
370 }
371 else
372 {
373 int suppressWarning = 0;
374 if (!Int32.TryParse(paramArg, out suppressWarning) || 0 >= suppressWarning)
375 {
376 this.Messaging.Write(ErrorMessages.IllegalSuppressWarningId(paramArg));
377 }
378 else
379 {
380 this.Messaging.SuppressWarningMessage(suppressWarning);
381 }
382 }
383 }
384 else if (parameter.StartsWith("wx", StringComparison.Ordinal))
385 {
386 string paramArg = parameter.Substring(2);
387 if (0 == paramArg.Length)
388 {
389 this.Messaging.WarningsAsError = true;
390 }
391 else
392 {
393 int elevateWarning = 0;
394 if (!Int32.TryParse(paramArg, out elevateWarning) || 0 >= elevateWarning)
395 {
396 this.Messaging.Write(ErrorMessages.IllegalWarningIdAsError(paramArg));
397 }
398 else
399 {
400 this.Messaging.ElevateWarningMessage(elevateWarning);
401 }
402 }
403 }
404 else if ("v" == parameter)
405 {
406 this.Messaging.ShowVerboseMessages = true;
407 }
408 else if ("?" == parameter || "help" == parameter)
409 {
410 this.ShowHelp = true;
411 break;
412 }
413 else
414 {
415 unprocessed.Add(arg);
416 }
417 }
418 else if ('@' == arg[0])
419 {
420 string[] parsedArgs = CommandLineResponseFile.Parse(arg.Substring(1));
421 string[] unparsedArgs = this.Parse(parsedArgs);
422 unprocessed.AddRange(unparsedArgs);
423 }
424 else
425 {
426 unprocessed.Add(arg);
427 }
428 }
429
430 return unprocessed.ToArray();
431 }
432
433 public string[] ParsePostExtensions(string[] remaining)
434 {
435 List<string> unprocessed = new List<string>();
436
437 for (int i = 0; i < remaining.Length; ++i)
438 {
439 string arg = remaining[i];
440 if (String.IsNullOrEmpty(arg)) // skip blank arguments
441 {
442 continue;
443 }
444
445 if (1 < arg.Length && ('-' == arg[0] || '/' == arg[0]))
446 {
447 unprocessed.Add(arg);
448 }
449 else
450 {
451 this.Files.AddRange(CommandLineHelper.GetFiles(arg, "Source"));
452 }
453 }
454
455 if (0 == this.Files.Count)
456 {
457 this.ShowHelp = true;
458 }
459 else if (String.IsNullOrEmpty(this.OutputFile))
460 {
461 if (1 < this.Files.Count)
462 {
463 this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput());
464 }
465
466 // After the linker tells us what the output type actually is, we'll change the ".wix" to the correct extension.
467 this.OutputFile = Path.ChangeExtension(Path.GetFileName(this.Files[0]), ".wix");
468
469 // Add the directories of the input files as unnamed bind paths.
470 foreach (string file in this.Files)
471 {
472 BindPath bindPath = new BindPath(Path.GetDirectoryName(Path.GetFullPath(file)));
473 this.BindPaths.Add(bindPath);
474 }
475 }
476
477 if (!this.SuppressWixPdb && String.IsNullOrEmpty(this.PdbFile) && !String.IsNullOrEmpty(this.OutputFile))
478 {
479 this.PdbFile = Path.ChangeExtension(this.OutputFile, ".wixpdb");
480 }
481
482 return unprocessed.ToArray();
483 }
484 }
485}
diff --git a/src/light/LightStrings.Designer.cs b/src/light/LightStrings.Designer.cs
new file mode 100644
index 00000000..50e271fd
--- /dev/null
+++ b/src/light/LightStrings.Designer.cs
@@ -0,0 +1,104 @@
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 using System;
5
6
7 /// <summary>
8 /// A strongly-typed resource class, for looking up localized strings, etc.
9 /// </summary>
10 // This class was auto-generated by the StronglyTypedResourceBuilder
11 // class via a tool like ResGen or Visual Studio.
12 // To add or remove a member, edit your .ResX file then rerun ResGen
13 // with the /str option, or rebuild your VS project.
14 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
15 [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
16 [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
17 internal class LightStrings {
18
19 private static global::System.Resources.ResourceManager resourceMan;
20
21 private static global::System.Globalization.CultureInfo resourceCulture;
22
23 [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
24 internal LightStrings() {
25 }
26
27 /// <summary>
28 /// Returns the cached ResourceManager instance used by this class.
29 /// </summary>
30 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
31 internal static global::System.Resources.ResourceManager ResourceManager {
32 get {
33 if (object.ReferenceEquals(resourceMan, null)) {
34 global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WixToolset.Tools.LightStrings", typeof(LightStrings).Assembly);
35 resourceMan = temp;
36 }
37 return resourceMan;
38 }
39 }
40
41 /// <summary>
42 /// Overrides the current thread's CurrentUICulture property for all
43 /// resource lookups using this strongly typed resource class.
44 /// </summary>
45 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
46 internal static global::System.Globalization.CultureInfo Culture {
47 get {
48 return resourceCulture;
49 }
50 set {
51 resourceCulture = value;
52 }
53 }
54
55 /// <summary>
56 /// Looks up a localized string similar to -b &lt;path&gt; specify a binder path to locate all files
57 /// (default: current directory)
58 /// prefix the path with &apos;name=&apos; where &apos;name&apos; is the name of your
59 /// named bindpath.
60 /// -cc &lt;path&gt; path to cache built cabinets (will not be deleted after linking)
61 /// -ct &lt;N&gt; number of threads to use when creating cabinets
62 /// (default: %NUMBER_OF_PROCESSORS%)
63 /// -cub &lt;file.cub&gt; additional .cub file containing ICEs to run
64 /// -dcl:level set default cabinet compression l [rest of string was truncated]&quot;;.
65 /// </summary>
66 internal static string CommandLineArguments {
67 get {
68 return ResourceManager.GetString("CommandLineArguments", resourceCulture);
69 }
70 }
71
72 /// <summary>
73 /// Looks up a localized string similar to The -bf (bind files) option is only applicable with the -xo option..
74 /// </summary>
75 internal static string EXP_BindFileOptionNotApplicable {
76 get {
77 return ResourceManager.GetString("EXP_BindFileOptionNotApplicable", resourceCulture);
78 }
79 }
80
81 /// <summary>
82 /// Looks up a localized string similar to Cannot link object files (.wixobj) files with an output file (.wixout).
83 /// </summary>
84 internal static string EXP_CannotLinkObjFilesWithOutpuFile {
85 get {
86 return ResourceManager.GetString("EXP_CannotLinkObjFilesWithOutpuFile", resourceCulture);
87 }
88 }
89
90 /// <summary>
91 /// Looks up a localized string similar to usage: light.exe [-?] [-b bindPath] [-nologo] [-out outputFile] objectFile [objectFile ...] [@responseFile]
92 ///
93 ///{0}
94 ///
95 ///Environment variables:
96 /// WIX_TEMP overrides the temporary directory used for cab creation, msm exploding, ....
97 /// </summary>
98 internal static string HelpMessage {
99 get {
100 return ResourceManager.GetString("HelpMessage", resourceCulture);
101 }
102 }
103 }
104}
diff --git a/src/light/LightStrings.resx b/src/light/LightStrings.resx
new file mode 100644
index 00000000..3f586a5d
--- /dev/null
+++ b/src/light/LightStrings.resx
@@ -0,0 +1,174 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 2.0
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">2.0</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 <comment>This is a comment</comment>
28 </data>
29
30 There are any number of "resheader" rows that contain simple
31 name/value pairs.
32
33 Each data row contains a name, and value. The row also contains a
34 type or mimetype. Type corresponds to a .NET class that support
35 text/value conversion through the TypeConverter architecture.
36 Classes that don't support this are serialized and stored with the
37 mimetype set.
38
39 The mimetype is used for serialized objects, and tells the
40 ResXResourceReader how to depersist the object. This is currently not
41 extensible. For a given mimetype the value must be set accordingly:
42
43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 that the ResXResourceWriter will generate, however the reader can
45 read any of the formats listed below.
46
47 mimetype: application/x-microsoft.net.object.binary.base64
48 value : The object must be serialized with
49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 : and then encoded with base64 encoding.
51
52 mimetype: application/x-microsoft.net.object.soap.base64
53 value : The object must be serialized with
54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 : and then encoded with base64 encoding.
56
57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 value : The object must be serialized into a byte array
59 : using a System.ComponentModel.TypeConverter
60 : and then encoded with base64 encoding.
61 -->
62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 <xsd:element name="root" msdata:IsDataSet="true">
65 <xsd:complexType>
66 <xsd:choice maxOccurs="unbounded">
67 <xsd:element name="metadata">
68 <xsd:complexType>
69 <xsd:sequence>
70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 </xsd:sequence>
72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 <xsd:attribute name="type" type="xsd:string" />
74 <xsd:attribute name="mimetype" type="xsd:string" />
75 <xsd:attribute ref="xml:space" />
76 </xsd:complexType>
77 </xsd:element>
78 <xsd:element name="assembly">
79 <xsd:complexType>
80 <xsd:attribute name="alias" type="xsd:string" />
81 <xsd:attribute name="name" type="xsd:string" />
82 </xsd:complexType>
83 </xsd:element>
84 <xsd:element name="data">
85 <xsd:complexType>
86 <xsd:sequence>
87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 </xsd:sequence>
90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 <xsd:attribute ref="xml:space" />
94 </xsd:complexType>
95 </xsd:element>
96 <xsd:element name="resheader">
97 <xsd:complexType>
98 <xsd:sequence>
99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 </xsd:sequence>
101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 </xsd:complexType>
103 </xsd:element>
104 </xsd:choice>
105 </xsd:complexType>
106 </xsd:element>
107 </xsd:schema>
108 <resheader name="resmimetype">
109 <value>text/microsoft-resx</value>
110 </resheader>
111 <resheader name="version">
112 <value>2.0</value>
113 </resheader>
114 <resheader name="reader">
115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 </resheader>
117 <resheader name="writer">
118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 </resheader>
120 <data name="CommandLineArguments" xml:space="preserve">
121 <value> -b &lt;path&gt; specify a binder path to locate all files
122 (default: current directory)
123 prefix the path with 'name=' where 'name' is the name of your
124 named bindpath.
125 -cc &lt;path&gt; path to cache built cabinets (will not be deleted after linking)
126 -ct &lt;N&gt; number of threads to use when creating cabinets
127 (default: %NUMBER_OF_PROCESSORS%)
128 -cub &lt;file.cub&gt; additional .cub file containing ICEs to run
129 -dcl:level set default cabinet compression level
130 (low, medium, high, none, mszip; mszip default)
131 -eav exact assembly versions (breaks .NET 1.1 RTM compatibility)
132 -ice:&lt;ICE&gt; run a specific internal consistency evaluator (ICE)
133 -pdbout &lt;output.wixpdb&gt; save the WixPdb to a specific file
134 (default: same name as output with wixpdb extension)
135 -reusecab reuse cabinets from cabinet cache
136 -sacl suppress resetting ACLs
137 (useful when laying out image to a network share)
138 -sice:&lt;ICE&gt; suppress an internal consistency evaluator (ICE)
139 -sl suppress layout
140 -spdb suppress outputting the WixPdb
141 -sval suppress MSI/MSM validation
142 -cultures:&lt;cultures&gt; semicolon or comma delimited list of localized
143 string cultures to load from .wxl files and libraries.
144 Precedence of cultures is from left to right.
145 -d&lt;name&gt;[=&lt;value&gt;] define a wix variable, with or without a value.
146 -ext &lt;extension&gt; extension assembly or "class, assembly"
147 -loc &lt;loc.wxl&gt; read localization strings from .wxl file
148 -nologo skip printing light logo information
149 -notidy do not delete temporary files (useful for debugging)
150 -o[ut] specify output file (default: write to current directory)
151 -pedantic show pedantic messages
152 -sloc suppress localization
153 -sw[N] suppress all warnings or a specific message ID
154 (example: -sw1009 -sw1103)
155 -usf &lt;output.xml&gt; unreferenced symbols file
156 -v verbose output
157 -wx[N] treat all warnings or a specific message ID as an error
158 (example: -wx1009 -wx1103)
159 -xo output wixout format instead of MSI format
160 -? | -help this help information</value>
161 </data>
162 <data name="EXP_CannotLinkObjFilesWithOutpuFile" xml:space="preserve">
163 <value>Cannot link object files (.wixobj) files with an output file (.wixout)</value>
164 </data>
165 <data name="HelpMessage" xml:space="preserve">
166 <value> usage: light.exe [-?] [-b bindPath] [-nologo] [-out outputFile] objectFile [objectFile ...] [@responseFile]
167
168{0}
169
170Environment variables:
171 WIX_TEMP overrides the temporary directory used for cab creation, msm exploding, ...</value>
172 <comment>{0} is replaced by a list of light's arguments.</comment>
173 </data>
174</root>
diff --git a/src/light/app.config b/src/light/app.config
new file mode 100644
index 00000000..71c529fb
--- /dev/null
+++ b/src/light/app.config
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<!-- 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. -->
3
4
5<configuration>
6 <runtime>
7 <loadFromRemoteSources enabled="true"/>
8 </runtime>
9</configuration>
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}
diff --git a/src/light/light.csproj b/src/light/light.csproj
new file mode 100644
index 00000000..20e10b11
--- /dev/null
+++ b/src/light/light.csproj
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>net461</TargetFramework>
7 <OutputType>Exe</OutputType>
8 <Description>Linker</Description>
9 <Title>WiX Toolset Linker</Title>
10 </PropertyGroup>
11
12 <ItemGroup>
13 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" />
14 <ProjectReference Include="..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" />
15 <ProjectReference Include="..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" />
16 </ItemGroup>
17
18 <ItemGroup>
19 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
20 </ItemGroup>
21</Project>
diff --git a/src/test/WixToolsetTest.LightIntegration/LightFixture.cs b/src/test/WixToolsetTest.LightIntegration/LightFixture.cs
new file mode 100644
index 00000000..21c10be9
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/LightFixture.cs
@@ -0,0 +1,48 @@
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 WixToolsetTest.LightIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixToolset.Core;
8 using WixToolset.Tools;
9 using WixToolsetTest.LightIntegration.Utility;
10 using Xunit;
11
12 public class LightFixture
13 {
14 [Fact]
15 public void CanBuildFromWixout()
16 {
17 var folder = TestData.Get(@"TestData\Wixout");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23
24 var program = new Light();
25 var result = program.Run(new WixToolsetServiceProvider(), null, new[]
26 {
27 Path.Combine(folder, "test.wixout"),
28 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
29 "-b", Path.Combine(folder, "data"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", Path.Combine(baseFolder, @"bin\test.msi")
32 });
33
34 Assert.Equal(0, result);
35
36 var binFolder = Path.Combine(baseFolder, @"bin\");
37 var builtFiles = Directory.GetFiles(binFolder, "*", SearchOption.AllDirectories);
38
39 Assert.Equal(new[]{
40 "MsiPackage\\test.txt",
41 "test.msi",
42 "test.wir",
43 "test.wixpdb",
44 }, builtFiles.Select(f => f.Substring(binFolder.Length)).OrderBy(s => s).ToArray());
45 }
46 }
47 }
48}
diff --git a/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/Package.en-us.wxl b/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/Package.en-us.wxl
new file mode 100644
index 00000000..38c12ac1
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/Package.en-us.wxl
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
diff --git a/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/data/test.txt b/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/data/test.txt
new file mode 100644
index 00000000..cd0db0e1
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/data/test.txt
@@ -0,0 +1 @@
This is test.txt. \ No newline at end of file
diff --git a/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/test.wixout b/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/test.wixout
new file mode 100644
index 00000000..009b625f
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/TestData/Wixout/test.wixout
Binary files differ
diff --git a/src/test/WixToolsetTest.LightIntegration/Utility/DisposableFileSystem.cs b/src/test/WixToolsetTest.LightIntegration/Utility/DisposableFileSystem.cs
new file mode 100644
index 00000000..3b8c0e19
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/Utility/DisposableFileSystem.cs
@@ -0,0 +1,86 @@
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 WixToolsetTest.LightIntegration.Utility
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8
9 public class DisposableFileSystem : IDisposable
10 {
11 protected bool Disposed { get; private set; }
12
13 private List<string> CleanupPaths { get; } = new List<string>();
14
15 protected string GetFile(bool create = false)
16 {
17 var path = Path.GetTempFileName();
18
19 if (!create)
20 {
21 File.Delete(path);
22 }
23
24 this.CleanupPaths.Add(path);
25
26 return path;
27 }
28
29 public string GetFolder(bool create = false)
30 {
31 var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
32
33 if (create)
34 {
35 Directory.CreateDirectory(path);
36 }
37
38 this.CleanupPaths.Add(path);
39
40 return path;
41 }
42
43
44 #region // IDisposable
45
46 public void Dispose()
47 {
48 this.Dispose(true);
49 GC.SuppressFinalize(this);
50 }
51
52 protected virtual void Dispose(bool disposing)
53 {
54 if (this.Disposed)
55 {
56 return;
57 }
58
59 if (disposing)
60 {
61 foreach (var path in this.CleanupPaths)
62 {
63 try
64 {
65 if (File.Exists(path))
66 {
67 File.Delete(path);
68 }
69 else if (Directory.Exists(path))
70 {
71 Directory.Delete(path, true);
72 }
73 }
74 catch
75 {
76 // Best effort delete, so ignore any failures.
77 }
78 }
79 }
80
81 this.Disposed = true;
82 }
83
84 #endregion
85 }
86}
diff --git a/src/test/WixToolsetTest.LightIntegration/Utility/TestData.cs b/src/test/WixToolsetTest.LightIntegration/Utility/TestData.cs
new file mode 100644
index 00000000..c13e9d6d
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/Utility/TestData.cs
@@ -0,0 +1,17 @@
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 WixToolsetTest.LightIntegration.Utility
4{
5 using System;
6 using System.IO;
7
8 public class TestData
9 {
10 public static string LocalPath => Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
11
12 public static string Get(params string[] paths)
13 {
14 return Path.Combine(LocalPath, Path.Combine(paths));
15 }
16 }
17}
diff --git a/src/test/WixToolsetTest.LightIntegration/WixToolsetTest.LightIntegration.csproj b/src/test/WixToolsetTest.LightIntegration/WixToolsetTest.LightIntegration.csproj
new file mode 100644
index 00000000..ef303868
--- /dev/null
+++ b/src/test/WixToolsetTest.LightIntegration/WixToolsetTest.LightIntegration.csproj
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>net461</TargetFramework>
7 <IsPackable>false</IsPackable>
8 </PropertyGroup>
9 <ItemGroup>
10 <None Remove="TestData\Wixout\Package.en-us.wxl" />
11 <None Remove="TestData\Wixout\test.wixout" />
12 </ItemGroup>
13
14 <ItemGroup>
15 <Content Include="TestData\Wixout\data\test.txt" CopyToOutputDirectory="PreserveNewest" />
16 <Content Include="TestData\Wixout\Package.en-us.wxl">
17 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
18 </Content>
19 <Content Include="TestData\Wixout\test.wixout">
20 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
21 </Content>
22 </ItemGroup>
23
24 <ItemGroup>
25 <ProjectReference Include="..\..\light\light.csproj" />
26 </ItemGroup>
27
28 <ItemGroup>
29 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
30 <PackageReference Include="xunit" Version="2.2.0" />
31 <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
32 </ItemGroup>
33</Project>