Suppose I have made some non-conventional use of make. Suppose further that I've come up with a name scheme within this usage that is elegant and useful with tools other than make, and which is almost fitting perfectly into the make paradigm.
I can almost leverage make built-ins to accomplish this task, and am looking for a bit of magic to make it 'just work' within the semantics and grammar of the system.
Right now, I have a buildable process that depends as follows:
# "<-" = "depends on"
foo.bar.biz.baz.new <- foo.bar.biz.baz.extension bar.biz.baz.new
bar.biz.baz.new <- bar.biz.baz.extension biz.baz.new
biz.baz.new <- biz.baz.extension baz.new
baz.new <- baz.extension
# and the following rules should suffice, where $(magic ...) is a stand-in for what I'd like to do:]
SHELL:=/usr/bin/env bash
.SECONDEXPANSION
%.new: $$*.extension $(magic $$*)
...
build: foo.bar.biz.baz.new
What I am looking for is some magic swapper that:
a...b -> ...c if ... != ""
# so, it chops off the prefix (dot-delimited), a
# and swaps the suffix only if something is present other than the suffix
I think this could be:
$(filter-out .c, $(patsubst %,%.c $(not-prefix $$*)))
If not-prefix existed, and I am wondering whether there is some built-in that more or less accomplishes this task out of the box?
Alternatively, is there some really compact bashism that can accomplish this?
For example:
$(shell echo "$$*" | sed -E 's/[^.]*(.*)\.[^.]*$$/\1.new/' | grep -v -E '^\.new$$')
Is super windy and verbose, but maybe there is some compact way to do this?
The most readable I've come up with so far:
%.new: $$*.extension $(filter-out .new, $(shell echo "$$#" | cut -d '.' -f 1 --complement))
Update Minimal verifiable example of the tree-build recursion problem:
%.ext:
#touch $#
.SECONDEXPANSION:
%.new: $$*.ext $(filter-out new, $(shell echo "$$#" | cut -d '.' -f 1 --complement))
#echo "#: $#"
#echo "<: $<"
#echo "^: $^"
#echo $(filter-out new, $(shell echo "$#" | cut -d '.' -f 1 --complement))
$ make descendant.ancestor.new
#: descendant.ancestor.new
<: descendant.ancestor.ext
^: descendant.ancestor.ext
ancestor.new
rm descendant.ancestor.ext
Next, I add in the second "$" sign to the rule, so that it executes during the second expansion instead:
%.ext:
#touch $#
.SECONDEXPANSION:
%.new: $$*.ext $$(filter-out new, $$(shell echo "$$#" | cut -d '.' -f 1 --complement))
#echo "#: $#"
#echo "<: $<"
#echo "^: $^"
#echo $(filter-out new, $(shell echo "$#" | cut -d '.' -f 1 --complement))
Which yields:
$ make descendant.ancestor.new
make: *** No rule to make target 'descendant.ancestor.new'. Stop.
Ultimately, I think this should be something like:
.SECONDEXPANSION:
%.new: $$*.ext | $$(filter-out new, $$(shell echo "$$#" | cut -d '.' -f 1 --complement))
But instead what I have done to make it "just work" is:
%.ext:
#touch $#
.SECONDEXPANSION:
%.new: $$*.ext .FORCE
#echo "#: $#"
#echo "<: $<"
#echo "^: $^"
#$(eval prior=$(filter-out new, $(shell echo "$#" | cut -d '.' -f 1 --complement)))
#$(if $(prior),$(MAKE) $(prior))
#echo "ta-da: idempotent, depth first tree recursion #:$#"
.FORCE:
Which yields a more brittle behavior than the rule line:
$ make -s branch.root.new
#: branch.tree.new
<: branch.tree.ext
^: branch.tree.ext .FORCE
#: tree.new
<: tree.ext
^: tree.ext .FORCE
ta-da: idempotent, depth first tree recursion #:tree.new
ta-da: idempotent, depth first tree recursion #:branch.tree.new
OK thanks for clarifying what you're looking for. So, I don't have anything I would consider "dead simple" but you can do it wholly with GNU make using this:
e =
sp = $e $e
magic1 = $(if $(filter-out 1,$(words $(subst ., ,$1))),$1,)
magic = $(call magic1,$(subst $(sp),.,$(wordlist 2,1000,$(subst ., ,$1))))
Now wherever you want "magic" to happen, call the magic function:
.SECONDEXPANSION:
%.new: $$*.ext $$(call magic,$$#)
The magic1 macro expands to its argument if its argument has >1 ., else expands to the empty string. The magic macro strips off the first "prefix" and calls magic1 with that value.
There may be simpler ways to do this, I didn't come up with one off the top of my head.
Also, I didn't review your edited question so I'm not sure if something you added there makes a difference here.
The problem here is that most build systems depend on stable prefixes and suffixes, with only varying stems (middle parts).
Here, you have an arbitrarily varying prefix, as well as middle part. So for instance bar.biz.baz is the common stem among all the names in the first dependency situation. It is arbitrary. However, each one of the names involved also has an independently arbitrary prefix, like foo.
To avoid fighting the tool, I would try to encode foo and biz.bar.baz, and every other such pair, together in some variables that act as the primary source of this information.
Sample Makefile. In this solution we use nothing but call, firstword, secondword a one-character subst, foreach and eval:
UNITS := foo#biz.bar.baz bar#biz.baz biz#baz
# $(call FIRST, foo#bar) -> foo
FIRST = $(firstword $(subst #, ,$(1)))
# $(call REST, foo#bar) -> bar
REST = $(lastword $(subst #, ,$(1)))
TARGETS := $(foreach U,$(UNITS),$(call FIRST,$(U)).$(call REST,$(U)).new)
DEPS := $(foreach U,$(UNITS),$(call FIRST,$(U)).$(call REST,$(U)).extension) \
$(foreach U,$(UNITS),$(call REST,$(U)))
.PHONY: all $(TARGETS) $(DEPS)
all: $(TARGETS)
define RULE
$(1).$(2).new: $(1).$(2).extension $(2)
#echo BUILD $$# '<-' $$^
endef
$(foreach U,$(UNITS),\
$(eval $(call RULE,$(call FIRST,$(U)),$(call REST,$(U)))))
Output:
$ make
BUILD foo.biz.bar.baz.new <- foo.biz.bar.baz.extension biz.bar.baz
BUILD bar.biz.baz.new <- bar.biz.baz.extension biz.baz
BUILD biz.baz.new <- biz.baz.extension baz
Alternative, using computed variable names. Here, in addition to computed variables, which simulate data structuring, we are only using foreach, call and eval. No string processing at all, other than the nested variable expansion:
UNITS := mercury mars venus
mercury.head := foo
mercury.tail := biz.bar.baz
mars.head := bar
mars.tail = biz.baz
venus.head := biz
venus.tail := baz
TARGETS := $(foreach U,$(UNITS),$($(U).head).$($(U).tail).new)
DEPS := $(foreach U,$(UNITS),$($(U).head).$($(U).tail).extension) \
$(foreach U,$(UNITS),$($(U).tail))
.PHONY: all $(TARGETS) $(DEPS)
all: $(TARGETS)
define RULE
$(1).$(2).new: $(1).$(2).extension $(2)
#echo BUILD $$# '<-' $$^
endef
$(foreach U,$(UNITS),\
$(eval $(call RULE,$($(U).head),$($(U).tail))))
What I am looking for is some magic swapper that:
a...b -> ...c if ... != ""
# so, it chops off the prefix (dot-delimited), a
# and swaps the suffix only if something is present other than the suffix
I don't completely understand the operation you want to execute, but the GNUmake table toolkit has, despite its name, also some practical string operations to offer:
include gmtt.mk
TEST := $(call glob-match,aprefix.thensomething.somemore.thenapostfix,aprefix.*.*apostfix)
$(info $(TEST))
$(if $(TEST),$(info Yes, string looks like),$(info No, string doesn't look like))
$(info aprefix..apostfix)
$(if $(TEST),$(info The variable contents of the string is "$(word 2,$(TEST))" and "$(word 4,$(TEST))"))
Output:
aprefix. thensomething . somemore.then apostfix
Yes, string looks like
aprefix..apostfix
The variable contents of the string is "thensomething" and "somemore.then"
PS: I want to note that the .* sequence in the pattern does not constitute the regex [sequence of 0..n arbitrary characters] but a glob pattern, where the * alone stands for repetition of 0..n arbitrary characters. The . is literal here.
EDIT:
Here is a solution with dynamic rules (to really generate them, replace info with eval:
include gmtt/gmtt.mk
gen-rules = \
$(if $(call glob-match,$1,*.*.new),\
$(info $1 : $(firstword $(call glob-match,$1,*.new)).extension $(word 3,$(call glob-match,$1,*.*)))\
$(call gen-rules,$(word 3,$(call glob-match,$1,*.*))),\
$(info $1 : $(patsubst %.new,%.extension,$1)))
$(foreach cmdline-target,$(filter %.new,$(MAKECMDGOALS)),$(call gen-rules,$(cmdline-target)))
Below is my Makefile. I just want to do a comparision using && operator (or its equivalent)
as shown in the following pseudocode.I want to run the following logic inside the "all" target
# if (CUR_PI_VERSION == LAST_PI_VERSION) && (CUR_GIT_VERSION == LAST_GIT_VERSION)
# print "everything matched. Nothing to do"
# else
# print "files not matched"
# run python script.
#
# How do I achieve this.
I have looked at other answers, but I couldn't get to the result I wished.
I have attached my sample code for reference.
CUR_PI_VERSION:= "abc"
CUR_GIT_VERSION:= "cde"
LAST_PI_VERSION:= "abc"
LAST_GIT_VERSION:= "cde"
$(info $$CUR_GIT_VERSION is [${CUR_GIT_VERSION}])
$(info $$CUR_PI_VERSION is [${CUR_PI_VERSION}])
$(info $$LAST_GIT_VERSION is [${LAST_GIT_VERSION}])
$(info $$LAST_PI_VERSION is [${LAST_PI_VERSION}])
all:
# The pseudocode of what I want to do is as follows
# if (CUR_PI_VERSION == LAST_PI_VERSION) && (CUR_GIT_VERSION == LAST_GIT_VERSION)
# print "everything matched. Nothing to do"
# else
# print "files not matched"
# run python script.
#
# How do I achieve this.
#
ifeq ($(CUR_GIT_VERSION),$(LAST_GIT_VERSION))
ifeq ($(CUR_FPI_VERSION),$(LAST_FPI_VERSION))
echo "Everything matched, so don't need the make top"
endif
endif
Any help is highly appreciated.
You can try this :
all:
ifeq ($(CUR_PI_VERSION)#$(CUR_GIT_VERSION),$(LAST_PI_VERSION)#$(LAST_GIT_VERSION))
echo "Everything matched, so don't need the make top"
endif
Above test compares two concatenated strings (joined by #):
$(CUR_PI_VERSION)#$(CUR_GIT_VERSION)
and
$(LAST_PI_VERSION)#$(LAST_GIT_VERSION)
Take the following makefile snippet:
VAR_LIST = "item1" "item2" "item 3 that has spaces" "item4"
ARGS = $(addprefix echo ,$(VAR_LIST))
What I am trying to achive is for ARGS to contain:
echo "item1" echo "item2" echo "item 3 that has spaces" echo "item4"
What I can't figure out how to resolve is that functions like addprefix act on spaces...
Not sure whether what you need is easily achievable with make because quoting strings have no effect on make functions that process words: " is a part of a word.
I would use a shell or python script for that.
You can do that with the help of gmtt entirely inside GNUmake. It is not as straightforward as in a full programming language, but at least it is portable and independent of external shell flavours and tools.
include gmtt/gmtt.mk
VAR_LIST = "item1" "item2" "item 3 that has spaces" "item4"
# make a prefix-list by splitting at ". This will yield superfluous space
# characters between the items, but we can eliminate them later
prefix-list := $(call chop-str-spc,$(VAR_LIST),A $(-alnum-as-str))
$(info $(prefix-list))
# Now we select the data payload from the prefix-list. Spaces inside items
# are still encoded as $(-spacereplace) characters, which is good as we have
# a normal make list this way
string-list := $(call get-sufx-val,$(prefix-list),A,,100)
$(info $(string-list))
# Using get-sufx-val() is fine, but we can have it even simpler, by dropping
# the prefix, as we have only one in the list anyway:
string-list := $(call drop-prfx,$(prefix-list))
# Now step through the list with a normal for loop, converting $(-spacereplace)
# back to real spaces
$(foreach item,$(string-list),$(if $(strip $(call spc-unmask,$(item))),\
$(info [$(call spc-unmask,$(item))])))
Output:
$ make
A¤item1 A¤§ A¤item2 A¤§ A¤item§3§that§has§spaces A¤§ A¤item4
item1 § item2 § item§3§that§has§spaces § item4
[item1]
[item2]
[item 3 that has spaces]
[item4]
I have one version file verfile which contains below version string
V1.1.2
And in Makefile I intend to read this version string,
So I wrote Makefile as follows,
filepath := $(PWD)
versionfile := $(filepath)/verfile
all:
cat $(versionfile)
version=$(shell cat $(versionfile))
echo "version=$(version)"
Now when I run the make file I get following ouput
cat /home/ubuntu/ankur/verfile
v1.1.2
version=v1.1.2
echo "version="
version=
So I am not able to store version string in the variable and use it later,
I am not sure what am I doing wrong?
Any suggestion/pointer ?
After reading answer from "Didier Trosset" I changed my makefile as follows,
filepath := $(PWD)
versionfile := $(filepath)/verfile
myversion := ""
all:
ifeq ($(wildcard $(versionfile)),)
all:
echo "File not present"
else
all: myversion = $(shell cat $(versionfile))
endif
all:
echo "myversion = $(myversion)"
and below is output for the
echo "myversion = v1.1.2"
myversion = v1.1.2
You have two problems. First to have bash (and not make) expand the variable you need to use $$version (or $${version}). By this Make first translates $$ into just $ and then bash will see $version (or ${version}).
Secondly each line in the rule will be executed as a separate command, so in order to let them interact via environmental variables you have to put them into a common subshell, enclosing with paranthesis.
filepath := $(PWD)
versionfile := $(filepath)/verfile
all:
cat $(versionfile)
(version=$(shell cat $(versionfile)); echo "version=$$version")
I usually prefer to have this version string in a make variable.
Therefore, I'd rather use the following that keeps variables into variables, and rules/target/commands in rules/target/commands.
filepath := $(PWD)
versionfile := $(filepath)/verfile
version := $(shell cat $(versionfile))
info:
echo "version=$(version)"
Note that here, version is a real make variable. (As opposed to a bash variable existing only in a lingle rule line of the target.)
If you need some commands to create the versionfile then it'd be better to create this file on its own target
filepath := $(PWD)
versionfile := $(filepath)/verfile
all: versionfile
echo "version=$(version)"
all: version = $(shell cat $(versionfile))
versionfile:
# Create your version file here
Note that you cannot use the := anymore for the target variable: it would read the file at Makefile reading instead of when used, i.e. after versionfile target has been created.
This is an example of use of DEFAULT_GOAL Variable:
ifeq ($(.DEFAULT_GOAL),)
$(warning no default goal is set)
endif
.PHONY: foo
foo: ; #echo $#
$(warning default goal is $(.DEFAULT_GOAL))
# Reset the default goal.
.DEFAULT_GOAL :=
.PHONY: bar
bar: ; #echo $#
$(warning default goal is $(.DEFAULT_GOAL))
# Set our own.
.DEFAULT_GOAL := foo
The output is:
no default goal is set
default goal is foo
default goal is bar
foo
I stuck up understanding that what is the flow of the echo and $(warning ) function i.e. when $(warning ) function is called the echo $# output is suppressed and last output of echo $# is displayed. Because there is 2 echo statement and 3 $(warning ) function call but only one target id printed by echo the last one foo . Why others are not printed and why the last value is printed as foo why not bar?
The warnings and assignments happen as make reads the Makefile. Only then does it begin to decide which of the rules to execute. The other echo statements are never executed because it only makes the default goal at that point, which is foo.