Makefile'da çok satırlı bir dize değişkeni oluşturmak mümkün mü


122

Çok satırlı bir dize olan bir makefile değişkeni oluşturmak istiyorum (örneğin, bir e-posta yayın duyurusunun gövdesi). gibi bir şey

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Ama bunu yapmanın bir yolunu bulamıyorum. Mümkün mü?

Yanıtlar:


172

Evet, çok satırlı bir değişken bildirmek için define anahtar sözcüğünü şu şekilde kullanabilirsiniz:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

İşin zor kısmı, çok satırlı değişkeninizi makefile'dan geri almaktır. Eğer "echo $ (ANNOUNCE_BODY)" kullanarak apaçık olan şeyi yaparsanız, diğerlerinin buraya gönderdiği sonucu görürsünüz - kabuk değişkenin ikinci ve sonraki satırlarını komut olarak kendileri işlemeye çalışır.

Bununla birlikte, değişken değerini olduğu gibi kabuğa bir ortam değişkeni olarak dışa aktarabilir ve ardından onu bir ortam değişkeni olarak kabuktan referans alabilirsiniz (bir make değişkeni DEĞİL). Örneğin:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Normal bir make değişken referansı $$ANNOUNCE_BODYyerine, bir kabuk ortam değişkeni referansını belirten kullanımına dikkat edin $(ANNOUNCE_BODY). Ayrıca, yeni satırların kabuğun kendisi tarafından yorumlanmadığından emin olmak için değişken referansınızın etrafında tırnak işaretleri kullandığınızdan emin olun.

Tabii ki, bu özel numara platforma ve kabuğa duyarlı olabilir. Ubuntu Linux üzerinde GNU bash 3.2.13 ile test ettim; YMMV.


1
export ANNOUNCE_BODYDeğişkeni yalnızca kuralların içinde ayarlar - diğer değişkenleri tanımlamak için $$ ANNOUNCE_BODY'ye başvurmaya izin vermez.
anatoly techtonik

@techtonik eğer değerini ANNOUNCE_BODYdiğer değişken tanımlarında kullanmak istiyorsanız , sadece diğer make değişkenleri gibi ona referans verin. Örneğin OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY),. Tabii ki , bir komuta exportgirmek istiyorsanız, yine de numaraya ihtiyacınız olacak OTHER.
Eric Melski

25

'Çok satırlı değişkeninizi makefile'dan çıkarmak' için başka bir yaklaşım (Eric Melski tarafından 'zor kısım' olarak belirtilmiştir), substişlevi defineçok satırlı dizenizde bulunan satırsonlarını değiştirmek için kullanmayı planlamaktır \n. Sonra echobunları yorumlamak için -e ile birlikte kullanın. Bunu yapan bir yankı elde etmek için .SHELL = bash ayarlamanız gerekebilir.

Bu yaklaşımın bir avantajı, bu tür diğer kaçış karakterlerini de metninize koymanız ve bunlara saygı duymanızdır.

Bu tür şu ana kadar bahsedilen tüm yaklaşımları sentezler ...

Şunlarla doluyorsunuz:

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

Son ekodaki tek alıntıların çok önemli olduğuna dikkat edin.


4
"Echo -e" nin taşınabilir olmadığını unutmayın. Bunun yerine muhtemelen printf (1) 'i tercih etmelisiniz.
MadScientist

2
Büyük cevap, ancak, kaldırmak zorunda =SONRA define ANNOUNCE_BODYçalışan almak için.
mschilli

13

Değişkeninizin içeriğini yalnızca standart çıktıya yazdırmak istediğinizi varsayarsak, başka bir çözüm vardır:

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

1
Bu no-op kural istenmeyen mesaj üretti: make: 'do-echo' is up to date.. "İşlem yok" komutunu kullanarak onu susturabildim:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin

