Dealing with warnings when running SAS in batch - makefile

I like to use Makefiles so I frequently run SAS programs in batch from the command line. SAS will return a non-zero status of 1 if it encounters any warnings, and a non-zero status of 2 or greater if there is an error.
This means the make errors out even if there are only warnings. I can force the Makefile to ignore non-zero statuses, but then it also ignores actual errors.
How would I write a Makefile such that it errors out if I get a non-zero status of 2 or greater, but continues for a non-zero status of 1?
ex:
myOutput.sas7bdat: myProgram.sas
"path/to/sas.exe" $<
## ignore errors
myOutput.sas7bdat: myProgram.sas
-"path/to/sas.exe" $<

You can either use .ONESHELL if your version of make supports it (4.0+ for windows)
.ONESHELL:
myOutput.sas7bdat: myProgram.sas
"path/to/sas.exe" $<
if %ERRORLEVEL% gtr 1 exit /b 1
Or you can wrap those two lines into a batch file (replacing $< with %1 in the file)
myOutput.sas7bdat: myProgram.sas
whatever.cmd $<

Related

Conditional execute within Makefile

I would like to check if a file exists in a Makefile and run a recipe if it doesn't exist and stop after creating it else continue working with the file that exists and execute more recipes. I think this is best illustrated with an example in my Makefile:
GEN_C_FILES: GEN_C_FLS
$(shell cat $(DESIGN_TOP).c.tmp c_template.c.tmp > $(DESIGN_TOP).c )
$(DESIGN_TOP).c:
$(MAKE) GEN_C_FILES
#(echo $(DESIGN_TOP).c created and modify it to suit the design before running COMPILE)
exit 1
COMPILE: $(DESIGN_TOP).c
#(echo bsub -I compile_script-batch $(DESIGN_TOP).c -log $(DESIGN_TOP).log)
#(bsub -I compile_script -batch $(DESIGN_TOP).c -log $(DESIGN_TOP).log)
I can accomplish what I want but the exit produces an error message. I want it to exit if the recipe of $(DESIGN_TOP).c is executed but if the recipe is not executed the file exist and so it need not be generated and the rest of COMPILE target should complete by running the compile on bsub. Is there a better way to accomplish this without generating the error on exit?. Thanks
You can't make it work as you want when running make COMPILE. The only way to get make to stop in the middle of a build process and not proceed any further is to fail. But, instead you can change what make wants to build in the first place, like this:
GOAL = $(if $(wildcard $(DESIGN_TOP).c),COMPILE,$(DESIGN_TOP).c)
all: $(GOAL)
$(DESIGN_TOP).c:
cat $(DESIGN_TOP).c.tmp c_template.c.tmp > $#
#echo $# created and modify it to suit the design
COMPILE: $(DESIGN_TOP).c
bsub -I compile_script -batch $< -log $(DESIGN_TOP).log
Now if you run make or make all and the source file does not exist, the prerequisite of all will be the source file and nothing else, so it will be built then make will stop.
If the source file does exist, then the prerequisite of all will be COMPILE and it will be built.

Run a command in a makefile and print STDERR / STDOUT to both the terminal and a text file

