···7071## Packaging a Dotnet Application {#packaging-a-dotnet-application}
7273-Ideally, we would like to build against the sdk, then only have the dotnet runtime available in the runtime closure.
00000000000000007475-TODO: Create closure-friendly way to package dotnet applications
000000000000000000
···7071## Packaging a Dotnet Application {#packaging-a-dotnet-application}
7273+To package Dotnet applications, you can use `buildDotnetModule`. This has similar arguments to `stdenv.mkDerivation`, with the following additions:
74+75+* `projectFile` has to be used for specifying the dotnet project file relative to the source root. These usually have `.sln` or `.csproj` file extensions.
76+* `nugetDeps` has to be used to specify the NuGet dependency file. Unfortunately, these cannot be deterministically fetched without a lockfile. This file should be generated using `nuget-to-nix` tool, which is available in nixpkgs.
77+* `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 `[]`.
78+* `runtimeDeps` is used to wrap libraries into `LD_LIBRARY_PATH`. This is how dotnet usually handles runtime dependencies.
79+* `buildType` is used to change the type of build. Possible values are `Release`, `Debug`, etc. By default, this is set to `Release`.
80+* `dotnet-sdk` is useful in cases where you need to change what dotnet SDK is being used.
81+* `dotnet-runtime` is useful in cases where you need to change what dotnet runtime is being used.
82+* `dotnetRestoreFlags` can be used to pass flags to `dotnet restore`.
83+* `dotnetBuildFlags` can be used to pass flags to `dotnet build`.
84+* `dotnetInstallFlags` can be used to pass flags to `dotnet install`.
85+* `dotnetFlags` can be used to pass flags to all of the above phases.
86+87+Here is an example `default.nix`, using some of the previously discussed arguments:
88+```nix
89+{ lib, buildDotnetModule, dotnetCorePackages, ffmpeg }:
9091+buildDotnetModule rec {
92+ pname = "someDotnetApplication";
93+ version = "0.1";
94+95+ src = ./.;
96+97+ projectFile = "src/project.sln";
98+ nugetDeps = ./deps.nix; # File generated with `nuget-to-nix path/to/src > deps.nix`.
99+100+ dotnet-sdk = dotnetCorePackages.sdk_3_1;
101+ dotnet-runtime = dotnetCorePackages.net_5_0;
102+ dotnetFlags = [ "--runtime linux-x64" ];
103+104+ executables = [ "foo" ]; # This wraps "$out/lib/$pname/foo" to `$out/bin/foo`.
105+ executables = []; # Don't install any executables.
106+107+ runtimeDeps = [ ffmpeg ]; # This will wrap ffmpeg's library path into `LD_LIBRARY_PATH`.
108+}
109+```
···1+{ lib, stdenv, makeWrapper, dotnetCorePackages, dotnetPackages, cacert, linkFarmFromDrvs, fetchurl }:
2+3+{ name ? "${args.pname}-${args.version}"
4+, enableParallelBuilding ? true
5+# Flags to pass to `makeWrapper`. This is done to avoid double wrapping.
6+, makeWrapperArgs ? []
7+8+# Flags to pass to `dotnet restore`.
9+, dotnetRestoreFlags ? []
10+# Flags to pass to `dotnet build`.
11+, dotnetBuildFlags ? []
12+# Flags to pass to `dotnet install`.
13+, dotnetInstallFlags ? []
14+# Flags to pass to dotnet in all phases.
15+, dotnetFlags ? []
16+17+# The binaries that should get installed to `$out/bin`, relative to `$out/lib/$pname/`. These get wrapped accordingly.
18+# Unfortunately, dotnet has no method for doing this automatically.
19+# If unset, all executables in the projects root will get installed. This may cause bloat!
20+, executables ? null
21+# The packages project file, which contains instructions on how to compile it.
22+, projectFile ? null
23+# The NuGet dependency file. This locks all NuGet dependency versions, as otherwise they cannot be deterministically fetched.
24+# This can be generated using the `nuget-to-nix` tool.
25+, nugetDeps ? null
26+# Libraries that need to be available at runtime should be passed through this.
27+# These get wrapped into `LD_LIBRARY_PATH`.
28+, runtimeDeps ? []
29+30+# The type of build to perform. This is passed to `dotnet` with the `--configuration` flag. Possible values are `Release`, `Debug`, etc.
31+, buildType ? "Release"
32+# The dotnet SDK to use.
33+, dotnet-sdk ? dotnetCorePackages.sdk_5_0
34+# The dotnet runtime to use.
35+, dotnet-runtime ? dotnetCorePackages.net_5_0
36+, ... } @ args:
37+38+assert projectFile == null -> throw "Defining the `projectFile` attribute is required. This is usually an `.csproj`, or `.sln` file.";
39+40+# TODO: Automatically generate a dependency file when a lockfile is present.
41+# This file is unfortunately almost never present, as Microsoft recommands not to push this in upstream repositories.
42+assert nugetDeps == null -> throw "Defining the `nugetDeps` attribute is required, as to lock the NuGet dependencies. This file can be generated using the `nuget-to-nix` tool.";
43+44+let
45+ _nugetDeps = linkFarmFromDrvs "${name}-nuget-deps" (import nugetDeps {
46+ fetchNuGet = { name, version, sha256 }: fetchurl {
47+ name = "nuget-${name}-${version}.nupkg";
48+ url = "https://www.nuget.org/api/v2/package/${name}/${version}";
49+ inherit sha256;
50+ };
51+ });
52+53+ package = stdenv.mkDerivation (args // {
54+ nativeBuildInputs = args.nativeBuildInputs or [] ++ [ dotnet-sdk dotnetPackages.Nuget cacert makeWrapper ];
55+56+ # Stripping breaks the executable
57+ dontStrip = true;
58+59+ DOTNET_NOLOGO = true; # This disables the welcome message.
60+ DOTNET_CLI_TELEMETRY_OPTOUT = true;
61+62+ configurePhase = args.configurePhase or ''
63+ runHook preConfigure
64+65+ export HOME=$(mktemp -d)
66+67+ nuget sources Add -Name nixos -Source "$PWD/nixos"
68+ nuget init "${_nugetDeps}" "$PWD/nixos"
69+70+ # This is required due to https://github.com/NuGet/Home/issues/4413.
71+ mkdir -p $HOME/.nuget/NuGet
72+ cp $HOME/.config/NuGet/NuGet.Config $HOME/.nuget/NuGet
73+74+ dotnet restore ${lib.escapeShellArg projectFile} \
75+ ${lib.optionalString (!enableParallelBuilding) "--disable-parallel"} \
76+ -p:ContinuousIntegrationBuild=true \
77+ -p:Deterministic=true \
78+ --source "$PWD/nixos" \
79+ "''${dotnetRestoreFlags[@]}" \
80+ "''${dotnetFlags[@]}"
81+82+ runHook postConfigure
83+ '';
84+85+ buildPhase = args.buildPhase or ''
86+ runHook preBuild
87+88+ dotnet build ${lib.escapeShellArg projectFile} \
89+ -maxcpucount:${if enableParallelBuilding then "$NIX_BUILD_CORES" else "1"} \
90+ -p:BuildInParallel=${if enableParallelBuilding then "true" else "false"} \
91+ -p:ContinuousIntegrationBuild=true \
92+ -p:Deterministic=true \
93+ -p:Version=${args.version} \
94+ --configuration ${buildType} \
95+ --no-restore \
96+ "''${dotnetBuildFlags[@]}" \
97+ "''${dotnetFlags[@]}"
98+99+ runHook postBuild
100+ '';
101+102+ installPhase = args.installPhase or ''
103+ runHook preInstall
104+105+ dotnet publish ${lib.escapeShellArg projectFile} \
106+ -p:ContinuousIntegrationBuild=true \
107+ -p:Deterministic=true \
108+ --output $out/lib/${args.pname} \
109+ --configuration ${buildType} \
110+ --no-build \
111+ --no-self-contained \
112+ "''${dotnetInstallFlags[@]}" \
113+ "''${dotnetFlags[@]}"
114+ '' + (if executables != null then ''
115+ for executable in ''${executables}; do
116+ execPath="$out/lib/${args.pname}/$executable"
117+118+ if [[ -f "$execPath" && -x "$execPath" ]]; then
119+ makeWrapper "$execPath" "$out/bin/$(basename "$executable")" \
120+ --set DOTNET_ROOT "${dotnet-runtime}" \
121+ --suffix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeDeps}" \
122+ "''${gappsWrapperArgs[@]}" \
123+ ''${makeWrapperArgs}
124+ else
125+ echo "Specified binary \"$executable\" is either not an executable, or does not exist!"
126+ exit 1
127+ fi
128+ done
129+ '' else ''
130+ for executable in $out/lib/${args.pname}/*; do
131+ if [[ -f "$executable" && -x "$executable" && "$executable" != *"dll"* ]]; then
132+ makeWrapper "$executable" "$out/bin/$(basename "$executable")" \
133+ --set DOTNET_ROOT "${dotnet-runtime}" \
134+ --suffix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeDeps}" \
135+ "''${gappsWrapperArgs[@]}" \
136+ ''${makeWrapperArgs}
137+ fi
138+ done
139+ '') + ''
140+ runHook postInstall
141+ '';
142+ });
143+in
144+ package