Building from Source#

Concepts#

Why Build from Source?#

Occasionally you need to compile software yourself:

  • The package is not available in any repository, Snap, or Flatpak
  • You need a newer version than what is available
  • You need to enable specific compile-time options
  • You are developing or patching the software
  • You are autistic

Building from source means downloading the program’s source code and compiling it into a binary executable on your machine.

Prerequisites: Build Tools#

Before compiling anything, install the essential build tools:

sudo apt install -y build-essential

This installs:

  • gcc / g++ — the C/C++ compilers
  • make — the build automation tool
  • libc-dev — C library headers
  • dpkg-dev — Debian package development tools

Many programs also need additional libraries. These are called build dependencies and vary by program.

The Standard Build Process#

Most C/C++ projects follow the same three-step process:

# 1. Configure — check your system, detect libraries, set options
./configure

# 2. Compile — build the binaries from source code
make

# 3. Install — copy binaries to system directories
sudo make install

Step 1: Download and Extract#

Source code is usually distributed as a compressed archive (tarball):

# Download
wget https://example.com/program-1.2.3.tar.gz

# Extract
tar -xzf program-1.2.3.tar.gz

# Enter the source directory
cd program-1.2.3/

Common archive formats:

Extension Extract Command
.tar.gz or .tgz tar -xzf file.tar.gz
.tar.bz2 tar -xjf file.tar.bz2
.tar.xz tar -xJf file.tar.xz
.zip unzip file.zip

Step 2: Configure#

./configure

The configure script:

  • Checks that your system has the required compilers and libraries
  • Detects system-specific settings (architecture, paths)
  • Generates a Makefile tailored to your system

Common options:

./configure --prefix=/usr/local        # install to /usr/local (default)
./configure --prefix=/opt/myprogram    # install to a custom location
./configure --enable-feature           # enable optional feature
./configure --disable-feature          # disable optional feature
./configure --with-library=/path       # specify library location
./configure --help                     # show all options

If configure fails, it will tell you what is missing. Common fix:

# Missing library "libfoo"? Install its development package:
sudo apt install libfoo-dev
# Then rerun ./configure

Development packages (ending in -dev) contain header files and libraries needed for compilation but not for running the program.

Step 3: Compile#

make

This compiles the source code. It can take a while for large projects. To speed it up, use multiple CPU cores:

make -j$(nproc)
# nproc returns the number of CPU cores

Step 4: Install#

sudo make install

This copies the compiled files to the system (default: /usr/local/bin, /usr/local/lib, /usr/local/share).

The /usr/local Prefix#

By default, make install puts files in /usr/local/:

Directory Content
/usr/local/bin/ Executable programs
/usr/local/lib/ Libraries
/usr/local/share/ Documentation, data files
/usr/local/include/ Header files
/usr/local/etc/ Configuration files

This keeps manually installed software separate from APT-managed software (which lives in /usr/). This is intentional — it prevents conflicts.

The Problem with make install#

make install copies files directly to your system with no tracking. The package manager (apt/dpkg) does not know about these files. This means:

  • dpkg -S /usr/local/bin/myprogram will not find it
  • There is no clean way to uninstall (some Makefiles support sudo make uninstall, many do not)
  • APT cannot track or update it

checkinstall — A Better Approach#

checkinstall wraps make install and creates a .deb package that dpkg tracks:

sudo apt install -y checkinstall

# Instead of: sudo make install
# Use:
sudo checkinstall --pkgname=myprogram --pkgversion=1.2.3 --default

Benefits:

  • Creates a .deb package and installs it via dpkg
  • You can uninstall with sudo apt remove myprogram
  • dpkg -L myprogram shows all installed files

CMake-Based Projects#

Some projects use CMake instead of ./configure:

sudo apt install -y cmake

mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install

CMake is more modern and supports out-of-source builds (keeping the build files separate from the source).

Other Build Systems#

Build System Typical Commands Language
make / configure ./configure && make && sudo make install C/C++
CMake cmake .. && make && sudo make install C/C++
Meson meson setup build && ninja -C build && sudo ninja -C build install C/C++/Rust
Python pip install . or pip install package Python
Rust cargo build --release Rust
Go go build Go

