web-dev-qa-db-fra.com

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

Je souhaite créer une variable makefile qui est une chaîne multiligne (par exemple, le corps d'une annonce de publication par courrier électronique). 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 faire cela. C'est possible?

96
jonner

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 la plus délicate est de récupérer votre variable multiligne du fichier makefile. Si vous utilisez simplement "echo $ (ANNOUNCE_BODY)", vous verrez le résultat que d'autres ont publié ici: le shell tente de gérer les lignes suivantes de la variable et les suivantes sous la forme de commandes.

Cependant, vous pouvez exporter la valeur de variable telle quelle vers le shell en tant que variable d'environnement, puis la référencer à partir du shell en tant que variable d'environnement (PAS une variable make). 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, au lieu de $(ANNOUNCE_BODY), qui serait une référence de variable régulière. Veillez également à utiliser des guillemets autour de votre référence de variable pour vous assurer que les nouvelles lignes ne sont pas interprétées par le shell lui-même.

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

146
Eric Melski

Une autre approche pour «récupérer votre variable multiligne du fichier makefile» (noté par Eric Melski comme étant «la partie la plus délicate») consiste à planifier l'utilisation de la fonction subst pour remplacer les nouvelles lignes introduites par define dans votre chaîne multiligne. avec \n. Puis utilisez -e avec echo pour les interpréter. Vous devrez peut-être définir le paramètre .Shell = bash pour obtenir un écho qui effectue cette opération. 

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

Cette sorte 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.

20
malcook

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

do-echo:
    $(info $(YOUR_MULTILINE_VAR))
10
superwhoopy

Oui. Vous échappez aux nouvelles lignes avec \:

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

mettre à jour

Ah, vous voulez les nouvelles lignes? Alors non, je ne pense pas qu'il y ait un moyen d'utiliser Vanilla Make. Cependant, vous pouvez toujours utiliser un here-document 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
3
Charlie Martin
3
ax.

Un simple 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
2
Jim Van Zandt

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
1
jlettvin

Avec GNU Make, l’option .ONESHELL est votre ami quand il s’agit d’extraits de shell multilignes. En rassemblant des indices d'autres réponses, je reçois:

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))"

Lors de la copie et du collage de l'exemple ci-dessus dans votre éditeur, assurez-vous que tous les caractères <tab> sont préservés, sinon la cible version se brisera!

1
sphakka

Dans l’esprit de .ONESHELL, il est possible de se rapprocher des environnements .ONESHELL:

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

Voici un exemple d'utilisation:

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

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

Cela montre la sortie (en supposant le 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 telle qu'elle est exécutée
  • Quitter sur la première commande ayant échoué
  • Traiter l'utilisation de variables Shell non définies comme une erreur

D'autres possibilités intéressantes seront probablement suggérées.

1
Earl

Vous devriez 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 transmettre la valeur de cette variable à la commande Shell. Mais si vous faites cela en utilisant Make substitution de substitution, la commande sera scindée en plusieurs:

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

Qouting ne va pas aider non plus.

La meilleure façon de transmettre une valeur consiste à la transmettre via une 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, afin que vous puissiez réutiliser cet environnement ne sera pas pollué beaucoup;
  2. Utiliser une variable d’environnement (double qoutes et accolades autour du nom de la variable);
  3. Utilisation de guillemets autour de variable. Sans eux, les nouvelles lignes seront perdues et tout le texte apparaîtra sur une seule ligne.
1
Maxim Kulkin

Ceci ne donne pas un document ici, mais affiche un message multiligne d’une manière qui convient aux pipes.

=====

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
---
$

=====

1
Paul Sander

Pourquoi ne vous servez-vous pas du caractère\n de 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"
1
Roalt

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

Pour simplement produire un message multiligne:

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

Si vous essayez de passer la chaîne en tant qu'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)'`"
0
zeroimpl

GNU Makefile peut faire les choses suivantes. 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 le Makefile GNU dans un environnement POSIX Shell. Le projet n'est pas un projet open source où la compatibilité de la plate-forme 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 diverses ressources simples, sans nécessiter des archives supplémentaires, etc. C'est en quelque sorte une archive Shell. Une procédure peut ensuite dire des choses comme déposer ce fichier Makefile dans le dossier de travail. Configurez votre espace de travail entrez make workspace, puis blah, entrez make blah, etc.

Ce qui peut être délicat, c’est de déterminer ce que Shell doit citer. Ce qui précède fait le travail et est proche de l’idée de spécifier un document here dans le Makefile. Que ce soit une bonne idée pour un usage général est un tout autre problème.

0
kbulgrien

Cela a fonctionné pour moi:

ANNOUNCE_BODY="first line\\nsecond line"

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

Je pense que la solution la plus sûre pour une utilisation multiplate-forme 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 formuler des hypothèses sur la version d'écho disponible.

0
Ben Martin

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

Dans mon Makefile, je voulais passer le contenu d'un fichier, à une commande de compilation 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 exemple ci-dessous.

nb: Dans mon cas particulier, je voulais transmettre 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 (utilisant un docker multi-étages pour cloner un dépôt Git, puis déposez la clé ssh de l'image finale au cours de la 2e é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
... 
0
mlo55

Pas vraiment une réponse utile, mais juste pour indiquer que 'définir' ne fonctionne pas comme l'a répondu Ax (cela 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 que la commande 'It' ne peut pas être trouvée, donc il essaie d'interpréter la deuxième ligne de ANNOUNCE BODY comme une commande.

0
Roalt

Utilisez 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 substitue toutes les occurrences de (notez l'espace) et les remplace par un caractère de nouvelle ligne ($$'\n'). Vous pouvez imaginer que les invocations Shell-script équivalentes ressemblent à 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 des 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 un macro :

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

Où vous l'invoqueriez comme ceci:

@$(call print,$(ANNOUNCE_BODY))

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

0
Alhadis