dotnet / dotnet/sdk

dotnetup install can accumulate duplicate tracking records when a global.json changes

Open
#56,225 1 comment 0 reactions 1 assignee Claimed by @marcpopMSFT View on GitHub
Area-dotnetup Bug
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

### Description

`dotnetup install` can add multiple install specifications for the same `global.json` when the SDK requirement in that file changes.

GlobalJson-sourced specifications are currently deduplicated using all of the following fields within a `DotnetRootEntry`:

- component
- version/channel
- install source
- `GlobalJsonPath`

Because the derived version/channel participates in identity, running `dotnetup install` again after the same `global.json` changes adds another specification instead of updating the specification associated with that file.

The durable identity of a GlobalJson specification should be the dotnet root, architecture, component, and normalized `GlobalJsonPath`. The derived SDK requirement is mutable state associated with that identity.

### User impact

This can result in multiple historical records for one `global.json`. Garbage collection may later refresh all of them to the file's current value, causing several visually identical tracked channels and requiring repeated uninstall commands.

This is a likely underlying cause of the behavior reported in #55312, where uninstalling one requirement caused an apparently unrelated SDK to be collected.

### Proposed behavior

When recording a GlobalJson SDK specification:

1. Find an existing record in the same root and architecture with the same component and normalized `GlobalJsonPath`.
2. Update that record's derived requirement instead of appending another record.
3. Consolidate legacy duplicate records when reading or reconciling the manifest.
4. Preserve independent records for different GlobalJson paths, roots, architectures, or components.

Deduplication should be source-specific:

- Explicit specifications: component plus version/channel.
- GlobalJson specifications: component plus normalized `GlobalJsonPath`.

### Change detection

We can hash the global.json content and store that locally as part of the manifest entry and rehash it to check if we need to update/replace perhaps. the hash is the full global.json file and is meant to give us future potential ways to check file diffs in the future, e.g. file modification timestamps which would be more performant but more prone to error.

We should also explicitly guard against duplicate entries in the dotnetup manifest at the manifest class and specification level itself - only allow one global json tracking node - deduplicate against not the version for global json entries but only the global.json. Every record records version, global json source, and global json path.

Decoupling manifest know how from the manifest class vs install or version resolution logic is essential.

Timestamp/content-hash optimization is already tracked separately by #53395.

### Acceptance criteria

- Repeated installation from an unchanged `global.json` leaves one logical tracking record.
- Installing after that file's SDK version or roll-forward policy changes updates the existing record.
- Existing duplicate records for the same root, architecture, component, and normalized path are consolidated.
- Records belonging to different GlobalJson files remain independent.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.