Cross-compile sysroot missing headers: how to fill them
When a cross build dies with xxx.h: No such file or directory, the compiler is often looking at the host /usr/include or the sysroot simply lacks target headers. Use GCC’s --sysroot (Directory Options) and pkg-config’s PKG_CONFIG_SYSROOT_DIR (cross-compiling notes) to fix search paths, then populate the sysroot with headers, link libraries, and .pc files from the board root (or an SDK). CMake toolchain wiring and CMAKE_SYSROOT stay in aarch64-cmake-toolchain — this post only covers filling missing headers. No affiliate, no invented board stories, no pricing.
What signals a broken include path?
One-line answer: Separate file missing from looking at the host. With --sysroot=dir, standard header/lib searches move to dir/usr/include and dir/usr/lib. -I/-L arguments that start with = or $SYSROOT get that prefix replaced by the sysroot (GCC Directory Options).
Typical signals:
| Signal | Usual meaning |
|---|---|
fatal error: foo.h: No such file or directory | Header absent under sysroot, or --sysroot/-isysroot not applied |
-E -v / -v shows only host /usr/include | Sysroot never reached the cross driver |
| Headers found but wrong ABI/arch | Host headers/libs were picked — a dangerous “success” |
pkg-config --cflags returns bare /usr/include/... | .pc was read without sysroot prefixing |
Checks (adjust the toolchain prefix):
# Dump include search order (preprocess only)
aarch64-linux-gnu-gcc --sysroot=/opt/target-sysroot -E -v -xc /dev/null
# Where does a header live under the sysroot?
find /opt/target-sysroot -name 'foo.h' 2>/dev/null
GCC essentials:
--sysroot=dir: Logical root for headers and libraries —/usr/includebecomesdir/usr/include.-isysroot: Alternate root for headers only (libraries keep--sysrootwhen both are set).-I=/usr/include/fooor-I$SYSROOT/usr/include/foo:=/$SYSROOTexpands to the sysroot prefix. A raw-I/usr/...can still hit the host.
If build logs keep showing unprefixed -I/usr/include, the flag generator (pkg-config or a CMake find) ignored the cross setup. For CMake auto-passing --sysroot, see only CMAKE_SYSROOT in aarch64-cmake-toolchain.
How do you align pkg-config?
One-line answer: Set PKG_CONFIG_SYSROOT_DIR to the sysroot, point PKG_CONFIG_LIBDIR at the target’s .pc directories, and clear PKG_CONFIG_PATH so host packages do not mix in. The sysroot is injected into -I/-L style output; paths from --variable are generally not prefixed (autotools.info, pkgconf(1)).
Minimal environment:
export SYSROOT=/opt/target-sysroot
export PKG_CONFIG_SYSROOT_DIR=$SYSROOT
export PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig
# If multiarch: also $SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_PATH=
Verify:
pkg-config --exists libfoo && echo ok
pkg-config --cflags libfoo
pkg-config --libs libfoo
If --cflags/--libs show -I/-L with $SYSROOT prepended, prefix injection is working. Bare host /usr/lib/pkgconfig hits mean LIBDIR/PATH are wrong.
Notes:
- Wrapper: A
${CHOST}-pkg-configscript that exports the variables above then execs pkg-config is the documented pattern when host and target queries must not share one environment. --variable=: Often without sysroot. Feeding those paths into compile flags leaks host locations..pccontents: Keep target-styleprefix=/usr; do not hard-code host absolutes. Discovery isLIBDIR; prefixing isSYSROOT_DIR.- Meson/CMake: Point the cross file / toolchain at a target pkg-config (or wrapper) rather than relying on ambient env alone. CMake find modes belong in the toolchain post’s
CMAKE_FIND_ROOT_PATH_MODE_*.
What do you copy from a board image?
One-line answer: A sysroot is the development slice of the target root filesystem. Prefer an SDK/sysroot artifact when the distro provides one; when filling from a board root or rootfs image, keep the layout and copy headers, link libraries, and pkg-config — not the entire runtime tree.
Usual paths (relative to the sysroot root):
| Path | Role |
|---|---|
usr/include/ | C/C++ headers (and per-package subdirs) |
lib/, usr/lib/ | Shared libs, linker scripts, .so symlinks |
usr/lib/*/pkgconfig/, usr/share/pkgconfig/ | .pc files (include multiarch libdirs when used) |
(as needed) usr/lib/*.a | Static archives only if you static-link |
(as needed) lib64 / usr/lib/<triplet>/ | Mirror the target’s multiarch/lib64 layout as-is |
Often omit for a build sysroot:
/home,/var/log, caches, journals- Full
/devnode trees, mounted/proc//syscontents - Expecting headers from a runtime-only image with no
-dev/-develpackages — headers come from devel packages, an SDK, or a staged install tree
Example sync (preserve links/permissions):
SYSROOT=/opt/target-sysroot
ROOTFS=/mnt/target-rootfs # mounted rootfs or NFS-exported board root
mkdir -p "$SYSROOT"
rsync -a --delete \
--include='usr/include/***' \
--include='lib/***' \
--include='usr/lib/***' \
--include='usr/share/pkgconfig/***' \
--include='usr/' --include='lib/' --include='usr/share/' \
--exclude='*' \
"$ROOTFS/" "$SYSROOT/"
Tune filters to the distro layout. The rule is: if the target had /usr/include/foo.h, the sysroot must have …/usr/include/foo.h. --sysroot only remaps that logical root.
After filling:
test -f "$SYSROOT/usr/include/stdio.h" && echo libc-headers-ok
pkg-config --exists libfoo && pkg-config --cflags libfoo
aarch64-linux-gnu-gcc --sysroot="$SYSROOT" -E -xc - <<'EOF'
#include <foo.h>
EOF
If Yocto/Buildroot (or a vendor SDK) already exports a sysroot, prefer that artifact over ad-hoc board copies so versions stay consistent. The copy steps above are the minimum when headers exist on the image but the host sysroot tree is empty.
Frequently asked questions
Q. Headers only, skip .so files?
Preprocess-only checks may pass with headers alone; link still needs .so files, linker scripts, and often -l from .pc. If include errors vanish and undefined references appear, restore the library/symlink tree.
Q. Can I just -I a host path?
It may compile once and still pull host headers. Prefer --sysroot standard paths or GCC’s =/… / $SYSROOT forms.
Q. Is PKG_CONFIG_SYSROOT_DIR enough alone?
Often no. Which .pc files you read (LIBDIR/PATH) is separate from prefixing those paths (SYSROOT_DIR). Reading a host .pc and prefixing it yields nonsense paths.
Q. Rewrite the CMake toolchain here?
No. Compilers, CMAKE_SYSROOT, and find modes stay in aarch64-cmake-toolchain. This post is only what to put inside that sysroot directory.
What should you remember?
- Signals: missing header vs host include —
gcc --sysroot=… -E -vandfind $SYSROOT -name '*.h'. - pkg-config:
PKG_CONFIG_SYSROOT_DIR+ targetPKG_CONFIG_LIBDIR, emptyPKG_CONFIG_PATH— confirm--cflagscarries the sysroot. - Populate: copy
usr/include,lib/usr/lib, andpkgconfigwith layout preserved; check whether a runtime image even has-devheaders. - CMake wiring: short link only — aarch64-cmake-toolchain.
Sources: GCC Directory Options (--sysroot, -isysroot), pkg-config cross-compiling, pkgconf(1) PKG_CONFIG_SYSROOT_DIR.