Чи можливо створити багато рядкову змінну рядка в Makefile


122

Я хочу створити змінну makefile, яка є багаторядковою рядком (наприклад, тілом оголошення про випуск електронної пошти). щось на зразок

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Але я не можу знайти спосіб зробити це. Це можливо?

Відповіді:


172

Так, ви можете скористатись ключовим словом defini для оголошення багаторядкової змінної, наприклад:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Хитра частина - це повернення вашої багаторядкової змінної назад із makefile. Якщо ви просто зробите очевидну справу використання "echo $ (ANNOUNCE_BODY)", ви побачите результат, який інші опублікували тут - оболонка намагається обробляти другий і наступні рядки змінної як самі команди.

Однак ви можете експортувати значення змінної as-is в оболонку як змінну середовища, а потім посилати її з оболонки як змінну середовища (НЕ make змінну). Наприклад:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Зверніть увагу на використання $$ANNOUNCE_BODY, вказуючи замість змінної посилання на середовище оболонки, а не $(ANNOUNCE_BODY), що було б регулярним посиланням на змінну. Також не забудьте використовувати лапки навколо вашої змінної посилання, щоб переконатися, що нові рядки не інтерпретуються самою оболонкою.

Звичайно, цей фокус може бути чутливим до платформи та оболонок. Я протестував його на Ubuntu Linux з GNU bash 3.2.13; YMMV.


1
export ANNOUNCE_BODYвстановлює лише змінну всередині правил - вона не дозволяє посилатися на $$ ANNOUNCE_BODY для визначення інших змінних.
anatoly techtonik

@techtonik, якщо ви хочете використовувати значення ANNOUNCE_BODYінших визначень змінних, просто посилайтеся на нього, як і будь-яке інше. Наприклад, OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY). Звичайно, вам все одно знадобиться exportхитрість, якщо ви хочете отримати OTHERкоманду.
Ерік Мельський

25

Інший підхід до «повернення багатолінійної змінної назад з makefile» (відзначається Еріком Мельським як «хитра частина») - це планувати використовувати substфункцію для заміни нових рядків, введених defineу багаторядковій рядку \n. Потім використовуйте -e з, echoщоб інтерпретувати їх. Вам може знадобитися встановити .SHELL = bash, щоб отримати відлуння, що робить це.

Перевага такого підходу полягає в тому, що ви також додаєте до тексту інші подібні символи і поважаєте їх.

Цей вид синтезує всі згадані підходи ...

Ви закінчите:

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

Зверніть увагу, що окремі цитати остаточного відлуння є вирішальними.


4
Зауважте, що "echo -e" не є портативним. Напевно, вам слід віддати перевагу printf (1).
MadScientist

2
чудова відповідь, однак, мені довелося видалити =після, define ANNOUNCE_BODYщоб запустити його.
mschilli

13

Якщо припустити, що ви хочете друкувати вміст змінної на стандартному виході, є ще одне рішення:

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

1
Це не оп правило справило небажане повідомлення: make: 'do-echo' is up to date.. Використовуючи команду "без оп", я зміг замовкнути це:@: $(info $(YOUR_MULTILINE_VAR))
Гійом Папін

@GuillaumePapin Трохи запізнився, але ви можете .PHONYсказати своєму Makefile, що перевірити це правило нема чого. Якщо я не помиляюсь, Makefiles спочатку був компіляторами, тому я makeроблю якусь магію, яку я не розумію, щоб передбачити, що правило нічого не змінить, і як таке передбачає, що це буде "виконано". Додавання .PHONY do-echoу ваш файл дозволить makeігнорувати це та все-таки запустити код.
M3D

Ви можете розміщувати $(info ...)поза правилами make. Він все одно отримає вихід.
Даніель Стівенс


3

Так. Ви уникаєте нових рядків за допомогою \:

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

оновлення

Ах, ти хочеш нових рядків? Тоді ні, я не думаю, що у ванільній формі є спосіб. Однак ви завжди можете використовувати тут документ у командній частині

[Це не працює, дивіться коментар MadScientist]

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

Це правда, але це не дає мені форматування (нові рядки). Це просто стає єдиним рядком тексту
jonner

Багаторядкові документи тут не працюють, як описано в GNU Make.
Метт Б.

3
Мультилінійні сюди документи всередині рецептів не працюватимуть у будь-якій стандартній версії make, яка підтримує стандарт POSIX: стандарт make вимагає, щоб кожен окремий рядок рецепта був запущений в окремій оболонці. Make не робить розбору команди, щоб сказати, що це документ тут чи ні, і обробляти його по-іншому. Якщо ви знаєте якийсь варіант make, який підтримує це (я жодного разу не чув про нього), ви, ймовірно, повинні це чітко вказати.
MadScientist

