Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f9d957f
Eliminate redundant existence checks in workload resolver
mhutch Jun 3, 2021
b8ade8e
Implement workload redirects
mhutch Jun 9, 2021
a22e8d0
Fix 'avaliable' typo
mhutch Jun 9, 2021
e7a3eb2
Allow deferred opening of workload manifest stream
mhutch Jun 9, 2021
cf013d2
Add workload resolver method to determine updated workloads
mhutch Jun 9, 2021
3374ec9
Replace workload TempDirResolver with more generic OverlayResolver
mhutch Jun 10, 2021
0a8238c
Improve diagnosability of manifest conflicts
mhutch Jun 10, 2021
accd669
Improve workload composition errors
mhutch Jun 22, 2021
ed81dd4
Nullability checks in workload manifest reader tests
mhutch Jun 22, 2021
3d64056
Stronger typing of workload/pack ids in public API
mhutch Jun 22, 2021
a6cd134
Disable workload redirects for now
mhutch Jul 1, 2021
0254d95
Improve WorkloadResolver.GetPacksInWorkload
mhutch Jul 2, 2021
15d527f
Fix available workloads returned by resolver
mhutch Jul 2, 2021
619304d
Include workload ID in exception when resolver cannot find it
mhutch Jul 2, 2021
d867fef
Hide WorkloadResolver.GetPackPath in favor of ResolvePackPath
mhutch Jul 2, 2021
536f67c
When suggesting workloads, only consider available workloads
mhutch Jul 2, 2021
e4b36e3
Localize remaining workload composition errors
mhutch Jul 13, 2021
d75137c
Simplify WorkloadResolver.GetInstalledManifests
mhutch Jul 14, 2021
c9d64aa
Fix ResolvePackPath returning empty workload pack ids
mhutch Jul 15, 2021
48720b8
Check updated composition error messages in workload tests
mhutch Jul 15, 2021
0537ca7
Don't assume missing workload packs can always be satisfied
mhutch Jul 17, 2021
f4ed4c7
Fix accidental removal that broke workload search tests
mhutch Jul 19, 2021
b736168
Fix error message argument indices
dsplaisted Jul 18, 2021
4d4f3ef
Fix workload mocks to use correct manifest IDs
dsplaisted Jul 18, 2021
9f15943
Update test now that mock workload manifest has right ID
dsplaisted Jul 19, 2021
88fc6b4
Generate correct error message when trying to install a workload whic…
dsplaisted Jul 19, 2021
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
Prev Previous commit
Next Next commit
Improve workload composition errors
Manifests have an "informational path" describing where they were
loaded from. The workload resolver includes the manifest IDs and
associated informational path in composition errors, so it's easier
to see what caused the conflict.
  • Loading branch information
