Makefile 和 Bash script 在使用的过程中有很多奇奇怪怪的坑,本文做一下纪录。

首先,有两个文件,一个叫 envs,里面定义了一个环境变量,比如

$ cat envs

export GOPROXY="test.local"

第二个文件就是 Makefile ,假如我这样写

test:
    source ./envs
    echo ${GOPROXY}

所以,总的目标是,我希望在 Makefile 中导入另一个文件中事先定义好的环境变量。 然而这样的写法有很多问题。

source 命令找不到

加入直接运行 make, 很有可能你会看到这样的错误

$ make
source ./envs
make: source: Command not found

可是在 terminal 里面明明可以用 source 命令啊? 于是,第一个坑出现:

source is a (non-POSIX) shell builtin, not an executable program on any conventional UNIX-like system. If source is changed to ., make changes its strategy; instead of trying to find and execute a program, it just passes the rule body to the system shell.

更加具体的原因在这篇文章中已经说了。

通过这个错可以学到什么呢?

  1. 保险的做法是在 Makefile 中指定 SHELL := /bin/bash
  2. 学习了用 strace 命令查看系统调用
$ strace -f -s65535 -- make | grep source

[pid 104517] execve("/usr/local/go/bin/source", ["source", "./envs"], 0x559253194a40 /* 31 vars */) = -1 ENOENT (No such file or directory)
[pid 104517] execve("/usr/local/sbin/source", ["source", "./envs"], 0x559253194a40 /* 31 vars */) = -1 ENOENT (No such file or directory)
[pid 104517] execve("/usr/local/bin/source", ["source", "./envs"], 0x559253194a40 /* 31 vars */) = -1 ENOENT (No such file or directory)

变量没有赋值

接下来把 Makefile 改成这样

SHELL := /bin/bash

test:
        source ./envs
        echo ${GOPROXY}

可是结果依然不对,GOPROXY 的值并没有被 echo 出来

$ make
source ./envs
echo

查了一番资料以后,发现了参考资料 2 中的解释

Each line of a make recipe is executed by a separate shell, so even if you source the file, the shell that sources it exits before the next line is evaluated.

也就是说,我的 Makefile 把 source 和 echo 写成了两行,就相当于

bash -c 'source .envs'
bash -c 'echo ${GOPROXY}'

Makefile 变量 和 Shell 变量

经过上面的错误,我把 Madefile 改成了下面这样,这下总对了吧?

SHELL := /bin/bash

test:
        source ./envs && echo ${GOPROXY}

结果还是不对

$ make
source ./envs && echo

为什么呢? 又经过一番搜索,发现了参考资料 3 的解释:

在 Makefile 中使用变量的值得时候,分为 makefile 的变量和 shell 变量

  1. makefile variable, use a single dollar sign
  2. shell variable, use two dollar signs

所以,最后写成这样才对

SHELL := /bin/bash

test:
        source ./envs && echo $${GOPROXY}

总结

搞得这么复杂,罪魁祸首就是把环境变量放在了单独的文件中,其实直接在 Makefile 开头 export 环境变量就没有这么多麻烦了。

参考资料

  1. https://stackoverflow.com/questions/44052093/makefile-with-source-get-error-no-such-file-or-directory
  2. https://stackoverflow.com/questions/54481072/source-env-variables-files-inside-makefile
  3. https://stackoverflow.com/questions/26564825/what-is-the-meaning-of-a-double-dollar-sign-in-bash-makefile