Define a makefile target from variables set with eval - makefile

I have to define a target that contains a version number, which in turn is extracted from some file (the prerequisite of this target) retrieved from git.
What I first tried was to set a variable with eval and use this in the name, like this.
version_file:
#checkout
version: version_file
#eval version := get version from version_file
file_$(version):
#echo file_$(version)
final_target: file_$(version)
#echo $#
This cannot work: make reads the makefile in the first pass and does not find the variable dynamic_prerequisite, so the target created is simply named file_.
But when I try to create the rule dynamically, after the variable has been set, like this
...
define rule_file_version
file_$(version):
#echo version: $#
endef
version: version_file
#eval version := get version from version_file
$(eval $(rule_file_version))
... this gives me the error message:
makefile:12: *** prerequisites cannot be defined in recipes.
Of course I cannot move the eval for the target outside of the recipe, as I then encounter the same problem as before.
Is there a way to achieve what I want with gnu make?

I found the problem can be solved by using constructed include files.
For the construction of the files itself I created a simple shell script, that takes the dynamically set variable as an argument:
#!/bin/bash
read -r -d '' MAKE <<EOF
file_$1:
#echo version: $1
final_target: file_$1
#echo final_target: $1
EOF
echo "$MAKE" > rules.mk
This is used in the makefile to create an included makefile rules.mk, like this:
version_file:
#checkout
version: version_file
#eval version := get version from version_file
rules.mk: version
$(shell ./create_rules_mk.sh $(version))
-include rules.mk
When I run make final_target it creates the rules.mk as wished.
The bottom line for me is, that target names, that themselves depend on other targets have to use dynamic creation of makefiles.

Here is a much simpler approach:
blackbox.sh:
#!/bin/bash
echo 1.0
Makefile:
dynamic_prerequisite := $(shell ./blackbox.sh)
file_$(dynamic_prerequisite):
#echo target is $#

Another option is to use a build tool more suited to dynamic targets. For example, I've written a Gnu Make-like tool incorporating some of the concepts from DJB's ReDo, called GoodMake. Your equivalent makefile would just be:
#? version_file
checkout
#? final_target
$0 version_file # Static dependency
version=$(get version from version_file)
$0 file_$version # Dynamic dependency
Simpler, huh? If you try out GoodMake, I'd love to hear what you think!

Related

Makefile function from make commands, NOT shell commands

Is there any way to create multiline functions out of Makefile commands?
I know we can do something like this to encapsulate a recipe (of shell commands) as a function:
define function
#echo 'First argument: $1'
#echo 'Second argument: $2'
endef
.PHONY test-function
test-function:
$(call function, a, b)
With this, running make test-function will give the output:
First argument: a
Second argument: b
I also know we can use the call directive with one-line macros consisting of make syntax/directives (example taken from here):
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))
LS := $(call pathsearch,ls)
But let's say I wanted to call a macro made up of multiple make commands, including conditionals. How would I achieve that?
When I run make build-type=API build with the following Makefile:
define check-arguments
ifeq ($1, api)
#echo 'Building API'
else ifeq ($1, service)
#echo 'Building Service'
else
$$(error 'Build type must be API or Service')
endif
endef
.PHONY: build
build:
$(call check-arguments, $(build-type))
#echo 'Starting build'
...
...
I keep getting the error Makefile:13: *** missing separator. Stop..
You can use eval. The GNU Make Manual states:
...it [eval] allows you to define new makefile constructs that are not constant; which are the result of evaluating other variables and functions.
eval will parse ifeq and $(error) as part of the makefile instead of as commands for the recipe.
One thing to keep in mind is that eval parses its input by itself, without regard for the surrounding syntax of the makefile. This means that you cannot use it to define only part of a rule, like in your example:
build:
$(call check-arguments, $(build-type))
If we use $(eval $(call check-arguments, $(build-type))), then eval will parse the expansion of check-arguments by itself and complain because the recipe has no target. (See this answer.) The solution here is to include build: in check-arguments somehow.
While having $(eval) is fine, I would like to recommend a different approach, based on target resolution instead of conditionals, like so:
$ cat Makefile
supported_build_types := api service
.PHONY: build
build: build-$(build-type)
.PHONY: $(addprefix build-,$(supported_build_types))
$(addprefix build-,$(supported_build_types)): build-%:
#echo 'Building $*'
#echo 'Starting build'
.PHONY: build-
build-:
$(error Must provide build-type of: $(supported_build_types))
.PHONY: build-%
build-%:
$(error Unsupported build type: $*. Must be one of: $(supported_build_types))
This can allow easier extensibility and maintenance while keeping away nuisances of $(eval)s, $(call)s and appropriate escaping.
Running supported build types:
$ make build build-type=api
Building api
Starting build
$ make build build-type=service
Building service
Starting build
Invalid build type:
$ make build build-type=foo
Makefile:17: *** Unsupported build type: foo. Must be one of: api service. Stop.
Missing build type:
$ make build
Makefile:13: *** Must provide build-type of: api service. Stop.

