Build Environment Setup
Install essential build tools and verify the toolchain
gcc), the build automation tool (make), the standard C library headers (libc6-dev), and optionally autoconf for projects using the configure script system. On Debian/Ubuntu, build-essential installs all the core tools in one package.
Install the build toolchain (skip if already installed):
Verify each tool is present and note the version:
Check how many CPU cores are available for parallel builds:
Create the lab workspace:
Compiling C Programs with gcc
Write and compile C source files directly — single file and multi-file projects
Write a minimal C program. Use cat with a heredoc to create the source file:
Compile to an executable with gcc. The -o flag specifies the output filename:
The binary is executable (x permission). It is 16KB because it includes the C standard library linked in.
Run the compiled program:
Compile with debugging symbols and warnings enabled (good development practice):
The debug binary is larger due to embedded symbol information. The -Wall and -Wextra flags catch potential bugs at compile time.
Compile with optimization for a production binary (smaller and faster):
Inspect what shared libraries the binary depends on:
This binary dynamically links to libc.so.6 — the C standard library. If this library is missing, the binary will not run.
Writing and Using Makefiles
Create a multi-file project with a Makefile for automated builds and cleaning
gcc on each file becomes error-prone. make reads a Makefile that describes build rules, dependencies, and targets. It only recompiles files that have changed — a critical optimization for large codebases.
Create a simple multi-file C project. First, a utility header and implementation:
Create the Makefile. Note: Makefiles require TABS (not spaces) before commands:
Build the project using make:
Run make again to see incremental build detection (nothing recompiles):
Make checks timestamps. Since no source file is newer than the target, it skips compilation. This saves enormous time on large projects.
Modify a source file and observe that only the affected file recompiles:
Only utils.c was recompiled. main.o was reused from the previous build.
Use make clean to remove all build artifacts:
| Target | Purpose | Notes |
|---|---|---|
| make / make all | Build the default target | Defined first in Makefile |
| make clean | Remove .o files and binaries | Keeps source files |
| make distclean | Remove everything including configure output | Returns to pristine state |
| make install | Copy binary to system paths | Usually requires sudo |
| make uninstall | Remove installed files | Not all projects implement this |
| make check / test | Run the test suite | Project-dependent |
| make -j$(nproc) | Parallel build using all cores | Dramatically speeds large projects |
The configure / make / make install Pipeline
Work through the full autoconf build pipeline with a custom install prefix
configure probes the system and generates a Makefile. make compiles. make install deploys the binary to the prefix path.
Create a minimal autoconf project to simulate a real-world source download:
Create a configure.ac file (the input to autoconf) and Makefile.am (the input to automake):
Generate the configure script from configure.ac using autoreconf:
Run ./configure with a custom --prefix to install into ~/local instead of /usr/local. This avoids needing root permissions:
Compile the project using parallel make:
Install to the custom prefix and test the installed binary:
Diagnosing and Fixing Build Errors
Identify and resolve the three most common compile failures
Error Type 1: Missing Header File — The most common configure failure
Fix: Install the
-dev package that provides the headers.Error Type 2: Linker Error (undefined reference)
sqrt() was used in source code but the math library was not linked.Fix: Add
-lm to the gcc command to link libm.Error Type 3: Library Not Found at Runtime (after successful compilation)
Fix: Run
ldconfig to refresh the linker cache, or set LD_LIBRARY_PATH.Use pkg-config to automatically get the correct flags for installed libraries:
Inspect a compiled binary to understand its shared library dependencies:
sudo make install (which installs files invisibly with no package manager tracking), use checkinstall to wrap the installation into a .deb package. This lets you track, upgrade, or remove the custom-compiled software cleanly: sudo checkinstall --pkgname=myutil --pkgversion=1.0. Install it with: sudo apt install checkinstall.
Lab Complete
You set up the build toolchain, compiled C programs directly with gcc, wrote and used a Makefile for a multi-file project, ran the full configure/make/make install pipeline with a custom prefix, and diagnosed the three most common build errors.