Як отримати скрипт у файлі Makefile?


79

Чи є кращий спосіб отримати скрипт, який встановлює env vars, із файлу make?

FLAG ?= 0
ifeq ($(FLAG),0)
export FLAG=1
/bin/myshell -c '<source scripts here> ; $(MAKE) $@'
else
...targets...
endif

2
Здається, те, що ви насправді хочете, - це написати сценарій, який створює скрипт для встановлення envvars, а потім запускає make, а не робить сам джерелом скрипт ...
— Кріс Додд,

Я побачив чудову відповідь тут: unix.stackexchange.com/a/235254/18152 і тут: blog.153.io/2016/04/18/source-a-shell-script-in-make
— zx1986

Відповіді:


47

Відповісти на запитання: не можна .

Основне питання полягає в тому, що дочірній процес не може змінити батьківське середовище. Оболонка обходить це, не розгалужуючи новий процес під час sourceстворення, а просто виконуючи ці команди в поточному втіленні оболонки. Це чудово працює, але makeце не так /bin/sh(або для будь-якої оболонки, для якої призначений ваш сценарій), і не розуміє цієї мови (крім бітів, які вони мають спільні).

Кріс Додд та Фу Ба вирішили одне із можливих обхідних шляхів, тому я запропоную інший (за умови, що у вас запущено GNU make): обробити сценарій оболонки у сумісний текст і включити результат:

shell-variable-setter.make: shell-varaible-setter.sh
    postprocess.py @^

# ...
else
include shell-variable-setter.make
endif

брудні деталі, залишені як вправа.


3
Ви не можете "створити" сценарій для цілого Makefile, але ви можете зробити це для одного рецепта.
— Ярослав Нікітенко

Так source compilationEnv.sh; $(CC) -c -o $< $^чи подібне буде працювати як лінія дії.
— dmckee --- екс-модератор кошеня

1
Ви можете використовувати include .env(або будь-який файл ключ-значення), щоб зробити змінні доступними для всього Makefile, а потім передати лише необхідні у відповідний рецепт.
— Рипель

98

Шаблон за замовчуванням Makefile - це той, /bin/shякий не реалізований source.

Зміна оболонки на /bin/bashдозволяє:

# Makefile

SHELL := /bin/bash

rule:
    source env.sh && YourCommand

4
@VivekAditya Це, звичайно, надзвичайно корисна річ. Але зауважте, що OP просить встановити змінну для використання, makeа не лише для використання в рядках дій. Це як зробити останнє, але прямого способу зробити перше не існує.
— dmckee --- екс-модератор кошеня

1
Для цього ви можете легко встановити SHELLвласну обгортку. Показано тут gist.github.com/ingydotnet/99f7e259319f6b90e50a754f3053be7f
— ingydotnet

22

Якщо ваша мета - просто встановити змінні середовища для Make, чому б не зберегти його в синтаксисі Makefile і не використати includeкоманду?

include other_makefile

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

JUST_DO_IT=$(shell source_script)

команда shell повинна виконуватися перед цілями. Однак це не встановить змінні середовища.

Якщо ви хочете встановити змінні середовища у збірці, напишіть окремий скрипт оболонки, який джерела ваших змінних середовища та викликів. Потім у файлі make попросіть цілі викликати новий сценарій оболонки.

Наприклад, якщо ваш оригінальний make-файл має цільову точку a, ви хочете зробити щось подібне:

# mysetenv.sh
#!/bin/bash
. <script to source>
export FLAG=1
make "$@" 

# Makefile
ifeq($(FLAG),0)
export FLAG=1
a: 
    ./mysetenv.sh a
else
a:
    .. do it
endif

4
Я не контролюю сценарій. Потрібно це отримати.
— brooksbp

Чи захопить це змінні середовища, якщо вони не експортуються? З першого погляду здається, що він викликає нову оболонку, запускає команду, потім виходить і повертається до старого середовища ..?
— brooksbp

@brooksbp Команда оболонки не захоплює змінні середовища. Але якщо ви напишете скрипт оболонки, який буде джерелом оригінального, тоді він буде. Поясню це далі
— Foo Bah

17

За допомогою GNU Make 3.81 Я можу отримати скрипт оболонки з make, використовуючи:

rule:
<tab>source source_script.sh && build_files.sh

