Apakah mungkin untuk membuat variabel string multi-baris dalam Makefile


122

Saya ingin membuat variabel makefile yang merupakan string multi-baris (misalnya badan pengumuman rilis email). sesuatu seperti

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Tapi sepertinya saya tidak bisa menemukan cara untuk melakukan ini. Apa itu mungkin?

Jawaban:


172

Ya, Anda dapat menggunakan kata kunci define untuk mendeklarasikan variabel multi-baris, seperti ini:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Bagian yang sulit adalah mengembalikan variabel multi-garis Anda dari makefile. Jika Anda hanya melakukan hal yang jelas dengan menggunakan "echo $ (ANNOUNCE_BODY)", Anda akan melihat hasil yang diposting orang lain di sini - shell mencoba menangani baris kedua dan berikutnya dari variabel sebagai perintah itu sendiri.

Namun, Anda dapat mengekspor nilai variabel apa adanya ke shell sebagai variabel lingkungan, lalu mereferensikannya dari shell sebagai variabel lingkungan (BUKAN variabel make). Sebagai contoh:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Perhatikan penggunaan $$ANNOUNCE_BODY, yang menunjukkan referensi variabel lingkungan shell, bukan $(ANNOUNCE_BODY), yang akan menjadi referensi variabel make reguler. Pastikan juga untuk menggunakan tanda kutip di sekitar referensi variabel Anda, untuk memastikan bahwa baris baru tidak diinterpretasikan oleh shell itu sendiri.

Tentu saja, trik khusus ini mungkin peka terhadap platform dan shell. Saya mengujinya di Ubuntu Linux dengan GNU bash 3.2.13; YMMV.


1
export ANNOUNCE_BODYhanya menetapkan variabel di dalam aturan - ini tidak mengizinkan referensi $$ ANNOUNCE_BODY untuk menentukan variabel lain.
anatoly techtonik

@techtonik jika Anda ingin menggunakan nilai ANNOUNCE_BODYdalam definisi variabel lain, cukup referensikan seperti variabel make lainnya. Misalnya OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY),. Tentu saja Anda masih memerlukan exporttrik jika Anda ingin OTHERkeluar sesuai perintah.
Eric Melski

25

Pendekatan lain untuk 'mendapatkan variabel multi-baris Anda kembali dari makefile' (dicatat oleh Eric Melski sebagai 'bagian yang sulit'), adalah merencanakan untuk menggunakan substfungsi untuk mengganti baris baru yang diperkenalkan dengan definedalam string multi-baris Anda dengan \n. Kemudian gunakan -e dengan echountuk menafsirkannya. Anda mungkin perlu menyetel .SHELL = bash untuk mendapatkan gema yang melakukan ini.

Keuntungan dari pendekatan ini adalah Anda juga memasukkan karakter pelarian lain ke dalam teks Anda dan membuatnya dihormati.

Semacam ini mensintesis semua pendekatan yang disebutkan sejauh ini ...

Anda berakhir dengan:

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

Perhatikan bahwa tanda kutip tunggal pada gema terakhir sangat penting.


4
Perhatikan bahwa "echo -e" tidak portabel. Anda mungkin lebih memilih printf (1) sebagai gantinya.
MadScientist

2
jawaban yang bagus, bagaimanapun, saya harus menghapus =setelahnya define ANNOUNCE_BODYuntuk menjalankannya.
mschilli

13

Dengan asumsi Anda hanya ingin mencetak konten variabel Anda pada keluaran standar, ada solusi lain:

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

1
Aturan no-op ini menghasilkan pesan yang tidak diinginkan: make: 'do-echo' is up to date.. Dengan menggunakan perintah "no op" dan saya bisa membungkamnya:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin

@GuillaumePapin Sedikit terlambat, tetapi Anda dapat menggunakan .PHONYuntuk memberi tahu Makefile Anda bahwa tidak ada yang perlu diperiksa untuk aturan itu. Makefiles pada awalnya untuk kompiler, jika saya tidak salah, begitu makejuga melakukan sihir yang saya tidak mengerti untuk mengantisipasi bahwa aturan tidak akan mengubah apa pun, dan dengan demikian menganggapnya 'selesai'. Menambahkan .PHONY do-echofile Anda akan memberitahu makeuntuk mengabaikan ini dan tetap menjalankan kodenya.
M3D

