MAKEPP_RULES(1)
π MAKEPP_RULES(1) π
okay bestie, you're here because you wanna tell makepp how to build stuff, and honestly? same. This is the makepp_rules manual page, and we're gonna make it β¨make senseβ¨. No cap. π
NAME β¨
makepp_rules -- How to tell makepp to build something
DESCRIPTION πΈ
First up, a lil glossary of the special tokens you're gonna see. Lowkey iconic. β¨
?: &,
-,
@, B: :build_cache,
:build_check, D: :dispatch, E: :env, I: "ignore_error",
:include, L: :last_chance, M: makeperl, N: "noecho", P: :parser,
"perl", S: :signature
So, a rule is what tells makepp how to build a file or a class of files. Makepp supports the same rule syntax as other implementations of make, plus some additions of its own. It's giving "we respect tradition but we also slay". π
A rule has the general format
target_expression : dependency_expression [ : optional arguments]
actions
The list of targets may not contain any automatic variables (except $(foreach)). The dependency list may contain only automatic variables referring to the target (i.e., $(output), $(outputs), or their synonyms). The action may contain any automatic variables. So like, know your limits, bestie. π
If makepp decides that the rule needs to be executed, each line of the rule is executed sequentially, and if any returns a non-zero status, the remainder are not executed (and makepp aborts with an error unless you specified the -k option on the command line.) Each action should be only one line. If an action is too long to write conveniently on a single line, you can split it into several lines and put a backslash to indicate that the several lines should be combined into one. Backslash is the glue, fr fr. π₯
In order to distinguish actions from the next rule, the action should be indented more than the line containing the targets and dependencies. Unlike other implementations of make, makepp doesn't really care how much you indent it or whether you use tab characters rather than spaces. To keep backward compatibility with traditional make, the rules makepp uses to decide when actions end and the next rule begins are somewhat complicated:
- The first action line must be indented more than the line containing the target.
- If a line is indented by one tab character or 8 spaces or more, then it is considered an action line.
- A blank line or a comment line with the
"#"character at the right margin ends the rule, unless the next non-blank line is indented more than 8 spaces (or more than one tab). - If a line is indented as much or more than the first action line, then it is considered an additional action line.
There are a few special action items:
&β This symbol shall be followed by a command name and any number of arguments. Shell syntax is not understood fully here, only single and double quotes and backslashed characters within, as throughout makepp. The command name either leads to a functionc_nameto be called with the remaining strings as arguments. If such a function can not be found, this is identical to callingrunfrom aperlblock.
This allows efficiently calling a built-in, makefile-provided or external command. The prefix & has been chosen because it is the function invoker in Perl, and because at the beginning it is illegal in Shell.
$(ROOT)/include/%.h: %.h
&ln $(input) $(output)
noecho,@β Normally, each shell command is printed as it is executed. However, if the first word of the action isnoecho(or if it begins with the character@), then the command is not printed. For example,
%.o: %.cxx
noecho $(LIBTOOL) --mode=compile $(CC) -c $(input)
This means that when the libtool command is executed, it is not printed. (Libtool itself usually prints the modified command that it executes, so it's redundant to print it twice.)
ignore_error,-β Normally, if the shell command returns a non-zero status, then makepp aborts because the command failed. However, some programs incorrectly set the status on exit, or there may be an error which really isn't fatal and shouldn't abort the whole compilation. You can cause makepp to ignore the return status by specifyingignore_erroras the first word of the command line (or-as the first character). For example,
$(phony distribution):
ignore_error rm -r my_program-$(VERSION) # Get rid of previous junk.
&mkdir my_program-$(VERSION)
&cp $(FILES) my_program-$(VERSION)
tar cf my_program-$(VERSION).tar my_program-$(VERSION)
This command makes a directory, copies a bunch of files into it, and then puts everything into a tar file for distribution. It's a good idea to clean out the previous contents of the directory, if there was anything there previously, and that's what the first line does. The rm might fail, but its return status is ignored.
perl,makeperlβ This is essentially the same as the perl statement, but it is performed each time when running the rule, not when reading the makefile. The first variant is plain Perl code, while the second variant first passes the statement through Make-style variable expansion.
For the two possibilities of putting the braces of the body, see the explanation at perl_perlcode in makepp_statements. Note that the third variant explained there makes no sence here, because all action lines must be indented. You must signal failure in Perl statements, by calling die.
Per rule the Perl statements are currently evaluated in a common subprocess, except on Windows. That means they have only read access to any makefile variables. It is also the process which executes non-Perl actions. So calling exec or exit will confuse makepp. But this may change in the future. For an efficient way to call Perl scripts, see the previous item & or run.
$(phony version):
noecho perl {{ # $(target) & $(VERSION) from Perl:
print "This is ".f_target()." $VERSION\n";
}}
echo You can mix this with Shell commands
-makeperl { print "This is $(target) $(VERSION)\n" }
There are several different kinds of rules, each with different purposes. Let's get into it. π¦
Explicit Rules π
target1 target2: dependency1 dependency2 ...
actions to be performed
This syntax specifies that in order to make either target1 or target2, all the files dependency1, dependency2, etc., must already have been made. Then the given actions are executed by the shell to make the targets.
The first explicit rule in a file is the default target, and is made if you do not specify any targets on the command line. So it's the main character, periodt. π
Unlike traditional make programs, makepp usually assumes that one invocation of the action makes all of the targets (unless there are no dependencies). For example, one invocation of yacc creates both output files for this rule:
y.tab.c y.tab.h : parser.y
$(YACC) -d parser.y
Note that other implementations of make do not have a concept of a single command producing multiple output files, and so when you specify multiple targets they will execute the rule once per target. Makepp will revert to this behavior if it looks like this is an old-style makefile. Specifically, it will execute the rule once per target, instead of just once overall, if all of the following are true:
- The rule action mentions the automatic variable
$@. (The synonyms$(output)or$(target)do not trigger this behavior.) - The rule action does not mention the automatic variable
$(outputs)(or its synonym$(targets)). - This is not a pattern rule, and there is no foreach clause.
For example,
all test install:
for subdir in $(SUBDIRS); do cd $$subdir && $(MAKE) $@; cd ..; done
is a common idiom in makefiles, and makepp supports it. (Note that you should never use recursive make in any new makefiles you write--use the load_makefile statement, or implicit makefile loading instead.)
π¨ bestie, seriously, never use recursive make in any new makefiles you write. Use the load_makefile statement, or implicit makefile loading instead. It's giving outdated. π
If you want to have the same rule executed once for each target (e.g., because the targets have similar commands), it's preferable to use either a pattern rule (see below) or a foreach clause. For example, if with a traditional make program you would write:
a b c d:
do_something to build $@ > $@
in makepp, you would probably want to write it like this:
$(foreach) : : foreach a b c d
do_something to build $(output) > $(output)
Phony targets π¦
A phony target is a target that will never actually exist in the file system; it's just a way of getting makepp to build some targets and possibly execute some additional commands. It's giving "I'm not real but I'm still that girl". β¨
A typical phony target is all, which usually is used to cause everything that can be built to be built, like this:
all: prog1 prog2 subdir/prog3 subdir2/libmine.a
@&echo "All done!"
If you type makepp all, or if you put all as the first explicit target in your makefile (which is typical) and just type makepp, then it will cause all the dependencies to be built, then it will print "All done!". At this point, makepp will look for the file ./all and will discover that it doesn't exist. It will complain loudly. π
To keep makepp from expecting the file ./all to exit, you need to tell it that it's a phony target. Just put a line like the following in your makefile (it makes no difference where):
.PHONY: all
An equivalent alternative which is sometimes more convenient is to use the $(phony ) function, like this:
$(phony all): prog1 prog2 subdir/prog3 subdir2/libmine.a
Phony targets in one makefile can refer to phony targets in another makefile. This is often done with the clean target, like this:
# Top level makefile:
# lots of rules and stuff here
# ....
$(phony clean): subdir1/clean subdir2/clean
&rm -fm my_program
Then in the subdirectories, the makefiles might read like this:
# Makefile in a subdirectory
# ...
$(phony clean):
&rm -fm $(wildcard *.o *.a)
But nowadays you would use the makeppclean command, instead of a clean target. It's the glow-up. π
Wildcards π
It is safe to specify wildcards in the dependency list. Wildcards match not only files that exist, but files which can be created given the rules in the makefile. For example, to build a library from all .o files in a directory, you could write this:
libmine.a: *.o
&rm -f $(output)
ar cr $(output) $(inputs)
...this whole thing works even if none of the .o files have even been created yet, bestie, because makepp's wildcards match files that don't exist yet but can be built. π It'll even pick up files whose rule gets discovered later (same makefile, or a makefile that hasn't been read yet). That last part is where it differs from the wildcard function β that one's stuck with the rules it already knows, because it has to hand back its result the instant it gets expanded. π€
Makepp supports all the usual shell wildcards β *, ?, and []. π And then it has the extra-special wildcard **, which matches any number of intervening directories. (This idea was stolen from zsh, iconic behaviour honestly.) So for example, **/*.c matches all the .c files in the entire source tree. And 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. It also will never return phony targets. No cap.
Makepp's wildcards will ignore files or directories that exist but can't be read. Which, honestly? Fair. Such files can't be used in the build process anyway. π Putting unreadable files in a directory is primarily useful to inhibit the automatic import of the given file from a repository.
The initial assertion was that this is safe β safe in the sense that it works whether the files already exist or need to be built first. However it is unsafe in the sense that it will still match files that were built by makepp but no longer have a rule. Like, you deleted the .c file but the .o file is still sitting there being a menace. π To prevent this, use the --rm-stale option.
β¨ Pattern rules β¨
A pattern rule is a rule that gets applied based on some textual pattern. It's how you apply the same rule to a whole class of files at once. The syntax is the same as GNU make's pattern rules:
%.o: %.c
$(CC) -c $(input) -o $(output)
This says that any file in the current directory matching *.c can be converted into the corresponding .o file using the given command. Slay.
Note that you can supply several pattern dependencies. For example, if your xyz.o file depends on the corresponding xyz.cpp file and also on a file called moc_xyz.cflags which contains the compiler options, you could express that like this:
%.o: %.cpp %.cflags
$(CXX) `cat $(stem).cflags` -c $(inputs) -o $(output)
You may also have several pattern targets. For example:
%.tab.h %.tab.c : %.y
yacc -d $(input)
&mv y.tab.h $(stem).tab.h
&mv y.tab.c $(stem).tab.c
Ordinarily, pattern rules only look for files in the current directories. You can force them to search in the current directory and all directories beneath it by setting
makepp_percent_subdirs := 1
before the first pattern rule in your makefile, or on the command line for example. π
There is a clear difference between % and the wildcard *, even though both match any string: the wildcard returns a list of files that is completely used at that point. So this depends on all .o files buildable here:
prog: *.o
$(LD) $(LDFLAGS) $(inputs) -o $(output)
And you could not achieve that by swapping * for %, because % is for one-by-one matching of input to output β it produces internally one rule for each matched stem. Different vibe entirely. π
π Static pattern rules π
A static pattern rule is a pattern rule that's applied only to a limited set of files:
$(SPECIAL_MODULES).o : %.o : %.cpp
$(CXX) -c $(input) -o $(output)
This says the pattern rule applies only to the files in $(SPECIAL_MODULES).o.
It's mostly there for compatibility with GNU make β foreach rules (see below, bestie, keep reading) are a more powerful way of doing the same thing.
π¦ Foreach rules π¦
The pattern rule syntax above is powerful enough to support almost all builds, but every so often you need to do something more complicated. So makepp provides a more powerful syntax: the :foreach clause for the rule. π«Ά
target_expression : dependency_expression : foreach file-list
actions
The simplest kind of foreach rule is just a pattern rule whose application is restricted to a specific list of files. Say you've got a pattern rule that tells makepp how to compile all .c files, but you also have a list of .c files where you want something different to happen. You could do something like this:
# Here's the rule that applies to everything:
%.o : %.c
$(CC) $(CFLAGS) -c $(input) -o $(output)
%.o : %.c : foreach $(SPECIAL_MODULES)
$(CC) $(SPECIAL_CFLAGS) -c $(input) -o $(output)
An even more powerful use of foreach rules takes advantage of the fact that the variable $(foreach) gets set in turn to each file matching the file list, and the target and dependency expressions are evaluated. The file-list may contain wildcards, and those match even files which don't exist yet but which can be built (see "Wildcards" in makepp_rules). β¨
This is an unwieldy syntax but it is extremely flexible, because the $(foreach) variable may appear in any way in the expression. First, note that pattern rules are in fact a special case of foreach rules; the pattern rule
%.o : %.c
$(CC) $(CFLAGS) -c $(input) -o $(output)
is exactly equivalent to:
$(patsubst %.c, %.o, $(foreach)) : $(foreach) : foreach *.c
$(CC) $(CFLAGS) -c $(input) -o $(output)
(In fact, it's converted to approximately that internally. π)
As an example of how you'd use a :foreach clause where a pattern rule just isn't enough, suppose you have some .c files built using some kind of preprocessor which takes as input files with a .k extension. You want to compile those .c files with a different set of compilation options than the usual .c files which are ordinary source files. You could do something like this:
# Rule for ordinary .c files:
%.o : %.c
$(CC) $(CFLAGS) -c $(input) -o $(output)
# Rule to make .c files from .k files:
%.c : %.k
$(preprocessor) $(input) > $(output)
# Special build rules for .c files which are made from .k files:
$(foreach:%.k=%.o) : $(foreach:%.c=%.k) : foreach *.k
$(CC) $(SPECIAL_CFLAGS) -c $(input) -o $(output)
(This uses the slightly more concise substitution reference syntax rather than calling patsubst explicitly. Cute, right?)
Note that if all you want to do is change the value of a variable (CFLAGS in this case), it's sometimes more convenient to use target-specific variables. π
πΈ Legacy suffix rules πΈ
For backward compatibility, makepp supports the old-style suffix rules.
.suffix1.suffix2:
actions
is equivalent to
%.suffix2: %.suffix1
actions
but much harder to remember. (Like, which suffix comes first?? π) Typically a rule will appear in a legacy makefile like this:
.c.o:
$(CC) $(CFLAGS) -c $*.c -o $*.o
which is exactly equivalent to
%.o : %.c
$(CC) $(CFLAGS) -c $(input) -o $(output)
π Conflicting rules π
When there's more than one way to make a file, makepp uses a simple procedure to figure out which rule to use.
- It is an error to have conflicting explicit rules for building a file.
- Pattern rules and foreach rules with wildcards never override explicit rules. So explicit rules can be used to specify exceptions for pattern rules. (Note that simply using a
:foreachclause doesn't make something a pattern rule. It must have a wildcard β like*or?β as part of the filename in the:foreachclause. If it's just an explicit list of files, it's treated as an explicit rule for each of those files.) - When conflicting pattern rules come from different makefiles, rules from "nearer" makefiles override rules from "farther" makefiles. "Nearer" means the makefile is located closer to the target in the directory hierarchy (i.e., the file name of the target relative to the directory the makefile is run from is shorter). If that doesn't distinguish the makefiles, then the rule from the makefile which is loaded latest is used.
This means you can specify a pattern rule that applies to all files in your entire directory tree in just the top-level makefile, and then override it in a lower-level makefile. For example, your top-level makefile could contain:
%.o : %.c : foreach **/*.c
$(CC) $(CFLAGS) -c $(input) -o $(output)
and you could have a makefile in one of the subdirectories that says:
%.o : %.c
$(CC) $(SPECIAL_CFLAGS) -c $(input) -o $(output)
- Pattern rules that have a shorter chain of inference are preferred over other pattern rules. For example, if you had the following rules (based on an example from the Linux kernel):
%.s: %.c
$(CC) -s $(input) -o $(output)
%.o: %.s
$(AS) $(input) -o $(output)
%.o: %.c
$(CC) -c $(input) -o $(output)
If we need to build xyz.o, we could either build the intermediate .s file and then run that through the assembler using the first two rules, or we could go directly to a .o file using the last rule. The last rule is preferred because there are fewer steps in the chain of inference (one instead of two). Efficiency queen. π
- Pattern rules later in a makefile override pattern rules that are earlier. (This is backwards from GNU make.) So you should put your more general rules earlier, and your more specific rules later. For example,
%.o: %.c # General compilation rule.
action
special_%.o: special_%.c # Special rule for files with a
different action # "special_" prefix.
π₯ Rule options π₯
Sometimes it's necessary to supply additional options to modify how makepp executes the rule. These options are specified as :optionname value, either on the line containing the dependencies, or on the next line.
Supplying the options on separate lines may make it possible for you to use the same makefile with makepp and a traditional make. For example,
target : dependencies
: signature target_newer
actions
will work fine with a traditional Unix make, because it interprets the : signature line as a shell command, and a command beginning with a colon does nothing. π
:build_cache /path/to/build/cache
target : dependencies
: build_cache /put/cache/files/over/there
actions
Okay so this one is giving build cache path energy π
β it specifies the path to a build cache that gets used for the files this rule produces. It overrides whatever the build_cache statement or the --build-cache command line option said, if you had any of those going on, but only for this rule. Wanna know the deets on build caches? Go read makepp_build_cache, bestie. β¨
And if you write none instead of an actual path? Build cache disabled for this one particular rule, periodt. Which is genuinely useful when you don't wanna waste disk space on files you know aren't worth caching β either because you're 100% sure they'll never be reused, or because they build so fast that caching them is just not it. π
:build_check build_check_methodβ tells makepp which algorithm to use when deciding whether the targets need rebuilding. More details live inmakepp_build_check. This overrides thebuild_checkstatement or the--build-check-methodcommand line option, if any, for this rule.
target : dependencies
: build_check target_newer
actions
:env VARIABLE ...β adds a dependency on the values of the named environment variables. If any of them are different from the previous build, the targets are considered out of date, if the build_check method says so. (Every single one of the built-in build check methods excepttarget_newerrespects this, no cap.)
VARIABLE can be of the form filename in PATH_VARIABLE (in quotes), and in that case the targets are considered out of date if the first directory from the colon-delimited value of PATH_VARIABLE in which filename exists is different from the last build. This is the move when you wanna avoid rebuilding the targets just because PATH_VARIABLE changed in some irrelevant way. π
:dispatch command ...β wraps each shell action (but not Perl actions, and not Perl commands either) in ash -c '...'and prefixes it with command, but assumes the target doesn't depend on command. Obsessed with this if you wanna send actions to a job queuing system, but the result is assumed to be independent of the queuing parameters β and also independent of whether the queuing system is used at all. π¦:include file_or_patternβ the rule varies depending on the compiler:
%.o : %.c
: include %.d : signature C
gcc -MD -c ...
%.o : %.c
: include %.u : signature C # IBM uses a different suffix
xlc -M -c ...
sub dependify { # Turn Microsoft's chatter into useful format
s/\$/\$\$/g;
s/(Note: including file: *)?(.+?)\r?\n/$1 ? "'$2' " : "'".f_output()."': "/e;
}
%.o : %.c
: include %.d : signature C
cl -showIncludes -c ... >$(stem).d
&sed &dependify -o +<$(stem).d
Some compilers (Intel's icc, just like gcc above, or IBM's xlc) can produce dependency files on the fly. Like, while they compile, they write a makefile that makepp can include. The glow-up here over makepp's scanner? It's guaranteed to be 100% correct, whereas makepp's scanner may only come close. π
This option harnesses that in a very special way: if the file isn't present β which is typically on the 1st build β normal scanning happens. But if the file <em>is</em> present, no scanning occurs at all (which is exactly why we specify a smart signature up above β not scanning falls back to the dumb default of timestamp and size π). Instead, makepp includes the file, before executing the rule. Then, after successfully executing the rule, it forgets whatever it read the first time, since that file might have been outdated. Instead it reads the file again, if it changed, so it has up-to-date build info. π
WARNING: This is inherently unreliable. The dependency file gets produced by the very rule for which it is a dependency. On the other hand, the compiler knows about all its internal sub-includes, which makepp usually ignores. That's a reliability advantage only for the case where a compiler patch fixes only the sub-includes. The price? makepp ends up looking at many more files, which takes time.
There's a catch when you remove an #include statement and the corresponding file: it will still be mentioned in the dependency file from the last time, back when it was needed. In a case like that you must edit the dependency file to remove the dependency which is no longer fulfillable. π
This feature can not be used with a build cache, because fetching a file from there requires knowing everything about the file. But a dependency file depends on those files makepp learns about by reading it. Such a circular dependency is not normally possible in a reliable build system. This is an exception, because after rebuilding and rereading a dependency file everything is correct again. β¨
If you build in your repositories, makepp will pick up the dependency file from the 1st repository which contains one. This is unlike other files, where it takes the 1st with the expected signature. Which is better than for build caches, where β for lack of signature β it can't even find the file. π
:last_chanceβ enables an open-ended rule, such as
%.foo foo%.bar: :last_chance
&echo $@ -o $@
&cp $(outputs)
Because a rule like this could generate an essentially infinite number of targets, a target of this rule won't match a $(wildcard) function or pattern rule unless something else has already instanced the rule by referencing the target specifically. And on top of that, if --rm-stale is specified, then a target left over from a previous makepp run will appear stale if the only way to build it is via a last_chance rule that hasn't been instanced for the target yet β which is a desirable behavior, because the build will fail more consistently when it erroneously relies on a wildcard to match targets from a previous run. π
The :last_chance option is intended to call attention to the special behavior of the rule with respect to matching wildcards. πΈ
:parser parserβ tells makepp how to parse the command for detecting (include) files. Usually makepp guesses how to do this based on the words in the command itself (seemakepp_scanningfor details). But if makepp guesses wrongly, you may wanna explicitly indicate the parser, like this:
%.o: %.abc
: parser c_compilation
action here
This causes makepp to perform the same parsing and scanning that it does for C/C++ build commands, even if it doesn't recognize the action as a C compilation. π«Ά
The default parser depends on the command. If you don't specify a :parser option, then the first word of each command gets examined. For example for a compile or link command, makepp will use the c_compilation parser; or if the command looks like the GNU variant, gcc_compilation. If no parser is found it uses the none parser. For more details on this, or if you wanna write your own parser or change makepp's default parsers, see makepp_scanning. β¨
Note that this applies to every command in the rule, which may not be what you want:
%.o: %.c : parser c-compilation
@echo 'Building $(output)'
@funny_cc ...
This will also interpret echo as a compiler and deduce its argument 'Building mymodule.o' as an implicit dependency. Which leads straight to the complaint that it doesn't know how to build such a file. π In this case you'd be better off with register_parser. There you find an explanation how parser can be given either as a classname or as a function name. π
:signature signature_methodβ tells makepp which algorithm to use to determine if the dependencies have changed. Seemakepp_signaturesfor more details. The signature methods included with the makepp distribution areplain,md5,Corc_compilation_md5, andshared_object. This overrides any signature method specified with the-mor--signature-methodcommand line options, or with thesignaturestatement. π₯
target : dependencies
: signature md5
actions
β¨ Special characters β¨
Makepp can support filenames that have special characters in them, like a colon or a space. Say, for example, you wanna create a file called a:thing from the file b:thing. You can't write the rule this way:
a:thing : b:thing # This is a syntax error
&cat $(input) -o $(output)
because makepp won't know which colons separate targets from dependencies and which ones are part of the filenames. π Instead, simply enclose the name in quotes, like this:
"a:thing" : "b:thing"
&cat $(input) -o $(output)
Now the rule is unambiguous. π
Makepp's quoting syntax is pretty similar to the shell's. You can, for example, use single quotes instead of double quotes, or you can escape special characters with a backslash:
a\:thing : 'b:thing'
&cat $(input) -o $(output)
Say, for example, that your filename is '"!;\$. Now why you'd want such a filename I do not know, but here are several ways you could specify it to makepp (and the shell):
\''"!;\$$'
"'\"!;\\$$"
Pay attention to when makepp strips quotes and when the shell does. Makepp looks at quotes only in the following cases:
- in the
ifeqfamily of tests - before and after the rule colon
- in a makepp builtin command
- in a function that pertains to files
Unlike the shell, makepp doesn't expand quotes while assigning them to a variable. So the following rules are identical:
FILE = 'name with spaces'
x := $(print $(FILE)) # just to check that quotes are still there
$(FILE): # quotes around single file stripped by makepp
&echo hello -o$(FILE) # quotes around single file stripped by makepp
echo there >>$(FILE) # quotes around single file stripped by Shell
'name with spaces':
&echo hello -o'name with spaces'
echo there >>'$(output)' # quotes were stripped above, add them again
Note that (unlike the Shell) variables beginning with $ are expanded even inside single quotes. Dollar signs cannot be protected by quotes or backslashes. To get a literal dollar sign, use a double dollar sign, e.g., πΈ
$(phony all):
@&echo This is a dollar sign: $$
@for val in a b c d; do echo $$val; done
Okay so the β¨vibeβ¨ here is: quoting is your bestie. Lowkey, you can handle basically any special character by quoting it somehow β spaces, control characters, all of it, no cap. BUT π¨ be aware that rn, makepp's comment stripping is a lil bit simplistic, and any "#" characters that come after whitespace will get read as comments no matter how you quote them. So that's a thing. π
When a target or dependency name gets put into an automatic variable like "$(output)", the quotes and any backslashes get stripped. Which means if you wanna reference that filename again inside the actions, you'll probs have to quote it again, like this:
"a file name with spaces":
echo "Special contents" > "$@"
And like⦠if you don't put the quotes around $@? Then the shell sees this command:
echo "Special contents" > a file name with spaces
β¦which writes the string "Special contents file name with spaces" to a file called a. Yeah, literally just a. That is probably not what you want, bestie ππ
π AUTHOR π
Gary Holt (holt-makepp@gholt.net) β the icon, the legend, the main character who wrote this. π«Άβ¨
And that's a wrap on the whole page, fr fr π This version you're reading is from perl v5.32.0, dated 2021-01-06, and it lives its life as MAKEPP_RULES(1). Stay slay, quote your filenames, and go build something iconic. ππ¦π
π¦ where this page comes from
- debian bookworm: makepp 2.0.98.5-2.1
- ubuntu jammy: makepp 2.0.98.5-2.1
- ubuntu noble: makepp 2.0.98.5-2.1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms