Skip to content
Open
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
71 changes: 71 additions & 0 deletions documentation/general/dotnetup/installation-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# How to track what's installed in dotnetup

dotnetup should support installing various versions of the .NET SDK or runtime, as well as updating or uninstalling them. To do this, it will need to store information about what is installed in what we call the dotnetup shared manifest.

## Desired behavior

When a user installs a .NET SDK or runtime with dotnetup, we will call the information about what the requested to be installed the "Install Spec". This includes the component that should be installed as well as the version or channel that should be installed. An install spec may also be derived from a global.json file, in which case the spec should also include the path to the corresponding global.json.
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammatical error: "what the requested to be installed" should be "what they requested to be installed" or "what was requested to be installed".

Suggested change
When a user installs a .NET SDK or runtime with dotnetup, we will call the information about what the requested to be installed the "Install Spec". This includes the component that should be installed as well as the version or channel that should be installed. An install spec may also be derived from a global.json file, in which case the spec should also include the path to the corresponding global.json.
When a user installs a .NET SDK or runtime with dotnetup, we will call the information about what was requested to be installed the "Install Spec". This includes the component that should be installed as well as the version or channel that should be installed. An install spec may also be derived from a global.json file, in which case the spec should also include the path to the corresponding global.json.

Copilot uses AI. Check for mistakes.

The effect of an update operation should be to install the latest version of the SDK or runtimes that matches each active install spec. Any installations that are no longer needed by any active install specs would then be removed.

An uninstall operation would be implemented as deleting an install spec and then running a garbage collection. This may not actually delete or uninstall anything if there are other install specs that resolve to the same version. In that case dotnetup should display a message explaining what happened (and what specs still refer to that version) so that the user doesn't get confused about why the version is still installed. We might also want to offer some sort of force uninstall command.

## Dotnetup shared manifest contents

### Install specs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should likely provide a json schema for the manifest file as well.


- Component (SDK or one of the runtimes)
- Version, channel, or version range
- Source: explicit install command or global.json (could there be other sources in the future?)
- Global.json path
- Dotnet root

### Installation
- Component
- Version (this is the exact version that is installed)
- Dotnet root
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the advantage of recording dotnet root here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it now but remark below that I think we should remove this

- Subcomponents
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Architecture is also important here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I haven't figured out how we'll handle architecture. I think probably each dotnet root is architecture-specific, so maybe the architecture is stored or associated with the dotnet root.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we may also want to record the hive in which it was installed so we can properly support --directory installs. That could point to the global json path instead to reduce the amount of properties and indicate the hive is controlled by the global.json (either default hive if no sdks path specified or that path in the global.json)


### Subcomponent

Subcomponents are sub-pieces of an installation. We need to represent these because different installed components or versions may have overlapping subcomponents. So for each installation, we will keeep a list of subcomponents that are part of that installation.
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: "keeep" should be "keep".

Suggested change
Subcomponents are sub-pieces of an installation. We need to represent these because different installed components or versions may have overlapping subcomponents. So for each installation, we will keeep a list of subcomponents that are part of that installation.
Subcomponents are sub-pieces of an installation. We need to represent these because different installed components or versions may have overlapping subcomponents. So for each installation, we will keep a list of subcomponents that are part of that installation.

Copilot uses AI. Check for mistakes.

A subcomponent can be identified by a relative path to a folder from the dotnet root. The depth of the folder depends on the top-level subfolder under the dotnet root. For example:

- `sdk/10.0.102` - 2 levels
- `packs/Microsoft.AspNetCore.App.Ref/10.0.2` - 3 levels
- `sdk-manifests/10.0.100/microsoft.net.sdk.android/36.1.2` - 4 levels

## Implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A manifest version should also likely be included for compat / breaking changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Do we fail if the manifest doesn't exist? I think we don't. We can try to recreate the manifest from a hive but it will never be the same - not including custom directories nor the intent of a user (e.g. explicit runtime install request vs sdk install runtime)

I also wonder if we should keep backup copies of the manifests, say in the temp folder, in case something goes wrong so we can restore the manifest. Then we get to the interesting question of 'history' like workload history but we don't need to do that for now 😁 This is probably better


### Installing a component
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to note that all operations against the manifest shall require a lock, or we should think about the designing the manifest in a way that enables concurrency more effectively (e.g. last writer wins.)

There are many ways to design that lock:

  1. Simplest : Doing any installation at all requires holding the same lock
  2. Directory Specific Lock - the specific hive/directory determines the lock - multiple readers/writers to the same manifest but they are working in different directories.
  3. Install version specific lock - enable concurrency for numerous installs across process (or instead across 1 install)

The check for which installs exist should never try to operate without holding the lock as well, so another interesting conversation is whether we want to hold read/write specific locks. (e.g. read only lock checks existing installs, but we can't get the read only lock if the write lock is taken.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would start simply, by having an exclusive lock. I don't know if there's much user need for multiple installs happening at once, and that would make it a lot more complicated.

We may want a reader/writer lock though so that multiple windows can list what's installed at once. But maybe it's OK if we don't, the commands should execute quickly so it would be OK if they were serialized.


- Is there already a matching install spec in the shared manifest?
- If yes, then we may want to do an update on that install spec instead of an install
- Resolve the version of the component to install
- If that version is not already installed:
- Install that version. Subcomponents that are already installed don't need to be overwritten.
- Add installation record to shared manifest
- Installation record should include subcomponents based on the archive that was used
- Add install spec to shared manifest

### Updating a component

- Get the latest available version that matches the install spec
- If there's no installation matching that version:
- Install that version
- Run garbage collection to remove any installations that are no longer needed

### Deleting a component

- Remove corresponding install spec
- Run garbage collection
- If there's still an installation matching removed install spec, print message explaining why

### Garbage collection

- Go through all install specs in the manifest.
- For install specs that came from a global.json file, update the versions in them if the global.json file has changed. Delete those specs if the global.json file has been deleted (or no longer specifies a version).
- For each install spec, find the latest installation record that matches. Mark that installation record to keep for this garbage collection.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach to simplify the algorithm for deleting old versions!

- Delete all installation records from the manifest which weren't marked.
- Iterate through all components installed in the dotnet root. Remove any which are no longer listed under an installation record in the manifest.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If dotnet_root is set to a path including installs also managed by the user, this may cause us to delete those installs. I think we likely shouldn't do this step.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several related issues which I haven't really figured out or included in this design:

  • Should we include data for multiple dotnet roots in the same manifest or have a different manifest for each root? Having everything in one manifest would let us keep track of all installs centrally. But maybe it would be better to have the manifest in each dotnet root so that the paths can be relative and things don't break if you move the dotnet root folder around.
  • Should we support installing into folders with an installation that was not tracked by dotnetup? We probably wouldn't be able to handle upgrades and installations well. Some options:
    • Don't support installing into folders with existing non-dnup installs at all
    • Install, but only in an "untracked" mode which lays down the files but doesn't add them to a manifest and doesn't support update and uninstall.
    • Create a manifest and add information to it about all the existing files. Those files should never be deleted as part of garbage collection (unless the user chooses to delete all the pre-existing installs).