How to comment sh scripts in Make - bash

Make's semantics force complex sh scripts to be broken up using \. Make turns those recipes into (arbitrarily long) one-liners. Therefore, I can't use # for comments. The Make function $(info my comment goes here) works, but the comment is printed even when the recipe itself isn't invoked.
I would like to have something like rem "string" in sh.
What I have been using so far is $(call rem, my comment goes here) in Make,
which I define as:
rem = $(if,$(1))
But I'd like to not reinvent the wheel if something already exists and I just missed it. In particular, in sh I'd like something that preserves pipes, or at the very least stdout.

You can use the shell's do-nothing operator, which is :. So like:
all:
#echo "hi" ; : this is a comment ; echo "there"
Just be aware that the shell does expand these, so if you want to use special characters like quotes, backticks, etc. you should escape them from the shell if you're worried they'll cause problems.
ETA:
If you want something that won't interfere with shell behaviors like pipelines, you can't do that with any shell construct; all possible methods will cause syntax errors in the shell.
You'll have to use a make construct to force make to get rid of the text before it invokes the shell. The example you have kind of works, but not for the reason you think. If you really wanted to use a user-defined function rem then to run it you'd need to invoke call, as in:
$(call rem, my comment goes here)
What your method is doing is, since there is no function rem defined, it's looking up the variable named rem my comment goes here and that variable doesn't exist, so it expands to the empty string. You can see this for yourself by having your rem function actually invoke $(info ...) or something: it will never be printed.
So you can continue to use the $(rem ...) syntax but you might as well remove the rem = $(if,$(1)) since it's not used anyway, and will just confuse people. Or you could use something smaller like $(: my comment goes here) or whatever. Really you can put anything in there as long as it doesn't expand to a valid variable name.
I should point out that this is not necessarily future-proof; since variable names containing whitespace are not allowed anymore in make, it's possible that make will decide to handle a variable reference containing whitespace in a different way, sometime in the future.

Related

remove Quotes from String in Windows Terminal

I want to retrieve some Values from AWS and use them afterwards in my Makefile. But for that purpose I need to remove the quotes.
I know different ways how to do that in Linux but not in Windows. The only hint I found is this: https://ss64.com/nt/syntax-dequote.html
But all my attempts to use it with my code have not worked so far.
The Code looks like this:
aws/project_name:
# $(eval PROJECT = $(shell aws ssm get-parameter --name "$(PROJECT_PARAMETER)" --query Parameter.Value))
#set PROJECT = %~1
# echo $(PROJECT)
When I run it the result is:
"MyProject"
Can someone give me hint pls how to strip the Double-quotes?
$(eval ) is a make function, not something which is evaluated as part of a shell script like your snippet suggests. Moreover you need to concatenate recipe shell lines with backslash, otherwise each line is executed in a separate shell process, thereby losing access to any variable instantiated in previous lines.
Another issue is the use of $(PROJECT) without quoting: make will replace this with its internal PROJECT variables' value before it goes to evaluate even the first line (see below) of your recipe. If you want to access the shell variable in this line, quote every use of $ with $$.
To answer your original question for replacement of "": $(patsubst "%",%,$(PROJECT)) would do that - you can wrap the whole $(shell ...) in it. but I suspect your troubles with what you are trying to achieve will not end there.
It is rather complicated to introduce make variable values in a running build in the right order. I'm stressing this again: At least you must keep in mind that make evaluates all of its own syntax (e.g. the $(eval ) call) always and entirely before it executes the recipe. Moreover, the order of evaluation of the rule is also split into two phases, see here: https://www.gnu.org/software/make/manual/html_node/Reading-Makefiles.html#Reading-Makefiles. If you don't understand the execution model of a makefile you will not be able to write controlled advanced scripting code in it.
One can do advanced scripting with make like you are trying, but I recommend a strict architectural approach for such makefiles - ad-hoc writing them will likely lead to chaos.
The current file could be written as:
aws/project_name: ;
# ';' reliably separates recipe lines from target & prerequisites
# we still need to $(eval) PROJECT because it is only accessible for other recipes this way
$(eval PROJECT = $(patsubst "%",%,$(shell aws ssm get-parameter --nam`enter code here`e "$(PROJECT_PARAMETER)" --profile enchomepage --query Parameter.Value)))
#echo $(PROJECT)
later_target: aws/project_name
# this should work now:
#echo $(PROJECT)
...but beware, this is exactly the part where such makefiles become hard to trace, becaus now you need to mentally follow the evaluation order, take care that variables used downstream are really $(eval )'ed upstream (they won't if the recipe isn't executed) thereby having sidestepped the usual contract which other programmers expect from a makefile.

In makefile, how to store multi-line shell output in variable

