Right, so you’ve downloaded a beautiful tarball of pristine source code. You run ./configure and—oh, look at that—it spews out an error about a missing library. You’re missing a dependency. This is the universal hazing ritual for compiling from source, and it’s about to become your new normal. Don’t panic. This isn’t a failure; it’s just the build system telling you, in its own obtuse way, what it needs for the next step.

The key thing to understand here is the difference between having a library installed and having it available for development. You might have libjpeg on your system so that programs can view JPEGs, but to compile a program that creates JPEGs, you need more. You need the header files (*.h) that tell the compiler what functions are available and the static archive files (*.a) or symlinks for the shared library (*.so) that the linker uses to stitch everything together. This collection of development files is precisely what the -dev or -devel package provides.

The Anatomy of a -dev Package

Let’s make this concrete. On a Debian or Ubuntu system, you’d install the runtime library for libjpeg with libjpeg-turbo8. But try to compile something that needs it, and you’ll hit a wall. The -dev package is the missing piece:

# This gets you the library to RUN programs that use jpeg
sudo apt install libjpeg-turbo8

# This gets you the tools to BUILD programs that use jpeg
sudo apt install libjpeg-turbo8-dev

So what exactly did that -dev package install? Let’s go snooping. The headers usually land in /usr/include.

ls -l /usr/include/j*.h
# You'll see something like jpeglib.h, jerror.h, etc. This is the API contract for the compiler.

The archive and linking files live in /usr/lib or /usr/lib/x86_64-linux-gnu.

ls -l /usr/lib/x86_64-linux-gnu/libjpeg*
# You'll likely see:
# libjpeg.a      - The static archive (linked directly into your binary)
# libjpeg.so     - A symlink to the shared version
# libjpeg.so.8   - The actual shared library (used at runtime)

The -dev package is responsible for all of it except the final libjpeg.so.8 file (that’s the job of the base runtime package). The package manager is essentially a brilliant butler who carefully separates the tools for building from the components for running.

The Distro Divide: -dev vs. -devel

This is where the designers had a chance to standardize and, in classic open-source fashion, chose violence instead. Debian and its derivatives (Ubuntu, Mint) use the -dev suffix. Red Hat and its ecosystem (Fedora, CentOS) use the -devel suffix. It’s a trivial difference that causes maximum annoyance when you’re hopping between systems.

# On Ubuntu/Debian:
sudo apt install libssl-dev

# On Fedora/RHEL/CentOS:
sudo dnf install openssl-devel

Notice the package name itself can also change (libssl-dev vs. openssl-devel). There’s no grand reason for this; it’s just historical baggage. The best practice is to know your distro’s naming convention and use its package search tool relentlessly. For apt, use apt search something-dev. For dnf, use dnf search something-devel.

The Pitfalls and The “I’m Sure I Installed That” Moment

The most common pitfall is thinking you’ve installed the right package but the configure script still fails. This usually happens for one of three reasons:

  1. You installed the runtime library, not the -dev package. This is the classic mistake. Double-check your apt install or dnf install command.
  2. You need a different, older, or newer version of the -dev package. Some software is picky. The error message might say it needs libexample >= 1.2.3 but you only have libexample-dev 1.2.2. You’ll need to find a way to install the newer version, often from a backports repo or by compiling that dependency from source too. Welcome to the dependency rabbit hole.
  3. pkg-config is confused. This is a big one. The ./configure script often uses a helper tool called pkg-config to find the correct compiler and linker flags for a dependency. If you have multiple versions of a library installed or have installed something to a non-standard location (/usr/local/), pkg-config might be looking at the wrong one. You can interrogate it directly:
pkg-config --cflags libjpeg    # Should output -I/usr/include
pkg-config --libs libjpeg      # Should output -ljpeg

If these commands fail or point somewhere weird, that’s your culprit. You might need to set the PKG_CONFIG_PATH environment variable to point to the correct directory containing the .pc file for your library.

The final piece of advice is to embrace the process. The ./configure step isn’t a single command you run; it’s a conversation. You run it, it yells at you, you install what it asks for, and you run it again. Lather, rinse, repeat until it finally shuts up and gives you a Makefile. That’s not inefficiency; that’s you, systematically assembling your exact build environment. Now get to it.