aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2020-07-10 23:41:25 -0700
committerRob Mensching <rob@firegiant.com>2020-07-14 10:53:45 -0700
commitf4b7d3f7c0a49e70fe5d25a309387786589488a6 (patch)
tree74162de1ece12ecd88c7c429f8f0ed155ca6b653
parent28f41d1afec40d79459402fbed21f6c237768adb (diff)
downloadwix-f4b7d3f7c0a49e70fe5d25a309387786589488a6.tar.gz
wix-f4b7d3f7c0a49e70fe5d25a309387786589488a6.tar.bz2
wix-f4b7d3f7c0a49e70fe5d25a309387786589488a6.zip
Major performance improvements to inline directory syntax parsing
-rw-r--r--src/WixToolset.Core/Common.cs21
-rw-r--r--src/WixToolset.Core/Compiler.cs168
-rw-r--r--src/WixToolset.Core/CompilerCore.cs37
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs216
4 files changed, 220 insertions, 222 deletions
diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs
index 19c77368..7a321d29 100644
--- a/src/WixToolset.Core/Common.cs
+++ b/src/WixToolset.Core/Common.cs
@@ -329,21 +329,22 @@ namespace WixToolset.Core
329 /// <returns>The generated identifier.</returns> 329 /// <returns>The generated identifier.</returns>
330 public static string GenerateIdentifier(string prefix, params string[] args) 330 public static string GenerateIdentifier(string prefix, params string[] args)
331 { 331 {
332 string stringData = String.Join("|", args); 332 string base64;
333 byte[] data = Encoding.UTF8.GetBytes(stringData);
334 333
335 // hash the data 334 using (var sha1 = new SHA1CryptoServiceProvider())
336 byte[] hash;
337 using (SHA1 sha1 = new SHA1CryptoServiceProvider())
338 { 335 {
339 hash = sha1.ComputeHash(data); 336 var combined = String.Join("|", args);
337 var data = Encoding.UTF8.GetBytes(combined);
338 var hash = sha1.ComputeHash(data);
339 base64 = Convert.ToBase64String(hash);
340 } 340 }
341 341
342 // Build up the identifier. 342 var identifier = new StringBuilder(32);
343 StringBuilder identifier = new StringBuilder(35, 35);
344 identifier.Append(prefix); 343 identifier.Append(prefix);
345 identifier.Append(Convert.ToBase64String(hash).TrimEnd('=')); 344 identifier.Append(base64);
346 identifier.Replace('+', '.').Replace('/', '_'); 345 identifier.Length -= 1; // removes the trailing '=' from base64
346 identifier.Replace('+', '.');
347 identifier.Replace('/', '_');
347 348
348 return identifier.ToString(); 349 return identifier.ToString();
349 } 350 }
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs
index 3fa06f9c..c504e96f 100644
--- a/src/WixToolset.Core/Compiler.cs
+++ b/src/WixToolset.Core/Compiler.cs
@@ -9,6 +9,7 @@ namespace WixToolset.Core
9 using System.Globalization; 9 using System.Globalization;
10 using System.IO; 10 using System.IO;
11 using System.Linq; 11 using System.Linq;
12 using System.Text;
12 using System.Xml.Linq; 13 using System.Xml.Linq;
13 using WixToolset.Data; 14 using WixToolset.Data;
14 using WixToolset.Data.Symbols; 15 using WixToolset.Data.Symbols;
@@ -25,8 +26,10 @@ namespace WixToolset.Core
25 private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB 26 private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
26 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) 27 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
27 28
28 private const string DefaultComponentIdPlaceholderPrefix = "WixComponentIdPlaceholder"; 29 private const char ComponentIdPlaceholderStart = (char)167;
29 private const string DefaultComponentIdPlaceholderWixVariablePrefix = "!(wix."; 30 private const char ComponentIdPlaceholderEnd = (char)167;
31 private Dictionary<string, string> componentIdPlaceholders;
32
30 // If these are true you know you are building a module or product 33 // If these are true you know you are building a module or product
31 // but if they are false you cannot not be sure they will not end 34 // but if they are false you cannot not be sure they will not end
32 // up a product or module. Use these flags carefully. 35 // up a product or module. Use these flags carefully.
@@ -36,9 +39,6 @@ namespace WixToolset.Core
36 private string activeName; 39 private string activeName;
37 private string activeLanguage; 40 private string activeLanguage;
38 41
39 // TODO: Implement this differently to not require the VariableResolver.
40 private VariableResolver componentIdPlaceholdersResolver;
41
42 /// <summary> 42 /// <summary>
43 /// Type of RadioButton element in a group. 43 /// Type of RadioButton element in a group.
44 /// </summary> 44 /// </summary>
@@ -129,7 +129,7 @@ namespace WixToolset.Core
129 129
130 this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); 130 this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace);
131 this.Core.ShowPedanticMessages = this.ShowPedanticMessages; 131 this.Core.ShowPedanticMessages = this.ShowPedanticMessages;
132 this.componentIdPlaceholdersResolver = new VariableResolver(this.ServiceProvider); 132 this.componentIdPlaceholders = new Dictionary<string, string>();
133 133
134 // parse the document 134 // parse the document
135 var source = this.Context.Source; 135 var source = this.Context.Source;
@@ -247,7 +247,7 @@ namespace WixToolset.Core
247 247
248 private void ResolveComponentIdPlaceholders(Intermediate target) 248 private void ResolveComponentIdPlaceholders(Intermediate target)
249 { 249 {
250 if (0 < this.componentIdPlaceholdersResolver.VariableCount) 250 if (0 < this.componentIdPlaceholders.Count)
251 { 251 {
252 foreach (var section in target.Sections) 252 foreach (var section in target.Sections)
253 { 253 {
@@ -255,15 +255,40 @@ namespace WixToolset.Core
255 { 255 {
256 foreach (var field in symbol.Fields) 256 foreach (var field in symbol.Fields)
257 { 257 {
258 if (field?.Type == IntermediateFieldType.String) 258 if (field != null && field.Type == IntermediateFieldType.String)
259 { 259 {
260 var data = field.AsString(); 260 var data = field.AsString();
261 if (!String.IsNullOrEmpty(data)) 261 if (!String.IsNullOrEmpty(data))
262 { 262 {
263 var resolved = this.componentIdPlaceholdersResolver.ResolveVariables(symbol.SourceLineNumbers, data, errorOnUnknown: false); 263 var changed = false;
264 if (resolved.UpdatedValue) 264 var start = data.IndexOf(ComponentIdPlaceholderStart);
265 while (start != -1)
266 {
267 var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1);
268 if (end == -1)
269 {
270 break;
271 }
272
273 var placeholderId = data.Substring(start, end - start + 1);
274 if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value))
275 {
276 var sb = new StringBuilder(data);
277 sb.Remove(start, end - start + 1);
278 sb.Insert(start, value);
279
280 data = sb.ToString();
281 changed = true;
282
283 end = start + value.Length;
284 }
285
286 start = data.IndexOf(ComponentIdPlaceholderStart, end);
287 }
288
289 if (changed)
265 { 290 {
266 field.Set(resolved.Value); 291 field.Overwrite(data);
267 } 292 }
268 } 293 }
269 } 294 }
@@ -2096,9 +2121,8 @@ namespace WixToolset.Core
2096 var encounteredODBCDataSource = false; 2121 var encounteredODBCDataSource = false;
2097 var files = 0; 2122 var files = 0;
2098 var guid = "*"; 2123 var guid = "*";
2099 var componentIdPlaceholder = Compiler.DefaultComponentIdPlaceholderPrefix + this.componentIdPlaceholdersResolver.VariableCount; // placeholder id for defaulting Component/@Id to keypath id. 2124 Identifier id = null;
2100 var componentIdPlaceholderWixVariable = Compiler.DefaultComponentIdPlaceholderWixVariablePrefix + componentIdPlaceholder + ")"; 2125 string componentIdPlaceholder = null;
2101 var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable);
2102 var keyFound = false; 2126 var keyFound = false;
2103 string keyPath = null; 2127 string keyPath = null;
2104 2128
@@ -2253,6 +2277,13 @@ namespace WixToolset.Core
2253 win64 = true; 2277 win64 = true;
2254 } 2278 }
2255 2279
2280 if (id == null)
2281 {
2282 // Placeholder id for defaulting Component/@Id to keypath id.
2283 componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd);
2284 id = new Identifier(AccessModifier.Private, componentIdPlaceholder);
2285 }
2286
2256 if (null == directoryId) 2287 if (null == directoryId)
2257 { 2288 {
2258 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); 2289 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory"));
@@ -2466,14 +2497,14 @@ namespace WixToolset.Core
2466 this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); 2497 this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2467 } 2498 }
2468 2499
2469 // if there isn't an @Id attribute value, replace the placeholder with the id of the keypath. 2500 // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath.
2470 // either an explicit KeyPath="yes" attribute must be specified or requirements for 2501 // either an explicit KeyPath="yes" attribute must be specified or requirements for
2471 // generatable guid must be met. 2502 // generatable guid must be met.
2472 if (componentIdPlaceholderWixVariable == id.Id) 2503 if (componentIdPlaceholder == id.Id)
2473 { 2504 {
2474 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath)) 2505 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2475 { 2506 {
2476 this.componentIdPlaceholdersResolver.AddVariable(sourceLineNumbers, componentIdPlaceholder, keyPath, false); 2507 this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath);
2477 2508
2478 id = new Identifier(AccessModifier.Private, keyPath); 2509 id = new Identifier(AccessModifier.Private, keyPath);
2479 } 2510 }
@@ -2483,12 +2514,6 @@ namespace WixToolset.Core
2483 } 2514 }
2484 } 2515 }
2485 2516
2486 // If an id was not determined by now, we have to error.
2487 if (null == id)
2488 {
2489 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2490 }
2491
2492 // finally add the Component table row 2517 // finally add the Component table row
2493 if (!this.Core.EncounteredError) 2518 if (!this.Core.EncounteredError)
2494 { 2519 {
@@ -4120,9 +4145,9 @@ namespace WixToolset.Core
4120 Identifier id = null; 4145 Identifier id = null;
4121 string componentGuidGenerationSeed = null; 4146 string componentGuidGenerationSeed = null;
4122 var fileSourceAttribSet = false; 4147 var fileSourceAttribSet = false;
4123 var nameHasValue = false; 4148 XAttribute nameAttribute = null;
4124 var name = "."; // default to parent directory. 4149 var name = "."; // default to parent directory.
4125 string[] inlineSyntax = null; 4150 string inlineSyntax = null;
4126 string shortName = null; 4151 string shortName = null;
4127 string sourceName = null; 4152 string sourceName = null;
4128 string shortSourceName = null; 4153 string shortSourceName = null;
@@ -4148,15 +4173,8 @@ namespace WixToolset.Core
4148 fileSourceAttribSet = true; 4173 fileSourceAttribSet = true;
4149 break; 4174 break;
4150 case "Name": 4175 case "Name":
4151 nameHasValue = true; 4176 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4152 if (attrib.Value == ".") 4177 nameAttribute = attrib;
4153 {
4154 name = attrib.Value;
4155 }
4156 else
4157 {
4158 inlineSyntax = this.Core.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attrib);
4159 }
4160 break; 4178 break;
4161 case "ShortName": 4179 case "ShortName":
4162 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); 4180 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
@@ -4185,37 +4203,7 @@ namespace WixToolset.Core
4185 } 4203 }
4186 } 4204 }
4187 4205
4188 // Create the directory rows for the inline. 4206 if (nameAttribute == null)
4189 if (null != inlineSyntax)
4190 {
4191 // Special case the single entry in the inline syntax since it is the most common case
4192 // and needs no extra processing. It's just the name of the directory.
4193 if (1 == inlineSyntax.Length)
4194 {
4195 name = inlineSyntax[0];
4196 }
4197 else
4198 {
4199 var pathStartsAt = 0;
4200 if (inlineSyntax[0].EndsWith(":"))
4201 {
4202 parentId = inlineSyntax[0].TrimEnd(':');
4203 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, parentId);
4204
4205 pathStartsAt = 1;
4206 }
4207
4208 for (var i = pathStartsAt; i < inlineSyntax.Length - 1; ++i)
4209 {
4210 var inlineId = this.Core.CreateDirectoryRow(sourceLineNumbers, null, parentId, inlineSyntax[i]);
4211 parentId = inlineId.Id;
4212 }
4213
4214 name = inlineSyntax[inlineSyntax.Length - 1];
4215 }
4216 }
4217
4218 if (!nameHasValue)
4219 { 4207 {
4220 if (!String.IsNullOrEmpty(shortName)) 4208 if (!String.IsNullOrEmpty(shortName))
4221 { 4209 {
@@ -4264,39 +4252,55 @@ namespace WixToolset.Core
4264 } 4252 }
4265 } 4253 }
4266 4254
4267 // Update the file source path appropriately. 4255 // Create the directory rows for the inline.
4268 if (fileSourceAttribSet) 4256 if (nameAttribute != null)
4269 { 4257 {
4270 if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) 4258 var lastSlash = name.LastIndexOf('\\');
4259 if (lastSlash > 0)
4271 { 4260 {
4272 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); 4261 inlineSyntax = name;
4262 name = inlineSyntax.Substring(lastSlash + 1);
4263
4264 parentId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, nameAttribute, parentId, inlineSyntax.Substring(0, lastSlash));
4265
4266 if (!this.Core.IsValidLongFilename(name, false, false))
4267 {
4268 this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, nameAttribute.Name.LocalName, nameAttribute.Value, name));
4269 }
4273 } 4270 }
4274 } 4271 }
4275 else // add the appropriate part of this directory element to the file source. 4272
4273 if (null == id)
4276 { 4274 {
4277 string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; 4275 id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
4278 4276
4279 if (!String.IsNullOrEmpty(append)) 4277 if (!String.IsNullOrEmpty(inlineSyntax))
4280 { 4278 {
4281 fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); 4279 this.Core.AddInlineDirectoryId(inlineSyntax, id.Id);
4282 } 4280 }
4283 } 4281 }
4284 4282
4285 if (null == id) 4283 if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null))
4286 { 4284 {
4287 id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); 4285 this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name));
4288 } 4286 }
4289 4287
4290 // Calculate the DefaultDir for the directory row. 4288 // Update the file source path appropriately.
4291 var defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name); 4289 if (fileSourceAttribSet)
4292 if (!String.IsNullOrEmpty(sourceName))
4293 { 4290 {
4294 defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName)); 4291 if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4292 {
4293 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4294 }
4295 } 4295 }
4296 4296 else // add the appropriate part of this directory element to the file source.
4297 if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !"SourceDir".Equals(defaultDir, StringComparison.Ordinal))
4298 { 4297 {
4299 this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir)); 4298 string append = String.IsNullOrEmpty(sourceName) ? name : sourceName;
4299
4300 if (!String.IsNullOrEmpty(append))
4301 {
4302 fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
4303 }
4300 } 4304 }
4301 4305
4302 foreach (var child in node.Elements()) 4306 foreach (var child in node.Elements())
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs
index d88858ae..e2c1a3d6 100644
--- a/src/WixToolset.Core/CompilerCore.cs
+++ b/src/WixToolset.Core/CompilerCore.cs
@@ -120,7 +120,7 @@ namespace WixToolset.Core
120 private readonly IParseHelper parseHelper; 120 private readonly IParseHelper parseHelper;
121 private readonly Intermediate intermediate; 121 private readonly Intermediate intermediate;
122 private readonly IMessaging messaging; 122 private readonly IMessaging messaging;
123 private HashSet<string> activeSectionInlinedDirectoryIds; 123 private Dictionary<string, string> activeSectionCachedInlinedDirectoryIds;
124 private HashSet<string> activeSectionSimpleReferences; 124 private HashSet<string> activeSectionSimpleReferences;
125 125
126 /// <summary> 126 /// <summary>
@@ -354,12 +354,13 @@ namespace WixToolset.Core
354 /// Creates directories using the inline directory syntax. 354 /// Creates directories using the inline directory syntax.
355 /// </summary> 355 /// </summary>
356 /// <param name="sourceLineNumbers">Source line information.</param> 356 /// <param name="sourceLineNumbers">Source line information.</param>
357 /// <param name="attribute">The attribute to parse.</param> 357 /// <param name="attribute">Attribute containing the inline syntax.</param>
358 /// <param name="parentId">Optional identifier of parent directory.</param> 358 /// <param name="parentId">Optional identifier of parent directory.</param>
359 /// <param name="inlineSyntax">Optional inline syntax to override attribute's value.</param>
359 /// <returns>Identifier of the leaf directory created.</returns> 360 /// <returns>Identifier of the leaf directory created.</returns>
360 public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId) 361 public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax = null)
361 { 362 {
362 return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, parentId, attribute, this.activeSectionInlinedDirectoryIds); 363 return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds);
363 } 364 }
364 365
365 /// <summary> 366 /// <summary>
@@ -1001,6 +1002,16 @@ namespace WixToolset.Core
1001 } 1002 }
1002 1003
1003 /// <summary> 1004 /// <summary>
1005 /// Adds inline directory syntax generated identifier.
1006 /// </summary>
1007 /// <param name="inlineSyntax">Inline directory syntax the identifier was generated.</param>
1008 /// <param name="id">Generated identifier for inline syntax.</param>
1009 internal void AddInlineDirectoryId(string inlineSyntax, string id)
1010 {
1011 this.activeSectionCachedInlinedDirectoryIds.Add(inlineSyntax, id);
1012 }
1013
1014 /// <summary>
1004 /// Creates a new section and makes it the active section in the core. 1015 /// Creates a new section and makes it the active section in the core.
1005 /// </summary> 1016 /// </summary>
1006 /// <param name="id">Unique identifier for the section.</param> 1017 /// <param name="id">Unique identifier for the section.</param>
@@ -1011,7 +1022,7 @@ namespace WixToolset.Core
1011 { 1022 {
1012 this.ActiveSection = this.CreateSection(id, type, codepage, compilationId); 1023 this.ActiveSection = this.CreateSection(id, type, codepage, compilationId);
1013 1024
1014 this.activeSectionInlinedDirectoryIds = new HashSet<string>(); 1025 this.activeSectionCachedInlinedDirectoryIds = new Dictionary<string, string>();
1015 this.activeSectionSimpleReferences = new HashSet<string>(); 1026 this.activeSectionSimpleReferences = new HashSet<string>();
1016 1027
1017 return this.ActiveSection; 1028 return this.ActiveSection;
@@ -1060,9 +1071,9 @@ namespace WixToolset.Core
1060 /// <param name="sourceName">Optional source name for the directory.</param> 1071 /// <param name="sourceName">Optional source name for the directory.</param>
1061 /// <param name="shortSourceName">Optional short source name for the directory.</param> 1072 /// <param name="shortSourceName">Optional short source name for the directory.</param>
1062 /// <returns>Identifier for the newly created row.</returns> 1073 /// <returns>Identifier for the newly created row.</returns>
1063 internal Identifier CreateDirectoryRow(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) 1074 internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
1064 { 1075 {
1065 return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); 1076 return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName);
1066 } 1077 }
1067 1078
1068 public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) 1079 public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after)
@@ -1070,18 +1081,6 @@ namespace WixToolset.Core
1070 this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); 1081 this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null);
1071 } 1082 }
1072 1083
1073 /// <summary>
1074 /// Gets the attribute value as inline directory syntax.
1075 /// </summary>
1076 /// <param name="sourceLineNumbers">Source line information.</param>
1077 /// <param name="attribute">Attribute containing the value to get.</param>
1078 /// <param name="resultUsedToCreateReference">Flag indicates whether the inline directory syntax should be processed to create a directory row or to create a directory reference.</param>
1079 /// <returns>Inline directory syntax split into array of strings or null if the syntax did not parse.</returns>
1080 internal string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
1081 {
1082 return this.parseHelper.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, resultUsedToCreateReference);
1083 }
1084
1085 internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) 1084 internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false)
1086 { 1085 {
1087 return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); 1086 return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable);
diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
index d9087ce3..e75f5fe5 100644
--- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
+++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
@@ -19,6 +19,8 @@ namespace WixToolset.Core.ExtensibilityServices
19 19
20 internal class ParseHelper : IParseHelper 20 internal class ParseHelper : IParseHelper
21 { 21 {
22 private static readonly char[] InlineDirectorySeparators = new char[] { ':', '\\', '/' };
23
22 public ParseHelper(IWixToolsetServiceProvider serviceProvider) 24 public ParseHelper(IWixToolsetServiceProvider serviceProvider)
23 { 25 {
24 this.ServiceProvider = serviceProvider; 26 this.ServiceProvider = serviceProvider;
@@ -53,13 +55,7 @@ namespace WixToolset.Core.ExtensibilityServices
53 this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); 55 this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId);
54 } 56 }
55 57
56 [Obsolete] 58 public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
57 public Identifier CreateDirectoryRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet<string> sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null)
58 {
59 return this.CreateDirectorySymbol(section, sourceLineNumbers, id, parentId, name, sectionInlinedDirectoryIds, shortName, sourceName, shortSourceName);
60 }
61
62 public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet<string> sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null)
63 { 59 {
64 // For anonymous directories, create the identifier. If this identifier already exists in the 60 // For anonymous directories, create the identifier. If this identifier already exists in the
65 // active section, bail so we don't add duplicate anonymous directory symbols (which are legal 61 // active section, bail so we don't add duplicate anonymous directory symbols (which are legal
@@ -67,11 +63,6 @@ namespace WixToolset.Core.ExtensibilityServices
67 if (null == id) 63 if (null == id)
68 { 64 {
69 id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); 65 id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
70
71 if (!sectionInlinedDirectoryIds.Add(id.Id))
72 {
73 return id;
74 }
75 } 66 }
76 67
77 var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) 68 var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
@@ -86,48 +77,30 @@ namespace WixToolset.Core.ExtensibilityServices
86 return symbol.Id; 77 return symbol.Id;
87 } 78 }
88 79
89 public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, string parentId, XAttribute attribute, ISet<string> sectionInlinedDirectoryIds) 80 public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary<string, string> sectionCachedInlinedDirectoryIds)
90 { 81 {
91 string id = null; 82 if (String.IsNullOrEmpty(inlineSyntax))
92 var inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true);
93
94 if (null != inlineSyntax)
95 { 83 {
96 // Special case the single entry in the inline syntax since it is the most common case 84 inlineSyntax = attribute.Value;
97 // and needs no extra processing. It's just a reference to an existing directory. 85 }
98 if (1 == inlineSyntax.Length)
99 {
100 id = inlineSyntax[0];
101 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id);
102 }
103 else // start creating symbols for the entries in the inline syntax
104 {
105 id = parentId;
106
107 var pathStartsAt = 0;
108 if (inlineSyntax[0].EndsWith(":", StringComparison.Ordinal))
109 {
110 // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide?
111 //if (null != parentId)
112 //{
113 // this.core.Write(WixErrors.Xxx(sourceLineNumbers));
114 //}
115
116 id = inlineSyntax[0].TrimEnd(':');
117 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id);
118 86
119 pathStartsAt = 1; 87 // If no separator is found, the string is a simple reference.
120 } 88 var separatorFound = inlineSyntax.IndexOfAny(InlineDirectorySeparators);
89 if (separatorFound == -1)
90 {
91 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, inlineSyntax);
92 return inlineSyntax;
93 }
121 94
122 for (var i = pathStartsAt; i < inlineSyntax.Length; ++i) 95 // If a parent id was provided and the inline syntax does not start with a directory reference, prepend the parent id.
123 { 96 if (!String.IsNullOrEmpty(parentId) && inlineSyntax[separatorFound] != ':')
124 var inlineId = this.CreateDirectorySymbol(section, sourceLineNumbers, null, id, inlineSyntax[i], sectionInlinedDirectoryIds); 97 {
125 id = inlineId.Id; 98 inlineSyntax = String.Concat(parentId, ":", inlineSyntax);
126 }
127 }
128 } 99 }
129 100
130 return id; 101 inlineSyntax = inlineSyntax.TrimEnd('\\', '/');
102
103 return this.ParseInlineSyntax(section, sourceLineNumbers, attribute, inlineSyntax, sectionCachedInlinedDirectoryIds);
131 } 104 }
132 105
133 public string CreateGuid(Guid namespaceGuid, string value) 106 public string CreateGuid(Guid namespaceGuid, string value)
@@ -544,69 +517,6 @@ namespace WixToolset.Core.ExtensibilityServices
544 return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); 517 return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute);
545 } 518 }
546 519
547 public string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
548 {
549 string[] result = null;
550 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
551
552 if (!String.IsNullOrEmpty(value))
553 {
554 var pathStartsAt = 0;
555 result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
556 if (result[0].EndsWith(":", StringComparison.Ordinal))
557 {
558 var id = result[0].TrimEnd(':');
559 if (1 == result.Length)
560 {
561 this.Messaging.Write(ErrorMessages.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
562 return null;
563 }
564 else if (!this.IsValidIdentifier(id))
565 {
566 this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
567 return null;
568 }
569
570 pathStartsAt = 1;
571 }
572 else if (resultUsedToCreateReference && 1 == result.Length)
573 {
574 if (value.EndsWith("\\", StringComparison.Ordinal))
575 {
576 if (!this.IsValidLongFilename(result[0], false, false))
577 {
578 this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
579 return null;
580 }
581 }
582 else if (!this.IsValidIdentifier(result[0]))
583 {
584 this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
585 return null;
586 }
587
588 return result; // return early to avoid additional checks below.
589 }
590
591 // Check each part of the relative path to ensure that it is a valid directory name.
592 for (var i = pathStartsAt; i < result.Length; ++i)
593 {
594 if (!this.IsValidLongFilename(result[i], false, false))
595 {
596 this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i]));
597 return null;
598 }
599 }
600
601 if (1 < result.Length && !value.EndsWith("\\", StringComparison.Ordinal))
602 {
603 this.Messaging.Write(WarningMessages.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
604 }
605 }
606
607 return result;
608 }
609
610 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) 520 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
611 { 521 {
612 return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); 522 return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum);
@@ -1032,6 +942,90 @@ namespace WixToolset.Core.ExtensibilityServices
1032 this.Creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>(); 942 this.Creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
1033 } 943 }
1034 944
945 private string ParseInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string inlineSyntax, IDictionary<string, string> sectionCachedInlinedDirectoryIds)
946 {
947 if (!sectionCachedInlinedDirectoryIds.TryGetValue(inlineSyntax, out var id))
948 {
949 string parentId;
950 int nameIndex;
951
952 var separatorIndex = inlineSyntax.LastIndexOfAny(InlineDirectorySeparators);
953 if (separatorIndex == -1)
954 {
955 nameIndex = 0;
956 parentId = "TARGETDIR";
957 }
958 else if (inlineSyntax[separatorIndex] == '\\' || inlineSyntax[separatorIndex] == '/')
959 {
960 nameIndex = separatorIndex + 1;
961
962 if (separatorIndex == 0)
963 {
964 parentId = "TARGETDIR";
965 }
966 else if (inlineSyntax[separatorIndex - 1] == ':')
967 {
968 parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex - 1);
969 }
970 else
971 {
972 var parentInlineDirectory = inlineSyntax.Substring(0, separatorIndex);
973 parentId = this.ParseInlineSyntax(section, sourceLineNumbers, attribute, parentInlineDirectory.TrimEnd('\\', '/'), sectionCachedInlinedDirectoryIds);
974 }
975 }
976 else
977 {
978 nameIndex = separatorIndex + 1;
979 parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex);
980 }
981
982 if (nameIndex == inlineSyntax.Length)
983 {
984 id = parentId;
985 }
986 else
987 {
988 var name = nameIndex != -1 ? inlineSyntax.Substring(nameIndex) : null;
989
990 if (!this.IsValidLongFilename(name, false, false))
991 {
992 this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, name));
993 return null;
994 }
995
996 var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, null, parentId, name);
997
998 id = identifier.Id;
999 }
1000
1001 sectionCachedInlinedDirectoryIds.Add(inlineSyntax, id);
1002 }
1003
1004 return id;
1005 }
1006
1007 private string ParseParentReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string reference, int colonIndex)
1008 {
1009 if (colonIndex == 0)
1010 {
1011 this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, String.Empty));
1012 return null;
1013 }
1014 else
1015 {
1016 var parentId = reference.Substring(0, colonIndex);
1017
1018 if (!Common.IsIdentifier(parentId))
1019 {
1020 this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, parentId));
1021 return null;
1022 }
1023
1024 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, parentId);
1025 return parentId;
1026 }
1027 }
1028
1035 private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension) 1029 private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension)
1036 { 1030 {
1037 extension = null; 1031 extension = null;