Unverified Commit 0159ddab authored by David McFarland's avatar David McFarland Committed by GitHub
Browse files

dotnet: implement autoPatchcilHook (#373107)

parents 87b2db19 e2d963b9
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
# autoPatchcilHook {#setup-hook-autopatchcilhook}

This is a special setup hook which helps in packaging .NET assemblies/programs in that it automatically tries to find missing shared library dependencies of .NET assemblies based on the given `buildInputs` and `nativeBuildInputs`.

As the hook needs information for the host where the package will be run on, there's a required environment variable called `autoPatchcilRuntimeId` which should be filled in with the RID (Runtime Identifier) of the machine where the output will be run on. If you're using `buildDotnetModule`, it will fall back to `dotnetRuntimeIds` (which is set to `lib.singleton (if runtimeId != null then runtimeId else systemToDotnetRid stdenvNoCC.hostPlatform.system)`) for you if not provided.

In certain situations you may want to run the main command (`autoPatchcil`) of the setup hook on a file or a set of directories instead of unconditionally patching all outputs. This can be done by setting the `dontAutoPatchcil` environment variable to a non-empty value.

By default, `autoPatchcil` will fail as soon as any .NET assembly requires a dependency which cannot be resolved via the given build inputs. In some situations you might prefer to just leave missing dependencies unpatched and continue to patch the rest. This can be achieved by setting the `autoPatchcilIgnoreMissingDeps` environment variable to a non-empty value. `autoPatchcilIgnoreMissingDeps` can be set to a list like `autoPatchcilIgnoreMissingDeps = [ "libcuda.so.1" "libcudart.so.1" ];` or to `[ "*" ]` to ignore all missing dependencies.

The `autoPatchcil` command requires the `--rid` command line flag, informing the RID (Runtime Identifier) it should assume the assemblies will be executed on, and also recognizes a `--no-recurse` command line flag, which prevents it from recursing into subdirectories.

::: {.note}
Since, unlike most native binaries, .NET assemblies are compiled once to run on any platform, many assemblies may have PInvoke stubs for libraries that might not be available on the platform that the package will effectively run on. A few examples are assemblies that call native Windows APIs through PInvoke targeting `kernel32`, `gdi32`, `user32`, `shell32` or `ntdll`.

`autoPatchcil` does its best to ignore dependencies from other platforms by checking the requested file extensions, however not all PInvoke stubs provide an extension so in those cases it will be necessary to list those in `autoPatchcilIgnoreMissingDeps` manually.
:::
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ The stdenv built-in hooks are documented in [](#ssec-setup-hooks).
```{=include=} sections
autoconf.section.md
automake.section.md
autopatchcil.section.md
autopatchelf.section.md
aws-c-common.section.md
bmake.section.md
+3 −0
Original line number Diff line number Diff line
@@ -2067,6 +2067,9 @@
  "setup-hook-automake": [
    "index.html#setup-hook-automake"
  ],
  "setup-hook-autopatchcilhook": [
    "index.html#setup-hook-autopatchcilhook"
  ],
  "setup-hook-autopatchelfhook": [
    "index.html#setup-hook-autopatchelfhook"
  ],
+118 −0
Original line number Diff line number Diff line
#!@shell@
# shellcheck shell=bash

declare -a autoPatchcilLibs
declare -a extraAutoPatchcilLibs

gatherLibraries() {
    if [ -d "$1/lib" ]; then
        autoPatchcilLibs+=("$1/lib")
    fi
}

addEnvHooks "${targetOffset:?}" gatherLibraries

# Can be used to manually add additional directories with shared object files
# to be included for the next autoPatchcil invocation.
addAutoPatchcilSearchPath() {
    local -a findOpts=()

    while [ $# -gt 0 ]; do
        case "$1" in
        --)
            shift
            break
            ;;
        --no-recurse)
            shift
            findOpts+=("-maxdepth" 1)
            ;;
        --*)
            echo "addAutoPatchcilSearchPath: ERROR: Invalid command line" \
                "argument: $1" >&2
            return 1
            ;;
        *) break ;;
        esac
    done

    local dir=
    while IFS= read -r -d '' dir; do
        extraAutoPatchcilLibs+=("$dir")
    done < <(
        find "$@" "${findOpts[@]}" \! -type d \
            \( -name '*.so' -o -name '*.so.*' \) -print0 |
            sed -z 's#/[^/]*$##' |
            uniq -z
    )
}

autoPatchcil() {
    local rid=
    local norecurse=
    while [ $# -gt 0 ]; do
        case "$1" in
        --)
            shift
            break
            ;;
        --rid)
            rid="$2"
            shift 2
            ;;
        --no-recurse)
            shift
            norecurse=1
            ;;
        --*)
            echo "autoPatchcil: ERROR: Invalid command line" \
                "argument: $1" >&2
            return 1
            ;;
        *) break ;;
        esac
    done

    if [ -z "$rid" ]; then
        echo "autoPatchcil: ERROR: No RID (Runtime ID) provided." >&2
        return 1
    fi

    local ignoreMissingDepsArray=("--ignore-missing")
    concatTo ignoreMissingDepsArray autoPatchcilIgnoreMissingDeps

    if [ ${#ignoreMissingDepsArray[@]} -lt 2 ]; then
        ignoreMissingDepsArray=()
    fi

    local autoPatchcilFlags=(
        ${norecurse:+--no-recurse}
        --rid "$rid"
        "${ignoreMissingDepsArray[@]}"
        --paths "$@"
        --libs "${autoPatchcilLibs[@]}"
    )

    # shellcheck disable=SC2016
    echoCmd 'patchcil auto flags' "${autoPatchcilFlags[@]}"
    @patchcil@ auto "${autoPatchcilFlags[@]}"
}

autoPatchcilFixupOutput() {
    if [[ -z "${dontAutoPatchcil-}" ]]; then
        if [ -n "${dotnetRuntimeIds+x}" ]; then
            if [[ -n $__structuredAttrs ]]; then
                local dotnetRuntimeIdsArray=("${dotnetRuntimeIds[@]}")
            else
                # shellcheck disable=SC2206 # Intentionally expanding it to preserve old behavior
                local dotnetRuntimeIdsArray=($dotnetRuntimeIds)
            fi
        else
            local dotnetRuntimeIdsArray=("")
        fi

        autoPatchcil --rid "${autoPatchcilRuntimeId:-${dotnetRuntimeIdsArray[0]}}" -- "${prefix:?}"
    fi
}

fixupOutputHooks+=(autoPatchcilFixupOutput)
+14 −0
Original line number Diff line number Diff line
{
  lib,
  bash,
  patchcil,
  makeSetupHook,
}:

makeSetupHook {
  name = "auto-patchcil-hook";
  substitutions = {
    shell = lib.getExe bash;
    patchcil = lib.getExe patchcil;
  };
} ./auto-patchcil.sh
Loading