MAKEPP_FUNCTIONS(1)

πŸ“¦ version 2.0.98.5, in debian bookworm, ubuntu jammy, ubuntu noble

πŸ’– MAKEPP_FUNCTIONS(1) πŸ’–

🌸 NAME 🌸

makepp_functions β€” Functions in makepp πŸ¦‹


✨ DESCRIPTION ✨

okay bestie, listen πŸ‘‘ makepp has a whole ensemble cast of builtin functions, and they're lowkey iconic. here's the full roster, sorted by letter because we're organized like that πŸ’…:

  • A: absolute_filename, absolute_filename_nolink, abspath, addprefix, addsuffix, and
  • B: basename
  • C: call
  • D: dir, dir_noslash
  • E: error
  • F: filesubst, filter, filter_out, filter_out_dirs, findfile, find_first_upwards, find_program, findstring, find_upwards, first_available, firstword, foreach
  • I: if, iftrue, infer_linker, infer_objects, info
  • J: join
  • M: make, makemap, makeperl, map, "mktemp"
  • N: notdir
  • O: only_generated, only_nontargets, only_phony_targets, only_stale, only_targets, or, origin
  • P: patsubst, perl, phony, prebuild, print
  • R: realpath, relative_filename, relative_to
  • S: shell, sort, strip, subst, suffix
  • T: temporary
  • W: warning, wildcard, word, wordlist, words
  • X: xargs

Any expression of the format $(name), where name is not the name of a variable, or $(name arg1 arg2 arg3) is interpreted as a function call. The name may contain letters, underscores, or hyphens; to avoid confusion, you may use hyphens or underscores interchangeably, since internally hyphens are converted to underscores. Evaluating such an expression simply invokes a Perl subroutine. If name is preceded by & it runs the builtin command or script of that name within the makepp process, and returns the standard output. This requires perl to be built for PerlIO. If the name does not name a function it is transformed to an invocation of call.

As with variables you have a choice of $(name ...) or ${name ...}. If you want to embed the same parenthesis, it must be paired, the other doesn't matter: $(name ...(){... or ${name ...{}(...}. (However for map and perl the first closing paren ends the expression.) Doubling allows the arguments to span several lines. The newlines are then treated as spaces, except maybe in define. There is also the syntax $[name ...] or $[[name ...]], which gets evaluated while reading the makefile, before grokking rules and other constructs.

Makepp has a number of builtin functions which may be useful. It supports almost all of GNU make's textual functions (see GNU make's documentation for details), and some of its own. You can define Perl subroutines to do whatever you like. See the sub statement and the section on extending makepp for more details.

That's the vibe check, bestie. now let's get into the functions themselves πŸ’–

πŸŽ€ Conditional Functions πŸŽ€

  • and condition1[,condition2[,condition3...]] β€” The and function provides a "short-circuiting" AND operation. Each argument is expanded, in order. If an argument expands to an empty string the processing stops and the result of the expansion is the empty string. If all arguments expand to a non-empty string then the result of the expansion is the expansion of the last argument.
  • if string, result-if-string-not-blank[, result-if-string-blank]
  • iftrue string, result-if-string-true[, result-if-string-false] β€” An alternative to the ifeq, etc., statements. If the string is not blank (i.e., the condition is true), the second argument (the "then" clause) is returned (after variable expansion); if the string is blank, the third argument (the "else" clause) is returned.

For example,

CFLAGS := $(if $(filter gcc egcc, $(CC)), -g -Wall, -g)

defines CFLAGS to be -g -Wall if the variable CC is either gcc or egcc, and -g otherwise. (This is what the default build rules do.)

iftrue is similar to if, except that the string 0 is treated as blank.

  • or condition1[,condition2[,condition3...]] β€” The or function provides a "short-circuiting" OR operation. Each argument is expanded, in order. If an argument expands to a non-empty string the processing stops and the result of the expansion is that string. If, after all arguments are expanded, all of them are false (empty), then the result of the expansion is the empty string.

