How to write install and uninstall targets that copy built artifacts to standard system directories, using the conventions that package builders expect.
You might wonder why install rules use the install command instead of cp. Three reasons:
- Permissions in one step --
install -m 755 greeter /usr/local/bin/copies the file and sets it executable atomically. - Directory creation --
install -d /usr/local/bincreates the directory (and parents) if it doesn't exist, likemkdir -p. - Convention -- every Unix Makefile uses
install. Package build systems (dpkg-buildpackage,rpmbuild,brew) expect it.
install -m 755 $(TARGET) $(DESTDIR)$(BINDIR) # executable: rwxr-xr-x
install -m 644 $(LIB) $(DESTDIR)$(LIBDIR) # library: rw-r--r--
install -m 644 include/greet.hpp $(DESTDIR)$(INCDIR) # header: rw-r--r--| Variable | Default | Contents |
|---|---|---|
PREFIX |
/usr/local |
Base directory for all installed files |
BINDIR |
$(PREFIX)/bin |
Executables |
LIBDIR |
$(PREFIX)/lib |
Static and shared libraries |
INCDIR |
$(PREFIX)/include |
Public headers (often in a subdirectory like include/greetlib/) |
PREFIX is declared with ?= so users can override it:
make install PREFIX=/opt/myappThis installs to /opt/myapp/bin/, /opt/myapp/lib/, etc.
DESTDIR is not the same as PREFIX. It is a staging root prepended to every path, used by package builders to install into a temporary directory tree without touching the real filesystem.
make install DESTDIR=/tmp/stagingThis creates:
/tmp/staging/usr/local/bin/greeter
/tmp/staging/usr/local/lib/libgreet.a
/tmp/staging/usr/local/include/greetlib/greet.hpp
The package builder then wraps that tree into a .deb, .rpm, or similar. The final user installs the package, which unpacks into the real /usr/local/.
The key difference: PREFIX affects the runtime paths baked into the software. DESTDIR only affects where files land during make install -- the software still thinks it lives at PREFIX.
The uninstall target removes exactly what install put in place:
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(notdir $(TARGET))
rm -f $(DESTDIR)$(LIBDIR)/$(notdir $(LIB))
rm -f $(DESTDIR)$(INCDIR)/greet.hpp
-rmdir $(DESTDIR)$(INCDIR) 2>/dev/null || trueThe -rmdir at the end removes the greetlib/ subdirectory, but only if it's empty. The leading - tells Make to ignore the error if the directory isn't empty (other software may have installed headers there too). The || true ensures the shell exit code is zero regardless.
The uninstall target uses $(notdir) to strip the directory prefix from a path. Make provides four path-dissection functions:
| Function | Input | Output | Use case |
|---|---|---|---|
$(notdir path) |
build/greeter |
greeter |
Get the filename for install/uninstall |
$(dir path) |
build/greeter |
build/ |
Get the directory part (includes trailing /) |
$(basename path) |
build/greeter |
build/greeter |
Strip the suffix (no .ext here, so unchanged) |
$(suffix path) |
build/libgreet.a |
.a |
Get the file extension |
Run make debug to see these in action with the actual values from this Makefile.
CMake has built-in install support that handles PREFIX, DESTDIR, permissions, and platform differences automatically:
install(TARGETS greeter DESTINATION bin)
install(TARGETS greet ARCHIVE DESTINATION lib)
install(FILES include/greet.hpp DESTINATION include/greetlib)Running cmake --install build --prefix /opt/myapp does the equivalent of make install PREFIX=/opt/myapp. CMake also generates an uninstall target and handles RPATH fixup for shared libraries.
cd 24_install_target
make # build the executable and library
./build/greeter World # test it locally
make debug # see path functions and install paths
make install PREFIX=/tmp/test_install # install to a safe location
ls /tmp/test_install/bin/ # greeter is there
ls /tmp/test_install/lib/ # libgreet.a is there
ls /tmp/test_install/include/greetlib/ # greet.hpp is there
/tmp/test_install/bin/greeter Make # run from installed location
make uninstall PREFIX=/tmp/test_install # clean up
rm -rf /tmp/test_install # remove the test directory
make clean| File | Purpose |
|---|---|
include/greet.hpp |
Declares greet() -- the public header installed to INCDIR |
src/greet.cpp |
Implements greet() -- compiled into libgreet.a |
src/main.cpp |
CLI entry point -- compiled into the greeter executable |
Makefile |
Demonstrates install, uninstall, DESTDIR, PREFIX, and path functions |