How do I safe git information in a file using GNU-make variables - gnu-make

I want to save some git information in a textfile, which I want to put in the src folder of my project.
In case branch Master is checked out, I only want the date of the latest commit. If any other branch is checked out I want the date and the name of the branch like so:
date-branchname
This is my code:
src/version2.txt:
DATE=$(shell git log -1 --date=format:"%Y.%m.%d" --format="%ad")
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
ifeq ($(BRANCH), 'Master')
$(DATE) > $#
else
DATE+='-'
DATE+=$(BRANCH)
$(DATE) > $#
endif
I'm new to GNU make and quite confused with its syntax.
I assume my ifeq/else blocks are working fine, since I checked printing a dummy text to the version.txt file while having the Master or some other branch checked out.
I also saw my commands to get the date or the branch are ok, since I can put them to the textfile like so:
git log -1 --date=format:"%Y.%m.%d" --format="%ad" > $#
Only when I want to use variables, it seems the variable is empty, for example
$(DATE) > $#
seems to print an empty string to the textfile.
Also, I don't know if my way of creating the DATE-BRANCH output is correct at all.
I've spent ages trying and would really appreciate some help.
Thanks

You cannot create GNU make variables inside recipes and use them later in the GNU make file because GNU make first parses whole file and then runs recipes
You cannot use GNU make ifeqs inside recipes
% in command lines may cause problems in Windows, so I've added a workaround for it in the following code (skip it if your Makefile should work only for Unix-like systems)
' should not be used in ifeqs constants (GNU make reads it literaly)
src/version2.txt does not depend on anything and therefore will not be regenerated by GNU make (if it already exists), consider .PHONY for the target
Try this:
BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
ifeq ($(OS),Windows_NT)
P:=%%
else
P:=%
endif
.PHONY : src/version2.txt
ifeq ($(BRANCH),master)
src/version2.txt :
git log -1 --date=format:"$PY.$Pm.$Pd" --format="$Pad" >$#
else
src/version2.txt :
git log -1 --date=format:"$PY.$Pm.$Pd" --format="$Pad-$(BRANCH)" >$#
endif
Note that BRANCH will be computed each time you run make even if the src/version2.txt should not be regenerated.

Related

How to make a single makefile that applies the same command to sub-directories?

For clarity, I am running this on windows with GnuWin32 make.
I have a set of directories with markdown files in at several different levels - theoretically they could be in the branch nodes, but I think currently they are only in the leaf nodes. I have a set of pandoc/LaTeX commands to run to turn the markdown files into PDFs - and obviously only want to recreate the PDFs if the markdown file has been updated, so a makefile seems appropriate.
What I would like is a single makefile in the root, which iterates over any and all sub-directories (to any depth) and applies the make rule I'll specify for running pandoc.
From what I've been able to find, recursive makefiles require you to have a makefile in each sub-directory (which seems like an administrative overhead that I would like to avoid) and/or require you to list out all the sub-directories at the start of the makefile (again, would prefer to avoid this).
Theoretical folder structure:
root
|-make
|-Folder AB
| |-File1.md
| \-File2.md
|-Folder C
| \-File3.md
\-Folder D
|-Folder E
| \-File4.md
|-Folder F
\-File5.md
How do I write a makefile to deal with this situation?
Here is a small set of Makefile rules that hopefuly would get you going
%.pdf : %.md
pandoc -o $# --pdf-engine=xelatex $^
PDF_FILES=FolderA/File1.pdf FolderA/File2.pdf \
FolderC/File3.pdf FolderD/FolderE/File4.pdf FolderD/FolderF/File5.pdf
all: ${PDF_FILES}
Let me explain what is going on here. First we have a pattern rule that tells make how to convert a Markdown file to a PDF file. The --pdf-engine=xelatex option is here just for the purpose of illustration.
Then we need to tell Make which files to consider. We put the names together in a single variable PDF_FILES. This value for this variable can be build via a separate scripts that scans all subdirectories for .md files.
Note that one has to be extra careful if filenames or directory names contain spaces.
Then we ask Make to check if any of the PDF_FILES should be updated.
If you have other targets in your makefile, make sure that all is the first non-pattern target, or call make as make all
Updating the Makefile
If shell functions works for you and basic utilities such as sed and find are available, you could make your makefile dynamic with a single line.
%.pdf : %.md
pandoc -o $# --pdf-engine=xelatex $^
PDF_FILES:=$(shell find -name "*.md" | xargs echo | sed 's/\.md/\.pdf/g' )
all: ${PDF_FILES}
MadScientist suggested just that in the comments
Otherwise you could implement a script using the tools available on your operating system and add an additional target update: that would compute the list of files and replace the line starting with PDF_FILES with an updated list of files.
Final version of the code that worked for Windows, based on #DmitiChubarov and #MadScientist's suggestions is as follows:
%.pdf: %.md
pandoc $^ -o $#
PDF_FILES:=$(shell dir /s /b *.md | sed "s/\.md/\.pdf/g")
all: ${PDF_FILES}