πŸ¦‹ File and Filename Functions πŸ¦‹

  • absolute_filename files
  • abspath files β€” Converts relative filenames into absolutes without . or ... For example, $(absolute_filename xyz.c) might return /usr/src/our_project/subdir/xyz.c.
  • absolute_filename_nolink files
  • realpath files β€” Like absolute_filename, but ensures that symbolic links are resolved.
  • basename filenames β€” The basename is the entire file name (with the directory), minus the text after and including the last period. E.g., $(basename myfile/version-1.0-module.c) is myfile/version-1.0-module
  • dir filenames β€” Extracts the directory part of each file in the filename list, including the trailing slash. Returns ./ if there is no directory in the filename.
  • dir_noslash filename β€” Same as $(dir ) except that it doesn't return the trailing slash.
  • filesubst pattern, substitute, words β€” Perform a pattern substitution on file names. This differs from patsubst in that it will perform correctly when alternate names for directories are given (as long as they precede the percent sign). For example,
$(filesubst ./src/%.c, %.o, $(wildcard src/*.c))

will work with filesubst but not with patsubst.

  • filter_out_dirs filenames β€” Returns all filenames that do not refer to directories.
  • findfile filename, path β€” Finds a file in the specified path, or in the environment variable PATH if nothing is specified. This can be useful for finding binaries or include files. For example,
TCL_INCLUDE := -I$(dir_noslash $(findfile tcl.h, \
    /usr/local/stow/tcl-8.4.5-nothread/include \
    /usr/include/tcl8.4 /usr/include/tcl \
    /net/na1/tcl8.4a3/include /net/na1/tcl8.4a3/include))

This locates the file tcl.h by searching all of the above directories. The absolute path to the file is returned. Then $(dir_noslash ) extracts that directory, and it is put into the include path.

  • find_program name β€” Return the first program in the list that can be found in the PATH. This is useful when there are multiple equivalent programs that may be used, and you just want to pick one of them. For example, here is the default definition of several common variables that makepp supplies if you do not put one in your makefile:
CC = $(find_program gcc egcc pgcc c89 cc) # and more, depending on machine
F77 = $(find_program f77 g77 fort77)
CXX = $(find_program g++ c++ pg++ cxx CC aCC)

If none of the programs is found, $(find_program ) returns the string not-found, and logs what was not found. This usually won't result in a functional makefile, but it will tend to make for better error messages. For example, if you do something like this:

%.o : %.c
    $(CC) $(inputs) -o $(outputs)

and makepp can't find a C compiler in the list above, it will substitute not-found. Otherwise the shell would attempt to execute the source file and the resulting error message might be really strange.

  • find_upwards filename β€” Searches for a file of the given name in the directory ., .., ../.., ../../.., etc., until the file is found or the root directory is reached or the directory is located on a different file system. (This last requirement is to prevent problems with automounters or hung network filesystems.) If you have a RootMakeppfile, that is also a barrier which prevents searching higher.

For example, if you have a project with many levels of subdirectories, you could include this common fragment in all of the makefiles (e.g., by using the include statement):

TOP_LEVEL_INCLUDE_DIR := $(find_upwards includes)
                            # Searches for a directory that contains the
                            # includes subdirectory.

%.o : %.c
    $(CC) $(CFLAGS) -I$(TOP_LEVEL_INCLUDE_DIR) -c $(input) -o $(output)

Another problem that find_upwards can help solve is locating the top-level directory of a build. Often it is useful to define a variable like this:

TOP := ../../..

if you have some important information located only in the top-level directory. But this is hard to maintain, because the number of .. is different for different levels of the directory tree. Instead, you can use find_upwards to locate a file which is known to be present only in the top level directory. Suppose, for example, that the file LICENSE is located only in the top level directory. Then you could do this:

TOP := $(dir_noslash $(find_upwards LICENSE))

$(find_upwards LICENSE) returns the full path of the license file; $(dir_noslash ...) strips off the filename, returning only the directory.

(Note that the include statement automatically searches upwards for files, so there is no need to do something like this:

include $(find_upwards top_level_rules.mk)

Instead, you can just do

include top_level_rules.mk

and it will work just as well.)

If the file is not found, find_upwards will abort the build with an error message.

If you specify more than one file, find_upwards will search for the first one, then the second one, and so on. In other words,

$(find_upwards file1 file2)

is equivalent to

$(find_upwards file1) $(find_upwards file2)

If you want to look for any one of the files, then use find_first_upwards instead.

  • find_first_upwards file1 file2 ... β€” This function behaves like find_upwards except that it returns the first file of any files in the list that it finds. Specifically, it checks the current directory for any of the files in the list, and returns the first file which exists or can be built. If none of the files exist or can be built in that directory, it checks .., then ../.., etc., until it reaches either the root directory or a directory which is located on a different file system.
  • first_available file1 file2 ... β€” Return the first file in a list that exists or can be built. This can be useful for adapting your makefiles to work on several different machines or networks, where important files may be located in different places. For example, here's a line from one of my makefiles:
TCL_LIB = $(first_available \
    /usr/local/stow/tcl-8.4.5-nothread/lib/libtcl8.4.so \
    /usr/lib/libtcl8.4.so /usr/lib/libtcl.so \
    /net/na1/tcl8.4a3/lib/libtcl8.4.a \
    /net/na1/tcl8.4a3/lib/libtcl8.4.sl)

So this line goes hunting for the Tcl library in every single one of those places, and it stops dead at the first one it finds. Then your link command just drops $(TCL_LIB) in there and boom πŸ’₯ β€” you get the appropriate Tcl library. Chef's kiss.


✨ infer_linker file1 file2 ... ✨

Hand it a list of object files and it'll build them first if they haven't been built yet. Then it figures out whether they depend on a Fortran, C++ or a C source and hands back the matching compiler (which lowkey knows how to link way better than ld, fr fr).


✨ infer_objects file1 file2 ..., pattern ✨

$(infer_objects object1.o object2.o, *.o)

Okay bestie, this one is iconic. If you stick to the standard conventions for header file names, makepp can literally guess which .o or .lo files need to be linked into your program. I use this to pull files out of a library directory that holds modules used across a ton of different programs. Instead of building a library .a file and making the linker dig out the relevant modules, makepp just picks the relevant modules for you. Which means only the relevant modules get compiled. Efficiency slay ✨

Makepp's algorithm for inferring object dependencies rides on the convention that the implementation of all classes or functions defined in a header file xyz.h are compiled into an object file called xyz.o (or xyz.lo). So the algorithm starts with one or a few objects that we know have to be linked into the program. It peeks at which files were included with #include in those sources, and tries to find matching object files for each of the include files.

$(infer_objects ) needs to be mentioned in the dependency list of a program, like this:

myprog: $(infer_objects main.o another_object.o, \
            **/*.o /other/library/dirs/**/*.o)
    $(CXX) $(inputs) -o $(output) $(LIBS)

The $(infer_objects) function takes two arguments (separated by a comma, as shown). The first is one or a few object files that are known to be required (wildcards are permissible here 🫢). The second is a list of possible objects (normally you'd use a wildcard here) that could be linked in if necessary. The return value from this function is a list that contains first all of the objects in the first argument, and then after those, all additional objects that were contained in the second argument that are required by the objects in the first argument.

Example time πŸ’… Suppose main.o comes from main.cpp, which includes my_class.h. $(infer_objects) goes looking for files with the name my_class.o. If exactly one such file is found, it gets added to the list. (If two object files my_class.o are found in different directories, a warning message is printed. πŸ’€) infer_objects also examines my_class.cpp to see what it includes, and what additional object files are implied.


✨ mktemp ✨

mktemp
mktemp prefix
mktemp prefixXXX
mktemp /

Returns an unpredictable temporary filename, which does not currently exist. No name pointing to the same file is returned twice, even with different relative paths, within one makepp run (except possibly with traditional recursive make, or if Perl code running within a rule action calls f_mktemp). At the end of the makepp run all files returned by this function are deleted, if they exist (again except for those returned by this function in Perl code running within a rule).

Any number of upper case Xs at the end of the argument are replaced by that many random letters and digits. The more there are, the less likely this is to collide with other processes, so if you give a prefix like /tmp/abc., you should have enough Xs. If there is more than one X, the first character comes from the process id. If there are none, it is as though there were ten, which is supposedly enough (8.4e17 possibilities or 3.7e15 on Windows). If there is no argument, the prefix defaults to tmp. in the current directory.

Note that you don't want to give such a name as rule targets and dependencies. The result would be correct, but it would be recreated every single time you run makepp. 😭

Also, as it is always different, you should use this in a rule action only if you use :build_check ignore_action:

TMPFILE ;= $(mktemp)        # 1 call; "=" would mean 3 calls: 3 files
A-count B-count: :build_check ignore_action
    produce-As-and-Bs >$(TMPFILE)
    &grep -c /A/ $(TMPFILE) -o A-count
    &grep -c /B/ $(TMPFILE) -o B-count

Or you should export it and let the Shell evaluate it:

export TMPFILE ;= $(mktemp)
A-count B-count:
    produce-As-and-Bs >$$TMPFILE # makepp doesn't see the var value
    fgrep -c A $$TMPFILE >A-count
    fgrep -c B $$TMPFILE >B-count

The last form repeats the previous return value, so you can use it in a pattern rule:

%.x: %.y
    &grep foo $(input) -o $(mktemp)
    &sed bar $(mktemp /) -o $(output) # Operate on the output of &grep

✨ notdir filenames ✨

Returns the non-directory portion of the filename(s), i.e., everything after the last slash if there is one, or the whole filename otherwise. Simple. Clean. Iconic.


✨ only_generated filenames ✨

Returns only those filenames in the list that were generated by makepp and not since modified, according to the build info file.

This function is useful in clean target rules (though of course makeppclean is the preferred variant πŸŽ€):

$(phony clean):
    &rm -f $(only_generated **/*)

✨ only_nontargets filenames ✨

Returns only those filenames in the list that are not targets of any rule (either explicit or pattern rules). You may specify a wildcard (see the $(wildcard ) function for more details on makepp's wildcards). This can be used for generating a distribution target, for example:

.PHONY: distribution

distribution:
    &mkdir our_product-$(VERSION)
    &cp $(filter-out %~, $(only_nontargets *)) our_product-$(VERSION)
    tar cf - our_product-$(VERSION) | gzip -9c > our_product-$(VERSION).tar.gz

In this case, the $(only_nontargets *) returns every file in the current directory that is not a target of some rule. The $(filter_out %~, ...) removes editor backups. Bye backup files πŸ¦‹

Similar to only_targets (see above), only_nontargets only knows about targets that have been defined already. This is only a problem if you use it to define variables with the := assignment; if you use it in the dependency list or in the body of a rule, all other rules will already have been seen.


✨ only_stale filenames ✨

Returns only those filenames in the list that were generated by makepp and not since modified, according to the build info file, but are no longer targets of any rule.

This function is useful for ensuring that there are no dependencies on such files, without forcing a clean build of all of the targets:

$(phony flush):
    &rm -f $(only_stale **/*)

Actually, it's probably better instead to write a script that calls makepp to generate the list of stale files, and then have that script remove all of the listed files that aren't currently under source control, just in case a generated file becomes a source file. Makepp doesn't have such a function built in because makepp is (and probably ought to remain) agnostic about source control.


✨ only_targets filenames ✨

Returns only those filenames in the list that are actually targets of some rule (either explicit or pattern rules). You may specify wildcards (including makepp's special wildcard, **) in the filenames. (See the $(wildcard ) function for more details.) This can be used for a clean target, for example:

.PHONY: clean

clean:
    &rm -f $(only_targets *)

Now if you type makepp clean, it will delete everything it knows how to build. But don't create a clean target, use makeppclean instead! 🫢

Another place where it may be useful is to avoid including stale .o files in your build. For example, if you build a library like this:

mylib.a: *.o
    &rm -f $(output)
    $(AR) cr $(output) $(inputs)

and then you delete some source files but forget to delete the corresponding .o files, the .o files will still be around. This means they will still be incorporated into the library despite the fact that they are not useful any more. If you modify your rule like this:

mylib.a: $(only_targets *.o)
    &rm -f $(output)
    $(AR) cr $(output) $(inputs)

then this problem won't occur. Problem solved πŸ’…

Note that this refers only to files that are known to be targets at the time you invoke only-targets. If only_targets appears in the dependencies or actions of a rule, then all possible targets will be known because dependencies and actions are not evaluated until the rule is executed. However, if you evaluate try to evaluate it earlier in the makefile with a := variable like this:

ALL_TARGETS := $(only_targets *)

target1: dependency1
    actions

target2: dependency2
    actions

then only_targets will not know about the subsequent rules. πŸ™ˆ

Similarly, only_targets doesn't know about targets produced in makefiles that are loaded with recursive make. (But you shouldn't be using recursive make anyway; use use the load_makefile statement, or implicit makefile loading instead.)


✨ relative_filename file1 file2 file3[, slash] ✨

Returns the name of those files relative to the current directory (the one the makefile is in). This can also be used to clean unnecessary ./ and other junk from the path:

DIR := .
SUBDIR := ..
FNAME := $(DIR)/../otherdir/$(SUBDIR)/files
X := $(relative_filename $(FNAME))

If slash is true (usually 1) the returned filenames are guaranteed to contain a slash by prepending ./ if necessary, so that you can use it as an executable name without worrying about the command search path overriding the directory location. No more path drama 🌈

If the path goes by the root directory β€” the parent of either your home directory or the $(ROOT) of your build system, or on Windows a drive's root (and depending on your environment, this also happens for /cygdrive/c or /c) β€” then an absolute path gets returned instead. πŸ’…

relative_to file1 file2 file3[, directory]

Returns the name of those files relative to the directory you specified. This is so useful when, for whatever reason, you have to run a command from a different directory (the default is the current directory):

source_backup.tar:
    cd .. && tar cf $(relative_to $(output), ..) $(relative_to ., ..)

suffix names...

Extracts the suffix of each file name in names. If the file name contains a period, the suffix is everything starting with the last period. Otherwise the suffix is the empty string. Which lowkey means the result can be empty even when names isn't, and if names has multiple file names in it, the result may contain fewer file names. 😭

For example,

$(suffix src/foo.c src-1.0/bar.c hacks)

produces the result .c .c.

temporary words

Lets makepp know that the targets you named may get removed by the rule that generates them. Similar to phony, except makepp expects that a real file of that name may be affected by the rule. A rule is not executed if only its temporary targets are out-of-date.

wildcard pattern

Returns the sorted names of all files matching the given pattern that exist, or those files that don't exist yet but can be built based on the rules makepp knows about at the exact moment it evaluates the expression. ✨ And that last bit is where it differs from rule input wildcards β€” those apply even to files created by rules found later.

Makepp supports all the usual shell wildcards (*, ?, and []). It also has a wildcard ** which matches any number of intervening directories. (This idea was stolen from zsh, iconic.) For example, **/*.c matches all the .c files in the entire source tree. objects/**/*.o matches all the .o files contained anywhere in the subdirectory objects or any of its subdirectories or any of their subdirectories. The ** wildcard will not follow soft links to directories at any level, nor will it attempt to enter directories which exist but cannot be read. Also files and directories which exist but cannot be read will not be returned by $(wildcard ). πŸ™ˆ

πŸŽ€ String Functions πŸŽ€

addprefix prefix, words

Prepends the prefix string to each of the words. This is mostly for GNU make compatibility; using rc-style expansion, this can be done in a way more readable fashion, like this:

MODULES := a b c d
X_OLD_STYLE := $(addprefix $(OBJDIR)/, $(addsuffix .o, $(MODULES)))
X_NEW_STYLE := $(OBJDIR)/$(MODULES).o   # Isn't that easier to read?

addsuffix suffix, words

Appends the suffix string to each of the words. Also mostly for GNU make compatibility; using rc-style expansion, it's cuter like this:

X_OLD_STYLE := $(addsuffix .o, $(MODULES))
X_NEW_STYLE := $(MODULES).o

call variable[, words]...

The function call is unique in that it can be used to regard variable as a parameterized function. You can assign a complex expression to variable and use call to expand its contents to different values, parametrized by words later on. In other make systems, a variable that's used mainly for the purpose of being expanded via call is called a macro.

During expansion of the macro, the temporary variables $1, $2, ... refer to the arguments given to call during its invocation. The variable $0 will be expanded to the name of the macro (i.e. variable) that call is currently expanding.

There is no limit on how many arguments a macro may be called with or how many parameters a macro may expect. If you pass more arguments to call than the macro needs, all the exceeding arguments get discarded. If you pass fewer arguments than a macro expects, all the exceeding parameters collapse into the empty string.

First, a simple example:

rest = $(wordlist 2, $(words $(1)),$(1))
list = A B C D E
butfirst := $(call rest,$(list))

Here, the variable $(butfirst) will contain the list B C D E.

And now for a more complex example to show what is possible:

rest = $(wordlist 2,$(words $(1)),${1})
mymap = $(if $2,$(call $1,$(firstword $2)) $(call $0,$1,$(call rest,$2)))
downcase = ${makeperl lc("$1")}

UCWORDS = ALL THESE WORDS ARE UPCASE
DCWORDS := $(call mymap,downcase,$(UCWORDS))

Now $(DCWORDS) contains all these words are upcase. By the way: it makes no difference whether we access the arguments via $1, ${1} or $(1) within a macro. πŸ¦‹

You can directly use the variable as though it were a function, if there is no function of that name. This is internally converted to call, so these are equivalent:

discussion = The $0 turned into $1 $2.
direct = $(discussion an,argument)
called = $(call discussion,an,argument)

It might seem debatable whether $[call] should also expand the macro's $[] expressions, or whether a function should always do the same thing, no matter how it is called. The latter was chosen, because with normal make syntax it would be impossible to get $[1], $[2]... into a variable (they'd get replaced by nothing, before the assignment even takes place). Hence, if you have a macro for defining a rule, you want expressions like $(output) to be seen when the rule gets parsed, so you must protect them from call:

define myrule
    $2: $1
        mycommand $$(input) -o $$(output)
endef
$[myrule myinput,myoutput]

filter patterns, words

Returns all words in the list that match the patterns. Patterns may simply be other words, or filename wildcards (i.e., *, ?, and [a-z] are recognized), or they may have a % character, which means to match any string at that point (same as *).

filter_out patterns, words

Returns all words in the list that do not match the patterns. Patterns may simply be other words, or filename wildcards (i.e., *, ?, and [a-z] are recognized), or they may have a % character, which means to match any string at that point (same as *).

For example:

libproduction.a: $(filter_out test_*, $(wildcard *.o))

will put all .o files which exist or can be built β€” except those beginning with test_ β€” into libproduction.a. πŸ’–

findstring find, in

Return find, if it is a substring of in.

firstword words

Return the first word.

map words, perlcode

makemap words, perlcode

Similarly to Perl's map, applies perlcode to each word in turn and returns the results. The first variant is plain Perl code, while the second variant first passes the perlcode through Make-style variable expansion. The words are expanded in both cases.

The words are in $_ and are returned unless you undef $_. This is intended for modifications not easily handled by patsubst. Only the first comma is a separator β€” any others are considered part of the perlcode.

# Switch words.  Double parens, to allow parens in perlcode, or use ${}:
X = $((map $(VALUES), s/(.+)-(.+)/$2-$1/))
# You can use make expressions, but then you must use $$ for Perl $:
Y = $(makemap $(VALUES), tr/$(OLDCHARS)/$(NEWCHARS)/ or $$_ = 'failed')
# You can eliminate candidates:
Y = $(map $(VALUES), undef $_ if /no_good/)

join words1, words2

Do a pairwise join of the first words and the second words.

patsubst pattern, substitute, words

Performs a substitution on each word in the word list. A % character matches any string. This is best illustrated by an example:

OBJS = $(patsubst %.c, object_dir/%.o, $(C_SOURCES))

takes every file in C_SOURCES and returns the name of an object file in object_dir. Sometimes it is more concise to use a substitution reference, e.g., the above could have been written as

OBJS = $(C_SOURCES:%.c=object_dir/%.o)

sort word1 word2 word3 ...

Sorts the words in lexical order and removes duplicates.

strip string

Removes leading and trailing whitespace from string and replaces each internal sequence of one or more whitespace characters with a single space. Thus, $(strip a b c ) results in a b c.

subst from,to,text

Performs a textual replacement on the text text: each occurrence of from is replaced by to. The result is substituted for the function call. For example,

$(subst ee,EE,feet on the street)

substitutes the string fEEt on the strEEt.

word n,text

Returns the nth word of text. The legitimate values of n start from 1 at the beginning or backwards from -1 at the end. If n is bigger than the number of words in text, the value is empty.

wordlist indexlist, words

wordlist firstindex, lastindex, words

In the first form you supply a list of indices (counting from 1 at the beginning or backwards from -1 at the end) to select the words you want. In the second form you specify the range of words you want returned.

words text

Returns the number of words in text.

🌈 Miscellaneous Functions 🌈

foreach var,list,text

The first two arguments, var and list, are expanded before anything else is done; note that the last argument, text, is not expanded at the same time. Then for each word of the expanded value of list, the variable named by the expanded value of var is set to that word, and text is expanded. Presumably text contains references to that variable, so its expansion will be different each time. πŸ‘‘

This simple example sets the variable files to the list of all files in the directories in the list dirs:

dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))