2

Просто постскрипт на відповідь Еріка Мельського: Ви можете включити вихід тексту в текст, але ви повинні використовувати синтаксис Makefile "$ (shell foo)", а не синтаксис оболонки "$ (foo)". Наприклад:

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

2

Тут не подається документ тут, але він відображає багаторядкове повідомлення таким чином, що підходить для труб.

=====

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

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

=====

Ви також можете використовувати макроси, що дзвонять 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 "---"

=====

Ось результат:

=====

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

=====


1

Чому ви не використовуєте символ \ n у рядку для визначення кінця рядка? Також додайте додатковий нахил, щоб додати його через кілька рядків.

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

Я вважаю за краще відповідь Еріка Мельського, але це може зробити трюк вже для вас, залежно від вашої заявки.
Роалт

У мене виникло питання з цього приводу. Це працює в основному чудово, за винятком того, що я бачу додатковий пробіл на початку кожного рядка (крім першого). З вами це трапляється? Я можу помістити весь текст в один рядок, розділений на \ n, щоб ефективно створити результат, який мені подобається. Проблема в тому, що це виглядає дуже некрасиво в самому Makefile!
Шахбаз

Я знайшов вирішення. Я вкладаю текст, $(subst \n ,\n,$(TEXT))хоча хочу, щоб був кращий шлях!
Шахбаз


1

Ви повинні використовувати "define / endef" Зробити конструкцію:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Тоді вам слід передати значення цієї змінної команді shell. Але якщо це зробити за допомогою підстановки змінної, це призведе до розбиття команди на кілька:

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

Qouting також не допоможе.

Найкращий спосіб передавати значення - це передати його через змінну середовища:

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

Зверніть увагу:

  1. Для цієї конкретної цілі експортується змінна, так що ви можете повторно використовувати, що середовище не забрудниться сильно;
  2. Використовувати змінну середовища (подвійні квоти та фігурні дужки навколо назви змінної);
  3. Використання цитат навколо змінної. Без них нові рядки будуть втрачені, і весь текст з’явиться в одному рядку.

1

У дусі .ONESHELL можна досить близько наблизитись до складних середовищ .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

Приклад використання може бути приблизно таким:

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

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

Це показує вихід (припускаючи pid 27801):

>
Hello
World\n/27801/

Цей підхід дозволяє отримати додаткову функціональність:

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

Ці параметри оболонки:

  • Роздрукуйте кожну команду по мірі її виконання
  • Вихід із першої невдалої команди
  • Трактуйте використання невизначених змінних оболонок як помилку

Інші цікаві можливості, ймовірно, підкажуть самі.


1

Найкраще мені подобається відповідь альхадіса. Але щоб зберегти стовпчикове форматування, додайте ще одне.

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

Виходи:

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

Конспект програми повинен бути легким і очевидним для пошуку. Я рекомендую додати цей рівень інформації в readme та / або manpage. Коли користувач працює make, вони зазвичай роблять це, розраховуючи розпочати процес збирання.

1
Мені вже багато разів хотілося побачити список складання цілей. Ваш коментар не має сенсу. Що очікують користувачі, не має значення, якщо їм знадобиться 3 секунди, щоб знати, що робити, тоді як замість будь-якої такої інформації, іноді це може зайняти години.
Xennex81

1
Використання очікувань як причини зробити щось теж круговий аргумент: оскільки люди очікують цього, ми мусимо це зробити, і тому, що ми це робимо, вони цього і очікують.
Xennex81

1

Не повністю пов'язаний з ОП, але, сподіваємось, це допоможе комусь у майбутньому. (оскільки саме це питання виникає найбільше в пошуку Google).

У своєму Makefile я хотів передати вміст файла команді docker build, після великого побоювання вирішив:

 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)

див. приклад нижче.

nb: У моєму конкретному випадку я хотів передати ключ ssh під час створення зображення, використовуючи приклад із https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (використовуючи багатоступенева збірка докерів для клонування git repo, а потім скиньте ключ ssh з остаточного зображення на 2-й стадії збірки)

Makefilefile

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

1

З GNU Make 3.82 і вище, цей .ONESHELLваріант є вашим другом, коли справа стосується багаторядкових фрагментів оболонки. Збираючи разом підказки з інших відповідей, я отримую:

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

