Skip to content
ALB Wiki

Building from Source

This is an old revision of this page — revision 5 (2026-10-26 12:55:52 UTC). It may differ from the current revision.

Building from source is the original Linux distribution mechanism — before package managers, you got software as a tarball, configured it for your system, compiled it, and installed it. Every LFS chapter is this ritual repeated; every BLFS package, every git clone project, every ancient tarball in an archive.org listing runs the same liturgy. This chapter teaches the liturgy and the build systems that automate it — the universal grammar of "source → installed".

The liturgy — three sacred steps

tar -xf package-1.0.tar.xz
cd package-1.0
./configure --prefix=/usr      # adapt the build to THIS system
make -j$(nproc)                # compile
make check                     # optional testsuite
sudo make install              # install

configure probes the system — compiler present? headers? libraries? — and generates the Makefiles. make builds per the Makefile. make install copies binaries, headers, libraries, docs into --prefix. Three steps; a hundred years of software follows them.

configure — the system's interrogation

./configure --help             # every package's custom options
./configure --prefix=/usr \        # WHERE it installs — the big one
            --sysconfdir=/etc \    # config location
            --localstatedir=/var \ # state location
            --disable-static \     # don't ship .a libraries
            --enable-shared \
            --with-foo --without-bar   # feature toggles

--prefix decides where everything lands: /usr (system), /usr/local (local additions, the classic default), /opt/package (self-contained). The LFS prefix dance — /tools, /usr, DESTDIR — is this flag doing deep work.

make DESTDIR=$STAGING install — the packager's trick: install believing prefix is /usr but landing in $STAGING/usr — how packages get built for one root and assembled for another.

The other build systems

SystemLiturgyFound in
autotools./configure && make && make installThe classic — most tarballs
cmakecmake -B build -DCMAKE_INSTALL_PREFIX=/usr && cmake --build buildKDE, modern C++
mesonmeson setup build --prefix=/usr && ninja -C buildGNOME, systemd-era
plain makemake && make installMinimal projects
cargo/go/npmthe language's ownRust, Go, Node projects

cmake's -B build creates an out-of-tree build dir — the modern way; source tree stays pristine. Meson+ninja is the fastest. Autotools remains the most common on old-school tarballs — and the deepest to understand: configure.ac → autoconf → configure → Makefile.in → make.

What configure actually detects

./configure ... 2>&1 | tee config.log     # keep the log — failures live there
tail -20 config.log                        # when it fails, the answer's here

configure compiles test programs — "checking for gcc... yes" means it ran a hello-world; "checking for foo.h... no" means a test compile failed. The fix is almost always a missing -dev/-devel package: libfoo-dev supplies the headers a build needs that the runtime libfoo doesn't. Debian's -dev, Red Hat's -devel, Slackware ships headers always — the host difference.

Patches — the surgical fixes

patch -p1 < fix.patch          # apply — -p1 strips the a/ b/ prefixes
patch -R -p1 < fix.patch       # reverse — the undo
diff -u old.c new.c > my.patch # create one
diff -Naur old/ new/ > big.patch  # whole trees

LFS's build applies dozens of small patches — FHS placements, security fixes, cross-build fixes — each a surgical diff that upstream hasn't folded in yet.

The configure internals — what it actually runs

./configure --help | less               # every option the package ships
cat config.log                          # the probe transcript — failures live here
less config.status                      # the record of what was detected
cat config.h                            # the generated feature macros

configure is a generated shell script — thousands of small test-compiles: "checking for gcc... yes" means it ran a hello-world; "checking foo.h usability... no" means a test compile failed and the config.log shows the compiler's actual error. config.status --recheck reruns; config.status regenerates the Makefiles.

The DESTDIR/prefix grammar — the packager's two knobs

./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
make
make install                        # writes to /usr, /etc — REAL
# vs:
make DESTDIR=/tmp/stage install     # writes to /tmp/stage/usr — STAGED

