Можно ли создать многострочную строковую переменную в Makefile


122

Я хочу создать переменную makefile, которая представляет собой многострочную строку (например, текст сообщения электронной почты о выпуске). что-то вроде

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

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

Но я не могу найти способ сделать это. Является ли это возможным?

Ответы:


172

Да, вы можете использовать ключевое слово define для объявления многострочной переменной, например:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Сложная часть - вернуть вашу многострочную переменную из make-файла. Если вы просто сделаете очевидную вещь, используя "echo $ (ANNOUNCE_BODY)", вы увидите результат, опубликованный здесь другими - оболочка пытается обработать вторую и последующие строки переменной как сами команды.

Однако вы можете экспортировать значение переменной как есть в оболочку как переменную среды, а затем ссылаться на нее из оболочки как переменную среды (НЕ переменную make). Например:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Обратите внимание на использование $$ANNOUNCE_BODY, указывающего на ссылку на переменную среды оболочки, а не $(ANNOUNCE_BODY)на обычную ссылку на переменную make. Также не забудьте заключить ссылку на переменную в кавычки, чтобы убедиться, что новые строки не интерпретируются самой оболочкой.

Конечно, этот конкретный трюк может зависеть от платформы и оболочки. Я тестировал его на Ubuntu Linux с GNU bash 3.2.13; YMMV.


1
export ANNOUNCE_BODYустанавливает только переменную внутри правил - это не позволяет ссылаться на $$ ANNOUNCE_BODY для определения других переменных.
анатолий техтоник

@techtonik, если вы хотите использовать значение ANNOUNCE_BODYв других определениях переменных, просто укажите его, как любую другую переменную make. Например, OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY). Конечно, вам все равно понадобится exportтрюк, если вы хотите выйти OTHERв команде.
Eric Melski

25

Другой подход к «получению вашей многострочной переменной обратно из make-файла» (отмеченный Эриком Мельски как «сложная часть») состоит в том, чтобы спланировать использование substфункции для замены defineсимволов новой строки, представленных в многострочной строке, на \n. Затем используйте -e с, echoчтобы интерпретировать их. Вам может потребоваться установить .SHELL = bash, чтобы получить эхо, которое делает это.

Преимущество этого подхода в том, что вы также добавляете в текст другие подобные escape-символы и уважаете их.

Это своего рода синтез всех упомянутых подходов ...

Вы получаете:

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 03

13

Предполагая, что вы хотите только распечатать содержимое своей переменной на стандартном выводе, есть другое решение:

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

1
Это не оп правило произвело нежелательное сообщение: make: 'do-echo' is up to date.. Используя команду «no op», я смог заглушить его:@: $(info $(YOUR_MULTILINE_VAR))
Guillaume Papin

@GuillaumePapin Немного поздно, но вы можете использовать его, .PHONYчтобы сообщить своему Makefile, что нечего проверять по этому правилу. Файлы Makefile изначально предназначались для компиляторов, если я не ошибаюсь, поэтому makeтворит некую магию, которую я не понимаю, чтобы предвидеть, что правило ничего не изменит, и поэтому предполагает, что это «сделано». Добавление .PHONY do-echoв ваш файл укажет вам makeигнорировать это и в любом случае запустить код.
M3D 06

Вы можете разместить $(info ...)вне правила make. Он по-прежнему будет генерировать вывод.
Дэниел Стивенс,


3

Да. Вы экранируете символы новой строки \:

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

Обновить

Ах, вы хотите новые строки? Тогда нет, я не думаю, что в vanilla Make что-то есть. Однако вы всегда можете использовать здесь-документ в командной части.

[Это не работает, см. Комментарий MadScientist]

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

Это правда, но это не дает мне никакого форматирования (новые строки). Он просто превращается в одну строку текста
Джоннер

Многострочные документы здесь не работают так, как описано в 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

Вы должны использовать конструкцию Make "define / endef":

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Затем вы должны передать значение этой переменной в команду оболочки. Но если вы сделаете это с помощью подстановки переменной Make, это приведет к разделению команды на несколько:

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

Qouting тоже не поможет.

Лучший способ передать значение - передать его через переменную окружения:

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

Примечание:

  1. Переменная экспортируется для этой конкретной цели, так что вы можете повторно использовать эту среду, не сильно загрязненную;
  2. Используйте переменную окружения (двойные кавычки и фигурные скобки вокруг имени переменной);
  3. Использование кавычек вокруг переменной. Без них новые строки будут потеряны, и весь текст будет отображаться в одной строке.

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 он выглядит очень некрасиво!
Shahbaz

Я нашел обходной путь. Я прописал текст, $(subst \n ,\n,$(TEXT))хотя хотел бы, чтобы был способ получше!
Shahbaz


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 и / или справочную страницу. Когда пользователь запускается make, он обычно ожидает запуска процесса сборки.

1
Я много раз хотел просто увидеть список целей make. Ваш комментарий не имеет смысла. То, что ожидают пользователи, не имеет значения, если им требуется 3 секунды, чтобы знать, что делать, тогда как вместо любой подобной информации иногда могут потребоваться часы.
Xennex81

1
Использование ожиданий в качестве причины что-то сделать также является круговым аргументом: поскольку люди ожидают этого, мы должны это делать, а поскольку мы это делаем, они этого ожидают.
Xennex81 02

1

Не полностью связано с OP, но, надеюсь, это поможет кому-то в будущем. (поскольку этот вопрос чаще всего встречается при поиске в Google).

В моем Makefile я хотел передать содержимое файла команде сборки docker, после долгого испуга я решил:

 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, а затем удаление ключа ssh из окончательного образа на 2-м этапе сборки)

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 и выше эта .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

@blueyed, я только что тестировал его с GNU Make 3.82 и GNU bash 4.2.45 (1) -release: он работает, как ожидалось. Также, пожалуйста, проверьте наличие ведущего символа TAB вместо пробелов перед @printf ...оператором - похоже, что TAB всегда отображаются как 4 пробела ...
sphakka

Похоже, что .ONESHELLэто новинка в версии 3.82.
blueyed

кстати: ошибка при использовании пробелов вместо табуляции была бы *** missing separator. Stop..
blueyed

0

Не совсем полезный ответ, но просто чтобы указать, что 'define' не работает, как ответил Ax (не вписывается в комментарий):

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)

Выдает ошибку, что команду «Оно» не может быть найдено, поэтому пытается интерпретировать вторую строку ANNOUNCE 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: = :: Synopsis: Makefile \ | :: \ | :: Использование: \ | :: make ..........: генерирует это сообщение \ | :: сделать синопсис. : генерирует это сообщение \ | :: очистить ....: удалить нежелательные промежуточные звенья и цели \ | :: make all ......: скомпилировать всю систему с нуля \ endef
jlettvin

Комментарии не разрешают код. Отправлю в ответ. Мне это нравится больше всего. Но чтобы сохранить форматирование столбцов, добавьте еще одну вещь. `SYNOPSIS: = :: 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.