Czy jest możliwe utworzenie wieloliniowej zmiennej łańcuchowej w pliku Makefile?


122

Chcę utworzyć zmienną makefile, która jest ciągiem wieloliniowym (np. Treść powiadomienia e-mail o wydaniu). coś jak

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Ale nie mogę znaleźć sposobu, żeby to zrobić. Czy to możliwe?

Odpowiedzi:


172

Tak, możesz użyć słowa kluczowego define, aby zadeklarować zmienną wielowierszową, na przykład:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Najtrudniejsze jest odzyskanie zmiennej wieloliniowej z pliku makefile. Jeśli po prostu zrobisz oczywistą rzecz, używając "echo $ (ANNOUNCE_BODY)", zobaczysz wynik, który inni opublikowali tutaj - powłoka próbuje obsłużyć drugą i kolejne linie zmiennej jako same polecenia.

Można jednak wyeksportować wartość zmiennej w niezmienionej postaci do powłoki jako zmienną środowiskową, a następnie odwołać się do niej z powłoki jako zmienną środowiskową (NIE zmienną make). Na przykład:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Zwróć uwagę na użycie $$ANNOUNCE_BODY, wskazując odwołanie do zmiennej środowiskowej powłoki, a nie $(ANNOUNCE_BODY), które byłoby zwykłym odwołaniem do zmiennej make. Upewnij się również, że używasz cudzysłowów wokół odniesienia do zmiennej, aby mieć pewność, że znaki nowej linii nie są interpretowane przez samą powłokę.

Oczywiście ta szczególna sztuczka może być wrażliwa na platformę i powłokę. Przetestowałem go na Ubuntu Linux z GNU bash 3.2.13; YMMV.


1
export ANNOUNCE_BODYustawia zmienną tylko w regułach - nie pozwala na odwoływanie się do $$ ANNOUNCE_BODY w celu zdefiniowania innych zmiennych.
anatoly techtonik

@techtonik, jeśli chcesz użyć wartości ANNOUNCE_BODYw innych definicjach zmiennych, po prostu odwołaj się do niej jak do każdej innej zmiennej make. Na przykład OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY). Oczywiście nadal będziesz potrzebować exportsztuczki, jeśli chcesz wyjść OTHERna komendę.
Eric Melski

25

Innym podejściem do „wyciągnięcia zmiennej wieloliniowej z powrotem z pliku makefile” (co Eric Melski zauważył jako „trudną część”) jest zaplanowanie użycia substfunkcji do zastąpienia nowych linii wprowadzonych definew łańcuchu wielowierszowym przez \n. Następnie użyj -e z, echoaby je zinterpretować. Może być konieczne ustawienie .SHELL = bash, aby uzyskać echo, które to robi.

Zaletą tego podejścia jest to, że umieszczasz w tekście również inne znaki ucieczki i szanujesz je.

Ten rodzaj syntetyzuje wszystkie wspomniane dotychczas podejścia ...

Skończysz z:

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})'

Zwróć uwagę, że kluczowe znaczenie mają pojedyncze cudzysłowy na ostatnim echu.


4
Zauważ, że "echo -e" nie jest przenośne. Prawdopodobnie powinieneś zamiast tego preferować printf (1).
MadScientist,

2
świetna odpowiedź, jednak musiałem usunąć =po, define ANNOUNCE_BODYaby go uruchomić.
mschilli

13

Zakładając, że chcesz wydrukować zawartość zmiennej tylko na standardowym wyjściu, istnieje inne rozwiązanie:

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

1
Ta zasada nie-op produkowane niechcianą wiadomość: make: 'do-echo' is up to date.. Używając komendy „no op”, udało mi się ją uciszyć:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin

@GuillaumePapin Trochę późno, ale możesz użyć .PHONYdo poinformowania swojego Makefile, że nie ma nic do sprawdzenia tej reguły. Pliki Makefile były pierwotnie dla kompilatorów, jeśli się nie mylę, więc makerobię trochę magii, której nie rozumiem, aby przewidzieć, że reguła niczego nie zmieni i jako taka zakłada, że ​​zostanie to „zrobione”. Dodanie .PHONY do-echodo pliku powie, makeaby zignorować to i mimo to uruchomić kod.
M3D

