Вызовите gnumake для всех подкаталогов параллельно (-j) и только затем запустите правило компоновщика последним (т.е. порядок важен) - PullRequest
0 голосов
/ 02 декабря 2018

У меня есть проект c ++ makefile.Это прекрасно работает для непараллельного строительства.Он работает на 99% для параллельного построения ... единственная проблема, которую я имею, в том, что я не могу заставить мою последнюю исполняемую линию связи работать в последнюю очередь (это должно быть последнее, что происходит).

У меня естьнекоторые ограничения: я не хочу иметь никаких PHONY-зависимостей на моей линии связи, потому что это заставляет ее повторно связываться каждый раз.Т.е. как только моя цель построена, когда я перестраиваю ее, ее не следует повторно связывать.

Вот (слегка надуманный) минимальный пример.Пожалуйста, не пытайтесь пробить дыры в этом, это действительно здесь просто, чтобы показать проблему, это не реально, но проблема, которую я показываю, есть.Вы должны быть в состоянии просто запустить это и увидеть ту же проблему, что и я.

# Set the default goal to build.
.DEFAULT_GOAL = build

#pretend subdirs (these don't really exist but it does not matter so long as they always try to be built)
MAKE_SUB_DIRS = 1 2 3

#pretend shared objects that are created by the pretend makefile sub directories (above)
OUTPUTS = out1.so out2.so out3.so

# Top level build goal - depends on all of the subdir makes and the target.out
.PHONY: build
build: $(MAKE_SUB_DIRS) target.out
    @echo build finished

# Takes 1 second to build each of these pretend sub make directories. PHONY so always runs
.PHONY: $(MAKE_SUB_DIRS)
$(MAKE_SUB_DIRS):
    @if [ ! -f out$@.so ] ; then echo making $@... ; sleep 1 ; echo a > out$@.so ; fi

# The main target, pretending that it needs out1,2 and 3 to link
# Should only run when target.out does not exist
# No PHONY deps allowed here
target.out:
    @echo linking $@...
    @ls $(OUTPUTS) > /dev/null
    @cat $(OUTPUTS) > target.out

# Clean for convinience
clean:
    @rm -rf *.so target.out

Теперь, мне действительно все равно, make работает, я хочу, чтобы make -j работал.Вот я пытаюсь запустить его:

admin@osboxes:~/sandbox$ make clean 
admin@osboxes:~/sandbox$ 
admin@osboxes:~/sandbox$ make -j     - 1st attempt
making 1...
making 2...
linking target.out...
making 3...
ls: cannot access 'out1.so': No such file or directory
ls: cannot access 'out2.so': No such file or directory
ls: cannot access 'out3.so': No such file or directory
makefile:24: recipe for target 'target.out' failed
make: *** [target.out] Error 2
make: *** Waiting for unfinished jobs....
admin@osboxes:~/sandbox$ 
admin@osboxes:~/sandbox$ make -j     - 2nd attempt
linking target.out...
build finished
admin@osboxes:~/sandbox$ 
admin@osboxes:~/sandbox$ make -j     - 3rd attempt
build finished
admin@osboxes:~/sandbox$

Итак, я выделил три попытки запустить его.

  • Попытка 1: вы можете увидеть все 4зависимости сборки запускаются одновременно (примерно).Поскольку каждый из makeing x... занимает 1 секунду, а linking почти мгновенный, мы видим мою ошибку.Однако все три «библиотеки» собраны правильно.
  • Попытка 2: библиотеки создаются, только если они еще не существуют (это код bash - притворяется, что сделал то, что мог сделать make-файл).В этом случае они уже созданы.Так что связывание проходит сейчас, так как для этого просто необходимы библиотеки.
  • Попытка 3: ничего не происходит, потому что ничего не нужно:)

Таким образом, вы можете видеть, что все шаги есть,это просто вопрос их заказа.Я хотел бы, чтобы make sub dirs 1, 2, 3 собирался в любом порядке параллельно, и тогда только после того, как все они будут завершены, я хочу, чтобы target.out работал (т.е. компоновщик).

Я не хочухотя это можно назвать так: $(MAKE) target.out, потому что в моем реальном make-файле у меня много переменных, которые все настроены ...