Cleaning Up Build Artifacts#

# Remove compiled objects (in the source directory)
make clean

# Remove everything configure generated
make distclean

# Remove the source directory entirely when done
cd ..
rm -rf program-1.2.3/

Lab#

Exercise 1: Install Build Tools#

# Install the essential build toolchain
sudo apt install -y build-essential

# Verify
gcc --version
make --version

Exercise 2: Build a Simple C Program#

Before building a real project, let’s compile a C program from scratch to understand the process:

mkdir -p ~/lab/build
cd ~/lab/build

# Write a simple C program
cat > hello.c << 'EOF'
#include <stdio.h>

int main() {
    printf("Hello from a compiled C program!\n");
    printf("Compiled on this machine with GCC.\n");
    return 0;
}
EOF

# Compile it
gcc -o hello hello.c

# Run it
./hello

# See the file type
file hello
# ELF 64-bit LSB pie executable...

Exercise 3: Build with a Makefile#

cd ~/lab/build

# Create a Makefile
cat > Makefile << 'EOF'
CC = gcc
CFLAGS = -Wall -O2
PREFIX = /usr/local

hello: hello.c
	$(CC) $(CFLAGS) -o hello hello.c

install: hello
	install -m 755 hello $(PREFIX)/bin/hello

uninstall:
	rm -f $(PREFIX)/bin/hello

clean:
	rm -f hello
EOF

# Build using make
make

# Run
./hello

# Clean
make clean
ls hello    # should be gone

# Rebuild
make

Exercise 4: Build a Real Project (htop from Source)#

cd ~/lab/build

# Install build dependencies for htop
sudo apt install -y libncursesw5-dev autoconf automake

# Download htop source
apt source htop 2>/dev/null || (
    wget https://github.com/htop-dev/htop/releases/download/3.3.0/htop-3.3.0.tar.xz
    tar -xJf htop-3.3.0.tar.xz
    cd htop-3.3.0
)

# If using apt source:
cd htop-*/

# Configure
./autogen.sh 2>/dev/null  # some projects need this first
./configure --prefix=/usr/local

# Compile (using all CPU cores)
make -j$(nproc)

# Test the binary (without installing)
./htop
# Press q to quit

# Do NOT install system-wide for this exercise
# In a real scenario: sudo make install
# Or better: sudo checkinstall

Exercise 5: Use checkinstall (Optional)#

# Install checkinstall
sudo apt install -y checkinstall

# In the htop source directory (from Exercise 4):
# Instead of sudo make install:
sudo checkinstall --pkgname=htop-custom --pkgversion=3.3.0 --default

# Now it's tracked by dpkg
dpkg -l htop-custom
dpkg -L htop-custom

# Uninstall cleanly
sudo apt remove htop-custom

Exercise 6: Clean Up#

cd ~
rm -rf ~/lab/build

Review#

1. What are the three standard steps to build software from source?

./configure (detect system settings and dependencies), make (compile the source code), sudo make install (install the compiled files to the system).

2. What does the `./configure` script do?

It checks your system for required compilers, libraries, and headers. It detects architecture-specific settings and generates a Makefile customized for your system. If it fails, it tells you which dependencies are missing.

3. What is a `-dev` package and when do you need it?

A -dev (development) package contains header files (.h) and library files needed at compile time. You need them when building software from source — they are not needed for running pre-built packages.

4. Why does `make install` default to `/usr/local/`?

To keep manually compiled software separate from packages managed by apt/dpkg (which install to /usr/). This prevents conflicts between the two.

5. What is the problem with `sudo make install`?

It installs files directly without any tracking. The package manager does not know about them, making it hard to uninstall or update. There is no reliable way to know which files belong to the program.

6. How does `checkinstall` improve on `make install`?

checkinstall creates a .deb package from the installation, registers it with dpkg, and then installs it. This means you can uninstall cleanly with apt remove, and dpkg -L shows all installed files.

7. How do you speed up compilation on a multi-core system?

Use make -j$(nproc). The -j flag tells make to run multiple jobs in parallel. $(nproc) expands to the number of CPU cores available.


Previous: Snap, Flatpak, and AppImage | Next: nano