Est-il possible de créer une variable de chaîne multiligne dans un Makefile

122

Je veux créer une variable makefile qui est une chaîne multiligne (par exemple le corps d'une annonce de publication par e-mail). quelque chose comme

ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Mais je n'arrive pas à trouver un moyen de le faire. C'est possible?

jonner
la source

Réponses:

172

Oui, vous pouvez utiliser le mot-clé define pour déclarer une variable multiligne, comme ceci:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

La partie délicate est de récupérer votre variable multiligne du makefile. Si vous faites simplement la chose évidente d'utiliser "echo $ (ANNOUNCE_BODY)", vous verrez le résultat que d'autres ont posté ici - le shell essaie de gérer la deuxième ligne et les suivantes de la variable comme des commandes.

Cependant, vous pouvez exporter la valeur de la variable telle quelle vers le shell en tant que variable d'environnement, puis la référencer depuis le shell en tant que variable d'environnement (PAS une variable de fabrication). Par exemple:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Notez l'utilisation de $$ANNOUNCE_BODY, indiquant une référence de variable d'environnement shell, plutôt que $(ANNOUNCE_BODY), qui serait une référence de variable make régulière. Veillez également à utiliser des guillemets autour de votre référence de variable, pour vous assurer que les retours à la ligne ne sont pas interprétés par le shell lui-même.

Bien sûr, cette astuce particulière peut être sensible à la plate-forme et au shell. Je l'ai testé sur Ubuntu Linux avec GNU bash 3.2.13; YMMV.

Eric Melski
la source
1
export ANNOUNCE_BODYne définit la variable que dans les règles - cela ne permet pas de référencer $$ ANNOUNCE_BODY pour définir d'autres variables.
anatoly techtonik
@techtonik si vous souhaitez utiliser la valeur de ANNOUNCE_BODYdans d'autres définitions de variable, référencez-la simplement comme n'importe quelle autre variable make. Par exemple OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY),. Bien sûr, vous aurez toujours besoin de l' exportastuce si vous voulez OTHERsortir dans une commande.
Eric Melski
25

Une autre approche pour «récupérer votre variable multiligne du makefile» (noté par Eric Melski comme «la partie délicate»), consiste à prévoir d'utiliser la substfonction pour remplacer les nouvelles lignes introduites par definedans votre chaîne multiligne par \n. Utilisez ensuite -e avec echopour les interpréter. Vous devrez peut-être définir le .SHELL = bash pour obtenir un écho qui fait cela.

Un avantage de cette approche est que vous mettez également d'autres caractères d'échappement de ce type dans votre texte et que vous les faites respecter.

Ce genre de synthétise toutes les approches mentionnées jusqu'à présent ...

Vous vous retrouvez avec:

define newline


endef

define ANNOUNCE_BODY=
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.  

It can be downloaded from $(DOWNLOAD_URL).  

endef

someTarget:
    echo -e '$(subst $(newline),\n,${ANNOUNCE_BODY})'

Notez que les guillemets simples sur l'écho final sont cruciaux.

malcook
la source
4
Notez que "echo -e" n'est pas portable. Vous devriez probablement préférer printf (1) à la place.
MadScientist
2
excellente réponse, cependant, j'ai dû supprimer l' =after define ANNOUNCE_BODYpour le faire fonctionner.
mschilli
13

En supposant que vous souhaitiez uniquement imprimer le contenu de votre variable sur la sortie standard, il existe une autre solution:

do-echo:
    $(info $(YOUR_MULTILINE_VAR))
super-héros
la source
1
Cette règle de non-op a produit un message indésirable: make: 'do-echo' is up to date.. En utilisant une commande "no op" j'ai pu le faire taire:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin
@GuillaumePapin Un peu en retard, mais vous pouvez utiliser .PHONYpour dire à votre Makefile qu'il n'y a rien à vérifier pour cette règle. Les makefiles étaient à l'origine pour les compilateurs, si je ne me trompe pas, makefaire de la magie que je ne comprends pas pour prévoir que la règle ne changera rien, et en tant que tel suppose que cela est «fait». L'ajout .PHONY do-echode votre fichier vous dira maked'ignorer cela et d'exécuter le code de toute façon.
M3D
Vous pouvez placer en $(info ...)dehors d'une règle de fabrication. Il générera toujours une sortie.
Daniel Stevens
Documentation: Make Control Functions
Daniel Stevens
3