Here text is $(wildcard $(dir)/*). The first repetition finds the value a for dir, so it produces the same result as $(wildcard a/*); the second repetition produces the result of $(wildcard b/*); and the third, that of $(wildcard c/*).

This example has the same result (except for setting dirs) as the following example:

files := $(wildcard a/* b/* c/* d/*)

When your text is doing the absolute most, you can give it a name with an extra variable and suddenly everything is readable:

find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))

So here's the tea on find_files in that example: we used plain = on purpose, bestie, because that makes it a recursively-expanding variable β€” meaning its value still holds a live function call that gets reexpanded under foreach's control. If you'd used a simply-expanded variable instead, wildcard would only fire once, right when find_files gets defined, and that is not the vibe we want. πŸ’…

Note: do NOT confuse this cutie with the $(foreach) special variable. They are two different girlies, ok?? 😭

πŸ“’ info text, warning text, error text

These three spit out your text and hand back nothing (literally returning the nothing, iconic). The first one goes to STDOUT, the second goes to STDERR, and the third is the dramatic one β€” it additionally aborts processing. No cap. πŸ’€


πŸ› οΈ prebuild targets, make targets

Returns its argument verbatim, but first it builds every file you listed. So useful when a file is needed while a make expression is being evaluated β€” which lowkey happens all the time when your build's file set is computed by some shell commands. Like:

file_list :
    # shell commands to compute a list of files to put into the program

my_program : $(&cat $(prebuild file_list))

If you need that list in more than one rule, it's highkey more efficient to use an expand-at-most-once variable:

file_list ;= $(&cat $(prebuild file_list))

my_program1 : a.o $(file_list)

my_program2 : b.o $(file_list)

Now if you'd just written $(&cat file_list) instead, makepp would not force file_list to be up-to-date before it runs the shell command. Using $(prebuild ) is the best way to solve this, periodt. You might be tempted to try other things, like:

my_program : file_list $(&cat file_list)

…but that won't work, bestie, because $(&cat file_list) gets evaluated before makepp even attempts to build file_list. πŸ™ˆ


🌸 only_phony_targets names

Hands you back only those names in the list that are phony targets of some rule (explicit or pattern rules, both count). You can throw wildcards in there too, including makepp's special wildcard ** for the filenames. (Peep the $(wildcard ) function for more details on that.) This is perfect for grouping targets, like:

$(phony tests): $(only_phony_targets */**/tests)

