How can I check syntax for Make but be sure I am not executing? - gnu-make

We work with Make files and want to create a precommit check in HG to check Makefile syntax. Originally, our check was just going to be
make -n FOO.mk
However, we realized that if a Makefile were syntactically correct but required some environment variable to be set, the test could fail.
Any ideas? Our default is to resort to writing our own python scripts to check for a limited subset of common Makefile mistakes.
We are using GNUmake.

$ make --dry-run > /dev/null
$ echo $?
0
The output is of no value to me so I always redirect to /dev/null (often stderr too) and rely on exit code. The man page https://linux.die.net/man/1/make explains:
-n, --just-print, --dry-run, --recon
Print the commands that would be executed, but do not execute them.
A syntax error would result in the sample output:
$ make --dry-run > /dev/null
Makefile:11: *** unterminated variable reference. Stop.

It is not a good idea to have makefiles depend on environment variables. Precisely because of the issue you mentioned.
Variables from the Environment:
... use of variables from the environment is not recommended. It is not wise for makefiles to depend for their functioning on environment variables set up outside their control, since this would cause different users to get different results from the same makefile. This is against the whole purpose of most makefiles.

References to an environment variable in the recipe need a $$ prefix so it is not that hard to find references to the pattern '[$][$][{] or the pattern [$][$][A-Z] which will find the direct references. A pretty simple perl filter (sed script) finds them all.
To find the indirect ones I would try the recipe with only PATH set and HOME set to /dev/null, and SHELL set to /bin/false. Make's macro SHELL is not the environment $SHELL, so you can get the recipes to run, you'll have to set SHELL=/bin/sh in the recipe file to run the command from the recipe. That should shake out enough data to help you find the depends.
What you do about the results is another issue.

Related

How to specify indent in jq config file?

Instead of always giving the --indent n flag on the command line, I would like to specify this in a config file (or library or module file – I'm not sure what the correct terminology is).
I tried putting various things in ~/.jq, such as indent: 4 or --indent 4, but they always gave me a compile error when I invoked jq. I looked at the manual but couldn't see any information on how to do this, nor find anything in the issues on GitHub.
Is it possible?
The ~/.jq file contains jq code such as function definitions. It's not a config file in the classical sense.
Most shells support aliases and you could set one yourself, e.g. in ~/.profile or ~/.bashrc/~/.bash_aliases with:
alias jq='jq --indent 4'
When invoked in an interactive shell, the alias will be resolved to the command including the option. Note however that aliases are ignored in shell scripts (i.e. non-interactive mode) by default.

How to find out where GNU make was started?

If I start (GNU) make with the -C option, it first cd into that directory and
$(CURDIR) will be set to that. I wonder if there is any mechanism to figure out in the makefile where make was originally invoked from?
That is, if I have a makefile in /some/location:
all:
#echo $(CURDIR)
#echo $(SOME_MAGIC_VARIABLE_I_AM_LOOKING_FOR)
and then I do:
$ cd /other/place
$ make -C /some/location
I would get
/some/location
/other/place
printed.
Thanks!
There is no such variable in make.
However, POSIX shells maintain and export a PWD variable to commands that they invoke. All environment variables are imported by make as make variables.
So, if you look in the make variable $(PWD) you will very likely find the original path that the shell that invoked make was using.

GNU-Make: how to copy files from source to destination

I know this is a basic question but I'm missing something fundamental about makefiles.
Take this simple rule/action:
doc: ${SRC_DIR}/doc/dir1/file1.pdf ${SRC_DIR}/doc/dir1/file2.pdf
cp $? ${DEST_DIR}/doc/
the first time I run it, it copies file1.pdf and file2.pdf to the destination/doc directory. Perfect. I'm expecting the next time I run it, for it to do nothing. The source files haven't changed, aren't they a dependency? But when I run I get :
cp : cannot create regular file ..... :Permission denied.
so, 2 questions:
1) Why is it trying to do it again? When I run make -d I see it eventually says: No need to remake target .../file1.pdf and .../file2.pdf but then
it says : must remake target 'doc'
If it doesn't need to make either pdf file, why does it need to make doc?
2) say the pdf files had changed in the source, they are read only though, so it gets the permission denied error. How do you get around this?
A make rule:
target: preqreq0 prereq1...
command
...
says that target needs to be (re)made if it does not exist or is older than
any of the prerequisites preqreq0 prereq1..., and that target shall be
(re)made by running the recipe command ....
Your rule:
doc: ${SRC_DIR}/doc/dir1/file1.pdf ${SRC_DIR}/doc/dir1/file2.pdf
cp $? ${DEST_DIR}/doc/
never creates a file or directory doc, so doc will never exist when
the rule is evaluated (unless you create doc by other means), so the recipe
will always be run.
The kind of target that I believe you want doc to be is a phony target,
but you are going about it wrongly. A reasonable makefile for the purpose would
be:
SRC_DIR := .
DEST_DIR := .
PDFS := file1.pdf file2.pdf
PDF_TARGS := $(patsubst %,$(DEST_DIR)/doc/%,$(PDFS))
.PHONY: doc clean
doc: $(PDF_TARGS)
$(DEST_DIR)/doc/%.pdf: $(SRC_DIR)/doc/dir1/%.pdf
cp $< $#
clean:
rm -f $(PDF_TARGS)
I recommend The GNU Make documentation
As for your second problem, how to overwrite "readonly" files, it is unrelated to make.
You cannot overwrite files to which you do not have write permission, regardless
of the means by which you try to do it. You must get write permission to any files
that you need to write to. It is a system administration matter. If you do not
understand file permissions you may find help at sister-site Unix & Linux
or serverfault

