Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1# Dotnet {#dotnet} 2 3## Local Development Workflow {#local-development-workflow} 4 5For local development, it's recommended to use nix-shell to create a dotnet environment: 6 7```nix 8# shell.nix 9with import <nixpkgs> {}; 10 11mkShell { 12 name = "dotnet-env"; 13 packages = [ 14 dotnet-sdk 15 ]; 16} 17``` 18 19### Using many sdks in a workflow {#using-many-sdks-in-a-workflow} 20 21It's very likely that more than one sdk will be needed on a given project. Dotnet provides several different frameworks (E.g dotnetcore, aspnetcore, etc.) as well as many versions for a given framework. Normally, dotnet is able to fetch a framework and install it relative to the executable. However, this would mean writing to the nix store in nixpkgs, which is read-only. To support the many-sdk use case, one can compose an environment using `dotnetCorePackages.combinePackages`: 22 23```nix 24with import <nixpkgs> {}; 25 26mkShell { 27 name = "dotnet-env"; 28 packages = [ 29 (with dotnetCorePackages; combinePackages [ 30 sdk_6_0 31 sdk_7_0 32 ]) 33 ]; 34} 35``` 36 37This will produce a dotnet installation that has the dotnet 6.0 7.0 sdk. The first sdk listed will have it's cli utility present in the resulting environment. Example info output: 38 39```ShellSession 40$ dotnet --info 41.NET SDK: 42 Version: 7.0.202 43 Commit: 6c74320bc3 44 45Środowisko uruchomieniowe: 46 OS Name: nixos 47 OS Version: 23.05 48 OS Platform: Linux 49 RID: linux-x64 50 Base Path: /nix/store/n2pm44xq20hz7ybsasgmd7p3yh31gnh4-dotnet-sdk-7.0.202/sdk/7.0.202/ 51 52Host: 53 Version: 7.0.4 54 Architecture: x64 55 Commit: 0a396acafe 56 57.NET SDKs installed: 58 6.0.407 [/nix/store/3b19303vwrhv0xxz1hg355c7f2hgxxgd-dotnet-core-combined/sdk] 59 7.0.202 [/nix/store/3b19303vwrhv0xxz1hg355c7f2hgxxgd-dotnet-core-combined/sdk] 60 61.NET runtimes installed: 62 Microsoft.AspNetCore.App 6.0.15 [/nix/store/3b19303vwrhv0xxz1hg355c7f2hgxxgd-dotnet-core-combined/shared/Microsoft.AspNetCore.App] 63 Microsoft.AspNetCore.App 7.0.4 [/nix/store/3b19303vwrhv0xxz1hg355c7f2hgxxgd-dotnet-core-combined/shared/Microsoft.AspNetCore.App] 64 Microsoft.NETCore.App 6.0.15 [/nix/store/3b19303vwrhv0xxz1hg355c7f2hgxxgd-dotnet-core-combined/shared/Microsoft.NETCore.App] 65 Microsoft.NETCore.App 7.0.4 [/nix/store/3b19303vwrhv0xxz1hg355c7f2hgxxgd-dotnet-core-combined/shared/Microsoft.NETCore.App] 66 67Other architectures found: 68 None 69 70Environment variables: 71 Not set 72 73global.json file: 74 Not found 75 76Learn more: 77 https://aka.ms/dotnet/info 78 79Download .NET: 80 https://aka.ms/dotnet/download 81``` 82 83## dotnet-sdk vs dotnetCorePackages.sdk {#dotnet-sdk-vs-dotnetcorepackages.sdk} 84 85The `dotnetCorePackages.sdk_X_Y` is preferred over the old dotnet-sdk as both major and minor version are very important for a dotnet environment. If a given minor version isn't present (or was changed), then this will likely break your ability to build a project. 86 87## dotnetCorePackages.sdk vs dotnetCorePackages.runtime vs dotnetCorePackages.aspnetcore {#dotnetcorepackages.sdk-vs-dotnetcorepackages.runtime-vs-dotnetcorepackages.aspnetcore} 88 89The `dotnetCorePackages.sdk` contains both a runtime and the full sdk of a given version. The `runtime` and `aspnetcore` packages are meant to serve as minimal runtimes to deploy alongside already built applications. 90 91## Packaging a Dotnet Application {#packaging-a-dotnet-application} 92 93To package Dotnet applications, you can use `buildDotnetModule`. This has similar arguments to `stdenv.mkDerivation`, with the following additions: 94 95* `projectFile` is used for specifying the dotnet project file, relative to the source root. These have `.sln` (entire solution) or `.csproj` (single project) file extensions. This can be a list of multiple projects as well. When omitted, will attempt to find and build the solution (`.sln`). If running into problems, make sure to set it to a file (or a list of files) with the `.csproj` extension - building applications as entire solutions is not fully supported by the .NET CLI. 96* `nugetDeps` takes either a path to a `deps.nix` file, or a derivation. The `deps.nix` file can be generated using the script attached to `passthru.fetch-deps`. This file can also be generated manually using `nuget-to-nix` tool, which is available in nixpkgs. If the argument is a derivation, it will be used directly and assume it has the same output as `mkNugetDeps`. 97* `packNupkg` is used to pack project as a `nupkg`, and installs it to `$out/share`. If set to `true`, the derivation can be used as a dependency for another dotnet project by adding it to `projectReferences`. 98* `projectReferences` can be used to resolve `ProjectReference` project items. Referenced projects can be packed with `buildDotnetModule` by setting the `packNupkg = true` attribute and passing a list of derivations to `projectReferences`. Since we are sharing referenced projects as NuGets they must be added to csproj/fsproj files as `PackageReference` as well. 99 For example, your project has a local dependency: 100 ```xml 101 <ProjectReference Include="../foo/bar.fsproj" /> 102 ``` 103 To enable discovery through `projectReferences` you would need to add: 104 ```xml 105 <ProjectReference Include="../foo/bar.fsproj" /> 106 <PackageReference Include="bar" Version="*" Condition=" '$(ContinuousIntegrationBuild)'=='true' "/> 107 ``` 108* `executables` is used to specify which executables get wrapped to `$out/bin`, relative to `$out/lib/$pname`. If this is unset, all executables generated will get installed. If you do not want to install any, set this to `[]`. This gets done in the `preFixup` phase. 109* `runtimeDeps` is used to wrap libraries into `LD_LIBRARY_PATH`. This is how dotnet usually handles runtime dependencies. 110* `buildType` is used to change the type of build. Possible values are `Release`, `Debug`, etc. By default, this is set to `Release`. 111* `selfContainedBuild` allows to enable the [self-contained](https://docs.microsoft.com/en-us/dotnet/core/deploying/#publish-self-contained) build flag. By default, it is set to false and generated applications have a dependency on the selected dotnet runtime. If enabled, the dotnet runtime is bundled into the executable and the built app has no dependency on .NET. 112* `useAppHost` will enable creation of a binary executable that runs the .NET application using the specified root. More info in [Microsoft docs](https://learn.microsoft.com/en-us/dotnet/core/deploying/#publish-framework-dependent). Enabled by default. 113* `useDotnetFromEnv` will change the binary wrapper so that it uses the .NET from the environment. The runtime specified by `dotnet-runtime` is given as a fallback in case no .NET is installed in the user's environment. This is most useful for .NET global tools and LSP servers, which often extend the .NET CLI and their runtime should match the users' .NET runtime. 114* `dotnet-sdk` is useful in cases where you need to change what dotnet SDK is being used. You can also set this to the result of `dotnetSdkPackages.combinePackages`, if the project uses multiple SDKs to build. 115* `dotnet-runtime` is useful in cases where you need to change what dotnet runtime is being used. This can be either a regular dotnet runtime, or an aspnetcore. 116* `dotnet-test-sdk` is useful in cases where unit tests expect a different dotnet SDK. By default, this is set to the `dotnet-sdk` attribute. 117* `testProjectFile` is useful in cases where the regular project file does not contain the unit tests. It gets restored and build, but not installed. You may need to regenerate your nuget lockfile after setting this. Note that if set, only tests from this project are executed. 118* `disabledTests` is used to disable running specific unit tests. This gets passed as: `dotnet test --filter "FullyQualifiedName!={}"`, to ensure compatibility with all unit test frameworks. 119* `dotnetRestoreFlags` can be used to pass flags to `dotnet restore`. 120* `dotnetBuildFlags` can be used to pass flags to `dotnet build`. 121* `dotnetTestFlags` can be used to pass flags to `dotnet test`. Used only if `doCheck` is set to `true`. 122* `dotnetInstallFlags` can be used to pass flags to `dotnet install`. 123* `dotnetPackFlags` can be used to pass flags to `dotnet pack`. Used only if `packNupkg` is set to `true`. 124* `dotnetFlags` can be used to pass flags to all of the above phases. 125 126When packaging a new application, you need to fetch its dependencies. Create an empty `deps.nix`, set `nugetDeps = ./deps.nix`, then run `nix-build -A package.fetch-deps` to generate a script that will build the lockfile for you. 127 128Here is an example `default.nix`, using some of the previously discussed arguments: 129```nix 130{ lib, buildDotnetModule, dotnetCorePackages, ffmpeg }: 131 132let 133 referencedProject = import ../../bar { ... }; 134in buildDotnetModule rec { 135 pname = "someDotnetApplication"; 136 version = "0.1"; 137 138 src = ./.; 139 140 projectFile = "src/project.sln"; 141 nugetDeps = ./deps.nix; # File generated with `nix-build -A package.passthru.fetch-deps`. 142 143 projectReferences = [ referencedProject ]; # `referencedProject` must contain `nupkg` in the folder structure. 144 145 dotnet-sdk = dotnetCorePackages.sdk_6.0; 146 dotnet-runtime = dotnetCorePackages.runtime_6_0; 147 148 executables = [ "foo" ]; # This wraps "$out/lib/$pname/foo" to `$out/bin/foo`. 149 executables = []; # Don't install any executables. 150 151 packNupkg = true; # This packs the project as "foo-0.1.nupkg" at `$out/share`. 152 153 runtimeDeps = [ ffmpeg ]; # This will wrap ffmpeg's library path into `LD_LIBRARY_PATH`. 154} 155``` 156 157## Dotnet global tools {#dotnet-global-tools} 158 159[.NET Global tools](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools) are a mechanism provided by the dotnet CLI to install .NET binaries from Nuget packages. 160 161They can be installed either as a global tool for the entire system, or as a local tool specific to project. 162 163The local installation is the easiest and works on NixOS in the same way as on other Linux distributions. 164[See dotnet documention](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools#install-a-local-tool) to learn more. 165 166[The global installation method](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools#install-a-global-tool) 167should also work most of the time. You have to remember to update the `PATH` 168value to the location the tools are installed to (the CLI will inform you about it during installation) and also set 169the `DOTNET_ROOT` value, so that the tool can find the .NET SDK package. 170You can find the path to the SDK by running `nix eval --raw nixpkgs#dotnet-sdk` (substitute the `dotnet-sdk` package for 171another if a different SDK version is needed). 172 173This method is not recommended on NixOS, since it's not declarative and involves installing binaries not made for NixOS, 174which will not always work. 175 176The third, and preferred way, is packaging the tool into a Nix derivation. 177 178### Packaging Dotnet global tools {#packaging-dotnet-global-tools} 179 180Dotnet global tools are standard .NET binaries, just made available through a special 181NuGet package. Therefore, they can be built and packaged like every .NET application, 182using `buildDotnetModule`. 183 184If however the source is not available or difficult to build, the 185`buildDotnetGlobalTool` helper can be used, which will package the tool 186straight from its NuGet package. 187 188This helper has the same arguments as `buildDotnetModule`, with a few differences: 189 190* `pname` and `version` are required, and will be used to find the NuGet package of the tool 191* `nugetName` can be used to override the NuGet package name that will be downloaded, if it's different from `pname` 192* `nugetSha256` is the hash of the fetched NuGet package. Set this to `lib.fakeHash256` for the first build, and it will error out, giving you the proper hash. Also remember to update it during version updates (it will not error out if you just change the version while having a fetched package in `/nix/store`) 193* `dotnet-runtime` is set to `dotnet-sdk` by default. When changing this, remember that .NET tools fetched from NuGet require an SDK. 194 195Here is an example of packaging `pbm`, an unfree binary without source available: 196```nix 197{ buildDotnetGlobalTool, lib }: 198 199buildDotnetGlobalTool { 200 pname = "pbm"; 201 version = "1.3.1"; 202 203 nugetSha256 = "sha256-ZG2HFyKYhVNVYd2kRlkbAjZJq88OADe3yjxmLuxXDUo="; 204 205 meta = with lib; { 206 homepage = "https://cmd.petabridge.com/index.html"; 207 changelog = "https://cmd.petabridge.com/articles/RELEASE_NOTES.html"; 208 license = licenses.unfree; 209 platforms = platforms.linux; 210 }; 211} 212``` 213 214When packaging a new .NET application in nixpkgs, you can tag the [`@NixOS/dotnet`](https://github.com/orgs/nixos/teams/dotnet) team for help and code review.