Переконайтеся, що копіюючи та вставляючи вищевказаний приклад у свій редактор, що будь-які <tab>символи збереглися, інакше versionціль зламається!

Зверніть увагу, що .ONESHELLзмусить усі цілі в Makefile використовувати одну оболонку для всіх своїх команд.


На жаль, це не працює: 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, я щойно тестував це за допомогою GNU Make 3.82 та GNU bash 4.2.45 (1) -release: працює як слід. Крім того, будь ласка, перевірте наявність головного символу TAB замість пробілів перед @printf ...заявою - схоже, що TAB завжди відображаються як 4 пробіли ...
sphakka

Здається, що .ONESHELLце нове в марці 3.82.
синюватий

btw: помилка при використанні пробілів замість вкладки буде *** missing separator. Stop..
синюватий

0

Не дуже корисна відповідь, але просто для того, щоб вказати, що "визначити" не працює, як відповів Axe (не вмістився у коментарі):

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)

Це дає помилку, що команду "Її" неможливо знайти, тому вона намагається інтерпретувати другий рядок ОГОЛОЖЕННЯ BODY як команду.


0

Це працювало для мене:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

0

GNU Makefile може робити наступні дії. Це некрасиво, і я не скажу, що ти повинен це робити, але я це роблю в певних ситуаціях.

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 створює .profile файл, якщо його не існує.

Це рішення використовувалося там, коли програма буде використовувати GNU Makefile лише в середовищі оболонки POSIX. Проект не є проектом з відкритим кодом, де проблема сумісності платформи є проблемою.

Метою було створити Makefile, який полегшить як налаштування, так і використання певного виду робочої області. Makefile містить разом з собою різні прості ресурси, не вимагаючи таких речей, як інший спеціальний архів тощо. Це, в певному сенсі, архів оболонок. Потім процедура може сказати такі речі, як, наприклад, скинути цей Makefile у папку для роботи. Налаштуйте робочу область увійти make workspace, потім зробити благ, увійти make blahтощо.

Що може бути складним - це з'ясувати, що слід цитувати. Вищезазначене виконує завдання і близьке до ідеї вказати тут документ у Makefile. Чи хороша це ідея для загального використання - це зовсім інше питання.


0

Я вважаю, що найбезпечнішою відповіддю для використання на платформах буде використання одного відлуння на рядок:

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

Це дозволяє уникнути надання будь-яких припущень щодо версії ехо.


0

Використовувати підстановку рядків :

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

Потім у свій рецепт покладіть

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

Це працює, тому що Make замінює всі події (відзначте пробіл) і замінивши його символом нової лінії ( $$'\n'). Ви можете подумати про еквівалентні виклики скрипта оболонки як щось подібне:

Перед:

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

Після:

$ 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, але якщо ви можете отримати доступ до одного символу нової рядки (навіть читаючи рядок із зовнішнього файлу), то основний принцип той самий.

Якщо у вас є багато подібних повідомлень, ви можете зменшити шум за допомогою макросу :

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

Куди б ви покликали це так:

@$(call print,$(ANNOUNCE_BODY))

Сподіваюся, це комусь допомагає. =)


Мені це найбільше подобається. Але щоб зберегти стовпчикове форматування, додайте ще одне. `SYNOPSIS: = :: Синопсис: Makefile \ | :: \ | :: Використання: \ | :: make ..........: генерує це повідомлення \ | :: зробити конспект. : створює це повідомлення \ | :: очистити ....: усунути небажані проміжні продукти та цілі \ | :: make all ......: компілювати всю систему з наземного \ endef
jlettvin

Коментарі не дозволяють код. Надішлемо як відповідь. Мені це найбільше подобається. Але щоб зберегти стовпчикове форматування, додайте ще одне. `SYNOPSIS: = :: Синопсис: Makefile`` | :: `` | :: Використання: `` | :: make ..........: генерує це повідомлення `` | :: зробити конспект. : створює це повідомлення `` | :: очистити ....: усунути небажані проміжні продукти та мішені `` | :: make all ......: компілювати всю систему з наземного `` endef`
jlettvin

@jlettvin Дивіться мою відповідь на вашу відповідь. Конспект програми, безумовно, не повинен вбудовуватися в Makefile, особливо не як завдання за замовчуванням.

0

В якості альтернативи можна використовувати команду printf. Це корисно для OSX або інших платформ з меншими можливостями.

Щоб просто вивести багаторядкове повідомлення:

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

Якщо ви намагаєтеся передати рядок як аргумент іншій програмі, ви можете зробити так:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.