Skip to content

Commit ac5c33d

Browse files
fanyang-monoCoffeeFluxdanmoseley
authored
Add msbuild task to generate binary runtimeconfig format (#49544)
* 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>
1 parent 49e5d17 commit ac5c33d

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

Directory.Build.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<WasmAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))</WasmAppBuilderDir>
7171
<WasmBuildTasksDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))</WasmBuildTasksDir>
7272
<MonoAOTCompilerDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(NetCoreAppToolCurrent)'))</MonoAOTCompilerDir>
73+
<RuntimeConfigParserDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'RuntimeConfigParser', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))</RuntimeConfigParserDir>
7374

7475
<InstallerTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' == 'Core'">$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', '$(NetCoreAppToolCurrent)', 'installer.tasks.dll'))</InstallerTasksAssemblyPath>
7576
<InstallerTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' != 'Core'">$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', 'net461', 'installer.tasks.dll'))</InstallerTasksAssemblyPath>
@@ -78,6 +79,7 @@
7879
<WasmAppBuilderTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll'))</WasmAppBuilderTasksAssemblyPath>
7980
<WasmBuildTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll'))</WasmBuildTasksAssemblyPath>
8081
<MonoAOTCompilerTasksAssemblyPath>$([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll'))</MonoAOTCompilerTasksAssemblyPath>
82+
<RuntimeConfigParserTasksAssemblyPath>$([MSBuild]::NormalizePath('$(RuntimeConfigParserDir)', 'RuntimeConfigParser.dll'))</RuntimeConfigParserTasksAssemblyPath>
8183
</PropertyGroup>
8284

8385
<PropertyGroup Label="CalculateConfiguration">
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Text.Json;
8+
using System.Text.Json.Serialization;
9+
using System.Reflection.Metadata;
10+
using Microsoft.Build.Framework;
11+
using Microsoft.Build.Utilities;
12+
13+
public class RuntimeConfigParserTask : Task
14+
{
15+
/// <summary>
16+
/// The path to runtimeconfig.json file.
17+
/// </summary>
18+
[Required]
19+
public string RuntimeConfigFile { get; set; } = "";
20+
21+
/// <summary>
22+
/// The path to the output binary file.
23+
/// </summary>
24+
[Required]
25+
public string OutputFile { get; set; } = "";
26+
27+
/// <summary>
28+
/// List of properties reserved for the host.
29+
/// </summary>
30+
public ITaskItem[] ReservedProperties { get; set; } = Array.Empty<ITaskItem>();
31+
32+
public override bool Execute()
33+
{
34+
if (string.IsNullOrEmpty(RuntimeConfigFile))
35+
{
36+
Log.LogError($"'{nameof(RuntimeConfigFile)}' is required.");
37+
}
38+
39+
if (string.IsNullOrEmpty(OutputFile))
40+
{
41+
Log.LogError($"'{nameof(OutputFile)}' is required.");
42+
}
43+
44+
Dictionary<string, string> configProperties = ConvertInputToDictionary(RuntimeConfigFile);
45+
46+
if (ReservedProperties.Length != 0)
47+
{
48+
CheckDuplicateProperties(configProperties, ReservedProperties);
49+
}
50+
51+
var blobBuilder = new BlobBuilder();
52+
ConvertDictionaryToBlob(configProperties, blobBuilder);
53+
54+
using var stream = File.OpenWrite(OutputFile);
55+
blobBuilder.WriteContentTo(stream);
56+
57+
return !Log.HasLoggedErrors;
58+
}
59+
60+
/// Reads a json file from the given path and extracts the "configProperties" key (assumed to be a string to string dictionary)
61+
private Dictionary<string, string> ConvertInputToDictionary(string inputFilePath)
62+
{
63+
var options = new JsonSerializerOptions {
64+
AllowTrailingCommas = true,
65+
ReadCommentHandling = JsonCommentHandling.Skip,
66+
};
67+
68+
var jsonString = File.ReadAllText(inputFilePath);
69+
var parsedJson = JsonSerializer.Deserialize<Root>(jsonString, options);
70+
71+
if (parsedJson == null)
72+
{
73+
throw new ArgumentException("Wasn't able to parse the json file successfully.");
74+
}
75+
76+
return parsedJson.ConfigProperties;
77+
}
78+
79+
/// Just write the dictionary out to a blob as a count followed by
80+
/// a length-prefixed UTF8 encoding of each key and value
81+
private void ConvertDictionaryToBlob(IReadOnlyDictionary<string, string> properties, BlobBuilder builder)
82+
{
83+
int count = properties.Count;
84+
85+
builder.WriteCompressedInteger(count);
86+
foreach (var kvp in properties)
87+
{
88+
builder.WriteSerializedString(kvp.Key);
89+
builder.WriteSerializedString(kvp.Value);
90+
}
91+
}
92+
93+
private void CheckDuplicateProperties(IReadOnlyDictionary<string, string> properties, ITaskItem[] keys)
94+
{
95+
foreach (var key in keys)
96+
{
97+
if (properties.ContainsKey(key.ItemSpec))
98+
{
99+
throw new ArgumentException($"Property '{key}' can't be set by the user!");
100+
}
101+
}
102+
}
103+
}
104+
105+
public class Root
106+
{
107+
// the configProperties key
108+
[JsonPropertyName("configProperties")]
109+
public Dictionary<string, string> ConfigProperties { get; set; } = new Dictionary<string, string>();
110+
// everything other than configProperties
111+
[JsonExtensionData]
112+
public Dictionary<string, object> ExtensionData { get; set; } = new Dictionary<string, object>();
113+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
4+
<OutputType>Library</OutputType>
5+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
6+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
7+
<Nullable>enable</Nullable>
8+
<NoWarn>$(NoWarn),CA1050</NoWarn>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
12+
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
13+
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
14+
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
15+
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<Compile Include="RuntimeConfigParser.cs" />
19+
</ItemGroup>
20+
21+
<Target Name="PublishBuilder"
22+
AfterTargets="Build"
23+
DependsOnTargets="Publish" />
24+
25+
<Target Name="GetFilesToPackage" />
26+
27+
</Project>

0 commit comments

Comments
 (0)