Zdefiniuj zmienną make w czasie wykonywania reguły

209

W moim pliku GNUmakefile chciałbym mieć regułę, która korzysta z katalogu tymczasowego. Na przykład:

out.tar: TMP := $(shell mktemp -d)
        echo hi $(TMP)/hi.txt
        tar -C $(TMP) cf $@ .
        rm -rf $(TMP)

Jak napisano, powyższa reguła tworzy katalog tymczasowy w momencie analizowania reguły . Oznacza to, że nawet ja nie rozumiem. Cały czas powstaje wiele katalogów tymczasowych. Chciałbym uniknąć zapełnienia mojego / tmp nieużywanymi katalogami tymczasowymi.

Czy istnieje sposób, aby zmienna była definiowana tylko podczas uruchamiania reguły, a nie w przypadku jej definiowania?

Moją główną myślą jest zrzucenie mktemp i tar do skryptu powłoki, ale wydaje się to trochę nieestetyczne.

Emil Sit
źródło

Odpowiedzi:

322

W twoim przykładzie TMPzmienna jest ustawiana (i tworzony jest katalog tymczasowy) za każdym razem, gdy oceniane są reguły dla out.tar. Aby utworzyć katalog tylko wtedy, gdy out.tarjest faktycznie uruchamiany, musisz przenieść tworzenie katalogu w dół do następujących kroków:

out.tar : 
    $(eval TMP := $(shell mktemp -d))
    @echo hi $(TMP)/hi.txt
    tar -C $(TMP) cf $@ .
    rm -rf $(TMP)

Funkcja eval ocenia ciąg znaków tak, jakby został ręcznie wpisany do pliku makefile. W takim przypadku ustawia TMPzmienną na wynik shellwywołania funkcji.

edycja (w odpowiedzi na komentarze):

Aby utworzyć unikalną zmienną, możesz wykonać następujące czynności:

out.tar : 
    $(eval $@_TMP := $(shell mktemp -d))
    @echo hi $($@_TMP)/hi.txt
    tar -C $($@_TMP) cf $@ .
    rm -rf $($@_TMP)

Spowodowałoby to dodanie nazwy zmiennej docelowej (w tym przypadku out.tar) do zmiennej, tworząc zmienną o nazwie out.tar_TMP. Mam nadzieję, że to wystarczy, aby zapobiec konfliktom.

e.James
źródło
2
Fajne kilka wyjaśnień ... to nie będzie obejmować TMP dla tego celu, prawda? Więc jeśli istnieją inne reguły, które mają własne użycie $ (TMP) (być może równolegle z -j), mogą wystąpić konflikty? Ponadto, czy @echo jest nawet konieczne? Wygląda na to, że możesz to pominąć.
Emil Sit
3
Wygląda na to, że załatwi sprawę (choć trochę nieprzejrzyste dla przeciętnego guru, który nie jest Make :-) Dzięki!
Emil Sit
29
Ostrożnie z tym rozwiązaniem! $(eval $@_TMP := $(shell mktemp -d))stanie się, gdy plik Makefile zostanie po raz pierwszy oceniony niezgodnie z procedurami reguł. Innymi słowy, $(eval ...)dzieje się wcześniej niż myślisz. Chociaż może to być w porządku w tym przykładzie, ta metoda spowoduje problemy w przypadku niektórych operacji sekwencyjnych.
JamesThomasMoon1979
1
@ JamesThomasMoon1979 czy wiesz, jak pokonać te ograniczenia, np. Ewaluować niektóre zmienne, które powinny być wynikiem kroków wykonanych wcześniej w regule?
Vadim Kotov
2
Odpowiadając na moje pytanie: obejściem było dla mnie tworzenie plików podczas wykonywania pierwszej reguły i próba ich ewaluacji w pierwszym kroku drugiej reguły.
Vadim Kotov
63

Stosunkowo łatwym sposobem na to jest napisanie całej sekwencji jako skryptu powłoki.

out.tar:
   set -e ;\
   TMP=$$(mktemp -d) ;\
   echo hi $$TMP/hi.txt ;\
   tar -C $$TMP cf $@ . ;\
   rm -rf $$TMP ;\

Skonsolidowałem kilka pokrewnych wskazówek tutaj: https://stackoverflow.com/a/29085684/86967

nobar
źródło
3
Jest to zdecydowanie najprostszy i dlatego najlepszą odpowiedzią (unikając @a evali wykonywanie tej samej pracy). Zauważ, że na wyjściu widać $TMP(np. tar -C $TMP ...), Chociaż wartość jest poprawna przekazana do polecenia.
Karl Richter
Tak to zwykle się robi; Mam nadzieję, że ludzie zobaczą tę odpowiedź, ponieważ w praktyce nikt nie przechodzi wszystkich wysiłków przyjętej.
pmos
Co się stanie, jeśli używasz poleceń specyficznych dla powłoki? W takim przypadku polecenia powłoki powinny być w tym samym języku powłoki, który wywołuje make, prawda? Widziałem jakiś makefile z shebang.
ptitpion
@ptitpion: Możesz również chcieć SHELL := /bin/bashw swoim makefile włączyć funkcje specyficzne dla BASH.
nobar
31

Inną możliwością jest użycie osobnych linii do skonfigurowania Make zmiennych, gdy reguła zostanie uruchomiona.

Na przykład tutaj jest plik makefile z dwiema regułami. Jeśli reguła zostanie uruchomiona, tworzy katalog tymczasowy i ustawia TMP na nazwę katalog tymczasowy.

PHONY = ruleA ruleB display

all: ruleA

ruleA: TMP = $(shell mktemp -d testruleA_XXXX)
ruleA: display

ruleB: TMP = $(shell mktemp -d testruleB_XXXX)
ruleB: display

display:
    echo ${TMP}

Uruchomienie kodu daje oczekiwany wynik:

$ ls
Makefile
$ make ruleB
echo testruleB_Y4Ow
testruleB_Y4Ow
$ ls
Makefile  testruleB_Y4Ow
użytkownik3124434
źródło
Przypominamy, że GNU make ma składnię dla wymagań wstępnych tylko dla zamówień, które mogą być konieczne do uzupełnienia tego podejścia.
anol
11
Ostrożnie z tym rozwiązaniem! ruleA: TMP = $(shell mktemp -d testruleA_XXXX)i ruleB: TMP = $(shell mktemp -d testruleB_XXXX)stanie się, gdy plik Makefile zostanie po raz pierwszy oceniony. Innymi słowy, ruleA: TMP = $(shell ...dzieje się wcześniej niż myślisz. Chociaż może to działać w tym konkretnym przypadku, ta metoda spowoduje problemy w przypadku niektórych operacji sekwencyjnych.
JamesThomasMoon1979
1

Nie lubię odpowiedzi „Nie”, ale… nie.

makeZmienne mają charakter globalny i należy je oceniać na etapie analizy pliku makefile, a nie na etapie wykonywania.

W takim przypadku, dopóki zmienna lokalna dla pojedynczego celu, podążaj za odpowiedzią @ nobar i uczyń ją zmienną powłoki.

Zmienne specyficzne dla celu również są uważane za szkodliwe przez implementacje innych marek : kati , pillaake Mozilli . Z ich powodu cel można zbudować inaczej w zależności od tego, czy jest on zbudowany autonomicznie, czy jako zależność celu nadrzędnego od zmiennej specyficznej dla celu. I nie będziesz wiedział, jak to było , ponieważ nie wiesz, co jest już zbudowane.

Victor Sergienko
źródło