diff options
Diffstat (limited to 'pkgs/build-support/dotnet/build-dotnet-module/default.nix')
-rw-r--r-- | pkgs/build-support/dotnet/build-dotnet-module/default.nix | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/pkgs/build-support/dotnet/build-dotnet-module/default.nix b/pkgs/build-support/dotnet/build-dotnet-module/default.nix new file mode 100644 index 00000000000..49a61f4e5d6 --- /dev/null +++ b/pkgs/build-support/dotnet/build-dotnet-module/default.nix @@ -0,0 +1,270 @@ +{ lib, stdenvNoCC, linkFarmFromDrvs, nuget-to-nix, writeScript, makeWrapper, fetchurl, xml2, dotnetCorePackages, dotnetPackages, cacert }: + +{ name ? "${args.pname}-${args.version}" +, enableParallelBuilding ? true +, doCheck ? false +# Flags to pass to `makeWrapper`. This is done to avoid double wrapping. +, makeWrapperArgs ? [] + +# Flags to pass to `dotnet restore`. +, dotnetRestoreFlags ? [] +# Flags to pass to `dotnet build`. +, dotnetBuildFlags ? [] +# Flags to pass to `dotnet test`, if running tests is enabled. +, dotnetTestFlags ? [] +# Flags to pass to `dotnet install`. +, dotnetInstallFlags ? [] +# Flags to pass to `dotnet pack`. +, dotnetPackFlags ? [] +# Flags to pass to dotnet in all phases. +, dotnetFlags ? [] + +# The binaries that should get installed to `$out/bin`, relative to `$out/lib/$pname/`. These get wrapped accordingly. +# Unfortunately, dotnet has no method for doing this automatically. +# If unset, all executables in the projects root will get installed. This may cause bloat! +, executables ? null +# Packs a 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`. +, packNupkg ? false +# The packages project file, which contains instructions on how to compile it. This can be an array of multiple project files as well. +, projectFile ? null +# The NuGet dependency file. This locks all NuGet dependency versions, as otherwise they cannot be deterministically fetched. +# This can be generated by running the `passthru.fetch-deps` script. +, nugetDeps ? null +# A list of derivations containing nupkg packages for local project references. +# Referenced derivations can be built with `buildDotnetModule` with `packNupkg=true` flag. +# Since we are sharing them as nugets they must be added to csproj/fsproj files as `PackageReference` as well. +# For example, your project has a local dependency: +# <ProjectReference Include="../foo/bar.fsproj" /> +# To enable discovery through `projectReferences` you would need to add a line: +# <ProjectReference Include="../foo/bar.fsproj" /> +# <PackageReference Include="bar" Version="*" Condition=" '$(ContinuousIntegrationBuild)'=='true' "/> +, projectReferences ? [] +# Libraries that need to be available at runtime should be passed through this. +# These get wrapped into `LD_LIBRARY_PATH`. +, runtimeDeps ? [] + +# Tests to disable. This gets passed to `dotnet test --filter "FullyQualifiedName!={}"`, to ensure compatibility with all frameworks. +# See https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test#filter-option-details for more details. +, disabledTests ? [] +# The project file to run unit tests against. This is usually referenced in the regular project file, but sometimes it needs to be manually set. +# It gets restored and build, but not installed. You may need to regenerate your nuget lockfile after setting this. +, testProjectFile ? "" + +# The type of build to perform. This is passed to `dotnet` with the `--configuration` flag. Possible values are `Release`, `Debug`, etc. +, buildType ? "Release" +# The dotnet SDK to use. +, dotnet-sdk ? dotnetCorePackages.sdk_5_0 +# The dotnet runtime to use. +, dotnet-runtime ? dotnetCorePackages.runtime_5_0 +# The dotnet SDK to run tests against. This can differentiate from the SDK compiled against. +, dotnet-test-sdk ? dotnet-sdk +, ... } @ args: + +assert projectFile == null -> throw "Defining the `projectFile` attribute is required. This is usually an `.csproj`, or `.sln` file."; + +# TODO: Automatically generate a dependency file when a lockfile is present. +# This file is unfortunately almost never present, as Microsoft recommands not to push this in upstream repositories. +assert nugetDeps == null -> throw "Defining the `nugetDeps` attribute is required, as to lock the NuGet dependencies. This file can be generated by running the `passthru.fetch-deps` script."; + +let + _nugetDeps = linkFarmFromDrvs "${name}-nuget-deps" (import nugetDeps { + fetchNuGet = { pname, version, sha256 }: fetchurl { + name = "${pname}-${version}.nupkg"; + url = "https://www.nuget.org/api/v2/package/${pname}/${version}"; + inherit sha256; + }; + }); + _localDeps = linkFarmFromDrvs "${name}-local-nuget-deps" projectReferences; + + nuget-source = stdenvNoCC.mkDerivation rec { + name = "${args.pname}-nuget-source"; + meta.description = "A Nuget source with the dependencies for ${args.pname}"; + + nativeBuildInputs = [ dotnetPackages.Nuget xml2 ]; + buildCommand = '' + export HOME=$(mktemp -d) + mkdir -p $out/{lib,share} + + nuget sources Add -Name nixos -Source "$out/lib" + nuget init "${_nugetDeps}" "$out/lib" + ${lib.optionalString (projectReferences != []) + "nuget init \"${_localDeps}\" \"$out/lib\""} + + # Generates a list of all unique licenses' spdx ids. + find "$out/lib" -name "*.nuspec" -exec sh -c \ + "xml2 < {} | grep "license=" | cut -d'=' -f2" \; | sort -u > $out/share/licenses + ''; + } // { # This is done because we need data from `$out` for `meta`. We have to use overrides as to not hit infinite recursion. + meta.licence = let + depLicenses = lib.splitString "\n" (builtins.readFile "${nuget-source}/share/licenses"); + getLicence = spdx: lib.filter (license: license.spdxId or null == spdx) (builtins.attrValues lib.licenses); + in (lib.flatten (lib.forEach depLicenses (spdx: + if (getLicence spdx) != [] then (getLicence spdx) else [] ++ lib.optional (spdx != "") spdx + ))); + }; + + package = stdenvNoCC.mkDerivation (args // { + inherit buildType; + + nativeBuildInputs = args.nativeBuildInputs or [] ++ [ dotnet-sdk cacert makeWrapper ]; + + # Stripping breaks the executable + dontStrip = true; + + # gappsWrapperArgs gets included when wrapping for dotnet, as to avoid double wrapping + dontWrapGApps = true; + + DOTNET_NOLOGO = true; # This disables the welcome message. + DOTNET_CLI_TELEMETRY_OPTOUT = true; + + passthru = { + fetch-deps = writeScript "fetch-${args.pname}-deps" '' + set -euo pipefail + cd "$(dirname "''${BASH_SOURCE[0]}")" + + export HOME=$(mktemp -d) + deps_file="/tmp/${args.pname}-deps.nix" + + store_src="${package.src}" + src="$(mktemp -d /tmp/${args.pname}.XXX)" + cp -rT "$store_src" "$src" + chmod -R +w "$src" + + trap "rm -rf $src $HOME" EXIT + pushd "$src" + + export DOTNET_NOLOGO=1 + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + + mkdir -p "$HOME/nuget_pkgs" + + for project in "${lib.concatStringsSep "\" \"" ((lib.toList projectFile) ++ lib.optionals (testProjectFile != "") (lib.toList testProjectFile))}"; do + ${dotnet-sdk}/bin/dotnet restore "$project" \ + ${lib.optionalString (!enableParallelBuilding) "--disable-parallel"} \ + -p:ContinuousIntegrationBuild=true \ + -p:Deterministic=true \ + --packages "$HOME/nuget_pkgs" \ + "''${dotnetRestoreFlags[@]}" \ + "''${dotnetFlags[@]}" + done + + echo "Writing lockfile..." + ${nuget-to-nix}/bin/nuget-to-nix "$HOME/nuget_pkgs" > "$deps_file" + echo "Succesfully wrote lockfile to: $deps_file" + ''; + } // args.passthru or {}; + + configurePhase = args.configurePhase or '' + runHook preConfigure + + export HOME=$(mktemp -d) + + for project in ''${projectFile[@]} ''${testProjectFile[@]}; do + dotnet restore "$project" \ + ${lib.optionalString (!enableParallelBuilding) "--disable-parallel"} \ + -p:ContinuousIntegrationBuild=true \ + -p:Deterministic=true \ + --source "${nuget-source}/lib" \ + "''${dotnetRestoreFlags[@]}" \ + "''${dotnetFlags[@]}" + done + + runHook postConfigure + ''; + + buildPhase = args.buildPhase or '' + runHook preBuild + + for project in ''${projectFile[@]} ''${testProjectFile[@]}; do + dotnet build "$project" \ + -maxcpucount:${if enableParallelBuilding then "$NIX_BUILD_CORES" else "1"} \ + -p:BuildInParallel=${if enableParallelBuilding then "true" else "false"} \ + -p:ContinuousIntegrationBuild=true \ + -p:Deterministic=true \ + -p:Version=${args.version} \ + --configuration "$buildType" \ + --no-restore \ + "''${dotnetBuildFlags[@]}" \ + "''${dotnetFlags[@]}" + done + + runHook postBuild + ''; + + checkPhase = args.checkPhase or '' + runHook preCheck + + for project in ''${testProjectFile[@]}; do + ${lib.getBin dotnet-test-sdk}/bin/dotnet test "$project" \ + -maxcpucount:${if enableParallelBuilding then "$NIX_BUILD_CORES" else "1"} \ + -p:ContinuousIntegrationBuild=true \ + -p:Deterministic=true \ + --configuration "$buildType" \ + --no-build \ + --logger "console;verbosity=normal" \ + ${lib.optionalString (disabledTests != []) "--filter \"FullyQualifiedName!=${lib.concatStringsSep "&FullyQualifiedName!=" disabledTests}\""} \ + "''${dotnetTestFlags[@]}" \ + "''${dotnetFlags[@]}" + done + + runHook postCheck + ''; + + installPhase = args.installPhase or '' + runHook preInstall + + for project in ''${projectFile[@]}; do + dotnet publish "$project" \ + -p:ContinuousIntegrationBuild=true \ + -p:Deterministic=true \ + --output $out/lib/${args.pname} \ + --configuration "$buildType" \ + --no-build \ + --no-self-contained \ + "''${dotnetInstallFlags[@]}" \ + "''${dotnetFlags[@]}" + done + '' + lib.optionalString packNupkg '' + for project in ''${projectFile[@]}; do + dotnet pack "$project" \ + -p:ContinuousIntegrationBuild=true \ + -p:Deterministic=true \ + --output $out/share \ + --configuration "$buildType" \ + --no-build \ + "''${dotnetPackFlags[@]}" \ + "''${dotnetFlags[@]}" + done + '' + '' + runHook postInstall + ''; + + preFixup = '' + _wrapDotnetProgram() { + makeWrapper "$1" "$out/bin/$(basename "$executable")" \ + --set DOTNET_ROOT "${dotnet-runtime}" \ + --suffix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeDeps}" \ + "''${gappsWrapperArgs[@]}" \ + "''${makeWrapperArgs[@]}" + } + '' + (if executables != null then '' + for executable in ''${executables[@]}; do + execPath="$out/lib/${args.pname}/$executable" + + if [[ -f "$execPath" && -x "$execPath" ]]; then + _wrapDotnetProgram $execPath + else + echo "Specified binary \"$executable\" is either not an executable, or does not exist!" + exit 1 + fi + done + '' else '' + for executable in $out/lib/${args.pname}/*; do + if [[ -f "$executable" && -x "$executable" && "$executable" != *"dll"* ]]; then + _wrapDotnetProgram $executable + fi + done + ''); + }); +in + package |