@joebigelow / wix / commits / f4b7d3f7

Major performance improvements to inline directory syntax parsing

Rob Mensching committed Jul 10, 2020 at 23:41 UTC f4b7d3f7c0a49e70fe5d25a309387786589488a6
4 files changed +220 -222
src/WixToolset.Core/Common.cs
+11 -10
@@ -329,21 +329,22 @@ namespace WixToolset.Core
329 /// <returns>The generated identifier.</returns>
330 public static string GenerateIdentifier(string prefix, params string[] args)
331 {
332 - string stringData = String.Join("|", args);
333 - byte[] data = Encoding.UTF8.GetBytes(stringData);
332 + string base64;
333
335 - // hash the data
336 - byte[] hash;
337 - using (SHA1 sha1 = new SHA1CryptoServiceProvider())
334 + using (var sha1 = new SHA1CryptoServiceProvider())
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 }
341
342 - // Build up the identifier.
343 - StringBuilder identifier = new StringBuilder(35, 35);
342 + var identifier = new StringBuilder(32);
343 identifier.Append(prefix);
345 - identifier.Append(Convert.ToBase64String(hash).TrimEnd('='));
346 - identifier.Replace('+', '.').Replace('/', '_');
344 + identifier.Append(base64);
345 + identifier.Length -= 1; // removes the trailing '=' from base64
346 + identifier.Replace('+', '.');
347 + identifier.Replace('/', '_');
348
349 return identifier.ToString();
350 }
src/WixToolset.Core/Compiler.cs
+86 -82
@@ -9,6 +9,7 @@ namespace WixToolset.Core
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 + using System.Text;
13 using System.Xml.Linq;
14 using WixToolset.Data;
15 using WixToolset.Data.Symbols;
@@ -25,8 +26,10 @@ namespace WixToolset.Core
26 private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
27 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
28
28 - private const string DefaultComponentIdPlaceholderPrefix = "WixComponentIdPlaceholder";
29 - private const string DefaultComponentIdPlaceholderWixVariablePrefix = "!(wix.";
29 + private const char ComponentIdPlaceholderStart = (char)167;
30 + private const char ComponentIdPlaceholderEnd = (char)167;
31 + private Dictionary<string, string> componentIdPlaceholders;
32 +
33 // If these are true you know you are building a module or product
34 // but if they are false you cannot not be sure they will not end
35 // up a product or module. Use these flags carefully.
@@ -36,9 +39,6 @@ namespace WixToolset.Core
39 private string activeName;
40 private string activeLanguage;
41
39 - // TODO: Implement this differently to not require the VariableResolver.
40 - private VariableResolver componentIdPlaceholdersResolver;
41 -
42 /// <summary>
43 /// Type of RadioButton element in a group.
44 /// </summary>
@@ -129,7 +129,7 @@ namespace WixToolset.Core
129
130 this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace);
131 this.Core.ShowPedanticMessages = this.ShowPedanticMessages;
132 - this.componentIdPlaceholdersResolver = new VariableResolver(this.ServiceProvider);
132 + this.componentIdPlaceholders = new Dictionary<string, string>();
133
134 // parse the document
135 var source = this.Context.Source;
@@ -247,7 +247,7 @@ namespace WixToolset.Core
247
248 private void ResolveComponentIdPlaceholders(Intermediate target)
249 {
250 - if (0 < this.componentIdPlaceholdersResolver.VariableCount)
250 + if (0 < this.componentIdPlaceholders.Count)
251 {
252 foreach (var section in target.Sections)
253 {
@@ -255,15 +255,40 @@ namespace WixToolset.Core
255 {
256 foreach (var field in symbol.Fields)
257 {
258 - if (field?.Type == IntermediateFieldType.String)
258 + if (field != null && field.Type == IntermediateFieldType.String)
259 {
260 var data = field.AsString();
261 if (!String.IsNullOrEmpty(data))
262 {
263 - var resolved = this.componentIdPlaceholdersResolver.ResolveVariables(symbol.SourceLineNumbers, data, errorOnUnknown: false);
264 - if (resolved.UpdatedValue)
263 + var changed = false;
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)
290 {
266 - field.Set(resolved.Value);
291 + field.Overwrite(data);
292 }
293 }
294 }
@@ -2096,9 +2121,8 @@ namespace WixToolset.Core
2121 var encounteredODBCDataSource = false;
2122 var files = 0;
2123 var guid = "*";
2099 - var componentIdPlaceholder = Compiler.DefaultComponentIdPlaceholderPrefix + this.componentIdPlaceholdersResolver.VariableCount; // placeholder id for defaulting Component/@Id to keypath id.
2100 - var componentIdPlaceholderWixVariable = Compiler.DefaultComponentIdPlaceholderWixVariablePrefix + componentIdPlaceholder + ")";
2101 - var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable);
2124 + Identifier id = null;
2125 + string componentIdPlaceholder = null;
2126 var keyFound = false;
2127 string keyPath = null;
2128
@@ -2253,6 +2277,13 @@ namespace WixToolset.Core
2277 win64 = true;
2278 }
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 +
2287 if (null == directoryId)
2288 {
2289 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory"));
@@ -2466,14 +2497,14 @@ namespace WixToolset.Core
2497 this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2498 }
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.
2501 // either an explicit KeyPath="yes" attribute must be specified or requirements for
2502 // generatable guid must be met.
2472 - if (componentIdPlaceholderWixVariable == id.Id)
2503 + if (componentIdPlaceholder == id.Id)
2504 {
2505 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2506 {
2476 - this.componentIdPlaceholdersResolver.AddVariable(sourceLineNumbers, componentIdPlaceholder, keyPath, false);
2507 + this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath);
2508
2509 id = new Identifier(AccessModifier.Private, keyPath);
2510 }
@@ -2483,12 +2514,6 @@ namespace WixToolset.Core
2514 }
2515 }
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 -
2517 // finally add the Component table row
2518 if (!this.Core.EncounteredError)
2519 {
@@ -4120,9 +4145,9 @@ namespace WixToolset.Core
4145 Identifier id = null;
4146 string componentGuidGenerationSeed = null;
4147 var fileSourceAttribSet = false;
4123 - var nameHasValue = false;
4148 + XAttribute nameAttribute = null;
4149 var name = "."; // default to parent directory.
4125 - string[] inlineSyntax = null;
4150 + string inlineSyntax = null;
4151 string shortName = null;
4152 string sourceName = null;
4153 string shortSourceName = null;
@@ -4148,15 +4173,8 @@ namespace WixToolset.Core
4173 fileSourceAttribSet = true;
4174 break;
4175 case "Name":
4151 - nameHasValue = true;
4152 - if (attrib.Value == ".")
4153 - {
4154 - name = attrib.Value;
4155 - }
4156 - else
4157 - {
4158 - inlineSyntax = this.Core.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attrib);
4159 - }
4176 + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4177 + nameAttribute = attrib;
4178 break;
4179 case "ShortName":
4180 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
@@ -4185,37 +4203,7 @@ namespace WixToolset.Core
4203 }
4204 }
4205
4188 - // Create the directory rows for the inline.
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)
4206 + if (nameAttribute == null)
4207 {
4208 if (!String.IsNullOrEmpty(shortName))
4209 {
@@ -4264,39 +4252,55 @@ namespace WixToolset.Core
4252 }
4253 }
4254
4267 - // Update the file source path appropriately.
4268 - if (fileSourceAttribSet)
4255 + // Create the directory rows for the inline.
4256 + if (nameAttribute != null)
4257 {
4270 - if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4258 + var lastSlash = name.LastIndexOf('\\');
4259 + if (lastSlash > 0)
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 + }
4270 }
4271 }
4275 - else // add the appropriate part of this directory element to the file source.
4272 +
4273 + if (null == id)
4274 {
4277 - string append = String.IsNullOrEmpty(sourceName) ? name : sourceName;
4275 + id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
4276
4279 - if (!String.IsNullOrEmpty(append))
4277 + if (!String.IsNullOrEmpty(inlineSyntax))
4278 {
4281 - fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
4279 + this.Core.AddInlineDirectoryId(inlineSyntax, id.Id);
4280 }
4281 }
4282
4285 - if (null == id)
4283 + if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null))
4284 {
4287 - id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
4285 + this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name));
4286 }
4287
4290 - // Calculate the DefaultDir for the directory row.
4291 - var defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name);
4292 - if (!String.IsNullOrEmpty(sourceName))
4288 + // Update the file source path appropriately.
4289 + if (fileSourceAttribSet)
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 }
4296 -
4297 - if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !"SourceDir".Equals(defaultDir, StringComparison.Ordinal))
4296 + else // add the appropriate part of this directory element to the file source.
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 + }
4304 }
4305
4306 foreach (var child in node.Elements())
src/WixToolset.Core/CompilerCore.cs
+18 -19
@@ -120,7 +120,7 @@ namespace WixToolset.Core
120 private readonly IParseHelper parseHelper;
121 private readonly Intermediate intermediate;
122 private readonly IMessaging messaging;
123 - private HashSet<string> activeSectionInlinedDirectoryIds;
123 + private Dictionary<string, string> activeSectionCachedInlinedDirectoryIds;
124 private HashSet<string> activeSectionSimpleReferences;
125
126 /// <summary>
@@ -354,12 +354,13 @@ namespace WixToolset.Core
354 /// Creates directories using the inline directory syntax.
355 /// </summary>
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>
359 + /// <param name="inlineSyntax">Optional inline syntax to override attribute's value.</param>
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)
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);
364 }
365
366 /// <summary>
@@ -1000,6 +1001,16 @@ namespace WixToolset.Core
1001 }
1002 }
1003
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>
1015 /// Creates a new section and makes it the active section in the core.
1016 /// </summary>
@@ -1011,7 +1022,7 @@ namespace WixToolset.Core
1022 {
1023 this.ActiveSection = this.CreateSection(id, type, codepage, compilationId);
1024
1014 - this.activeSectionInlinedDirectoryIds = new HashSet<string>();
1025 + this.activeSectionCachedInlinedDirectoryIds = new Dictionary<string, string>();
1026 this.activeSectionSimpleReferences = new HashSet<string>();
1027
1028 return this.ActiveSection;
@@ -1060,9 +1071,9 @@ namespace WixToolset.Core
1071 /// <param name="sourceName">Optional source name for the directory.</param>
1072 /// <param name="shortSourceName">Optional short source name for the directory.</param>
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)
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);
1077 }
1078
1079 public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after)
@@ -1070,18 +1081,6 @@ namespace WixToolset.Core
1081 this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null);
1082 }
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 -
1084 internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false)
1085 {
1086 return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable);
src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
+105 -111
@@ -19,6 +19,8 @@ namespace WixToolset.Core.ExtensibilityServices
19
20 internal class ParseHelper : IParseHelper
21 {
22 + private static readonly char[] InlineDirectorySeparators = new char[] { ':', '\\', '/' };
23 +
24 public ParseHelper(IWixToolsetServiceProvider serviceProvider)
25 {
26 this.ServiceProvider = serviceProvider;
@@ -53,13 +55,7 @@ namespace WixToolset.Core.ExtensibilityServices
55 this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId);
56 }
57
56 - [Obsolete]
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)
58 + public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
59 {
60 // For anonymous directories, create the identifier. If this identifier already exists in the
61 // active section, bail so we don't add duplicate anonymous directory symbols (which are legal
@@ -67,11 +63,6 @@ namespace WixToolset.Core.ExtensibilityServices
63 if (null == id)
64 {
65 id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
70 -
71 - if (!sectionInlinedDirectoryIds.Add(id.Id))
72 - {
73 - return id;
74 - }
66 }
67
68 var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
@@ -86,48 +77,30 @@ namespace WixToolset.Core.ExtensibilityServices
77 return symbol.Id;
78 }
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)
81 {
91 - string id = null;
92 - var inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true);
93 -
94 - if (null != inlineSyntax)
82 + if (String.IsNullOrEmpty(inlineSyntax))
83 {
96 - // Special case the single entry in the inline syntax since it is the most common case
97 - // and needs no extra processing. It's just a reference to an existing directory.
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);
84 + inlineSyntax = attribute.Value;
85 + }
86
119 - pathStartsAt = 1;
120 - }
87 + // If no separator is found, the string is a simple reference.
88 + var separatorFound = inlineSyntax.IndexOfAny(InlineDirectorySeparators);
89 + if (separatorFound == -1)
90 + {
91 + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, inlineSyntax);
92 + return inlineSyntax;
93 + }
94
122 - for (var i = pathStartsAt; i < inlineSyntax.Length; ++i)
123 - {
124 - var inlineId = this.CreateDirectorySymbol(section, sourceLineNumbers, null, id, inlineSyntax[i], sectionInlinedDirectoryIds);
125 - id = inlineId.Id;
126 - }
127 - }
95 + // If a parent id was provided and the inline syntax does not start with a directory reference, prepend the parent id.
96 + if (!String.IsNullOrEmpty(parentId) && inlineSyntax[separatorFound] != ':')
97 + {
98 + inlineSyntax = String.Concat(parentId, ":", inlineSyntax);
99 }
100
130 - return id;
101 + inlineSyntax = inlineSyntax.TrimEnd('\\', '/');
102 +
103 + return this.ParseInlineSyntax(section, sourceLineNumbers, attribute, inlineSyntax, sectionCachedInlinedDirectoryIds);
104 }
105
106 public string CreateGuid(Guid namespaceGuid, string value)
@@ -544,69 +517,6 @@ namespace WixToolset.Core.ExtensibilityServices
517 return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute);
518 }
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 -
520 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
521 {
522 return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum);
@@ -1032,6 +942,90 @@ namespace WixToolset.Core.ExtensibilityServices
942 this.Creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
943 }
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 +
1029 private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension)
1030 {
1031 extension = null;