@joebigelow / wix-1 / commits / 6e7a3274

Fix loading custom table definitions and support tupledef revisions

Loading multiple intermediates with the same custom table definition would crash when trying to redefine the definition. Now definitions contain a revision and the highest tuple definition is used when loading multiple intermediates.

Rob Mensching committed Dec 31, 2018 at 20:45 UTC 6e7a3274a1710a734e5369d0a1703b9c9ac9345b
4 files changed +253 -19
src/WixToolset.Data/Intermediate.cs
+105 -12
@@ -17,7 +17,7 @@ namespace WixToolset.Data
17 public const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/wixobj";
18 private static readonly Version CurrentVersion = new Version("4.0.0.0");
19
20 - private Dictionary<string, Localization> localizationsByCulture;
20 + private readonly Dictionary<string, Localization> localizationsByCulture;
21
22 /// <summary>
23 /// Instantiate a new Intermediate.
@@ -70,7 +70,7 @@ namespace WixToolset.Data
70 {
71 var uri = new Uri(Path.GetFullPath(path));
72 var creator = new SimpleTupleDefinitionCreator();
73 - return Intermediate.Load(stream, uri, creator, suppressVersionCheck);
73 + return Intermediate.LoadIntermediate(stream, uri, creator, suppressVersionCheck);
74 }
75 }
76
@@ -97,13 +97,13 @@ namespace WixToolset.Data
97 /// <returns>Returns the loaded intermediate.</returns>
98 public static Intermediate Load(Assembly assembly, string resourceName, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
99 {
100 - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
100 + using (var resourceStream = assembly.GetManifestResourceStream(resourceName))
101 {
102 var uriBuilder = new UriBuilder(assembly.CodeBase);
103 uriBuilder.Scheme = "embeddedresource";
104 uriBuilder.Fragment = resourceName;
105
106 - return Intermediate.Load(resourceStream, uriBuilder.Uri, creator, suppressVersionCheck);
106 + return Intermediate.LoadIntermediate(resourceStream, uriBuilder.Uri, creator, suppressVersionCheck);
107 }
108 }
109
@@ -120,10 +120,61 @@ namespace WixToolset.Data
120 {
121 var uri = new Uri(Path.GetFullPath(path));
122
123 - return Intermediate.Load(stream, uri, creator, suppressVersionCheck);
123 + return Intermediate.LoadIntermediate(stream, uri, creator, suppressVersionCheck);
124 }
125 }
126
127 + /// <summary>
128 + /// Loads several intermediates from paths on disk using the same definitions.
129 + /// </summary>
130 + /// <param name="intermediateFiles">Paths to intermediate files saved on disk.</param>
131 + /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
132 + /// <returns>Returns the loaded intermediates</returns>
133 + public static IEnumerable<Intermediate> Load(IEnumerable<string> intermediateFiles, bool suppressVersionCheck = false)
134 + {
135 + var creator = new SimpleTupleDefinitionCreator();
136 +
137 + return Intermediate.Load(intermediateFiles, creator);
138 + }
139 +
140 + /// <summary>
141 + /// Loads several intermediates from paths on disk using the same definitions.
142 + /// </summary>
143 + /// <param name="intermediateFiles">Paths to intermediate files saved on disk.</param>
144 + /// <param name="creator">ITupleDefinitionCreator to use when reconstituting the intermediates.</param>
145 + /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
146 + /// <returns>Returns the loaded intermediates</returns>
147 + public static IEnumerable<Intermediate> Load(IEnumerable<string> intermediateFiles, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
148 + {
149 + var jsons = new Queue<JsonWithPath>();
150 + var intermediates = new List<Intermediate>();
151 +
152 + foreach (var path in intermediateFiles)
153 + {
154 + using (var stream = File.OpenRead(path))
155 + {
156 + var uri = new Uri(Path.GetFullPath(path));
157 +
158 + var json = Intermediate.LoadJson(stream, uri, suppressVersionCheck);
159 +
160 + Intermediate.LoadDefinitions(json, creator);
161 +
162 + jsons.Enqueue(new JsonWithPath { Json = json, Path = uri });
163 + }
164 + }
165 +
166 + while (jsons.Count > 0)
167 + {
168 + var jsonWithPath = jsons.Dequeue();
169 +
170 + var intermediate = Intermediate.FinalizeLoad(jsonWithPath.Json, jsonWithPath.Path, creator);
171 +
172 + intermediates.Add(intermediate);
173 + }
174 +
175 + return intermediates;
176 + }
177 +
178 /// <summary>
179 /// Saves an intermediate to a path on disk.
180 /// </summary>
@@ -151,7 +202,7 @@ namespace WixToolset.Data
202
203 jsonObject.Add("sections", sectionsJson);
204
154 - var customDefinitions = GetCustomDefinitionsInSections();
205 + var customDefinitions = this.GetCustomDefinitionsInSections();
206
207 if (customDefinitions.Count > 0)
208 {
@@ -191,10 +242,25 @@ namespace WixToolset.Data
242 /// <param name="creator">ITupleDefinitionCreator to use when reconstituting the intermediate.</param>
243 /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
244 /// <returns>Returns the loaded intermediate.</returns>
194 - private static Intermediate Load(Stream stream, Uri baseUri, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
245 + private static Intermediate LoadIntermediate(Stream stream, Uri baseUri, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
246 {
196 - JsonObject jsonObject;
247 + var json = Intermediate.LoadJson(stream, baseUri, suppressVersionCheck);
248
249 + Intermediate.LoadDefinitions(json, creator);
250 +
251 + return Intermediate.FinalizeLoad(json, baseUri, creator);
252 + }
253 +
254 + /// <summary>
255 + /// Loads json form of intermedaite from stream.
256 + /// </summary>
257 + /// <param name="stream">Stream to intermediate file.</param>
258 + /// <param name="baseUri">Path name of intermediate file.</param>
259 + /// <param name="suppressVersionCheck">Suppress checking for wix.dll version mismatches.</param>
260 + /// <returns>Returns the loaded json.</returns>
261 + private static JsonObject LoadJson(Stream stream, Uri baseUri, bool suppressVersionCheck)
262 + {
263 + JsonObject jsonObject;
264 using (var fs = FileStructure.Read(stream))
265 {
266 if (FileFormat.WixIR != fs.FileFormat)
@@ -216,7 +282,17 @@ namespace WixToolset.Data
282 }
283 }
284
219 - var definitionsJson = jsonObject.GetValueOrDefault<JsonArray>("definitions");
285 + return jsonObject;
286 + }
287 +
288 + /// <summary>
289 + /// Loads custom definitions in intermediate json into the creator.
290 + /// </summary>
291 + /// <param name="json">Json version of intermediate.</param>
292 + /// <param name="creator">ITupleDefinitionCreator to use when reconstituting the intermediate.</param>
293 + private static void LoadDefinitions(JsonObject json, ITupleDefinitionCreator creator)
294 + {
295 + var definitionsJson = json.GetValueOrDefault<JsonArray>("definitions");
296
297 if (definitionsJson != null)
298 {
@@ -226,12 +302,22 @@ namespace WixToolset.Data
302 creator.AddCustomTupleDefinition(definition);
303 }
304 }
305 + }
306
230 - var id = jsonObject.GetValueOrDefault<string>("id");
307 + /// <summary>
308 + /// Loads the sections and localization for the intermediate.
309 + /// </summary>
310 + /// <param name="json">Json version of intermediate.</param>
311 + /// <param name="baseUri">Path to the intermediate.</param>
312 + /// <param name="creator">ITupleDefinitionCreator to use when reconstituting the intermediate.</param>
313 + /// <returns>The finalized intermediate.</returns>
314 + private static Intermediate FinalizeLoad(JsonObject json, Uri baseUri, ITupleDefinitionCreator creator)
315 + {
316 + var id = json.GetValueOrDefault<string>("id");
317
318 var sections = new List<IntermediateSection>();
319
234 - var sectionsJson = jsonObject.GetValueOrDefault<JsonArray>("sections");
320 + var sectionsJson = json.GetValueOrDefault<JsonArray>("sections");
321 foreach (JsonObject sectionJson in sectionsJson)
322 {
323 var section = IntermediateSection.Deserialize(creator, baseUri, sectionJson);
@@ -240,7 +326,7 @@ namespace WixToolset.Data
326
327 var localizations = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
328
243 - var localizationsJson = jsonObject.GetValueOrDefault<JsonArray>("localizations") ?? new JsonArray();
329 + var localizationsJson = json.GetValueOrDefault<JsonArray>("localizations") ?? new JsonArray();
330 foreach (JsonObject localizationJson in localizationsJson)
331 {
332 var localization = Localization.Deserialize(localizationJson);
@@ -409,5 +495,12 @@ namespace WixToolset.Data
495
496 return customDefinitions;
497 }
498 +
499 + private struct JsonWithPath
500 + {
501 + public JsonObject Json { get; set; }
502 +
503 + public Uri Path { get; set; }
504 + }
505 }
506 }
src/WixToolset.Data/IntermediateTupleDefinition.cs
+19 -5
@@ -1,4 +1,4 @@
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.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Data
4 {
@@ -8,19 +8,25 @@ namespace WixToolset.Data
8 public class IntermediateTupleDefinition
9 {
10 public IntermediateTupleDefinition(string name, IntermediateFieldDefinition[] fieldDefinitions, Type strongTupleType)
11 - : this(TupleDefinitionType.MustBeFromAnExtension, name, fieldDefinitions, strongTupleType)
11 + : this(TupleDefinitionType.MustBeFromAnExtension, name, 0, fieldDefinitions, strongTupleType)
12 + {
13 + }
14 +
15 + public IntermediateTupleDefinition(string name, int revision, IntermediateFieldDefinition[] fieldDefinitions, Type strongTupleType)
16 + : this(TupleDefinitionType.MustBeFromAnExtension, name, revision, fieldDefinitions, strongTupleType)
17 {
18 }
19
20 internal IntermediateTupleDefinition(TupleDefinitionType type, IntermediateFieldDefinition[] fieldDefinitions, Type strongTupleType)
16 - : this(type, type.ToString(), fieldDefinitions, strongTupleType)
21 + : this(type, type.ToString(), 0, fieldDefinitions, strongTupleType)
22 {
23 }
24
20 - private IntermediateTupleDefinition(TupleDefinitionType type, string name, IntermediateFieldDefinition[] fieldDefinitions, Type strongTupleType)
25 + private IntermediateTupleDefinition(TupleDefinitionType type, string name, int revision, IntermediateFieldDefinition[] fieldDefinitions, Type strongTupleType)
26 {
27 this.Type = type;
28 this.Name = name;
29 + this.Revision = revision;
30 this.FieldDefinitions = fieldDefinitions;
31 this.StrongTupleType = strongTupleType ?? typeof(IntermediateTuple);
32 #if DEBUG
@@ -28,6 +34,8 @@ namespace WixToolset.Data
34 #endif
35 }
36
37 + public int Revision { get; }
38 +
39 public TupleDefinitionType Type { get; }
40
41 public string Name { get; }
@@ -48,6 +56,7 @@ namespace WixToolset.Data
56 internal static IntermediateTupleDefinition Deserialize(JsonObject jsonObject)
57 {
58 var name = jsonObject.GetValueOrDefault<string>("name");
59 + var revision = jsonObject.GetValueOrDefault("rev", 0);
60 var definitionsJson = jsonObject.GetValueOrDefault<JsonArray>("fields");
61
62 var fieldDefinitions = new IntermediateFieldDefinition[definitionsJson.Count];
@@ -60,7 +69,7 @@ namespace WixToolset.Data
69 fieldDefinitions[i] = new IntermediateFieldDefinition(fieldName, fieldType);
70 }
71
63 - return new IntermediateTupleDefinition(name, fieldDefinitions, null);
72 + return new IntermediateTupleDefinition(name, revision, fieldDefinitions, null);
73 }
74
75 internal JsonObject Serialize()
@@ -70,6 +79,11 @@ namespace WixToolset.Data
79 { "name", this.Name }
80 };
81
82 + if (this.Revision > 0)
83 + {
84 + jsonObject.Add("rev", this.Revision);
85 + }
86 +
87 var fieldsJson = new JsonArray(this.FieldDefinitions.Length);
88
89 foreach (var fieldDefinition in this.FieldDefinitions)
src/WixToolset.Data/SimpleTupleDefinitionCreator.cs
+5 -2
@@ -1,4 +1,4 @@
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.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Data
4 {
@@ -10,7 +10,10 @@ namespace WixToolset.Data
10
11 public void AddCustomTupleDefinition(IntermediateTupleDefinition definition)
12 {
13 - this.CustomDefinitionByName.Add(definition.Name, definition);
13 + if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision)
14 + {
15 + this.CustomDefinitionByName[definition.Name] = definition;
16 + }
17 }
18
19 public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition)
src/test/WixToolsetTest.Data/SerializeFixture.cs
+124
@@ -41,6 +41,130 @@ namespace WixToolsetTest.Data
41 Assert.Equal(2, tuple.Attributes);
42 }
43
44 + [Fact]
45 + public void CanSaveAndLoadIntermediateWithCustomDefinitions()
46 + {
47 + var sln = new SourceLineNumber("test.wxs", 1);
48 +
49 + var section = new IntermediateSection("test", SectionType.Product, 65001);
50 +
51 + var fieldDefs = new[]
52 + {
53 + new IntermediateFieldDefinition("A", IntermediateFieldType.String),
54 + new IntermediateFieldDefinition("B", IntermediateFieldType.Number),
55 + new IntermediateFieldDefinition("C", IntermediateFieldType.Bool),
56 + };
57 +
58 + var tupleDef = new IntermediateTupleDefinition("CustomDef", fieldDefs, null);
59 +
60 + var tuple = tupleDef.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT"));
61 + tuple.Set(0, "foo");
62 + tuple.Set(1, 2);
63 + tuple.Set(2, true);
64 +
65 + section.Tuples.Add(tuple);
66 +
67 + var intermediate = new Intermediate("TestIntermediate", new[] { section }, null, null);
68 +
69 + var path = Path.GetTempFileName();
70 + try
71 + {
72 + intermediate.Save(path);
73 +
74 + var loaded = Intermediate.Load(path);
75 + var loadedSection = loaded.Sections.Single();
76 + var loadedTuple = loadedSection.Tuples.Single();
77 +
78 + Assert.Equal("foo", loadedTuple.AsString(0));
79 + Assert.Equal(2, loadedTuple[1].AsNumber());
80 + Assert.True(loadedTuple[2].AsBool());
81 + }
82 + finally
83 + {
84 + File.Delete(path);
85 + }
86 + }
87 +
88 + [Fact]
89 + public void CanSaveAndLoadMultipleIntermediateWithCustomDefinitions()
90 + {
91 + var sln = new SourceLineNumber("test.wxs", 1);
92 +
93 + // Intermediate #1
94 + var fieldDefs = new[]
95 + {
96 + new IntermediateFieldDefinition("A", IntermediateFieldType.String),
97 + new IntermediateFieldDefinition("B", IntermediateFieldType.Number),
98 + new IntermediateFieldDefinition("C", IntermediateFieldType.Bool),
99 + };
100 +
101 + var tupleDef = new IntermediateTupleDefinition("CustomDef", fieldDefs, null);
102 +
103 + var tuple = tupleDef.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT"));
104 + tuple.Set(0, "foo");
105 + tuple.Set(1, 2);
106 + tuple.Set(2, true);
107 +
108 + var section = new IntermediateSection("test", SectionType.Product, 65001);
109 + section.Tuples.Add(tuple);
110 +
111 + var intermediate1 = new Intermediate("TestIntermediate", new[] { section }, null, null);
112 +
113 + // Intermediate #2
114 + var fieldDefs2 = new[]
115 + {
116 + new IntermediateFieldDefinition("A", IntermediateFieldType.String),
117 + new IntermediateFieldDefinition("B", IntermediateFieldType.Number),
118 + new IntermediateFieldDefinition("C", IntermediateFieldType.Bool),
119 + new IntermediateFieldDefinition("D", IntermediateFieldType.String),
120 + };
121 +
122 + var tupleDef2 = new IntermediateTupleDefinition("CustomDef", 1, fieldDefs2, null);
123 +
124 + var tuple2 = tupleDef2.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT2"));
125 + tuple2.Set(0, "bar");
126 + tuple2.Set(1, 3);
127 + tuple2.Set(2, false);
128 + tuple2.Set(3, "baz");
129 +
130 + var section2 = new IntermediateSection("test2", SectionType.Fragment, 65001);
131 + section2.Tuples.Add(tuple2);
132 +
133 + var intermediate2 = new Intermediate("TestIntermediate2", new[] { section2 }, null, null);
134 +
135 + // Save
136 + var path1 = Path.GetTempFileName();
137 + var path2 = Path.GetTempFileName();
138 + try
139 + {
140 + intermediate1.Save(path1);
141 + intermediate2.Save(path2);
142 +
143 + var loaded = Intermediate.Load(new[] { path1, path2 });
144 +
145 + var loaded1 = loaded.First();
146 + var loaded2 = loaded.Skip(1).Single();
147 +
148 + var loadedTuple1 = loaded1.Sections.Single().Tuples.Single();
149 + var loadedTuple2 = loaded2.Sections.Single().Tuples.Single();
150 +
151 + Assert.Equal("foo", loadedTuple1.AsString(0));
152 + Assert.Equal(2, loadedTuple1[1].AsNumber());
153 + Assert.True(loadedTuple1[2].AsBool());
154 + Assert.Null(loadedTuple1.AsString(3));
155 +
156 + Assert.Equal("bar", loadedTuple2.AsString(0));
157 + Assert.Equal(3, loadedTuple2[1].AsNumber());
158 + Assert.False(loadedTuple2[2].AsBool());
159 + Assert.Equal("baz", loadedTuple2.AsString(3));
160 + }
161 + finally
162 + {
163 + File.Delete(path2);
164 + File.Delete(path1);
165 + }
166 + }
167 +
168 [Fact]
169 public void CanSaveAndLoadIntermediateWithLocalization()
170 {