|
// 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;
using System.Diagnostics.CodeAnalysis;
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[] RuntimeConfigReservedProperties { get; set; } = Array.Empty<ITaskItem>();
private static readonly JsonSerializerOptions s_jsonOptions = new JsonSerializerOptions
{
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
Converters =
{
new StringConverter()
}
};
public override bool Execute()
{
if (string.IsNullOrEmpty(RuntimeConfigFile))
{
Log.LogError($"'{nameof(RuntimeConfigFile)}' is required.");
return false;
}
if (string.IsNullOrEmpty(OutputFile))
{
Log.LogError($"'{nameof(OutputFile)}' is required.");
return false;
}
if (!TryConvertInputToDictionary(RuntimeConfigFile, out Dictionary<string, string>? configProperties))
{
return false;
}
if (RuntimeConfigReservedProperties.Length != 0)
{
if (!CheckReservedProperties(configProperties, RuntimeConfigReservedProperties))
{
return false;
}
}
var blobBuilder = new BlobBuilder();
ConvertDictionaryToBlob(configProperties, blobBuilder);
Directory.CreateDirectory(Path.GetDirectoryName(OutputFile!)!);
using var stream = new FileStream(OutputFile, FileMode.Create, FileAccess.Write, FileShare.None);
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 bool TryConvertInputToDictionary(string inputFilePath, [NotNullWhen(true)] out Dictionary<string, string>? result)
{
result = null;
var jsonString = File.ReadAllText(inputFilePath);
var parsedJson = JsonSerializer.Deserialize<Root>(jsonString, s_jsonOptions);
if (parsedJson == null)
{
Log.LogError("Wasn't able to parse the json file successfully.");
return false;
}
if (parsedJson.RuntimeOptions == null)
{
Log.LogError("Key runtimeOptions wasn't found in the json file.");
return false;
}
if (parsedJson.RuntimeOptions.ConfigProperties == null)
{
Log.LogError("Key runtimeOptions->configProperties wasn't found in the json file.");
return false;
}
result = parsedJson.RuntimeOptions.ConfigProperties;
return true;
}
/// Just write the dictionary out to a blob as a count followed by
/// a length-prefixed UTF8 encoding of each key and value
private static void ConvertDictionaryToBlob(Dictionary<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 bool CheckReservedProperties(Dictionary<string, string> properties, ITaskItem[] keys)
{
var succeed = true;
foreach (var key in keys)
{
if (properties.ContainsKey(key.ItemSpec))
{
Log.LogError($"Property '{key}' can't be set by the user!");
succeed = false;
}
}
return succeed;
}
}
public class RuntimeOption
{
// 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> ExtensionDataSub { get; set; } = new Dictionary<string, object>();
}
public class Root
{
// the runtimeOptions key
[JsonPropertyName("runtimeOptions")]
public RuntimeOption RuntimeOptions { get; set; } = new RuntimeOption();
// everything other than runtimeOptions
[JsonExtensionData]
public Dictionary<string, object> ExtensionDataRoot { get; set; } = new Dictionary<string, object>();
}
public class StringConverter : JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Number:
var stringValueInt = reader.GetInt32();
return stringValueInt.ToString();
case JsonTokenType.True:
return "true";
case JsonTokenType.False:
return "false";
case JsonTokenType.String:
var stringValue = reader.GetString();
if (stringValue != null)
{
return stringValue;
}
break;
default:
throw new System.Text.Json.JsonException();
}
throw new System.Text.Json.JsonException();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
|