Expose converter as a command-line command for use by wix.exe
Rob Mensching committed
Jun 6, 2020 at 15:40 UTC
60c9a081d337bf432a61bee6784d4c05da1a049a
7 files changed
+378
-2
src/WixToolset.Converters.Tupleizer/WixToolset.Converters.Tupleizer.csproj
+1
-1
@@ -28,7 +28,7 @@
28
</ItemGroup>
29
30
<ItemGroup>
31
- <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All"/>
31
+ <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
32
<PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
33
</ItemGroup>
34
</Project>
src/WixToolset.Converters/ConvertCommand.cs
new
+281
@@ -0,0 +1,281 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Converters
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+ using System.Xml;
9
+ using WixToolset.Extensibility.Data;
10
+ using WixToolset.Extensibility.Services;
11
+
12
+ internal class ConvertCommand : ICommandLineCommand
13
+ {
14
+ private const string SettingsFileDefault = "wixcop.settings.xml";
15
+
16
+ public ConvertCommand(IWixToolsetServiceProvider serviceProvider)
17
+ {
18
+ this.Messaging = serviceProvider.GetService<IMessaging>();
19
+
20
+ this.IndentationAmount = 4; // default indentation amount
21
+ this.ErrorsAsWarnings = new HashSet<string>();
22
+ this.ExemptFiles = new HashSet<string>();
23
+ this.IgnoreErrors = new HashSet<string>();
24
+ this.SearchPatternResults = new HashSet<string>();
25
+ this.SearchPatterns = new List<string>();
26
+ }
27
+
28
+ private IMessaging Messaging { get; }
29
+
30
+ public bool ShowLogo { get; private set; }
31
+
32
+ public bool StopParsing { get; private set; }
33
+
34
+ private bool ShowHelp { get; set; }
35
+
36
+ private HashSet<string> ErrorsAsWarnings { get; }
37
+
38
+ private HashSet<string> ExemptFiles { get; }
39
+
40
+ private bool FixErrors { get; set; }
41
+
42
+ private int IndentationAmount { get; set; }
43
+
44
+ private HashSet<string> IgnoreErrors { get; }
45
+
46
+ private HashSet<string> SearchPatternResults { get; }
47
+
48
+ private List<string> SearchPatterns { get; }
49
+
50
+ private string SettingsFile1 { get; set; }
51
+
52
+ private string SettingsFile2 { get; set; }
53
+
54
+ private bool SubDirectories { get; set; }
55
+
56
+ public bool TryParseArgument(ICommandLineParser parser, string argument)
57
+ {
58
+ if (!parser.IsSwitch(argument))
59
+ {
60
+ this.SearchPatterns.Add(argument);
61
+ return true;
62
+ }
63
+
64
+ var parameter = argument.Substring(1);
65
+ switch (parameter.ToLowerInvariant())
66
+ {
67
+ case "?":
68
+ this.ShowHelp = true;
69
+ this.ShowLogo = true;
70
+ this.StopParsing = true;
71
+ return true;
72
+
73
+ case "f":
74
+ this.FixErrors = true;
75
+ return true;
76
+
77
+ case "nologo":
78
+ this.ShowLogo = false;
79
+ return true;
80
+
81
+ case "s":
82
+ this.SubDirectories = true;
83
+ return true;
84
+
85
+ default: // other parameters
86
+ if (parameter.StartsWith("set1", StringComparison.Ordinal))
87
+ {
88
+ this.SettingsFile1 = parameter.Substring(4);
89
+ return true;
90
+ }
91
+ else if (parameter.StartsWith("set2", StringComparison.Ordinal))
92
+ {
93
+ this.SettingsFile2 = parameter.Substring(4);
94
+ return true;
95
+ }
96
+ else if (parameter.StartsWith("indent:", StringComparison.Ordinal))
97
+ {
98
+ try
99
+ {
100
+ this.IndentationAmount = Convert.ToInt32(parameter.Substring(7));
101
+ }
102
+ catch
103
+ {
104
+ parser.ErrorArgument = parameter; // $"Invalid numeric argument: {parameter}";
105
+ }
106
+ return true;
107
+ }
108
+
109
+ return false;
110
+ }
111
+ }
112
+
113
+ public int Execute()
114
+ {
115
+ if (this.ShowHelp)
116
+ {
117
+ DisplayHelp();
118
+ return 1;
119
+ }
120
+
121
+ // parse the settings if any were specified
122
+ if (null != this.SettingsFile1 || null != this.SettingsFile2)
123
+ {
124
+ this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2);
125
+ }
126
+ else
127
+ {
128
+ if (File.Exists(ConvertCommand.SettingsFileDefault))
129
+ {
130
+ this.ParseSettingsFiles(ConvertCommand.SettingsFileDefault, null);
131
+ }
132
+ }
133
+
134
+ var converter = new Wix3Converter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors);
135
+
136
+ var errors = this.InspectSubDirectories(converter, Path.GetFullPath("."));
137
+
138
+ foreach (var searchPattern in this.SearchPatterns)
139
+ {
140
+ if (!this.SearchPatternResults.Contains(searchPattern))
141
+ {
142
+ Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern);
143
+ errors++;
144
+ }
145
+ }
146
+
147
+ return errors != 0 ? 2 : 0;
148
+ }
149
+
150
+ private static void DisplayHelp()
151
+ {
152
+ Console.WriteLine(" usage: wix.exe convert sourceFile [sourceFile ...]");
153
+ Console.WriteLine();
154
+ Console.WriteLine(" -f fix errors automatically for writable files");
155
+ Console.WriteLine(" -nologo suppress displaying the logo information");
156
+ Console.WriteLine(" -s search for matching files in current dir and subdirs");
157
+ Console.WriteLine(" -set1<file> primary settings file");
158
+ Console.WriteLine(" -set2<file> secondary settings file (overrides primary)");
159
+ Console.WriteLine(" -indent:<n> indentation multiple (overrides default of 4)");
160
+ Console.WriteLine(" -? this help information");
161
+ Console.WriteLine();
162
+ Console.WriteLine(" sourceFile may use wildcards like *.wxs");
163
+ }
164
+
165
+ /// <summary>
166
+ /// Get the files that match a search path pattern.
167
+ /// </summary>
168
+ /// <param name="baseDir">The base directory at which to begin the search.</param>
169
+ /// <param name="searchPath">The search path pattern.</param>
170
+ /// <returns>The files matching the pattern.</returns>
171
+ private static string[] GetFiles(string baseDir, string searchPath)
172
+ {
173
+ // convert alternate directory separators to the standard one
174
+ var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
175
+ var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
176
+ string[] files = null;
177
+
178
+ try
179
+ {
180
+ if (0 > lastSeparator)
181
+ {
182
+ files = Directory.GetFiles(baseDir, filePath);
183
+ }
184
+ else // found directory separator
185
+ {
186
+ var searchPattern = filePath.Substring(lastSeparator + 1);
187
+
188
+ files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern);
189
+ }
190
+ }
191
+ catch (DirectoryNotFoundException)
192
+ {
193
+ // don't let this function throw the DirectoryNotFoundException. (this exception
194
+ // occurs for non-existant directories and invalid characters in the searchPattern)
195
+ }
196
+
197
+ return files;
198
+ }
199
+
200
+ /// <summary>
201
+ /// Inspect sub-directories.
202
+ /// </summary>
203
+ /// <param name="directory">The directory whose sub-directories will be inspected.</param>
204
+ /// <returns>The number of errors that were found.</returns>
205
+ private int InspectSubDirectories(Wix3Converter converter, string directory)
206
+ {
207
+ var errors = 0;
208
+
209
+ foreach (var searchPattern in this.SearchPatterns)
210
+ {
211
+ foreach (var sourceFilePath in GetFiles(directory, searchPattern))
212
+ {
213
+ var file = new FileInfo(sourceFilePath);
214
+
215
+ if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant()))
216
+ {
217
+ this.SearchPatternResults.Add(searchPattern);
218
+ errors += converter.ConvertFile(file.FullName, this.FixErrors);
219
+ }
220
+ }
221
+ }
222
+
223
+ if (this.SubDirectories)
224
+ {
225
+ foreach (var childDirectoryPath in Directory.GetDirectories(directory))
226
+ {
227
+ errors += this.InspectSubDirectories(converter, childDirectoryPath);
228
+ }
229
+ }
230
+
231
+ return errors;
232
+ }
233
+
234
+ /// <summary>
235
+ /// Parse the primary and secondary settings files.
236
+ /// </summary>
237
+ /// <param name="localSettingsFile1">The primary settings file.</param>
238
+ /// <param name="localSettingsFile2">The secondary settings file.</param>
239
+ private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2)
240
+ {
241
+ if (null == localSettingsFile1 && null != localSettingsFile2)
242
+ {
243
+ throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2));
244
+ }
245
+
246
+ var settingsFile = localSettingsFile1;
247
+ while (null != settingsFile)
248
+ {
249
+ var doc = new XmlDocument();
250
+ doc.Load(settingsFile);
251
+
252
+ // get the types of tests that will have their errors displayed as warnings
253
+ var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test");
254
+ foreach (XmlElement test in testsIgnoredElements)
255
+ {
256
+ var key = test.GetAttribute("Id");
257
+ this.IgnoreErrors.Add(key);
258
+ }
259
+
260
+ // get the types of tests that will have their errors displayed as warnings
261
+ var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test");
262
+ foreach (XmlElement test in testsAsWarningsElements)
263
+ {
264
+ var key = test.GetAttribute("Id");
265
+ this.ErrorsAsWarnings.Add(key);
266
+ }
267
+
268
+ // get the exempt files
269
+ var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File");
270
+ foreach (XmlElement file in localExemptFiles)
271
+ {
272
+ var key = file.GetAttribute("Name").ToUpperInvariant();
273
+ this.ExemptFiles.Add(key);
274
+ }
275
+
276
+ settingsFile = localSettingsFile2;
277
+ localSettingsFile2 = null;
278
+ }
279
+ }
280
+ }
281
+}
src/WixToolset.Converters/ConverterExtensionCommandLine.cs
new
+39
@@ -0,0 +1,39 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Converters
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using WixToolset.Extensibility;
8
+ using WixToolset.Extensibility.Data;
9
+ using WixToolset.Extensibility.Services;
10
+
11
+ /// <summary>
12
+ /// Parses the "convert" command-line command. See <c>ConvertCommand</c> for
13
+ /// the bulk of the command-line processing.
14
+ /// </summary>
15
+ internal class ConverterExtensionCommandLine : BaseExtensionCommandLine
16
+ {
17
+ public ConverterExtensionCommandLine(IWixToolsetServiceProvider serviceProvider)
18
+ {
19
+ this.ServiceProvider = serviceProvider;
20
+ }
21
+
22
+ private IWixToolsetServiceProvider ServiceProvider { get; }
23
+
24
+ // TODO: Do something with CommandLineSwitches
25
+ public override IEnumerable<ExtensionCommandLineSwitch> CommandLineSwitches => base.CommandLineSwitches;
26
+
27
+ public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command)
28
+ {
29
+ command = null;
30
+
31
+ if ("convert".Equals(argument, StringComparison.OrdinalIgnoreCase))
32
+ {
33
+ command = new ConvertCommand(this.ServiceProvider);
34
+ }
35
+
36
+ return command != null;
37
+ }
38
+ }
39
+}
src/WixToolset.Converters/ConverterExtensionFactory.cs
new
+30
@@ -0,0 +1,30 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Converters
4
+{
5
+ using System;
6
+ using WixToolset.Extensibility;
7
+ using WixToolset.Extensibility.Services;
8
+
9
+ internal class ConverterExtensionFactory : IExtensionFactory
10
+ {
11
+ public ConverterExtensionFactory(IWixToolsetServiceProvider serviceProvider)
12
+ {
13
+ this.ServiceProvider = serviceProvider;
14
+ }
15
+
16
+ private IWixToolsetServiceProvider ServiceProvider { get; }
17
+
18
+ public bool TryCreateExtension(Type extensionType, out object extension)
19
+ {
20
+ extension = null;
21
+
22
+ if (extensionType == typeof(IExtensionCommandLine))
23
+ {
24
+ extension = new ConverterExtensionCommandLine(this.ServiceProvider);
25
+ }
26
+
27
+ return extension != null;
28
+ }
29
+ }
30
+}
src/WixToolset.Converters/WixToolset.Converters.csproj
+1
-1
@@ -20,7 +20,7 @@
20
</ItemGroup>
21
22
<ItemGroup>
23
- <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All"/>
23
+ <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
24
<PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
25
</ItemGroup>
26
</Project>
src/WixToolset.Converters/WixToolsetCoreServiceProviderExtensions.cs
new
+17
@@ -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
+
3
+namespace WixToolset.Converters
4
+{
5
+ using WixToolset.Extensibility.Services;
6
+
7
+ public static class WixToolsetCoreServiceProviderExtensions
8
+ {
9
+ public static IWixToolsetCoreServiceProvider AddConverter(this IWixToolsetCoreServiceProvider serviceProvider)
10
+ {
11
+ var extensionManager = serviceProvider.GetService<IExtensionManager>();
12
+ extensionManager.Add(typeof(ConverterExtensionFactory).Assembly);
13
+
14
+ return serviceProvider;
15
+ }
16
+ }
17
+}
src/test/WixToolsetTest.Converters.Tupleizer/WixToolsetTest.Converters.Tupleizer.v3.ncrunchproject
new
+9
@@ -0,0 +1,9 @@
1
+<ProjectConfiguration>
2
+ <Settings>
3
+ <IgnoredTests>
4
+ <NamedTestSelector>
5
+ <TestName>WixToolsetTest.Converters.Tupleizer.ConvertTuplesFixture.CanLoadWixoutAndConvertToIntermediate</TestName>
6
+ </NamedTestSelector>
7
+ </IgnoredTests>
8
+ </Settings>
9
+</ProjectConfiguration>
\ No newline at end of file