Skip to content
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
9 changes: 8 additions & 1 deletion docs/uninstall.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ dnvm uninstall <version> [--dir <directory>]

## How It Works

The uninstall command removes the SDK and checks which runtime components (runtimes, ASP.NET, templates, etc.) are still needed by other installed SDKs. Only components that are no longer needed are removed.
The uninstall command removes the SDK and checks which shared components are still needed by other installed SDKs. Only components that are no longer needed are removed. This includes:

- Shared frameworks (`Microsoft.NETCore.App`, `Microsoft.AspNetCore.App`, `Microsoft.WindowsDesktop.App`)
- Reference and host packs under `packs/` (`Microsoft.NETCore.App.Ref`, `Microsoft.AspNetCore.App.Ref`, `Microsoft.NETCore.App.Host.<rid>`, `Microsoft.WindowsDesktop.App.Ref`)
- Host `fxr`, templates, and the SDK itself
- Workload manifests under `sdk-manifests/<feature-band>` (removed only when no other installed SDK contributed that directory and no workload installation metadata references it). A single SDK archive can lay down manifests under several feature bands, so dnvm records the set of directories each install contributed. SDKs installed by dnvm versions older than this feature have no recorded set, and their presence disables `sdk-manifests` cleanup for that SDK directory entirely.

Workload packs installed via `dotnet workload install` (for example the iOS, Android, and MacCatalyst packs) are not tracked by dnvm and are not removed. Use `dotnet workload clean` to reclaim that space.

## Examples

Expand Down
47 changes: 29 additions & 18 deletions src/dnvm/InstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Serde.Json;
using Spectre.Console;
using StaticCs;
using StaticCs.Collections;
using Zio;
using Zio.FileSystems;

Expand Down Expand Up @@ -323,6 +324,7 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(

env.Console.WriteLine($"Downloading SDK {sdkVersion} for {ridString}");
var curMuxerVersion = manifest.MuxerVersion(sdkDir);
var sdkManifestBands = new HashSet<string>(StringComparer.Ordinal);
var err = await InstallSdkToDir(
curMuxerVersion,
release.Runtime.Version,
Expand All @@ -332,7 +334,8 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
env.DnvmHomeFs,
sdkInstallPath,
env.TempFs,
logger);
logger,
sdkManifestBands);
if (err is not null)
{
return err;
Expand All @@ -343,22 +346,28 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
var result = JsonSerializer.Serialize(manifest.ConvertToLatest());
logger.Log("Existing manifest: " + result);

if (!manifest.IsSdkInstalled(sdkVersion, sdkDir))
var installedSdk = new InstalledSdk
{
manifest = manifest with
{
InstalledSdks = manifest.InstalledSdks.Add(new InstalledSdk
{
ReleaseVersion = release.ReleaseVersion,
RuntimeVersion = release.Runtime.Version,
AspNetVersion = release.AspNetCore.Version,
SdkVersion = sdkVersion,
SdkDirName = sdkDir,
})
};

await @lock.WriteManifest(env, manifest);
}
ReleaseVersion = release.ReleaseVersion,
RuntimeVersion = release.Runtime.Version,
AspNetVersion = release.AspNetCore.Version,
WindowsDesktopVersion = release.WindowsDesktop.Version,
SdkManifestBands = sdkManifestBands.OrderBy(band => band, StringComparer.Ordinal).ToEq(),
SdkManifestBandsKnown = true,
SdkVersion = sdkVersion,
SdkDirName = sdkDir,
};

var existingSdk = manifest.InstalledSdks.FirstOrDefault(
sdk => sdk.SdkVersion == sdkVersion && sdk.SdkDirName == sdkDir);
manifest = manifest with
{
InstalledSdks = existingSdk is null
? manifest.InstalledSdks.Add(installedSdk)
: manifest.InstalledSdks.Replace(existingSdk, installedSdk)
};

await @lock.WriteManifest(env, manifest);

