What are pkg-config for Cross Toolchains-Embedded Linux Course In Hyderabad

pkg-config for Cross Toolchains
Finding the right compiler and linker flags automatically in a free embedded Linux course
15 min read
Beginner → Intermediate
Chapter 2 · Toolchains
pkg-config
cross compiling
sysroot
free linux kernel development course
free embedded systems course
PKG_CONFIG_LIBDIR

If you’ve been following along in this free embedded Linux course, you already know how to link a
library by hand with -l and -I flags. That’s fine for one library. It falls apart the moment
a library needs a dozen dependent flags, or when different versions of the same library expect different linker
arguments. This is exactly the problem pkg-config solves, and understanding it properly is essential
for anyone serious about a free linux kernel development course or embedded build pipeline, because
every non-trivial cross-compiled project — from a simple database client to a full Yocto image — leans on it.

What You Will Learn

  • Why manually tracking compiler/linker flags breaks down as projects grow
  • How pkg-config’s .pc metadata files work
  • Why the default pkg-config search fails for cross toolchains, and how to fix it with PKG_CONFIG_LIBDIR
  • How to build a working cross-compile command using pkg-config’s output
  • How to write your own .pc file for a library you ship

Prerequisites

  • A working cross toolchain with a populated sysroot (covered earlier in this course)
  • Comfort with basic gcc invocation and environment variables
  • Familiarity with the concept of a sysroot as the toolchain’s private “root filesystem” for headers and libraries

The Problem pkg-config Solves

Say a library needs three link flags and two include paths, and depends on two other libraries which each need
their own flags. Hard-coding all of that into every Makefile that uses the library is fragile: the moment the
library is rebuilt with a different configuration, every consumer’s build flags go stale. pkg-config fixes this by
having each library ship a small text file describing its own flags, so consumers just ask pkg-config instead of
hard-coding anything.

Anatomy of a .pc File

Every library that supports pkg-config installs a <name>.pc file, normally under
/usr/lib/pkgconfig or /usr/lib/<arch>/pkgconfig. Here’s a minimal, original example
for a small logging library — not copied from any book, just illustrating the format:

eplog.pc — a minimal pkg-config metadata file
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: eplog
Description: Tiny structured logging library for embedded targets
Version: 1.2.0
Libs: -L${libdir} -leplog
Libs.private: -lpthread
Cflags: -I${includedir}
Field Meaning
Libs Public link flags every consumer needs
Libs.private Extra flags needed only for static linking
Cflags Include-path and preprocessor flags
Requires Other pkg-config packages this one depends on (pulls their flags in too)

Querying Flags With pkg-config

Once a .pc file exists, any consumer can ask pkg-config for the flags instead of hard-coding them:

Querying eplog’s flags
$ pkg-config eplog --cflags --libs
-I/usr/include -L/usr/lib -leplog

That’s the whole point: one command, always up to date, regardless of how the library’s internal layout changes.

Where It Breaks: Cross Compiling

Run that same command while building for an ARM target and it usually fails outright, or worse, silently returns
host flags:

pkg-config searching the wrong place
$ pkg-config eplog --cflags --libs
Package eplog was not found in the pkg-config search path.
Perhaps you should add the directory containing `eplog.pc'
to the PKG_CONFIG_PATH environment variable
No package 'eplog' found

pkg-config, by default, only looks inside the host’s pkgconfig directories. Your cross-compiled
copy of eplog and its .pc file live inside the toolchain’s sysroot, in a completely different location
that pkg-config never checks unless told to.

The Fix: PKG_CONFIG_LIBDIR

Point pkg-config directly at the sysroot’s pkgconfig directory using the PKG_CONFIG_LIBDIR
environment variable. Note this is LIBDIR, not PATH — setting PKG_CONFIG_PATH
instead adds to the search, while PKG_CONFIG_LIBDIR replaces it, which is what you want for cross
builds so host libraries never leak in.

Pointing pkg-config at the sysroot
$ export SYSROOT=$(arm-none-linux-gnueabihf-gcc -print-sysroot)
$ export PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig

$ pkg-config eplog --cflags --libs
-I/usr/include -L/usr/lib -leplog

Now the flags returned are correct for the target, not the host. Chain that straight into your compile command:

Full cross-compile using pkg-config output
$ PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig \
  arm-none-linux-gnueabihf-gcc $(pkg-config eplog --cflags --libs) \
  eplog-demo.c -o eplog-demo

$ file eplog-demo
eplog-demo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)

Common Mistakes

  • Using PKG_CONFIG_PATH instead of PKG_CONFIG_LIBDIR for cross builds — this appends to the
    default search path rather than replacing it, so host .pc files can still be picked up and silently
    break the build.
  • Forgetting to export the variable in every shell/CI step that invokes pkg-config, not just the
    one where it was set.
  • Hand-editing prefix in .pc files after copying them between machines instead of letting the
    build system regenerate them — this reintroduces exactly the staleness pkg-config was meant to prevent.

Best Practices

  • Set PKG_CONFIG_LIBDIR once per cross-build session (a wrapper script or environment setup file
    works well) rather than per command.
  • Never let host and target pkgconfig directories mix in the same search — always use LIBDIR, not PATH, for
    cross work.
  • If you ship your own library, ship a .pc file with it — it costs a few lines and saves every
    downstream consumer from guessing your flags.

Summary / Key Takeaways

  • pkg-config replaces hand-maintained compiler/linker flags with a queryable, versioned metadata file per library.
  • Cross builds must redirect pkg-config into the sysroot using PKG_CONFIG_LIBDIR, not
    PKG_CONFIG_PATH.
  • Once redirected, pkg-config’s output can be substituted directly into the cross gcc invocation.

Conclusion

pkg-config looks like a small utility, but skipping it — and hard-coding flags instead — is one of the fastest
ways to make a cross-compiled project unmaintainable. Once PKG_CONFIG_LIBDIR is set correctly for your
sysroot, every library that ships a .pc file becomes trivial to link against, on host or target alike.

FAQ

Why does pkg-config default to host paths even when I’m cross compiling?

pkg-config has no built-in concept of “target” versus “host” — it just searches a fixed default location unless
told otherwise. Redirecting it is the build system’s job, via PKG_CONFIG_LIBDIR.

What’s the difference between PKG_CONFIG_PATH and PKG_CONFIG_LIBDIR?

PKG_CONFIG_PATH is additive — it extends the default search. PKG_CONFIG_LIBDIR
replaces the default search entirely, which is what you want for cross builds so host packages can’t leak in.

Do I need a .pc file for every library I cross compile?

No — only libraries that ship pkg-config support have one. Plenty of smaller libraries still expect manual
-l/-I flags, and that’s fine.

Can build systems like Buildroot and Yocto set this up for me?

Yes — both configure PKG_CONFIG_LIBDIR (or an equivalent wrapper) automatically as part of their
cross environment, which is one reason they’re recommended over fully manual cross compiling for larger projects.

What happens if I forget to set PKG_CONFIG_LIBDIR?

Best case, pkg-config reports the package as not found. Worst case, it silently finds a same-named host package
and hands you host flags, producing a binary that fails to link or run on the target.

Keep Building Your Toolchain Skills

Next up: the classic pain points of cross compiling real-world packages — and when to stop doing it by hand.

 

Leave a Reply

Your email address will not be published. Required fields are marked *