Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add msbuild task to generate binary runtimeconfig format #49544

Merged
merged 10 commits into from
Mar 17, 2021
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<WasmAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))</WasmAppBuilderDir>
<WasmBuildTasksDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))</WasmBuildTasksDir>
<MonoAOTCompilerDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(NetCoreAppToolCurrent)'))</MonoAOTCompilerDir>
<RuntimeconfigParserDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'RuntimeconfigParser', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))</RuntimeconfigParserDir>

<InstallerTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' == 'Core'">$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', '$(NetCoreAppToolCurrent)', 'installer.tasks.dll'))</InstallerTasksAssemblyPath>
<InstallerTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' != 'Core'">$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', 'net461', 'installer.tasks.dll'))</InstallerTasksAssemblyPath>
Expand All @@ -78,6 +79,7 @@
<WasmAppBuilderTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll'))</WasmAppBuilderTasksAssemblyPath>
<WasmBuildTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll'))</WasmBuildTasksAssemblyPath>
<MonoAOTCompilerTasksAssemblyPath>$([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll'))</MonoAOTCompilerTasksAssemblyPath>
<RuntimeconfigParserTasksAssemblyPath>$([MSBuild]::NormalizePath('$(RuntimeconfigParserDir)', 'RuntimeconfigParser.dll'))</RuntimeconfigParserTasksAssemblyPath>
</PropertyGroup>

<PropertyGroup Label="CalculateConfiguration">
Expand Down
108 changes: 108 additions & 0 deletions src/tasks/RuntimeconfigParser/RuntimeconfigParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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
CoffeeFlux marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// The path to runtimeconfig.json file.
/// </summary>
[Required]
public string runtimeConfigFile { get; set; } = ""!;
fanyang-mono marked this conversation as resolved.
Show resolved Hide resolved
fanyang-mono marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The path to the output binary file.
/// </summary>
[Required]
public string outputFile { get; set; } = ""!;

/// <summary>
/// List of reserved properties.
fanyang-mono marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public ITaskItem[] reservedProperties { get; set; } = Array.Empty<ITaskItem>();

public override bool Execute()
{
if (string.IsNullOrEmpty(runtimeConfigFile))
{
throw new ArgumentException($"'{nameof(runtimeConfigFile)}' is required.", nameof(runtimeConfigFile));
}

if (string.IsNullOrEmpty(outputFile))
{
throw new ArgumentException($"'{nameof(outputFile)}' is required.", nameof(outputFile));
}

Dictionary<string, string> 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 true;
}

/// Reads a json file from the given path and extracts the "configProperties" key (assumed to be a string to string dictionary)
private Dictionary<string, string> ConvertInputToDictionary(string inputFilePath)
{
var options = new JsonSerializerOptions {
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
};

var jsonString = File.ReadAllText(inputFilePath);
var parsedJson = JsonSerializer.Deserialize<Root>(jsonString, options);

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<string, string> properties, BlobBuilder b)
{
int count = properties.Count;

b.WriteCompressedInteger (count);
foreach (var kvp in properties)
{
b.WriteSerializedString (kvp.Key);
b.WriteSerializedString (kvp.Value);
}
}

private void checkDuplicateProperties (IReadOnlyDictionary<string, string> 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<string, string> ConfigProperties {get; set;} = new Dictionary<string, string> ();
// everything other than configProperties
[JsonExtensionData]
public Dictionary<string, object> ExtensionData {get; set;} = new Dictionary<string, object> ();
}
27 changes: 27 additions & 0 deletions src/tasks/RuntimeconfigParser/RuntimeconfigParser.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<OutputType>Library</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn),CA1050</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="RuntimeconfigParser.cs" />
</ItemGroup>

<Target Name="PublishBuilder"
AfterTargets="Build"
DependsOnTargets="Publish" />

<Target Name="GetFilesToPackage" />

</Project>