Skip to content

Commit

Permalink
Add msbuild task to generate binary runtimeconfig format (#49544)
Browse files Browse the repository at this point in the history
* Add msbuild task to generate binary runtimeconfig format

* Update property name due to naming conversion.

* Fixed more formatting issue

* Fixed one more naming convention

* Update src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>

* Update src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>

* Update src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>

* Fixed comments

* Update src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs

Co-authored-by: Dan Moseley <danmose@microsoft.com>

* Fix error handling

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>
Co-authored-by: Dan Moseley <danmose@microsoft.com>
  • Loading branch information
3 people committed Mar 17, 2021
1 parent 49e5d17 commit ac5c33d
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
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
113 changes: 113 additions & 0 deletions src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The path to runtimeconfig.json file.
/// </summary>
[Required]
public string RuntimeConfigFile { get; set; } = "";

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

/// <summary>
/// List of properties reserved for the host.
/// </summary>
public ITaskItem[] ReservedProperties { get; set; } = Array.Empty<ITaskItem>();

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<string, string> configProperties = ConvertInputToDictionary(RuntimeConfigFile);

if (ReservedProperties.Length != 0)
{
CheckDuplicateProperties(configProperties, ReservedProperties);

This comment has been minimized.

Copy link
@dellis1972

dellis1972 Apr 14, 2021

Both CheckDuplicateProperties and ConvertInputToDictionary raise ArgumentException. Currently these will be caught by MSBuild and will result in a pretty nasty error message which will just contain the error and a stack trace. Should we add some exception handling here and add a good actionable error message via LogError (or even better LogCodedError) so the user can at least try fix the config file?

This comment has been minimized.

Copy link
@fanyang-mono

fanyang-mono Apr 14, 2021

Author Member

Good suggestion. I will look into it. I've created an issue to track this. #51232

}

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<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);

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<string, string> 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<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>

0 comments on commit ac5c33d

Please sign in to comment.