Makefile not reading a timestamp variable

I would like to add a Makefile in my project to use the make command for my migrations, the file generated should have a timestamp and a label as a name, but when I run the make command the timestamp is not appearing in the file name.
Here's the Makefile script:
MIGRATION_LABEL = "to-be-changed"
DATE_WITH_TIME := $(shell /bin/date "+%Y%m%d%H%M%S")
makeMigration:
mvn liquibase:diff -DdiffChangeLogFile=src/main/resources/db/changelog/changes/${DATE_WITH_TIME}-${MIGRATION_LABEL}.yml
#echo " - include:" >> src/main/resources/db/changelog/db.changelog-master.yml
#echo " file: classpath*:db/changelog/changes/$(DATE_WITH_TIME)-$(MIGRATION_LABEL).yml" >> src/main/resources/db/changelog/db.changelog-master.yml
and here is the error :
C:\Users\DELL\Desktop\Project\project-backend>make makeMigration MIGRATION_LABEL="user-creation"
File path not found.
mvn liquibase:diff -DdiffChangeLogFile=src/main/resources/db/changelog/changes/-user-creation.yml
I would like to see a timestamp before "-user-creation.yml"
A couple of things that might help here. First, you set DATE_WITH_TIME using :=, which does an immediate expansion. If you have a really long build, this will represent the time the makefile was first read, as opposed to the time the recipe is run. Not sure if this is your intent. You likely want deferred expansion.
Next, the windows version of date and time will look like this (based off of https://www.windows-commandline.com/get-date-time-batch-file/):
DATE_WITH_TIME = $(shell echo %date%:%time%)
Last, if you want to test this quickly and easily, you can use the $(info ...) makefile directive:
DATE_WITH_TIME = $(shell echo %date%:%time%)
$(info DATE_WITH_TIME right now is $(DATE_WITH_TIME))
which will print a log in your output so you know if you've made a mistake early on, without having to actually build anything.

Define target name in other recipe

It may be ugly but I'd like to use a macro in a target name which gets a value assigned in another recipe. I know how to define macros/variables in other recipes with the eval function like this:
read :
$(eval TEXT := $(shell cat somefile.txt))
say : read
echo ${TEXT}
It would result in:
$ make say
what ever's in this file
That's fine. Also one can use Macros to define target names. Now let's say I have a file, for example a tar ball, the name of which always starts with 'program-' and ends whith '.bar' and in between is the version which is located in a separate file, e.g. 'program-1.2.3.tar' . So in principle I could define the target for this file with:
VERSION = $(shell cat version.txt)
program-${VERSION}.tar :
tar cf program-${VERSION}.tar program-${VERSION}
Alright, this also works fine. But actually I want to read this file only in a target, e.g.:
.PHONY : getversion
getversion :
$(eval VERSION := $(shell cat version.txt))
program-${VERSION}.tar : gerversion
tar cf program-${VERSION}.tar program-${VERSION}
but of course this doesn't work since make already determines the target name by invoking make in the first place. Is there some way to solve this weird problem without having an extra makefile?
I would suggest you to evaluate VERSION and TAR_VERSION outside out the targets. To set TAR_VERSION as the target name, you should also add it to .PHONY target. The whole thing can be like this,
.PHONY : getversion $(TAR_VERSION)
VERSION := $(shell cat version.txt)
TAR_VERSION := $(addprefix program-, $(addsuffix .tar, ${VERSION}))
getversion :
echo ${VERSION}
$(TAR_VERSION) :
tar cf ${TAR_VERSION} program-${VERSION}
I don't know if I understood your problem in all nuances, but I think you are seeing this more complicated than it really is. As far as your description can be understood, you have the following makefile, which you use to make this and make that, where the former is a target which is not dependant on the program-x.y.z.tar and only the latter asks over a slow connection which version you are actually dealing with:
.PHONY : this that getversion
this:
#echo etc....
that: program-${VERSION}.tar
#echo done with program-${VERSION}
getversion :
$(eval VERSION := $(shell cat version.txt))
program-${VERSION}.tar : getversion
tar cf program-${VERSION}.tar program-${VERSION}
This however is your dependencies misunderstood, if I may say this. The build of that is never dependant on the specific version, only on the pure act of accessing version.txt. You also couldn't sensibly build one specific program-${VERSION}.tar (by passing it as the target on the command line) as you aren't able to specifiy which version, because this piece of information comes from the network. All this hints very strongly to the conclusion, that there isn't a dependency involved between that and program-${VERSION} which simplifies the makefile to:
.PHONY : this that getversion
this:
#echo etc....
that: getversion
#echo done with program-${VERSION}
getversion:
$(eval VERSION := $(file < version.txt))
tar cf program-${VERSION}.tar program-${VERSION}

Recursive make with nonstandard makefile name on old make version

I have a makefile which calls itself, in order to obtain a license for the compiler before compiling anything, and release the license even when compilation fails, like this:
.PHONY main_target
main_target:
#license_grab &
#sleep 2
-#$(MAKE) real_target
#license_release
This works great if the makefile is named "makefile". But if I make a copy of the makefile to experiment with something, and invoke it with make -f makefile_copy, then the wrong makefile gets used in the recursive call. How do I prevent this without hard-coding the makefile name in the makefile itself?
Edit: Unfortunately I'm stuck using GNU Make version 3.79.1, so I cannot use MAKEFILE_LIST, which was apparently introduced in version 3.80. Therefore none of the answers in this question will work for me.
You can use the MAKEFILE_LIST variable:
THIS_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
.PHONY main_target
main_target:
#license_grab &
#sleep 2
-#$(MAKE) -f $(THIS_MAKEFILE) real_target
#license_release
You can set the MAKE variable outside the makefile, to include the makefile name (unless of course, it gets overridden). Something like this (for bash):
MAKE="make -f makefile_copy" make -e -f makefile_copy
or this (in pretty much any shell):
make MAKE="make -f makefile_copy" -f makefile_copy

start other makefile within makefile

Since i am not so experienced with the building process / makefiles on linux i ran in follow problem:
the Setup:
i have an makefile A, which needs some enviroment variables set before running, this is done by running . ./set_A_vars.sh (set_A_vars.sh contains many export lines) before running make -f A
now i need to make project A within a makefile B.
i tried the following setup for makefile B:
all: debug release
A_debug:
. ./set_A_vars.sh && make -f A DEBUG=1
A_release:
. ./set_A_vars.sh && make -f A DEBUG=0
debug: A_debug some_B_stuff_debug
release: A_release some_B_stuff_debug
however i get lots of errors, which sound like the enviroment variables in set_A_vars.sh have not been set for make -f A ... in B.
How can i call makefile A from makefile B with the enviroment variables in set_A_vars.sh set in makefile B ??
Any help appreciated.
Your makefile looks good with these provisos:
When you call make from a makefile, please use the macro invocation ${MAKE} rather than plain make. (This ensures parallel make works, and also means it still works even if your make has another name (GNUmake say).)
If your targets do not correspond to actual files, then mark them with .PHONY (see below).
Does some_B_stuff_debug require A to be built first? Then you must tell make this.
some_B_stuff_debug: A_debug
some_B_stuff_debug: A_release
This is clearly wrong. One way is to enforce the ordering via the shell.
Try something like this:
.PHONY: debug
debug:
. ./set_A_vars.sh && ${MAKE} -f A DEBUG=1
${MAKE} some_B_stuff_debug
.PHONY: release
release:
. ./set_A_vars.sh && ${MAKE} -f A DEBUG=0
${MAKE} some_B_stuff_debug
.PHONY: some_B_stuff_debug
∶
Your makefiles should work. I suggest you try the following:
Try running set_A_vars.sh from the command line.
Verify that the variables you wanted set are set.
make -f MakefileA, to verify that MakefileA really does work nicely with these variables set.
Try a rule in MakefileB that will test one of the variables, say FOO:
test_var:
#echo FOO is $(FOO)
This should work if you have just run set_vars.sh. If it doesn't, then there are a couple of things that could be wrong...
Now clear the variables (including FOO) and try this rule in MakefileB:
set_vars_and_test_them:
./set_A_vars.sh && echo FOO is $(FOO)
Now put it together:
A_debug:
./set_A_vars.sh && make -f MakefileA DEBUG=1
(I recommend against calling a makefile "A".)

Resources