@joebigelow / wix / commits / 60c9a081

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