diff --git a/doc/hooks/autoPatchPkgConfigHook.section.md b/doc/hooks/autoPatchPkgConfigHook.section.md new file mode 100644 index 0000000000000..e76961cbe0a8f --- /dev/null +++ b/doc/hooks/autoPatchPkgConfigHook.section.md @@ -0,0 +1,12 @@ +# autoPatchPkgConfigHook {#auto-patch-pkg-config-hook} + +The `autoPatchPkgConfigHook` hook replaces all packages listed in `Requires` and +`Requires.private` fields with absolute paths to their pkg-config files. This +effectively means that dependency resolution by `pkg-config` is moved from the +build phase of the dependent package to the build phase of the dependency which +is important since the dependent package may not be aware of its transitive +dependencies. + +You should use this hook if your package produces pkg-config files with +non-empty `Requires` or `Requires.private` fields. To use it simply add it to +the `nativeBuildInputs`. diff --git a/doc/hooks/index.md b/doc/hooks/index.md index e4b744056c5e4..ab3f4791d6af1 100644 --- a/doc/hooks/index.md +++ b/doc/hooks/index.md @@ -36,6 +36,7 @@ tauri.section.md tetex-tex-live.section.md unzip.section.md validatePkgConfig.section.md +autoPatchPkgConfigHook.section.md versionCheckHook.section.md waf.section.md zig.section.md diff --git a/doc/redirects.json b/doc/redirects.json index df7592d179206..5e83a0d194e3e 100644 --- a/doc/redirects.json +++ b/doc/redirects.json @@ -1,4 +1,7 @@ { + "auto-patch-pkg-config-hook": [ + "index.html#auto-patch-pkg-config-hook" + ], "chap-build-helpers-finalAttrs": [ "index.html#chap-build-helpers-finalAttrs" ], diff --git a/pkgs/build-support/setup-hooks/patch-pc.py b/pkgs/build-support/setup-hooks/patch-pc.py new file mode 100644 index 0000000000000..b4f4f21bd09e8 --- /dev/null +++ b/pkgs/build-support/setup-hooks/patch-pc.py @@ -0,0 +1,52 @@ +# Replaces dependencies in pc files with their absolute paths +# +# Usage: python patch-pc.py /path/to/pkgconf libfoo.pc +# The tool writes patched .pc file to the stdout + +from subprocess import run +import sys +import re + +LINE_SEP = re.compile(r"(? str: + result = run([pkgconf_bin, *args], capture_output=True, check=True) + return result.stdout.decode().rstrip() + + +def get_path(name: str) -> str: + return run_pkgconf("--path", name) + + +def list_requires(file: str, private: bool) -> list[str]: + return run_pkgconf( + f"--print-requires{"-private" if private else ""}", file + ).splitlines() + + +def transform_dep_spec(dep_spec: str) -> str: + def escape(s: str) -> str: + return s.replace("#", "\\#") + + [name, *version_spec] = dep_spec.split(" ") + return " ".join(escape(s) for s in [get_path(name), *version_spec]) + + +def print_requires(file: str, private: bool): + requires = (transform_dep_spec(dep) for dep in list_requires(file, private)) + print(f"Requires{".private" if private else ""}: ", end="") + print(*requires, sep=", ") + + +with open(pc_file, "r") as f: + for line in LINE_SEP.split(f.read()): + if line.lstrip().startswith("Requires:"): + print_requires(pc_file, private=False) + elif line.lstrip().startswith("Requires.private:"): + print_requires(pc_file, private=True) + else: + print(line) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index f5e04f33882ec..51fa11a6114d3 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -1004,6 +1004,36 @@ with pkgs; ]; } ../build-support/setup-hooks/validate-pkg-config.sh; + autoPatchPkgConfigHook = + makeSetupHook + { + name = "auto-patch-pkg-config-hook"; + propagatedBuildInputs = [ pkg-config ]; + } + ( + writeScript "patch-pc.sh" '' + fixupOutputHooks+=(autoPatchPcHook) + + autoPatchPcHook() ( + # Some packages list their own libraries as deps, they should be searched first in case of cyclic deps + export PKG_CONFIG_PATH_${pkgconf.suffixSalt}="$prefix/lib/pkgconfig:$prefix/share/pkgconfig:$PKG_CONFIG_PATH_${pkgconf.suffixSalt}" + + shopt -s nullglob + for pc in $prefix/{lib,lib64,share}/pkgconfig/*.pc; do + echo Fixing require paths in $pc file + + if ! ${lib.getExe python3Minimal} \ + ${../build-support/setup-hooks/patch-pc.py} \ + ${lib.getExe pkgconf} "$pc" > "$pc.patched"; then + exit 1 + fi + mv "$pc.patched" "$pc" + done + ) + + '' + ); + #package writers writers = callPackage ../build-support/writers { };