First off, I'm using GNU Make 4.3 on Windows 10. I previously tried GNU Make 4.2.1, which gave me the exact same results as 4.3.
Anyway, I have a very simple makefile that does (or at least is intended to do) nothing more than run a simple command and print the output of that command (both stderr and stdout) to the terminal and to a text file.
$(info $$(MAKECMDGOALS) is "$(MAKECMDGOALS)". $$(SHELL) is \
"$(SHELL)". $$(MAKESHELL) is "$(MAKESHELL)". $$(COMSPEC) is "$(COMSPEC)". $$(OS) is "$(OS)".)
TEE := C:\tools\UnixTools\usr\local\wbin\tee.exe
LOG_FILE := C:\Temp\loggy__.txt
.PHONY : meep
all : meep
meep :
$(info Making meep.)
$(info Running command {dir 2>&1 | $(TEE) $(LOG_FILE)}.)
$(shell dir 2>&1 | $(TEE) $(LOG_FILE))
The last line is the one that is giving me trouble. Two things are happening that run counter to my expectations:
While the $(shell ...) call does print the output of the dir command both to my text file and the terminal, the output on the terminal is weirdly formatted. Where normally, dir prints one element per line, here I'm getting the entire output in one line, so it seems like GNU Make (or something else) somehow removes the newline characters from the output before it is shown in the terminal window.
In addition, I'm getting a The system cannot find the file specified. error message (and as usual, Windows is not nice enough to tell me which file it is that it cannot find). Running an echo %errorlevel% in the same CMD shell in which I ran GNU Make confirms that the Make call errored out (exit status is 2).
Weirdly enough, if I run the command dir 2>&1 | C:\tools\UnixTools\usr\local\wbin\tee.exe C:\Temp\loggy__.txt directly in the CMD window, everything works exactly as one would expect, without any errors whatsoever, so I'm thinking there's either something wrong with GNU Make's $(shell ...) function, or I'm using it wrong. Does anyone spot something silly in how I'm trying to use the $(shell ...) function?
I just added --debug=a to my make call to get extra debug output, and I found the following in the output:
Creating temporary batch file C:\Users\mkemp\AppData\Local\Temp\make23400-1.bat
Batch file contents:
#echo off
dir 2>&1 | C:\tools\UnixTools\usr\local\wbin\tee.exe C:\Temp\loggy__.txt
CreateProcess(C:\Users\mkemp\AppData\Local\Temp\make23400-1.bat,C:\Users\mkemp\AppData\Local\Temp\make23400-1.bat,...)
Main thread handle = 00000000000000B4
Cleaning up temporary batch file C:\Users\mkemp\AppData\Local\Temp\make23400-1.bat
Creating temporary batch file C:\Users\mkemp\AppData\Local\Temp\make23400-2.bat
Batch file contents:
#echo off
Volume in drive C is Windows Volume Serial Number is 045A-E422 Directory of C:\tools\UnixTools\usr\local\wbin (... the rest of the output)
CreateProcess(C:\Users\mkemp\AppData\Local\Temp\make23400-2.bat,C:\Users\mkemp\AppData\Local\Temp\make23400-2.bat,...)
So it appears that GNU Make's $(shell ...) function somehow interprets the output produced by the dir call as an additional command it needs to run, which is nonsense, of course.
Using $(shell) is nonsense here. make is acting exactly like you instructed it.
The proper solution is to not add the $(shell ...) function call where it makes no sense.
meep :
$(info Making meep.)
$(info Running command {dir 2>&1 | $(TEE) $(LOG_FILE)}.)
dir 2>&1 | $(TEE) $(LOG_FILE)
Of course, using $(info ...) in a recipe is probably bogus. Inside each recipe, you are running the shell; use the shell's syntax to print diagnostic messages.
meep:
#echo Making meep. >&2
#echo Running command '{dir 2>&1 | $(TEE) $(LOG_FILE)}.' >&2
dir 2>&1 | $(TEE) $(LOG_FILE)
Better yet, don't run make -s and let make itself print what commands it is running, as it does by default (if you don't sprinkle your Makefile with # before all commands to make it harder to debug).

Make ignore errors: what is the difference between -i and -k

I want make to continue even if a dependency's build fails. I usually use -i to accomplish this. A colleague of mine said he uses -k. Indeed, this stack overflow question has an answer for each:
Make: how to continue after a command fails?
Is there a difference between these two options?
Here's what the make man page says for these two options:
-i, --ignore-errors
Ignore all errors in commands executed to remake files.
-k, --keep-going
Continue as much as possible after an error. While the
target that failed, and those that depend on it, cannot be
remade, the other dependencies of these targets can be
processed all the same.
What -k describes is what I think -i does. I'm sure I'm missing something: can someone help me understand the difference?
Consider this makefile:
all: fail success
all success:
#echo $#
fail:
exit 1
#echo $#
Now run with the two flags:
$ make -i
exit 1
make: [Makefile:7: fail] Error 1 (ignored)
fail
success
all
This flag caused make to pretend that a specific recipe command succeeded, even though it failed. Thus the all target is still run, because make believes that the fail target actually succeeded. It's equivalent to adding a - at the beginning of every recipe line.
As opposed to:
$ make -k
exit 1
make: *** [Makek:7: fail] Error 1
success
make: Target 'all' not remade because of errors.
Here, make knows the fail target was not built. success is run because of -k: it doesn't depend on fail. However, all is not built because it does depend on fail.
I've never really needed -i; it seems dangerous to me.
On the other hand, I use -k almost by default.

makefile recursive -q mode Error 1