@Gu GuillaumePapin Biraz geç, ancak .PHONYMakefile'ınıza bu kuralı kontrol edecek bir şey olmadığını söylemek için kullanabilirsiniz . Makefile'lar başlangıçta derleyiciler içindi, yanılmıyorsam, makekuralın hiçbir şeyi değiştirmeyeceğini tahmin etmek için anlamadığım bir sihir yapmak da öyle ve bu nedenle 'tamamlandı' varsayılıyor. .PHONY do-echoDosyanıza eklemek , makebunu göz ardı etmenizi ve yine de kodu çalıştırmanızı söyleyecektir .
M3D

$(info ...)Yapma kuralının dışına yerleştirebilirsiniz . Yine de çıktı üretecek.
Daniel Stevens


3

Evet. Yeni satırlardan şu şekilde kaçarsınız \:

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

Güncelleme

Ah, yeni satırları mı istiyorsun? O halde hayır, vanilya yapımında herhangi bir yol olduğunu sanmıyorum. Ancak, komut bölümünde her zaman bir buradaki belgeyi kullanabilirsiniz.

[Bu işe yaramıyor, MadScientist'in yorumuna bakın]

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

Bu doğru, ancak bana herhangi bir biçimlendirme (satırsonu) vermiyor. Sadece tek bir metin satırı haline geliyor
Jonner

Çok satırlı buradaki belgeler, GNU Make'de açıklandığı gibi çalışmaz.
Matt B.

3
Çok satırlı buradaki tarifler içindeki dokümanlar, POSIX standardını destekleyen HERHANGİ bir standart marka sürümünde çalışmaz: üretim standardı, tarifin her ayrı satırının ayrı bir kabukta çalıştırılmasını gerektirir. Make, bunun burada-belge olup olmadığını söylemek için komut üzerinde herhangi bir çözümleme yapmaz ve onu farklı şekilde ele alır. Bunu destekleyen bir marka varyantı biliyorsanız (hiç duymadım) muhtemelen bunu açıkça belirtmelisiniz.
MadScientist

2

Eric Melski'nin cevabına bir ek: Metne komutların çıktısını dahil edebilirsiniz, ancak "$ (foo)" kabuk sözdizimi yerine Makefile sözdizimi "$ (shell foo)" kullanmalısınız. Örneğin:

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

"Define / endef" Make yapısını kullanmalısınız:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Daha sonra bu değişkenin değerini kabuk komutuna iletmelisiniz. Ancak, bunu Make değişken ikamesi kullanarak yaparsanız, komutun birden çok parçaya bölünmesine neden olur:

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

Sorgulamak da yardımcı olmayacak.

Değeri iletmenin en iyi yolu, onu ortam değişkeni aracılığıyla iletmektir:

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

Farkına varmak:

  1. Değişken, bu belirli hedef için dışa aktarılır, böylece bu ortamı yeniden kullanabilirsiniz, çok fazla kirlenmez;
  2. Ortam değişkeni kullanın (değişken adının etrafında çift satır ve küme parantezleri);
  3. Değişken etrafında alıntıların kullanılması. Bunlar olmadan yeni satırlar kaybolacak ve tüm metin tek satırda görünecektir.

2

Bu, burada bir belge vermez, ancak borulara uygun bir şekilde çok satırlı bir mesaj görüntüler.

=====

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

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

=====

Ayrıca Gnu'nun çağrılabilir makrolarını da kullanabilirsiniz:

=====

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

=====

İşte çıktı:

=====

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

=====


1

Satır sonunu tanımlamak için neden dizenizde \ n karakterini kullanmıyorsunuz? Ayrıca, birden çok satıra eklemek için fazladan ters eğik çizgiyi ekleyin.

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

Erik Melski'nin cevabını tercih ederim, ancak bu, başvurunuza bağlı olarak sizin için zaten işe yarayabilir.
Roalt

Bununla ilgili bir sorum var. Bu, her satırın başında fazladan bir "boşluk" görmem dışında (ilki hariç) temelde iyi çalışıyor. Bu sana oluyor mu? İstediğim çıktıyı çok etkili bir şekilde oluşturmak için \ n ile ayırarak tüm metni tek bir satıra koyabilirim. Sorun şu ki, Makefile'ın kendisinde çok çirkin görünüyor!
Shahbaz

Bir çözüm buldum. $(subst \n ,\n,$(TEXT))Keşke daha iyi bir yol olsaydı da metni koydum !
Shahbaz


1

.ONESHELL ruhu içinde, .ONESHELL'in zorlu ortamlarında oldukça yakınlaşmak mümkündür:

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

Kullanım örneği şunun gibi bir şey olabilir:

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

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

Bu çıktıyı gösterir (pid 27801 varsayılarak):

>
Hello
World\n/27801/

Bu yaklaşım, bazı ekstra işlevlere izin verir:

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

Bu kabuk seçenekleri:

  • Her komutu yürütülürken yazdırın
  • İlk başarısız komuttan çıkın
  • Tanımlanmamış kabuk değişkenlerinin kullanımını bir hata olarak ele alın

Diğer ilginç olasılıklar muhtemelen kendilerini gösterecektir.


1

Alhadis'in cevabını en çok seviyorum. Ancak sütunlu biçimlendirmeyi korumak için bir şey daha ekleyin.

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

Çıktılar:

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

Bir programın özetini bulmak kolay ve açık olmalıdır. Bu bilgi düzeyini bir benioku ve / veya kılavuz sayfasına eklemenizi tavsiye ederim. Bir kullanıcı çalıştığında make, genellikle bunu bir derleme süreci başlatmayı beklerler.

1
Birçok kez sadece hedef koyma listesini görmek istedim. Yorumunuz mantıklı değil. Kullanıcıların ne beklediklerini bilmeleri 3 saniye sürüyorsa alakasızdır, oysa bunun gibi herhangi bir bilgi yerine bazen saatler sürebilir.
Xennex81

1
Beklentileri bir şeyi yapmak için bir neden olarak kullanmak da döngüsel bir argümandır: İnsanlar beklediği için yapmalıyız ve biz yaptığımız için de bekliyorlar.
Xennex81

1

Tamamen OP ile ilgili değil, ancak umarım bu gelecekte birine yardımcı olur. (çünkü bu soru google aramalarında en çok karşımıza çıkan sorudur).

Makefile dosyamda, bir dosyanın içeriğini bir docker build komutuna geçirmek istedim, çok fazla şaşkınlıktan sonra, karar verdim:

 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)