Możesz umieścić $(info ...)poza regułą tworzenia. Nadal będzie generować dane wyjściowe.
Daniel Stevens,


3

Tak. Uciekasz przed nowymi liniami za pomocą \:

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

aktualizacja

Ach, chcesz nowe linie? Więc nie, nie sądzę, że w waniliowej marce jest jakiś sposób. Jednak zawsze możesz użyć dokumentu tutaj w części poleceń

[To nie działa, zobacz komentarz MadScientist]

foo:
    echo <<EOF
    Here is a multiple line text
    with embedded newlines.
    EOF

To prawda, ale nie daje mi żadnego formatowania (znaki nowej linii). Po prostu staje się pojedynczą linijką tekstu
jonner

Wielowierszowe dokumenty tutaj nie działają tak, jak opisano w GNU Make.
Matt B.

3
Multiline tutaj dokumentuje wewnątrz receptur nie będzie działać w ŻADNEJ standardowej wersji make, która obsługuje standard POSIX: standard make wymaga, aby każda oddzielna linia receptury była uruchamiana w osobnej powłoce. Make nie analizuje polecenia, aby stwierdzić, czy jest to dokument „tutaj”, czy nie, i obsługuje go inaczej. Jeśli znasz jakiś wariant marki, który to obsługuje (nigdy o żadnym nie słyszałem), prawdopodobnie powinieneś to wyraźnie określić.
MadScientist,

2

Tylko dopisek do odpowiedzi Erica Melskiego: Możesz dołączyć wynik poleceń do tekstu, ale musisz użyć składni pliku Makefile „$ (shell foo)” zamiast składni powłoki „$ (foo)”. Na przykład:

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

To nie daje dokumentu tutaj, ale wyświetla wielowierszowy komunikat w sposób odpowiedni dla potoków.

=====

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

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

=====

Możesz także użyć wywoływanych makr 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 "---"

=====

Oto wynik:

=====

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

=====


1

Dlaczego nie użyjesz znaku \ n w swoim ciągu do zdefiniowania końca linii? Dodaj także dodatkowy ukośnik odwrotny, aby dodać go w wielu wierszach.

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

Wolę odpowiedź Erika Melskiego, ale może to już załatwić sprawę, w zależności od aplikacji.
Roalt

Mam pytanie w tej sprawie. Zasadniczo działa to dobrze, z wyjątkiem tego, że widzę dodatkową „spację” na początku każdego wiersza (z wyjątkiem pierwszego). Czy ci się to przytrafiło? Mogę umieścić cały tekst w jednym wierszu, oddzielonym \ n \ n tak skutecznie tworząc wynik, który mi się podoba. Problem w tym, że w samym Makefile wygląda bardzo brzydko!
Shahbaz,

Znalazłem obejście. Przekopiowałem tekst, $(subst \n ,\n,$(TEXT))chociaż chciałbym, żeby był lepszy sposób!
Shahbaz,


1

Powinieneś użyć konstrukcji „define / endef”:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Następnie należy przekazać wartość tej zmiennej do polecenia powłoki. Ale jeśli zrobisz to za pomocą podstawiania zmiennej Make, spowoduje to podzielenie polecenia na wiele:

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

Qouting też nie pomoże.

Najlepszym sposobem przekazania wartości jest przekazanie jej przez zmienną środowiskową:

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

Ogłoszenie:

  1. Zmienna jest eksportowana dla tego konkretnego celu, dzięki czemu można ponownie wykorzystać to środowisko, które nie zostanie zbytnio zanieczyszczone;
  2. Użyj zmiennej środowiskowej (podwójne znaki zapytania i nawiasy klamrowe wokół nazwy zmiennej);
  3. Stosowanie cudzysłowów wokół zmiennej. Bez nich nowe linie zostaną utracone, a cały tekst pojawi się w jednej linii.