mhutch committed Jul 17, 2021
commit accd6692504823392cb216ba3d7453dabf7ab3f6
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal static ReleaseVersion GetValidatedSdkVersion(string versionOption, stri
}
try
{
foreach ((string manifestId, Func<Stream> openManifestStream) in manifests)
foreach ((string manifestId, string informationalPath, Func<Stream> openManifestStream) in manifests)
{
using (var manifestStream = openManifestStream())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader
/// </summary>
public interface IWorkloadManifestProvider
{
IEnumerable<(string manifestId, Func<Stream> openManifestStream)> GetManifests();
IEnumerable<(string manifestId, string? informationalPath, Func<Stream> openManifestStream)> GetManifests();

IEnumerable<string> GetManifestDirectories();

string GetSdkFeatureBand();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<WarningsAsErrors>true</WarningsAsErrors>
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>

<LangVersion>8.0</LangVersion>
<Nullable>Enable</Nullable>
<IsPackable>true</IsPackable>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ static int Last2DigitsTo0(int versionBuild)
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ public TempDirectoryWorkloadManifestProvider(string manifestsPath, string sdkVer
_sdkVersionBand = sdkVersion;
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader
/// </summary>
public class WorkloadManifest
{
internal WorkloadManifest(string id, FXVersion version, string? description, Dictionary<WorkloadId, BaseWorkloadDefinition> workloads, Dictionary<WorkloadPackId, WorkloadPack> packs, Dictionary<string, FXVersion>? dependsOnManifests)
internal WorkloadManifest(string id, FXVersion version, string? description, string? informationalPath, Dictionary<WorkloadId, BaseWorkloadDefinition> workloads, Dictionary<WorkloadPackId, WorkloadPack> packs, Dictionary<string, FXVersion>? dependsOnManifests)
{
Id = id;
ParsedVersion = version;
Description = description;
InformationalPath = informationalPath;
Workloads = workloads;
Packs = packs;
DependsOnManifests = dependsOnManifests;
Expand Down Expand Up @@ -44,6 +45,12 @@ internal WorkloadManifest(string id, FXVersion version, string? description, Dic

public string? Description { get; }

/// <summary>
/// A path that indicates where the manifest was loaded from, for diagnostic purposes only.
/// Not guaranteed to be set or to be a valid filesystem path.
/// </summary>
public string? InformationalPath { get; }

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

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

var reader = new Utf8JsonStreamReader(jsonReader);

return ReadWorkloadManifest(manifestId, ref reader);
return ReadWorkloadManifest(manifestId, informationalPath, ref reader);
}
// this is a compat wrapper so the source matches the system.text.json impl
private ref struct Utf8JsonStreamReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
public partial class WorkloadManifestReader
{
public static WorkloadManifest ReadWorkloadManifest(string manifestId, Stream manifestStream)
public static WorkloadManifest ReadWorkloadManifest(string manifestId, Stream manifestStream, string? informationalPath = null)
{
var readerOptions = new JsonReaderOptions
{
Expand All @@ -22,7 +22,7 @@ public static WorkloadManifest ReadWorkloadManifest(string manifestId, Stream ma

var reader = new Utf8JsonStreamReader(manifestStream, readerOptions);

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

private ref struct Utf8JsonStreamReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ 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(string id, ref Utf8JsonStreamReader reader)
private static WorkloadManifest ReadWorkloadManifest(string id, string? informationalPath, ref Utf8JsonStreamReader reader)
{
ConsumeToken(ref reader, JsonTokenType.StartObject);

Expand Down Expand Up @@ -155,6 +155,7 @@ private static WorkloadManifest ReadWorkloadManifest(string id, ref Utf8JsonStre
id,
version,
description,
informationalPath,
workloads ?? new Dictionary<WorkloadId, BaseWorkloadDefinition> (),
packs ?? new Dictionary<WorkloadPackId, WorkloadPack> (),
dependsOn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,11 @@ public void RefreshWorkloadManifests()

private void LoadManifestsFromProvider(IWorkloadManifestProvider manifestProvider)
{
foreach ((string manifestId, Func<Stream> openManifestStream) in manifestProvider.GetManifests())
foreach ((string manifestId, string? informationalPath, Func<Stream> openManifestStream) in manifestProvider.GetManifests())
{
using (var manifestStream = openManifestStream())
{
var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId, manifestStream);
if(!string.Equals (manifestId, manifest.Id, StringComparison.OrdinalIgnoreCase))
{
throw new WorkloadManifestCompositionException($"Manifest '{manifestId}' from provider {manifestProvider} does not match payload id '{manifest.Id}'");
}
var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId, manifestStream, informationalPath);
if (!_manifests.TryAdd(manifestId, manifest))
{
throw new WorkloadManifestCompositionException($"Duplicate manifest '{manifestId}' from provider {manifestProvider}");
Expand All @@ -124,12 +120,12 @@ private void ComposeWorkloadManifests()
{
if (FXVersion.Compare(dependency.Value, resolvedDependency.ParsedVersion) > 0)
{
throw new WorkloadManifestCompositionException($"Inconsistency in workload manifest '{manifest.Id}': requires '{dependency.Key}' version at least {dependency.Value} but found {resolvedDependency.Version}");
throw new WorkloadManifestCompositionException($"Inconsistency in workload manifest '{manifest.Id}' ({manifest.InformationalPath}): requires '{dependency.Key}' version at least {dependency.Value} but found {resolvedDependency.Version}");
}
}
else
{
throw new WorkloadManifestCompositionException($"Inconsistency in workload manifest '{manifest.Id}': missing dependency '{dependency.Key}'");
throw new WorkloadManifestCompositionException($"Inconsistency in workload manifest '{manifest.Id}' ({manifest.InformationalPath}): missing dependency '{dependency.Key}'");
}
}
}
Expand All @@ -145,7 +141,8 @@ private void ComposeWorkloadManifests()
{
if (!_workloads.TryAdd(workload.Key, ((WorkloadDefinition)workload.Value, manifest)))
{
throw new WorkloadManifestCompositionException($"Workload '{workload.Key}' in manifest '{manifest.Id}' conflicts with manifest '{_workloads[workload.Key].manifest.Id}'");
WorkloadManifest conflictingManifest = _workloads[workload.Key].manifest;
throw new WorkloadManifestCompositionException($"Workload '{workload.Key}' in manifest '{manifest.Id}' ({manifest.InformationalPath}) conflicts with manifest '{conflictingManifest.Id}' ({conflictingManifest.InformationalPath})");
}
}
}
Expand All @@ -161,7 +158,8 @@ private void ComposeWorkloadManifests()
{
if (!_workloads.TryAdd(redirect.Id, replacement))
{
throw new WorkloadManifestCompositionException($"Workload '{redirect.Id}' in manifest '{manifest.Id}' conflicts with manifest '{_workloads[redirect.Id].manifest.Id}'");
WorkloadManifest conflictingManifest = _workloads[redirect.Id].manifest;
throw new WorkloadManifestCompositionException($"Workload '{redirect.Id}' in manifest '{manifest.Id}' ({manifest.InformationalPath}) conflicts with manifest '{conflictingManifest.Id}' ({conflictingManifest.InformationalPath})");
}
return true;
}
Expand All @@ -178,7 +176,8 @@ private void ComposeWorkloadManifests()
{
if (!_packs.TryAdd(pack.Key, (pack.Value, manifest)))
{
throw new WorkloadManifestCompositionException($"Workload pack '{pack.Key}' in manifest '{manifest.Id}' conflicts with manifest '{_packs[pack.Key].manifest.Id}'");
WorkloadManifest conflictingManifest = _packs[pack.Key].manifest;
throw new WorkloadManifestCompositionException($"Workload pack '{pack.Key}' in manifest '{manifest.Id}' ({manifest.InformationalPath}) conflicts with manifest '{conflictingManifest.Id}' ({conflictingManifest.InformationalPath})");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@ public FakeManifestProvider(params string[] filePaths)
_filePaths = filePaths;
}

public IEnumerable<string> GetManifestDirectories() => throw new System.NotImplementedException();
public IEnumerable<string> GetManifestDirectories() => throw new NotImplementedException();

public IEnumerable<(string manifestId, Func<Stream> openManifestStream)> GetManifests()
public IEnumerable<(string manifestId, string? informationalPath, Func<Stream> openManifestStream)> GetManifests()
{
foreach (var filePath in _filePaths)
{
yield return (Path.GetFileNameWithoutExtension(filePath), () => new FileStream(filePath, FileMode.Open, FileAccess.Read));
yield return (Path.GetFileNameWithoutExtension(filePath), filePath,() => new FileStream(filePath, FileMode.Open, FileAccess.Read));
}
}

public string GetSdkFeatureBand() => throw new System.NotImplementedException();
public string GetSdkFeatureBand() => throw new NotImplementedException();
}

internal class InMemoryFakeManifestProvider : IWorkloadManifestProvider, IEnumerable<(string id, string content)>
{
readonly List<(string id, byte[] content)> _manifests = new List<(string, byte[])>();

public void Add(string id, string content) => _manifests.Add((id, Encoding.UTF8.GetBytes(content)));
public IEnumerable<string> GetManifestDirectories() => throw new System.NotImplementedException();
public IEnumerable<string> GetManifestDirectories() => throw new NotImplementedException();

public IEnumerable<(string manifestId, Func<Stream> openManifestStream)> GetManifests()
=> _manifests.Select(m => (m.id, (Func<Stream>)(() => new MemoryStream(m.content))));
public IEnumerable<(string manifestId, string? informationalPath, Func<Stream> openManifestStream)> GetManifests()
=> _manifests.Select(m => (m.id, (string?)null, (Func<Stream>)(() => new MemoryStream(m.content))));

// these are just so the collection initializer works
public IEnumerator<(string id, string content)> GetEnumerator() => throw new System.NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => throw new System.NotImplementedException();
public string GetSdkFeatureBand() => throw new System.NotImplementedException();
public IEnumerator<(string id, string content)> GetEnumerator() => throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
public string GetSdkFeatureBand() => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ string MakeManifest(string version, params (string id, string version)[] depends
{ "AAA", MakeManifest("20.0.0", ("BBB", "5.0.0"), ("CCC", "63.0.0"), ("DDD", "25.0.0")) }
};

var missingManifestEx = Assert.Throws<Exception>(() => WorkloadResolver.CreateForTests(missingManifestProvider, new[] { fakeRootPath }));
var missingManifestEx = Assert.Throws<WorkloadManifestCompositionException>(() => WorkloadResolver.CreateForTests(missingManifestProvider, new[] { fakeRootPath }));
Assert.Contains("missing dependency", missingManifestEx.Message);

var inconsistentManifestProvider = new InMemoryFakeManifestProvider
Expand All @@ -201,7 +201,7 @@ string MakeManifest(string version, params (string id, string version)[] depends
{ "DDD", MakeManifest("30.0.0") },
};

var inconsistentManifestEx = Assert.Throws<Exception>(() => WorkloadResolver.CreateForTests(inconsistentManifestProvider, new[] { fakeRootPath }));
var inconsistentManifestEx = Assert.Throws<WorkloadManifestCompositionException>(() => WorkloadResolver.CreateForTests(inconsistentManifestProvider, new[] { fakeRootPath }));
Assert.Contains("Inconsistency in workload manifest", inconsistentManifestEx.Message);
}

Expand Down
19 changes: 12 additions & 7 deletions src/Tests/dotnet-workload-install.Tests/MockManifestProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,31 @@ namespace ManifestReaderTests
{
internal class MockManifestProvider : IWorkloadManifestProvider
{
readonly string[] _filePaths;
readonly (string name, string path)[] _manifests;

public MockManifestProvider(params string[] filePaths)
public MockManifestProvider(params string[] manifestPaths)
{
_filePaths = filePaths;
_manifests = Array.ConvertAll(manifestPaths, mp => (mp, mp));
}

public MockManifestProvider(params (string name, string path)[] manifests)
{
_manifests = manifests;
}

public IEnumerable<string> GetManifestDirectories()
{
foreach (var filePath in _filePaths)
foreach ((_, var filePath) in _manifests)
{
yield return Path.GetDirectoryName(filePath);
}
}

public IEnumerable<(string manifestId, Func<Stream> openManifestStream)> GetManifests()
public IEnumerable<(string manifestId, string informationalPath, Func<Stream> openManifestStream)> GetManifests()
{
foreach (var filePath in _filePaths)
foreach ((var id, var path) in _manifests)
{
yield return (filePath, () => new FileStream(filePath, FileMode.Open, FileAccess.Read));
yield return (id, path, () => File.OpenRead(path));
}
}

Expand Down