--prefix = where the software believes it lives (baked into paths); DESTDIR = where it actually lands (the packager's staging). LFS uses both — --prefix=/usr + DESTDIR=$LFS = "believe /usr, land in $LFS/usr" — exactly where the chroot needs it. The --sysconfdir/--localstatedir pair move /etc and /var away from /usr/etc//usr/var (the classic wrong defaults).

The meson + cmake grammar

# meson — the modern fast one
meson setup build --prefix=/usr --buildtype=release -Dfeature=enabled
ninja -C build
ninja -C build test
DESTDIR=/tmp/stage ninja -C build install
meson configure build -Doption=value   # reconfigure

# cmake — the heavyweight
cmake -B build -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=/usr \
      -DBUILD_TESTING=OFF -DWITH_X=ON
cmake --build build -j$(nproc)
cmake --install build --prefix /tmp/stage/usr
ctest --test-dir build                 # the tests

Both honor DESTDIR; meson's -D toggles the package's own options (meson_options.txt lists them); cmake's -DWITH_*/-DBUILD_* the same.

The canonical builds — what the majors actually look like

# binutils/gcc/glibc — the toolchain trio (LFS chapter 3-5 grammar):
../configure --prefix=/usr --enable-shared --disable-werror
make -j$(nproc); make check || true; make install

# a typical autotools package:
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
            --disable-static --docdir=/usr/share/doc/foo-1.2
make -j$(nproc); make check; make install

# a meson package:
meson setup --prefix=/usr --buildtype=release -Dopt=val build/
ninja -C build/; ninja -C build/ test; ninja -C build/ install

# a cmake package:
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release \
      -DBUILD_TESTING=OFF
cmake --build build -j$(nproc); ctest --test-dir build; cmake --install build

# a kernel module's DKMS-style build:
make -C /lib/modules/$(uname -r)/build M=$PWD modules
make -C /lib/modules/$(uname -r)/build M=$PWD modules_install

# a python package:
pip install --prefix=/usr/local .
python -m build; pip install dist/*.whl

# a rust binary:
cargo build --release; install -Dm755 target/release/foo /usr/bin/foo

# a go binary:
go build -o foo ./cmd/foo; install -Dm755 foo /usr/bin/foo

Each ecosystem's liturgy is the same shape — configure the build dir, build, test, install — the verbs differ.

Dependencies — the real work

The tarball itself is the easy half; the dependencies are the job:

./configure   # missing dep = the error is your answer
apt build-dep package        # Debian's dep-fetcher — installs every build-dep
ldd binary | grep 'not found' # runtime gaps
pkg-config --modversion foo  # is it visible to builds?

apt build-dep, dnf builddep, Slackware's "install everything" — each distro's answer to "install the deps this needs". On LFS, dependency management is the book's ordering — the build sequence is topological.

Version sanity and the release hygiene

make V=1                      # verbose builds — the actual commands
./configure --version         # sometimes tells you what's expected
cat README INSTALL NEWS       # the three files always worth a skim

And the trust layer: gpg --verify package.tar.xz.sig package.tar.xz, md5sum/sha256 checks — the sources are the system's DNA; verify before compiling. This handbook's LFS chapters verify checksums before the first configure — chapter 2 makes it law.

The install aftermath

make install DESTDIR=$D       # staged install — packager's form
make install                  # real install — needs root for /usr
ldconfig                      # refresh the .so registry after new libs
command -v newtool            # did it land in PATH?
make uninstall                # often supported — often doesn't exist

checkinstall/make install into /usr/local keeps hand-built software quarantined from the package manager's /usr — the classic hygiene.

Where source builds go wrong

SymptomCause
configure: foo.h not foundMissing -dev/devel package
config.log cryptic failureRead the last 20 lines — always
Installed but command not found--prefix landed outside PATH — /usr/local/bin?
libtool version fightsThe tarball's ltmain vs the host's — autoreconf -fi
Build works, install fails on permssudo make install — or DESTDIR staging
Old build residue breaks rebuildmake distclean — or re-extract fresh
make fails only parallelRacy Makefile — -j1
.la files breaking downstreamrm the .la files — libtool's legacy hazard

Exercises

  1. Build a real tarball — tar, configure, make, DESTDIR-staged install into /tmp/stage.
  2. tee a configure log; find the exact probe that failed for a missing lib.
  3. apt build-dep or dnf builddep something — see the dep list as a set.
  4. Write and apply a patch — diff -u, patch -p1, then patch -R it back.
  5. Compare a cmake and an autotools build side by side — the different liturgies.

See also

Categories: Compilers · Handbook · Linux · Linux Handbook
This page was last edited 2026-10-26 12:55:52 UTC by devin. What links here