build_files.sh "отримує" змінні середовища, експортовані source_script.sh.

Зверніть увагу, що використання:

rule:
<tab>source source_script.sh
<tab>build_files.sh

не буде працювати. Кожен рядок запускається у своїй підоболонці.


1
Зверніть увагу, що збереження sourceта команди в одному рядку стають необхідними з GNU Make 4.1 (а можливо, також і 4.0). Використовуючи 3.81, я можу використовувати різні рядки. Для мобільності в цих версіях працює один рядок. Виклик зовнішнього сценарію стає більш читабельним, якщо рядок стає занадто довгим!
— Ерік Платон,

Ти впевнений? Кілька рядків не працювали для мене в 3.81, як я вже згадував у відповіді. Make запускає кожен рядок у своїй власному підшелупці, тому кілька рядків не повинні працювати в будь-якій версії. Можливо, у вас є незвичайна ОС або побудова?
— Самуель

1
кілька рядків працює до тих пір, поки ви закінчуєте кожен рядок
— символом

@ user5359531 Так, це правда.
— Самуель

10

Це працює для мене. Замініть env.shім'я файлу, який потрібно створити. Він працює, шукаючи файл у bash і виводячи модифіковане середовище після його форматування у файл, makeenvякий називається, а потім отримує файл make.

IGNORE := $(shell bash -c "source env.sh; env | sed 's/=/:=/' | sed 's/^/export /' > makeenv")                         
include makeenv   

1
ifdef _intel64onosx<br/> GCC=/opt/intel/bin/icc<br/> CFLAGS= -m64 -g -std=c99 IGNORE :=$(shell bash -c "source /opt/intel/bin/iccvars.sh intel64; env | sed 's/=/:=/' | sed 's/^/export /' > makeenv") include makeenv
— revher

Хороший варіант для встановлення правильних середовищ компілятора (тут 64-розрядна версія). Дякую.
— revher

Чи є синтаксис для виконання цього: "make IGNORE"?
— Джон

@ Джон, ні. Просто make.
— gdw2

Що робити, якщо значення змінних у вашому env.shмістять спеціальні символи Make, такі як #або $. Коли ви це зробите, include makeenvці значення будуть заплутані.
— donhector

6

Деякі конструкції однакові в shellі в GNU Make.

var=1234
text="Some text"

Ви можете змінити свій сценарій оболонки, щоб отримати вихідні дані. Усі вони повинні бути простими name=valueтипами.

Тобто,

[script.sh]

. ./vars.sh

[Makefile]

include vars.sh

Тоді сценарій оболонки та Makefile можуть спільно використовувати одне й те саме «джерело» інформації. Я знайшов це запитання, тому що шукав маніфест загальноприйнятого синтаксису, який можна використовувати у скриптах Gnu Make та shell (мені все одно, яка оболонка).

Змінити: оболонки та дати зрозуміти $ {var} . Це означає, що ви можете об'єднати і т.д., var = "Один рядок" var = $ {var} "Другий рядок"


Помилка, сценарій оболонки, як вище, не створює брудних деталей, як у прийнятій відповіді. У підсумку це схоже на файл kbuild .
— бездумний шум

1
shellзмінні та makeзмінні не завжди прозоро взаємозамінні. Коли ви includeїх у своєму Makefile, вони повинні відповідати makeсинтаксису, який нав'язує певні символи, які слід уникати. Наприклад, значення змінної оболонки, як SECRET='12#34$56'у вашому .vars.sh, створить вам проблеми, коли ви будете використовувати його післяinclude vars.sh
— donhector

1
Я думаю, що є два випадки використання; OP не має контролю над сценарієм. Отже, ваша думка дуже добра, оскільки ви, швидше за все, отримаєте дані, які потрібно врятувати. Це "% $ #". Якщо варіант використання полягає у спільному використанні змінних між інструментами, які використовують змінні середовища та роблять, тоді це рішення може працювати. Він має перевагу лише в одному джерелі інформації та відсутність зайвих процесів. Якщо у вас немає контролю над сценарієм оболонки, ця відповідь не така корисна. Також сумнівно, чому ви хочете "$% #" в назві змінної. Якщо ви обчислюєте елементи, вам потрібно дотримуватися частини "Редагувати:".
— бездумний шум

