如何在Makefile中获取脚本?


77

是否有更好的方法从makefile中获取设置环境变量的脚本?

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

2
听起来您真正想要的是编写一个脚本,该脚本将源脚本设置为envvars,然后运行make,而不是使源脚本本身……
Chris Dodd

Answers:


46

要回答询问的问题:您不能

基本问题是子进程不能更改父进程的环境。外壳由能够规避这种不分叉一个新的进程时,source荷兰国际集团“,但只是运行在shell的当前版本的命令。那很好用,但make不是/bin/sh(或您的脚本使用的是任何shell),并且不理解该语言(除了它们的共同点)。

Chris Dodd和Foo Bah解决了一个可能的解决方法,因此我建议另一个解决方法(假设您正在运行GNU make):将Shell脚本后处理为make兼容文本并包含结果:

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

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

杂乱的细节留作练习。


3
您“无法”为整个Makefile提供脚本,但是您可以为单个配方执行脚本。
Yaroslav Nikitenko '18

source compilationEnv.sh; $(CC) -c -o $< $^或类似名称将用作操作线。
dmckee ---前主持人小猫,

1
您可以使用include .env(或任何键值文件)使整个Makefile都可以访问变量,然后仅将所需的变量传递到相应的配方中。
rypel

94

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脚本,请通过以下shell命令捕获结果:

JUST_DO_IT=$(shell source_script)

shell命令应在目标之前运行。但是,这不会设置环境变量。

如果要在构建中设置环境变量,请编写一个单独的Shell脚本来获取环境变量并调用make。然后,在makefile中,让目标调用新的shell脚本。

例如,如果您的原始makefile具有target 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 2011年

如果不导出环境变量,是否可以捕获环境变量?乍一看,似乎是调用一个新的shell,运行命令,然后退出并返回到旧环境。
brooksbp 2011年

@brooksbp shell命令不会捕获环境变量。但是,如果您编写了一个源于原始脚本的shell脚本,那么它将。我将进一步解释
美孚呸

17

使用GNU Make 3.81,我可以使用以下命令从make中获取shell脚本:

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

@ user5359531是的,是的。
塞缪尔

10

这对我有用。env.sh用要获取的文件名替换。它的工作方式是在bash中获取文件,然后在格式化后将修改后的环境输出到一个名为的文件中makeenv,然后由makefile来获取该文件。

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
2014年

非常适合设置正确的编译器环境(此处为64位)。谢谢。
2014年

执行的语法是:“ make IGNORE”吗?
约翰

@约翰,不。只是make
gdw2

如果变量在你的价值观env.sh包含特殊制作字符,如#$。当您执行include makeenv这些操作时,这些值将全部弄乱。
donhector

6

有些结构是在同shellGNU Make

var=1234
text="Some text"

您可以更改您的Shell脚本来获取定义。它们必须都是简单name=value类型。

[script.sh]

. ./vars.sh

[Makefile]

include vars.sh

然后,shell脚本和Makefile可以共享相同的信息“源”。我找到了这个问题,因为我正在寻找可以在Gnu Make和shell脚本中使用的通用语法的清单(我不在乎哪个shell)。

编辑: 外壳并理解$ {var}。这意味着您可以串联等,var =“一个字符串” var = $ {var}“第二个字符串”


错误,上面的shell脚本不会像接受的答案那样产生任何混乱的细节。最终变得非常像kbuild文件。
artless噪音

1
shell变量和make变量并非始终透明地互换。当您在中使用include它们时Makefile,它们必须遵守make语法,该语法强加了要转义的某些字符。例如SECRET='12#34$56',您中的shell变量值在.vars.sh之后使用时会给您带来麻烦include vars.sh
donhector

1
我认为有两个用例;OP无法控制脚本。因此,您的观点非常好,因为您最有可能获得需要转义的数据。这些是“%$#”。如果用例是在使用环境变量和制造的工具之间共享变量,则此解决方案可以工作。它具有仅一个信息源且没有额外流程的优点。如果您无法控制shell脚本,那么此答案就没有用了。还令人怀疑为什么要在变量名中使用“ $%#”。如果你计算的项目,您需要按照“编辑:”部分
烂漫的噪音

3

我真的很喜欢Foo Bah的回答,在该命令中make调用了脚本,而脚本又回调了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:*** Notarget。Stop。”。您可以通过在隐式规则中添加“ all”来解决此问题,例如:“ all%:”。请注意,这会在较新版本的make中引起“ ***混合的隐式和常规规则:不建议使用的语法”警告。
塞缪尔

1

我对此的解决方案:(假设您有bash,例如,其语法$@是不同的tcsh

拥有一个脚本sourceThenExec.sh,例如:

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

然后,在您的makefile中,使用以下命令作为目标的开头 bash sourceThenExec.sh,例如:

ExampleTarget:
    bash sourceThenExec.sh gcc ExampleTarget.C

你当然可以放一些 STE=bash sourceThenExec.sh在makefile的顶部并缩短它:

ExampleTarget:
    $(STE) gcc ExampleTarget.C

所有这些都有效,因为sourceThenExec.sh打开了一个子外壳,但是命令在同一子外壳中运行。

这种方法的缺点是为每个目标获取文件,这可能是不希望的。


1

如果要将变量放入环境中,以便将它们传递给子进程,则可以使用bash的set -aset +a。前者的意思是“当我设置变量时,也要设置相应的环境变量。” 所以这对我有用:

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

那将把一切传递.env.testcargo 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

根据您的制作和封装外壳的版本,你可以实现一个很好的解决方案通过evalcat以及与链接电话&&

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

试试看,它将起作用,脚本位于当前目录内。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.