From 53e877183abe0dbbb623c39380101bc369e9f265 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 2 Dec 2017 00:44:45 -0800 Subject: Support tuples from extensions and make SourcePath a path instead of string --- src/Directory.Build.props | 4 +- src/WixToolset.Data/DictionaryExtensions.cs | 14 ++ src/WixToolset.Data/ITupleDefinitionCreator.cs | 2 + src/WixToolset.Data/Intermediate.cs | 170 +++++++++++++++------ src/WixToolset.Data/IntermediateFieldExtensions.cs | 72 +++++++-- .../IntermediateFieldValueExtensions.cs | 76 ++++++++- src/WixToolset.Data/IntermediateSection.cs | 3 +- src/WixToolset.Data/IntermediateTuple.cs | 14 +- src/WixToolset.Data/IntermediateTupleDefinition.cs | 50 +++++- src/WixToolset.Data/IntermediateTupleExtensions.cs | 2 +- .../SimpleTupleDefinitionCreator.cs | 17 ++- src/WixToolset.Data/Tuples/WixFileTuple.cs | 4 +- 12 files changed, 352 insertions(+), 76 deletions(-) create mode 100644 src/WixToolset.Data/DictionaryExtensions.cs (limited to 'src') diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 25cb6d36..7cd6767f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,11 +5,13 @@ Debug $(MSBuildThisFileDirectory)..\build\obj\$(MSBuildProjectName)\ - $(MSBuildThisFileDirectory)..\build\$(Configuration)\ + $(MSBuildThisFileDirectory)..\build\$(Configuration)\ + $(BaseOutputPath) WiX Toolset Team WiX Toolset Copyright (c) .NET Foundation and contributors. All rights reserved. + WiX Toolset diff --git a/src/WixToolset.Data/DictionaryExtensions.cs b/src/WixToolset.Data/DictionaryExtensions.cs new file mode 100644 index 00000000..cb6647ff --- /dev/null +++ b/src/WixToolset.Data/DictionaryExtensions.cs @@ -0,0 +1,14 @@ +// 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. + +namespace WixToolset.Data +{ + using System.Collections.Generic; + + internal static class DictionaryExtensions + { + public static V GetValueOrDefault(this IDictionary dictionary, K key, V defaultValue = default(V)) + { + return dictionary.TryGetValue(key, out var value) ? value : defaultValue; + } + } +} diff --git a/src/WixToolset.Data/ITupleDefinitionCreator.cs b/src/WixToolset.Data/ITupleDefinitionCreator.cs index 63477314..7d818be1 100644 --- a/src/WixToolset.Data/ITupleDefinitionCreator.cs +++ b/src/WixToolset.Data/ITupleDefinitionCreator.cs @@ -4,6 +4,8 @@ namespace WixToolset.Data { public interface ITupleDefinitionCreator { + void AddCustomTupleDefinition(IntermediateTupleDefinition definition); + bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition); } } diff --git a/src/WixToolset.Data/Intermediate.cs b/src/WixToolset.Data/Intermediate.cs index b03492ce..4d4e17cc 100644 --- a/src/WixToolset.Data/Intermediate.cs +++ b/src/WixToolset.Data/Intermediate.cs @@ -4,6 +4,7 @@ namespace WixToolset.Data { using System; using System.Collections.Generic; + using System.Linq; using System.IO; using SimpleJson; @@ -95,9 +96,24 @@ namespace WixToolset.Data /// Suppress checking for wix.dll version mismatches. /// Returns the loaded intermediate. public static Intermediate Load(string path, bool suppressVersionCheck = false) + { + using (var stream = File.OpenRead(path)) + { + var creator = new SimpleTupleDefinitionCreator(); + return Intermediate.Load(stream, path, creator, suppressVersionCheck); + } + } + + /// + /// Loads an intermediate from a stream. + /// + /// Stream to intermediate file. + /// Suppress checking for wix.dll version mismatches. + /// Returns the loaded intermediate. + public static Intermediate Load(Stream stream, bool suppressVersionCheck = false) { var creator = new SimpleTupleDefinitionCreator(); - return Intermediate.Load(path, creator, suppressVersionCheck); + return Intermediate.Load(stream, creator, suppressVersionCheck); } /// @@ -109,51 +125,22 @@ namespace WixToolset.Data /// Returns the loaded intermediate. public static Intermediate Load(string path, ITupleDefinitionCreator creator, bool suppressVersionCheck = false) { - JsonObject jsonObject; - - using (FileStream stream = File.OpenRead(path)) - using (FileStructure fs = FileStructure.Read(stream)) - { - if (FileFormat.WixIR != fs.FileFormat) - { - throw new WixUnexpectedFileFormatException(path, FileFormat.WixIR, fs.FileFormat); - } - - var json = fs.GetData(); - jsonObject = SimpleJson.DeserializeObject(json) as JsonObject; - } - - if (!suppressVersionCheck) + using (var stream = File.OpenRead(path)) { - var versionJson = jsonObject.GetValueOrDefault("version"); - - if (!Version.TryParse(versionJson, out var version) || !Intermediate.CurrentVersion.Equals(version)) - { - throw new WixException(WixDataErrors.VersionMismatch(SourceLineNumber.CreateFromUri(path), "intermediate", versionJson, Intermediate.CurrentVersion.ToString())); - } + return Intermediate.Load(stream, path, creator, suppressVersionCheck); } + } - var id = jsonObject.GetValueOrDefault("id"); - - var sections = new List(); - - var sectionsJson = jsonObject.GetValueOrDefault("sections"); - foreach (JsonObject sectionJson in sectionsJson) - { - var section = IntermediateSection.Deserialize(creator, sectionJson); - sections.Add(section); - } - - var localizations = new Dictionary(StringComparer.OrdinalIgnoreCase); - - //var localizationsJson = jsonObject.GetValueOrDefault("localizations") ?? new JsonArray(); - //foreach (JsonObject localizationJson in localizationsJson) - //{ - // var localization = Localization.Deserialize(localizationJson); - // localizations.Add(localization.Culture, localization); - //} - - return new Intermediate(id, sections, localizations, null); + /// + /// Loads an intermediate from a path on disk. + /// + /// Stream to intermediate file. + /// ITupleDefinitionCreator to use when reconstituting the intermediate. + /// Suppress checking for wix.dll version mismatches. + /// Returns the loaded intermediate. + public static Intermediate Load(Stream stream, ITupleDefinitionCreator creator, bool suppressVersionCheck = false) + { + return Load(stream, "", creator, suppressVersionCheck); } /// @@ -183,6 +170,21 @@ namespace WixToolset.Data jsonObject.Add("sections", sectionsJson); + var customDefinitions = GetCustomDefinitionsInSections(); + + if (customDefinitions.Count > 0) + { + var customDefinitionsJson = new JsonArray(customDefinitions.Count); + + foreach (var kvp in customDefinitions.OrderBy(d => d.Key)) + { + var customDefinitionJson = kvp.Value.Serialize(); + customDefinitionsJson.Add(customDefinitionJson); + } + + jsonObject.Add("definitions", customDefinitionsJson); + } + //if (this.Localizations.Any()) //{ // var localizationsJson = new JsonArray(); @@ -200,6 +202,73 @@ namespace WixToolset.Data } } + /// + /// Loads an intermediate from a path on disk. + /// + /// Stream to intermediate file. + /// Path name of intermediate file. + /// ITupleDefinitionCreator to use when reconstituting the intermediate. + /// Suppress checking for wix.dll version mismatches. + /// Returns the loaded intermediate. + internal static Intermediate Load(Stream stream, string path, ITupleDefinitionCreator creator, bool suppressVersionCheck = false) + { + JsonObject jsonObject; + + using (var fs = FileStructure.Read(stream)) + { + if (FileFormat.WixIR != fs.FileFormat) + { + throw new WixUnexpectedFileFormatException(path, FileFormat.WixIR, fs.FileFormat); + } + + var json = fs.GetData(); + jsonObject = SimpleJson.DeserializeObject(json) as JsonObject; + } + + if (!suppressVersionCheck) + { + var versionJson = jsonObject.GetValueOrDefault("version"); + + if (!Version.TryParse(versionJson, out var version) || !Intermediate.CurrentVersion.Equals(version)) + { + throw new WixException(WixDataErrors.VersionMismatch(SourceLineNumber.CreateFromUri(path), "intermediate", versionJson, Intermediate.CurrentVersion.ToString())); + } + } + + var definitionsJson = jsonObject.GetValueOrDefault("definitions"); + + if (definitionsJson != null) + { + foreach (JsonObject definitionJson in definitionsJson) + { + var definition = IntermediateTupleDefinition.Deserialize(definitionJson); + creator.AddCustomTupleDefinition(definition); + } + } + + var id = jsonObject.GetValueOrDefault("id"); + + var sections = new List(); + + var sectionsJson = jsonObject.GetValueOrDefault("sections"); + foreach (JsonObject sectionJson in sectionsJson) + { + var section = IntermediateSection.Deserialize(creator, sectionJson); + sections.Add(section); + } + + var localizations = new Dictionary(StringComparer.OrdinalIgnoreCase); + + //var localizationsJson = jsonObject.GetValueOrDefault("localizations") ?? new JsonArray(); + //foreach (JsonObject localizationJson in localizationsJson) + //{ + // var localization = Localization.Deserialize(localizationJson); + // localizations.Add(localization.Culture, localization); + //} + + return new Intermediate(id, sections, localizations, null); + } + #if false /// /// Loads an intermediate from a path on disk. @@ -344,5 +413,20 @@ namespace WixToolset.Data writer.WriteEndElement(); } #endif + + private Dictionary GetCustomDefinitionsInSections() + { + var customDefinitions = new Dictionary(); + + foreach (var tuple in this.Sections.SelectMany(s => s.Tuples).Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension)) + { + if (!customDefinitions.ContainsKey(tuple.Definition.Name)) + { + customDefinitions.Add(tuple.Definition.Name, tuple.Definition); + } + } + + return customDefinitions; + } } } diff --git a/src/WixToolset.Data/IntermediateFieldExtensions.cs b/src/WixToolset.Data/IntermediateFieldExtensions.cs index c551b455..be225452 100644 --- a/src/WixToolset.Data/IntermediateFieldExtensions.cs +++ b/src/WixToolset.Data/IntermediateFieldExtensions.cs @@ -11,6 +11,8 @@ namespace WixToolset.Data public static IntermediateField Set(this IntermediateField field, object value) { + var data = value; + if (field == null) { throw new ArgumentNullException(nameof(field)); @@ -21,30 +23,72 @@ namespace WixToolset.Data } else if (field.Type == IntermediateFieldType.Bool && !(value is bool)) { - throw new ArgumentException(nameof(value)); + if (value is int) + { + data = ((int)value) != 0; + } + else if (value is string str) + { + if (str.Equals("yes", StringComparison.OrdinalIgnoreCase) || str.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + data = true; + } + else if (str.Equals("no", StringComparison.OrdinalIgnoreCase) || str.Equals("false", StringComparison.OrdinalIgnoreCase)) + { + data = false; + } + else + { + throw new ArgumentException(nameof(value)); + } + } + else + { + throw new ArgumentException(nameof(value)); + } } else if (field.Type == IntermediateFieldType.Number && !(value is int)) { - throw new ArgumentException(nameof(value)); + if (value is string str && Int32.TryParse(str, out var number)) + { + data = number; + } + else + { + throw new ArgumentException(nameof(value)); + } } else if (field.Type == IntermediateFieldType.String && !(value is string)) { - throw new ArgumentException(nameof(value)); - } - else if (field.Type == IntermediateFieldType.Path && !(value is IntermediateFieldPathValue || value is string)) - { - throw new ArgumentException(nameof(value)); - } - - if (field.Type == IntermediateFieldType.Path && value != null && value is string) - { - value = new IntermediateFieldPathValue { Path = (string)value }; + if (value is int) + { + data = value.ToString(); + } + else if (value is bool b) + { + data = b ? "true" : "false"; + } + else + { + throw new ArgumentException(nameof(value)); + } + } + else if (field.Type == IntermediateFieldType.Path && !(value is IntermediateFieldPathValue)) + { + if (value is string str) + { + data = new IntermediateFieldPathValue { Path = str }; + } + else + { + throw new ArgumentException(nameof(value)); + } } field.Value = new IntermediateFieldValue { Context = valueContext, - Data = value, + Data = data, PreviousValue = field.Value }; @@ -103,7 +147,7 @@ namespace WixToolset.Data return field.Value.AsNumber() != 0; case IntermediateFieldType.String: - return !System.String.IsNullOrEmpty(field.Value.AsString()); + return !String.IsNullOrEmpty(field.Value.AsString()); default: throw new InvalidCastException($"Cannot convert field {field.Name} with type {field.Type} to boolean"); diff --git a/src/WixToolset.Data/IntermediateFieldValueExtensions.cs b/src/WixToolset.Data/IntermediateFieldValueExtensions.cs index bc106d4b..9a4237d8 100644 --- a/src/WixToolset.Data/IntermediateFieldValueExtensions.cs +++ b/src/WixToolset.Data/IntermediateFieldValueExtensions.cs @@ -2,26 +2,71 @@ namespace WixToolset.Data { + using System; + public static class IntermediateFieldValueExtensions { public static bool AsBool(this IntermediateFieldValue value) { - return value?.Data == null ? false : (bool)value.Data; + var result = value.AsNullableBool(); + return result.HasValue && result.Value; } public static bool? AsNullableBool(this IntermediateFieldValue value) { - return (bool?)value?.Data; + if (value?.Data == null) + { + return null; + } + else if (value.Data is bool b) + { + return b; + } + else if (value.Data is int n) + { + return n != 0; + } + else if (value.Data is string s) + { + if (s.Equals("yes", StringComparison.OrdinalIgnoreCase) || s.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (s.Equals("no", StringComparison.OrdinalIgnoreCase) || s.Equals("false", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return (bool)value.Data; } public static int AsNumber(this IntermediateFieldValue value) { - return value?.Data == null ? 0 : (int)value.Data; + var result = value.AsNullableNumber(); + return result ?? 0; } public static int? AsNullableNumber(this IntermediateFieldValue value) { - return (int?)value?.Data; + if (value?.Data == null) + { + return null; + } + else if (value.Data is int n) + { + return n; + } + else if (value.Data is bool b) + { + return b ? 1 : 0; + } + else if (value.Data is string s) + { + return Convert.ToInt32(s); + } + + return (int)value.Data; } public static IntermediateFieldPathValue AsPath(this IntermediateFieldValue value) @@ -31,7 +76,28 @@ namespace WixToolset.Data public static string AsString(this IntermediateFieldValue value) { - return (string)value?.Data; + if (value?.Data == null) + { + return null; + } + else if (value.Data is string s) + { + return s; + } + else if (value.Data is int n) + { + return n.ToString(); + } + else if (value.Data is bool b) + { + return b ? "true" : "false"; + } + else if (value.Data is IntermediateFieldPathValue p) + { + return p.Path; + } + + return (string)value.Data; } } } diff --git a/src/WixToolset.Data/IntermediateSection.cs b/src/WixToolset.Data/IntermediateSection.cs index 2b1f7375..5d17eb31 100644 --- a/src/WixToolset.Data/IntermediateSection.cs +++ b/src/WixToolset.Data/IntermediateSection.cs @@ -66,7 +66,6 @@ namespace WixToolset.Data var codepage = jsonObject.GetValueOrDefault("codepage", 0); var id = jsonObject.GetValueOrDefault("id"); var type = jsonObject.GetEnumOrDefault("type", SectionType.Unknown); - var tuplesJson = jsonObject.GetValueOrDefault("tuples"); if (null == id && (SectionType.Unknown != type && SectionType.Fragment != type)) { @@ -80,6 +79,8 @@ namespace WixToolset.Data var section = new IntermediateSection(id, type, codepage); + var tuplesJson = jsonObject.GetValueOrDefault("tuples"); + foreach (JsonObject tupleJson in tuplesJson) { var tuple = IntermediateTuple.Deserialize(creator, tupleJson); diff --git a/src/WixToolset.Data/IntermediateTuple.cs b/src/WixToolset.Data/IntermediateTuple.cs index 0f1e5965..cda133b5 100644 --- a/src/WixToolset.Data/IntermediateTuple.cs +++ b/src/WixToolset.Data/IntermediateTuple.cs @@ -6,9 +6,9 @@ namespace WixToolset.Data public class IntermediateTuple { - //public IntermediateTuple(IntermediateTupleDefinition definition) : this(definition, null, null) - //{ - //} + public IntermediateTuple(IntermediateTupleDefinition definition) : this(definition, null, null) + { + } public IntermediateTuple(IntermediateTupleDefinition definition, SourceLineNumber sourceLineNumber, Identifier id = null) { @@ -35,11 +35,11 @@ namespace WixToolset.Data var sourceLineNumbersJson = jsonObject.GetValueOrDefault("ln"); var fieldsJson = jsonObject.GetValueOrDefault("fields"); - creator.TryGetTupleDefinitionByName(definitionName, out var definition); // TODO: this isn't sufficient. - var tuple = definition.CreateTuple(); + var id = (idJson == null) ? null : Identifier.Deserialize(idJson); + var sourceLineNumbers = (sourceLineNumbersJson == null) ? null : SourceLineNumber.Deserialize(sourceLineNumbersJson); - tuple.Id = (idJson == null) ? null : Identifier.Deserialize(idJson); - tuple.SourceLineNumbers = (sourceLineNumbersJson == null) ? null : SourceLineNumber.Deserialize(sourceLineNumbersJson); + creator.TryGetTupleDefinitionByName(definitionName, out var definition); // TODO: this isn't sufficient. + var tuple = definition.CreateTuple(sourceLineNumbers, id); for (var i = 0; i < fieldsJson.Count; ++i) { diff --git a/src/WixToolset.Data/IntermediateTupleDefinition.cs b/src/WixToolset.Data/IntermediateTupleDefinition.cs index 5658cfe9..eb4ab12e 100644 --- a/src/WixToolset.Data/IntermediateTupleDefinition.cs +++ b/src/WixToolset.Data/IntermediateTupleDefinition.cs @@ -3,6 +3,7 @@ namespace WixToolset.Data { using System; + using SimpleJson; public class IntermediateTupleDefinition { @@ -23,7 +24,7 @@ namespace WixToolset.Data this.FieldDefinitions = fieldDefinitions; this.StrongTupleType = strongTupleType ?? typeof(IntermediateTuple); #if DEBUG - if (!this.StrongTupleType.IsSubclassOf(typeof(IntermediateTuple))) throw new ArgumentException(nameof(strongTupleType)); + if (this.StrongTupleType != typeof(IntermediateTuple) && !this.StrongTupleType.IsSubclassOf(typeof(IntermediateTuple))) throw new ArgumentException(nameof(strongTupleType)); #endif } @@ -43,5 +44,52 @@ namespace WixToolset.Data return result; } + + internal static IntermediateTupleDefinition Deserialize(JsonObject jsonObject) + { + var name = jsonObject.GetValueOrDefault("name"); + var definitionsJson = jsonObject.GetValueOrDefault("fields"); + + var fieldDefinitions = new IntermediateFieldDefinition[definitionsJson.Count]; + + for (var i = 0; i < definitionsJson.Count; ++i) + { + var definitionJson = (JsonObject)definitionsJson[i]; + var fieldName = definitionJson.GetValueOrDefault("name"); + var fieldType = definitionJson.GetEnumOrDefault("type", IntermediateFieldType.String); + fieldDefinitions[i] = new IntermediateFieldDefinition(fieldName, fieldType); + } + + return new IntermediateTupleDefinition(name, fieldDefinitions, null); + } + + internal JsonObject Serialize() + { + var jsonObject = new JsonObject + { + { "name", this.Name } + }; + + var fieldsJson = new JsonArray(this.FieldDefinitions.Length); + + foreach (var fieldDefinition in this.FieldDefinitions) + { + var fieldJson = new JsonObject + { + { "name", fieldDefinition.Name }, + }; + + if (fieldDefinition.Type != IntermediateFieldType.String) + { + fieldJson.Add("type", fieldDefinition.Type.ToString().ToLowerInvariant()); + } + + fieldsJson.Add(fieldJson); + } + + jsonObject.Add("fields", fieldsJson); + + return jsonObject; + } } } diff --git a/src/WixToolset.Data/IntermediateTupleExtensions.cs b/src/WixToolset.Data/IntermediateTupleExtensions.cs index 9865c06a..615c21f9 100644 --- a/src/WixToolset.Data/IntermediateTupleExtensions.cs +++ b/src/WixToolset.Data/IntermediateTupleExtensions.cs @@ -8,7 +8,7 @@ namespace WixToolset.Data { var definition = tuple.Definition.FieldDefinitions[index]; - var field = tuple.Fields[index].Set(definition, value); ; + var field = tuple.Fields[index].Set(definition, value); return tuple.Fields[index] = field; } diff --git a/src/WixToolset.Data/SimpleTupleDefinitionCreator.cs b/src/WixToolset.Data/SimpleTupleDefinitionCreator.cs index b9d0b620..6a86639a 100644 --- a/src/WixToolset.Data/SimpleTupleDefinitionCreator.cs +++ b/src/WixToolset.Data/SimpleTupleDefinitionCreator.cs @@ -2,12 +2,27 @@ namespace WixToolset.Data { + using System.Collections.Generic; + internal class SimpleTupleDefinitionCreator : ITupleDefinitionCreator { + private Dictionary CustomDefinitionByName { get; } = new Dictionary(); + + public void AddCustomTupleDefinition(IntermediateTupleDefinition definition) + { + this.CustomDefinitionByName.Add(definition.Name, definition); + } + public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) { tupleDefinition = TupleDefinitions.ByName(name); + + if (tupleDefinition == null) + { + tupleDefinition = this.CustomDefinitionByName.GetValueOrDefault(name); + } + return tupleDefinition != null; } } -} \ No newline at end of file +} diff --git a/src/WixToolset.Data/Tuples/WixFileTuple.cs b/src/WixToolset.Data/Tuples/WixFileTuple.cs index 88dc4c82..3581e2e6 100644 --- a/src/WixToolset.Data/Tuples/WixFileTuple.cs +++ b/src/WixToolset.Data/Tuples/WixFileTuple.cs @@ -131,9 +131,9 @@ namespace WixToolset.Data.Tuples set => this.Set((int)WixFileTupleFields.DiskId, value); } - public string Source + public IntermediateFieldPathValue Source { - get => this.Fields[(int)WixFileTupleFields.Source].AsPath()?.Path; + get => this.Fields[(int)WixFileTupleFields.Source].AsPath(); set => this.Set((int)WixFileTupleFields.Source, value); } -- cgit v1.2.3-55-g6feb