1

W duchu .ONESHELL możliwe jest zbliżenie się do trudnych środowisk .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

Przykład użycia mógłby wyglądać następująco:

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

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

To pokazuje wynik (zakładając pid 27801):

>
Hello
World\n/27801/

Takie podejście pozwala na dodatkowe funkcje:

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

Te opcje powłoki:

  • Wydrukuj każde polecenie w trakcie jego wykonywania
  • Zakończ przy pierwszym nieudanym poleceniu
  • Traktuj użycie niezdefiniowanych zmiennych powłoki jako błąd

Prawdopodobnie nasuną się inne interesujące możliwości.


1

Najbardziej podoba mi się odpowiedź Alhadisa. Ale aby zachować formatowanie kolumnowe, dodaj jeszcze jedną rzecz.

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

Wyjścia:

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

Streszczenie programu powinno być łatwe i oczywiste do zlokalizowania. Zalecałbym dodanie tego poziomu informacji do pliku readme i / lub strony podręcznika. Gdy użytkownik uruchamia się make, zazwyczaj robi to, spodziewając się rozpoczęcia procesu kompilacji.

1
Wiele razy chciałem po prostu zobaczyć listę celów. Twój komentarz nie ma sensu. To, czego oczekują użytkownicy, nie ma znaczenia, jeśli wiedza, co mają zrobić, zajmuje im 3 sekundy, podczas gdy zamiast takich informacji może to czasem zająć godziny.
Xennex81

1
Używanie oczekiwań jako powodu do zrobienia czegoś jest również argumentem cyrkularnym: ponieważ ludzie tego oczekują, musimy to zrobić, a ponieważ to robimy, oczekują tego.
Xennex81

1

Niezupełnie związane z PO, ale mam nadzieję, że pomoże to komuś w przyszłości. (ponieważ to pytanie pojawia się najczęściej w wyszukiwaniach Google).

W moim Makefile chciałem przekazać zawartość pliku do polecenia docker build, po wielu konsternacjach postanowiłem:

 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)

patrz przykład poniżej.

Uwaga: W moim przypadku chciałem przekazać klucz ssh podczas budowania obrazu, korzystając z przykładu z https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (używając wieloetapowa kompilacja dockera w celu sklonowania repozytorium git, a następnie upuść klucz ssh z ostatecznego obrazu w drugim etapie kompilacji)

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

1

W przypadku GNU Make 3.82 i nowszych, .ONESHELLopcja ta jest twoim przyjacielem, jeśli chodzi o wielowierszowe fragmenty powłoki. Łącząc wskazówki z innych odpowiedzi, otrzymuję:

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

Podczas kopiowania i wklejania powyższego przykładu do edytora upewnij się, że wszystkie <tab>znaki są zachowane, w przeciwnym razieversion cel się zepsuje!

Zwróć na to uwagę .ONESHELL spowoduje to, że wszystkie cele w Makefile będą używać jednej powłoki dla wszystkich swoich poleceń.


Niestety to nie działa: 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)
niebieskawy

@blueyed, właśnie przetestowałem go z GNU Make 3.82 i GNU bash 4.2.45 (1) -release: działa zgodnie z oczekiwaniami. Sprawdź również obecność wiodącego znaku TAB, zamiast @printf ...spacji , przed instrukcją - wygląda na to, że TAB są zawsze renderowane jako 4 spacje ...
sphakka

Wygląda na .ONESHELLto, że to nowość w make 3.82.
niebieskawy

btw: błąd podczas używania spacji zamiast tabulatora to *** missing separator. Stop..
niebieskawy

0

Niezbyt pomocna odpowiedź, ale wystarczy wskazać, że `` zdefiniuj '' nie działa tak, jak odpowiedział Ax (nie mieści się w komentarzu):

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)

Daje błąd, że nie można znaleźć polecenia „To”, więc próbuje zinterpretować drugi wiersz ANNOUNCE BODY jako polecenie.


0

