将 Powershell 核心设置为默认 GNU Make shell on windows/linux

set Powershell core as a default GNU Make shell on windows/linux

在 Windows 上的 makefile 中。

使用以下 make 版本:

PS C:\projects> make --version
GNU Make 4.1
Built for i686-w64-mingw32
Copyright (C) 1988-2014 Free Software Foundation, Inc.

我尝试设置 SHELL := pwsh / COMSPEC := pwsh 和 运行 命令,但没有明确 shell 指定:

# COMSPEC := pwsh -c
SHELL := pwsh -c

VAR=asdf/asdf

.PHONY: get_var

get_var:
    @Write-Output $(VAR)

没有成功。我有一个错误:

PS C:\projects\makefile_factory> make -f .\fi.makefile get_var
process_begin: CreateProcess(NULL, Write-Output asdf/asdf, ...) failed.
make (e=2): ═х єфрхЄё  эрщЄш єърчрээ√щ Їрщы.
.\fi.makefile:10: recipe for target 'get_var' failed
make: *** [get_var] Error 2

如果在命令中明确指定shell,则有效:

# COMSPEC := pwsh -c
# SHELL := pwsh -c

VAR=asdf/asdf

.PHONY: get_var

get_var:
    @pwsh -c Write-Output $(VAR)

运行:

PS C:\projects\makefile_factory> make -f .\fi.makefile get_var
asdf/asdf

此外,我调查了 make documentation:

However, on MS-DOS and MS-Windows the value of SHELL in the environment is used, since on those systems most users do not set this variable, and therefore it is most likely set specifically to be used by make. On MS-DOS, if the setting of SHELL is not suitable for make, you can set the variable MAKESHELL to the shell that make should use; if set it will be used as the shell instead of the value of SHELL.

所以我尝试设置环境变量 SHELL/MAKESHELL 但没有结果:

PS C:\projects\makefile_factory> $env:SHELL
C:\Program Files\PowerShell\pwsh.exe
PS C:\projects\makefile_factory> $env:MAKESHELL
C:\Program Files\PowerShell\pwsh.exe
PS C:\projects\makefile_factory> make -f .\fi.makefile get_var
Write-Output asdf/asdf
process_begin: CreateProcess(NULL, Write-Output asdf/asdf, ...) failed.
make (e=2): ═х єфрхЄё  эрщЄш єърчрээ√щ Їрщы.
.\fi.makefile:9: recipe for target 'get_var' failed
make: *** [get_var] Error 2

那么,有没有办法将 pwsh 指定为默认值 shell?

这并不像人们想象的那么容易。一般来说,GNU make 不会真正调用 shell 直到它决定它必须这样做,而是采取快捷方式并尝试直接调用命令(execCreateProcess 取决于平台)。

make 是如何决定是否需要 shell 的?好吧,它有一个 predefined list of keywords to be known by a specific shell. For Windows there is a separate list 特定于 cmd 的关键字和一个 Unixy-shell.

现在,什么是 Unixy(a.k.a Bourne 兼容)shell?嗯,这是另一个 predefined 列表:shbashkshrkshzshashdash.注意这里没有pwsh

所以为了让 make 实际执行 shell,该命令必须包含已知关键字或字符之一,否则 make 将尝试 运行直接命令,而不调用 shell.

还有一点要记住:在设置SHELL时,必须是要通过搜索PATH环境变量解析的文件的完整路径或全名。请注意,PowerShell 的可执行文件是 pwsh.exe 而不是 pwsh,虽然后者在命令行上运行,但它是尝试不同扩展名的命令行解释器,make 只是简单地检查文件是否 exists.

将以上所有内容相加,我能够 运行 PowerShell 如下:

> Get-Content .\Makefile
SHELL := pwsh.exe

VAR=asdf/asdf

.PHONY: get_var

get_var:
        @&Write-Output $(VAR)
        @$$PSVersionTable

使用新编译的本机 Win32 GNU make 进行测试:

> ..\make-4.1\WinRel\gnumake.exe -dr
GNU Make 4.1
Built for Windows32
Copyright (C) 1988-2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Reading makefiles...
Reading makefile 'Makefile'...
find_and_set_shell() path search set default_shell = C:/Program Files/PowerShell/7/pwsh.exe
Updating makefiles....
 Considering target file 'Makefile'.
  Looking for an implicit rule for 'Makefile'.
  No implicit rule found for 'Makefile'.
  Finished prerequisites of target file 'Makefile'.
 No need to remake target 'Makefile'.
Updating goal targets....
Considering target file 'get_var'.
 File 'get_var' does not exist.
 Finished prerequisites of target file 'get_var'.
Must remake target 'get_var'.
CreateProcess(NULL,C:/Program Files/PowerShell/7/pwsh.exe -c "&Write-Output asdf/asdf",...)
Putting child 00B86400 (get_var) PID 12170424 on the chain.
Live child 00B86400 (get_var) PID 12170424
Main thread handle = 0000016C
asdf/asdf
Reaping winning child 00B86400 PID 12170424
CreateProcess(NULL,C:/Program Files/PowerShell/7/pwsh.exe -c $PSVersionTable,...)
Live child 00B86400 (get_var) PID 12193536

Name                           Value
----                           -----
PSVersion                      7.0.0
PSEdition                      Core
GitCommitId                    7.0.0
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Reaping winning child 00B86400 PID 12193536
Removing child 00B86400 PID 12193536 from chain.
Successfully remade target file 'get_var'.

@raspy 说的是真的,但是,我找到了一个 极好的 解决方法!

如果你看看这个if statement right here you'll notice that if the shellflags is set to something besides -c or -ce it will always use the slow option (Which means it will always execute through the SHELL rather the binary directly! ). Luckily for us, we can set the .SHELLFLAGS directly, take a look at these docs

无论如何,这意味着我们需要做的就是将 .SHELLFLAGS 设置为 -c-ce 以外的值,例如使用 powershell 我们可以使用 -Command,将它们捆绑在一起:

SHELL := pwsh
.SHELLFLAGS := -Command

VAR=asdf/asdf

.PHONY: get_var
get_var:
    @Write-Output $(VAR)

或者,在我的示例中,我希望 Windows 上的 PowerShell.exe 和 Linux 上的默认 shell:

ifeq ($(OS),Windows_NT)
SHELL := powershell.exe
.SHELLFLAGS := -NoProfile -Command
endif

test.txt:
    echo "hello, world" > test.txt

test:
    rm test.txt

干杯!