Anda dapat menempatkan di $(info ...)luar aturan make. Ini masih akan menghasilkan keluaran.
Daniel Stevens


3

Iya. Anda keluar dari baris baru dengan \:

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

memperbarui

Ah, Anda ingin baris baru? Kalau begitu tidak, saya rasa tidak ada cara lain di Vanilla Make. Namun, Anda selalu dapat menggunakan dokumen di sini di bagian perintah

[Ini tidak berfungsi, lihat komentar dari MadScientist]

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

Ini benar, tetapi tidak memberi saya pemformatan apa pun (baris baru). Itu hanya menjadi satu baris teks
jonner

Dokumen multi-baris di sini tidak berfungsi seperti yang dijelaskan di GNU Make.
Matt B.

3
Multiline di sini, dokumen di dalam resep tidak akan berfungsi dalam versi standar apa pun dari make yang mendukung standar POSIX: standar make mengharuskan setiap baris resep terpisah harus dijalankan di shell terpisah. Make tidak melakukan parsing pada perintah untuk mengetahui bahwa itu adalah dokumen di sini atau bukan, dan menanganinya secara berbeda. Jika Anda mengetahui beberapa varian make yang mendukung ini (saya belum pernah mendengarnya), Anda mungkin harus menyatakannya secara eksplisit.
MadScientist

2

Hanya sebuah postscript untuk jawaban Eric Melski: Anda dapat menyertakan output dari perintah dalam teks, tetapi Anda harus menggunakan sintaks Makefile "$ (shell foo)" daripada sintaks shell "$ (foo)". Sebagai contoh:

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

Ini tidak memberikan dokumen di sini, tetapi ini menampilkan pesan multi-baris dengan cara yang sesuai untuk pipa.

=====

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

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

=====

Anda juga dapat menggunakan makro yang dapat dipanggil 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 "---"

=====

Berikut hasilnya:

=====

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

=====


1

Mengapa Anda tidak menggunakan karakter \ n dalam string Anda untuk menentukan akhir baris? Tambahkan juga garis miring terbalik ekstra untuk menambahkannya di beberapa baris.

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

Saya lebih suka jawaban Erik Melski tetapi ini mungkin sudah melakukan trik untuk Anda, tergantung pada aplikasi Anda.
Roalt

Saya punya pertanyaan tentang ini. Ini pada dasarnya bekerja dengan baik, kecuali saya melihat tambahan "spasi" di awal setiap baris (kecuali yang pertama). Apakah ini terjadi pada Anda? Saya bisa meletakkan semua teks dalam satu baris, dipisahkan oleh \ n sehingga secara efektif membuat keluaran yang saya suka. Masalahnya adalah itu terlihat sangat jelek di Makefile itu sendiri!
Shahbaz

Saya menemukan solusi. Saya memasukkan teks $(subst \n ,\n,$(TEXT))meskipun saya berharap ada cara yang lebih baik!
Shahbaz


1

Anda harus menggunakan "define / endef" Buat konstruksi:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Maka Anda harus meneruskan nilai variabel ini ke perintah shell. Tetapi, jika Anda melakukan ini menggunakan Make variable substitution, itu akan menyebabkan perintah terpecah menjadi beberapa:

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

Qouting juga tidak akan membantu.

Cara terbaik untuk meneruskan nilai adalah dengan meneruskannya melalui variabel lingkungan:

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

Memperhatikan:

  1. Variabel diekspor untuk target khusus ini, sehingga Anda dapat menggunakan kembali lingkungan itu tidak akan banyak tercemar;
  2. Gunakan variabel lingkungan (qoutes ganda dan tanda kurung kurawal di sekitar nama variabel);
  3. Penggunaan tanda kutip di sekitar variabel. Tanpa mereka baris baru akan hilang dan semua teks akan muncul dalam satu baris.

1

Dalam semangat .ONESHELL, Anda dapat mendekati lingkungan yang menantang .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

Contoh penggunaannya akan seperti ini:

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

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

Itu menunjukkan output (dengan asumsi pid 27801):

>
Hello
World\n/27801/

Pendekatan ini memungkinkan beberapa fungsi tambahan:

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

Opsi shell ini akan:

  • Cetak setiap perintah saat dijalankan
  • Keluar dari perintah pertama yang gagal
  • Perlakukan penggunaan variabel shell yang tidak ditentukan sebagai kesalahan

Kemungkinan menarik lainnya kemungkinan besar akan muncul dengan sendirinya.


1

Saya paling suka jawaban alhadis. Tetapi untuk mempertahankan format kolom, tambahkan satu hal lagi.

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

Keluaran:

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

Sinopsis sebuah program harus mudah dan jelas ditemukan. Saya akan merekomendasikan menambahkan tingkat informasi ini dalam readme dan / atau halaman manual. Saat pengguna berjalan make, mereka biasanya melakukannya dengan harapan untuk memulai proses pembangunan.

1
Saya ingin berkali-kali hanya melihat daftar membuat target. Komentar Anda tidak masuk akal. Apa yang diharapkan pengguna tidak relevan jika mereka membutuhkan waktu 3 detik untuk mengetahui apa yang harus dilakukan, sedangkan sebagai pengganti informasi seperti ini, terkadang dapat memakan waktu berjam-jam.
Xennex81

1
Menggunakan ekspektasi sebagai alasan untuk melakukan sesuatu juga merupakan argumen melingkar: karena orang mengharapkannya, kita harus melakukannya, dan karena kita melakukannya, mereka mengharapkannya.
Xennex81

1

Tidak sepenuhnya terkait dengan OP, tapi semoga ini akan membantu seseorang di masa depan. (karena pertanyaan ini adalah yang paling sering muncul di pencarian google).

Di Makefile saya, saya ingin meneruskan konten file, ke perintah build buruh pelabuhan, setelah banyak kekhawatiran, saya memutuskan untuk:

 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)

lihat contoh di bawah ini.

nb: Dalam kasus khusus saya, saya ingin meneruskan kunci ssh, selama pembuatan gambar, menggunakan contoh dari https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (menggunakan build buruh pelabuhan multi-tahap untuk mengkloning repo git, lalu jatuhkan kunci ssh dari gambar terakhir di tahap ke-2 pembuatan)

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

Dengan GNU Make 3.82 dan yang lebih baru, .ONESHELLopsinya adalah teman Anda dalam hal cuplikan kerangka multiline. Mengumpulkan petunjuk dari jawaban lain, saya mendapatkan:

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

Pastikan, saat menyalin dan menempel contoh di atas ke dalam editor Anda, bahwa setiap <tab>karakter dipertahankan, jika tidak, versiontarget akan rusak!

Perhatikan itu .ONESHELLakan menyebabkan semua target di Makefile menggunakan satu shell untuk semua perintah mereka.


Sayangnya itu tidak berhasil: 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, saya baru saja mengujinya dengan GNU Make 3.82 dan GNU bash 4.2.45 (1) -release: berfungsi seperti yang diharapkan. Selain itu, periksa keberadaan karakter TAB utama, bukan kosong, di depan @printf ...pernyataan - sepertinya TAB selalu dirender sebagai 4 spasi ...
sphakka

Tampaknya .ONESHELLbaru di make 3.82.
biru

btw: kesalahan saat menggunakan spasi alih-alih tab akan *** missing separator. Stop..
biru

0

Bukan jawaban yang benar-benar membantu, tetapi hanya untuk menunjukkan bahwa 'definisikan' tidak berfungsi seperti yang dijawab oleh Ax (tidak sesuai dengan komentar):

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)

Ini memberikan kesalahan bahwa perintah 'Itu' tidak dapat ditemukan, jadi ia mencoba untuk menafsirkan baris kedua dari ANNOUNCE BODY sebagai perintah.


0

