Skip to content

Commit

Permalink
Merge pull request #17329 from dsplaisted/workload-manifest-string-ve…
Browse files Browse the repository at this point in the history
…rsion

Support strings in manifest versions
  • Loading branch information
dsplaisted authored May 1, 2021
2 parents 9f8539a + ff83aa9 commit ec1fa41
Show file tree
Hide file tree
Showing 30 changed files with 342 additions and 34 deletions.
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

0 comments on commit ec1fa41

Please sign in to comment.