0

I want to embed a python script in a Makefile

I built a python -c script (below), and it works well in my MacBook Hyper terminal:

% python -c $'from subprocess import getstatusoutput\noutput=getstatusoutput("open --background -a Docker")\nif int(output[0])>0:\n    print("Docker desktop failed to launch: exit-code:{}".format(output[0]))'

For reasons I can't yet figure out, this seems to fail if I build a Makefile with it (note: a tab is four spaces... I used tab indentation in the Makefile).

all:
    $(shell python -c $'from subprocess import getstatusoutput\noutput=getstatusoutput("open --background -a Docker")\nif int(output[0])>0:\n    print("Docker desktop failed to launch: exit-code:{}".format(output[0]))')

Running the make all target...

% make all
/bin/sh: -c: line 0: syntax error near unexpected token `('
/bin/sh: -c: line 0: `python -c from subprocess import getstatusoutput\noutput=getstatusoutput("open --background -a Docker")\nif int(output[0])>0:\n    print("Docker desktop failed to launch: exit-code:{}".format(output[0]))''
make: `all' is up to date.
%

I have been struggling with this for a while...

Can someone help explain why make all fails and the best way to fix this python -c command? My shell CLI python -c ... command successfully launches Docker desktop on my MacBook.

I understand there are non-python ways to solve this specific problem... I need a general python Makefile solution.

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
  • Can you extract that script to a Python file, and just run it? That would get around the various escaping problems and it seems like it'd be much more maintainable. – David Maze Sep 17 '21 at 16:08
  • I considered calling a separate .py script in the `Makefile`, but I want all variables and control native inside the `Makefile`. I agree that oneliners are not fun to maintain... – Mike Pennington Sep 17 '21 at 16:10
  • 2
    Using `$(shell ...)` in recipes is a common enough beginner error that we have a canonical duplicate for that: https://stackoverflow.com/questions/10024279/how-to-use-shell-commands-in-makefile – tripleee Sep 17 '21 at 16:45

2 Answers2

2

Using python -c in a Makefile is tricky because of Python's indentation requirements. One simple solution is to use SHELL=/bin/bash if you want to use a Bash "C-style" string:

SHELL=/bin/bash
all:
    # python command must be wrapped in single quotes _and_ have double dollar sign in front
    python -c $$'from subprocess import getstatusoutput\noutput=getstatusoutput("open --background -a Docker")\nif int(output[0])>0:\n    print("Docker desktop failed to launch: exit-code:{}".format(output[0]))'

(Notice how the dollar sign needs to be doubled to escape it. And obviously, this restricts the portability of your Makefile to systems where Bash is available. The $'...' syntax lets you use escape codes like \n and \t within a string, and have them expanded to newline and tab, respectively. This construct specifically requires a leading dollar sign and single quotes around the string - merely '...' does someting slightly different, and $"..." does something entirely different.)

You could also define a make multi-line variable. But in this isolated case, Python is not playing any useful role anyway.

all:
    open --background -a Docker

make will terminate with an error message if open fails; printing essentinally the same message from Python seems superfluous. If you want to proceed in spite of the error, you can do

all:
    open --background -a Docker || \
    echo "Docker desktop failed to launch: exit-code: $$?"

... though I assume failing to fail (sic) from the Python script was just a mistake.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 1
    You have a stray newline before the backslash which shouldn't be there FYI. – MadScientist Sep 17 '21 at 16:53
  • I agree that this example doesn't need python, but... we have all visited the unpleasantries know as complex shell regular expressions... `python -c $'import re'` [PCRE](https://www.pcre.org/ "pcre site") is much easier than dealing with shell regexp – Mike Pennington Sep 17 '21 at 17:08
  • Perl has by and large the same regex facilities and is somewhat easier for oneliners, though having to double all the dollar signs in Perl is tedious, too. – tripleee Sep 17 '21 at 17:30
1

I found a fairly simple way to embed a multiline python script in a Makefile...

  1. Save this as Makefile...
define MULTILINE_PYTHON_SCRIPT
###########################################
# Start multiline python string here...
###########################################
from subprocess import getstatusoutput as gso

print("Here we go:")
for hello_int in [1, 2, 3,]:
    print('    Hello World %i' % hello_int)

retval, _ = gso("ls -la")
assert retval==0, "ls command execution failed"

###########################################
# End of multiline python string...
###########################################
endef
export MULTILINE_PYTHON_SCRIPT
EMBEDDED_PY := python -c "$$MULTILINE_PYTHON_SCRIPT"

.PHONY: nothing
nothing:
    echo "raw makefile command"

.PHONY: test
test:
    $(EMBEDDED_PY)

.PHONY: all
all:
    open --background -a Docker
  1. Testing the output:
% make nothing
echo "raw makefile command"
raw makefile command
%
% make test
python -c "$MULTILINE_PYTHON_SCRIPT"
Here we go:
    Hello World 1
    Hello World 2
    Hello World 3
%
%
Mike Pennington
  • 41,899
  • 19
  • 136
  • 174