Skip to content

dotnet-sdk: add withWorkloads support#498033

Open
JamieMagee wants to merge 4 commits intoNixOS:masterfrom
JamieMagee:dotnet-workloads
Open

dotnet-sdk: add withWorkloads support#498033
JamieMagee wants to merge 4 commits intoNixOS:masterfrom
JamieMagee:dotnet-workloads

Conversation

@JamieMagee
Copy link
Member

@JamieMagee JamieMagee commented Mar 8, 2026

Add withWorkloads to the .NET SDK, so you can do:

dotnet-sdk_10.withWorkloads ["aspire" "wasm-tools"]

Workload packs are fetched from NuGet into a separate derivation. The SDK finds them via DOTNETSDK_WORKLOAD_PACK_ROOTS, set by a setup hook. Nothing gets merged into the SDK tree except metadata sentinels for dotnet workload list. Native ELF binaries in packs are patched with autoPatchelfHook.

Pack data (hashes, per-RID mappings) comes from the SDK's own bundled manifests, generated by a Python script and checked in as Nix files. Regenerate with:

./pkgs/development/compilers/dotnet/update-workloads.sh

The update script is also chained into passthru.updateScript via sequence, so it runs alongside SDK version updates.

Includes a wasm build test that verifies dotnet build works with the wasm-tools workload installed.

Supports .NET 8.0, 9.0, 10.0, and 11.0 across linux-x64, linux-arm64, osx-x64, and osx-arm64.

See #226107

Things done

  • Built on platform:
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • Tested, as applicable:
  • Ran nixpkgs-review on this PR. See nixpkgs-review usage.
  • Tested basic functionality of all binary files, usually in ./result/bin/.
  • Nixpkgs Release Notes
    • Package update: when the change is major or breaking.
  • NixOS Release Notes
    • Module addition: when adding a new NixOS module.
    • Module update: when the change is significant.
  • Fits CONTRIBUTING.md, pkgs/README.md, maintainers/README.md and other READMEs.

@nixpkgs-ci nixpkgs-ci bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux. 6.topic: dotnet Language: .NET labels Mar 8, 2026
@JamieMagee
Copy link
Member Author

nixpkgs-review result

Generated using nixpkgs-review-gha

Command: nixpkgs-review pr 498033
Commit: e31fcf03f256bb7670ba7ce03a062321ef4555ae (subsequent changes)
Merge: 08811f2931d63652a437f40c9a0cd4ee06dacbda

Logs: https://github.com/JamieMagee/nixpkgs-review-gha/actions/runs/22830917508


x86_64-linux

No rebuilds


aarch64-linux

No rebuilds

@JamieMagee JamieMagee force-pushed the dotnet-workloads branch 3 times, most recently from 8fa9333 to 02689b0 Compare March 11, 2026 03:59
@JamieMagee JamieMagee marked this pull request as ready for review March 11, 2026 16:32
@nixpkgs-ci nixpkgs-ci bot requested a review from corngood March 11, 2026 16:36
@JamieMagee JamieMagee requested a review from a team March 11, 2026 17:22
@anpin
Copy link
Contributor

anpin commented Mar 12, 2026

That's great @JamieMagee! I'll try to give it a spin next week.

@lostmsu
Copy link
Contributor

lostmsu commented Mar 12, 2026

A review from GPT-5.4

