GNU Сделать обязательное правило не выполненным и зависимые библиотеки не найдены - PullRequest
0 голосов
/ 03 июля 2018

Моя проблема заключается в том, что предварительное условие (заданное переменной) не выполняется, и даже если раньше оно выполнялось вручную, необходимые библиотеки не будут найдены при компоновке. Мои Makefile не являются рекурсивными, но обязательным условием является внешнее программное обеспечение googletest, и, следовательно, он вызывается рекурсивно, так как источники не меняются между несколькими запусками «make».

Моя среда:

$ make --version
GNU Make 4.2.1
Built for x86_64-unknown-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ uname -a
Linux archlinux 4.17.3-1-ARCH #1 SMP PREEMPT Tue Jun 26 04:42:36 UTC 2018 x86_64 GNU/Linux

Это соответствующая структура каталогов:

|-- ExampleTrunk
|   |-- BUILD
|   |   |-- BIN
|   |   |-- LIB
|   |   `-- OBJ
|   |-- buildenv
|   |   |-- ...
|   |   |-- GTEST.mk
|   |   |-- Makefile
|   |   |-- commons.mk
|   |   |-- module_commons.mk
|   |   |-- pathdefs.mk
|   |-- src
|       `-- HelloWorld
|           `-- module.mk
|   `-- test
|       `-- HelloWorld
|           `-- module.mk
`-- Tools
    `-- GoogleTest
        `-- googletest
            |-- make
                |-- Makefile

Папка ExampleTrunk / BUILD и ее подкаталоги немедленно создаются в pathdefs.mk и поэтому существуют до дальнейшего выполнения правила.

Начальный Makefile: ExampleTrunk / buildenv / Makefile:

...
export SHELL := $(shell which bash)
...
.DEFAULT_GOAL := all
#some common function definitions
include commons.mk
#get $(PROJECT_ROOT), $(REL_PROJECT_ROOT); immediately create $(GLOBAL_OBJ_PATH), $(GLOBAL_LIB_PATH), $(GLOBAL_BIN_PATH)
include pathdefs.mk
export BUILD_DIRS := $(GLOBAL_BIN_PATH) $(GLOBAL_LIB_PATH) $(GLOBAL_OBJ_PATH)
include $(REL_PROJECT_ROOT)/src/HelloWorld/module.mk
...
#only include test stuff, when tests shall be built or cleaned to save dependency calculation time in all other cases
ifeq "$(call givenStringEndsWithTest,$(MAKECMDGOALS))" "0"
    include GTEST.mk
    include $(REL_PROJECT_ROOT)/test/HelloWorld/module.mk
endif

all : $(programs) | $(BUILD_DIRS);
all_Test: $(testPrograms) | $(BUILD_DIRS);
$(GLOBAL_OBJ_PATH)/%.o: %.cpp | $(BUILD_DIRS)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
clean:
    $(RM) $(addprefix $(GLOBAL_BIN_PATH)/,$(programs)) $(addprefix $(GLOBAL_BIN_PATH)/,$(testPrograms))
    $(RM) $(objectsToClean)
    $(RM) $(objectsToClean:.o=.d)
clean_Test: clean gtest_clean gmock_clean;

Правила "all", "clean" и "clean_Test" разрешают и выполняют свои предварительные условия, как ожидается, и работают без ошибок. (оценивается make ... -p) Правило "all_Test" выдает ошибки, как описано ниже.

GTest.mk содержит следующие рекурсивные правила:

gtest: | $(BUILD_DIRS)
    [ -d "$(REL_GTEST_MAKEFILE_DIR)" ] && cd "$(REL_GTEST_MAKEFILE_DIR)" && $(MAKE)
gtest_clean:
    [ -d "$(REL_GTEST_MAKEFILE_DIR)" ] && cd "$(REL_GTEST_MAKEFILE_DIR)" && $(MAKE) clean

Каталог "$ (REL_GTEST_MAKEFILE_DIR)" существует, потому что это всего лишь другое представление пути к каталогам ExampleTrunk / BUILD / *, которые были непосредственно созданы ранее pathdefs.mk. Когда я вызываю любое из обоих правил напрямую, они могут быть выполнены без ошибок. Поскольку я адаптировал Makefile googletest, выходные данные создаются в каталогах ExampleTrunk / BUILD / * по мере необходимости.

Файлы module.mk очень короткие, так как включают в себя module_commons.mk, который содержит большинство выполняемых вещей:

ExampleTrunk / SRC / HelloWorld / module.mk:

additionalPrograms := 
gmockIsRequired := no
include $(PROJECT_ROOT)/buildenv/module_commons.mk

ExampleTrunk / тест / HelloWorld / module.mk:

additionalPrograms := HelloWorld
gmockIsRequired := no
include $(PROJECT_ROOT)/buildenv/module_commons.mk

ExampleTrunk / buildenv / module_commons.mk генерирует большинство переменных автоматически из расположения и настроек файла module.mk.

Когда я выхожу

[.....ExampleTrunk/buildenv]$ make HelloWorld

все работает нормально, но с

[.....ExampleTrunk/buildenv]$ make HelloWorld_Test

происходит сбой с ошибкой компоновщика (показано ниже). Вот соответствующие части * .mk для HelloWorld_Test с уже разрешенными переменными, как показано в make HelloWorld_Test -p (переменные с файлами / каталогами или флагами не записаны в разрешенной форме для краткости):

...
LDFLAGS_TEST := -L../BUILD/LIB -lgtest -lgtest_main
CPPFLAGS_TEST := $(googleStuffCPPFLAGS) # these are -I arguments for GCC, generated in GTEST.mk
moduleObj := "this variable is generated correctly by functions..."
...
HelloWorld_Test: ../BUILD/BIN/HelloWorld_Test gtest | $(BUILD_DIRS);

../BUILD/BIN/HelloWorld_Test: $(moduleObj)
    g++ $(CPPFLAGS) $(CPPFLAGS_TEST) $(CXXFLAGS) $(LDFLAGS_TEST) $^ -o $@

Когда я выхожу

[.....ExampleTrunk/buildenv]$ make HelloWorld_Test -p>log.txt

log.txt показывает, что переменные правила разрешены правильно, но я получаю ошибку

/usr/bin/ld: cannot find -lgtest
/usr/bin/ld: cannot find -lgtest_main
collect2: error: ld returned 1 exit status

Это потому, что правило "gtest" не запускалось раньше, но даже если я запускаю его вручную

[.....ExampleTrunk/buildenv]$ make gtest

и все необходимые файлы созданы в правильном каталоге ../BUILD/*, ошибка все еще остается.

В чем может быть причина, почему 1. Предварительное условие gtest не выполняется для make HelloWorld_Test и 2. даже если библиотеки созданы вручную и поэтому существуют, они не найдены компоновщиком

Спасибо за вашу помощь Jasmin

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

Как отмечалось в предыдущем комментарии, ответ MadScientist решил первую проблему построения gtest перед самим тестовым кодом, но для работы ссылок все же потребовались некоторые изменения. Чтобы получить сборку gtest до тестовых источников, я изменил правила сборки:

HelloWorld_Test: ../BUILD/BIN/HelloWorld_Test gtest | $(BUILD_DIRS);

../BUILD/BIN/HelloWorld_Test: $(moduleObj)
    g++ $(CPPFLAGS) $(CPPFLAGS_TEST) $(CXXFLAGS) $(LDFLAGS_TEST) $^ -o $@

до

HelloWorld_Test: ../BUILD/BIN/HelloWorld_Test;

../BUILD/BIN/HelloWorld_Test: $(moduleObj) | gtest $(BUILD_DIRS)
    g++ $(CXXFLAGS) $(LDFLAGS_TEST) $^ -o $@

Процесс связывания не требует заголовочных файлов. Теперь необходимые библиотеки gtest были собраны в ../BUILD/LIB до того, как файлы * .o были связаны, но библиотеки все еще не были найдены в процессе компоновки:

[.....ExampleTrunk/buildenv]$ make HelloWorld_Test -p>log.txt

все равно выдал ошибку

/usr/bin/ld: cannot find -lgtest
/usr/bin/ld: cannot find -lgtest_main
collect2: error: ld returned 1 exit status

Проблема может быть выявлена ​​путем получения подробного вывода процесса компоновки путем изменения правила на:

../BUILD/BIN/HelloWorld_Test: $(moduleObj) | gtest $(BUILD_DIRS)
    ld -o $@ $(LDFLAGS_TEST) $^ --verbose

Подробный вывод был:

GNU ld (GNU Binutils) 2.30
...
==================================================
attempt to open ../BUILD/LIB/libgtest.so failed
attempt to open ../BUILD/LIB/libgtest.a failed
...

поэтому проблема заключалась в том, что параметр "-l ..." добавляет префикс "lib" к имени библиотеки. Так как имена библиотек были gtest.a и gtest_main.a, их не удалось найти. Решением было изменить переменную с:

LDFLAGS_TEST := -L../BUILD/LIB/ -lgtest -lgtest_main

к полному пути представления

LDFLAGS_TEST := ../BUILD/LIB/gtest.a ../BUILD/LIB/gtest_main

Теперь все отлично работает: D

Если нужно связать больше библиотек со «особыми» именами, было бы лучше изменить соответствующий Makefile, добавив префикс «lib» к выходным именам библиотеки. Это позволит сократить командную строку, так как путь ../BUILD/LIB появляется только один раз с ключом -L. Тем не менее, я думаю, что в моем случае полные пути в порядке, потому что длина параметра не намного больше, а манипуляции со сторонним Makefile googletest меньше.

Спасибо за вашу помощь, Jasmin

0 голосов
/ 03 июля 2018

Это помогло бы людям ответить на ваш вопрос, если бы это был короткий, самостоятельный, правильный пример . Здесь слишком много информации, которая, вероятно, не имеет отношения к проблеме, и для ее прочтения требуется много усилий. Вместо этого начните с простой версии того, что вы хотите сделать с урезанным make-файлом. Если это не дает сбоя, продолжайте добавлять части вашей реальной среды, пока это не произойдет.

Я подозреваю, что ваша проблема прямо здесь, в конце вашего вопроса:

HelloWorld_Test: ../BUILD/BIN/HelloWorld_Test gtest | $(BUILD_DIRS);

Это не то, что вы хотите, потому что это означает, что сначала создается ../BUILD/BIN/HelloWorld_Test, а затем gtest.

На самом деле вы хотите, чтобы gtest был собран до ../BUILD/BIN/HelloWorld_Test, иначе библиотеки gtest недоступны. Я думаю, что вам нужно это вместо:

HelloWorld_Test: ../BUILD/BIN/HelloWorld_Test

../BUILD/BIN/HelloWorld_Test: $(moduleObj) | gtest $(BUILD_DIRS)
        g++ $(CPPFLAGS) $(CPPFLAGS_TEST) $(CXXFLAGS) $(LDFLAGS_TEST) $^ -o $@

Это гарантирует, что gtest будет создан до того, как вы попытаетесь связать вашу программу. В идеале вы хотели бы, чтобы эта цель также указала библиотеки gtest в качестве предварительных условий, чтобы в случае их изменения ваша программа была пересобрана.

...