πŸ” origin variable

Give it the name of a variable and it tells you where its value comes from. Sherlock mode. πŸ•΅οΈ


πŸͺ„ perl perlcode, makeperl perlcode

Evaluates perlcode in a block and returns the result. The first variant is plain Perl code, while the second one first passes the perlcode through Make-style variable expansion. ✨

Note: as with all functions, the function delimiter you used may not appear within the perlcode outside of single or double quoted strings. But you can double it, like in the last example:
VAR = 1
VAR1 = ${perl ($VAR + 1) * 3}
VAR2 = $(perl do { $VAR *= 3; return $VAR + 1 } if $VAR)
VAR3 = $(makeperl $(VAR1) * 3 + $$VAR) # one Make var and one Perl var
VAR = $((perl if( ... ) { ... }))

πŸ‘» phony words

Says "hey, this list of words is actually phony targets" and returns the list of targets. It's meant to be used like this:

$(phony all): my_program

$(phony clean):
    &rm -f *.o my_program

You can also declare one or more targets as phony with a line like this anywhere in your makefile:

.PHONY: all clean

Outputs the text and returns it. Mostly a debugging bestie for when you're sitting there like "why is variable substitution giving me this result??" 😭 For example,

XYZ := $(print $(patsubst %.c, %o, $(SOURCE_FILES)))

