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

Support strings in manifest versions #17329

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": 5,
"version": "6.0.0-preview1",
"workloads": {
"microsoft-net-sdk-testworkload": {
"description": "SDK Test Workload",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
-->
<ItemGroup>
<Compile Include="..\Resolvers\Microsoft.NET.Sdk.WorkloadManifestReader\**\*.cs" LinkBase="Microsoft.DotNet.SdkResolver"/>
<Compile Include="..\Resolvers\Microsoft.DotNet.MSBuildSdkResolver\FXVersion.cs" LinkBase="Microsoft.DotNet.SdkResolver"/>
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/FXVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#nullable disable

using System.Text;

namespace Microsoft.DotNet.MSBuildSdkResolver
{
// Note: This is SemVer 2.0.0 https://semver.org/spec/v2.0.0.html
Expand Down Expand Up @@ -313,5 +315,14 @@ public static bool TryParse(string fxVersionString, out FXVersion FXVersion)

return true;
}

public override string ToString()
=> (!string.IsNullOrEmpty(Pre), !string.IsNullOrEmpty(Build)) switch
{
(false, false) => $"{Major}.{Minor}.{Patch}",
(true, false) => $"{Major}.{Minor}.{Patch}{Pre}",
(false, true) => $"{Major}.{Minor}.{Patch}{Build}",
(true, true) => $"{Major}.{Minor}.{Patch}{Pre}{Build}",
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader
/// </summary>
public interface IWorkloadManifestProvider
{
IEnumerable<Stream> GetManifests();
IEnumerable<(string manifestId, Stream manifestStream)> GetManifests();
IEnumerable<string> GetManifestDirectories();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<ItemGroup>
<EmbeddedResource Update="**\*.resx" GenerateSource="true" Namespace="Microsoft.NET.Sdk.Localization" />
<Compile Include="..\Microsoft.DotNet.MSBuildSdkResolver\FXVersion.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ static int Last2DigitsTo0(int versionBuild)
}
}

public IEnumerable<Stream> GetManifests()
public IEnumerable<(string manifestId, Stream manifestStream)> GetManifests()
{
foreach (var workloadManifestDirectory in GetManifestDirectories())
{
var workloadManifest = Path.Combine(workloadManifestDirectory, "WorkloadManifest.json");
yield return File.OpenRead(workloadManifest);
var id = Path.GetFileName(workloadManifestDirectory);
yield return (id, File.OpenRead(workloadManifest));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@
<data name="ExpectedBoolAtOffset" xml:space="preserve">
<value>Expected boolean value at offset {0}</value>
</data>
<data name="ExpectedIntegerAtOffset" xml:space="preserve">
<value>Expected integer value at offset {0}</value>
</data>
<data name="ExpectedStringAtOffset" xml:space="preserve">
<value>Expected string value at offset {0}</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,47 @@

using System.Collections.Generic;

using Microsoft.DotNet.MSBuildSdkResolver;

namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
/// <summary>
/// An SDK workload manifest
/// </summary>
internal class WorkloadManifest
{
public WorkloadManifest(long version, string? description, Dictionary<WorkloadDefinitionId, WorkloadDefinition> workloads, Dictionary<WorkloadPackId, WorkloadPack> packs)
internal WorkloadManifest(string id, FXVersion version, string? description, Dictionary<WorkloadDefinitionId, WorkloadDefinition> workloads, Dictionary<WorkloadPackId, WorkloadPack> packs, Dictionary<string, FXVersion>? dependsOnManifests)
{
Version = version;
Id = id;
ParsedVersion = version;
Description = description;
Workloads = workloads;
Packs = packs;
DependsOnManifests = dependsOnManifests;
}

public long Version { get; }
/// <summary>
/// The ID of the manifest is its filename without the extension.
/// </summary>
public string Id { get; }

/// <summary>
/// The version of the manifest. It is relative to the SDK band.
/// </summary>
public string Version => ParsedVersion.ToString()!;

/// <summary>
/// The version of the manifest. It is relative to the SDK band.
/// </summary>
internal FXVersion ParsedVersion { get; }

/// <summary>
/// ID and minimum version for any other manifests that this manifest depends on. Use only for validating consistancy.
/// </summary>
internal Dictionary<string, FXVersion>? DependsOnManifests { get; }

public string? Description { get; }

public Dictionary<WorkloadDefinitionId, WorkloadDefinition> Workloads { get; }
public Dictionary<WorkloadPackId, WorkloadPack> Packs { get; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader
internal partial class WorkloadManifestReader
{

public static WorkloadManifest ReadWorkloadManifest(Stream manifestStream)
public static WorkloadManifest ReadWorkloadManifest(string manifestId, Stream manifestStream)
{
using var textReader = new StreamReader(manifestStream, System.Text.Encoding.UTF8, true);
using var jsonReader = new JsonTextReader(textReader);

var reader = new Utf8JsonStreamReader(jsonReader);

return ReadWorkloadManifest(ref reader);
return ReadWorkloadManifest(manifestId, ref reader);
}
// this is a compat wrapper so the source matches the system.text.json impl
private ref struct Utf8JsonStreamReader
Expand Down Expand Up @@ -74,6 +74,7 @@ public bool Read()
internal static class JsonTokenTypeExtensions
{
public static bool IsBool(this JsonTokenType tokenType) => tokenType == JsonTokenType.Boolean;
public static bool IsInt(this JsonTokenType tokenType) => tokenType == JsonTokenType.Integer;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
internal partial class WorkloadManifestReader
{
public static WorkloadManifest ReadWorkloadManifest(Stream manifestStream)
public static WorkloadManifest ReadWorkloadManifest(string manifestId, Stream manifestStream)
{
var readerOptions = new JsonReaderOptions
{
Expand All @@ -22,7 +22,7 @@ public static WorkloadManifest ReadWorkloadManifest(Stream manifestStream)

var reader = new Utf8JsonStreamReader(manifestStream, readerOptions);

return ReadWorkloadManifest(ref reader);
return ReadWorkloadManifest(manifestId, ref reader);
}

private ref struct Utf8JsonStreamReader
Expand Down Expand Up @@ -109,6 +109,7 @@ public bool Read()
internal static class JsonTokenTypeExtensions
{
public static bool IsBool(this JsonTokenType tokenType) => tokenType == JsonTokenType.True || tokenType == JsonTokenType.False;
public static bool IsInt(this JsonTokenType tokenType) => tokenType == JsonTokenType.Number;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System.Collections.Generic;
using Microsoft.NET.Sdk.Localization;
using FXVersion = Microsoft.DotNet.MSBuildSdkResolver.FXVersion;

#if USE_SYSTEM_TEXT_JSON
using System.Text.Json;
Expand Down Expand Up @@ -35,6 +36,19 @@ private static string ReadString(ref Utf8JsonStreamReader reader)
throw new WorkloadManifestFormatException(Strings.ExpectedStringAtOffset, reader.TokenStartIndex);
}

private static long ReadInt64(ref Utf8JsonStreamReader reader)
{
if (reader.Read() && reader.TokenType.IsInt())
{
if (reader.TryGetInt64 (out long value))
{
return value;
}
}

throw new WorkloadManifestFormatException(Strings.ExpectedIntegerAtOffset, reader.TokenStartIndex);
}

private static bool ReadBool(ref Utf8JsonStreamReader reader)
{
if (reader.Read() && reader.TokenType.IsBool())
Expand All @@ -48,14 +62,15 @@ private static bool ReadBool(ref Utf8JsonStreamReader reader)
private static void ThrowDuplicateKeyException<T> (ref Utf8JsonStreamReader reader, T key)
=> throw new WorkloadManifestFormatException(Strings.DuplicateKeyAtOffset, key?.ToString() ?? throw new ArgumentNullException (nameof(key)), reader.TokenStartIndex);

private static WorkloadManifest ReadWorkloadManifest(ref Utf8JsonStreamReader reader)
private static WorkloadManifest ReadWorkloadManifest(string id, ref Utf8JsonStreamReader reader)
{
ConsumeToken(ref reader, JsonTokenType.StartObject);

long? version = null;
FXVersion? version = null;
string? description = null;
Dictionary<WorkloadDefinitionId, WorkloadDefinition>? workloads = null;
Dictionary<WorkloadPackId, WorkloadPack>? packs = null;
Dictionary<string, FXVersion>? dependsOn = null;

while (reader.Read())
{
Expand All @@ -67,9 +82,27 @@ private static WorkloadManifest ReadWorkloadManifest(ref Utf8JsonStreamReader re
if (string.Equals("version", propName, StringComparison.OrdinalIgnoreCase))
{
if (version != null) ThrowDuplicateKeyException(ref reader, propName);
if (!reader.Read() || !reader.TryGetInt64(out var v)) throw new WorkloadManifestFormatException(Strings.MissingOrInvalidManifestVersion);
version = v;
continue;
if (reader.Read())
{
if (reader.TokenType == JsonTokenType.String)
{
if (FXVersion.TryParse(reader.GetString(), out version))
{
continue;
}
}
else if (reader.TokenType.IsInt())
{
// older manifests could have an int value
if (reader.TryGetInt64(out var intVersion) && intVersion < int.MaxValue)
{
version = new FXVersion((int)intVersion, 0, 0);
continue;
}
}

}
throw new WorkloadManifestFormatException(Strings.MissingOrInvalidManifestVersion);
}

if (string.Equals("description", propName, StringComparison.OrdinalIgnoreCase))
Expand All @@ -79,6 +112,13 @@ private static WorkloadManifest ReadWorkloadManifest(ref Utf8JsonStreamReader re
continue;
}

if (string.Equals("depends-on", propName, StringComparison.OrdinalIgnoreCase))
{
if (dependsOn != null) ThrowDuplicateKeyException(ref reader, propName);
dependsOn = ReadDependsOn(ref reader);
continue;
}

if (string.Equals("workloads", propName, StringComparison.OrdinalIgnoreCase))
{
if (workloads != null) ThrowDuplicateKeyException(ref reader, propName);
Expand Down Expand Up @@ -107,16 +147,18 @@ private static WorkloadManifest ReadWorkloadManifest(ref Utf8JsonStreamReader re
throw new WorkloadManifestFormatException(Strings.UnknownKeyAtOffset, propName, reader.TokenStartIndex);
case JsonTokenType.EndObject:

if (version == null || version < 0)
if (version == null)
{
throw new WorkloadManifestFormatException(Strings.MissingOrInvalidManifestVersion);
}

return new WorkloadManifest (
version.Value,
id,
version,
description,
workloads ?? new Dictionary<WorkloadDefinitionId, WorkloadDefinition> (),
packs ?? new Dictionary<WorkloadPackId, WorkloadPack> ()
packs ?? new Dictionary<WorkloadPackId, WorkloadPack> (),
dependsOn
);
default:
throw new WorkloadManifestFormatException(Strings.UnexpectedTokenAtOffset, reader.TokenType, reader.TokenStartIndex);
Expand Down Expand Up @@ -154,6 +196,38 @@ private static bool ConsumeValue (ref Utf8JsonStreamReader reader)
return true;
}

private static Dictionary<string, FXVersion> ReadDependsOn(ref Utf8JsonStreamReader reader)
{
ConsumeToken(ref reader, JsonTokenType.StartObject);

var dependsOn = new Dictionary<string, FXVersion>(StringComparer.OrdinalIgnoreCase);

while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
var dependencyId = reader.GetString();
if (FXVersion.TryParse(ReadString(ref reader), out var dependencyVersion))
{
if (dependsOn.ContainsKey(dependencyId))
{
ThrowDuplicateKeyException(ref reader, dependencyId);
}
dependsOn.Add(dependencyId, dependencyVersion);
continue;
}
goto default;
case JsonTokenType.EndObject:
return dependsOn;
default:
throw new WorkloadManifestFormatException(Strings.UnexpectedTokenAtOffset, reader.TokenType, reader.TokenStartIndex);
}
}

throw new WorkloadManifestFormatException(Strings.IncompleteDocument);
}

private static Dictionary<WorkloadDefinitionId, WorkloadDefinition> ReadWorkloadDefinitions(ref Utf8JsonStreamReader reader)
{
ConsumeToken(ref reader, JsonTokenType.StartObject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System.Linq;
using System.Runtime.InteropServices;

using Microsoft.DotNet.MSBuildSdkResolver;

namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
/// <remarks>
Expand Down Expand Up @@ -60,19 +62,40 @@ private WorkloadResolver(IWorkloadManifestProvider manifestProvider, string [] d
_dotnetRootPaths = dotnetRootPaths;
_currentRuntimeIdentifiers = currentRuntimeIdentifiers;

var manifests = new List<WorkloadManifest>();
var manifests = new Dictionary<string,WorkloadManifest>(StringComparer.OrdinalIgnoreCase);

foreach (var manifestStream in manifestProvider.GetManifests())
foreach ((string manifestId, Stream manifestStream) in manifestProvider.GetManifests())
{
using (manifestStream)
{
var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestStream);
manifests.Add(manifest);
var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId, manifestStream);
if (manifests.ContainsKey(manifestId))
{
throw new Exception($"Duplicate workload manifest {manifestId}");
}
manifests.Add(manifestId, manifest);
}
}

foreach (var manifest in manifests)
foreach (var manifest in manifests.Values)
{
if (manifest.DependsOnManifests != null)
{
foreach (var dependency in manifest.DependsOnManifests)
{
if (manifests.TryGetValue(dependency.Key, out var resolvedDependency))
{
if (FXVersion.Compare(dependency.Value, resolvedDependency.ParsedVersion) > 0)
{
throw new Exception($"Inconsistency in workload manifest '{manifest.Id}': requires '{dependency.Key}' version at least {dependency.Value} but found {resolvedDependency.Version}");
}
}
else
{
throw new Exception($"Inconsistency in workload manifest '{manifest.Id}': missing dependency '{dependency.Key}'");
}
}
}
foreach (var workload in manifest.Workloads)
{
_workloads.Add(workload.Key, workload.Value);
Expand Down
Loading