3

Мені дуже подобається відповідь Фу Ба, де make викликає сценарій, а сценарій дзвонить назад, щоб зробити. Щоб розширити цю відповідь, я зробив це:

# Makefile
.DEFAULT_GOAL := all 

ifndef SOME_DIR

%:
<tab>. ./setenv.sh $(MAKE) $@

else

all:
<tab>...

clean:
<tab>...

endif

-

# setenv.sh
export SOME_DIR=$PWD/path/to/some/dir

if [ -n "$1" ]; then
    # The first argument is set, call back into make.
    $1 $2
fi

Це має додаткову перевагу використання $ (MAKE) у випадку, якщо хтось використовує унікальну програму make, а також буде обробляти будь-яке правило, вказане в командному рядку, без необхідності дублювати ім'я кожного правила у випадку, коли SOME_DIR не визначено .


Якщо у вас є старіша версія make, а .DEFAULT_GOAL не підтримується, перша гілка ifndef вийде з ладу з "make: *** Немає цілей. Зупинити.". Ви можете це виправити, додавши "все" до неявного правила, наприклад: "всі%:". Зауважте, що це спричинить попередження "*** змішані неявні та звичайні правила: застарілий синтаксис" із новими версіями make.
— Самуель

1

Моє рішення цього: (припускаючи, що у вас є, наприклад bash, синтаксис для $@відрізняється tcsh)

Майте сценарій sourceThenExec.shяк такий:

#!/bin/bash
source whatever.sh
$@

Потім у своєму файлі make вводьте перед цілями bash sourceThenExec.sh, наприклад:

ExampleTarget:
    bash sourceThenExec.sh gcc ExampleTarget.C

Звичайно, ви можете помістити щось на кшталт STE=bash sourceThenExec.shу верхній частині вашого make-файлу і скоротити це:

ExampleTarget:
    $(STE) gcc ExampleTarget.C

Все це працює, оскільки sourceThenExec.shвідкриває під оболонку, але тоді команди виконуються в тій самій під оболонці.

Недоліком цього методу є те, що файл отримується для кожної цілі, що може бути небажаним.


1

Якщо ви хочете отримати змінні у середовищі , щоб вони передавалися дочірнім процесам, тоді ви можете використовувати bash set -aта set +a. Перший означає: "Коли я встановлюю змінну, встановлюю відповідну змінну середовища". Отже, це працює для мене:

check:
    bash -c "set -a && source .env.test && set +a && cargo test"

Це буде проходити все в .env.testна cargo testяк змінні середовища.

Зверніть увагу, що це дозволить вам передавати середовище підкомандам, але не дозволить встановлювати змінні Makefile (які в будь-якому випадку різні). Якщо вам потрібно останнє, спробуйте одну з інших пропозицій тут.


0

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

$ grep ID /etc/os-release 

ID=ubuntu
ID_LIKE=debian


$ cat Makefile

default: help rule/setup/lsb

source?=.

help:
        -${MAKE} --version | head -n1

rule/setup/%:
        echo ID=${@F}

rule/setup/lsb: /etc/os-release
        ${source} $< && export ID && ${MAKE} rule/setup/$${ID}


$ make

make --version | head -n1
GNU Make 3.81
. /etc/os-release && export ID && make rule/setup/${ID}
make[1]: Entering directory `/tmp'
echo ID=ubuntu
ID=ubuntu

- http://rzr.online.fr/q/gnumake


0

В залежності від версії Марки і огороджувальних оболонок, ви можете реалізувати хороше рішення з допомогою eval, catі ланцюжок викликів з &&:

ENVFILE=envfile

source-via-eval:
  @echo "FOO: $${FOO}"
  @echo "FOO=AMAZING!" > $(ENVFILE)
  @eval `cat $(ENVFILE)` && echo "FOO: $${FOO}"

І швидкий тест:

> make source-via-eval
FOO:
FOO: AMAZING!


-1

Іншим можливим способом може бути створення сценарію sh, наприклад run.sh, отримання необхідних сценаріїв і виклик make у скрипті.

#!/bin/sh
source script1
source script2 and so on

make 

-1
target: output_source
    bash ShellScript_name.sh

спробуйте це буде працювати, скрипт знаходиться всередині поточного каталогу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.