return manifest;
}
Expand All @@ -372,7 +381,8 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
IFileSystem destFs,
UPath destPath,
IFileSystem tempFs,
Logger logger)
Logger logger,
ISet<string>? sdkManifestBands = null)
{
// The Release name does not contain a version
string archiveName = ConstructArchiveName(versionString: null, Utilities.CurrentRID, Utilities.ZipSuffix);
Expand Down Expand Up @@ -403,7 +413,8 @@ internal static async Task<Result<Manifest, InstallError>> InstallSdk(
archivePath,
tempFs,
destFs,
destPath);
destPath,
sdkManifestBands);
File.Delete(archivePath);
if (extractResult != null)
{
Expand Down
16 changes: 15 additions & 1 deletion src/dnvm/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ public partial record InstalledSdk
public required SemVersion SdkVersion { get; init; }
public required SemVersion RuntimeVersion { get; init; }
public required SemVersion AspNetVersion { get; init; }
public required SemVersion? WindowsDesktopVersion { get; init; }

/// <summary>
/// The <c>sdk-manifests/&lt;feature-band&gt;</c> directories contained in this SDK's archive.
/// A single archive can lay down in-box workload manifests under multiple feature bands
/// (mobile/MAUI manifests routinely lag the SDK's own band), so the SDK's own feature band
/// is not sufficient to determine which manifest directories it contributed.
/// <see cref="SdkManifestBandsKnown"/> distinguishes a known-empty archive from a legacy
/// SDK whose manifest-band ownership could not be determined.
/// </summary>
public EqArray<string> SdkManifestBands { get; init; } = EqArray<string>.Empty;
public bool SdkManifestBandsKnown { get; init; } = false;

public SdkDirName SdkDirName { get; init; } = DnvmEnv.DefaultSdkDirName;
}
Expand Down Expand Up @@ -88,7 +100,10 @@ public Manifest AddSdk(
SdkVersion = semVersion,
RuntimeVersion = semVersion,
AspNetVersion = semVersion,
WindowsDesktopVersion = semVersion,
ReleaseVersion = semVersion,
SdkManifestBands = EqArray.Create(semVersion.ToFeatureBand()),
SdkManifestBandsKnown = true,
};
return AddSdk(installedSdk, c);
}
Expand Down Expand Up @@ -322,4 +337,3 @@ internal static class ManifestLockingConfig
/// </summary>
public static TimeSpan BaseRetryDelay { get; set; } = TimeSpan.FromMilliseconds(50);
}

53 changes: 30 additions & 23 deletions src/dnvm/ManifestSchema/ManifestSerialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ private sealed partial class ManifestVersionOnly
public static string Serialize(Manifest manifest)
{
// Convert to the latest version before serializing
var manifestV9 = manifest.ConvertToLatest();
return JsonSerializer.Serialize(manifestV9);
var manifestV10 = manifest.ConvertToLatest();
return JsonSerializer.Serialize(manifestV10);
}

/// <summary>
Expand All @@ -35,11 +35,12 @@ public static async Task<Manifest> DeserializeNewOrOldManifest(
// Handle versions that don't need the release index to convert
Manifest? manifest = version switch
{
ManifestV5.VersionField => JsonSerializer.Deserialize<ManifestV5>(manifestSrc).Convert().Convert().Convert().Convert().Convert(),
ManifestV6.VersionField => JsonSerializer.Deserialize<ManifestV6>(manifestSrc).Convert().Convert().Convert().Convert(),
ManifestV7.VersionField => JsonSerializer.Deserialize<ManifestV7>(manifestSrc).Convert().Convert().Convert(),
ManifestV8.VersionField => JsonSerializer.Deserialize<ManifestV8>(manifestSrc).Convert().Convert(),
ManifestV9.VersionField => JsonSerializer.Deserialize<ManifestV9>(manifestSrc).Convert(),
ManifestV5.VersionField => JsonSerializer.Deserialize<ManifestV5>(manifestSrc).Convert().Convert().Convert().Convert().Convert().Convert(),
ManifestV6.VersionField => JsonSerializer.Deserialize<ManifestV6>(manifestSrc).Convert().Convert().Convert().Convert().Convert(),
ManifestV7.VersionField => JsonSerializer.Deserialize<ManifestV7>(manifestSrc).Convert().Convert().Convert().Convert(),
ManifestV8.VersionField => JsonSerializer.Deserialize<ManifestV8>(manifestSrc).Convert().Convert().Convert(),
ManifestV9.VersionField => JsonSerializer.Deserialize<ManifestV9>(manifestSrc).Convert().Convert(),
ManifestV10.VersionField => JsonSerializer.Deserialize<ManifestV10>(manifestSrc).Convert(),
_ => null
};
if (manifest is not null)
Expand All @@ -54,33 +55,36 @@ public static async Task<Manifest> DeserializeNewOrOldManifest(
// The first version didn't have a version field
null => throw new InvalidDataException("Manifest is invalid: missing version field"),
ManifestV2.VersionField => (await JsonSerializer.Deserialize<ManifestV2>(manifestSrc)
.Convert().Convert().Convert(httpClient, releasesIndex)).Convert().Convert().Convert().Convert().Convert(),
.Convert().Convert().Convert(httpClient, releasesIndex)).Convert().Convert().Convert().Convert().Convert().Convert(),
ManifestV3.VersionField => (await JsonSerializer.Deserialize<ManifestV3>(manifestSrc)
.Convert().Convert(httpClient, releasesIndex)).Convert().Convert().Convert().Convert().Convert(),
.Convert().Convert(httpClient, releasesIndex)).Convert().Convert().Convert().Convert().Convert().Convert(),
ManifestV4.VersionField => (await JsonSerializer.Deserialize<ManifestV4>(manifestSrc)
.Convert(httpClient, releasesIndex)).Convert().Convert().Convert().Convert().Convert(),
.Convert(httpClient, releasesIndex)).Convert().Convert().Convert().Convert().Convert().Convert(),
_ => throw new InvalidDataException("Unknown manifest version: " + version)
};
}
}

