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
{