makefiles with multiple targets

I have a simple makefile that runs an R program that generates 3 csv files. I used this SO answer:https://stackoverflow.com/a/3077254/4564432
But I don't feel this is working quite right.
.PHONY: all one clean
all: one
clean:
-rm *.csv
one: csv1.csv csv2.csv csv3.csv
csv1%csv csv2%csv csv3%csv: myProgram.R
R CMD BATCH $<
When I run make initially it works fine, runs my program, and generates my output. My understanding is that if I rerun make, without changing myProgram.R it should know not to run anything, but I find that with the above makefile that it reruns everything anytime I run make regardless of the time-stamp.
Any help would be appreciated.
edit:
using GNU MAKE 3.79.1
Answer:
I realize that my output, the csvs, were going into a different folder so I needed to do one: myFolder/csv1.csv myFolder/csv2.csv etc.
I've changed your makefile to not require R but to simply perform the operations that I'm assuming R is performing, and I've tried it with GNU make 3.79.1 and the latest version, and it works as expected:
.PHONY: all one clean
all: one
clean: ; -rm *.csv
one: csv1.csv csv2.csv csv3.csv
csv1%csv csv2%csv csv3%csv: myProgram.R ; touch csv1$*csv csv2$*csv csv3$*csv
When I run this it works fine:
$ make-3.79.1
touch csv1.csv csv2.csv csv3.csv
$ make-3.79.1
make: Nothing to be done for `all'.
$ touch myProgram.R
$ make-3.79.1
touch csv1.csv csv2.csv csv3.csv
$ make-3.79.1
make: Nothing to be done for `all'.
This means that something is not right about either your environment (as rveerd suggests maybe your myProgram.R has a timestamp from the future) or else the "R" command is not creating all of those output files, or it's not updating the timestamps on those output files correctly.
After you run make you should use ls -al *.R *.csv and verify that all the files are created and that they have the expected timestamps.

writing a recursive make recipe with prerequisite on parent directory