Ini berhasil untuk saya:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile dapat melakukan hal-hal seperti berikut ini. Ini jelek, dan saya tidak akan mengatakan Anda harus melakukannya, tetapi saya melakukannya dalam situasi tertentu.

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 membuat file .profile jika tidak ada.

Solusi ini digunakan di mana aplikasi hanya akan menggunakan GNU Makefile di lingkungan shell POSIX. Proyek ini bukanlah proyek sumber terbuka yang masalah kompatibilitas platformnya.

Tujuannya adalah untuk membuat Makefile yang memfasilitasi penyiapan dan penggunaan jenis ruang kerja tertentu. Makefile membawa serta berbagai sumber daya sederhana tanpa memerlukan hal-hal seperti arsip khusus lainnya, dll. Ini, dalam arti tertentu, arsip shell. Sebuah prosedur kemudian dapat mengatakan hal-hal seperti meletakkan Makefile ini di folder untuk bekerja masuk Siapkan ruang kerja Anda masukmake workspace , lalu lakukan bla, masuk make blah, dll.

Apa yang bisa menjadi rumit adalah mencari tahu apa yang harus dikutip oleh shell. Di atas melakukan pekerjaan dan dekat dengan gagasan untuk menentukan dokumen di sini di Makefile. Apakah itu ide yang baik untuk penggunaan umum adalah masalah lain.


0

Saya yakin jawaban teraman untuk penggunaan lintas platform adalah menggunakan satu gema per baris:

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

Ini untuk menghindari asumsi tentang versi gema tersedia.


0

Gunakan substitusi string :

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

Lalu di resep Anda, masukkan

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

Ini berfungsi karena Make menggantikan semua kemunculan | (perhatikan spasi) dan menukar dengan karakter baris baru ( $$'\n'). Anda dapat menganggap pemanggilan skrip-shell yang setara sebagai sesuatu seperti ini:

Sebelum:

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

Setelah:

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

Saya tidak yakin apakah $'\n'tersedia pada sistem non-POSIX, tetapi jika Anda dapat memperoleh akses ke satu karakter baris baru (bahkan dengan membaca string dari file eksternal), prinsip dasarnya sama.

Jika Anda memiliki banyak pesan seperti ini, Anda bisa mengurangi kebisingan dengan menggunakan makro :

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

Di mana Anda akan memintanya seperti ini:

@$(call print,$(ANNOUNCE_BODY))

Semoga ini bisa membantu seseorang. =)


Saya paling suka yang ini. Tetapi untuk mempertahankan format kolom, tambahkan satu hal lagi. `SYNOPSIS: = :: Sinopsis: Makefile \ | :: \ | :: Penggunaan: \ | :: make ..........: menghasilkan pesan ini \ | :: buat sinopsis. : menghasilkan pesan ini \ | :: make clean ....: hilangkan target dan intermediet yang tidak diinginkan \ | :: make all ......: kompilasi seluruh sistem dari awal \ endef
jlettvin

Komentar tidak mengizinkan kode. Akan dikirim sebagai jawaban. Saya paling suka yang ini. Tetapi untuk mempertahankan format kolom, tambahkan satu hal lagi. `SYNOPSIS: = :: Sinopsis: Makefile`` | :: `` | :: Penggunaan: `` | :: make ..........: menghasilkan pesan ini` `| :: buat sinopsis. : menghasilkan pesan ini` `| :: make clean ....: hilangkan intermediate dan target` `| :: make all ......: kompilasi seluruh sistem dari ground-up` `endef`
jlettvin

@jlettvin Lihat tanggapan saya atas jawaban Anda. Sinopsis sebuah program seharusnya tidak disematkan di dalam Makefile, terutama bukan sebagai tugas default.

0

Sebagai alternatif, Anda dapat menggunakan perintah printf. Ini berguna di OSX atau platform lain dengan fitur yang lebih sedikit.

Untuk hanya mengeluarkan pesan multiline:

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

Jika Anda mencoba meneruskan string sebagai arg ke program lain, Anda dapat melakukannya seperti ini:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.