diff options
Diffstat (limited to 'src/WixToolset.BuildTasks/WixToolTask.cs')
-rw-r--r-- | src/WixToolset.BuildTasks/WixToolTask.cs | 403 |
1 files changed, 0 insertions, 403 deletions
diff --git a/src/WixToolset.BuildTasks/WixToolTask.cs b/src/WixToolset.BuildTasks/WixToolTask.cs deleted file mode 100644 index 60305e00..00000000 --- a/src/WixToolset.BuildTasks/WixToolTask.cs +++ /dev/null | |||
@@ -1,403 +0,0 @@ | |||
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.BuildTasks | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | using System.Globalization; | ||
9 | using System.IO; | ||
10 | using System.Reflection; | ||
11 | using System.Text; | ||
12 | using System.Threading; | ||
13 | |||
14 | using Microsoft.Build.Framework; | ||
15 | using Microsoft.Build.Utilities; | ||
16 | using WixToolset.Core.CommandLine; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Base class for WiX tool tasks; executes tools in-process | ||
20 | /// so that repeated invocations are much faster. | ||
21 | /// </summary> | ||
22 | public abstract class WixToolTask : ToolTask, IDisposable | ||
23 | { | ||
24 | private string additionalOptions; | ||
25 | private bool disposed; | ||
26 | private bool noLogo; | ||
27 | private bool runAsSeparateProcess; | ||
28 | private bool suppressAllWarnings; | ||
29 | private string[] suppressSpecificWarnings; | ||
30 | private string[] treatSpecificWarningsAsErrors; | ||
31 | private bool treatWarningsAsErrors; | ||
32 | private bool verboseOutput; | ||
33 | private Queue<string> messageQueue; | ||
34 | private ManualResetEvent messagesAvailable; | ||
35 | private ManualResetEvent toolExited; | ||
36 | private int exitCode; | ||
37 | |||
38 | /// <summary> | ||
39 | /// Gets or sets additional options that are appended the the tool command-line. | ||
40 | /// </summary> | ||
41 | /// <remarks> | ||
42 | /// This allows the task to support extended options in the tool which are not | ||
43 | /// explicitly implemented as properties on the task. | ||
44 | /// </remarks> | ||
45 | public string AdditionalOptions | ||
46 | { | ||
47 | get { return this.additionalOptions; } | ||
48 | set { this.additionalOptions = value; } | ||
49 | } | ||
50 | |||
51 | /// <summary> | ||
52 | /// Gets or sets a flag indicating whether the task should be run as separate | ||
53 | /// process instead of in-proc with MSBuild which is the default. | ||
54 | /// </summary> | ||
55 | public bool RunAsSeparateProcess | ||
56 | { | ||
57 | get { return this.runAsSeparateProcess; } | ||
58 | set { this.runAsSeparateProcess = value; } | ||
59 | } | ||
60 | |||
61 | #region Common Options | ||
62 | /// <summary> | ||
63 | /// Gets or sets whether all warnings should be suppressed. | ||
64 | /// </summary> | ||
65 | public bool SuppressAllWarnings | ||
66 | { | ||
67 | get { return this.suppressAllWarnings; } | ||
68 | set { this.suppressAllWarnings = value; } | ||
69 | } | ||
70 | |||
71 | /// <summary> | ||
72 | /// Gets or sets a list of specific warnings to be suppressed. | ||
73 | /// </summary> | ||
74 | public string[] SuppressSpecificWarnings | ||
75 | { | ||
76 | get { return this.suppressSpecificWarnings; } | ||
77 | set { this.suppressSpecificWarnings = value; } | ||
78 | } | ||
79 | |||
80 | /// <summary> | ||
81 | /// Gets or sets whether all warnings should be treated as errors. | ||
82 | /// </summary> | ||
83 | public bool TreatWarningsAsErrors | ||
84 | { | ||
85 | get { return this.treatWarningsAsErrors; } | ||
86 | set { this.treatWarningsAsErrors = value; } | ||
87 | } | ||
88 | |||
89 | /// <summary> | ||
90 | /// Gets or sets a list of specific warnings to treat as errors. | ||
91 | /// </summary> | ||
92 | public string[] TreatSpecificWarningsAsErrors | ||
93 | { | ||
94 | get { return this.treatSpecificWarningsAsErrors; } | ||
95 | set { this.treatSpecificWarningsAsErrors = value; } | ||
96 | } | ||
97 | |||
98 | /// <summary> | ||
99 | /// Gets or sets whether to display verbose output. | ||
100 | /// </summary> | ||
101 | public bool VerboseOutput | ||
102 | { | ||
103 | get { return this.verboseOutput; } | ||
104 | set { this.verboseOutput = value; } | ||
105 | } | ||
106 | |||
107 | /// <summary> | ||
108 | /// Gets or sets whether to display the logo. | ||
109 | /// </summary> | ||
110 | public bool NoLogo | ||
111 | { | ||
112 | get { return this.noLogo; } | ||
113 | set { this.noLogo = value; } | ||
114 | } | ||
115 | #endregion | ||
116 | |||
117 | /// <summary> | ||
118 | /// Cleans up the ManualResetEvent members | ||
119 | /// </summary> | ||
120 | public void Dispose() | ||
121 | { | ||
122 | if (!this.disposed) | ||
123 | { | ||
124 | this.Dispose(true); | ||
125 | GC.SuppressFinalize(this); | ||
126 | disposed = true; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | /// <summary> | ||
131 | /// Cleans up the ManualResetEvent members | ||
132 | /// </summary> | ||
133 | protected virtual void Dispose(bool disposing) | ||
134 | { | ||
135 | if (disposing) | ||
136 | { | ||
137 | messagesAvailable.Close(); | ||
138 | toolExited.Close(); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | /// <summary> | ||
143 | /// Generate the command line arguments to write to the response file from the properties. | ||
144 | /// </summary> | ||
145 | /// <returns>Command line string.</returns> | ||
146 | protected override string GenerateResponseFileCommands() | ||
147 | { | ||
148 | WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder(); | ||
149 | this.BuildCommandLine(commandLineBuilder); | ||
150 | return commandLineBuilder.ToString(); | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Builds a command line from options in this and derivative tasks. | ||
155 | /// </summary> | ||
156 | /// <remarks> | ||
157 | /// Derivative classes should call BuildCommandLine() on the base class to ensure that common command line options are added to the command. | ||
158 | /// </remarks> | ||
159 | protected virtual void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) | ||
160 | { | ||
161 | commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo); | ||
162 | commandLineBuilder.AppendArrayIfNotNull("-sw", this.SuppressSpecificWarnings); | ||
163 | commandLineBuilder.AppendIfTrue("-sw", this.SuppressAllWarnings); | ||
164 | commandLineBuilder.AppendIfTrue("-v", this.VerboseOutput); | ||
165 | commandLineBuilder.AppendArrayIfNotNull("-wx", this.TreatSpecificWarningsAsErrors); | ||
166 | commandLineBuilder.AppendIfTrue("-wx", this.TreatWarningsAsErrors); | ||
167 | } | ||
168 | |||
169 | /// <summary> | ||
170 | /// Executes a tool in-process by loading the tool assembly and invoking its entrypoint. | ||
171 | /// </summary> | ||
172 | /// <param name="pathToTool">Path to the tool to be executed; must be a managed executable.</param> | ||
173 | /// <param name="responseFileCommands">Commands to be written to a response file.</param> | ||
174 | /// <param name="commandLineCommands">Commands to be passed directly on the command-line.</param> | ||
175 | /// <returns>The tool exit code.</returns> | ||
176 | protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) | ||
177 | { | ||
178 | if (this.RunAsSeparateProcess) | ||
179 | { | ||
180 | return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); | ||
181 | } | ||
182 | |||
183 | this.messageQueue = new Queue<string>(); | ||
184 | this.messagesAvailable = new ManualResetEvent(false); | ||
185 | this.toolExited = new ManualResetEvent(false); | ||
186 | |||
187 | WixToolTaskLogger logger = new WixToolTaskLogger(this.messageQueue, this.messagesAvailable); | ||
188 | TextWriter saveConsoleOut = Console.Out; | ||
189 | TextWriter saveConsoleError = Console.Error; | ||
190 | Console.SetOut(logger); | ||
191 | Console.SetError(logger); | ||
192 | |||
193 | string responseFile = null; | ||
194 | try | ||
195 | { | ||
196 | responseFile = this.GetTemporaryResponseFile(responseFileCommands, out var responseFileSwitch); | ||
197 | if (!String.IsNullOrEmpty(responseFileSwitch)) | ||
198 | { | ||
199 | commandLineCommands = commandLineCommands + " " + responseFileSwitch; | ||
200 | } | ||
201 | |||
202 | string[] arguments = CommandLineResponseFile.ParseArgumentsToArray(commandLineCommands); | ||
203 | |||
204 | Thread toolThread = new Thread(new ParameterizedThreadStart(this.ExecuteToolThread)); | ||
205 | toolThread.Start(new object[] { pathToTool, arguments }); | ||
206 | |||
207 | this.HandleToolMessages(); | ||
208 | |||
209 | if (this.exitCode == 0 && this.Log.HasLoggedErrors) | ||
210 | { | ||
211 | this.exitCode = -1; | ||
212 | } | ||
213 | |||
214 | return this.exitCode; | ||
215 | } | ||
216 | finally | ||
217 | { | ||
218 | if (responseFile != null) | ||
219 | { | ||
220 | File.Delete(responseFile); | ||
221 | } | ||
222 | |||
223 | Console.SetOut(saveConsoleOut); | ||
224 | Console.SetError(saveConsoleError); | ||
225 | } | ||
226 | } | ||
227 | |||
228 | /// <summary> | ||
229 | /// Called by a new thread to execute the tool in that thread. | ||
230 | /// </summary> | ||
231 | /// <param name="parameters">Tool path and arguments array.</param> | ||
232 | private void ExecuteToolThread(object parameters) | ||
233 | { | ||
234 | try | ||
235 | { | ||
236 | object[] pathAndArguments = (object[])parameters; | ||
237 | Assembly toolAssembly = Assembly.LoadFrom((string)pathAndArguments[0]); | ||
238 | this.exitCode = (int)toolAssembly.EntryPoint.Invoke(null, new object[] { pathAndArguments[1] }); | ||
239 | } | ||
240 | catch (FileNotFoundException fnfe) | ||
241 | { | ||
242 | Log.LogError("Unable to load tool from path {0}. Consider setting the ToolPath parameter to $(WixToolPath).", fnfe.FileName); | ||
243 | this.exitCode = -1; | ||
244 | } | ||
245 | catch (Exception ex) | ||
246 | { | ||
247 | this.exitCode = -1; | ||
248 | this.LogEventsFromTextOutput(ex.Message, MessageImportance.High); | ||
249 | foreach (string stackTraceLine in ex.StackTrace.Split('\n')) | ||
250 | { | ||
251 | this.LogEventsFromTextOutput(stackTraceLine.TrimEnd(), MessageImportance.High); | ||
252 | } | ||
253 | |||
254 | throw; | ||
255 | } | ||
256 | finally | ||
257 | { | ||
258 | this.toolExited.Set(); | ||
259 | } | ||
260 | } | ||
261 | |||
262 | /// <summary> | ||
263 | /// Waits for messages from the tool thread and sends them to the MSBuild logger on the original thread. | ||
264 | /// Returns when the tool thread exits. | ||
265 | /// </summary> | ||
266 | private void HandleToolMessages() | ||
267 | { | ||
268 | WaitHandle[] waitHandles = new WaitHandle[] { this.messagesAvailable, this.toolExited }; | ||
269 | while (WaitHandle.WaitAny(waitHandles) == 0) | ||
270 | { | ||
271 | lock (this.messageQueue) | ||
272 | { | ||
273 | while (this.messageQueue.Count > 0) | ||
274 | { | ||
275 | this.LogEventsFromTextOutput(messageQueue.Dequeue(), MessageImportance.Normal); | ||
276 | } | ||
277 | |||
278 | this.messagesAvailable.Reset(); | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | /// <summary> | ||
284 | /// Creates a temporary response file for tool execution. | ||
285 | /// </summary> | ||
286 | /// <returns>Path to the response file.</returns> | ||
287 | /// <remarks> | ||
288 | /// The temporary file should be deleted after the tool execution is finished. | ||
289 | /// </remarks> | ||
290 | private string GetTemporaryResponseFile(string responseFileCommands, out string responseFileSwitch) | ||
291 | { | ||
292 | string responseFile = null; | ||
293 | responseFileSwitch = null; | ||
294 | |||
295 | if (!String.IsNullOrEmpty(responseFileCommands)) | ||
296 | { | ||
297 | responseFile = Path.GetTempFileName(); | ||
298 | using (StreamWriter writer = new StreamWriter(responseFile, false, this.ResponseFileEncoding)) | ||
299 | { | ||
300 | writer.Write(responseFileCommands); | ||
301 | } | ||
302 | responseFileSwitch = this.GetResponseFileSwitch(responseFile); | ||
303 | } | ||
304 | return responseFile; | ||
305 | } | ||
306 | |||
307 | /// <summary> | ||
308 | /// Cycles thru each task to find correct path of the file in question. | ||
309 | /// Looks at item spec, hintpath and then in user defined Reference Paths | ||
310 | /// </summary> | ||
311 | /// <param name="tasks">Input task array</param> | ||
312 | /// <param name="referencePaths">SemiColon delimited directories to search</param> | ||
313 | /// <returns>List of task item file paths</returns> | ||
314 | [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] | ||
315 | protected static List<string> AdjustFilePaths(ITaskItem[] tasks, string[] referencePaths) | ||
316 | { | ||
317 | List<string> sourceFilePaths = new List<string>(); | ||
318 | |||
319 | if (tasks == null) | ||
320 | { | ||
321 | return sourceFilePaths; | ||
322 | } | ||
323 | |||
324 | foreach (ITaskItem task in tasks) | ||
325 | { | ||
326 | string filePath = task.ItemSpec; | ||
327 | if (!File.Exists(filePath)) | ||
328 | { | ||
329 | filePath = task.GetMetadata("HintPath"); | ||
330 | if (!File.Exists(filePath)) | ||
331 | { | ||
332 | string searchPath = FileSearchHelperMethods.SearchFilePaths(referencePaths, filePath); | ||
333 | if (!String.IsNullOrEmpty(searchPath)) | ||
334 | { | ||
335 | filePath = searchPath; | ||
336 | } | ||
337 | } | ||
338 | } | ||
339 | sourceFilePaths.Add(filePath); | ||
340 | } | ||
341 | |||
342 | return sourceFilePaths; | ||
343 | } | ||
344 | |||
345 | /// <summary> | ||
346 | /// Used as a replacement for Console.Out to capture output from a tool | ||
347 | /// and redirect it to the MSBuild logging system. | ||
348 | /// </summary> | ||
349 | private class WixToolTaskLogger : TextWriter | ||
350 | { | ||
351 | private StringBuilder buffer; | ||
352 | private Queue<string> messageQueue; | ||
353 | private ManualResetEvent messagesAvailable; | ||
354 | |||
355 | /// <summary> | ||
356 | /// Creates a new logger that sends tool output to the tool task's log handler. | ||
357 | /// </summary> | ||
358 | public WixToolTaskLogger(Queue<string> messageQueue, ManualResetEvent messagesAvailable) : base(CultureInfo.CurrentCulture) | ||
359 | { | ||
360 | this.messageQueue = messageQueue; | ||
361 | this.messagesAvailable = messagesAvailable; | ||
362 | this.buffer = new StringBuilder(); | ||
363 | } | ||
364 | |||
365 | /// <summary> | ||
366 | /// Gets the encoding of the logger. | ||
367 | /// </summary> | ||
368 | public override Encoding Encoding | ||
369 | { | ||
370 | get { return Encoding.Unicode; } | ||
371 | } | ||
372 | |||
373 | /// <summary> | ||
374 | /// Redirects output to a buffer; watches for newlines and sends each line to the | ||
375 | /// MSBuild logging system. | ||
376 | /// </summary> | ||
377 | /// <param name="value">Character being written.</param> | ||
378 | /// <remarks>All other Write() variants eventually call into this one.</remarks> | ||
379 | public override void Write(char value) | ||
380 | { | ||
381 | lock (this.messageQueue) | ||
382 | { | ||
383 | if (value == '\n') | ||
384 | { | ||
385 | if (this.buffer.Length > 0 && this.buffer[this.buffer.Length - 1] == '\r') | ||
386 | { | ||
387 | this.buffer.Length = this.buffer.Length - 1; | ||
388 | } | ||
389 | |||
390 | this.messageQueue.Enqueue(this.buffer.ToString()); | ||
391 | this.messagesAvailable.Set(); | ||
392 | |||
393 | this.buffer.Length = 0; | ||
394 | } | ||
395 | else | ||
396 | { | ||
397 | this.buffer.Append(value); | ||
398 | } | ||
399 | } | ||
400 | } | ||
401 | } | ||
402 | } | ||
403 | } | ||