> However, I'm not sure that this function is sufficiently generic to be
added as a built-in function.  Can you provide use-cases for it?

I suppose that depends on the definition of "sufficiently generic".  The
two definitions that come to mind:

   1. Useful for many people who do build maintenance
   2. Useful for people trying to improve performance on large builds or on
   makefile "libraries" (eg gmsl <https://gmsl.sourceforge.io/> or
   libmakefile <https://github.com/alperakcan/libmakefile>)

If the chosen definition is (1) then you're right, but I think it's safe to
say most people in (2) would find it useful (or would have).

I'll try to give some good examples, though I came up with this years ago
and I'm just getting around to contributing it.


One of the more annoying problems I've encountered when writing canned
expressions is whitespace creeping in.  Here's a contrived example with a
potential solution:

$ cat makefile
> F1 = ${${1}}
> F2 = $(call ${0}_,$(strip ${1}))
> F2_ = ${${1}}
> TEXT = something
> # note: the space before the word *TEXT* is helpful for readability but
> causes a problem
> $(info $(call F1, TEXT))
> $(warning $(call F2, TEXT))
> $ make --warn-undefined-variables
> makefile:6: warning: undefined variable ' TEXT'
> makefile:7: something

Adding that extra level of indirection makes it easier to write the
expression without worrying about unexpected whitespace but there's a cost
that's easy to ignore if you don't have a straightforward way to measure it.


One of the bottlenecks I uncovered in our build looked something like this:

> ${CC} *${SOMETHING_EXPENSIVE}* ${OTHER_FLAGS} ${^} -o ${@}

The documentation says it but I didn't fully appreciate it until diving
into this problem: the entire recipe undergoes expansion before a subshell
gets created (either that or it occurs after vfork() and before execve(),
but the effect is still the same).  The non-obvious [to me] consequence:
expensive operations in a recipe can artificially limit the number of jobs
make can spawn/reap since time spent on recipe expansion is time not spent
reaping/spawning jobs.  The impact isn't as bad if the build is recursive,
but that doesn't apply to us.


After I created the *timeit* function I spent a few days coming up with /
looking for awful contrivances to help me better understand what effect
various expressions might have on time spent parsing them.  Here's a few I
remember, though I cannot guarantee these are functional as written -- I'm
writing from memory without testing them:

# Recursion:
> F1 = $(strip $(if $(filter 16,${3}),${2},$(call ${0},${1},${${1}}
> ${2},$(words ${2}))))
> TEST = something
> # prints the word "something" 16 times
> $(info $(call F1,TEST,,))

# Automatically generate a target
> define MAKE_TARGET
> $(eval ${1}.o := $(addsuffix .o, $(basename $(wildcard ${2}))))
> # evals needed because of *${${1}.o}*
> $(eval ${1} : ${${1}.o})
> # ... whatever other strange contrivance comes to mind ...
> endef

# recursively expanded variable (B) with partial expansion.
> A = a
> B = ${A}
> $(eval B=$(value B) ${B})
> # prints: ${A} a
> $(info $(value B))

# variable reflection
> THING = $(call ${0}_,$(filter $(firstword $(notdir $(basename
> ${1}))).$(strip ${2}),${.VARIABLES}),$(strip ${2}))
> THING_ = ${$(strip $(if ${1},${1},${2}))}
> # Depending on its existence either *bar.CFLAGS* or *CFLAGS* gets
> evaluated
> bar : ; ${CC} $(call THING, ${@}, CFLAGS) ${^} -o ${@}

# Conditional evaluation of macro arguments; *ifdef/ifndef* is from our
> plugin

F = $(ifndef 3,$(error ${0} requires 3 arguments))...stuff...

Testing the effect they have on performance could be tricky.  Without a way
to evaluate timing of specific expressions you're limited to:

   1. Build make / attach a profiler / figure out how to attribute
   performance changes to expressions used in the makefile
   2. Use *time* (or something similar) / establish a baseline by removing
   the code in question
   3. Cook up some makefile hackery that in some way measures time spent
   evaluating the expression

(1) is probably not realistic.  With (2): if expression A causes expression
B to have performance problems, but B is in your baseline then the
performance loss will be attributed to A whereas the problem may lie in B.

Without a builtin or plugin function, (3) amounts to something like this:

NOW = $(shell date +%s%N)
> before := ${NOW}
> after := ${NOW}
> $(info difference: $(shell expr ${after} - ${before}))

... which has limitations:

   - Relies on *$(shell)* which may skew/bias the results (though to be
   fair a builtin/plugin function could be added that returns a high
   resolution timestamp)
   - Would be more complicated to apply this to recipe parsing,
   *.SECONDEXPANSION* and other situations where temporary variables may be
   an issue, though you could get around it with something like: *$(info
   ${NOW})${EXPRESSION}$(info ${NOW})* and post-process the results

Anyway, my diarrhea of the keyboard should probably end before I think of
more to write.

Bug-make mailing list

Reply via email to