aşağıdaki örneğe bakın.

nb: Benim özel durumumda, görüntü oluşturma sırasında https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (kullanarak bir git deposunu klonlamak için çok aşamalı bir docker derlemesi, ardından yapının 2. aşamasındaki son görüntüden ssh anahtarını bırakın)

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

GNU Make 3.82 ve üstü ile, konu .ONESHELLçok satırlı kabuk parçacıkları olduğunda arkadaşınızdır. Diğer cevaplardan ipuçlarını bir araya getirerek şunu anlıyorum:

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

Yukarıdaki örneği düzenleyicinize kopyalayıp yapıştırırken, tüm <tab>karakterlerin korunduğundan emin olun, aksi takdirde versionhedef kırılır!

.ONESHELLBunun Makefile'daki tüm hedeflerin tüm komutları için tek bir kabuk kullanmasına neden olacağını unutmayın .


Maalesef bu işe yaramıyor: 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 3,81 yaptı)
blueyed

@blueyed, GNU Make 3.82 ve GNU bash 4.2.45 (1) -release ile test ettim: Beklendiği gibi çalışıyor. Ayrıca, lütfen @printf ...ifadenin önünde boşluklar yerine
baştaki

.ONESHELLYapım 3.82'de yeni olduğu anlaşılıyor .
mavi gözlü

btw: sekme yerine boşluk kullanıldığında oluşan hata *** missing separator. Stop..
mavi gözlü

0

Gerçekten yararlı bir cevap değil, ama sadece 'tanımla'nın Ax tarafından cevaplandığı gibi çalışmadığını belirtmek için (bir yoruma uymadı):

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)

'O' komutunun bulunamaması hatası veriyor, bu nedenle ANNOUNCE BODY'nin ikinci satırını bir komut olarak yorumlamaya çalışıyor.


0

