2012-10-08 12 views
11

Chcę zdefiniować funkcję powłokiJak zdefiniować globalne funkcje powłoki w pliku Makefile?

#!/bin/sh 

test() 
{ 
    do_some_complicated_tests $1 $2; 
    if something; then 
    build_thisway $1 $2; 
    else 
    build_otherway $1 $2; 
    fi 
} 

w taki sposób, że można go używać w każdej reguły mojego Makefile, takich jak:

foo: bar 
    test foo baz 

Żeby było jasne, chcę muszlę funkcja jest częścią Makefile. Jaki jest najbardziej elegancki sposób na zrobienie tego? Punkty premiowe, jeśli możesz to zrobić bez wywoływania rekursywnie.


Tło: Mój rzeczywisty problem polega na tym make -n produkuje bardzo długi i nieczytelny wyjście. Każda reguła używa prawie tej samej sekwencji nieczytelnych poleceń powłoki i istnieje wiele reguł. Powyższe rozwiązanie sprawiłoby, że wyjście z make -n było bardziej użyteczne.

+0

zrobiłem coś @JonathanLeffler powiedział mogłem” być zrobione?Będę niezmiernie zadowolony z siebie przez co najmniej miesiąc. – Beta

+0

Dlaczego po prostu nie umieścić kodu w skrypcie powłoki i wywołać to? – reinierpost

Odpowiedz

7

To rozwiązanie nie polega na zewnętrznym pliku tymczasowego i nie zmuszają do majstrować przy zmiennej SHELL.

TESTTOOL=sh -c '\ 
    do_some_complicated_tests $$1 $$2; \ 
    if something; then 
    build_thisway $$1 $$2; 
    else 
    build_otherway $$1 $$2; 
    fi' TESTTOOL 

ifneq (,$(findstring n,$(MAKEFLAGS))) 
TESTTOOL=: TESTTOOL 
endif 

foo: bar 
    ${TESTTOOL} foo baz 

W ifneq…endif sprawdza blokowe flaga -n w linii poleceń i ustawia ekspansję TESTTOOL do : TESTTOOL, który jest łatwy do odczytania i bezpiecznie wykonać.

Najlepszym rozwiązaniem może być przekształcenie funkcji powłoki w rzeczywisty program, jeśli jest to opcja dla Ciebie.

3

Coś mi mówi, że będziesz lepiej filtrowanie wyjście make -n, ale co pytasz jest możliwa:

define test 
    @echo do some tests with $(1) and $(2); \ 
    SOMETHING=$(1)_3 ; \ 
    if [ $$SOMETHING == foo_3 ]; then \ 
    echo build this way $(1) $(2); \ 
    else \ 
    echo build another way $(1) $(2) ; \ 
    fi 
endef 

someTarget: 
    $(call test,foo,bar) 

someOtherTarget: 
    $(call test,baz,quartz) 
+0

Dzięki! Niestety to rozwiązanie jest dokładnie tym, którego starałem się ominąć. – Holger

+0

@Holger, co jest nie tak z tym? – Beta

+0

Działa dobrze, ale dane wyjściowe 'make -n' staną się nieczytelne, ponieważ skrypt powłoki pojawi się ponownie dla każdego celu, który jest wywoływany. Co gorsza, skrypt będzie w jednej linii. Mógłbym używać filtrów, ale wtedy musiałbym również debugować filtry, których chcę uniknąć. – Holger

5

Ponieważ pytanie jest oznaczone gnu-make, może być zadowolony z rozwiązania Beta, ale Myślę, że najlepszym rozwiązaniem jest 1) zmiana nazwy funkcji innej niż test (unikaj także nazw takich jak ls i cd); dla celów dyskusji załóżmy, że zmieniłeś nazwę na foo, 2) po prostu napisz skrypt o nazwie foo i wywołaj go z pliku Makefile. Jeśli naprawdę chcesz, aby zdefiniować funkcję powłoki w Makefile, po prostu zdefiniować dla każdej reguły:

F = foo() { \ 
    do_some_complicated_tests $$1 $$2; \ 
    if something; then \ 
    build_thisway $$1 $$2; \ 
    else \ 
    build_otherway $$1 $$2; \ 
    fi \ 
} 

all: bar 
    @$(F); foo baz bar 

Nie, że trzeba mieć kontynuacje linii na każdej linii definicji foo, a cała $ są uciekł tak że są przekazywane do powłoki.

+1

Dzięki! Definiowanie funkcji w każdej regule nie rozwiązuje problemu, że wyjście 'make -n' jest za długie. W tym przypadku wolałbym rozwiązanie Bety. Jednak dla mnie mogą działać następujące: Mogę dodać zależność 'all: foo.sh' i reguła' foo.sh: ', która wyprowadza skrypt do' foo.sh'. Jest powolny i chętnie znajdę coś bardziej eleganckiego, ale naprawdę chcę, żeby wszystko było w jednym pliku. – Holger

+0

Great; to jest moje ulubione. Jeszcze bardziej podoba mi się następujący skrót: 'F_DEF = foo() {as above}', a następnie 'F = @ $ (F_DEF); foo', a następnie możesz ponownie użyć tego z dowolnego miejsca jako '$ (F) arg1 ... argn'. –

