>
> 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:

.RECIPEPREFIX := >
some_target:
> ${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}
> ${EXPRESSION}
> 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.

-brian
_______________________________________________
Bug-make mailing list
Bug-make@gnu.org
https://lists.gnu.org/mailman/listinfo/bug-make

Reply via email to