Примечание: этот ответ предполагает, что вы используете GNU make . Если это не так, вероятно, есть несколько вещей для адаптации. Я не буду отвечать на ваш последний вопрос о кросс-платформенной переносимости. Во-первых, потому что это сложный вопрос, во-вторых, потому что у меня нет коробки с Windows, и я не могу выполнять никаких тестов с этой ОС. Но если вы знаете переносимый способ обнаружения ОС, посмотрите на последнюю заметку. Между тем, следующее должно работать под Windows.
Самый простой и стандартный способ использования GNU make в вашем случае, вероятно, выглядит примерно так:
MKDIR := md
RMDIR := rd /S /Q
CC := gcc
BIN := ./bin
OBJ := ./obj
INCLUDE := ./include
SRC := ./src
SRCS := $(wildcard $(SRC)/*.c)
OBJS := $(patsubst $(SRC)/%.c,$(OBJ)/%.o,$(SRCS))
EXE := $(BIN)/main.exe
CFLAGS := -I$(INCLUDE)
LDLIBS := -lm
.PHONY: all run clean
all: $(EXE)
$(EXE): $(OBJS) | $(BIN)
$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
$(OBJ)/%.o: $(SRC)/%.c | $(OBJ)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN) $(OBJ):
$(MKDIR) $@
run: $(EXE)
$<
clean:
$(RMDIR) $(OBJ) $(BIN)
Пояснения:
- Функция
wildcard
make используется для обнаружения списка исходных файлов на языке C.
- Функция
patsubst
make используется для преобразования имен файлов исходного кода C. в имена файлов объектов.
- Специальная цель
.PHONY
сообщает make, что все ее предпосылки phony : они не являются реальными файлами и должны учитываться make, даже если файл с таким именем уже существует случайно.
$(OBJ)/%.o: $(SRC)/%.c | $(OBJ)
- это шаблонное правило , универсальное правило, которое работает для всех ваших похожих правил построения объектов. В этом рассказывается make, как создать каждый объектный файл ./obj/xxx.o
, скомпилировав соответствующий исходный файл ./src/xxx.c
C.
- Часть
... | $(OBJ)
правила шаблона говорит make, что $(OBJ)
является обязательным условием только для заказа . Make соберет его, если он еще не существует. В противном случае он не будет учитывать время последнего изменения, чтобы решить, должна ли цель быть восстановлена или нет. Каталоги почти всегда указываются как предварительное условие только для заказа, поскольку время их последнего изменения не имеет значения. Здесь он используется, чтобы сказать make, что каталог $(OBJ)
должен быть собран до того, как может быть создан любой объектный файл. То же самое для $(BIN)
в правиле $(EXE): $(OBJS) | $(BIN)
.
$@
, $<
и $^
- 3 из автоматических переменных make . В рецептах правила они расширяются соответственно как цель, первая регулярная предпосылка и все регулярные (не только для заказа) предпосылки правила.
CC
, CFLAGS
и LDLIBS
являются стандартными делают неявные переменные соответственно используемыми для определения компилятора C, параметров компилятора C и параметров компоновщика -lxxx
.
- Назначение переменной
:=
предпочтительнее , в данном конкретном случае , чем назначение =
по соображениям производительности. См. документацию GNU make для подробного объяснения.
Примечание: поскольку у вас есть каталог include
, я предполагаю, что у вас также есть пользовательские заголовочные файлы. Они должны быть перечислены в качестве предварительных условий соответствующих объектных файлов, чтобы make знал, что объектный файл должен быть перестроен, если заголовочные файлы зависят от изменений. Вы можете сделать это, добавив правила без рецепта в любом месте после правила шаблона:
$(OBJ)/Distances.o: $(INCLUDE)/foo.h $(INCLUDE)/bar.h
Если заголовочный файл включен во все исходные файлы C вашего проекта, просто добавьте:
$(OBJS): $(INCLUDE)/common.h
И если для заголовочных файлов существует своего рода шаблон (например, если каждый $(SRC)/xxx.c
включает $(INCLUDE)/xxx.h
), вы также можете добавить правило шаблона, чтобы объявить этот тип зависимости:
$(OBJ)/%.o: $(INCLUDE)/%.h
Примечание: если вы знаете способ установки переменной make (например, OS
) для имени текущей ОС, вы, вероятно, можете изменить то, что предшествует, чтобы сделать ее переносимой, используя GNU make conditionals. Например, вы можете заменить две первые строки на:
OS := $(shell <the-command-that-returns-the-current-OS-name>)
ifeq ($(OS),Windows)
MKDIR := md
RMDIR := rd /S /Q
else ifeq ($(OS),GNU/Linux)
MKDIR := mkdir -p
RMDIR := rm -rf
else ifeq ($(OS),pokemon)
MKDIR := bulbasaur
RMDIR := charmander
endif