Benim için çalıştı:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile aşağıdaki gibi şeyler yapabilir. Çirkin ve yapman gerektiğini söylemeyeceğim, ama bazı durumlarda yaparım.

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 yoksa bir .profile dosyası oluşturur.

Bu çözüm, uygulamanın bir POSIX kabuk ortamında yalnızca GNU Makefile'ı kullanacağı durumlarda kullanıldı. Proje, platform uyumluluğunun sorun olduğu açık kaynaklı bir proje değildir.

Amaç, belirli bir çalışma alanının hem kurulumunu hem de kullanımını kolaylaştıran bir Makefile oluşturmaktı. Makefile, başka bir özel arşiv vb. Gibi şeyler gerektirmeden çeşitli basit kaynakları da beraberinde getiriyor. Bu bir anlamda bir kabuk arşividir. Bir prosedür daha sonra bu Makefile'ı çalışmak için klasöre bırakmak gibi şeyler söyleyebilir. Çalışma alanınızı girin enter make workspace, ardından blah, enter make blahvb. Yapmak için ayarlayın .

Zorlaşan şey, neyin alınacağını bulmaktır. Yukarıdakiler işi yapar ve Makefile'da bir burada belge belirtme fikrine yakındır. Genel kullanım için iyi bir fikir olup olmadığı tamamen başka bir konudur.


0

Çapraz platform kullanımı için en güvenli cevabın her satırda bir yankı kullanmak olduğuna inanıyorum:

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

Bu, mevcut yankı sürümüyle ilgili herhangi bir varsayımda bulunmaktan kaçınır.


0

Dize ikamesi kullanın :

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

Sonra tarifinize koyun

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

Bu işe yarar çünkü Make tüm tekrarlarını değiştirir (boşluğa dikkat edin) ve onu bir satırsonu karakteri ( $$'\n') ile değiştirir. Eşdeğer kabuk betiği çağrılarını şunun gibi düşünebilirsiniz:

Önce:

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

Sonra:

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

$'\n'POSIX olmayan sistemlerde mevcut olup olmadığından emin değilim , ancak tek bir satırsonu karakterine erişebilirseniz (harici bir dosyadan bir dizeyi okuyarak bile), temel ilke aynıdır.

Bunun gibi birçok mesajınız varsa, bir makro kullanarak gürültüyü azaltabilirsiniz :

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

Bunu böyle çağırırdın:

@$(call print,$(ANNOUNCE_BODY))

Umarım bu birine yardımcı olur. =)


Bunu en çok beğendim. Ancak sütunlu biçimlendirmeyi korumak için bir şey daha ekleyin. `SYNOPSIS: = :: Özet: Makefile \ | :: \ | :: Kullanım: \ | :: make ..........: bu mesajı oluşturur \ | :: özet oluştur. : bu mesajı oluşturur \ | :: make clean ....: istenmeyen ara ürünleri ve hedefleri ortadan kaldırın \ | :: make all ......: tüm sistemi sıfırdan derleyin \ endef
jlettvin

Yorumlar koda izin vermez. Cevap olarak gönderecek. Bunu en çok beğendim. Ancak sütunlu biçimlendirmeyi korumak için bir şey daha ekleyin. `SYNOPSIS: = :: Özet: Makefile` '| :: `` | :: Kullanım: `` | :: make ..........: bu mesajı oluşturur` '| :: özet oluştur. : bu mesajı oluşturur `` | :: make clean ....: istenmeyen ara ürünleri ve hedefleri ortadan kaldırın '' | :: make all ......: tüm sistemi sıfırdan derleyin `endef`
jlettvin

@jlettvin Cevabınıza cevabımı görün. Bir programın özet kesinlikle gerekir değil özellikle de varsayılan bir görev olarak, bir Makefile içine yerleştirilemez.

0

Alternatif olarak printf komutunu kullanabilirsiniz. Bu, OSX veya daha az özelliğe sahip diğer platformlarda yararlıdır.

Çok satırlı bir mesajın çıktısını almak için:

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

Dizeyi başka bir programa argüman olarak iletmeye çalışıyorsanız, bunu şu şekilde yapabilirsiniz:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.