Directory Structure:
.
├── check.sh
├── GNUmakefile
├── Makefile
└── src
Makefile
has these contents:# Dummy Makefile to indicate that GNU Make should be used. .POSIX: all x: @echo "You need GNU Make to build x" 1>&2; false
When run on Solaris, a BSD system, or any other system that doesn't come with GNU make as the default make, this results in a nice error message. On Linux, and others that come with GNU make as the default, this file is ignored because GNU make looks for
"GNUMakefile"
first.The
src
directory is empty because I haven't actually written any code yet."check.sh"
is a shell script that checks whether the program has been installed at$(PREFIX)/bin/
. If it is not, it prints a message showing how to add$(PREFIX)/bin/
to thePATH
variable, and then offers to add it automatically. (There's notest
directory ortest
target because there are none yet. I will add that later.)It is generic in the sense that I only have to change the value of
target
for other projects, because the directory structure remains the same. It should work at least on Windows (MINGW64 and CYGWIN), Linux, Oracle Solaris, OmniOS, OpenBSD, NetBSD, FreeBSD, and Mac OS X. (Assuming proper tools likegcc
/clang
,valgrind
,lubsan
et cetera are installed).
Makefile:
ifeq '$(PREFIX)' ''
ifeq ($(shell id -u), 0)
PREFIX := /usr/local
else
PREFIX := $(HOME)/.local
endif
endif
MKDIR := mkdir
INSTALL := install -Dpm 755
RM := rm
VALGRIND := valgrind --tool=memcheck --leak-check=yes
CC := $(CC)
CFLAGS := $(CFLAGS)
CFLAGS += -std=c2x
CFLAGS += -I..
COMPILE.C = $(CC) $(CFLAGS)
COMPILER_VERSION := $(shell $(CC) --version)
ifneq '$(findstring debug,$(MAKECMDGOALS))' ''
CFLAGS += -g3
CFLAGS += -ggdb
CFLAGS += -no-pie
CFLAGS += -fno-builtin
CFLAGS += -fno-common
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -Wall
CFLAGS += -Wextra
CFLAGS += -Warray-bounds
CFLAGS += -Wconversion
CFLAGS += -Wformat-signedness
CFLAGS += -Wno-parentheses
CFLAGS += -Wpedantic
CFLAGS += -pedantic-errors
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wwrite-strings
CFLAGS += -Wno-missing-braces
CFLAGS += -Wno-missing-field-initializers
CFLAGS += -fsanitize=address
CFLAGS += -fsanitize=undefined
CFLAGS += -fsanitize=bounds-strict
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=null
CFLAGS += -fsanitize=signed-integer-overflow
CFLAGS += -fsanitize=bool
CFLAGS += -fsanitize=pointer-overflow
CFLAGS += -fsanitize-address-use-after-scope
ifneq '$(findstring clang,$(COMPILER_VERSION))' ''
CFLAGS += -fsanitize=function
CFLAGS += -fsanitize=implicit-unsigned-integer-truncation
CFLAGS += -fsanitize=implicit-signed-integer-truncation
CFLAGS += -fsanitize=implicit-integer-sign-change
CFLAGS += -Wreserved-identifier
CFLAGS += -Xanalyzer
else ifneq '$(findstring gcc,$(COMPILER_VERSION))' ''
CFLAGS += -Wformat-signedness
CFLAGS += -Wsuggest-attribute=pure
CFLAGS += -Wsuggest-attribute=const
CFLAGS += -Wsuggest-attribute=noreturn
CFLAGS += -Wsuggest-attribute=cold
CFLAGS += -Wsuggest-attribute=malloc
CFLAGS += -Wsuggest-attribute=format
CFLAGS += -fanalyzer
endif
else ifneq '$(findstring release,$(MAKECMDGOALS))' ''
ifneq '$(findstring gcc,$(COMPILER_VERSION))' ''
ifeq '$(firstword $(sort $(shell $(CC) -dumpversion) 14))' '14'
CFLAGS += -fhardened
else
CFLAGS += -D_FORTIFY_SOURCE=2
CFLAGS += -fPIE
endif
endif
CFLAGS += -pedantic
CFLAGS += -O3
CFLAGS += -march=native
CFLAGS += -s
endif
src-dir := src
srcs := $(wildcard $(src-dir)/*.c)
obj-dir := obj
objs := $(patsubst $(src-dir)/%.c,$(obj-dir)/%.o,$(srcs))
deps := $(objs:.o=.d)
target := kilo
.PHONY: release debug
release debug: $(target)
$(target): $(objs) | $(obj-dir)
$(COMPILE.C) -o $@ $^
$(obj-dir)/%.o: CFLAGS += -MMD -c
$(obj-dir)/%.o: $(src-dir)/%.c | $(obj-dir)
$(COMPILE.C) -o $@ $<
$(obj-dir):
$(MKDIR) $(obj-dir)
ifneq '$(MAKECMDGOALS)' 'clean'
-include $(deps)
endif
.PHONY: valgrind
valgrind: clean $(target)
$(VALGRIND) ./$(target)
.PHONY: fclean
fclean:
$(RM) $(target)
.PHONY: clean
clean:
$(RM) -rf $(obj-dir) $(target)
.PHONY: install
install: release
$(INSTALL) $(target) $(PREFIX)/bin
@./check.sh $(PREFIX)/bin $(target)
.PHONY: uninstall
uninstall:
$(RM) -f $(PREFIX)/bin/$(target)
.DELETE_ON_ERROR:
Review Request:
Redundancies, duplication, simplifications. Anything. Everything.
1 Answer 1
This looks wrong:
$(target): $(objs) | $(obj-dir) $(COMPILE.C) -o $@ $^
Make predefines $(LINK.c)
for this command.
-
\$\begingroup\$ Only now did I discover
$(COMPILE.c)
and$(LINK.c)
, thanks! Do you think there would be a problem due to keeping the object files of both the debug and release build in the same directory? \$\endgroup\$Madagascar– Madagascar2024年06月27日 11:18:35 +00:00Commented Jun 27, 2024 at 11:18 -
1\$\begingroup\$ That can be a problem, as they share the same names and we can end up linking a mismatching set of objects. I guess it depends whether their public interfaces depend on
NDEBUG
etc. (when we add that toCFLAGS
release builds). Instead of rewriting%.o: %c
rules, I find it better to start a sub-make in the output directory (something like+$(MAKE) $(builddir) -f ../Makefile $@ VPATH=$(PWD)
). Then you can build as many configurations as you want, from a single source directory which doesn't need to be writeable by the user doing the build. \$\endgroup\$Toby Speight– Toby Speight2024年06月27日 12:10:37 +00:00Commented Jun 27, 2024 at 12:10 -
1\$\begingroup\$ Sorry for the very short review - I have a lot going on until late July. I hope you get a more thorough answer as well! \$\endgroup\$Toby Speight– Toby Speight2024年06月27日 20:14:45 +00:00Commented Jun 27, 2024 at 20:14
-
\$\begingroup\$ That's completely alright. Have a good month! \$\endgroup\$Madagascar– Madagascar2024年06月28日 04:31:25 +00:00Commented Jun 28, 2024 at 4:31
CC := $(CC)
is a null op. \$\endgroup\$$(CC)
will still produce the same value it did before that statement. WasCC
even defined in terms of other variables? \$\endgroup\$$(CC)
really. Just saw it being suggested on some StackOverflow answers. \$\endgroup\$