How do you create and use multi-line string variables in NMake? - makefile

I am writing an NMake build script for Windows. I want to write a multiline string in the NMake makefile.
The approach suggested in the following question works in GNUMake, but when I use it in NMake, I get syntax errors indicating that NMake is trying to parse every line as syntax rather than as a literal string variable.
Is it possible to create a multi-line string variable in a Makefile
Eventually what I want to do with the multiline string is to print it out to a file.
How do you do this in NMake (create a multiline string and echo it to a file)?

NMake "inline files" seem to do the job for my use case:
#echo <<filename.txt
blah
blah
blah
<<KEEP
https://learn.microsoft.com/en-us/cpp/build/inline-files-in-a-makefile

Related

gnu make hash symbol in the middle of string

I'm trying to print out the perforce file version of the make file when it's executed. I'm using the $Id$ tag, which expands to $Id: //repository/path/check.make#6 $ or the like and I want to print //repository/path/check.make#6 to a file (currently using echo). I can't figure out how to get make to take the # as part of the string and not the beginning of a comment. I tried:
str1 = $(subst \#,\\\#,'$Id: //repository/path/check.make#6 $')
and other variations but I keep getting:
unterminated call to function `subst': missing `)'. Stop.
It would help if you provided a full example of what you want. I don't really understand why you're trying to subst a hash with a backslash hash. If you showed us a full example, including how you get the string and also what you want to do with the variable ar1, we could actually give you advice.
But, the way to use hashes in GNU make is to put them into a variable:
HASH := \#
$(info HASH = $(HASH))
That's all I can say without more info.
ETA
Yes, I'm very familiar with keyword expansion... it originated with SCCS/RCS back in the day :).
I see, you mean, you want to put the $Id$ into your makefile, then when your makefile is checked out the value will be replaced. That wasn't clear to me.
I'm sorry to say that what you want to do is close to impossible. The problem is that you can't escape the value in the makefile because you're not writing the value into the makefile, Perforce is. And Perforce is not escaping it.
You have only two options that I can see:
First, don't try to put this into a make variable. There are many ways to do this, depending on what you really want. One way is to create a header file that contains const char* foo = "$Id$"; and let that be replaced. If you really want the ID of the makefile, but you only need it within a certain recipe, you can put it directly into that recipe:
myrecipe: ; echo '$$Id$$'
(I'm not actually sure the $$ trick here will work, it depends on how Perforce replaces things... if it doesn't you can use echo '$Id$x' you'll lose the dollar signs but keep the rest).
The only other option is to upgrade your version of GNU make to the latest (4.3). In that release, some broken handling of hash characters in the $(shell ...) function was fixed, which means you can use:
var1 := $(shell echo '$$Id$$')
and it will work (same caveats, and solutions, for $$ here as above).
Maybe I didn't get you correctly but the following works for an outside actor replacing $Id$ without escaping:
define PERFORCE_ID
$Id$
endef
PERFORCE_ID := $(word 2,$(value PERFORCE_ID))
$(info $(PERFORCE_ID))
As a test, I simply put in the text substitution from Perforce myself:
define PERFORCE_ID
$Id: //repository/path/check.make#6 $
endef
PERFORCE_ID := $(word 2,$(value PERFORCE_ID))
$(info Perforce id is: $(PERFORCE_ID))
Output:
Perforce id is: //repository/path/check.make#6
You can't have an unescaped literal # in a make assignment and not have it be interpreted as a comment character. But as a hack, you can have the shell extract this token from the current Makefile.
# $Id: //repository/path/check.make#6 $
str1 := $(shell sed '/[$$]Id[$$:]/!d;s/^\# [$$]Id: \(.*\) [$$].*/\1/' Makefile)
The sed script looks for the $Id$ or $Id: token in the Makefile itself by way of a regex which doesn't match itself; the doubled dollar sign is how you put a literal dollar sign in a Makefile. It extracts the contents of the field, and make assigns the output to str1. Because there is no literal # in the code which assigns the variable, no further escaping is necessary.
Demo: https://ideone.com/hWjnCp
This requires GNU Make, but that's apparently what you are using already. (Please tag such questions explicitly as gnu-make.)

How to comment a line within a Makefile define directive?

I want to comment one or more line(s) within a define directive in a Makefile so as the line is ignored when the directive is expanded. The goal is to place the commented line as a hint for the users of my Makefile to show an example of what could be into the define directive. The directive is expanded into a target.
In other words, I want that Makefile
define ECHO_FOO =
# #echo foo
endef
all:
#echo Before call
$(ECHO_FOO)
#echo After call
.PHONY: all
to have the same behavior than this one :
define ECHO_FOO =
endef
all:
#echo Before call
$(ECHO_FOO)
#echo After call
.PHONY: all
The issue is that the first Makefile gives me the following error :
process_begin: CreateProcess(NULL, ##echo foo, ...) failed.
make (e=2): The system cannot find the file specified.
Makefile:6: recipe for target 'all' failed
make: *** [all] Error 2
The GNU make:Makefile contents page states that :
Within a define directive, comments are not ignored during the definition of the variable, but rather kept intact in the value of the variable. When the variable is expanded they will either be treated as make comments or as recipe text, depending on the context in which the variable is evaluated.
But this doesn't explain in which specific case the # symbol is treated as a make comment or as a recipe text (which seems to be the problem I meet).
Can someone tell me how to have the # symbol treated as a comment mark in a define function ?
I have already tried all of the following lines with the idea of escaping the # symbol or changing the indentation but none of them gave me a correct output :
##echo foo
##echo foo
###echo foo
###echo foo
\##echo foo
\##echo foo
/##echo foo
/##echo foo
I'm running MinGW make 3.82 on Windows but I have already tried other implementations of make v3.82.90 and 4.1.
There's no way to do what you're asking for directly. The contents of the variable are expanded in a recipe context, so no matter what the variable expands to it will be considered part of the recipe and whatever characters are there will be passed to the shell.
Note you can use : in UNIX shells as well as Windows command.com, because : is the shell no-op operator. You have to add a space after it though otherwise it will try to run the command :echo which is not a valid command. However, further note that the shell will still expand the line! This means that if you use backquotes etc. then those still are expanded. Also note that since it's a statement, semicolon will stop it. So for example:
define ECHO_FOO
: echo hi `echo there 1>&2` ; echo bye
endef
all: ; #$(ECHO_FOO)
Here, the hi won't be printed because the echo command is not run, but the backticks are still expanded so there will be printed (to stderr) and the semicolon ends the "no-op" command so bye will also be printed.
If your commands are simple enough then : will work, but if they're that simple one wonders why you're using define...
Another option is just to override the variable, rather than comment it out:
define ECHO_FOO =
#echo foo
endef
ECHO_FOO =
ETA:
In the comments you affirm that the command is simple. I don't quite know what you mean by could be expanded by the final user or why that makes a difference.
But what I was alluding to is that if you have a simple command you can just write:
ECHO_FOO = echo hi
and not use define. define is only needed for complicated commands: really it's only required for commands that contain un-escaped newlines.
And, if you write:
ECHO_FOO =# echo hi
then you ARE commenting out the content of the variable using make comments, not shell comments, so it will work everywhere.
On Windows, you can use : as a comment character. The traditional comment keyword in MS-DOS is REM (as in "remark").

Writing contents of makefile (>131000 chars) variable to a file?

How can I write the contents of a makefile variable to file, without invoking a shell command?
The problem is that the contents of the variable is possible longer than the shell allows for a command (i.e. longer than MAX_ARG_STRLEN (131072) characters).
In particular, in a makefile I have a variable containing a long list of filenames to process (including their absolute pathes for out-of-source builds). Now I need to write those filenames to a (temporary) file, which I can then pass to another command.
So far, we had a rule like ($COLLATED_FILES is the variable containing the paths):
$(outdir)/collated-files.tely: $(COLLATED_FILES)
$(LYS_TO_TELY) --name=$(outdir)/collated-files.tely --title="$(TITLE)" \
--author="$(AUTHOR)" $^
This breaks if COLLATED_FILES is longer than about 130000 characters, we get the error message:
make[2]: execvp: /bin/sh: Argument list too long
As a solution, we are now trying to write the contents of the variable to a file and use that file in the $(LYS_TO_TELY) command. Unfortunately, I have not yet found a way to do this without invoking the shell.
My attempts include:
$(outdir)/collated-files.list: $(COLLATED_FILES)
echo "" > $#
$(foreach f,$^,echo $f >> $#;)
But this also invokes all echo commands at once in a shell, so the shell command is just as long.
Is there any way to write the contents of $(COLLATED_FILES) to a file on disk without passing them on the command line to a shell command?
I also searched whether I could pipe the contents of the variable to the shell, but I couldn't find anything in that direction, either...
Assuming you are using GNU Make, there is the file function!
https://www.gnu.org/software/make/manual/html_node/File-Function.html
$(file op filename,text)
where op is either > or >>.
This requires GNU Make 4.0+
You could move whatever makefile code you use to build up the value of COLLATED_FILES to a trivial helper makefile, then invoke make recursively from your original makefile and use trivial shell redirection to capture the stdout of the recursive make invocation -- basically using make as a rudimentary text-processing tool in that context. For example, create a makefile called get_collated_files.mk with these contents:
COLLATED_FILES=abc
COLLATED_FILES+=def
COLLATED_FILES+=ghi
# ... etc ...
# Use $(info) to print the list to stdout. If you want each filename on a
# separate line, use this instead:
#
# $(foreach name,$(COLLATED_FILES),$(info $(name)))
$(info $(COLLATED_FILES))
all: ;##shell no-op to quell make complaints
Then, in your original makefile:
collated-files.list:
$(MAKE) -f get_collated_files.mk > $#
$(outdir)/collated-files.tely: collated-files.list
$(LYS_TO_TELY) --name=$(outdir)/collated-files.tely --title="$(TITLE)" \
--author="$(AUTHOR)" --filelist=collated-files.list
This will be quite a lot more efficient than using hundreds or thousands of individual echo invocations to append to the file one path at a time.
EDIT: One final option, if you really want to have each filename on a separate line, and you have a lot of control over how COLLATED_FILES is defined:
define NL
endef
COLLATED_FILES=abc
COLLATED_FILES+=$(NL)def
COLLATED_FILES+=$(NL)ghi
$(info $(COLLATED_FILES))
all: ;##no-op
This approach allows you to again use just one call to $(info), if that's important to you for some reason.
Here's a patch to gnu make that lets you directly write a variable into a file. It creates a new 'writefile' function, similar to the existing 'info' function, except it takes a filename argument and writes to the file:
https://savannah.gnu.org/bugs/?35384
It looks to me as if you should rethink your build design-- surely there's a better way than letting a variable get this big. But here's a way to do it:
# Make sure this doesn't collide with any of your other targets.
NAMES_TO_WRITE = $(addprefix write_,$(COLLATED_FILES))
collated-files.list: $(NAMES_TO_WRITE)
write_blank:
echo "" > collated-files.list
.PHONY: $(NAMES_TO_WRITE)
$(NAMES_TO_WRITE) : write_% : write_blank
echo $* >> collated-files.list

Defining and using a build timestamp for GNU Make on Windows

I am trying to build some software on Windows using both GNU make 3.81 and an ancient make distributed with Wind River Tornado (make 3.76).
So far, I managed to capture the date from windows:
NOW=\"$(shell cmd /C date /T) $(shell cmd /C time /T)\"
but when I am passing it on the compiler
CFLAGS = ... -DBUILD_TIMESTAMP=$(NOW) ...
I am getting build errors because of the spaces and colons and slashes in the timestamp. If I echo the $(NOW) variable, it is properly quoted, but when I echo the $(CFLAGS) variable, the quotes disappear.
You want to quote the variable for the shell (so it isn't subject to word splitting) and quote it again for C (so when it's substituted by cpp, you have a string literal). Try this:
NOW := "\"$(shell cmd /C date /T) $(shell cmd /C time /T)\""
Note also that I'm using := instead of =. Unless your old make doesn't support it, use :=, which evaluates the substitution at the point of definition, not the point of expansion. Using = will make it call those two shell commands twice every time you try to compile a file. Not so good for performance.
I think you should set:
CFLAGS = ... -DBUILD_TIMESTAMP="$(NOW)" ...
i.e. add quotes.
There is another "hacky" solution - removing the spaces in $(NOW) --> Try:
empty:=
space:= $(empty) $(empty)
NOW:=$(subst $space,_,$(NOW))
You might find the problem easier to debug if you eliminate the trickiness of passing quoted strings on a compiler commandline, like so:
.PHONY: force
timestamp.h: force
echo "#define BUILD_TIMESTAMP \"`cmd /c date /t` `cmd /c time /t`\"" > $#
...and #include "timestamp.h" in your code. This way you only have to worry about the quoting behaviour of your shell, not of make and the compiler.
It's been a while, but I think I remember that Tornado ships with a csh-style shell, which handles quoting, er, differently than the more common Bourne shells. It also includes a TCL interpreter, which you could use instead by creating a timestamp.tcl with content like
puts "#define BUILD_TIMESTAMP \"[clock format [clock seconds] -format {%D %T}]\""
and writing
timestamp.h: force
tclsh timestamp.tcl > $#
in the makefile.

Firefox make file

I want to backtrace the makefile for firefox so that I can get the final gcc command that is used to compile the c++ files. How can I do that?
If you find a line in there that begins with "# $(CXX)" or "# g++", then change the line to "$(CXX)" or "g++" -- in other words, delete the "#" symbol from the line. When an "#" symbol appears at the beginning of a command in a Makefile, it causes Make to not echo the command before executing it. Deleting the "#" symbol will cause the expanded form of the line to be echoed before the command is invoked.
I haven't looked at Firefox's makefile, so it is more than possible that they are using predefined pattern rules for building the code, in which case you won't see any lines beginning with "$(CXX)" . If that is the case, you will need to override the rules, so that the default build rules echo the commands before executing them.
For more information on overriding Makefile pattern build rules, see this link:
http://www.gnu.org/software/make/manual/make.html#Pattern-Rules
The usual stunt for this is to replace gcc with a program that reads the gcc command line,
store it in some log file so it can be inspected, and then launches gcc with the command line. You can do this by replacing "gcc.exe" in your development directories by this stepping stone program.
Here's the make rule that compiles C++ files:
http://hg.mozilla.org/mozilla-central/annotate/c1ab8650e0ce/config/rules.mk#l1391
If all you want to do is replace the compiler, you can (in your mozconfig, or on the configure commandline) set CXX="whatever".
Can you redirect the output of make to a file, then use a text editor to search for the line of interest?

Resources