Oui. Vous échappez aux nouvelles lignes avec \:

VARIABLE="\
THIS IS A VERY LONG\
TEXT STRING IN A MAKE VARIABLE"

mettre à jour

Ah, tu veux les nouvelles lignes? Alors non, je ne pense pas qu'il y ait moyen de faire vanille. Cependant, vous pouvez toujours utiliser un document ici dans la partie commande

[Cela ne fonctionne pas, voir le commentaire de MadScientist]

foo:
    echo <<EOF
    Here is a multiple line text
    with embedded newlines.
    EOF
Charlie Martin
la source
C'est vrai, mais cela ne me donne aucun formatage (retours à la ligne). Cela devient juste une seule ligne de texte
jonner
Les documents ici multilignes ne fonctionnent pas comme décrit dans GNU Make.
Matt B.
3
Les documents multilignes ici à l'intérieur des recettes ne fonctionneront dans AUCUNE version standard de make prenant en charge le standard POSIX: le standard make exige que chaque ligne distincte de la recette soit exécutée dans un shell séparé. Make n'effectue aucune analyse sur la commande pour indiquer que c'est un document ici ou non, et le gère différemment. Si vous connaissez une variante de make qui prend en charge cela (je n'en ai jamais entendu parler), vous devriez probablement l'indiquer explicitement.
MadScientist
2

Juste un post-scriptum à la réponse d'Eric Melski: Vous pouvez inclure la sortie des commandes dans le texte, mais vous devez utiliser la syntaxe Makefile "$ (shell foo)" plutôt que la syntaxe shell "$ (foo)". Par exemple:

define ANNOUNCE_BODY  
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.  

It can be downloaded from $(DOWNLOAD_URL).  

endef
Jim Van Zandt
la source
2

Cela ne donne pas de document ici, mais affiche un message sur plusieurs lignes d'une manière adaptée aux tuyaux.

=====

MSG = this is a\\n\
multi-line\\n\
message

method1:
     @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'

=====

Vous pouvez également utiliser les macros appelables de Gnu:

=====

MSG = this is a\\n\
multi-line\\n\
message

method1:
        @echo "Method 1:"
        @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'
        @echo "---"

SHOW = $(SHELL) -c "echo '$1'" | sed -e 's/^ //'

method2:
        @echo "Method 2:"
        @$(call SHOW,$(MSG))
        @echo "---"

=====

Voici le résultat:

=====

$ make method1 method2
Method 1:
this is a
multi-line
message
---
Method 2:
this is a
multi-line
message
---
$

=====

Paul Sander
la source
1

Pourquoi n'utilisez-vous pas le caractère \ n dans votre chaîne pour définir la fin de ligne? Ajoutez également la barre oblique inverse supplémentaire pour l'ajouter sur plusieurs lignes.

ANNOUNCE_BODY=" \n\
Version $(VERSION) of $(PACKAGE_NAME) has been released \n\
\n\
It can be downloaded from $(DOWNLOAD_URL) \n\
\n\
etc, etc"
Roalt
la source
Je préfère la réponse d'Erik Melski mais cela pourrait déjà faire l'affaire pour vous, en fonction de votre application.
Roalt
J'ai une question à ce sujet. Cela fonctionne principalement bien, sauf que je vois un "espace" supplémentaire au début de chaque ligne (sauf la première). Cela vous arrive-t-il? Je peux mettre tout le texte sur une seule ligne, séparé par \ n pour créer efficacement la sortie que j'aime. Le problème est que cela a l'air très moche dans le Makefile lui-même!
Shahbaz
J'ai trouvé une solution de contournement. J'ai fait passer le texte $(subst \n ,\n,$(TEXT))bien que j'aurais aimé qu'il y ait un meilleur moyen!
Shahbaz
1

Vous devez utiliser "define / endef" Make construct:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Ensuite, vous devez passer la valeur de cette variable à la commande shell. Mais, si vous faites cela en utilisant la substitution de variable, la commande se divisera en plusieurs:

ANNOUNCE.txt:
  echo $(ANNOUNCE_BODY) > $@               # doesn't work

Qouting n'aidera pas non plus.

La meilleure façon de transmettre une valeur est de la transmettre via la variable d'environnement:

ANNOUNCE.txt: export ANNOUNCE_BODY:=$(ANNOUNCE_BODY)
ANNOUNCE.txt:
  echo "$${ANNOUNCE_BODY}" > $@

Remarquer:

  1. La variable est exportée pour cette cible particulière, de sorte que vous pouvez réutiliser cet environnement ne sera pas beaucoup pollué;
  2. Utilisez la variable d'environnement (doubles qoutes et accolades autour du nom de la variable);
  3. Utilisation de guillemets autour de la variable. Sans eux, les nouvelles lignes seront perdues et tout le texte apparaîtra sur une seule ligne.
Maxim Kulkin
la source
1

Dans l'esprit du .ONESHELL, il est possible de se rapprocher assez près des environnements .ONESHELL contestés:

define _oneshell_newline_


endef

define oneshell
@eval "$$(printf '%s\n' '$(strip                            \
                         $(subst $(_oneshell_newline_),\n,  \
                         $(subst \,\/,                      \
                         $(subst /,//,                      \
                         $(subst ','"'"',$(1))))))' |       \
          sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef

Un exemple d'utilisation serait quelque chose comme ceci:

define TEST
printf '>\n%s\n' "Hello
World\n/$$$$/"
endef

all:
        $(call oneshell,$(TEST))

Cela montre la sortie (en supposant pid 27801):

>
Hello
World\n/27801/

Cette approche permet certaines fonctionnalités supplémentaires:

define oneshell
@eval "set -eux ; $$(printf '%s\n' '$(strip                            \
                                    $(subst $(_oneshell_newline_),\n,  \
                                    $(subst \,\/,                      \
                                    $(subst /,//,                      \
                                    $(subst ','"'"',$(1))))))' |       \
                     sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef

Ces options de shell vont:

  • Imprimer chaque commande au fur et à mesure de son exécution
  • Quitter à la première commande ayant échoué
  • Traitez l'utilisation de variables shell non définies comme une erreur

D'autres possibilités intéressantes se suggéreront probablement.

comte
la source
1

J'aime mieux la réponse d'alhadis. Mais pour conserver le formatage en colonnes, ajoutez une dernière chose.

SYNOPSIS := :: Synopsis: Makefile\
| ::\
| :: Usage:\
| ::    make .......... : generates this message\
| ::    make synopsis . : generates this message\
| ::    make clean .... : eliminate unwanted intermediates and targets\
| ::    make all ...... : compile entire system from ground-up\
endef

Les sorties:

:: Synopsis: Makefile 
:: 
:: Usage: 
:: make .......... : generates this message 
:: make synopsis . : generates this message 
:: make clean .... : eliminate unwanted intermediates and targets 
:: make all ...... : compile entire system from ground-up
jlettvin
la source
Le synopsis d'un programme doit être facile et évident à localiser. Je recommanderais d'ajouter ce niveau d'informations dans un fichier readme et / ou une page de manuel. Lorsqu'un utilisateur s'exécute make, il s'attend généralement à démarrer un processus de génération.
1
J'ai souvent voulu voir une liste de cibles de fabrication. Votre commentaire n'a aucun sens. Ce que les utilisateurs attendent n'est pas pertinent s'il leur faut 3 secondes pour savoir quoi faire, alors qu'au lieu de toute information comme celle-ci, cela peut parfois prendre des heures.
Xennex81
1
Utiliser les attentes comme raison de faire quelque chose est aussi un argument circulaire: parce que les gens l'attendent, nous devons le faire, et parce que nous le faisons, ils l'attendent.
Xennex81
1

Pas complètement lié au PO, mais j'espère que cela aidera quelqu'un à l'avenir. (car cette question est celle qui revient le plus dans les recherches Google).

Dans mon Makefile, je voulais passer le contenu d'un fichier, à une commande de construction de docker, après beaucoup de consternation, j'ai décidé de:

 base64 encode the contents in the Makefile (so that I could have a single line and pass them as a docker build arg...)
 base64 decode the contents in the Dockerfile (and write them to a file)

voir l'exemple ci-dessous.

