From 43afb140c94237d2174f04e1656c12bf0e1da38c Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Fri, 10 Jul 2026 14:51:05 -0700 Subject: [PATCH] dev: make plain 'go test' work on Linux with no env vars Three pieces: - hack/pkg-config.sh: a pkg-config wrapper that injects the meson build dir's pkgconfig paths, but only for modules our build provides (streamplacedeps, glib/gstreamer families, lpms's libav*). On Linux --libs answers also gain -lm and an old-style rpath to the build dir, so ld resolves transitive .so deps at link time and test binaries run without LD_LIBRARY_PATH. Explicit PKG_CONFIG_PATH still wins, and everything else passes through untouched. - make dev-setup now registers the wrapper via 'go env -w PKG_CONFIG=...', which Go persists in its own per-user config -- no shell env needed, and gopls picks it up too. - streamplacedeps.pc gains -Wl,-rpath,${libdir} -Wl,--disable-new-dtags on Linux shared builds; DT_RPATH (unlike DT_RUNPATH) also covers transitive loads like libgobject -> libffi and libavcodec -> libswresample, whose meson-built libraries carry no rpath. Note: a build dir produced inside a container (prefix=/app) bakes wrong paths into every .pc file; rerun 'make dev-setup' natively after using one. Co-Authored-By: Claude Fable 5 --- Makefile | 7 +++++ hack/pkg-config.sh | 67 ++++++++++++++++++++++++++++++++++++++++++++++ meson.build | 13 ++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100755 hack/pkg-config.sh diff --git a/Makefile b/Makefile index e8c331f5b..659cc12ff 100644 --- a/Makefile +++ b/Makefile @@ -292,6 +292,13 @@ test-vod: .PHONY: dev-setup dev-setup: $(MAKE) -j16 app-cached dev-setup-meson + $(MAKE) dev-go-env + +# Point Go's cgo at our pkg-config wrapper (persisted in `go env`, no shell +# env vars needed) so plain `go test ./pkg/...` finds the meson-built deps. +.PHONY: dev-go-env +dev-go-env: + go env -w PKG_CONFIG=$(shell pwd)/hack/pkg-config.sh .PHONY: dev dev: app-cached $(LEXICON_STAMP) diff --git a/hack/pkg-config.sh b/hack/pkg-config.sh new file mode 100755 index 000000000..4d767bd4a --- /dev/null +++ b/hack/pkg-config.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +# pkg-config wrapper so `go test ./pkg/...` works with no environment variables. +# +# `make dev-setup` registers this via `go env -w PKG_CONFIG=/hack/pkg-config.sh`, +# which Go persists in its own config (~/.config/go/env) — no shell env needed, +# and gopls/IDEs pick it up too. +# +# When cgo asks for a package that streamplace's meson build provides +# (streamplacedeps itself, the glib/gstreamer modules requested by +# go-glib/go-gst, or the libav* modules requested by lpms), we add the build +# directory's pkgconfig paths so the freshly-built copies are found. Everything +# else passes through untouched. An explicitly-set PKG_CONFIG_PATH always takes +# precedence over the injected paths, so other projects that point cgo at +# their own ffmpeg/glib (e.g. go-livepeer) keep working. + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +OS="$(uname -s | tr '[:upper:]' '[:lower:]')" +ARCH="$(uname -m)" +case "$ARCH" in +x86_64) ARCH=amd64 ;; +aarch64 | arm64) ARCH=arm64 ;; +esac +BUILDDIR="${STREAMPLACE_BUILDDIR:-$ROOT/build-$OS-$ARCH}" + +inject=no +for arg in "$@"; do + case "$arg" in + streamplacedeps | streamplacedeps-uninstalled) inject=yes ;; + glib-2.0 | gobject-2.0 | gio-2.0 | gio-unix-2.0 | gthread-2.0) inject=yes ;; + gmodule-2.0 | gmodule-no-export-2.0 | gmodule-export-2.0) inject=yes ;; + gstreamer-*) inject=yes ;; + libavcodec | libavdevice | libavfilter | libavformat | libavutil) inject=yes ;; + libswscale | libswresample | libpostproc) inject=yes ;; + esac +done + +if [ "$inject" = yes ]; then + if [ ! -d "$BUILDDIR/lib/pkgconfig" ]; then + echo "hack/pkg-config.sh: $BUILDDIR/lib/pkgconfig not found; run 'make dev-setup' first" >&2 + fi + PKG_CONFIG_PATH="${PKG_CONFIG_PATH:+$PKG_CONFIG_PATH:}$BUILDDIR/lib/pkgconfig:$BUILDDIR/lib/gstreamer-1.0/pkgconfig:$BUILDDIR/meson-uninstalled" + export PKG_CONFIG_PATH + + # On Linux, --libs queries additionally get an rpath to the build dir (so + # ld can resolve transitive .so deps at link time and binaries run without + # LD_LIBRARY_PATH; --disable-new-dtags makes the rpath cover transitive + # loads) plus -lm (lpms uses libm symbols but ffmpeg's .pc files only list + # it in Libs.private). Not all cgo packages pull in streamplacedeps, whose + # .pc carries the same flags, so every injected --libs answer needs them. + if [ "$OS" = linux ]; then + wantlibs=no + for arg in "$@"; do + case "$arg" in + --libs) wantlibs=yes ;; + esac + done + if [ "$wantlibs" = yes ]; then + out="$(pkg-config "$@")" || exit $? + printf '%s -lm -Wl,-rpath,%s/lib -Wl,--disable-new-dtags\n' "$out" "$BUILDDIR" + exit 0 + fi + fi +fi + +exec pkg-config "$@" diff --git a/meson.build b/meson.build index 70034a02d..9150734f9 100644 --- a/meson.build +++ b/meson.build @@ -239,9 +239,20 @@ if host_machine.system() == 'windows' endif pkg = import('pkgconfig') +streamplacedeps_extra_link_args = [] +if host_machine.system() == 'linux' and get_option('default_library') == 'shared' + # bake an rpath into everything that links streamplacedeps, so dev binaries + # (including `go test` binaries) find the build-dir shared libraries at + # runtime without LD_LIBRARY_PATH. macOS doesn't need this: dylibs carry + # absolute install names. --disable-new-dtags makes it an old-style DT_RPATH + # rather than DT_RUNPATH: unlike RUNPATH, the executable's RPATH also covers + # transitive loads (e.g. libgobject -> libffi, libavcodec -> libswresample), + # whose meson-built .so files carry no rpath of their own. + streamplacedeps_extra_link_args += ['-Wl,-rpath,${libdir}', '-Wl,--disable-new-dtags'] +endif pkg.generate( name: 'streamplacedeps', - libraries: [streamplace_deps, iroh_streamplace_dep], + libraries: [streamplace_deps, iroh_streamplace_dep, streamplacedeps_extra_link_args], description: 'all streamplace dependencies bundled for easy inclusion', ) -- 2.51.2