`rm` a file named '--help'?

How does one use rm to delete a file named '--help'? When I try, it just shows the help prompt.
I ended up opening a file browser to delete it.
Two approaches:
rm ./--help
rm -- --help
This latter approach is supported by many common UNIX tools (-- means "end of options" by convention, ie. that everything else will be a positional parameter), and is particularly handy in a script, when you don't know what data you'll be dealing with.
The rm command will accept '--' to tell it not to process any more options.
rm -- '--help'

unix command line execute with . (dot) vs. without

At a unix command line, what's the difference between executing a program by simply typing it's name, vs. executing a program by typing a . (dot) followed by the program name? e.g.:
runme
vs.
. runme
. name sources the file called name into the current shell. So if a file contains this
A=hello
Then if you sources that, afterwards you can refer to a variable called A which will contain hello. But if you execute the file (given proper execution rights and #!/interpreterline), then such things won't work, since the variable and other things that script sets will only affects its subshell it is run in.
Sourcing a binary file will not make any sense: Shell wouldn't know how to interpret the binary stuff (remember it inserts the things appearing in that file into the current shell - much like the good old #include <file> mechanism in C). Example:
head -c 10 /dev/urandom > foo.sh; . foo.sh # don't do this at home!
bash: �ǻD$�/�: file or directory not found
Executing a binary file, however, does make a lot of sense, of course. So normally you want to just name the file you want to execute, and in special cases, like the A=hello case above, you want to source a file.
Using "source" or "." causes the commands to run in the current process. Running the script as an executable gives it its own process.
This matters most if you are trying to set environment variable in current shell (which you can't do in a separate process) or want to abort the script without aborting your shell (which you can only do in a separate process).
The first executes the command. The second is shorthand for including a shell script inside another.
This syntax is used to "load" and parse a script. It's most useful when you have a script that has common functionality to a bunch of other scripts, and you can just "dot include" it. See http://tldp.org/LDP/abs/html/internal.html for details (scroll down to the "dot" command).
Running "runme" will create a new process which will go on its merry little way and not affect your shell.
Running ". runme" will allow the script "runme" to change your environment variables, change directories, and all sorts of other things that you might want it to do for you. It can only do this because it's being interpreted by the shell process that's already running for you. As a consequence, if you're running bash as your login shell, you can only use the "." notation with a bash script, not (for example) a binary on C shell script.

Resources