Я попытался посмотреть (из других ответов) .NOT_PARALLEL и использовать оператор порядка депозита| (pipe), и я попытался упорядочить загрузку правил, чтобы target.out стал последним .... но опция -j просто пропускает все это и разрушает мой порядок :( ... тамдолжен быть какой-то простой способ сделать это?

Ответы [ 3 ]

0 голосов
/ 02 декабря 2018

Обычно вы просто добавляете файлы lib в качестве предварительных условий для target.out:

target.out: $(OUTPUTS)
        @echo linking $@...

Дело в том, что это приведет к повторному связыванию target.out, если какой-либо из выходных файлов lib будет новее.Обычно это то, что вы хотите (если библиотека изменилась, вам нужно повторно связать цель), но вы, в частности, говорите, что нет.

GNU make предоставляет расширение под названием «только предварительные условия заказа», которое вы ставите послеa |:

target.out: | $(OUTPUTS)
        @echo linking $@...

сейчас, target.out будет перекомпонован, только если он не существует, но в этом случае он все еще будет ждать, пока $(OUTPUTS) не завершит сборку

Если ваши $(OUTPUT) файлы собраны подкаталогами, вы можете обнаружить, что вам нужно правило вроде:

.PHONY: $(OUTPUT)
$(OUTPUT):
        $(MAKE) -C $$(dirname $@) $@

для вызова рекурсивного make, если у вас нет других правил, которые будут вызывать make вподкаталоги

0 голосов
/ 02 декабря 2018

РЕДАКТИРОВАТЬ : добавить пример способов передачи переменных в под-производителей.Немного оптимизирован, добавив $(SUBDIRS) к предпосылкам build вместо того, чтобы сделать их в своем рецепте.

Я не уверен, что полностью понимаю вашу организацию, но одно решение для работы с подкаталогами заключается в следующем,Я предполагаю, что, как в вашем примере, этот подкаталог сборки foo производит foo.o в верхнем каталоге.Я также предполагаю, что ваш верхний Makefile определяет переменные (VAR1, VAR2 ...), которые вы хотите передать подкомпонентам при создании ваших подкаталогов.

VAR1    := some-value
VAR2    := some-other-value
...
SUBDIRS := foo bar baz
SUBOBJS := $(patsubst %,%.o,$(SUBDIRS))

.PHONY: build clean $(SUBDIRS)

build: $(SUBDIRS)
    $(MAKE) top

$(SUBDIRS):
    $(MAKE) -C $@ VAR1=$(VAR1) VAR2=$(VAR2) ...

top: top.o $(SUBOBJS)
    $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)

top.o: top.cc
    $(CXX) $(CXXFLAGS) -c $< -o $@

clean:
    rm -f top top.o $(SUBOBJS)
    for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done

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

VAR1    := some-value
VAR2    := some-other-value
...
export VAR1 VAR2 ...
0 голосов
/ 02 декабря 2018

Хорошо, поэтому я нашел «а» решение ... но оно идет немного вразрез с тем, что я хотел, и поэтому уродливо (но не так , что ужасно):

Способ только , который я могу понять, чтобы обеспечить порядок в параллельной сборке (опять же из других прочитанных мной ответов), выглядит следующим образом:

rule: un ordered deps
rule:
    @echo this will happen last

Здесь будут сделаны три операции (или помечены?)) в любом порядке, а затем, наконец, будет запущена строка эха.

Однако я хочу сделать правило и, в частности, так, чтобы оно проверяло, изменилось ли что-нибудь или файл не существует.- и затем, и только тогда, запускается правило.

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

  1. Переменные не передаются по умолчанию
  2. Многие из тех же правил будут переопределены (не разрешено)или хотел)

Итак, я придумал это:

makefile:

# Set the default goal to build.
.DEFAULT_GOAL = build

#pretend subdirs (these don't really exist but it does not matter so long as they always try to be built)
MAKE_SUB_DIRS = 1 2 3

#pretend shared objects that are created by the pretend makefile sub directories (above)
OUTPUTS = out1.so out2.so out3.so

# Top level build goal - depends on all of the subdir makes and the target.out
export OUTPUTS
.PHONY: build
build: $(MAKE_SUB_DIRS)
    @$(MAKE) -f link.mk target.out --no-print-directory
    @echo build finished

# Takes 1 second to build each of these pretend sub make directories. PHONY so always runs
.PHONY: $(MAKE_SUB_DIRS)
$(MAKE_SUB_DIRS):
    @if [ ! -f out$@.so ] ; then echo making $@... ; sleep 1 ; echo a > out$@.so ; fi

# Clean for convinience
clean:
    @rm -rf *.so target.out

link.mk:

# The main target, pretending that it needs out1,2 and 3 to link
# Should only run when target.out does not exist
# No PHONY deps allowed here
target.out:
    @echo linking $@...
    @ls $(OUTPUTS) > /dev/null
    @cat $(OUTPUTS) > target.out

Итак, здесь я поместил правило компоновщика в отдельный make-файл под названием link.mk, это позволяет избежать рекурсивного вызова make для одного и того же файла (и, следовательно, с переопределенными правилами).Но я должен экспортировать все переменные, через которые мне нужно пройти ... что некрасиво и добавляет немного накладных расходов на обслуживание, если эти переменные изменятся.

... но ... это работает:)

Я не буду отмечать это в ближайшее время, потому что я надеюсь, что какой-нибудь гений укажет более аккуратный / лучший способ сделать это ...

...