···70707171## Packaging a Dotnet Application {#packaging-a-dotnet-application}
72727373-Ideally, we would like to build against the sdk, then only have the dotnet runtime available in the runtime closure.
7373+To package Dotnet applications, you can use `buildDotnetModule`. This has similar arguments to `stdenv.mkDerivation`, with the following additions:
7474+7575+* `projectFile` has to be used for specifying the dotnet project file relative to the source root. These usually have `.sln` or `.csproj` file extensions.
7676+* `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.
7777+* `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 `[]`.
7878+* `runtimeDeps` is used to wrap libraries into `LD_LIBRARY_PATH`. This is how dotnet usually handles runtime dependencies.
7979+* `buildType` is used to change the type of build. Possible values are `Release`, `Debug`, etc. By default, this is set to `Release`.
8080+* `dotnet-sdk` is useful in cases where you need to change what dotnet SDK is being used.
8181+* `dotnet-runtime` is useful in cases where you need to change what dotnet runtime is being used.
8282+* `dotnetRestoreFlags` can be used to pass flags to `dotnet restore`.
8383+* `dotnetBuildFlags` can be used to pass flags to `dotnet build`.
8484+* `dotnetInstallFlags` can be used to pass flags to `dotnet install`.
8585+* `dotnetFlags` can be used to pass flags to all of the above phases.
8686+8787+Here is an example `default.nix`, using some of the previously discussed arguments:
8888+```nix
8989+{ lib, buildDotnetModule, dotnetCorePackages, ffmpeg }:
74907575-TODO: Create closure-friendly way to package dotnet applications
9191+buildDotnetModule rec {
9292+ pname = "someDotnetApplication";
9393+ version = "0.1";
9494+9595+ src = ./.;
9696+9797+ projectFile = "src/project.sln";
9898+ nugetDeps = ./deps.nix; # File generated with `nuget-to-nix path/to/src > deps.nix`.
9999+100100+ dotnet-sdk = dotnetCorePackages.sdk_3_1;
101101+ dotnet-runtime = dotnetCorePackages.net_5_0;
102102+ dotnetFlags = [ "--runtime linux-x64" ];
103103+104104+ executables = [ "foo" ]; # This wraps "$out/lib/$pname/foo" to `$out/bin/foo`.
105105+ executables = []; # Don't install any executables.
106106+107107+ runtimeDeps = [ ffmpeg ]; # This will wrap ffmpeg's library path into `LD_LIBRARY_PATH`.
108108+}
109109+```
···11+{ lib, stdenv, makeWrapper, dotnetCorePackages, dotnetPackages, cacert, linkFarmFromDrvs, fetchurl }:
22+33+{ name ? "${args.pname}-${args.version}"
44+, enableParallelBuilding ? true
55+# Flags to pass to `makeWrapper`. This is done to avoid double wrapping.
66+, makeWrapperArgs ? []
77+88+# Flags to pass to `dotnet restore`.
99+, dotnetRestoreFlags ? []
1010+# Flags to pass to `dotnet build`.
1111+, dotnetBuildFlags ? []
1212+# Flags to pass to `dotnet install`.
1313+, dotnetInstallFlags ? []
1414+# Flags to pass to dotnet in all phases.
1515+, dotnetFlags ? []
1616+1717+# The binaries that should get installed to `$out/bin`, relative to `$out/lib/$pname/`. These get wrapped accordingly.
1818+# Unfortunately, dotnet has no method for doing this automatically.
1919+# If unset, all executables in the projects root will get installed. This may cause bloat!
2020+, executables ? null
2121+# The packages project file, which contains instructions on how to compile it.
2222+, projectFile ? null
2323+# The NuGet dependency file. This locks all NuGet dependency versions, as otherwise they cannot be deterministically fetched.
2424+# This can be generated using the `nuget-to-nix` tool.
2525+, nugetDeps ? null
2626+# Libraries that need to be available at runtime should be passed through this.
2727+# These get wrapped into `LD_LIBRARY_PATH`.
2828+, runtimeDeps ? []
2929+3030+# The type of build to perform. This is passed to `dotnet` with the `--configuration` flag. Possible values are `Release`, `Debug`, etc.
3131+, buildType ? "Release"
3232+# The dotnet SDK to use.
3333+, dotnet-sdk ? dotnetCorePackages.sdk_5_0
3434+# The dotnet runtime to use.
3535+, dotnet-runtime ? dotnetCorePackages.net_5_0
3636+, ... } @ args:
3737+3838+assert projectFile == null -> throw "Defining the `projectFile` attribute is required. This is usually an `.csproj`, or `.sln` file.";
3939+4040+# TODO: Automatically generate a dependency file when a lockfile is present.
4141+# This file is unfortunately almost never present, as Microsoft recommands not to push this in upstream repositories.
4242+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.";
4343+4444+let
4545+ _nugetDeps = linkFarmFromDrvs "${name}-nuget-deps" (import nugetDeps {
4646+ fetchNuGet = { name, version, sha256 }: fetchurl {
4747+ name = "nuget-${name}-${version}.nupkg";
4848+ url = "https://www.nuget.org/api/v2/package/${name}/${version}";
4949+ inherit sha256;
5050+ };
5151+ });
5252+5353+ package = stdenv.mkDerivation (args // {
5454+ nativeBuildInputs = args.nativeBuildInputs or [] ++ [ dotnet-sdk dotnetPackages.Nuget cacert makeWrapper ];
5555+5656+ # Stripping breaks the executable
5757+ dontStrip = true;
5858+5959+ DOTNET_NOLOGO = true; # This disables the welcome message.
6060+ DOTNET_CLI_TELEMETRY_OPTOUT = true;
6161+6262+ configurePhase = args.configurePhase or ''
6363+ runHook preConfigure
6464+6565+ export HOME=$(mktemp -d)
6666+6767+ nuget sources Add -Name nixos -Source "$PWD/nixos"
6868+ nuget init "${_nugetDeps}" "$PWD/nixos"
6969+7070+ # This is required due to https://github.com/NuGet/Home/issues/4413.
7171+ mkdir -p $HOME/.nuget/NuGet
7272+ cp $HOME/.config/NuGet/NuGet.Config $HOME/.nuget/NuGet
7373+7474+ dotnet restore ${lib.escapeShellArg projectFile} \
7575+ ${lib.optionalString (!enableParallelBuilding) "--disable-parallel"} \
7676+ -p:ContinuousIntegrationBuild=true \
7777+ -p:Deterministic=true \
7878+ --source "$PWD/nixos" \
7979+ "''${dotnetRestoreFlags[@]}" \
8080+ "''${dotnetFlags[@]}"
8181+8282+ runHook postConfigure
8383+ '';
8484+8585+ buildPhase = args.buildPhase or ''
8686+ runHook preBuild
8787+8888+ dotnet build ${lib.escapeShellArg projectFile} \
8989+ -maxcpucount:${if enableParallelBuilding then "$NIX_BUILD_CORES" else "1"} \
9090+ -p:BuildInParallel=${if enableParallelBuilding then "true" else "false"} \
9191+ -p:ContinuousIntegrationBuild=true \
9292+ -p:Deterministic=true \
9393+ -p:Version=${args.version} \
9494+ --configuration ${buildType} \
9595+ --no-restore \
9696+ "''${dotnetBuildFlags[@]}" \
9797+ "''${dotnetFlags[@]}"
9898+9999+ runHook postBuild
100100+ '';
101101+102102+ installPhase = args.installPhase or ''
103103+ runHook preInstall
104104+105105+ dotnet publish ${lib.escapeShellArg projectFile} \
106106+ -p:ContinuousIntegrationBuild=true \
107107+ -p:Deterministic=true \
108108+ --output $out/lib/${args.pname} \
109109+ --configuration ${buildType} \
110110+ --no-build \
111111+ --no-self-contained \
112112+ "''${dotnetInstallFlags[@]}" \
113113+ "''${dotnetFlags[@]}"
114114+ '' + (if executables != null then ''
115115+ for executable in ''${executables}; do
116116+ execPath="$out/lib/${args.pname}/$executable"
117117+118118+ if [[ -f "$execPath" && -x "$execPath" ]]; then
119119+ makeWrapper "$execPath" "$out/bin/$(basename "$executable")" \
120120+ --set DOTNET_ROOT "${dotnet-runtime}" \
121121+ --suffix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeDeps}" \
122122+ "''${gappsWrapperArgs[@]}" \
123123+ ''${makeWrapperArgs}
124124+ else
125125+ echo "Specified binary \"$executable\" is either not an executable, or does not exist!"
126126+ exit 1
127127+ fi
128128+ done
129129+ '' else ''
130130+ for executable in $out/lib/${args.pname}/*; do
131131+ if [[ -f "$executable" && -x "$executable" && "$executable" != *"dll"* ]]; then
132132+ makeWrapper "$executable" "$out/bin/$(basename "$executable")" \
133133+ --set DOTNET_ROOT "${dotnet-runtime}" \
134134+ --suffix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeDeps}" \
135135+ "''${gappsWrapperArgs[@]}" \
136136+ ''${makeWrapperArgs}
137137+ fi
138138+ done
139139+ '') + ''
140140+ runHook postInstall
141141+ '';
142142+ });
143143+in
144144+ package