Define target name in other recipe - makefile

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}

Related

Makefile not updating when dependency changed

all: ./data/for_analysis.csv ./data/tables/*.docx
./data/for_analysis.csv : ./src/convert-xls-to-gold-standard.py ./data/ED-TRAUMA-DELTA-STUDY_3_2019_total.xlsx
python3 $< --rawDataPath $(word 2,$^) --fieldCodesPath ./data/excel_field_codes.json --processedDataPath ./data/for_analysis.csv --logDir ./logs
./data/tables/%.docx : ./src/make-%.py ./data/for_analysis.csv
python3 $< --fieldCodesPath ./data/excel_field_codes.json --processedDataPath ./data/for_analysis.csv --logDir ./logs --tablesDir ./data/tables
When I update ./src/make-table-2.py, the second target isn't updated. This behavior doesn't depend on whether ./data/table/table-2.docx exists or not.
When I run make or make all even after updating the py file, I get the message make: Nothing to be done for 'all'.
It's not exactly clear from your question what the state of your targets is before you run make. But:
all: ./data/for_analysis.csv ./data/tables/*.docx
this can't really work, in general. This tells make, "go find all the files that exist with the filename matching the wildcard ./data/tables/*.docx". E.g., that's the same thing you'd get if you run ls ./data/tables/*.docx before you started make.
But of course, if you haven't built anything yet then there are no files matching that pattern, because that's what you're asking make to build. So this expands to nothing and make won't do anything with them.
You have to list the targets that you want to build explicitly, or else convert them from the source files you want them to be built from, so you can tell make what it should be building.
For example, maybe:
all: ./data/for_analysis.csv $(patsubst ./src/make-%.py,./data/tables/%.docx,$(wildcard ./src/make-*.py))

Ensure that make is invoked from a specific directory

I would like all my recipes to be executed from a specific directory, the directory where the Makefile is located.
This is the default behaviour when invoking make without options, but an user could always run :
(cd /somewhere; make -f /path/to/directory/Makefile)
To ensure that make working directory is the same as the directory where the Makefile is located, there are multiple solutions :
run make without options (default), from this specific directory (cd /path/to/directory; make)
use make -C /path/to/directory
cd to /path/to/directory for each recipe, like this :
MAKEFILE_DIR_LOCATION := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
a:
cd ${MAKEFILE_DIR_LOCATION} && do_something_from_makefile_folder
b:
cd ${MAKEFILE_DIR_LOCATION} && do_another_thing_from_makefile_folder
The problem is that the first two solutions requires actions from the user invoking the Makefile, while the last one clutters the Makefile.
Is there a prettier way to ensure that all recipes are executed from the directory where the Makefile is located?
Extra solution (does not work)
I also thought comparing the working directory ($(shell pwd)) to ${MAKEFILE_DIR_LOCATION}, and exit if it does not match (at least to warn the user that make is not correctly invoked), but I can't find how to do this. I tried :
MAKEFILE_DIR_LOCATION := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
WORKING_DIR := $(shell pwd)
ifneq (${MAKEFILE_DIR_LOCATION}, ${WORKING_DIR})
#error "Please run make from the directory of the Makefile, or use make -C"
endif
a:
do_something_from_makefile_folder
b:
do_another_thing_from_makefile_folder
But I got a missing separator error (line #error), or a recipe commences before first target if #error line is indented.
Answering the question you asked without commenting on whether it's a good idea or not, I'm not sure where you found this syntax:
#error "Please run make from the directory of the Makefile, or use make -C"
but it's definitely wrong. error is a make function, so you want this:
$(error Please run make from the directory of the Makefile, or use make -C)
A variant on your last attempt would re-invoke Make in the correct directory, with the same target:
ifneq (${MAKEFILE_DIR_LOCATION},${WORKING_DIR})
%:
$(MAKE) -C ${MAKEFILE_DIR_LOCATION} $#
.PHONY: %
else
## rest of Makefile rules
endif

Define a makefile target from variables set with eval

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!

Make target using a pattern

I'm doing something which it feels like should be pretty straightforward. I have source files in a directory called ./src which are transformed and saved to ./. For the sake of the question, I'll just say they get copied there.
Here's what the directory looks like before building:
/src/lib/foo.js
/src/lib/mod/bar.js
/src/bin/baz.js
/Makefile
Here's what should be there after building:
/src/lib/foo.js
/src/lib/mod/bar.js
/src/bin/baz.js
/lib/foo.js
/lib/mod/bar.js
/bin/baz.js
/Makefile
In my Makefile I have this:
SRC_FILES := src/lib/foo.js src/lib/mod/bar.js src/bin/baz.js
OUT_FILES := lib/foo.js lib/mod/bar.js bin/baz.js
These are generated from find and a pattern substitution, but are listed like this here for simplicity...
Now, what I was hoping would work would be:
%.js: src/%.js
cp $< $#
But when I run make lib/foo.js I get Nothing to be done for 'lib/foo.js'.
Looking at the output from debugging, it appears to be trying to find a dependency named lib/src/foo.js. This is kind of what is described in the manual.
This feels as though it should be a really easy thing! What am I doing wrong?
Additionally, I tried:
$(OUT_FILES): $(SRC_FILES)
cp $< $#
...which works, but it rebuilds every single file if any of the sources change. (Remember that my actual transformation isn't just cp, it's a relatively expensive operation, so this is undesirable)
Found something which works. Using secondary expansion, you have access to the target variable in the dependencies. I saw this earlier, but missed the all-important thing: adding an empty target called .SECONDEXPANSION
.SECONDEXPANSION:
all: $(OUT_FILES)
%.js: src/$$#
cp $< $#

Alias target name in Makefile

The Problem:
Is it possible to give a target a different name or alias, such that it can be invoked using either the original target name or the alias.
For example something like
/very/long/path/my_binary: dep_a dep_b dep_c
# Compile
# Desired command
ALIAS my_binary = /very/long/path/my_binary
# NOTE: Notice the use of 'my_binary' in the dependencies
data1: my_binary datafile
# Build data file using compiled my_binary
Attempt 1: .PHONY
I have tried using a .PHONY target:
.PHONY: my_binary
my_binary: /very/long/path/my_binary
This works great when invoked from the command-line:
# Runs rule 'my_binary' and then *only* runs rule '/very/long/path/my_binary'
# if the rule '/very/long/path/my_binary' needs updating.
make my_binary
However, this does not work well when the alias my_binary is listed as a dependency:
# *Always* thinks that rule 'data1' needs updating, because it always thinks that
# the .PHONY target 'my_binary' "needs updating". As a result, 'data1' is
# rebuilt every time.
make /very/long/path/my_binary
Possible hack?
A possible hack is to use an empty target as suggested in an answer to this question, but that would require introducing fake files with names corresponding to the alias:
my_binary: /very/long/path/my_binary
touch my_binary
This will clutter the main directory with files! Placing the fake files in a sub-directory would defeat the purpose, as the alias would have to be referred to as 'directory/my_binary'
Okay, I needed something similar. The path to my output artifacts were quite long, but I wanted short target names and also benefit easily from bash-completion.
Here is what I'm came up with:
os := [arbitrary long path to an artifact]
platform := [arbitrary long path to a differ artifact]
packer := [common parts of my packer build command]
.PHONY: all
all: $(platform)
.PHONY: platform
platform: $(platform)
$(platform): platform.json $(os)
#$(packer) $<
.PHONY: os
os: $(os)
$(os): os.json
#$(packer) $<
.PHONY: clean
clean:
rm -fr build/
With the Makefile above you can say:
$ make os
$ make platform
Which will be aliases for the long artifact names. I've made the snippet above quite long, because it's important to see the relationships between the .PHONY aliases and the real targets. I hope that works for you.
Note: I did not delete the clean target from the above example, because many people does not make that a .PHONY target. However, semantically it should be.
I don't think there's any way to do it so that you can use the alias from within your makefile as well as the command line, except by creating those temporary files.
Why can't you just set a variable in the makefile, like:
my_binary = /very/long/path/my_binary
then use $(my_binary) everywhere in the makefile? I don't see any point in creating a real alias target for use inside the makefile.
I had a somewhat similar need. I wanted users of my makefile to be able to enter any of the following to accomplish the same result, such that the following were effectively synonyms of each other:
make hit list
make hitlist
make hit_list
What I did in my makefile was the following:
hit_list:
#echo Got here
<the real recipe goes here>
hit: hit_list
hitlist: hit_list
.PHONY: list
list:
#echo > /dev/null
Then, when I tested it using any of the commands "make hit list", "make hitlist", or "make hit_list", I got identical results, as intended.
By extension, if one of your targets was the one with the long name but you used this approach whereby a simple short name identified the target with the long name as a prerequisite, I think that you should be able to say "make short_name" and accomplish what you're asking about.
This differs from your Approach 1 in that none of the synonyms is defined as a phony target (considering that "make hit list" is a command to make two targets, the second being effectively a noop), so the complication that you described would not arise.

Resources