C Makefile with two executables and a common directory
An answer to this question on Stack Overflow.
Question
I need to make a makefile which compiles two executables, cassini and saturnd
I've been having a ton of problems with this makefile I'm using. Sometimes it compiles, sometimes not.
Makefile
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -Wpedantic -Wstrict-aliasing -I include
SRCCASSINI = $(wildcard src/cassini/*.c)
SRCSATURND = $(wildcard src/saturnd/*.c)
SRCCOMMON = $(wildcard src/common/*.c)
OBJCASSINI = $(SRCCASSINI:.c=.o)
OBJSATURND = $(SRCSATURND:.c=.o)
OBJCOMMON = $(SRCCOMMON:.c=.o)
EXEC = cassini saturnd
all: objs $(EXEC)
objs: $(OBJCOMMON)
cassini : $(OBJCASSINI)
$(CC) -o $@ $(OBJCASSINI) $(OBJCOMMON)
saturnd : $(OBJSATURND) objs
$(CC) -o $@ $(OBJSATURND) $(OBJCOMMON)
%.o : %.c
$(CC) -o $@ -c $< $(CFLAGS)
clean :
rm -f src/*/*.o $(EXEC)
distclean :
rm -f src/*/*/.o $(EXEC)
Project structure
Include dir:
includecontains the.hfiles of allcfiles and more.SRC dir:
SRC contains 3 directories:
cassini*contains all source files that should be compiled only with the cassini executablesaturndcontains all source files that should be compiled only with the saturnd executablecommoncontains all source files that should be compiled with both cassini and saturnd
Screenshot : https://prnt.sc/26brtpq
make will fill my screen with verbose output. Sometimes it compiles, sometimes not. For some reason.
Answer
The trick is not to use a makefile. I've been programming for years and still can't reliably make one of those do what I want.
Instead, you use cmake. It has an easy syntax which you can mostly copy-paste from one project to another and generates makefiles which reliably Do The Right Thing™.
To use cmake, you make a file called CMakeLists.txt in the parent directory of src. Put this in the file:
cmake_minimum_required (VERSION 3.9)
project(cassini_saturn LANGUAGES C)
SET(common_files
src/common/g.c
src/common/h.c
src/common/i.c
)
add_executable(cassini
src/cassini/a.c
src/cassini/b.c
src/cassini/c.c
${common_files}
)
target_compile_features(cassini PRIVATE cxx_std_11)
target_compile_options(cassini PRIVATE -Wall -Wextra -Wpedantic -Wstrict-aliasing)
target_include_directories(cassini PRIVATE include)
add_executable(saturnd
src/saturnd/d.c
src/saturnd/e.c
src/saturnd/f.c
${common_files}
)
target_compile_features(saturnd PRIVATE cxx_std_11)
target_compile_options(saturnd PRIVATE -Wall -Wextra -Wpedantic -Wstrict-aliasing)
target_include_directories(saturnd PRIVATE include)
Edit the obvious places so that the files your project uses are each listed individually. It is a best practice to list all the files, rather than relying on wildcards and globs, though if you want to not follow best practices you can use, eg:
file(GLOB SRC_FILES src/common/*.c)
Now that your CMakeLists.txt file is set up, it's time to use it. To do so, you'll run the following:
mkdir build/
cd build/
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
make
You make a directory called build (it could be named anything) to contain all the byproducts of building and compiling your project. That way, you can just rm -rf build/ to get a clean slate. This is called an out-of-source build and it is a best practice.
The line
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
generates a makefile for your project that, when run, will build your project with optimizations and debugging info. Other options are Release (all optimizations, no debugging info) and Debug (no optimizations, debugging info).
The final line, make, runs the generated makefile and builds the executables inside the build directory.