I have a shell command where it outputs multiple lines. I want to store it in a variable in makefile for later processing in the target.
A simplified example:
I have this file called zfile1
#zfile1
some text
$echo 123
more text
$$$#&^$
more text
The makefile:
a:
#$(eval v1 = $(shell cat zfile1))
# need to process the variable here, example:
#echo "$(v1)"
# I want to prevent expansion of values in the file and print in multi-line
If you have GNU make 4.2 or above you can use the $(file <zfile1) function. See https://www.gnu.org/software/make/manual/html_node/File-Function.html
If you don't have a new-enough version of GNU make, you can't do it. Of course in your example there's no real need to put the contents of the file into a make variable at all: you can just have your recipe use the file itself. But maybe your real use-case isn't so simple.
ETA
You should never use either the make function eval or the make function shell in a recipe [1].
You can just write:
v1 := $(file <zfile1)
.ONESHELL:
a:
#echo "$(v1)"
You must have the .ONESHELL because otherwise each line of the recipe (after it expands into multiple lines) is considered a separate recipe line. Note that .ONESHELL is in effect for the entire make process so could cause other recipes to break if they rely on each line being invoked in a different shell.
Another option is to export the result into the environment, and use a shell variable like this:
export v1 := $(file <zfile1)
a:
#echo "$$v1"
There are probably better ways to do it but since you've only given us this example and not what you really want to do, that's about all we can say.
[1] There are times where it can be useful but if you have a sufficiently sophisticated requirement to need this you'll probably know how to use it.
I think you're making things too complicated.
Start by writing your recipes as proper self-contained shell scripts.
You can then either store the whole script in a file and run it from make, or you can include it directly in your makefile as a single logical line, as in the following:
a:
#v1=$$(< zfile1); \
echo $$v1
Note the need to "escape" the dollar sign by repeating it.
You could also use global make variables, depending on the actual logic of your real-world use.

Multi-line define in GNU make

