Building from Source
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 \
--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
| System | Liturgy | Found in |
|---|---|---|
| autotools | ./configure && make && make install | The classic — most tarballs |
| cmake | cmake -B build -DCMAKE_INSTALL_PREFIX=/usr && cmake --build build | KDE, modern C++ |
| meson | meson setup build --prefix=/usr && ninja -C build | GNOME, systemd-era |
| plain make | make && make install | Minimal projects |
| cargo/go/npm | the language's own | Rust, 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.
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
| Symptom | Cause |
|---|---|
configure: foo.h not found | Missing -dev/devel package |
config.log cryptic failure | Read the last 20 lines — always |
Installed but command not found | --prefix landed outside PATH — /usr/local/bin? |
libtool version fights | The tarball's ltmain vs the host's — autoreconf -fi |
| Build works, install fails on perms | sudo make install — or DESTDIR staging |
| Old build residue breaks rebuild | make distclean — or re-extract fresh |
make fails only parallel | Racy Makefile — -j1 |
| .la files breaking downstream | rm the .la files — libtool's legacy hazard |
Exercises
- Build a real tarball —
tar,configure,make, DESTDIR-staged install into/tmp/stage. teea configure log; find the exact probe that failed for a missing lib.apt build-depordnf builddepsomething — see the dep list as a set.- Write and apply a patch —
diff -u,patch -p1, thenpatch -Rit back. - Compare a cmake and an autotools build side by side — the different liturgies.