aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.BuildTasks/WixToolTask.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.BuildTasks/WixToolTask.cs')
-rw-r--r--src/WixToolset.BuildTasks/WixToolTask.cs403
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
3namespace 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}