Как я могу создать Makefile для проектов C с подкаталогами SRC, OBJ и BIN?

95

Несколько месяцев назад я придумал следующий универсальный вариант Makefileшкольных заданий:

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# Date  : 2010-11-05
#
# Changelog :
#   0.01 - first version
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc -std=c99 -c
# compiling flags here
CFLAGS   = -Wall -I.

LINKER   = gcc -o
# linking flags here
LFLAGS   = -Wall

SOURCES  := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS  := $(SOURCES:.c=*.o)
rm       = rm -f

$(TARGET): obj
    @$(LINKER) $(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

obj: $(SOURCES) $(INCLUDES)
    @$(CC) $(CFLAGS) $(SOURCES)
    @echo "Compilation complete!"

clean:
    @$(rm) $(TARGET) $(OBJECTS)
    @echo "Cleanup complete!"

Это будет в основном компилировать каждый .cи .hфайл для создания .oфайлов и исполняемые projectnameвсе в той же папке.

Теперь я хотел бы немного подтолкнуть это. Как я могу написать Makefile для компиляции проекта C со следующей структурой каталогов?

 ./
 ./Makefile
 ./src/*.c;*.h
 ./obj/*.o
 ./bin/<executable>

Другими словами, я хотел бы иметь Makefile, который компилирует исходные коды C ./src/в, ./obj/а затем связывает все для создания исполняемого файла ./bin/.

Я пробовал читать разные Makefile, но просто не могу заставить их работать для структуры проекта выше; вместо этого проект не компилируется со всевозможными ошибками. Конечно, я мог бы использовать полноценную среду IDE (Monodevelop, Anjuta и т. Д.), Но, честно говоря, я предпочитаю придерживаться gEdit и старого доброго терминала.

Есть ли гуру, который может дать мне рабочее решение или четкую информацию о том, как это можно сделать? Спасибо!

** ОБНОВЛЕНИЕ (v4) **

Окончательное решение:

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# Date  : 2011-08-10
#
# Changelog :
#   2010-11-05 - first version
#   2011-08-10 - added structure : sources, objects, binaries
#                thanks to http://stackoverflow.com/users/128940/beta
#   2017-04-24 - changed order of linker params
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc
# compiling flags here
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
# linking flags here
LFLAGS   = -Wall -I. -lm

# change these to proper directories where each file should be
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SOURCES  := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm       = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
    @$(LINKER) $(OBJECTS) $(LFLAGS) -o $@
    @echo "Linking complete!"

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

.PHONY: clean
clean:
    @$(rm) $(OBJECTS)
    @echo "Cleanup complete!"

.PHONY: remove
remove: clean
    @$(rm) $(BINDIR)/$(TARGET)
    @echo "Executable removed!"
Яник Рочон
источник
Какой конкретный вопрос здесь?
Оливер Чарльзуорт
Я не уверен, что понимаю, что ты хочешь сделать.
Том
Обновил Makefile. Я приближаюсь, но у меня проблемы с автоматическими переменными, так что, похоже, все равно
Яник Рочон
Я только что нашел решение. Если кто-то хочет найти что-то получше, Makefile все еще можно улучшить.
Яник Рочон
2
@YanickRochon Я не хотел критиковать твои навыки английского. Но для целей PHONY сделать какой - либо смысл вы определенно не можете писать BANANA;) gnu.org/software/make/manual/html_node/Phony-Targets.html
Джони

Ответы:

34

Во-первых, ваше $(OBJECTS)правило проблематично, потому что:

  1. это своего рода неизбирательный подход, делающий все источники предпосылками каждого объекта,
  2. он часто использует неправильный источник (как вы обнаружили с помощью file1.oи file2.o)
  3. он пытается создавать исполняемые файлы вместо того, чтобы останавливаться на объектах, и
  4. имя target ( foo.o) - это не то, что на самом деле будет создавать правило ( obj/foo.o).

Предлагаю следующее:

OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

У $(TARGET)правила та же проблема, что целевое имя фактически не описывает то, что строит правило. По этой причине, если вы наберете makeнесколько раз, Make будет перестраивать цель каждый раз, даже если для этого нет причин. Небольшое изменение исправляет следующее:

$(BINDIR)/$(TARGET): $(OBJECTS)
    $(LINKER) $@ $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Как только все будет в порядке, вы можете подумать о более сложной обработке зависимостей; если вы измените один из файлов заголовков, этот make-файл не будет знать, какие объекты / исполняемые файлы необходимо перестроить. Но это может подождать еще один день.

РЕДАКТИРОВАТЬ:
Извините, я пропустил часть $(OBJECTS)правила выше; Я поправил. (Хотел бы я использовать "strike" внутри образца кода.)

Бета
источник
с вашими предложенными изменениями я получаю:obj/file1.o: In function 'main': \n main.c:(.text+0x0): multiple definition of 'main' \n obj/main.o:main.c:(.text+0x0): first defined here
Яник Рочон
@ Яник Рошон: У вас есть несколько mainфункций? Может быть, один за file1.cдругим main.c? В таком случае вы не сможете связать эти объекты; mainв исполняемом файле может быть только один .
Бета,
Нет. Все работает нормально с последней версией, которую я разместил в вопросе. Когда я меняю свой Makefile на то, что вы предлагаете (и я понимаю преимущества того, что вы говорите), я получаю именно это. Я только что вставил, file1.cно он дает одно и то же сообщение для всех файлов проекта. И main.cэто единственный, у которого есть основная функция ... и main.cимпорт file1.hи file2.h(нет связи между file1.cи file2.c), но я сомневаюсь, что проблема исходит оттуда.
Яник Рочон
@ Яник Рошон: Я сделал ошибку, вставив первую строку своего $(OBJECTS)правила; Я его отредактировал. С плохой строкой у меня ошибка, но не та, что у тебя ...
Beta
6

Вы можете добавить -Iфлаг к флагам компилятора (CFLAGS), чтобы указать, где компилятор должен искать исходные файлы, и флаг -o, чтобы указать, где следует оставить двоичный файл:

CFLAGS   = -Wall -I./src
TARGETPATH = ./bin

$(TARGET): obj
    @$(LINKER) $(TARGETPATH)/$(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Чтобы поместить объектные файлы в objкаталог, используйте -oопцию компиляции. Кроме того , обратите внимание на $@и $< автоматических переменных .

Например, рассмотрим этот простой Makefile

CFLAGS= -g -Wall -O3                                                            
OBJDIR= ./obj

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o )
all:$(OBJS)

%.o: %.c 
   $(CC) $(CFLAGS) -c $< -o $(OBJDIR)/$@

Обновить>

Глядя на ваш make-файл, я понимаю, что вы используете -oфлаг. Хороший. Продолжайте использовать его, но добавьте переменную целевого каталога, чтобы указать, куда должен быть записан выходной файл.

Том
источник
Не могли бы Вы уточнить? Вы имеете в виду добавление -l ...к CFLAGSи ... уже есть -oаргумент для линкера ( LINKER)
Яник Рочон
Да, CFLAGS, и да, продолжайте использовать -o, просто добавьте переменную TARGETPATH.
Том
Спасибо, я внес изменения, но, похоже, что-то мне все еще не хватает (см. Обновление по вопросу)
Яник Рочон
просто оттуда make, где находится Makefile
Яник Рочон
Вы не можете прочитать выполняемую команду? например gcc -c yadayada. Совершенно уверен, что есть переменная, которая не содержит того, что вы ожидаете,
Том
-1

В наши дни я перестал писать make-файлы, если вы собираетесь учиться, продолжайте, иначе у вас есть хороший генератор make-файлов, который поставляется с eclipse CDT. Если вам нужна некоторая ремонтопригодность / поддержка нескольких проектов в вашем дереве сборки, взгляните на следующее:

https://github.com/dmoulding/boilermake Я нашел это очень хорошим ...!

Камат
источник
3
Основано на мнении. Не отвечает на вопрос ОП. Предполагает среду Eclipse.
Натаниэль Джонсон