Próbuję wykonać następujące czynności. Istnieje program, nazwij go foo-bin
, który pobiera pojedynczy plik wejściowy i generuje dwa pliki wyjściowe. Głupia reguła Makefile byłaby następująca:
file-a.out file-b.out: input.in
foo-bin input.in file-a.out file-b.out
Nie oznacza to jednak make
w żaden sposób, że oba cele zostaną wygenerowane jednocześnie. Jest to dobre, gdy działa make
w trybie seryjnym, ale prawdopodobnie spowoduje problemy, jeśli spróbujesz make -j16
lub coś równie szalonego.
Pytanie brzmi, czy istnieje sposób na napisanie właściwej reguły Makefile dla takiego przypadku? Oczywiście wygenerowałoby to DAG, ale w jakiś sposób podręcznik GNU make nie precyzuje, jak można by obsłużyć ten przypadek.
Uruchomienie tego samego kodu dwa razy i wygenerowanie tylko jednego wyniku nie wchodzi w grę, ponieważ obliczenia wymagają czasu (pomyśl: godziny). Wyprowadzenie tylko jednego pliku również byłoby raczej trudne, ponieważ często jest on używany jako dane wejściowe do GNUPLOT, który nie wie, jak obsłużyć tylko ułamek pliku danych.
Odpowiedzi:
Rozwiązałbym to w następujący sposób:
W tym przypadku make równoległe „serializuje” tworząc a i b, ale ponieważ tworzenie b nic nie robi, nie zajmuje to czasu.
źródło
file-b.out
został utworzony zgodnie z opisem, a następnie w tajemniczy sposób usunięty, niemake
będzie można go odtworzyć, ponieważfile-a.out
nadal będzie obecny. jakieś pomysły?file-a.out
powyższe rozwiązanie działa dobrze. Sugeruje to włamanie: gdy istnieje tylko jedna z a lub b, zależności należy uporządkować w taki sposób, aby brakujący plik pojawił się jako wynikinput.in
. Kilka$(widcard...)
s,$(filter...)
s,$(filter-out...)
s itd. Powinno załatwić sprawę. Fuj.foo-bin
oczywiście najpierw utworzy jeden z plików (używając rozdzielczości mikrosekund), więc musisz upewnić się, że masz poprawną kolejność zależności, gdy oba pliki istnieją.touch file-a.out
jako drugie polecenie pofoo-bin
wywołaniu.touch file-b.out
jako drugie polecenie w pierwszej regule i zduplikuj polecenia w drugiej regule. Jeśli brakuje któregokolwiek pliku lub jest starszy niżinput.in
, polecenie zostanie wykonane tylko raz i ponownie wygeneruje oba pliki zfile-b.out
nowszymi niżfile-a.out
.Sztuczka polega na zastosowaniu reguły wzorcowej z wieloma celami. W takim przypadku make przyjmie, że oba cele są tworzone przez pojedyncze wywołanie polecenia.
Ta różnica w interpretacji między regułami wzorców a normalnymi regułami nie ma sensu, ale jest przydatna w takich przypadkach i jest udokumentowana w podręczniku.
Ta sztuczka może być użyta dla dowolnej liczby plików wyjściowych, o ile ich nazwy mają jakiś wspólny podciąg dla
%
dopasowania. (W tym przypadku wspólnym podciągiem jest „.”)źródło
make
uruchomi to samo polecenie dwa razy jednocześnie.foo%in bar%src: input.in
:-)$(subst .,%,$(varA) $(varB)): input.in
(przetestowany z GNU make 4.1).Make nie ma na to żadnego intuicyjnego sposobu, ale istnieją dwa przyzwoite obejścia.
Po pierwsze, jeśli zaangażowane cele mają wspólny rdzeń, możesz użyć reguły przedrostka (z GNU make). To znaczy, jeśli chcesz naprawić następującą regułę:
Możesz to napisać w ten sposób:
(Używając zmiennej $ * wzorca reguły, która oznacza dopasowaną część wzorca)
Jeśli chcesz być przenośny do implementacji innych niż GNU Make lub jeśli twoje pliki nie mogą być nazwane tak, aby pasowały do reguły wzorca, jest inny sposób:
To mówi make, że input.in.intermediate nie będzie istniał przed uruchomieniem make, więc jego brak (lub jego znacznik czasu) nie spowoduje fałszywego uruchomienia foo-bin. I niezależnie od tego, czy plik-a.out, czy plik-b.out, czy oba są nieaktualne (w stosunku do input.in), foo-bin zostanie uruchomione tylko raz. Możesz użyć .SECONDARY zamiast .INTERMEDIATE, co poinstruuje make NOT o usuwaniu hipotetycznej nazwy pliku input.intermediate. Ta metoda jest również bezpieczna w przypadku kompilacji równoległych.
Ważny jest średnik w pierwszym wierszu. Tworzy pustą receptę dla tej reguły, dzięki czemu Make wie, że naprawdę będziemy aktualizować file-a.out i file-b.out (dzięki @siulkiulki i innym, którzy to wskazali)
źródło
-j1
kompilacjami). Ponadto, jeśli makefile zostanie przerwany i pozostawiinput.in.intermediate
plik wokół, będzie naprawdę zdezorientowany.file-a.out file-b.out: input.in.intermediate
Abyfile-a.out file-b.out: input.in.intermediate ;
uzyskać więcej informacji, wystarczy przejść na stronę stackoverflow.com/questions/37873522/… .Jest to oparte na drugiej odpowiedzi @ deemer, która nie opiera się na regułach wzorców, i rozwiązuje problem, który miałem z zagnieżdżonymi zastosowaniami obejścia.
Dodałbym to jako komentarz do odpowiedzi @ deemer, ale nie mogę, ponieważ właśnie utworzyłem to konto i nie mam żadnej reputacji.
Objaśnienie: Pusta receptura jest potrzebna, aby umożliwić firmie Make prawidłowe prowadzenie księgowości w celu oznaczenia
file-a.out
ifile-b.out
przebudowania. Jeśli masz jeszcze inny cel pośredni, od którego zależyfile-a.out
, Make zdecyduje się nie tworzyć zewnętrznego pośredniego celu, twierdząc, że:źródło
Po GNU Make 4.3 (19 stycznia 2020) możesz używać „zgrupowanych wyraźnych celów”. Wymień
:
się&:
.Plik NEWS w GNU Make mówi:
źródło
Tak to robię. Najpierw zawsze oddzielam wymagania wstępne od przepisów. Wtedy w tym przypadku nowy cel do wykonania przepisu.
Za pierwszym razem:
1. Próbujemy skompilować plik-a.out, ale najpierw trzeba wykonać atrapę-a-i-b.out, więc make uruchamia przepis atrapy-a-i-b.out.
2. Próbujemy zbudować plik-b.out, atrapy-a-i-b.out są aktualne.
Drugi i kolejny raz:
1. Próbujemy zbudować plik-a.out: sprawdź wymagania wstępne, normalne wymagania wstępne są aktualne, dodatkowe wymagania wstępne są pominięte, więc są ignorowane.
2. Próbujemy zbudować plik-b.out: przyjrzyj się wymaganiom wstępnym, normalne wymagania wstępne są aktualne, dodatkowe wymagania wstępne brakuje, więc są ignorowane.
źródło
file-b.out
make nie ma możliwości ponownego utworzenia.Jako rozszerzenie odpowiedzi @ deemer uogólniłem ją na funkcję.
Awaria:
Usuń wszelkie początkowe / końcowe spacje z pierwszego argumentu
Utwórz zmienną docelową o unikalnej wartości, która będzie używana jako pośrednia wartość docelowa. W tym przypadku wartością będzie .inter.file-a.out_file-b.out.
Utwórz pustą recepturę dla wyników z unikalnym celem jako zależnością.
Zadeklaruj unikalny cel jako pośredni.
Zakończ z odniesieniem do unikalnego celu, aby ta funkcja mogła być używana bezpośrednio w przepisie.
Zauważ również, że użycie eval jest tutaj spowodowane tym, że eval nie rozszerza się do zera, więc pełne rozwinięcie funkcji jest tylko unikalnym celem.
Muszę przyznać uznanie Regułom Atomowym w GNU Make, z których ta funkcja jest inspirowana.
źródło
Aby zapobiec równoległemu wielokrotnemu wykonywaniu reguły z wieloma wyjściami
make -jN
, użyj.NOTPARALLEL: outputs
. W Twoim przypadku :źródło