From f7d8046fb83cf0bb4e0a8ad179dee89bcbe0335d Mon Sep 17 00:00:00 2001 From: Ivan Trubach Date: Thu, 16 May 2024 02:10:39 +0300 Subject: [PATCH] buildDotnetModule: fix structured attributes support This change refactors internal hooks used by buildDotnetModule to support derivations with structured attributes. Note that this changes variable names that the internal hooks expect. --- .../misc/ArchiSteamFarm/default.nix | 2 +- .../build-dotnet-global-tool/default.nix | 2 +- .../dotnet/build-dotnet-module/default.nix | 36 ++++--- .../build-dotnet-module/hooks/default.nix | 36 ++----- .../hooks/dotnet-build-hook.sh | 76 ++++++++------ .../hooks/dotnet-check-hook.sh | 62 ++++++++---- .../hooks/dotnet-configure-hook.sh | 98 +++++++++++++------ .../hooks/dotnet-fixup-hook.sh | 81 +++++++++++---- .../hooks/dotnet-install-hook.sh | 80 +++++++++------ pkgs/test/dotnet/default.nix | 4 +- .../dotnet/project-references/default.nix | 10 +- pkgs/test/dotnet/structured-attrs/default.nix | 36 +++++++ .../dotnet/structured-attrs/nuget-deps.nix | 5 + .../structured-attrs/src/Application.cs | 10 ++ .../structured-attrs/src/Application.csproj | 5 + .../dotnet/use-dotnet-from-env/default.nix | 60 ++++++++++++ .../dotnet/use-dotnet-from-env/nuget-deps.nix | 5 + .../use-dotnet-from-env/src/Application.cs | 3 + .../src/Application.csproj | 5 + 19 files changed, 440 insertions(+), 176 deletions(-) create mode 100644 pkgs/test/dotnet/structured-attrs/default.nix create mode 100644 pkgs/test/dotnet/structured-attrs/nuget-deps.nix create mode 100644 pkgs/test/dotnet/structured-attrs/src/Application.cs create mode 100644 pkgs/test/dotnet/structured-attrs/src/Application.csproj create mode 100644 pkgs/test/dotnet/use-dotnet-from-env/default.nix create mode 100644 pkgs/test/dotnet/use-dotnet-from-env/nuget-deps.nix create mode 100644 pkgs/test/dotnet/use-dotnet-from-env/src/Application.cs create mode 100644 pkgs/test/dotnet/use-dotnet-from-env/src/Application.csproj diff --git a/pkgs/applications/misc/ArchiSteamFarm/default.nix b/pkgs/applications/misc/ArchiSteamFarm/default.nix index 7c334fb804558..fc3335ca3a927 100644 --- a/pkgs/applications/misc/ArchiSteamFarm/default.nix +++ b/pkgs/applications/misc/ArchiSteamFarm/default.nix @@ -41,7 +41,7 @@ buildDotnetModule rec { doCheck = true; preBuild = '' - export projectFile=(ArchiSteamFarm) + dotnetProjectFiles=(ArchiSteamFarm) ''; preInstall = '' diff --git a/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix b/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix index 16cf029ca3451..7ae9cfc9f6618 100644 --- a/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix +++ b/pkgs/build-support/dotnet/build-dotnet-global-tool/default.nix @@ -28,7 +28,7 @@ buildDotnetModule (args // { ] ++ (nugetDeps fetchNuGet); }; - projectFile = ""; + dotnetGlobalTool = true; useDotnetFromEnv = true; diff --git a/pkgs/build-support/dotnet/build-dotnet-module/default.nix b/pkgs/build-support/dotnet/build-dotnet-module/default.nix index e2ddee48cc006..6a4f9129cd44d 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/default.nix +++ b/pkgs/build-support/dotnet/build-dotnet-module/default.nix @@ -69,7 +69,7 @@ , 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 ? "" +, testProjectFile ? null # The type of build to perform. This is passed to `dotnet` with the `--configuration` flag. Possible values are `Release`, `Debug`, etc. , buildType ? "Release" @@ -88,17 +88,18 @@ } @ args: let + projectFiles = + lib.optionals (projectFile != null) (lib.toList projectFile); + testProjectFiles = + lib.optionals (testProjectFile != null) (lib.toList testProjectFile); + platforms = if args ? meta.platforms then lib.intersectLists args.meta.platforms dotnet-sdk.meta.platforms else dotnet-sdk.meta.platforms; inherit (callPackage ./hooks { - inherit dotnet-sdk disabledTests nuget-source dotnet-runtime runtimeDeps buildType; - runtimeId = - if runtimeId != null - then runtimeId - else dotnetCorePackages.systemToDotnetRid stdenvNoCC.targetPlatform.system; + inherit dotnet-sdk dotnet-runtime; }) dotnetConfigureHook dotnetBuildHook dotnetCheckHook dotnetInstallHook dotnetFixupHook; localDeps = @@ -143,6 +144,19 @@ let nugetDepsFile = _nugetDeps.sourceFile; in stdenvNoCC.mkDerivation (args // { + dotnetInstallPath = installPath; + dotnetExecutables = executables; + dotnetBuildType = buildType; + dotnetProjectFiles = projectFiles; + dotnetTestProjectFiles = testProjectFiles; + dotnetDisabledTests = disabledTests; + dotnetRuntimeId = runtimeId; + nugetSource = nuget-source; + dotnetRuntimeDeps = map lib.getLib runtimeDeps; + dotnetSelfContainedBuild = selfContainedBuild; + dotnetUseAppHost = useAppHost; + inherit useDotnetFromEnv; + nativeBuildInputs = args.nativeBuildInputs or [ ] ++ [ dotnetConfigureHook dotnetBuildHook @@ -172,7 +186,7 @@ stdenvNoCC.mkDerivation (args // { else [ ])); makeWrapperArgs = args.makeWrapperArgs or [ ] ++ [ - "--prefix LD_LIBRARY_PATH : ${dotnet-sdk.icu}/lib" + "--prefix" "LD_LIBRARY_PATH" ":" "${dotnet-sdk.icu}/lib" ]; # Stripping breaks the executable @@ -181,8 +195,6 @@ stdenvNoCC.mkDerivation (args // { # gappsWrapperArgs gets included when wrapping for dotnet, as to avoid double wrapping dontWrapGApps = args.dontWrapGApps or true; - inherit selfContainedBuild useAppHost useDotnetFromEnv; - # propagate the runtime sandbox profile since the contents apply to published # executables propagatedSandboxProfile = toString dotnet-runtime.__propagatedSandboxProfile; @@ -267,11 +279,11 @@ stdenvNoCC.mkDerivation (args // { --no-cache \ --force \ ${lib.optionalString (!enableParallelBuilding) "--disable-parallel"} \ - ${lib.optionalString (flags != []) (toString flags)} + ${lib.escapeShellArgs flags} } - declare -a projectFiles=( ${toString (lib.toList projectFile)} ) - declare -a testProjectFiles=( ${toString (lib.toList testProjectFile)} ) + declare -a projectFiles=( ${lib.escapeShellArgs projectFiles} ) + declare -a testProjectFiles=( ${lib.escapeShellArgs testProjectFiles} ) export DOTNET_NOLOGO=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1 diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix b/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix index 44091604f5c2c..b9c51a743c6a6 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix +++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/default.nix @@ -4,28 +4,21 @@ , coreutils , zlib , openssl -, callPackage , makeSetupHook -, makeWrapper +, dotnetCorePackages + # Passed from ../default.nix , dotnet-sdk -, disabledTests -, nuget-source , dotnet-runtime -, runtimeDeps -, buildType -, runtimeId }: -assert (builtins.isString runtimeId); - let - libraryPath = lib.makeLibraryPath runtimeDeps; + runtimeId = dotnetCorePackages.systemToDotnetRid stdenv.hostPlatform.system; in { dotnetConfigureHook = makeSetupHook { name = "dotnet-configure-hook"; substitutions = { - nugetSource = nuget-source; + runtimeId = lib.escapeShellArg runtimeId; dynamicLinker = "${stdenv.cc}/nix-support/dynamic-linker"; libPath = lib.makeLibraryPath [ stdenv.cc.cc.lib @@ -34,7 +27,6 @@ in zlib openssl ]; - inherit runtimeId; }; } ./dotnet-configure-hook.sh; @@ -43,7 +35,7 @@ in { name = "dotnet-build-hook"; substitutions = { - inherit buildType runtimeId; + runtimeId = lib.escapeShellArg runtimeId; }; } ./dotnet-build-hook.sh; @@ -52,15 +44,7 @@ in { name = "dotnet-check-hook"; substitutions = { - inherit buildType runtimeId libraryPath; - disabledTests = lib.optionalString (disabledTests != [ ]) - ( - let - escapedNames = lib.lists.map (n: lib.replaceStrings [ "," ] [ "%2C" ] n) disabledTests; - filters = lib.lists.map (n: "FullyQualifiedName!=${n}") escapedNames; - in - "${lib.concatStringsSep "&" filters}" - ); + runtimeId = lib.escapeShellArg runtimeId; }; } ./dotnet-check-hook.sh; @@ -69,7 +53,7 @@ in { name = "dotnet-install-hook"; substitutions = { - inherit buildType runtimeId; + runtimeId = lib.escapeShellArg runtimeId; }; } ./dotnet-install-hook.sh; @@ -79,11 +63,7 @@ in name = "dotnet-fixup-hook"; substitutions = { dotnetRuntime = dotnet-runtime; - runtimeDeps = libraryPath; - shell = stdenv.shell; - which = "${which}/bin/which"; - dirname = "${coreutils}/bin/dirname"; - realpath = "${coreutils}/bin/realpath"; + wrapperPath = lib.makeBinPath [ which coreutils ]; }; } ./dotnet-fixup-hook.sh; diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh index 798109291f92a..f209861f79b15 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh +++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-build-hook.sh @@ -1,12 +1,25 @@ -# inherit arguments from derivation -dotnetBuildFlags=( ${dotnetBuildFlags[@]-} ) - dotnetBuildHook() { echo "Executing dotnetBuildHook" runHook preBuild - if [ "${enableParallelBuilding-}" ]; then + local -r hostRuntimeId=@runtimeId@ + local -r dotnetBuildType="${dotnetBuildType-Release}" + local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}" + + if [[ -n $__structuredAttrs ]]; then + local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" ) + local dotnetTestProjectFilesArray=( "${dotnetTestProjectFiles[@]}" ) + local dotnetFlagsArray=( "${dotnetFlags[@]}" ) + local dotnetBuildFlagsArray=( "${dotnetBuildFlags[@]}" ) + else + local dotnetProjectFilesArray=($dotnetProjectFiles) + local dotnetTestProjectFilesArray=($dotnetTestProjectFiles) + local dotnetFlagsArray=($dotnetFlags) + local dotnetBuildFlagsArray=($dotnetBuildFlags) + fi + + if [[ -n "${enableParallelBuilding-}" ]]; then local -r maxCpuFlag="$NIX_BUILD_CORES" local -r parallelBuildFlag="true" else @@ -14,50 +27,53 @@ dotnetBuildHook() { local -r parallelBuildFlag="false" fi - if [ "${selfContainedBuild-}" ]; then - dotnetBuildFlags+=("-p:SelfContained=true") + if [[ -n ${dotnetSelfContainedBuild-} ]]; then + dotnetBuildFlagsArray+=("-p:SelfContained=true") else - dotnetBuildFlags+=("-p:SelfContained=false") + dotnetBuildFlagsArray+=("-p:SelfContained=false") fi - if [ "${useAppHost-}" ]; then - dotnetBuildFlags+=("-p:UseAppHost=true") + if [[ -n ${dotnetUseAppHost-} ]]; then + dotnetBuildFlagsArray+=("-p:UseAppHost=true") fi - local versionFlags=() - if [ "${version-}" ]; then - versionFlags+=("-p:InformationalVersion=${version-}") + local versionFlagsArray=() + if [[ -n ${version-} ]]; then + versionFlagsArray+=("-p:InformationalVersion=$version") fi - if [ "${versionForDotnet-}" ]; then - versionFlags+=("-p:Version=${versionForDotnet-}") + if [[ -n ${versionForDotnet-} ]]; then + versionFlagsArray+=("-p:Version=$versionForDotnet") fi dotnetBuild() { - local -r project="${1-}" + local -r projectFile="${1-}" - runtimeIdFlags=() - if [[ "$project" == *.csproj ]] || [ "${selfContainedBuild-}" ]; then - runtimeIdFlags+=("--runtime @runtimeId@") + local runtimeIdFlagsArray=() + if [[ $projectFile == *.csproj || -n ${dotnetSelfContainedBuild-} ]]; then + runtimeIdFlagsArray+=("--runtime" "$dotnetRuntimeId") fi - dotnet build ${project-} \ - -maxcpucount:$maxCpuFlag \ - -p:BuildInParallel=$parallelBuildFlag \ + dotnet build ${1+"$projectFile"} \ + -maxcpucount:"$maxCpuFlag" \ + -p:BuildInParallel="$parallelBuildFlag" \ -p:ContinuousIntegrationBuild=true \ -p:Deterministic=true \ - --configuration "@buildType@" \ + --configuration "$dotnetBuildType" \ --no-restore \ - ${versionFlags[@]} \ - ${runtimeIdFlags[@]} \ - ${dotnetBuildFlags[@]} \ - ${dotnetFlags[@]} + "${versionFlagsArray[@]}" \ + "${runtimeIdFlagsArray[@]}" \ + "${dotnetBuildFlagsArray[@]}" \ + "${dotnetFlagsArray[@]}" } - (( "${#projectFile[@]}" == 0 )) && dotnetBuild + if (( ${#dotnetProjectFilesArray[@]} == 0 )); then + dotnetBuild + fi - for project in ${projectFile[@]} ${testProjectFile[@]-}; do - dotnetBuild "$project" + local projectFile + for projectFile in "${dotnetProjectFilesArray[@]}" "${dotnetTestProjectFilesArray[@]}"; do + dotnetBuild "$projectFile" done runHook postBuild @@ -65,6 +81,6 @@ dotnetBuildHook() { echo "Finished dotnetBuildHook" } -if [[ -z "${dontDotnetBuild-}" && -z "${buildPhase-}" ]]; then +if [[ -z ${dontDotnetBuild-} && -z ${buildPhase-} ]]; then buildPhase=dotnetBuildHook fi diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh index f19bf9f620feb..c91251f4f1807 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh +++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-check-hook.sh @@ -1,39 +1,65 @@ -# inherit arguments from derivation -dotnetTestFlags=( ${dotnetTestFlags[@]-} ) - dotnetCheckHook() { echo "Executing dotnetCheckHook" runHook preCheck - if [ "${disabledTests-}" ]; then - local -r disabledTestsFlag="--filter @disabledTests@" + local -r hostRuntimeId=@runtimeId@ + local -r dotnetBuildType="${dotnetBuildType-Release}" + local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}" + + if [[ -n $__structuredAttrs ]]; then + local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" ) + local dotnetTestProjectFilesArray=( "${dotnetTestProjectFiles[@]}" ) + local dotnetTestFlagsArray=( "${dotnetTestFlags[@]}" ) + local dotnetDisabledTestsArray=( "${dotnetDisabledTests[@]}" ) + local dotnetRuntimeDepsArray=( "${dotnetRuntimeDeps[@]}" ) + else + local dotnetProjectFilesArray=($dotnetProjectFiles) + local dotnetTestProjectFilesArray=($dotnetTestProjectFiles) + local dotnetTestFlagsArray=($dotnetTestFlags) + local dotnetDisabledTestsArray=($dotnetDisabledTests) + local dotnetRuntimeDepsArray=($dotnetRuntimeDeps) + fi + + if (( ${#dotnetDisabledTestsArray[@]} > 0 )); then + local disabledTestsFilters=("${dotnetDisabledTestsArray[@]/#/FullyQualifiedName!=}") + local OLDIFS="$IFS" IFS='&' + dotnetTestFlagsArray+=("--filter:${disabledTestsFilters[*]//,/%2C}") + IFS="$OLDIFS" + fi + + local libraryPath="${LD_LIBRARY_PATH-}" + if (( ${#dotnetRuntimeDepsArray[@]} > 0 )); then + local libraryPathArray=("${dotnetRuntimeDepsArray[@]/%//lib}") + local OLDIFS="$IFS" IFS=':' + libraryPath="${libraryPathArray[*]}${libraryPath:+':'}$libraryPath" + IFS="$OLDIFS" fi - if [ "${enableParallelBuilding-}" ]; then + if [[ -n ${enableParallelBuilding-} ]]; then local -r maxCpuFlag="$NIX_BUILD_CORES" else local -r maxCpuFlag="1" fi - for project in ${testProjectFile[@]-${projectFile[@]}}; do - runtimeIdFlags=() - if [[ "$project" == *.csproj ]]; then - runtimeIdFlags=("--runtime @runtimeId@") + local projectFile + for projectFile in "${dotnetTestProjectFilesArray[@]-${dotnetProjectFilesArray[@]}}"; do + local runtimeIdFlagsArray=() + if [[ $projectFile == *.csproj ]]; then + runtimeIdFlagsArray=("--runtime" "$dotnetRuntimeId") fi - LD_LIBRARY_PATH="@libraryPath@" \ - dotnet test "$project" \ - -maxcpucount:$maxCpuFlag \ + LD_LIBRARY_PATH=$libraryPath \ + dotnet test "$projectFile" \ + -maxcpucount:"$maxCpuFlag" \ -p:ContinuousIntegrationBuild=true \ -p:Deterministic=true \ - --configuration "@buildType@" \ + --configuration "$dotnetBuildType" \ --no-build \ --logger "console;verbosity=normal" \ - ${disabledTestsFlag-} \ - ${runtimeIdFlags[@]} \ - "${dotnetTestFlags[@]}" \ - "${dotnetFlags[@]}" + "${runtimeIdFlagsArray[@]}" \ + "${dotnetTestFlagsArray[@]}" \ + "${dotnetFlagsArray[@]}" done runHook postCheck diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh index 3eb0d4e1f2309..12fa348699865 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh +++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-configure-hook.sh @@ -1,63 +1,103 @@ -declare -a projectFile testProjectFile - -# Inherit arguments from derivation -dotnetFlags=( ${dotnetFlags[@]-} ) -dotnetRestoreFlags=( ${dotnetRestoreFlags[@]-} ) - dotnetConfigureHook() { echo "Executing dotnetConfigureHook" runHook preConfigure - if [ -z "${enableParallelBuilding-}" ]; then + if [[ -z ${nugetSource-} ]]; then + echo + echo "ERROR: no dependencies were specified" + echo 'Hint: set `nugetSource` if using these hooks individually. If this is happening with `buildDotnetModule`, please open an issue.' + echo + + exit 1 + fi + + local nugetSourceSedQuoted="${nugetSource//[\/\\&$'\n']/\\&}" + local nugetSourceXMLQuoted="$nugetSource" + nugetSourceXMLQuoted="${nugetSource//&/\&}" + nugetSourceXMLQuoted="${nugetSourceXMLQuoted//\"/\"}" + + local -r hostRuntimeId=@runtimeId@ + local -r dynamicLinker=@dynamicLinker@ + local -r libPath=@libPath@ + local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}" + + if [[ -n $__structuredAttrs ]]; then + local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" ) + local dotnetTestProjectFilesArray=( "${dotnetTestProjectFiles[@]}" ) + local dotnetFlagsArray=( "${dotnetFlags[@]}" ) + local dotnetRestoreFlagsArray=( "${dotnetRestoreFlags[@]}" ) + else + local dotnetProjectFilesArray=($dotnetProjectFiles) + local dotnetTestProjectFilesArray=($dotnetTestProjectFiles) + local dotnetFlagsArray=($dotnetFlags) + local dotnetRestoreFlagsArray=($dotnetRestoreFlags) + fi + + if [[ -z ${enableParallelBuilding-} ]]; then local -r parallelFlag="--disable-parallel" fi dotnetRestore() { - local -r project="${1-}" - dotnet restore ${project-} \ + local -r projectFile="${1-}" + dotnet restore ${1+"$projectFile"} \ -p:ContinuousIntegrationBuild=true \ -p:Deterministic=true \ - --runtime "@runtimeId@" \ - --source "@nugetSource@/lib" \ + --runtime "$dotnetRuntimeId" \ + --source "$nugetSource/lib" \ ${parallelFlag-} \ - ${dotnetRestoreFlags[@]} \ - ${dotnetFlags[@]} + "${dotnetRestoreFlagsArray[@]}" \ + "${dotnetFlagsArray[@]}" } # Generate a NuGet.config file to make sure everything, # including things like dependencies, is restored from the proper source -cat < "./NuGet.config" + cat >NuGet.config < - + EOF - # Patch paket.dependencies and paket.lock (if found) to use the proper source. This ensures - # paket restore works correctly - # We use + instead of / in sed to avoid problems with slashes - find -name paket.dependencies -exec sed -i 's+source .*+source @nugetSource@/lib+g' {} \; - find -name paket.lock -exec sed -i 's+remote:.*+remote: @nugetSource@/lib+g' {} \; - - dotnet tool restore --add-source "@nugetSource@/lib" - - (( "${#projectFile[@]}" == 0 )) && dotnetRestore + # Patch paket.dependencies and paket.lock (if found) to use the proper + # source. This ensures paket restore works correctly. Note that the + # nugetSourceSedQuoted abomination below safely escapes nugetSource string + # for use as a sed replacement string to avoid issues with slashes and other + # special characters ('&', '\\' and '\n'). + find -name paket.dependencies -exec sed -i "s/source .*/source $nugetSourceSedQuoted\/lib/g" {} \; + find -name paket.lock -exec sed -i "s/remote:.*/remote: $nugetSourceSedQuoted\/lib/g" {} \; + + dotnet tool restore --add-source "$nugetSource/lib" + + # dotnetGlobalTool is set in buildDotnetGlobalTool to patch dependencies but + # avoid other project-specific logic. This is a hack, but the old behavior + # is worse as it relied on a bug: setting projectFile to an empty string + # made the hooks actually skip all project-specific logic. It’s hard to keep + # backwards compatibility with this odd behavior now since we are using + # arrays, so instead we just pass a variable to indicate that we don’t have + # projects. + if [[ -z ${dotnetGlobalTool-} ]]; then + if (( ${#dotnetProjectFilesArray[@]} == 0 )); then + dotnetRestore + fi - for project in ${projectFile[@]} ${testProjectFile[@]-}; do - dotnetRestore "$project" - done + local projectFile + for projectFile in "${dotnetProjectFilesArray[@]}" "${dotnetTestProjectFilesArray[@]}"; do + dotnetRestore "$projectFile" + done + fi echo "Fixing up native binaries..." # Find all native binaries and nuget libraries, and fix them up, # by setting the proper interpreter and rpath to some commonly used libraries + local binary for binary in $(find "$HOME/.nuget/packages/" -type f -executable); do if patchelf --print-interpreter "$binary" >/dev/null 2>/dev/null; then echo "Found binary: $binary, fixing it up..." - patchelf --set-interpreter "$(cat "@dynamicLinker@")" "$binary" + patchelf --set-interpreter "$(cat "$dynamicLinker")" "$binary" # This makes sure that if the binary requires some specific runtime dependencies, it can find it. # This fixes dotnet-built binaries like crossgen2 @@ -68,7 +108,7 @@ EOF --add-needed libssl.so \ "$binary" - patchelf --set-rpath "@libPath@" "$binary" + patchelf --set-rpath "$libPath" "$binary" fi done diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh index e3671728af35e..f9aba29a43555 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh +++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh @@ -1,28 +1,55 @@ -# Inherit arguments from the derivation -declare -a derivationMakeWrapperArgs="( ${makeWrapperArgs-} )" -makeWrapperArgs=( "${derivationMakeWrapperArgs[@]}" ) - # First argument is the executable you want to wrap, # the second is the destination for the wrapper. wrapDotnetProgram() { - local dotnetRootFlags=() + local -r dotnetRuntime=@dotnetRuntime@ + local -r wrapperPath=@wrapperPath@ + + local -r dotnetFromEnvScript='dotnetFromEnv() { + local dotnetPath + if command -v dotnet 2>&1 >/dev/null; then + dotnetPath=$(which dotnet) && \ + dotnetPath=$(realpath "$dotnetPath") && \ + dotnetPath=$(dirname "$dotnetPath") && \ + export DOTNET_ROOT="$dotnetPath" + fi +} +dotnetFromEnv' + + if [[ -n $__structuredAttrs ]]; then + local -r dotnetRuntimeDepsArray=( "${dotnetRuntimeDeps[@]}" ) + local -r makeWrapperArgsArray=( "${makeWrapperArgs[@]}" ) + else + local -r dotnetRuntimeDepsArray=($dotnetRuntimeDeps) + local -r makeWrapperArgsArray=($makeWrapperArgs) + fi - if [ ! "${selfContainedBuild-}" ]; then - if [ "${useDotnetFromEnv-}" ]; then + local dotnetRuntimeDepsFlags=() + if (( ${#dotnetRuntimeDepsArray[@]} > 0 )); then + local libraryPathArray=("${dotnetRuntimeDepsArray[@]/%//lib}") + local OLDIFS="$IFS" IFS=':' + dotnetRuntimeDepsFlags+=("--suffix" "LD_LIBRARY_PATH" ":" "${libraryPathArray[*]}") + IFS="$OLDIFS" + fi + + local dotnetRootFlagsArray=() + if [[ -z ${dotnetSelfContainedBuild-} ]]; then + if [[ -n ${useDotnetFromEnv-} ]]; then # if dotnet CLI is available, set DOTNET_ROOT based on it. Otherwise set to default .NET runtime - dotnetRootFlags+=("--run" 'command -v dotnet &>/dev/null && export DOTNET_ROOT="$(@dirname@ "$(@realpath@ "$(@which@ dotnet)")")" || export DOTNET_ROOT="@dotnetRuntime@"') - dotnetRootFlags+=("--suffix" "PATH" ":" "@dotnetRuntime@/bin") + dotnetRootFlagsArray+=("--suffix" "PATH" ":" "$wrapperPath") + dotnetRootFlagsArray+=("--run" "$dotnetFromEnvScript") + dotnetRootFlagsArray+=("--set-default" "DOTNET_ROOT" "$dotnetRuntime") + dotnetRootFlagsArray+=("--suffix" "PATH" ":" "$dotnetRuntime/bin") else - dotnetRootFlags+=("--set" "DOTNET_ROOT" "@dotnetRuntime@") - dotnetRootFlags+=("--prefix" "PATH" ":" "@dotnetRuntime@/bin") + dotnetRootFlagsArray+=("--set" "DOTNET_ROOT" "$dotnetRuntime") + dotnetRootFlagsArray+=("--prefix" "PATH" ":" "$dotnetRuntime/bin") fi fi makeWrapper "$1" "$2" \ - --suffix "LD_LIBRARY_PATH" : "@runtimeDeps@" \ - "${dotnetRootFlags[@]}" \ + "${dotnetRuntimeDepsFlags[@]}" \ + "${dotnetRootFlagsArray[@]}" \ "${gappsWrapperArgs[@]}" \ - "${makeWrapperArgs[@]}" + "${makeWrapperArgsArray[@]}" echo "installed wrapper to "$2"" } @@ -30,13 +57,24 @@ wrapDotnetProgram() { dotnetFixupHook() { echo "Executing dotnetFixupPhase" - # check if executables is declared (including empty values, in which case we generate no executables) - if declare -p executables &>/dev/null; then - for executable in ${executables[@]}; do - path="${installPath-$out/lib/$pname}/$executable" + local -r dotnetInstallPath="${dotnetInstallPath-$out/lib/$pname}" + + local executable executableBasename + + # check if dotnetExecutables is declared (including empty values, in which case we generate no executables) + if declare -p dotnetExecutables &>/dev/null; then + if [[ -n $__structuredAttrs ]]; then + local dotnetExecutablesArray=( "${dotnetExecutables[@]}" ) + else + local dotnetExecutablesArray=($dotnetExecutables) + fi + for executable in "${dotnetExecutablesArray[@]}"; do + executableBasename=$(basename "$executable") + + local path="$dotnetInstallPath/$executable" if test -x "$path"; then - wrapDotnetProgram "$path" "$out/bin/$(basename "$executable")" + wrapDotnetProgram "$path" "$out/bin/$executableBasename" else echo "Specified binary \"$executable\" is either not an executable or does not exist!" echo "Looked in $path" @@ -45,8 +83,9 @@ dotnetFixupHook() { done else while IFS= read -d '' executable; do - wrapDotnetProgram "$executable" "$out/bin/$(basename "$executable")" \; - done < <(find "${installPath-$out/lib/$pname}" ! -name "*.dll" -executable -type f -print0) + executableBasename=$(basename "$executable") + wrapDotnetProgram "$executable" "$out/bin/$executableBasename" \; + done < <(find "$dotnetInstallPath" ! -name "*.dll" -executable -type f -print0) fi echo "Finished dotnetFixupPhase" diff --git a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh index ed754d8ffcad9..4d9b3c502c354 100644 --- a/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh +++ b/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-install-hook.sh @@ -1,70 +1,86 @@ -# inherit arguments from derivation -dotnetInstallFlags=( ${dotnetInstallFlags[@]-} ) - dotnetInstallHook() { echo "Executing dotnetInstallHook" runHook preInstall - if [ "${selfContainedBuild-}" ]; then - dotnetInstallFlags+=("--self-contained") + local -r hostRuntimeId=@runtimeId@ + local -r dotnetInstallPath="${dotnetInstallPath-$out/lib/$pname}" + local -r dotnetBuildType="${dotnetBuildType-Release}" + local -r dotnetRuntimeId="${dotnetRuntimeId-$hostRuntimeId}" + + if [[ -n $__structuredAttrs ]]; then + local dotnetProjectFilesArray=( "${dotnetProjectFiles[@]}" ) + local dotnetFlagsArray=( "${dotnetFlags[@]}" ) + local dotnetInstallFlagsArray=( "${dotnetInstallFlags[@]}" ) + local dotnetPackFlagsArray=( "${dotnetPackFlags[@]}" ) + else + local dotnetProjectFilesArray=($dotnetProjectFiles) + local dotnetFlagsArray=($dotnetFlags) + local dotnetInstallFlagsArray=($dotnetInstallFlags) + local dotnetPackFlagsArray=($dotnetPackFlags) + fi + + if [[ -n ${dotnetSelfContainedBuild-} ]]; then + dotnetInstallFlagsArray+=("--self-contained") else - dotnetInstallFlags+=("--no-self-contained") + dotnetInstallFlagsArray+=("--no-self-contained") # https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained # Trimming is only available for self-contained build, so force disable it here - dotnetInstallFlags+=("-p:PublishTrimmed=false") + dotnetInstallFlagsArray+=("-p:PublishTrimmed=false") fi - if [ "${useAppHost-}" ]; then - dotnetInstallFlags+=("-p:UseAppHost=true") + if [[ -n ${dotnetUseAppHost-} ]]; then + dotnetInstallFlagsArray+=("-p:UseAppHost=true") fi dotnetPublish() { - local -r project="${1-}" + local -r projectFile="${1-}" - runtimeIdFlags=() - if [[ "$project" == *.csproj ]] || [ "${selfContainedBuild-}" ]; then - runtimeIdFlags+=("--runtime @runtimeId@") + runtimeIdFlagsArray=() + if [[ $projectFile == *.csproj || -n ${dotnetSelfContainedBuild-} ]]; then + runtimeIdFlagsArray+=("--runtime" "$dotnetRuntimeId") fi - dotnet publish ${project-} \ + dotnet publish ${1+"$projectFile"} \ -p:ContinuousIntegrationBuild=true \ -p:Deterministic=true \ - --output "${installPath-$out/lib/$pname}" \ - --configuration "@buildType@" \ + --output "$dotnetInstallPath" \ + --configuration "$dotnetBuildType" \ --no-build \ - ${runtimeIdFlags[@]} \ - ${dotnetInstallFlags[@]} \ - ${dotnetFlags[@]} + "${runtimeIdFlagsArray[@]}" \ + "${dotnetInstallFlagsArray[@]}" \ + "${dotnetFlagsArray[@]}" } dotnetPack() { - local -r project="${1-}" - dotnet pack ${project-} \ + local -r projectFile="${1-}" + dotnet pack ${1+"$projectFile"} \ -p:ContinuousIntegrationBuild=true \ -p:Deterministic=true \ --output "$out/share" \ - --configuration "@buildType@" \ + --configuration "$dotnetBuildType" \ --no-build \ - --runtime "@runtimeId@" \ - ${dotnetPackFlags[@]} \ - ${dotnetFlags[@]} + --runtime "$dotnetRuntimeId" \ + "${dotnetPackFlagsArray[@]}" \ + "${dotnetFlagsArray[@]}" } - if (( "${#projectFile[@]}" == 0 )); then + if (( ${#dotnetProjectFilesArray[@]} == 0 )); then dotnetPublish else - for project in ${projectFile[@]}; do - dotnetPublish "$project" + local projectFile + for projectFile in "${dotnetProjectFilesArray[@]}"; do + dotnetPublish "$projectFile" done fi - if [[ "${packNupkg-}" ]]; then - if (( "${#projectFile[@]}" == 0 )); then + if [[ -n ${packNupkg-} ]]; then + if (( ${#dotnetProjectFilesArray[@]} == 0 )); then dotnetPack else - for project in ${projectFile[@]}; do - dotnetPack "$project" + local projectFile + for projectFile in "${dotnetProjectFilesArray[@]}"; do + dotnetPack "$projectFile" done fi fi diff --git a/pkgs/test/dotnet/default.nix b/pkgs/test/dotnet/default.nix index 7592b09d76e3c..d70850c05fdb0 100644 --- a/pkgs/test/dotnet/default.nix +++ b/pkgs/test/dotnet/default.nix @@ -1,5 +1,7 @@ -{ callPackage }: +{ lib, callPackage }: { project-references = callPackage ./project-references { }; + use-dotnet-from-env = lib.recurseIntoAttrs (callPackage ./use-dotnet-from-env { }); + structured-attrs = lib.recurseIntoAttrs (callPackage ./structured-attrs { }); } diff --git a/pkgs/test/dotnet/project-references/default.nix b/pkgs/test/dotnet/project-references/default.nix index f40b9196c2091..4ac5cf2ad82e6 100644 --- a/pkgs/test/dotnet/project-references/default.nix +++ b/pkgs/test/dotnet/project-references/default.nix @@ -4,11 +4,13 @@ { lib , dotnet-sdk -, buildDotnetModule +, buildPackages # buildDotnetModule , runCommand }: let + inherit (buildPackages) buildDotnetModule; + nugetDeps = ./nuget-deps.nix; # Specify the TargetFramework via an environment variable so that we don't @@ -18,7 +20,8 @@ let library = buildDotnetModule { name = "project-references-test-library"; src = ./library; - inherit nugetDeps TargetFramework; + inherit nugetDeps; + env.TargetFramework = TargetFramework; packNupkg = true; }; @@ -26,7 +29,8 @@ let application = buildDotnetModule { name = "project-references-test-application"; src = ./application; - inherit nugetDeps TargetFramework; + inherit nugetDeps; + env.TargetFramework = TargetFramework; projectReferences = [ library ]; }; diff --git a/pkgs/test/dotnet/structured-attrs/default.nix b/pkgs/test/dotnet/structured-attrs/default.nix new file mode 100644 index 0000000000000..cf96fef8dbdcc --- /dev/null +++ b/pkgs/test/dotnet/structured-attrs/default.nix @@ -0,0 +1,36 @@ +{ lib +, dotnet-sdk +, buildPackages # buildDotnetModule +, testers +, runCommand +}: +let + # Note: without structured attributes, we can’t use derivation arguments that + # contain spaces unambiguously because arguments are passed as space-separated + # environment variables. + copyrightString = "Public domain 🅮"; + + inherit (buildPackages) buildDotnetModule; + + app = buildDotnetModule { + name = "structured-attrs-test-application"; + src = ./src; + nugetDeps = ./nuget-deps.nix; + dotnetFlags = [ "--property:Copyright=${copyrightString}" ]; + env.TargetFramework = "net${lib.versions.majorMinor (lib.getVersion dotnet-sdk)}"; + __structuredAttrs = true; + }; +in +{ + no-structured-attrs = testers.testBuildFailure (app.overrideAttrs { + __structuredAttrs = false; + }); + + check-output = testers.testEqualContents { + assertion = "buildDotnetModule sets AssemblyCopyrightAttribute with structured attributes"; + expected = builtins.toFile "expected-copyright.txt" copyrightString; + actual = runCommand "dotnet-structured-attrs-test" { } '' + ${app}/bin/Application >"$out" + ''; + }; +} diff --git a/pkgs/test/dotnet/structured-attrs/nuget-deps.nix b/pkgs/test/dotnet/structured-attrs/nuget-deps.nix new file mode 100644 index 0000000000000..f3a17967e25c8 --- /dev/null +++ b/pkgs/test/dotnet/structured-attrs/nuget-deps.nix @@ -0,0 +1,5 @@ +# This file was automatically generated by passthru.fetch-deps. +# Please dont edit it manually, your changes might get overwritten! + +{ fetchNuGet }: [ +] diff --git a/pkgs/test/dotnet/structured-attrs/src/Application.cs b/pkgs/test/dotnet/structured-attrs/src/Application.cs new file mode 100644 index 0000000000000..3bc548105c2b4 --- /dev/null +++ b/pkgs/test/dotnet/structured-attrs/src/Application.cs @@ -0,0 +1,10 @@ +using System; +using System.Reflection; + +Console.Write( + ( + (AssemblyCopyrightAttribute)Assembly + .GetExecutingAssembly() + .GetCustomAttributes(typeof(AssemblyCopyrightAttribute), true)[0] + ).Copyright +); diff --git a/pkgs/test/dotnet/structured-attrs/src/Application.csproj b/pkgs/test/dotnet/structured-attrs/src/Application.csproj new file mode 100644 index 0000000000000..decaa6d961aab --- /dev/null +++ b/pkgs/test/dotnet/structured-attrs/src/Application.csproj @@ -0,0 +1,5 @@ + + + exe + + diff --git a/pkgs/test/dotnet/use-dotnet-from-env/default.nix b/pkgs/test/dotnet/use-dotnet-from-env/default.nix new file mode 100644 index 0000000000000..711a98eb0c29d --- /dev/null +++ b/pkgs/test/dotnet/use-dotnet-from-env/default.nix @@ -0,0 +1,60 @@ +{ lib +, dotnet-sdk +, buildPackages # buildDotnetModule, dotnet-runtime +, testers +, runCommand +, removeReferencesTo +}: +let + inherit (buildPackages) buildDotnetModule dotnet-runtime; + + app = buildDotnetModule { + name = "use-dotnet-from-env-test-application"; + src = ./src; + nugetDeps = ./nuget-deps.nix; + useDotnetFromEnv = true; + env.TargetFramework = "net${lib.versions.majorMinor (lib.getVersion dotnet-sdk)}"; + }; + + appWithoutFallback = app.overrideAttrs (oldAttrs: { + nativeBuildInputs = (oldAttrs.nativeBuildInputs or [ ]) ++ [ + removeReferencesTo + ]; + postFixup = (oldAttrs.postFixup or "") + '' + remove-references-to -t ${dotnet-runtime} "$out/bin/Application" + ''; + }); + + runtimeVersion = lib.getVersion dotnet-runtime; + runtimeVersionFile = builtins.toFile "dotnet-version.txt" runtimeVersion; +in +{ + fallback = testers.testEqualContents { + assertion = "buildDotnetModule sets fallback DOTNET_ROOT in wrapper"; + expected = runtimeVersionFile; + actual = runCommand "use-dotnet-from-env-fallback-test" { } '' + ${app}/bin/Application >"$out" + ''; + }; + + # Check that appWithoutFallback does not use fallback .NET runtime. + without-fallback = testers.testBuildFailure (runCommand "use-dotnet-from-env-without-fallback-test" { } '' + ${appWithoutFallback}/bin/Application >"$out" + ''); + + # NB assumes that without-fallback above to passes. + use-dotnet-root-env = testers.testEqualContents { + assertion = "buildDotnetModule uses DOTNET_ROOT from environment in wrapper"; + expected = runtimeVersionFile; + actual = runCommand "use-dotnet-from-env-root-test" { env.DOTNET_ROOT = dotnet-runtime; } '' + ${appWithoutFallback}/bin/Application >"$out" + ''; + }; + use-dotnet-path-env = testers.testEqualContents { + assertion = "buildDotnetModule uses DOTNET_ROOT from dotnet in PATH in wrapper"; + expected = runtimeVersionFile; + actual = runCommand "use-dotnet-from-env-path-test" { dotnetRuntime = dotnet-runtime; } '' + PATH=$dotnetRuntime''${PATH+:}$PATH ${appWithoutFallback}/bin/Application >"$out" + ''; + }; +} diff --git a/pkgs/test/dotnet/use-dotnet-from-env/nuget-deps.nix b/pkgs/test/dotnet/use-dotnet-from-env/nuget-deps.nix new file mode 100644 index 0000000000000..f3a17967e25c8 --- /dev/null +++ b/pkgs/test/dotnet/use-dotnet-from-env/nuget-deps.nix @@ -0,0 +1,5 @@ +# This file was automatically generated by passthru.fetch-deps. +# Please dont edit it manually, your changes might get overwritten! + +{ fetchNuGet }: [ +] diff --git a/pkgs/test/dotnet/use-dotnet-from-env/src/Application.cs b/pkgs/test/dotnet/use-dotnet-from-env/src/Application.cs new file mode 100644 index 0000000000000..5efc571ca9a3e --- /dev/null +++ b/pkgs/test/dotnet/use-dotnet-from-env/src/Application.cs @@ -0,0 +1,3 @@ +using System; + +Console.Write(Environment.Version.ToString()); diff --git a/pkgs/test/dotnet/use-dotnet-from-env/src/Application.csproj b/pkgs/test/dotnet/use-dotnet-from-env/src/Application.csproj new file mode 100644 index 0000000000000..decaa6d961aab --- /dev/null +++ b/pkgs/test/dotnet/use-dotnet-from-env/src/Application.csproj @@ -0,0 +1,5 @@ + + + exe + +