will print out the result of the patsubst call. But this:

XYZ := $(patsubst %.c, %o, $(print $(SOURCE_FILES)))

will print out the last argument to the patsubst call. Same function, different plot twist. πŸŽ€


🐚 shell shell-command

Returns the output from the given shell command, with newlines replaced by spaces.

Note: as with all functions, the function delimiter used may not appear within the shell-command outside of single or double quoted strings. But you can double it, like in the second example:
date = $(shell date)        # better: $(perl scalar localtime)
VAR = ${{shell f() { echo hello; }; f}}

πŸ“¦ xargs command,arguments[,suffix[,length]]

Returns a newline-separated list of commands that each begin with the specified command, and end with as many elements of the list as possible without going over length (default 1000) characters.

The whole point is dodging the command length limit on your system. For example, if you've got a ton of generated files, you'd probably want your clean target (which you should not have, because makeppclean is more efficient, just saying πŸ‘€) to look something like:

$(phony clean):
    $(xargs $(RM), $(only_targets **/*))

It also has the side-effect that no command whatsoever gets generated if the list happens to be empty. But honestly in this case it'd be better to use the builtin &rm, because the arguments to builtin commands are only limited by Perl's memory:

$(phony clean):
    &rm -f $(only_targets **/*)

If you specify a third argument, it gets used to postfix each command. Super handy for redirectors, e.g. (though again, &echo would help here):

manifest:
    &rm -f $@
    &touch $@
    $(xargs echo, $(only_nontargets **/*), >> $@)

Some of this documentation is based on the GNU make documentation. πŸ“š

Please note: if a function gets called during makefile initialization β€” like the expansion of export variables, for instance β€” error or warning messages will report line number 0. Yes, zero. We know. 😭

πŸ‘‘ AUTHOR

Gary Holt (holt-makepp@gholt.net)


And that's a wrap on this whole page, besties! πŸŽ€ This was written against perl v5.32.0, dated 2021-01-06, and it lives at MAKEPP_FUNCTIONS(1). Go forth and make beautifully, you absolute icon. πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from