Comment affecter la sortie d'une commande à une variable Makefile

225

J'ai besoin d'exécuter certaines règles de création de façon conditionnelle, uniquement si le Python installé est supérieur à une certaine version (disons 2.5).

Je pensais pouvoir faire quelque chose comme exécuter:

python -c 'import sys; print int(sys.version_info >= (2,5))'

puis en utilisant la sortie ('1' si ok, '0' sinon) dans une ifeqinstruction make.

Dans un simple script shell bash, c'est juste:

MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`

mais cela ne fonctionne pas dans un Makefile.

Aucune suggestion? Je pourrais utiliser toute autre solution de contournement raisonnable pour y parvenir.

fortran
la source
Étrange retour tourne autour du travail de commande pour exécuter d'autres scripts pour moi dans un Makefile. Ça pourrait être autre chose.
Leif Gruenwoldt

Réponses:

346

Utilisez le Make shellMake comme dansMY_VAR=$(shell echo whatever)

me@Zack:~$make
MY_VAR IS whatever

me@Zack:~$ cat Makefile 
MY_VAR := $(shell echo whatever)

all:
    @echo MY_VAR IS $(MY_VAR)
Arkaitz Jimenez
la source
34
shell n'est pas une commande intégrée Make standard. Il s'agit d'une fonction intégrée GNU Make.
Dereckson
12
stackoverflow.com/a/2373111/12916 ajoute une note importante sur l'évasion $.
Jesse Glick
6
Cet exemple simple fonctionne. Il fonctionne également avec le pipeline de commandes shell. Mais il est essentiel que vous utilisiez $$ pour représenter $ dans la commande shell
Sergey P. aka azure
28
Bien que la question soit légèrement ancienne, il est préférable de faire MY_VAR: = $ (shell ...), sinon chaque fois que MY_VAR est évalué, il exécutera à nouveau $ (shell ...).
Russ Schultz
J'avais un espace entre le shell et les parenthèses ouvrantes et ce n'est qu'après avoir supprimé l'espace que mon texte de sortie de makefile
peterchaula
29

Envelopper la mission dans un evaltravail pour moi.

# dependency on .PHONY prevents Make from 
# thinking there's `nothing to be done`
set_opts: .PHONY
  $(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)
Dave
la source
6
"Remarque: Le @true ici empêche Make de penser qu'il n'y a rien à faire." C'est pour ça .PHONY always make these targets.
underscore_d
Je vous remercie! Cela m'aide à contourner "bash bizarre" dans le makefile
Nam G VU
19

Voici un exemple un peu plus compliqué de tuyauterie et d'affectation de variables dans la recette:

getpodname:
    # Getting pod name
    @eval $$(minikube docker-env) ;\
    $(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
    echo $(PODNAME)
Francesco Casula
la source
2
Au cas où cela serait utile, j'utilise une approche un peu similaire pour obtenir le PODNAMEnom du déploiement:$(eval PODNAME=$(shell sh -c "kubectl get pod -l app=sqlproxy -o jsonpath='{.items[0].metadata.name}'"))
Jan Richter
La syntaxe m'a confondu pendant une seconde jusqu'à ce que je réalise que vous utilisiez à la fois le shell intégré eval (sur la ligne docker-env) et la fonction make eval (sur la ligne suivante).
Brian Gordon
17

J'écris une réponse pour augmenter la visibilité de la syntaxe réelle qui résout le problème. Malheureusement, ce que quelqu'un peut considérer comme insignifiant peut devenir un casse-tête très important pour quelqu'un qui cherche une réponse simple à une question raisonnable.

Mettez ce qui suit dans le fichier "Makefile".

MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')

all:
    @echo MY_VAR IS $(MY_VAR)

Le comportement que vous aimeriez voir est le suivant (en supposant que vous avez installé python récent).

make
MY_VAR IS 1

Si vous copiez et collez le texte ci-dessus dans le Makefile, l'obtiendrez-vous? Probablement pas. Vous obtiendrez probablement une erreur semblable à ce qui est signalé ici:

makefile: 4: *** séparateur manquant. Arrêtez

Pourquoi: Parce que même si j'ai personnellement utilisé un véritable onglet, Stack Overflow (essayant d'être utile) convertit mon onglet en un certain nombre d'espaces. Vous, citoyen Internet frustré, copiez maintenant ceci, pensant que vous avez maintenant le même texte que j'ai utilisé. La commande make lit maintenant les espaces et constate que la commande "all" n'est pas formatée correctement. Copiez donc le texte ci-dessus, collez-le, puis convertissez l'espace avant "@echo" en onglet, et cet exemple devrait enfin, espérons-le, fonctionner pour vous.

AlanSE
la source
Cela dépend de l'éditeur dans lequel vous collez. Je viens de copier et coller dans un éditeur Eclipse Makefile, et j'ai obtenu un onglet de tête (selon les besoins).
Technophile
Oh je n'y ai pas pensé. Atome ici.
AlanSE
11

Méfiez-vous des recettes comme celle-ci

target:
    MY_ID=$(GENERATE_ID);
    echo $MY_ID;

Cela fait deux choses mal. La première ligne de la recette est exécutée dans une instance de shell distincte de la deuxième ligne. La variable est perdue entre-temps. La deuxième chose qui cloche, c'est que le $n'est pas échappé.

target:
    MY_ID=$(GENERATE_ID); \
    echo $$MY_ID;

Les deux problèmes ont été corrigés et la variable est utilisable. La barre oblique inverse combine les deux lignes pour s'exécuter dans un seul shell, d'où le réglage de la variable et la lecture des mots de passe variables, fonctionne.

Je me rends compte que le message d'origine disait comment obtenir les résultats d'une commande shell dans une variable MAKE, et cette réponse montre comment l'obtenir dans une variable shell. Mais d'autres lecteurs pourraient en bénéficier.

Une dernière amélioration, si le consommateur s'attend à ce qu'une "variable d'environnement" soit définie, alors vous devez l'exporter.

my_shell_script
    echo $MY_ID

aurait besoin de cela dans le makefile

target:
    export MY_ID=$(GENERATE_ID); \
    ./my_shell_script;

J'espère que cela aide quelqu'un. En général, il faut éviter de faire un vrai travail en dehors des recettes, car si quelqu'un utilise le makefile avec l'option '--dry-run', pour SEULEMENT voir ce qu'il fera, il n'aura pas d'effets secondaires indésirables. Chaque $(shell)appel est évalué au moment de la compilation et certains travaux réels pourraient accidentellement être effectués. Mieux vaut laisser le vrai travail, comme générer des identifiants, à l'intérieur des recettes lorsque cela est possible.

Juraj
la source
9

Avec GNU Make, vous pouvez utiliser shellet evalstocker, exécuter et affecter la sortie des appels de ligne de commande arbitraires. La différence entre l'exemple ci-dessous et ceux qui utilisent :=est l' :=affectation qui se produit une fois (lorsqu'elle est rencontrée) et pour tous. Les variables étendues récursivement définies avec =sont un peu plus "paresseuses"; les références à d'autres variables restent jusqu'à ce que la variable elle-même soit référencée, et l'expansion récursive suivante a lieu à chaque fois que la variable est référencée , ce qui est souhaitable pour rendre "des extraits cohérents et appelables". Voir le manuel sur la définition des variables pour plus d'informations.

# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)

# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))

# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
    @echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
    $(SET_ID)
# This will now be a random number
    @echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
    $(SET_ID)
# This will now be a different random number
    @echo $(MY_ID)
Mitchell Tracy
la source
Vous avez la première belle explication que j'ai rencontrée. Je vous remercie. Bien qu'une chose à ajouter est que si la $(SET_ID)vie à l' intérieur d'une ifclause qui est faux alors il est encore appelé .
Roman
Il est toujours appelé car les if stmts sont évalués au moment de la compilation du makefile et non au moment de l'exécution. Pour des instructions spécifiques à l'exécution, mettez-les dans des recettes. S'ils sont conditionnels, ajoutez if stmts, écrit en bash / shell, dans le cadre de la recette.
Juraj
0

Dans l'exemple ci-dessous, j'ai stocké le chemin d'accès au dossier Makefile LOCAL_PKG_DIR, puis j'utilise une LOCAL_PKG_DIRvariable dans les cibles.

Makefile:

LOCAL_PKG_DIR := $(shell eval pwd)

.PHONY: print
print:
    @echo $(LOCAL_PKG_DIR)

Sortie borne:

$ make print
/home/amrit/folder
Amritpal Singh
la source