public static class ManifestConvert
{
public static Manifest Convert(this ManifestV9 manifestV9)
public static Manifest Convert(this ManifestV10 manifestV10)
{
return new Manifest
{
PreviewsEnabled = manifestV9.PreviewsEnabled,
CurrentSdkDir = manifestV9.CurrentSdkDir.Convert(),
InstalledSdks = manifestV9.InstalledSdks.SelectAsArray(sdk => new InstalledSdk
PreviewsEnabled = manifestV10.PreviewsEnabled,
CurrentSdkDir = manifestV10.CurrentSdkDir.Convert(),
InstalledSdks = manifestV10.InstalledSdks.SelectAsArray(sdk => new InstalledSdk
{
ReleaseVersion = sdk.ReleaseVersion,
SdkVersion = sdk.SdkVersion,
RuntimeVersion = sdk.RuntimeVersion,
AspNetVersion = sdk.AspNetVersion,
WindowsDesktopVersion = sdk.WindowsDesktopVersion,
SdkManifestBands = sdk.SdkManifestBands,
SdkManifestBandsKnown = sdk.SdkManifestBandsKnown,
SdkDirName = sdk.SdkDirName.Convert()
}),
RegisteredChannels = manifestV9.RegisteredChannels.SelectAsArray(channel => new RegisteredChannel
RegisteredChannels = manifestV10.RegisteredChannels.SelectAsArray(channel => new RegisteredChannel
{
ChannelName = channel.ChannelName,
SdkDirName = channel.SdkDirName.Convert(),
Expand All @@ -90,21 +94,24 @@ public static Manifest Convert(this ManifestV9 manifestV9)
};
}

internal static ManifestV9 ConvertToLatest(this Manifest @this)
internal static ManifestV10 ConvertToLatest(this Manifest @this)
{
return new ManifestV9
return new ManifestV10
{
PreviewsEnabled = @this.PreviewsEnabled,
CurrentSdkDir = @this.CurrentSdkDir.ConvertToLatest(),
InstalledSdks = @this.InstalledSdks.SelectAsArray(sdk => new InstalledSdkV9
InstalledSdks = @this.InstalledSdks.SelectAsArray(sdk => new InstalledSdkV10
{
ReleaseVersion = sdk.ReleaseVersion,
SdkVersion = sdk.SdkVersion,
RuntimeVersion = sdk.RuntimeVersion,
AspNetVersion = sdk.AspNetVersion,
WindowsDesktopVersion = sdk.WindowsDesktopVersion,
SdkManifestBands = sdk.SdkManifestBands,
SdkManifestBandsKnown = sdk.SdkManifestBandsKnown,
SdkDirName = sdk.SdkDirName.ConvertToLatest()
}),
RegisteredChannels = @this.RegisteredChannels.SelectAsArray(channel => new RegisteredChannelV9
RegisteredChannels = @this.RegisteredChannels.SelectAsArray(channel => new RegisteredChannelV10
{
ChannelName = channel.ChannelName,
SdkDirName = channel.SdkDirName.ConvertToLatest(),
Expand All @@ -117,12 +124,12 @@ internal static ManifestV9 ConvertToLatest(this Manifest @this)

public static class SdkDirNameConvert
{
public static SdkDirName Convert(this SdkDirNameV9 sdkDirNameV9)
public static SdkDirName Convert(this SdkDirNameV10 sdkDirNameV10)
{
return new SdkDirName(sdkDirNameV9.Name);
return new SdkDirName(sdkDirNameV10.Name);
}
public static SdkDirNameV9 ConvertToLatest(this SdkDirName sdkDirName)
public static SdkDirNameV10 ConvertToLatest(this SdkDirName sdkDirName)
{
return new SdkDirNameV9(sdkDirName.Name);
return new SdkDirNameV10(sdkDirName.Name);
}
}
88 changes: 88 additions & 0 deletions src/dnvm/ManifestSchema/ManifestV10.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Linq;
using Semver;
using Serde;
using StaticCs.Collections;

namespace Dnvm;

[GenerateSerde(As = typeof(string))]
public sealed partial record SdkDirNameV10(string Name)
{
public string Name { get; init; } = Name.ToLower();

public static implicit operator string(SdkDirNameV10 dirName) => dirName.Name;
public static implicit operator SdkDirNameV10(string name) => new(name);
public static implicit operator SdkDirNameV10(SdkDirNameV9 dirName) => new(dirName.Name);
}

[GenerateSerde]
public sealed partial record ManifestV10
{
public const int VersionField = 10;

[SerdeMemberOptions(SkipDeserialize = true)]
public int Version => VersionField;

public required bool PreviewsEnabled { get; init; }
public required SdkDirNameV10 CurrentSdkDir { get; init; }
public required EqArray<InstalledSdkV10> InstalledSdks { get; init; }
public required EqArray<RegisteredChannelV10> RegisteredChannels { get; init; }
}

[GenerateSerde]
[UseProxy(ForType = typeof(SemVersion), Proxy = typeof(SemVersionProxy))]
public partial record RegisteredChannelV10
{
public required Channel ChannelName { get; init; }
public required SdkDirNameV10 SdkDirName { get; init; }
public EqArray<SemVersion> InstalledSdkVersions { get; init; } = EqArray<SemVersion>.Empty;
public bool Untracked { get; init; } = false;
}

[GenerateSerde]
[UseProxy(ForType = typeof(SemVersion), Proxy = typeof(SemVersionProxy))]
public partial record InstalledSdkV10
{
public required SemVersion ReleaseVersion { get; init; }
public required SemVersion SdkVersion { get; init; }
public required SemVersion RuntimeVersion { get; init; }
public required SemVersion AspNetVersion { get; init; }
[SerdeMemberOptions(
SerializeProxy = typeof(NullableRefProxy.Ser<SemVersion, SemVersionProxy>),
DeserializeProxy = typeof(NullableRefProxy.De<SemVersion, SemVersionProxy>))]
public required SemVersion? WindowsDesktopVersion { get; init; }
public EqArray<string> SdkManifestBands { get; init; } = EqArray<string>.Empty;
public bool SdkManifestBandsKnown { get; init; } = false;
public required SdkDirNameV10 SdkDirName { get; init; }
}

public static partial class ManifestV10Convert
{
public static ManifestV10 Convert(this ManifestV9 v9) => new()
{
PreviewsEnabled = v9.PreviewsEnabled,
CurrentSdkDir = v9.CurrentSdkDir,
InstalledSdks = v9.InstalledSdks.SelectAsArray(v => v.Convert()),
RegisteredChannels = v9.RegisteredChannels.SelectAsArray(c => c.Convert())
};

public static InstalledSdkV10 Convert(this InstalledSdkV9 v9) => new()
{
ReleaseVersion = v9.ReleaseVersion,
SdkVersion = v9.SdkVersion,
RuntimeVersion = v9.RuntimeVersion,
AspNetVersion = v9.AspNetVersion,
WindowsDesktopVersion = null,
SdkManifestBandsKnown = false,
SdkDirName = v9.SdkDirName,
};

public static RegisteredChannelV10 Convert(this RegisteredChannelV9 v9) => new()
{
ChannelName = v9.ChannelName,
SdkDirName = v9.SdkDirName,
InstalledSdkVersions = v9.InstalledSdkVersions,
Untracked = v9.Untracked,
};
}
Loading
Loading