3

Nie sądzę, to kwalifikuje się jako "elegancki", ale wydaje się, aby robić to, co chcesz:

## 
## --- Start of ugly hack 
## 

THIS_FILE := $(lastword $(MAKEFILE_LIST)) 

define shell-functions 
: BEGIN 
    # Shell syntax here 
    f() 
    { 
    echo "Here you define your shell function. This is f(): [email protected]" 
    } 

    g() 
    { 
    echo "Another shell function. This is g(): [email protected]" 
    } 
: END 
endef 

# Generate the file with function declarations for the shell 
$(shell sed -n '/^: BEGIN/,/^: END/p' $(THIS_FILE) > .functions.sh) 

# The -i is necessary to use --init-file 
SHELL := /bin/bash --init-file .functions.sh -i 

## 
## -- End of ugly hack 
## 

all: 
    @f 1 2 3 4 
    @g a b c d 

Running to produkuje:

$ make -f hack.mk 
Here you define your shell function. This if f(): 1 2 3 4 
Another shell function. This is g(): a b c d 

Running go -n produkuje:

$ make -f hack.mk -n 
f 1 2 3 4 
g a b c d 

To opiera się na fakcie, że makra zdefiniowane między define i endef nie są w ogóle interpretowane przez make, dopóki nie zostaną faktycznie użyte, więc możesz użyć składni powłoki bezpośrednio i nie musisz kończyć każdej linii odwrotnym ukośnikiem. Oczywiście będzie bombardować, jeśli zadzwonisz do makra shell-functions.

+0

Twoja metoda wyodrębniania pliku .sh jest bardzo pomocna, dziękuję. Jest to zbliżone do tego, co chcę. Jeśli mimo to piszę tymczasowy plik .sh, to nie obchodzi mnie, że używam f i g jako funkcji powłoki; Zamiast tego użyłbym f.sh i g.sh. Wtedy nie jest już tak brzydka, ponieważ nie ma potrzeby na nowo definiować '$ (SHELL)'. – Holger

+0

Zaletą sztuczki 'SHELL' jest to, że wszystko dzieje się automatycznie i przejrzyście. Nie musisz ładować bezpośrednio plików '.sh' do powłoki za każdym razem, gdy ich potrzebujesz. – Idelic

+0

Myślałem, że zrobić restartów $ (POWŁOKI) dla każdego polecenia? Tak więc efektywnie 'make all' powyżej, uruchamia $ (SHELL) i potoki" f 1 2 3 4 "w swoim standardzie. W następnym wierszu make uruchamia nową instancję $ (SHELL) i potoki "g a b c d" w jej standardowe wejście. Czy to prawda? Jeśli tak, to nie widzę przewagi nad używaniem oddzielnych plików (ponieważ 'bash --init-file ...' musi ponownie przeczytać plik .sh). [Aby było jasne, moją proponowaną alternatywą jest użycie '@./F.sh 1 2 3 4' zamiast' @f 1 2 3 4'.] – Holger

3

To naprawdę getto, ale cokolwiek. Używam zsh, ale jestem pewien, że istnieją odpowiedniki bash i sh.

Makefile:

export ZDOTDIR := ./ 

SHELL := /usr/bin/env zsh 

default: 
    f 

.zshenv, sam katalog jako Makefile:

f() { echo 'CHECK OUT THIS AWESOME FUNCTION!' } 

Zmienna ZDOTDIR sprawia zsh spojrzeć w bieżącym katalogu dla dotfiles. Wtedy po prostu trzymasz to, co chcesz w .zshenv.

$ make 
f 
CHECK OUT THIS AWESOME FUNCTION! 
1

TL; DR:

W swoim makefile:

SHELL=bash 
BASH_FUNC_command%%=() { ... } 
export BASH_FUNC_command%% 

Długa odpowiedź

Robiłem coś podobnego z bash w non-make kontekście, który pracuje tutaj .

oświadczyłem moje funkcje powłoki bash, a następnie eksportowane im:

export -f function-name 

Są to oczywiście dostępne, jeśli ta sama powłoka jest wywoływana jako sub-procesu, w danym przypadku, od marki.

Przykład:

$ # define the function 
$ something() { echo do something here ; } 
$ export -f something 

$ # the Makefile 
$ cat > Makefile <<END 
SHELL=bash 
all: ; something 
END 
$ 

Wypróbuj

$ make 
something 
do something here 
$ 

Jak to wygląda w środowisku?

$ env | grep something 
BASH_FUNC_something%%=() { echo do something here 

Pytanie brzmi: jak ustawić zmienne środowiskowe z pliku Makefile. Wzór wydaje się być BASH_FUNC_ nazwa-funkcji %% =() {funkcja ciała}

bezpośrednio w makijażu

Więc jak to działa dla Ciebie? Spróbuj tego makefile

SHELL=bash 
define BASH_FUNC_something-else%% 
() { 
    echo something else 
} 
endef 
export BASH_FUNC_something-else%% 

all: ; something-else 

i spróbuj go:

$ make 
something-else 
something else 

To trochę brzydki w Makefile ale nie prezentuje brzydota dla zrobić -n