Я пытаюсь создать make-файл для компиляции под Linux программы, созданной с помощью Code::Blocks в Windows (поэтому у меня нет рабочего make-файла, на который можно было бы посмотреть).
Проект большой, исходные файлы находятся в разных подкаталогах, поэтому я сделал свой make-файл следующим образом:
# Main project folder
MyProject_PATH := ~/MyProject/
# Main Source Folder
SRC_PATH := $(MyProject_PATH)src
# All Source subfolders
SRC_DIRS := $(shell find $(SRC_PATH) -type d)
# - Output folders
OBJ_PATH := $(MyProject_PATH)LINUXmake/obj-arm
BIN_PATH := $(MyProject_PATH)LINUXmake/bin-arm
# - Final executable name
EXE := $(BIN_PATH)/MyProject-arm
# get all .c and .cpp files
SRC := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c) $(wildcard $(dir)/*.cpp))
# Define object files for each source file
OBJ := $(patsubst %.c,$(OBJ_PATH)/%.o,$(notdir $(SRC:.c=.o)))
OBJ := $(patsubst %.cpp,$(OBJ_PATH)/%.o,$(notdir $(OBJ:.cpp=.o)))
Тогда у меня есть этот раздел правил:
.PHONY: all show clean
# Rules for compiling .c files
%.o: %.c
@echo "Compiling C file: $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# Rules for compiling .cpp files
%.o: %.cpp
@echo "Compiling C++ file: $<"
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
# Rule to build the final target
$(EXE): $(OBJ)
@echo "Linking objects to create target: $@"
$(CXX) $(LDLIBS) $^ -o $@
# Rule to create output directories
$(shell mkdir -p $(OBJ_PATH) $(BIN_PATH))
# Rule to clean object files and the target
clean:
$(RM) -r $(OBJ_PATH) $(BIN_PATH)
show:
@printf "\nPaths: $(OBJ_PATH) $(BIN_PATH)\n"
@printf "\nSource files:\n$(SRC)"
@printf "\n\nObject files:\n$(OBJ)"
@printf "\n\nCompiler:\n$(CC)"
@printf "\n\nC++ Compiler:\n$(CXX)"
@printf "\n\nC Flags:\n$(CFLAGS)"
@printf "\n\nC++ Flags:\n$(CXXFLAGS)"
@printf "\n\nInclude Flags:\n$(CPPFLAGS)"
@printf "\n\nLinked Libraries:\n$(LDLIBS)"
@echo ""
Но я всегда получаю эту ошибку:
make: *** No rule to make target 'cKeyboard.o', needed by '/home/buster/MyProject/LINUXmake/bin-arm/MyProject'. Stop.
Если я использую цель «показать», я получаю этот результат:
Paths: ~/MyProject/LINUXmake/obj-arm ~/MyProject/LINUXmake/bin-arm
Source files:
/home/buster/MyProject/src/cKeyboard.cpp /home/buster/MyProject/src/GUI_MainForm.cpp /home/buster/MyProject/src/MyProject_bitmaps.cpp /home/buster/MyProject/src/CommManager.cpp /home/buster/MyProject/src/cMSX.cpp /home/buster/MyProject/src/fMainForm.cpp /home/buster/MyProject/src/MSX_PG.cpp /home/buster/MyProject/src/MsgManager.cpp /home/buster/MyProject/src/ClassiEventi/cBaseObjWithEvents.cpp /home/buster/MyProject/src/cMSXTool/cMSXTool.cpp /home/buster/MyProject/src/Comm/X_OS_Socket.cpp /home/buster/MyProject/src/Comm/serialib.cpp /home/buster/MyProject/src/Comm/BBBSerial.cpp /home/buster/MyProject/src/CustomControls/cCCDCalibration/cCCDCal.cpp /home/buster/MyProject/src/CustomControls/TAntiAliasedGauge/TAntiAliasedGauge.cpp /home/buster/MyProject/src/CustomControls/TGradientButton/wxGradientButton.cpp /home/buster/MyProject/src/CustomControls/TGradientButton/wxGradientSwitch.cpp /home/buster/MyProject/src/CustomControls/TPacket/cPacchetto.cpp /home/buster/MyProject/src/CustomControls/TPacket/TPacket.cpp /home/buster/MyProject/src/CustomControls/TPacket/cSigaretta.cpp /home/buster/MyProject/src/CustomControls/TScannerImage/TScannerImage.cpp /home/buster/MyProject/src/CustomControls/TScannerImage/cResampler.cpp /home/buster/MyProject/src/CustomControls/TScannerImage/resampler.cpp /home/buster/MyProject/src/CustomControls/TSigaTollerance/TSigaTolerance.cpp /home/buster/MyProject/src/CustomControls/wxActiveArea/wxActiveArea.cpp /home/buster/MyProject/src/CustomControls/wxPanelSensorBar/wxPanelSensorBar.cpp /home/buster/MyProject/src/CustomControls/wxPngAnimator/wxpnganimator.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_XMLManager.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_MSXT.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_MSXT (2).cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_UnusedControlEvents.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_GraphReader.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_TextBoxManagement.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_Charts.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_FileBrowser.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_RealTimeClock.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_SystemPages.cpp /home/buster/MyProject/src/GUI_MainformParts/GUI_KeysManagement.cpp /home/buster/MyProject/src/GUI_MainformParts/_DEBUG_STUFFS.cpp /home/buster/MyProject/src/I2C/24cXX.c /home/buster/MyProject/src/I2C/24cXX.cpp /home/buster/MyProject/src/I2C/i2cfunc.cpp /home/buster/MyProject/src/MsxData/MsxGraph.cpp /home/buster/MyProject/src/MSX_ClassParts/MSX_SendCommands.cpp /home/buster/MyProject/src/MSX_ClassParts/cTendenzaScarti.cpp /home/buster/MyProject/src/MSX_ClassParts/CCD_Averages.cpp /home/buster/MyProject/src/MSX_ClassParts/MSX_SalvaGrafici.cpp /home/buster/MyProject/src/Utilities/Utilities.cpp /home/buster/MyProject/src/Utilities/StringUtils.cpp /home/buster/MyProject/src/Utilities/cGraphFile.cpp
Object files:
cKeyboard.o GUI_MainForm.o MyProject_bitmaps.o CommManager.o cMSX.o fMainForm.o MSX_PG.o MsgManager.o cBaseObjWithEvents.o cMSXTool.o X_OS_Socket.o serialib.o BBBSerial.o cCCDCal.o TAntiAliasedGauge.o wxGradientButton.o wxGradientSwitch.o cPacchetto.o TPacket.o cSigaretta.o TScannerImage.o cResampler.o resampler.o TSigaTolerance.o wxActiveArea.o wxPanelSensorBar.o wxpnganimator.o GUI_XMLManager.o GUI_MSXT.o GUI_MSXT (2).o GUI_UnusedControlEvents.o GUI_GraphReader.o GUI_TextBoxManagement.o GUI_Charts.o GUI_FileBrowser.o GUI_RealTimeClock.o GUI_SystemPages.o GUI_KeysManagement.o _DEBUG_STUFFS.o 24cXX.o 24cXX.o i2cfunc.o MsxGraph.o MSX_SendCommands.o cTendenzaScarti.o CCD_Averages.o MSX_SalvaGrafici.o Utilities.o StringUtils.o cGraphFile.o
Я явно что-то упускаю, но не могу понять что. Что я должен делать?
Механизм, который вы используете для идентификации исходных файлов, кажется излишне сложным. Я сторонник явного перечисления файлов, даже если их много, но если вы хотите найти их автоматически, то зачем find
исходные каталоги, а затем сканировать их, вместо того, чтобы find
просто find
непосредственно искать сами исходные файлы? (И, кстати, я бы не стал считать количество исходных файлов, которые вы показываете в вопросе, даже отдаленно «большими».)
Насколько важно для вас помещать объектные файлы в отдельный каталог от исходников? Путь наименьшего сопротивления (и традиционный подход в традициях Unix) состоит в том, чтобы размещать объекты в тех же каталогах, что и их соответствующие источники. Кроме того, если вы настаиваете на создании отдельного каталога, важно ли для вас сгладить дерево, как вы, кажется, делаете?
@JohnBollinger: 1) О «~»: я усвоил это на собственном горьком опыте сразу после публикации этого вопроса. Что касается использования папки «Домашняя», в целом вы правы, в данном конкретном случае Linux работает на WSL просто как «инструмент» для кросс-компиляции под Linux с компьютера под управлением Windows, так что это не имеет большого значения. 2) О find
: какая строка будет, чтобы напрямую найти все исходные файлы? 3) Насколько важно? 0 из 10. Я думал, что это просто побочный продукт того, что источники находятся по разным путям и просто заменяют общую часть строки.
Как насчет find $(SRC_PATH) -name '*.c' -o -name '*.cpp'
? find
выполняет сопоставление глобусов.
Это правило шаблона:
%.o: %.cpp
@echo "Compiling C file: $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
означает: для файла, соответствующего <SOMETHING>.cpp
, создайте файл <SOMETHING>.o
с помощью
запуск расширения каждой строки:
@echo "Compiling C file: $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
как команда оболочки.
Ваш первый исходный файл, как показано на выходе make show
:
/home/buster/MyProject/src/cKeyboard.cpp
так что это правило шаблона подсказывает make
, как сделать:
/home/buster/MyProject/src/cKeyboard.o
Но первый объектный файл, необходимый вашей цели $(EXE)
, как также показано
по make show
, это:
cKeyboard.o
(= ./cKeyboard.o
), и для этого нет никаких правил. Более того,
вы не хотите это сделать. Файл, из которого вы хотите сделать:
/home/buster/MyProject/src/cKeyboard.cpp
очевидно:
$(OBJ_PATH)/cKeyboard.o
= ~/MyProject/LINUXmake/obj-arm
= /home/buster/MyProject/LINUXmake/obj-arm/cKeyboard.o
Очевидно, тот же недостаток касается и правила для %.o: %.c
.
Шаблонное правило, которое позволит правильно компилироваться /home/buster/MyProject/src/cKeyboard.cpp
было бы:
$(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp
@echo "Compiling C file: $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
Но этого будет недостаточно для исходных файлов в более глубоких подкаталогах.
например /home/buster/MyProject/src/ClassiEventi/cBaseObjWithEvents.cpp
. Есть
нет единого правила шаблона, которое соответствовало бы всем исходным файлам, рассредоточено
через дерево подкаталогов.
Для подобных сценариев make
предоставляет специальную переменную VPATH
,
с помощью которого вы можете указать список каталогов, в которых make
будет искать
для предварительных условий правил, когда они не найдены в текущем каталоге,
и относитесь к ним так, как будто они есть. См. руководство GNU Make: 4.5.1 VPATH: путь поиска для всех необходимых компонентов
Используя VPATH
, вы установите:
VPATH := $(SRC_DIRS)
и тогда ваше правило шаблона может быть:
$(OBJ_PATH)/%.o: %.cpp
@echo "Compiling C file: $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
И аналогично $(OBJ_PATH)/%.o: %.c ....
.
Пока все хорошо, но нам еще предстоит объяснить и исправить тот факт, что вы генерируют:
$(OBJ) = cKeyboard.o ...
вместо того, что вам нужно:
$(OBJ) = ~/MyProject/LINUXmake/obj-arm/cKeyboard.o ...
Что подводит нас к:
OBJ := $(patsubst %.c,$(OBJ_PATH)/%.o,$(notdir $(SRC:.c=.o)))
OBJ := $(patsubst %.cpp,$(OBJ_PATH)/%.o,$(notdir $(OBJ:.cpp=.o)))
Для простоты представим, что:
/home/buster/MyProject/src/cKeyboard.cpp
является единственным исходным файлом. Тогда первое из этих OBJ
заданий означает: Найдите все слова в расширении:
$(notdir $(SRC:.c=.o))
= $(notdir $(/home/buster/MyProject/src/cKeyboard.cpp:.c=.o)
= $(notdir /home/buster/MyProject/src/cKeyboard.cpp) # Because there was no `.c=.o` substitution
= cKeyboard.cpp
затем замените любые слова в результате:
cKeyboard.cpp
это соответствует <SOMETHING>.c
с $(OBJ_PATH)/<SOMETHING>.o
. В тексте нет слов:
cKeyboard.cpp
это совпадение <SOMETHING>.c
, поэтому окончательный результат этого первого OBJ
задания
останки
cKeyboard.cpp
Второе задание OBJ
означает: Найдите все слова в:
$(notdir $(OBJ:.cpp=.o))
= $(notdir $(cKeyboard.cpp:.cpp=.o)
= $(notdir cKeyboard.o)
= cKeyboard.o
затем замените любые слова в результате:
cKeyboard.o
это соответствует <SOMETHING>.cpp
с $(OBJ_PATH)/<SOMETHING>.o
. Нет
слова в:
cKeyboard.o
это совпадение <SOMETHING>.cpp
, поэтому окончательный результат второго OBJ
задания
останки:
cKeyboard.o
вместо желаемого
$(OBJ_PATH)/cKeyboard.o
Правильные результаты будут получены при:
CSRC := $(filter %.c,$(SRC))
CXXSRC := $(filter %.cpp,$(SRC))
OBJ := $(addprefix $(OBJ_PATH)/, $(notdir $(CSRC:.c=.o) $(CXXSRC:.cpp=.o)))
Затем с помощью правил с помощью VPATH
:
$(OBJ_PATH)/%.o: %.c
...
$(OBJ_PATH)/%.o: %.cpp
...
мы готовы идти.
Однако важное предостережение. Такая система сборки все еще существует (независимо от того, поддерживается ли VPATH
),
в каких исходных файлах file.(c|cpp|cc|cxx|...)
из нескольких подкаталогов
будут скомпилированы в объектные файлы file.o
в одном каталоге, зависит от
каждое имя исходного файла file.(c|cpp|cc|cxx|...)
уникально во всех
исходные подкаталоги. В любом случае как:
srcdirA/file.cpp
с:
srcdirB/file.c
и/или
srcdirC/file.cpp
make
поручено скомпилировать все эти исходные файлы в одно и то же:
objdir/file.o
То же самое произойдет и с первым исходным файлом, скажем srcdirA/file.cpp
;
тогда для последующих исходных файлов он обнаружит это objdir/file.o
обновлен и не утруждаюсь их компиляцией.
Это может быть загадочной ошибкой в больших базах кода, и по этой причине такой стиль системы сборки в принципе небезопасен.
Средством (или хаком), позволяющим избежать долгих переосмыслений, является создание имен объектных файлов.
которые кодируют элементы пути исходных файлов, из которых они скомпилированы, например.
objdir/srcdirA_file.o
.
Также шаткий захват, что бы там ни было, подход предоставить исходные файлы. Лучше перечислить их явно, например.
SRC := src/cKeyboard.cpp \
src/GUI_MainForm.cpp \
...
Это может показаться утомительным и неразумным, если вы начнете заниматься этим поздно, но однажды
вы обновлены, добавлять новые файлы и удалять лишние не составит труда
по мере возникновения обстоятельств. Система сборки должна четко определить и разъяснить пользователям, что именно
файлы, если таковые имеются, изначально должны существовать для каждой цели. На самом деле все, что видно, это то, что ваша сборка
система стремится создать любую программу, которая, если повезет, может возникнуть в результате компиляции и компоновки
какие бы файлы .c
, .cpp
ни находились где-нибудь в каталоге src
или под ним,
время сборки.
Спасибо. Ваше предложение о рабочем правиле заставило меня переосмыслить тот факт, что в списке файлов .o отсутствуют пути, что привело меня к рабочему решению. Буду рад услышать ваше мнение по поводу моего собственного ответа.
Я обнаружил, что основная проблема заключалась в создании списка OBJ:
путем изменения
OBJ := $(patsubst %.c, $(OBJ_PATH)/%.o, $(notdir $(SRC:.c=.o) ) )
OBJ := $(patsubst %.cpp, $(OBJ_PATH)/%.o, $(notdir $(OBJ:.cpp=.o) ) )
в
OBJ := $(patsubst $(SRC_PATH)/%.c, $(OBJ_PATH)/%.o, $(SRC) )
OBJ := $(patsubst $(SRC_PATH)/%.cpp, $(OBJ_PATH)/%.o, $(OBJ) )
я обнаружил, что каждый файл .o теперь имеет тот же относительный путь к OBJ_PATH
, что и исходные файлы SRC_PATH
.
Используя это в правилах шаблона, make-файл работает.
# Rules for compiling .c files
$(OBJ_PATH)/%.o: $(SRC_PATH)/%.c
@echo "Compiling C file: $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# Rules for compiling .cpp files
$(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp
@echo "Compiling C++ file: $<"
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
Поскольку в комментариях вы написали, что вынесение объектных файлов в отдельный подкаталог для вас не важно, я бы поступил проще. Сохраняйте исходные правила %.o: %.c
и %.o: %.cpp
и получайте имена объектных файлов, просто преобразуя .c
и .cpp
в .o
, не изменяя пути.
@JohnBollinger: я этого не понимаю: когда я это понимаю OBJ := $(patsubst $(SRC_PATH)/%.cpp, $(OBJ_PATH)/%.o, $(OBJ) )
, в конечном итоге у меня есть путь с подкаталогом исходного файла также в имени объектного файла. Как я могу удалить его и сохранить соответствие правилу?
Использовать для этого $(patsubst)
— это все равно, что просить у Бобби Флая бутерброд с сыром. Он мог бы это сделать, и результат был бы потрясающим, но для ломтика сыра между двумя кусками хлеба не нужно ничего подобного. Просто используйте старую добрую ссылку на замену: OBJ := $(SRC:.cpp=.o)
. Аналогично для .c
и .o
. И результат будет иметь путь в имени файла. Так и задумано, и все будет работать нормально, пока вы собираете объектные файлы в те же каталоги, что и их соответствующие источники.
MyProject_PATH := ~/MyProject/
это мерзость. Практически никогда не уместно указывать абсолютные пути к исходному коду проекта в make-файле, и еще хуже указывать путь, начинающийся с~
, потому что в разных контекстах это означает разные вещи. Вместо этого поместите make-файл в корневой каталог проекта и укажите относительно него исходные пути. Планируйте сделать эту папку рабочим каталогом при сборке.