I am trying to write a recursive make recipe. In this recipe, each target is dependent on a file with an equal name on the parent directory. A minimal (non-working) example:
foo/.dirstamp:
mkdir $(dir $#)
touch $#
.SECONDEXPANSION:
%/.dirstamp: $$(dir $$*).dirstamp
mkdir $(dir $#)
touch $#
With this example, I would expect make foo/bar/qux/lol/.dirstamp to generate the whole directory tree (if it does not exist), touching all .dirstamp files along the way. However, it does not work:
$ ls # note that there is nothing, make is meant to create the dir tree
Makefile
$ make --debug=v foo/bar/qux/lol/.dirstamp
GNU Make 4.0
[...]
Reading makefiles...
Reading makefile 'Makefile'...
Updating goal targets....
Considering target file 'foo/bar/qux/lol/.dirstamp'.
File 'foo/bar/qux/lol/.dirstamp' does not exist.
Finished prerequisites of target file 'foo/bar/qux/lol/.dirstamp'.
Must remake target 'foo/bar/qux/lol/.dirstamp'.
make: *** No rule to make target 'foo/bar/qux/lol/.dirstamp'. Stop.
It works fine as long as the recursive recipe only needs to be expanded twice, e.g., make foo/bar/.dirstamp works fine.
How can this work for an arbitrary number of levels? How can I handle a recursive expansion for the target and prerequisites names?
Note: my real problem is that the prerequisites of my recipes are in a root
directory different from the target so I am using the recipe above to duplicate the directory tree. I know about mkdir -p which seems to work fine in GNU systems. I am still interested on knowing how I would solve the recursion problem for arbitrary levels. which no longer works because part of the team is using Mac and mounting this directories over smb.
More details on the actual problem: prerequisites are in data/x/y/z while targets go into results/x/y/z. However, the results directory tree does not exist and needs to be created as needed. To solve this, I made the creation of parent directories an order-only prerequisite (via the .dirstamp files on my minimal example above).
can't copy data into results, that's several TB of data;
can't have the targets created in data, that's read-only;
can't use mkdir -p because the results directory will not be local, mounted over smb, and others may use non-GNU systems;
After an hint from #EtanReisner on the question:
make won't apply a rule more than once. That's a built-in (intentional) limitation. Without working around that with manual recursion or manually building the set of targets and using a static pattern rule (which may or may not actually work I'm not sure) there's not much you can do about this.
I worked up this solution:
RESULT_DIRS := $(patsubst data/%, results/%, $(shell find data/* -type d -print))
DIRSTAMPS := $(addsuffix /.dirstamp, $(RESULT_DIRS))
results/.dirstamp:
mkdir $(dir $#)
touch $#
.SECONDEXPANSION:
$(DIRSTAMPS): $$(dir $$(patsubst %/.dirstamp, %, $$#)).dirstamp
mkdir $(dir $#)
touch $#
It will duplicate the data directory tree in results as the dirstamp files are required. They are required by making them prerequisites of the other recipes (note the | which makes them order-only prerequisites):
results/%/foo.analysis: data/%/foo.data | results/%/.dirstamp
$(SOME_ANALYSIS_PROGRAM) $^ > $#

How to write a makefile executing make one directory level up

Can I write a wrapper makefile that will cd one level up and execute there make with all the command options I have given the wrapper?
In more detail:
Directory project contains a real Makefile with some different targets.
Directory project/resources contains the wrapper Makefile which should call Makefile in project.
When I am in my shell in directory project/resources, I execute
make TARGET
and the Makefile there just cds one directory up and calls
make TARGET
in the directory project.
Is this possible? And how?
You could use a very simple Makefile for all your sub-directories:
%:
$(MAKE) -C .. $#
% is a last resort match-anything pattern rule that will match any target... for which there is no implicit rule (GNU make has an incredibly large number of implicit rules). So, if none of your targets are covered by an implicit rule, this should work. Else you will have to tell make not to use the implicit rules it knows. This can be done (with GNU make) by calling make with the -r option:
cd project/resources
make -r <anything>
will call make in project for target <anything>. The main drawback is that the -r flag is passed to the sub-make and so the implicit rules will not apply neither in project, which can be a problem. If it is you can obtain the same effect by adding an empty .SUFFIXES target to theMakefile in project/resources:
.SUFFIXES:
%:
$(MAKE) -C .. $#
With my version of GNU make (3.82) it works like a charm and the sub-make has all the default implicit rules.
Yes, you can have a makefile which works for "any" target.
The GNU make manual discusses this in the Overriding Part of Another Makefile section:
Sometimes it is useful to have a makefile that is mostly just like another makefile. You can often use the ‘include’ directive to include one in the other, and add more targets or variable definitions. However, it is invalid for two makefiles to give different recipes for the same target. But there is another way.
In the containing makefile (the one that wants to include the other), you can use a match-anything pattern rule to say that to remake any target that cannot be made from the information in the containing makefile, make should look in another makefile. See Pattern Rules, for more information on pattern rules.
For example, if you have a makefile called Makefile that says how to make the target ‘foo’ (and other targets), you can write a makefile called GNUmakefile that contains:
foo:
frobnicate > foo
%: force
#$(MAKE) -f Makefile $#
force: ;
If you say ‘make foo’, make will find GNUmakefile, read it, and see that to make foo, it needs to run the recipe ‘frobnicate > foo’. If you say ‘make bar’, make will find no way to make bar in GNUmakefile, so it will use the recipe from the pattern rule: ‘make -f Makefile bar’. If Makefile provides a rule for updating bar, make will apply the rule. And likewise for any other target that GNUmakefile does not say how to make.
The way this works is that the pattern rule has a pattern of just ‘%’, so it matches any target whatever. The rule specifies a prerequisite force, to guarantee that the recipe will be run even if the target file already exists. We give the force target an empty recipe to prevent make from searching for an implicit rule to build it—otherwise it would apply the same match-anything rule to force itself and create a prerequisite loop!
One option: use a wrapper file to execute the commands to do that. Just be sure your target make files don't include the child directory that has the wrapper, or else you can create an endless loop. For example,
clean:
pushd .. && make clean && popd
Using the comment of user Renaud Pacalet and the answer to a different question the following one-liner is as close as I could get. The whole Makefile reads:
IGNORE := $(shell $(MAKE) -C .. $(MAKECMDGOALS))
This solutions comes with a few caveats:
Command line option -B does not get passed through to the subsequent make call.
The output of the subsequently called make process (in the project directory) is not printed to stdout.
The wrapper make process reports for any given target at the end :
make: *** No rule to make target TARGET. Stop.

Force make clean and remake if specific marker file is missing

I have a Makefile that I run with multithreading (-j8 specifically).
I want to force a make clean and make all operation if I'm missing a specific marker file identifying the version compiled.
(This file should be introduced when the make is completed after the second make all.)
I can't seem to make this work properly. I either get stuck in loops or it just doesn't happen at all.
(This is part of a huge system so I can't just change any paradigms and I have to work with what I have)
Here's the relevant section I have so far. This wasn't the original plan but I shifted so many things around this is the current situation:
VERSION = 2.8
.DEFAULT_GOAL := all
.PHONY : all
all : {some targets} | marker_file
###########################
.PHONY : marker_file
marker_file : build/$(VERSION).marker
.PHONY : check_marker
check_marker :
ifeq (,$(wildcard build/$(VERSION).marker))
#echo -e "\e[41mYOU ARE ON NEW PREREQUISITES $(VERSION)! FORCING MAKE CLEAN BEFORE REBUILDING\e[0m"
$(MAKE) clean
#mkdir -p build
#touch build/$(VERSION).marker
$(MAKE) $(MAKECMDGOALS)
endif
# if the marker file needs generation, force clean and rebuild
build/$(VERSION).marker : check_marker
Can anyone figure out how to properly plan the rules and dependencies so that I can generate the file on the second time?
You definitely don't want to use order-only prerequisites. That forces the prerequisite to always run, but doesn't use the results in determining whether to run the target. That's almost the exact opposite of what you want.
Also you cannot use make preprocessor constructs like ifeq inside a recipe (indented by a TAB). Recipes are passed to the shell, and the shell is not make and does not understand make constructs like ifeq.
You can use make's auto-re-exec feature: if an included file changes then make will re-exec itself. So:
VERSION = 2.8
.DEFAULT_GOAL := all
.PHONY : all
all : {some targets}
###########################
MARKER_FILE = build/$(VERSION).marker
$(MARKER_FILE) :
#echo -e "\e[41mYOU ARE ON NEW PREREQUISITES $(VERSION)! FORCING MAKE CLEAN BEFORE REBUILDING\e[0m"
$(MAKE) clean MARKER_FILE=
#mkdir -p $(#D)
#touch $#
include $(MARKER_FILE)

Resources