1

On make version 4.2 {2016} was added the variable .SHELLSTATUS. But my most linux are using make version 4.1. How I can I get the exit status and the output results of the make $(shell ...) command on older make versions?

With make 4.2, I can do this:

newmake:
    printf '%s\n' "$(shell command && \
        ( printf 'Successfully created the zip file!\n'; exit 0 ) || \
        ( printf 'Error: Could not create the zip file!\n'; exit 1 ) )"
    printf 'Exit status: '%s'\n' "${.SHELLSTATUS}"

Which run correctly:

printf '%s\n' "Successfully created the zip file!"
Successfully created the zip file!
printf 'Exit status: '%s'\n' "0"
Exit status: 0

But, if I am using make 4.1 or older, there is no .SHELLSTATUS variable. Then, I am trying to define my own .SHELLSTATUS variable like this:

oldmake:
    printf '%s\n' "$(shell command && \
        ( printf 'Successfully created the zip file!\n'; $(eval exit_status := 0) echo ${exit_status} > /dev/null ) || \
        ( printf 'Error: Could not create the zip file!\n'; $(eval exit_status := 1) echo ${exit_status} > /dev/null ) )"
    printf 'Exit status: '%s'\n' "${exit_status}"

But, it does not run correctly:

printf '%s\n' "Successfully created the zip file!"
Successfully created the zip file!
printf 'Exit status: '%s'\n' "1"
Exit status: 1

As you can see, the right message was print, but the exit status should be 0, not 1.

Notice

The messages Successfully created the zip file! and Error: Could not create the zip file! are purely illustrative and cannot be used outside $(shell ...). What do I have inside shell is a full featured Python script which outputs its results to the screen:

define NEWLINE


endef

define RELEASE_CODE
from __future__ import print_function
import os
import zipfile

version = "${version}"
if not version:
    print( "Error: You need pass the release version. For example: make release version=1.1", end="@NEWNEWLINE@" )
    exit(1)

CURRENT_DIRECTORY = os.path.dirname( os.path.realpath( __file__ ) )
print( "Packing files on %s" % CURRENT_DIRECTORY, end="@NEWNEWLINE@" )

file_names = []
initial_file_names = [
    "Makefile",
    "build.bat",
    "fc-portuges.def",
    os.path.join("setup", "makefile.mk"),
    os.path.join("setup", "scripts", "timer_calculator.sh"),
]

for direcory_name, dirs, files in os.walk(CURRENT_DIRECTORY):

    if ".git" in direcory_name:
        continue

    for filename in files:
        filepath = os.path.join( direcory_name, filename )

        if ".git" in filepath or not ( filepath.endswith( ".tex" )
                or filepath.endswith( ".bib" )
                or filepath.endswith( ".pdf" ) ):
            continue

        file_names.append( filepath )

for filename in initial_file_names:
    filepath = os.path.join( CURRENT_DIRECTORY, filename )
    file_names.append( filepath )

zipfilepath = os.path.join( CURRENT_DIRECTORY, version + ".zip" )
zipfileobject = zipfile.ZipFile(zipfilepath, mode="w")
zipfilepathreduced = os.path.dirname( os.path.dirname( zipfilepath ) )

try:
    for filename in file_names:
        relative_filename = filename.replace( zipfilepathreduced, "" )
        print( relative_filename, end="@NEWNEWLINE@" )
        zipfileobject.write( filename, relative_filename, compress_type=zipfile.ZIP_DEFLATED )

except Exception as error:
    print( "", end="@NEWNEWLINE@" )
    print( "An error occurred: %s" % error, end="@NEWNEWLINE@" )
    exit(1)

finally:
    zipfileobject.close()

print( "", end="@NEWNEWLINE@" )
print( "Successfully created the release version on:@NEWNEWLINE@  %s!" % zipfilepath , end="" )
endef

all:
    printf '%s\n' "$(shell echo \
        '$(subst ${NEWLINE},@NEWLINE@,${RELEASE_CODE})' | \
        sed 's/@NEWLINE@/\n/g' | python - && \
        printf 'Successfully created the zip file!\n' || \
        printf 'Error: Could not create the zip file!\n' )" | sed 's/@NEWNEWLINE@/\n/g'

Now it runs correctly, but I am missing the python exit code, which can be fixed with this .SHELLSTATUS:

all:
    printf '%s\n' "$(shell echo \
        '$(subst ${NEWLINE},@NEWLINE@,${RELEASE_CODE})' | \
        sed 's/@NEWLINE@/\n/g' | python - && \
        ( printf 'Successfully created the zip file!\n'; exit 0 ) || \
        ( printf 'Error: Could not create the zip file!\n'; exit 1 ) )" | sed 's/@NEWNEWLINE@/\n/g'
    exit "${.SHELLSTATUS}"

But, it only works for make version 4.2 or newer.

Related:

  1. https://askubuntu.com/questions/93499/assign-standard-exit-status-to-a-makefile-variable
  2. Returning value from called function in a shell script
  3. How to get exit status of a shell command used in GNU Makefile?
  4. How to make GNU Make fail if a shell command assigned to a variable failed?
  5. Shell status codes in make
  6. Create zip archive with multiple files
  7. Zip Multiple files with multiple result in Python
  8. python/zip: How to eliminate absolute path in zip archive if absolute paths for files are provided?
  9. How to send input on stdin to a python script defined inside a Makefile?
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
  • Would it be practical to build and install a newer `make` from source? – Keith Thompson Apr 25 '19 at 00:27
  • 1
    No. The focus is to be an effortless solution to end users. Managing to create a temporary file is more practical because does not require any changes other than asking for them to run `sudo apt install make`. – Evandro Coan Apr 25 '19 at 03:01

2 Answers2

2

I just can't understand why you're trying to make use of $(shell ...) at all. This is so overly complex, and so meaningless. The only effect of $(shell ...) is that it expands before any recipe is run. But you never try to make use of that. On the contrary, it looks only natural to move the script directly inside the recipe.

For example, consider this:

unused = """
ifeq (true,false)
"""

# some python stuff
print("Hello world")
exit(1)

unused = """
endif

# here comes make stuff
.ONESHELL:
.PHONY: all
all:
    @/usr/bin/python $(lastword $(MAKEFILE_LIST))
    ec=$$?
    echo "script exitcode is $$ec"
    exit $$ec

#"""

Maybe even better solution is simply to make python a custom SHELL for a single rule. Although, in this case it could be tricky to mix python and shell scripts in the same rule (but I don't think it's really essential).

Matt
  • 13,674
  • 1
  • 18
  • 27
0

I managed to do it by creating a file:

fixedmake:
    printf '%s\n' "$(shell command && \
        printf 'Successfully created the zip file!\n' || \
        ( printf 'Error: Could not create the zip file!\n'; \
            printf 'error\n' > /tmp/has_errors ) )"

    if [ -e /tmp/has_errors ]; \
        then printf 'Exit with errror\n'; \
        rm /tmp/has_errors; \
    else \
        printf 'Exit with success\n'; \
    fi;

-->

printf '%s\n' "Successfully created the zip file!"
Successfully created the zip file!
if [ -e /tmp/has_errors ]; \
        then printf 'Exit with errror\n'; \
        rm /tmp/has_errors; \
else \
        printf 'Exit with success\n'; \
fi;
Exit with success

Any better alternative?

Evandro Coan
  • 8,560
  • 11
  • 83
  • 144