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

Viewed 2594

In the makefile on Windows.

With the following make version:

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

I have tried to set SHELL := pwsh / COMSPEC := pwsh and run command without explicit shell specifying:

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

VAR=asdf/asdf

.PHONY: get_var

get_var:
    @Write-Output $(VAR)

without success. I've got an error:

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

If shell specified explicitly in the command, it works:

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

VAR=asdf/asdf

.PHONY: get_var

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

run:

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

Also, i've looked into 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.

So i tried to set environment variable SHELL/MAKESHELL with no result:

PS C:\projects\makefile_factory> $env:SHELL
C:\Program Files\PowerShell\7\pwsh.exe
PS C:\projects\makefile_factory> $env:MAKESHELL
C:\Program Files\PowerShell\7\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

So, isn't there a way to specify pwsh as a default shell?

2 Answers

What @raspy says is true, but, I found a glorious workaround!

If you take a look at this 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

In any event, all that means we need to do is set the .SHELLFLAGS to something besides -c or -ce, for example with powershell we can use -Command, tying it all together:

SHELL := pwsh
.SHELLFLAGS := -Command

VAR=asdf/asdf

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

Or, in my example where I want PowerShell.exe on Windows & the default shell on Linux:

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

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

test:
    rm test.txt

Cheers!

This is not as easy as one may think. In general GNU make does not really call shell until it decides it has to do, but rather takes a shortcut and tries to invoke command directly (exec or CreateProcess depending on the platform).

And how does make decide whether the shell is needed? Well, it has a predefined list of keywords to be known by a specific shell. For Windows there is a separate list of keywords specific to cmd and a Unixy-shell.

Now, what is a Unixy (a.k.a Bourne-compatible) shell? Well, it's another predefined list: sh, bash, ksh, rksh, zsh, ash, dash. Note no pwsh here.

So in order to get make to actually execute shell, the command must include one of known keywords or characters, otherwise make will try to run the command directly, without invoking the shell.

One more thing to remember: when setting up SHELL, it has to be either full path or full name of the file to be resolved by searching PATH environment variable. Note that PowerShell's executable is pwsh.exe not pwsh and while the latter works on the command line, it is the command line interpreter that tries different extensions, make just simply checks if the file exists.

Adding up all of the above, I was able to run PowerShell as follows:

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

VAR=asdf/asdf

.PHONY: get_var

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

Testing with a freshly compiled native 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'.
Related