I tested this on Linux with .NET 10 and an actual net10.0-android smoke app, and I think there are still a couple of issues before this is ready.

  1. Major: sdk.withWorkloads workloads don't work with normal direct use of the wrapped SDK.

    The implementation builds a separate workloadPackRoot in with-workloads.nix, but the final SDK image only merges sdk.unwrapped and workloadMetadata in with-workloads.nix. The packs are then exposed only through the setup hook wiring in wrapper.nix and dotnet-workload-hook.sh.

    In practice that means the package works when it is injected into a Nix build environment that sources setup hooks, but not when the wrapped SDK is used directly.

    I built sdk_10_0.withWorkloads ["android"] from this branch and ran dotnet restore against a real net10.0-android smoke project. With only DOTNET_ROOT set, restore failed with NETSDK1147 even though dotnet workload list reported android as installed. The exact same restore, and a subsequent dotnet build, succeeded immediately once I manually exported:

    DOTNETSDK_WORKLOAD_PACK_ROOTS=/nix/store/...-dotnet-workload-packs-10.0/share/dotnet

    I also verified that the packaged Android llc binaries in that pack root run correctly on NixOS, so the pack contents themselves look fine. The problem is that the runtime wrapper never exposes them for normal interactive use.

  2. Minor: dotnet workload list prints a warning about workloads verification

    The metadata synthesis in with-workloads.nix appears to create only metadata/workloads/<band>/InstalledWorkloads/*. On the resulting SDK, dotnet workload list does show android, but it also prints:

    An issue was encountered verifying workloads. For more information, run "dotnet workload update".

  3. The test coverage would not catch issue 1 because mkDotnetTest injects the SDK through buildInputs in wrapper.nix, which means setup hooks are sourced, and the new workload-specific coverage is the wasm test added in wrapper.nix.

    That only exercises workload resolution inside a Nix build sandbox, not the direct sdk.withWorkloads invocation path that users will hit from nix shell, environment.systemPackages, or a plain $sdk/bin/dotnet.

@lostmsu
Copy link
Contributor

lostmsu commented Mar 14, 2026

Might also makes sense wrapping dotnet to disable workload commands except list because using any of them on a nixos-based environment will result in invalid state.

@JamieMagee
Copy link
Member Author

JamieMagee commented Mar 14, 2026

@lostmsu Thanks for testing this, really appreciate it. I pushed fixes:

  1. The wrapper now uses wrapProgram to set DOTNETSDK_WORKLOAD_PACK_ROOTS, so packs work from nix shell / environment.systemPackages without needing setup hooks. I also added workloadPackRoot to the buildEnv paths so packs are physically under $DOTNET_ROOT/packs/ too.

  2. Added echo "FileBased" > "$metaDir/InstallType" to the metadata derivation. Warning gone.

  3. Fair point about test coverage. The mkDotnetTest path sources setup hooks, which masked the issue. Open to ideas on how to best test the direct invocation path. Maybe a simple runCommand that calls the wrapper binary without buildInputs.

Re: wrapping dotnet workload install/update/restore agreed that's worth doing, but I'd rather tackle it separately.

@nixpkgs-ci nixpkgs-ci bot requested review from kuznero and mdarocha March 14, 2026 19:08
@nixpkgs-ci nixpkgs-ci bot added 10.rebuild-linux: 101-500 This PR causes between 101 and 500 packages to rebuild on Linux. 10.rebuild-darwin: 101-500 This PR causes between 101 and 500 packages to rebuild on Darwin. and removed 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux. labels Mar 14, 2026
@lostmsu
Copy link
Contributor

lostmsu commented Mar 14, 2026

with maui-android templates are not being picked up (e.g. dotnet new maui fails), but I think this can be punted to future updates. Restricting templates installation to Nix only makes sense when there's an easy way to add them. And I am not sure if there's a way to tell dotnet to read templates from multiple locations. GPT-5.4 thinks best option is to link/move/copy templates from packs/.Templates.* folder(s) to templates/<sdk-version>/.

Copy link
Contributor

@lostmsu lostmsu left a comment

Choose a reason for hiding this comment

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

Tested locally - works except templates. As mentioned in a comment that could be fixed separately.

@nixpkgs-ci nixpkgs-ci bot added the 12.approvals: 1 This PR was reviewed and approved by one person. label Mar 14, 2026
Add generate-workload-data.py, which builds the binary SDK for a given
.NET version, reads its bundled workload manifests, resolves packs per
RID, fetches NuGet hashes, and writes a Nix expression.

Add update-workloads.sh as a convenience wrapper that regenerates data
for all versions (derived from existing workloads/*.nix files, so no
hardcoded version list to maintain).
Output of generate-workload-data.py for each supported SDK version.
Each file contains SRI hashes for every NuGet pack and per-RID
workload-to-pack mappings (linux-x64, linux-arm64, osx-x64, osx-arm64).
@corngood
Copy link
Contributor

The generated workload nix stuff feels like it should be part of the existing generated expressions (versions/*.nix). Is there a reason we'd ever want to update them separately?

@JamieMagee
Copy link
Member Author

The generated workload nix stuff feels like it should be part of the existing generated expressions (versions/*.nix). Is there a reason we'd ever want to update them separately?

The two generators have different inputs, dependencies, and update cadences. update.sh curls the .NET release JSON with bash/jq. The workload generator nix-builds the SDK, parses its manifests, then fetches ~200 NuGet packages for hashes.

Merging them is possible but would mean the fast path (update.sh for a patch release) now also has to build an SDK and hit NuGet for hundreds of packages every time. Seemed easier to keep them decoupled.

@JamieMagee JamieMagee requested a review from corngood March 19, 2026 05:34
with-workloads.nix composes a .NET SDK with workload packs pre-installed.
Packs are fetched from NuGet into a separate derivation and made available
to the SDK via the DOTNETSDK_WORKLOAD_PACK_ROOTS environment variable
(set by dotnet-workload-hook.sh). This avoids merging packs into the SDK
tree entirely.

A small buildEnv overlay adds workload metadata (InstalledWorkloads
sentinels) so `dotnet workload list` reports them, and removes the
userlocal sentinel that would redirect pack lookups to ~/.dotnet.

Native ELF binaries in workload packs are patched via autoPatchelfHook.

Usage: dotnet-sdk.withWorkloads ["wasm-tools" "aspire"]
Add withWorkloads to dotnetCorePackages (takes an SDK and workload ID
list) and to each wrapped SDK's passthru (takes just the workload ID
list, capturing the SDK via finalAttrs).

When the unwrapped SDK has a workloadPackRoot in its passthru, the
wrapper sources dotnet-workload-hook.sh to set WORKLOAD_PACK_ROOTS.

Add a wasm test that builds a browser-wasm project when the wasm-tools
workload is installed, verifying the full workload pipeline.

Chain update.sh and update-workloads.sh via the sequence combinator
so workload data is regenerated alongside SDK version updates.
@anpin
Copy link
Contributor

anpin commented Mar 23, 2026

How to use this with dotnetCorePackages.combinePackages?

dotnet-combined = (with pkgs.dotnetCorePackages; 
      combinePackages [
        sdk_10_0-bin
        sdk_9_0-bin
        sdk_8_0-bin
      ]).withWorkloads [
        "maui"
        "maui-android"
        "maui-ios"
        "maui-maccatalyst"
        "wasm-tools"
        "aspire"
      ];

results in :

       error: No workload data for .NET combined. Supported: 8.0, 9.0, 10.0, 11.0.

EDIT: this seems to work

dotnet-combined = (with pkgs.dotnetCorePackages; 
      combinePackages [
        (sdk_10_0-bin.withWorkloads [
            "maui"
            "maui-mobile"
            "maui-android"
            "maui-ios"
            "maui-maccatalyst"
            "android"
            "ios" 
            "maccatalyst" 
            "macos"
          ])
        sdk_9_0-bin
        sdk_8_0-bin
      ]);

@JamieMagee
Copy link
Member Author

Yes, workloads are specific to the major SDK version, not shared across major versions.

@anpin
Copy link
Contributor

anpin commented Mar 23, 2026

@JamieMagee should it be working with the dotnetCorePackages.sdk_10_0-bin or only with dotnetCorePackages.sdk_10_0?

For -bin sdk workloads are visible in the dotnet workload list, but then fail to compile a demo app with no workload installed (or similar error)

Edit: running this on aarch64-darwin

> dotnet build -c Debug -f net9.0-ios 
  Determining projects to restore...
/nix/store/fb978fbhg5k3hwnyxl7g341qriq1v288-dotnet-sdk-9.0.312/share/dotnet/sdk/9.0.312/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.ImportWorkloads.targets(38,5): error NETSDK1147: To build this project, the following workloads must be installed: maui-android [/Users/a/app_repo/Mobile.App.csproj::TargetFramework=net9.0-android]
/nix/store/fb978fbhg5k3hwnyxl7g341qriq1v288-dotnet-sdk-9.0.312/share/dotnet/sdk/9.0.312/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.ImportWorkloads.targets(38,5): error NETSDK1147: To install these workloads, run the following command: dotnet workload restore [/Users/a/app_repo/Mobile.App.csproj::TargetFramework=net9.0-android]
> dotnet workload list                

Installed Workload Id      Manifest Version       Installation Source
---------------------------------------------------------------------
android                    35.0.105/9.0.100       SDK 9.0.300        
ios                        26.2.9000/9.0.100      SDK 9.0.300        
maccatalyst                26.2.9000/9.0.100      SDK 9.0.300        
macos                      26.2.9000/9.0.100      SDK 9.0.300        
maui                       9.0.120/9.0.100        SDK 9.0.300        
maui-android               9.0.120/9.0.100        SDK 9.0.300        
maui-ios                   9.0.120/9.0.100        SDK 9.0.300        
maui-maccatalyst           9.0.120/9.0.100        SDK 9.0.300        
maui-mobile                9.0.120/9.0.100        SDK 9.0.300        

Use `dotnet workload search` to find additional workloads to install.

> dotnet --info        
.NET SDK:
 Version:           9.0.312
 Commit:            c45411d3fd
 Workload version:  9.0.300-manifests.93c62a5d
 MSBuild version:   17.14.43+2a0eb78b3

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  26.2
 OS Platform: Darwin
 RID:         osx-arm64
 Base Path:   /nix/store/fb978fbhg5k3hwnyxl7g341qriq1v288-dotnet-sdk-9.0.312/share/dotnet/sdk/9.0.312/

.NET workloads installed:
 [macos]
   Installation Source: SDK 9.0.300
   Manifest Version:    26.2.9000/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.macos/26.2.9000/WorkloadManifest.json
   Install Type:        FileBased

 [maui-maccatalyst]
   Installation Source: SDK 9.0.300
   Manifest Version:    9.0.120/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.maui/9.0.120/WorkloadManifest.json
   Install Type:        FileBased

 [maui-ios]
   Installation Source: SDK 9.0.300
   Manifest Version:    9.0.120/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.maui/9.0.120/WorkloadManifest.json
   Install Type:        FileBased

 [maui-android]
   Installation Source: SDK 9.0.300
   Manifest Version:    9.0.120/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.maui/9.0.120/WorkloadManifest.json
   Install Type:        FileBased

 [ios]
   Installation Source: SDK 9.0.300
   Manifest Version:    26.2.9000/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.ios/26.2.9000/WorkloadManifest.json
   Install Type:        FileBased

 [maui-mobile]
   Installation Source: SDK 9.0.300
   Manifest Version:    9.0.120/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.maui/9.0.120/WorkloadManifest.json
   Install Type:        FileBased

 [maccatalyst]
   Installation Source: SDK 9.0.300
   Manifest Version:    26.2.9000/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.maccatalyst/26.2.9000/WorkloadManifest.json
   Install Type:        FileBased

 [maui]
   Installation Source: SDK 9.0.300
   Manifest Version:    9.0.120/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.maui/9.0.120/WorkloadManifest.json
   Install Type:        FileBased

 [android]
   Installation Source: SDK 9.0.300
   Manifest Version:    35.0.105/9.0.100
   Manifest Path:       /nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk-manifests/9.0.100/microsoft.net.sdk.android/35.0.105/WorkloadManifest.json
   Install Type:        FileBased

Configured to use loose manifests when installing new manifests.

Host:
  Version:      9.0.14
  Architecture: arm64
  Commit:       19c07820cb

.NET SDKs installed:
  9.0.312 [/nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 9.0.14 [/nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 9.0.14 [/nix/store/z16n8s7fpfc9j5m0c2kydskrh6jiybck-dotnet-sdk-with-workloads-9.0.312/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  DOTNET_ROOT       [/nix/store/f27i8zs2c1z5nd6brhd2fw8mixlzs1qy-dotnet-sdk-wrapped-9.0.312]

global.json file:
  /Users/a/app_repo/global.json

Learn more:
  https://aka.ms/dotnet/info

Download .NET:

EDIT_2: same with the source built sdk

@JamieMagee
Copy link
Member Author

JamieMagee commented Mar 23, 2026

@anpin What's the error you're seeing? Most workloads will need the corresponding native tooling available too i.e ios/macos/maccatalyst need xcode installed

EDIT: You might also be running into this issue #464575

@anpin
Copy link
Contributor

anpin commented Mar 24, 2026

What's the error you're seeing?

As seen above:

/nix/store/fb978fbhg5k3hwnyxl7g341qriq1v288-dotnet-sdk-9.0.312/share/dotnet/sdk/9.0.312/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.ImportWorkloads.targets(38,5): error NETSDK1147: To build this project, the following workloads must be installed: maui-android [/Users/a/app_repo/Mobile.App.csproj::TargetFramework=net9.0-android]
/nix/store/fb978fbhg5k3hwnyxl7g341qriq1v288-dotnet-sdk-9.0.312/share/dotnet/sdk/9.0.312/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.ImportWorkloads.targets(38,5): error NETSDK1147: To install these workloads, run the following command: dotnet workload restore [/Users/a/app_repo/Mobile.App.csproj::TargetFramework=net9.0-android]

Same error appears for both *-bin and source built sdks, with and without combinePackages. If in the target project net9.0-android is commented out with only net9.0-ios left the error is the same, but for ios workloads. I do have global.json set to the 9.0.312 and it matches with installed sdk in the above log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: dotnet Language: .NET 10.rebuild-darwin: 101-500 This PR causes between 101 and 500 packages to rebuild on Darwin. 10.rebuild-linux: 101-500 This PR causes between 101 and 500 packages to rebuild on Linux. 12.approvals: 1 This PR was reviewed and approved by one person.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants