summary refs log tree commit diff
path: root/pkgs/build-support/dotnet/build-dotnet-module/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/build-support/dotnet/build-dotnet-module/default.nix')
-rw-r--r--pkgs/build-support/dotnet/build-dotnet-module/default.nix270
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