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?
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.
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.
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))
Oui. Vous échappez aux nouvelles lignes avec \
:
VARIABLE="\
THIS IS A VERY LONG\
TEXT STRING IN A MAKE VARIABLE"
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
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
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
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!
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:
D'autres possibilités intéressantes seront probablement suggérées.
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:
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
---
$
=====
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"
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)'`"
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.
Cela a fonctionné pour moi:
ANNOUNCE_BODY="first line\\nsecond line"
all:
@echo -e $(ANNOUNCE_BODY)
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.
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)
...
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 .
...
...
ARG MY_VAR_ENCODED
RUN mkdir /root/.ssh/ && \
echo "${MY_VAR_ENCODED}" | base64 -d > /path/to/my/file/in/container
...
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.
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. =)