U mnie zadziałało:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile może wykonywać następujące czynności. Jest brzydki i nie powiem, że powinieneś to robić, ale robię to w pewnych sytuacjach.

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 tworzy plik .profile, jeśli taki nie istnieje.

To rozwiązanie zostało użyte, gdy aplikacja będzie używać GNU Makefile tylko w środowisku powłoki POSIX. Projekt nie jest projektem typu open source, w którym problemem jest kompatybilność platformy.

Celem było stworzenie pliku Makefile, który ułatwi zarówno konfigurację, jak i korzystanie z określonego rodzaju obszaru roboczego. Plik Makefile zawiera różne proste zasoby, nie wymagając takich rzeczy jak inne specjalne archiwum itp. Jest to w pewnym sensie archiwum powłoki. Procedura może wtedy powiedzieć takie rzeczy, jak upuszczenie tego pliku Makefile do folderu do pracy. Skonfiguruj obszar roboczy enter make workspace, a następnie wykonaj blah, entermake blah itp.

To, co może być trudne, to ustalenie, co zacytować. Powyższe spełnia swoje zadanie i jest bliskie idei określenia dokumentu w pliku Makefile. To, czy jest to dobry pomysł do ogólnego użytku, to zupełnie inna kwestia.


0

Uważam, że najbezpieczniejszą odpowiedzią w przypadku użycia na wielu platformach byłoby użycie jednego echa w wierszu:

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

Pozwala to uniknąć dokonywania jakichkolwiek założeń dotyczących dostępnej wersji echa.


0

Użyj zastępowania ciągów :

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

Następnie w swoim przepisie umieść

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

Działa to, ponieważ Make zastępuje wszystkie wystąpienia (zwróć uwagę na spację) i zamienia je na znak nowej linii ( $$'\n'). Możesz myśleć o równoważnych wywołaniach skryptu powłoki jako o czymś takim:

Przed:

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

Po:

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

Nie jestem pewien, czy $'\n'jest dostępny w systemach innych niż POSIX, ale jeśli możesz uzyskać dostęp do pojedynczego znaku nowej linii (nawet czytając ciąg z zewnętrznego pliku), podstawowa zasada jest taka sama.

Jeśli masz wiele takich wiadomości, możesz zredukować szum za pomocą makra :

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

Gdzie można to wywołać w ten sposób:

@$(call print,$(ANNOUNCE_BODY))

Mam nadzieję, że to komuś pomoże. =)


To mi się najbardziej podoba. Ale aby zachować formatowanie kolumnowe, dodaj jeszcze jedną rzecz. `SKŁADNIA: = :: Streszczenie: Makefile \ | :: \ | :: Sposób użycia: \ | :: make ..........: generuje tę wiadomość \ | :: zrobić streszczenie. : generuje tę wiadomość \ | :: make clean ....: eliminacja niechcianych produktów pośrednich i celów \ | :: make all ......: skompiluj cały system od podstaw \ endef
jlettvin

Komentarze nie zezwalają na kod. Wyślę jako odpowiedź. To mi się najbardziej podoba. Ale aby zachować formatowanie kolumnowe, dodaj jeszcze jedną rzecz. `SKŁADNIA: = :: Streszczenie: Makefile`` | :: `` | :: Użycie: `` | :: make ..........: generuje ten komunikat` `| :: zrobić streszczenie. : generuje tę wiadomość` `` | :: make clean ....: eliminacja niechcianych półproduktów i celów` `| :: make all ......: kompiluj cały system od podstaw `endef`
jlettvin

@jlettvin Zobacz moją odpowiedź na twoją odpowiedź. Streszczenie programu zdecydowanie nie powinno być osadzane w pliku Makefile, zwłaszcza jako zadanie domyślne.

0

Alternatywnie możesz użyć polecenia printf. Jest to przydatne na OSX lub innych platformach z mniejszą liczbą funkcji.

Aby po prostu wyświetlić wiadomość wielowierszową:

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

Jeśli próbujesz przekazać łańcuch jako argument do innego programu, możesz to zrobić w następujący sposób:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.