I am trying to run a recursive invocation of "question mode", and I get an error in a very unique scenario.
I am using MAKE 3.81, and this has been tested on two completely separate environments.
I call "make -q", then that makefile calls "$(MAKE) -C sub/a/", then that makefile calls "$(MAKE) -f ../../makefile.b"
The testcase is as simple as I can make it. Can someone tell me why I get this error:
nachum:/home/nachum/makefile_bug[1497]$make -q
make -C sub/a
make[1]: Entering directory `/home/nachum/makefile_bug/sub/a'
make -f ../../makefile.b
make[1]: *** [b] Error 1
make[1]: Leaving directory `/home/nachum/makefile_bug/sub/a'
make: *** [a] Error 2
nachum:/home/nachum/makefile_bug[1498]$
Here are the makefiles:
makefile:
a:
$(MAKE) -C sub/a
sub/a/makefile:
b:
$(MAKE) -f ../../makefile.b
makefile.b:
all:
echo hi
The whole point of this exercise is to be able to check if sub projects need to be recompiled so I can properly build the top level project when necessary. Otherwise I have to use timestamps for everything. (I previously used timestamps, but I realized that caused extra confusion for other things.)
There are some weird workarounds for this problem. For example, if the recipe for a (in makefile) has an additional line above the call to $(MAKE), ie:
makefile:
a:
#echo hi
$(MAKE) -C sub/a
The problem goes away, AND the dependencies (in my full testcase) still work. Also using make directly seems to change the behavior (as opposed to $(MAKE)).
Any help would be appreciated.
Thanks,
Nachum
Your problem is roaming around the -q option specified. -q would run no command but give exit status if up to date. When you use ${MAKE} make -q -C sub/a is executed. after little permutation and combination I found that we can't use -q with -C option. If you want quite then use make --quite or if you just want to check the timestamps then try with -t (touch but don't compile) or '-n` (dry run).

How to make a failing $(shell) command interrupt Make

I have a Makefile that starts by running a tool before applying the build rules (which this tool writes for me). If this tool, which is a python script, exits with a non-null status code, I want GNU Make to stop right there and not go on with building the program.
Currently, I do something like this (top level, i.e. column 1):
$(info Generating build rules...)
$(shell python collect_sources.py)
include BuildRules.mk
But this does not stop make if collect_sources.py exits with a status code of 1. This also captures the standard output of collect_sources.py but does not print it out, so I have the feeling I'm looking in the wrong direction.
If at all possible, the solution should even work when a simple MS-DOS shell is the standard system shell.
Any suggestion?
There might be a better way, but I tried the following and it works:
$(if $(shell if your_command; then echo ok; fi), , $(error your_command failed))
Here I did assume that your_command does not give any output, but it shouldn't be hard to work around such a situation.
Edit: To make it work with the default Windows shell (and probably any decent shell) you could write your_command && echo ok instead of the if within the shell function. I do not think this is possible for (older) DOS shells. For these you probably want to adapt your_command or write a wrapper script to print something on error (or success).
Ok, here's my own solution, which is unfortunately not based on the status code of the collect_sources.py script, but which Works For Me (TM) and lets me see any output that the script produces:
SHELL_OUTPUT := $(shell python collect_sources.py 2>&1)
ifeq ($(filter error: [Errno %],$(SHELL_OUTPUT)),)
$(info $(SHELL_OUTPUT))
else
$(error $(SHELL_OUTPUT))
endif
The script is written so that any error produces an output beginning with "collect_sources: error:". Additionally, if python cannot find or execute the given script, it outputs an error message containing the message "[Errno 2]" or similar. So this little piece of code just captures the output (redirecting stderr to stdout) and searches for error messages. If none is found, it simply uses $(info) to print the output, otherwise it uses $(error), which effectively makes Make stop.
Note that the indentation in the ifeq ... endif is done with spaces. If tabs are used, Make thinks you're trying to invoke a command and complains about it.
You should use a regular target to create BuildRules.mk:
BuildRules.mk: collect_sources.py
python $< >$#
include BuildRules.mk
This is the standard trick to use when automatically generating dependencies.
Fixing https://stackoverflow.com/a/226974/192373
.PHONY: BuildRules.mk
BuildRules.mk: collect_sources.py
echo Generating build rules...)
python $< >$#
$(MAKE) -f BuildRules.mk
Make sure you're not invoking make/gmake with the -k option.

Resources