nb: Dans mon cas particulier, je voulais passer une clé ssh, lors de la construction de l'image, en utilisant l'exemple de https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (en utilisant une build de docker en plusieurs étapes pour cloner un repo git, puis déposez la clé ssh de l'image finale dans la 2ème étape de la construction)

Makefile

...
MY_VAR_ENCODED=$(shell cat /path/to/my/file | base64)

my-build:
    @docker build \
      --build-arg MY_VAR_ENCODED="$(MY_VAR_ENCODED)" \
      --no-cache \
      -t my-docker:build .
...

Dockerfile

...
ARG MY_VAR_ENCODED

RUN mkdir /root/.ssh/  && \
    echo "${MY_VAR_ENCODED}" | base64 -d >  /path/to/my/file/in/container
... 
mlo55
la source
1

Avec GNU Make 3.82 et supérieur, l' .ONESHELLoption est votre ami quand il s'agit d'extraits de shell multilignes. En rassemblant les indices d'autres réponses, j'obtiens:

VERSION = 1.2.3
PACKAGE_NAME = foo-bar
DOWNLOAD_URL = $(PACKAGE_NAME).somewhere.net

define nwln

endef

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

.ONESHELL:

# mind the *leading* <tab> character
version:
    @printf "$(subst $(nwln),\n,$(ANNOUNCE_BODY))"

Assurez-vous, lorsque vous copiez et collez l'exemple ci-dessus dans votre éditeur, que tous les <tab>caractères sont conservés, sinon la versioncible se cassera!

Notez que .ONESHELLtoutes les cibles du Makefile utiliseront un seul shell pour toutes leurs commandes.

sphakka
la source
Malheureusement, cela ne fonctionne pas: make version printf "Version 1.2.3 of foo-bar has been released. /bin/sh: 1: Syntax error: Unterminated quoted string make: *** [version] Error 2(GNU make 3,81)
blueyed
@blueyed, je viens de le tester avec GNU Make 3.82 et GNU bash 4.2.45 (1) -release: cela fonctionne comme prévu. Vérifiez également la présence du caractère TAB principal, au lieu de blancs, devant l' @printf ...instruction - il semble que les tabulations soient toujours rendues sous forme de 4 espaces ...
sphakka
Il semble que ce .ONESHELLsoit nouveau dans la marque 3.82.
blueyed
btw: l'erreur lors de l'utilisation d'espaces au lieu d'une tabulation serait *** missing separator. Stop..
blueyed
0

Pas vraiment une réponse utile, mais juste pour indiquer que `` définir '' ne fonctionne pas comme la réponse d'Axe (ne rentre pas dans un commentaire):

VERSION=4.3.1
PACKAGE_NAME=foobar
DOWNLOAD_URL=www.foobar.com

define ANNOUNCE_BODY
    Version $(VERSION) of $(PACKAGE_NAME) has been released
    It can be downloaded from $(DOWNLOAD_URL)
    etc, etc
endef

all:
    @echo $(ANNOUNCE_BODY)

Cela donne une erreur indiquant que la commande «Il» ne peut pas être trouvée, donc il essaie d'interpréter la deuxième ligne de ANNOUNCE BODY comme une commande.

Roalt
la source
0

Cela a fonctionné pour moi:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)
fdb
la source
0

GNU Makefile peut faire des choses comme ce qui suit. C'est moche, et je ne dirai pas que vous devriez le faire, mais je le fais dans certaines situations.

PROFILE = \
\#!/bin/sh.exe\n\
\#\n\
\# A MinGW equivalent for .bash_profile on Linux.  In MinGW/MSYS, the file\n\
\# is actually named .profile, not .bash_profile.\n\
\#\n\
\# Get the aliases and functions\n\
\#\n\
if [ -f \$${HOME}/.bashrc ]\n\
then\n\
  . \$${HOME}/.bashrc\n\
fi\n\
\n\
export CVS_RSH="ssh"\n  
#
.profile:
        echo -e "$(PROFILE)" | sed -e 's/^[ ]//' >.profile

make .profile crée un fichier .profile s'il n'en existe pas.

Cette solution a été utilisée là où l'application utilisera uniquement GNU Makefile dans un environnement shell POSIX. Le projet n'est pas un projet open source où la compatibilité des plates-formes est un problème.

