diff --git a/Directory.Build.props b/Directory.Build.props index 5736fce3341c20..f971fa0a774fcc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -70,6 +70,7 @@ $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(NetCoreAppToolCurrent)')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'RuntimeConfigParser', 'Debug', '$(NetCoreAppToolCurrent)', 'publish')) $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', '$(NetCoreAppToolCurrent)', 'installer.tasks.dll')) $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', 'net461', 'installer.tasks.dll')) @@ -78,6 +79,7 @@ $([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll')) $([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll')) + $([MSBuild]::NormalizePath('$(RuntimeConfigParserDir)', 'RuntimeConfigParser.dll')) diff --git a/src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs new file mode 100644 index 00000000000000..12cbf59442f5f4 --- /dev/null +++ b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Reflection.Metadata; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +public class RuntimeConfigParserTask : Task +{ + /// + /// The path to runtimeconfig.json file. + /// + [Required] + public string RuntimeConfigFile { get; set; } = ""; + + /// + /// The path to the output binary file. + /// + [Required] + public string OutputFile { get; set; } = ""; + + /// + /// List of properties reserved for the host. + /// + public ITaskItem[] ReservedProperties { get; set; } = Array.Empty(); + + public override bool Execute() + { + if (string.IsNullOrEmpty(RuntimeConfigFile)) + { + Log.LogError($"'{nameof(RuntimeConfigFile)}' is required."); + } + + if (string.IsNullOrEmpty(OutputFile)) + { + Log.LogError($"'{nameof(OutputFile)}' is required."); + } + + Dictionary configProperties = ConvertInputToDictionary(RuntimeConfigFile); + + if (ReservedProperties.Length != 0) + { + CheckDuplicateProperties(configProperties, ReservedProperties); + } + + var blobBuilder = new BlobBuilder(); + ConvertDictionaryToBlob(configProperties, blobBuilder); + + using var stream = File.OpenWrite(OutputFile); + blobBuilder.WriteContentTo(stream); + + return !Log.HasLoggedErrors; + } + + /// Reads a json file from the given path and extracts the "configProperties" key (assumed to be a string to string dictionary) + private Dictionary ConvertInputToDictionary(string inputFilePath) + { + var options = new JsonSerializerOptions { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + }; + + var jsonString = File.ReadAllText(inputFilePath); + var parsedJson = JsonSerializer.Deserialize(jsonString, options); + + if (parsedJson == null) + { + throw new ArgumentException("Wasn't able to parse the json file successfully."); + } + + return parsedJson.ConfigProperties; + } + + /// Just write the dictionary out to a blob as a count followed by + /// a length-prefixed UTF8 encoding of each key and value + private void ConvertDictionaryToBlob(IReadOnlyDictionary properties, BlobBuilder builder) + { + int count = properties.Count; + + builder.WriteCompressedInteger(count); + foreach (var kvp in properties) + { + builder.WriteSerializedString(kvp.Key); + builder.WriteSerializedString(kvp.Value); + } + } + + private void CheckDuplicateProperties(IReadOnlyDictionary properties, ITaskItem[] keys) + { + foreach (var key in keys) + { + if (properties.ContainsKey(key.ItemSpec)) + { + throw new ArgumentException($"Property '{key}' can't be set by the user!"); + } + } + } +} + +public class Root +{ + // the configProperties key + [JsonPropertyName("configProperties")] + public Dictionary ConfigProperties { get; set; } = new Dictionary(); + // everything other than configProperties + [JsonExtensionData] + public Dictionary ExtensionData { get; set; } = new Dictionary(); +} diff --git a/src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj new file mode 100644 index 00000000000000..39204be9b0bbf5 --- /dev/null +++ b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj @@ -0,0 +1,27 @@ + + + $(NetCoreAppToolCurrent) + Library + true + false + enable + $(NoWarn),CA1050 + + + + + + + + + + + + + + + + +