I'm trying to make sense out of the multi-line define directive of GNU make and I cannot. Example:
define A
1
2
endef
all:
#echo W=$(word 1,$(A))
Running make produces a result I have expected the least:
W=1
make: 2: Command not found
make: *** [all] Error 127
It appears that part of $(A) has spilled outside the $(word) function.
Is it a bug or intended behavior? If the "spill" is intentional, how does it really works?
P.S. GNU make v3.81 on Linux/x64
The thing to remember here is that make stores each recipe as a single recursive variable. At the point that make decides that it must run your recipe, it expands that variable. Make then passes each line in the resulting expansion to a separate shell, stopping if any of those shell executions return an error.
In your example, before running anything make expands #echo W=$(word 1,$(A)).
$(A) becomes 1¶2 (dunno what this looks like on your browser, but I'm using ¶ to represent a newline character)
Now, 1¶2 is a single word as far as make is concerned, so $(word 1,1¶2) naturally expands to 1¶2 (can you see where this is going yet?)
This leaves make with the string #echo W=1¶2. Make dutifully passes the first line of this to the shell (without the # as that is special to make). The shell executes echo W=1.
make executes 2 in a new shell.
The second shell complains that it can't find the command 2.
So, yes, expected behaviour.
[Warning: slight simplification in the above where I gloss over the bit where make is able to elide the shell and invoke the command itself if the string has no shell metacharacters in it]
The $(word) function is splitting on spaces. Not whitespace, spaces.
There are no spaces in your A macro so nothing gets split.
Add a trailing space on the 1 line or a leading space on the 2 line and you get your expected behaviour.
This is consistent across GNU make 3.81, 3.82, 4.0, and 4.1 in some quick testing here.
The reason you see the "spill" as you called it is because of how the define is expanded. It is expanded literally, newline and all. (Think template expansion.)
So make expands the define into the call to $(word 1,...) then expands that result (the whole define including the newline) into the recipe template and ends up with two lines that it executes as the recipe.
Consider a macro like this:
define somecommands
echo foo
echo bar
echo baz
endef
all:
$(somecommands)
What would you expect to happen here? How many lines is the body of all? How many shells are run here? What commands are executed? The answer is three lines, three shells and three echo commands.
If the newlines weren't counted then you would effectively run echo foo echo bar echo baz in one command and get foo echo bar echo baz as output instead of the expected (and far more useful) foo, bar, and baz on three different lines.

Makefile define variable using if

I'm trying to do something like it
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi;
But if i call in the next line the variable it don't work.
If i define it outside the if it works perfect.
Someone can help me?
As everyone is saying, you haven't given us enough information. But I'll make a guess. You want to set this variable conditionally, then use it elsewhere in the makefile, and in other makefiles which include this one.
The trouble is that you are trying to use shell syntax. In a command this will work (if the syntax is correct), but the value will apply only in that command. Outside commands, shell syntax is just wrong and will cause an error, malfunction, or be ignored depending on exactly what you do.
Try this in the makefile, outside of any rule (that is, not in the recipe for any particular target):
ifeq (1,1)
COMPILER_CMD = -fPic
endif
$(info $(COMPILER_CMD))
If that works, then you can try to adapt it to do whatever it is you're actually trying to do.
Each line in the Makefile is executed separately in a new shell process, so that's why changes you made to the environment are not propagated to next line.
You can combine both lines into one long one to achieve what you want. You probably have something like this in you Makefile:
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi;
echo $COMPILER_CMD
You want to add the line continuation backslash to the line before echo:
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi; \
echo $COMPILER_CMD
I'm assuming that the example you show is the recipe for some rule. By the syntax here it looks like you're trying to set a make variable COMPILER_CMD from within a recipe based on the value of some shell boolean test, which is of course impossible. You have to be very clear in your mind how make works: make is not interpreting the recipes you write, in any way. Make is simply passing those recipes to another program (the shell) and the other program is interpreting those commands. Thus, you can't change the behavior of make, including setting make variables, from within a recipe: that recipe is being run in a completely different program.
As others have said, you don't give enough information about what you REALLY want to do, at a higher level, for us to give a complete solution. Having a boolean like 1==1 doesn't give any hint whatsoever as to why you're doing this. Also your shell syntax contains syntax errors, so we can tell you didn't actually cut and paste this from a real, working example.
You can, as piokuc implies, use a shell variable COMPILER_CMD (you have to remove the whitespace around the = to make it a shell variable assignment) but that value takes effect only while that one recipe line is running. For the next recipe line a new shell is started and any values set in the previous shell are lost:
all:
# if [[ 1 == 1 ]]; then COMPILER_CMD=-fpic; fi; \
echo COMPILER_CMD=$$COMPILER_CMD
# echo COMPILER_CMD=$$COMPILER_CMD
will give:
COMPILER_CMD=-fpic
COMPILER_CMD=

How do I process extremely long lists of files in a make recipe?

Because GNU make allows variables to be as large as memory allows, it has no problem building massive dependency lists. However, if you want to actually use these lists of files in a recipe (sequence of shell commands for building a target), you run into a problem: the command might exceed the shell's command line length limit, producing an error such as "Argument list too long".
For example, suppose I want to concatenate several files contained in the list $(INPUTS) to produce a file combined.txt. Ordinarily, I could use:
combined.txt: $(INPUTS)
cat $^ > $#
But if $(INPUTS) contains many thousands of files, as it does in my case, the call to cat is too long and fails. Is there a way to get around this problem in general? It's safe to assume that there exists some sequence of commands that have identical behaviour to the one enormous command -- in this case, a series of cat commands, one per input file, that use >> to append to combined.txt would work. But how can make be persuaded to generate those commands?
In looking for the answer, about the best suggestion I could find was to break up the list into a series of smaller lists and process them using shell for loops. But you can't always do that, and even when you can it's a messy hack: for example, it's not obvious how to get the usual make behaviour of stopping as soon as a command fails. Luckily, after much searching and experimentation, it turns out that a general solution does exist.
Subshells and newlines
make recipes invoke a separate subshell for each line in the recipe. This behaviour can be annoying and counterintuitive: for example, a cd command on one line will not affect subsequent commands because they are run in separate subshells. Nevertheless it's actually what we need to get make to perform actions on very long lists of files.
Ordinarily, if you build a "multiline" list of files with a regular variable assignment that uses backslashes to break the statement over multiple lines, make removes all newlines:
# The following two statements are equivalent
FILES := a b c
FILES := \
a \
b \
c
However, using the define directive, it's possible to build variable values that contain newlines. What's more, if you substitute such a variable into a recipe, each line will indeed be run using a separate subshell, so that for example running make test from /home/jbloggs with the makefile below (and assuming no file called test exists) will produce the output /home/jbloggs, because the effect of the cd .. command is lost when its subshell ends:
define CMDS
cd ..
pwd
endef
test:
$(CMDS)
If we create a variable that contains newlines using define, it can be concatenated with other text as usual, and processed using all the usual make functions. This, combined with the $(foreach) function, allows us to get what we want:
# Just a single newline! Note 2 blank lines are needed.
define NL
endef
combined.txt: $(INPUTS)
rm $#
$(foreach f,$(INPUTS),cat $(f) >> $#$(NL))
We ask $(foreach) to convert each filename into a newline-terminated command, which will be executed in its own subshell. For more complicated needs, you could instead write out the list of filenames to a file with a series of echo commands and then use xargs.
Notes
The define directive is described as optionally taking a =, := or += token on the end of the first line to determine which variable flavour is to be created -- but note that that only works on versions of GNU make 3.82 and up! You may well be running the popular version 3.81, as I was, which silently assigns nothing to the variable if you add one of these tokens, leading to much frustration. See here for more.
All recipe lines must begin with a literal tab character, not the 8 spaces I have used here.

Resources