L'objectif était de créer un Makefile qui facilite à la fois la configuration et l'utilisation d'un type particulier d'espace de travail. Le Makefile apporte avec lui diverses ressources simples sans nécessiter des choses comme une autre archive spéciale, etc. C'est, en un sens, une archive shell. Une procédure peut alors dire des choses comme déposer ce Makefile dans le dossier pour travailler. Configurez votre espace de travail enter make workspace, puis pour faire bla, enter make blah, etc.

Ce qui peut être délicat, c'est de savoir quoi faire. Ce qui précède fait le travail et est proche de l'idée de spécifier un document ici dans le Makefile. Que ce soit une bonne idée pour une utilisation générale est une toute autre question.

kbulgrien
la source
0

Je pense que la réponse la plus sûre pour une utilisation multiplateforme serait d'utiliser un écho par ligne:

  ANNOUNCE.txt:
    rm -f $@
    echo "Version $(VERSION) of $(PACKAGE_NAME) has been released" > $@
    echo "" >> $@
    echo "It can be downloaded from $(DOWNLOAD_URL)" >> $@
    echo >> $@
    echo etc, etc" >> $@

Cela évite de faire des hypothèses sur la version de l'écho disponible.

Ben Martin
la source
0

Utilisez la substitution de chaîne :

VERSION := 1.1.1
PACKAGE_NAME := Foo Bar
DOWNLOAD_URL := https://go.get/some/thing.tar.gz

ANNOUNCE_BODY := Version $(VERSION) of $(PACKAGE_NAME) has been released. \
    | \
    | It can be downloaded from $(DOWNLOAD_URL) \
    | \
    | etc, etc

Puis dans votre recette, mettez

    @echo $(subst | ,$$'\n',$(ANNOUNCE_BODY))

Cela fonctionne car Make remplace toutes les occurrences de (notez l'espace) et le remplace par un caractère de nouvelle ligne ( $$'\n'). Vous pouvez penser aux invocations équivalentes de script shell comme étant quelque chose comme ceci:

Avant:

$ echo "Version 1.1.1 of Foo Bar has been released. | | It can be downloaded from https://go.get/some/thing.tar.gz | | etc, etc"

Après:

$ echo "Version 1.1.1 of Foo Bar has been released.
>
> It can be downloaded from https://go.get/some/thing.tar.gz
> 
> etc, etc"

Je ne sais pas si $'\n'est disponible sur les systèmes non POSIX, mais si vous pouvez accéder à un seul caractère de nouvelle ligne (même en lisant une chaîne à partir d'un fichier externe), le principe sous-jacent est le même.

Si vous avez beaucoup de messages comme celui-ci, vous pouvez réduire le bruit en utilisant une macro :

print = $(subst | ,$$'\n',$(1))

Où vous l'invoqueriez comme ceci:

@$(call print,$(ANNOUNCE_BODY))

J'espère que cela aide quelqu'un. =)


la source
J'aime mieux celui-ci. Mais pour conserver le formatage en colonnes, ajoutez une dernière chose. `SYNOPSIS: = :: Synopsis: Makefile \ | :: \ | :: Utilisation: \ | :: make ..........: génère ce message \ | :: faire un synopsis. : génère ce message \ | :: make clean ....: élimine les intermédiaires et les cibles indésirables \ | :: make all ......: compile tout le système à partir de la base \ endef
jlettvin
Les commentaires n'autorisent pas le code. Envoie comme réponse. J'aime mieux celui-ci. Mais pour conserver le formatage en colonnes, ajoutez une dernière chose. `SYNOPSIS: = :: Synopsis: Makefile`` | :: `` | :: Utilisation: `` | :: make ..........: génère ce message` `| :: faire un synopsis. : génère ce message` `| :: make clean ....: élimine les intermédiaires et les cibles indésirables` `| :: make all ......: compile tout le système à partir de la base` `endef`
jlettvin
@jlettvin Voir ma réponse à votre réponse. Le synopsis d'un programme ne doit certainement pas être intégré dans un Makefile, surtout pas comme tâche par défaut.
0

Vous pouvez également utiliser la commande printf. Ceci est utile sur OSX ou d'autres plates-formes avec moins de fonctionnalités.

Pour sortir simplement un message multiligne:

all:
        @printf '%s\n' \
            'Version $(VERSION) has been released' \
            '' \
            'You can download from URL $(URL)'

Si vous essayez de transmettre la chaîne comme argument à un autre programme, vous pouvez le faire comme ceci:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
zéroimpl
la source