aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/PreprocessorCore.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/PreprocessorCore.cs')
-rw-r--r--src/WixToolset.Core/PreprocessorCore.cs560
1 files changed, 0 insertions, 560 deletions
diff --git a/src/WixToolset.Core/PreprocessorCore.cs b/src/WixToolset.Core/PreprocessorCore.cs
deleted file mode 100644
index b58fc80c..00000000
--- a/src/WixToolset.Core/PreprocessorCore.cs
+++ /dev/null
@@ -1,560 +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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// The preprocessor core.
15 /// </summary>
16 internal class PreprocessorCore : IPreprocessorCore
17 {
18 private static readonly char[] variableSplitter = new char[] { '.' };
19 private static readonly char[] argumentSplitter = new char[] { ',' };
20
21 private Platform currentPlatform;
22 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
23 private string sourceFile;
24 private IDictionary<string, string> variables;
25
26 /// <summary>
27 /// Instantiate a new PreprocessorCore.
28 /// </summary>
29 /// <param name="extensionsByPrefix">The extensions indexed by their prefixes.</param>
30 /// <param name="messageHandler">The message handler.</param>
31 /// <param name="sourceFile">The source file being preprocessed.</param>
32 /// <param name="variables">The variables defined prior to preprocessing.</param>
33 internal PreprocessorCore(Dictionary<string, IPreprocessorExtension> extensionsByPrefix, string sourceFile, IDictionary<string, string> variables)
34 {
35 this.extensionsByPrefix = extensionsByPrefix;
36 this.sourceFile = String.IsNullOrEmpty(sourceFile) ? null : Path.GetFullPath(sourceFile);
37
38 this.variables = new Dictionary<string, string>();
39 foreach (var entry in variables)
40 {
41 this.AddVariable(null, entry.Key, entry.Value);
42 }
43 }
44
45 /// <summary>
46 /// Event for resolved variables.
47 /// </summary>
48 private event ResolvedVariableEventHandler ResolvedVariable;
49
50 /// <summary>
51 /// Sets event for ResolvedVariableEventHandler.
52 /// </summary>
53 public ResolvedVariableEventHandler ResolvedVariableHandler
54 {
55 set { this.ResolvedVariable = value; }
56 }
57
58 /// <summary>
59 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
60 /// </summary>
61 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
62 public Platform CurrentPlatform
63 {
64 get { return this.currentPlatform; }
65 set { this.currentPlatform = value; }
66 }
67
68 /// <summary>
69 /// Gets whether the core encountered an error while processing.
70 /// </summary>
71 /// <value>Flag if core encountered an error during processing.</value>
72 public bool EncounteredError
73 {
74 get { return Messaging.Instance.EncounteredError; }
75 }
76
77 /// <summary>
78 /// Replaces parameters in the source text.
79 /// </summary>
80 /// <param name="sourceLineNumbers">The source line information for the function.</param>
81 /// <param name="value">Text that may contain parameters to replace.</param>
82 /// <returns>Text after parameters have been replaced.</returns>
83 public string PreprocessString(SourceLineNumber sourceLineNumbers, string value)
84 {
85 StringBuilder sb = new StringBuilder();
86 int currentPosition = 0;
87 int end = 0;
88
89 while (-1 != (currentPosition = value.IndexOf('$', end)))
90 {
91 if (end < currentPosition)
92 {
93 sb.Append(value, end, currentPosition - end);
94 }
95
96 end = currentPosition + 1;
97 string remainder = value.Substring(end);
98 if (remainder.StartsWith("$", StringComparison.Ordinal))
99 {
100 sb.Append("$");
101 end++;
102 }
103 else if (remainder.StartsWith("(loc.", StringComparison.Ordinal))
104 {
105 currentPosition = remainder.IndexOf(')');
106 if (-1 == currentPosition)
107 {
108 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
109 break;
110 }
111
112 sb.Append("$"); // just put the resource reference back as was
113 sb.Append(remainder, 0, currentPosition + 1);
114
115 end += currentPosition + 1;
116 }
117 else if (remainder.StartsWith("(", StringComparison.Ordinal))
118 {
119 int openParenCount = 1;
120 int closingParenCount = 0;
121 bool isFunction = false;
122 bool foundClosingParen = false;
123
124 // find the closing paren
125 int closingParenPosition;
126 for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++)
127 {
128 switch (remainder[closingParenPosition])
129 {
130 case '(':
131 openParenCount++;
132 isFunction = true;
133 break;
134 case ')':
135 closingParenCount++;
136 break;
137 }
138 if (openParenCount == closingParenCount)
139 {
140 foundClosingParen = true;
141 break;
142 }
143 }
144
145 // move the currentPosition to the closing paren
146 currentPosition += closingParenPosition;
147
148 if (!foundClosingParen)
149 {
150 if (isFunction)
151 {
152 this.OnMessage(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, remainder));
153 break;
154 }
155 else
156 {
157 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
158 break;
159 }
160 }
161
162 string subString = remainder.Substring(1, closingParenPosition - 1);
163 string result = null;
164 if (isFunction)
165 {
166 result = this.EvaluateFunction(sourceLineNumbers, subString);
167 }
168 else
169 {
170 result = this.GetVariableValue(sourceLineNumbers, subString, false);
171 }
172
173 if (null == result)
174 {
175 if (isFunction)
176 {
177 this.OnMessage(WixErrors.UndefinedPreprocessorFunction(sourceLineNumbers, subString));
178 break;
179 }
180 else
181 {
182 this.OnMessage(WixErrors.UndefinedPreprocessorVariable(sourceLineNumbers, subString));
183 break;
184 }
185 }
186 else
187 {
188 if (!isFunction)
189 {
190 this.OnResolvedVariable(new ResolvedVariableEventArgs(sourceLineNumbers, subString, result));
191 }
192 }
193 sb.Append(result);
194 end += closingParenPosition + 1;
195 }
196 else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing
197 {
198 sb.Append('$');
199 }
200 }
201
202 if (end < value.Length)
203 {
204 sb.Append(value.Substring(end));
205 }
206
207 return sb.ToString();
208 }
209
210 /// <summary>
211 /// Evaluate a Pragma.
212 /// </summary>
213 /// <param name="sourceLineNumbers">The source line information for the function.</param>
214 /// <param name="pragmaName">The pragma's full name (<prefix>.<pragma>).</param>
215 /// <param name="args">The arguments to the pragma.</param>
216 /// <param name="parent">The parent element of the pragma.</param>
217 public void PreprocessPragma(SourceLineNumber sourceLineNumbers, string pragmaName, string args, XContainer parent)
218 {
219 string[] prefixParts = pragmaName.Split(variableSplitter, 2);
220 // Check to make sure there are 2 parts and neither is an empty string.
221 if (2 != prefixParts.Length)
222 {
223 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
224 }
225 string prefix = prefixParts[0];
226 string pragma = prefixParts[1];
227
228 if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma))
229 {
230 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
231 }
232
233 switch (prefix)
234 {
235 case "wix":
236 switch (pragma)
237 {
238 // Add any core defined pragmas here
239 default:
240 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
241 break;
242 }
243 break;
244 default:
245 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
246 if (null == extension || !extension.ProcessPragma(sourceLineNumbers, prefix, pragma, args, parent))
247 {
248 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
249 }
250 break;
251 }
252 }
253
254 /// <summary>
255 /// Evaluate a function.
256 /// </summary>
257 /// <param name="sourceLineNumbers">The source line information for the function.</param>
258 /// <param name="function">The function expression including the prefix and name.</param>
259 /// <returns>The function value.</returns>
260 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string function)
261 {
262 string[] prefixParts = function.Split(variableSplitter, 2);
263 // Check to make sure there are 2 parts and neither is an empty string.
264 if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length)
265 {
266 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
267 }
268 string prefix = prefixParts[0];
269
270 string[] functionParts = prefixParts[1].Split(new char[] { '(' }, 2);
271 // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren.
272 if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal))
273 {
274 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
275 }
276 string functionName = functionParts[0];
277
278 // Remove the trailing closing paren.
279 string allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1);
280
281 // Parse the arguments and preprocess them.
282 string[] args = allArgs.Split(argumentSplitter);
283 for (int i = 0; i < args.Length; i++)
284 {
285 args[i] = this.PreprocessString(sourceLineNumbers, args[i].Trim());
286 }
287
288 string result = this.EvaluateFunction(sourceLineNumbers, prefix, functionName, args);
289
290 // If the function didn't evaluate, try to evaluate the original value as a variable to support
291 // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve.
292 if (null == result)
293 {
294 result = this.GetVariableValue(sourceLineNumbers, function, false);
295 }
296
297 return result;
298 }
299
300 /// <summary>
301 /// Evaluate a function.
302 /// </summary>
303 /// <param name="sourceLineNumbers">The source line information for the function.</param>
304 /// <param name="prefix">The function prefix.</param>
305 /// <param name="function">The function name.</param>
306 /// <param name="args">The arguments for the function.</param>
307 /// <returns>The function value or null if the function is not defined.</returns>
308 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string prefix, string function, string[] args)
309 {
310 if (String.IsNullOrEmpty(prefix))
311 {
312 throw new ArgumentNullException("prefix");
313 }
314
315 if (String.IsNullOrEmpty(function))
316 {
317 throw new ArgumentNullException("function");
318 }
319
320 switch (prefix)
321 {
322 case "fun":
323 switch (function)
324 {
325 case "AutoVersion":
326 // Make sure the base version is specified
327 if (args.Length == 0 || String.IsNullOrEmpty(args[0]))
328 {
329 throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(sourceLineNumbers));
330 }
331
332 // Build = days since 1/1/2000; Revision = seconds since midnight / 2
333 DateTime now = DateTime.UtcNow;
334 TimeSpan build = now - new DateTime(2000, 1, 1);
335 TimeSpan revision = now - new DateTime(now.Year, now.Month, now.Day);
336
337 return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2));
338
339 default:
340 return null;
341 }
342 default:
343 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
344 if (null != extension)
345 {
346 try
347 {
348 return extension.EvaluateFunction(prefix, function, args);
349 }
350 catch (Exception e)
351 {
352 throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(sourceLineNumbers, prefix, function, String.Join(",", args), e.Message));
353 }
354 }
355 else
356 {
357 return null;
358 }
359 }
360 }
361
362 /// <summary>
363 /// Get the value of a variable expression like var.name.
364 /// </summary>
365 /// <param name="sourceLineNumbers">The source line information for the variable.</param>
366 /// <param name="variable">The variable expression including the optional prefix and name.</param>
367 /// <param name="allowMissingPrefix">true to allow the variable prefix to be missing.</param>
368 /// <returns>The variable value.</returns>
369 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string variable, bool allowMissingPrefix)
370 {
371 // Strip the "$(" off the front.
372 if (variable.StartsWith("$(", StringComparison.Ordinal))
373 {
374 variable = variable.Substring(2);
375 }
376
377 string[] parts = variable.Split(variableSplitter, 2);
378
379 if (1 == parts.Length) // missing prefix
380 {
381 if (allowMissingPrefix)
382 {
383 return this.GetVariableValue(sourceLineNumbers, "var", parts[0]);
384 }
385 else
386 {
387 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
388 }
389 }
390 else
391 {
392 // check for empty variable name
393 if (0 < parts[1].Length)
394 {
395 string result = this.GetVariableValue(sourceLineNumbers, parts[0], parts[1]);
396
397 // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix
398 if (null == result && allowMissingPrefix && variable.Contains("."))
399 {
400 result = this.GetVariableValue(sourceLineNumbers, "var", variable);
401 }
402
403 return result;
404 }
405 else
406 {
407 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
408 }
409 }
410 }
411
412 /// <summary>
413 /// Get the value of a variable.
414 /// </summary>
415 /// <param name="sourceLineNumbers">The source line information for the function.</param>
416 /// <param name="prefix">The variable prefix.</param>
417 /// <param name="name">The variable name.</param>
418 /// <returns>The variable value or null if the variable is not set.</returns>
419 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string prefix, string name)
420 {
421 if (String.IsNullOrEmpty(prefix))
422 {
423 throw new ArgumentNullException("prefix");
424 }
425
426 if (String.IsNullOrEmpty(name))
427 {
428 throw new ArgumentNullException("name");
429 }
430
431 switch (prefix)
432 {
433 case "env":
434 return Environment.GetEnvironmentVariable(name);
435 case "sys":
436 switch (name)
437 {
438 case "CURRENTDIR":
439 return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar);
440 case "SOURCEFILEDIR":
441 return String.Concat(Path.GetDirectoryName(sourceLineNumbers.FileName), Path.DirectorySeparatorChar);
442 case "SOURCEFILEPATH":
443 return sourceLineNumbers.FileName;
444 case "PLATFORM":
445 this.OnMessage(WixWarnings.DeprecatedPreProcVariable(sourceLineNumbers, "$(sys.PLATFORM)", "$(sys.BUILDARCH)"));
446
447 goto case "BUILDARCH";
448
449 case "BUILDARCH":
450 switch (this.currentPlatform)
451 {
452 case Platform.X86:
453 return "x86";
454 case Platform.X64:
455 return "x64";
456 case Platform.IA64:
457 return "ia64";
458 case Platform.ARM:
459 return "arm";
460 default:
461 throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.currentPlatform.ToString());
462 }
463 default:
464 return null;
465 }
466 case "var":
467 string result = null;
468 return this.variables.TryGetValue(name, out result) ? result : null;
469 default:
470 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
471 if (null != extension)
472 {
473 try
474 {
475 return extension.GetVariableValue(prefix, name);
476 }
477 catch (Exception e)
478 {
479 throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(sourceLineNumbers, prefix, name, e.Message));
480 }
481 }
482 else
483 {
484 return null;
485 }
486 }
487 }
488
489 /// <summary>
490 /// Sends a message to the message delegate if there is one.
491 /// </summary>
492 /// <param name="mea">Message event arguments.</param>
493 public void OnMessage(MessageEventArgs e)
494 {
495 Messaging.Instance.OnMessage(e);
496 }
497
498 /// <summary>
499 /// Sends resolved variable to delegate if there is one.
500 /// </summary>
501 /// <param name="mea">Message event arguments.</param>
502 public void OnResolvedVariable(ResolvedVariableEventArgs mea)
503 {
504 if (null != this.ResolvedVariable)
505 {
506 this.ResolvedVariable(this, mea);
507 }
508 }
509
510 /// <summary>
511 /// Add a variable.
512 /// </summary>
513 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
514 /// <param name="name">The variable name.</param>
515 /// <param name="value">The variable value.</param>
516 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value)
517 {
518 this.AddVariable(sourceLineNumbers, name, value, true);
519 }
520
521 /// <summary>
522 /// Add a variable.
523 /// </summary>
524 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
525 /// <param name="name">The variable name.</param>
526 /// <param name="value">The variable value.</param>
527 /// <param name="overwrite">Set to true to show variable overwrite warning.</param>
528 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value, bool showWarning)
529 {
530 string currentValue = this.GetVariableValue(sourceLineNumbers, "var", name);
531
532 if (null == currentValue)
533 {
534 this.variables.Add(name, value);
535 }
536 else
537 {
538 if (showWarning)
539 {
540 this.OnMessage(WixWarnings.VariableDeclarationCollision(sourceLineNumbers, name, value, currentValue));
541 }
542
543 this.variables[name] = value;
544 }
545 }
546
547 /// <summary>
548 /// Remove a variable.
549 /// </summary>
550 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
551 /// <param name="name">The variable name.</param>
552 internal void RemoveVariable(SourceLineNumber sourceLineNumbers, string name)
553 {
554 if (!this.variables.Remove(name))
555 {
556 this.OnMessage(WixErrors.CannotReundefineVariable(sourceLineNumbers, name));
557 }
558 }
559 }
560}