Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/hooks/autoPatchPkgConfigHook.section.md
Original file line number Diff line number Diff line change
@@ -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`.
1 change: 1 addition & 0 deletions doc/hooks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions doc/redirects.json
Original file line number Diff line number Diff line change
@@ -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"
],
Expand Down
52 changes: 52 additions & 0 deletions pkgs/build-support/setup-hooks/patch-pc.py
Original file line number Diff line number Diff line change
@@ -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"(?<!\\)\n")

pkgconf_bin = sys.argv[1]
pc_file = sys.argv[2]


def run_pkgconf(*args: str) -> 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))
Copy link
Contributor

@LordGrimmauld LordGrimmauld Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistic nit (feel free to ignore):

Suggested change
requires = (transform_dep_spec(dep) for dep in list_requires(file, private))
requires = map(transform_dep_spec, list_requires(file, private))

We do functional programming in nix, a map might be more appropriate than list comprehension, but i am happy with either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't write a lot of Python but my understanding is that list comprehensions are considered more pythonic than the functional equivalents, e.g., see here.

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)
30 changes: 30 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually intended to be a different pkgconf than above propagated pkg-config? Might it make more sense to make a let...in for the whole block and use the same here as in propagation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually intended to be a different pkgconf than above propagated pkg-config?

Yes, the pkg-config in propagated build inputs is the freedesktop's implementation which is currently the default in nixpkgs. We can't use it in the patch-pc.py because it does not support the --path flag. I want to avoid packages having both pkg-config and pkgconf in their nativeBuildInputs so I propagate the pkg-config to ensure that its setup hook is registered while the script uses pkgconf. Once nixpkgs switches to pkgconf this can be simplified to propagating pkgconf and the script using the the program from PATH.

exit 1
fi
mv "$pc.patched" "$pc"
done
)

''
);

#package writers
writers = callPackage ../build-support/writers { };

Expand Down