MAKEPP_INCOMPATIBILITIES(1)

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

πŸ’– NAME πŸ’–

makepp_incompatibilities β€” the tea on what's different between makepp and GNU make β˜•βœ¨ (from the Makepp manual, bestie)


✨ DESCRIPTION ✨

Okay so here's the vibe 🫢 Makepp was literally designed to be as close as possible to GNU make (http://www.gnu.org/software/make/manual/make.html). Like, GNU autotools (http://www.gnu.org/software/automake/manual/automake.html), CMake (http://www.cmake.org/), Premake (http://industriousone.com/premake β€” and yes, peep the remark below πŸ‘€) or even handcrafted legacy build systems should all be buildable with makepp. Iconic. πŸ’…

Why do we care? Two reasons, bestie:

  • So you can migrate projects effortlessly, no drama, no tears 😌
  • Or, if you don't wanna enjoy all of makepp's advantages (e.g. so other people can still build your project with GNU make), you can still profit from the reliability advantage for your development. Best of both worlds, fr fr.

However. 🚨 Because of the difference in philosophy, some of GNU make's or POSIX make's (http://pubs.opengroup.org/onlinepubs/009695399/utilities/make.html) features literally cannot be supported. A few just haven't been implemented because we haven't had time 😭. Most of the differences from GNU make are super technical and only rarely cause problems. But alas β€” the workarounds for the short-comings of traditional make are becoming more and more complex, and they're giving makepp a hard time. It's giving ✨ chaos ✨.

In a nutshell, if it doesn't build out of the box, try this:

makepp --no-warn makepp_simple_concatenation=1 makepp_percent_subdirs=1 \
    --build-check=target_newer --last-chance-rules --no-remake-makefiles

If that succeeds, you can try to eliminate those arguments one by one β€” slay, we love a process 🌸. But if that fails, try adding:

    --traditional-recursive-make

And if that also fails... yeah, the build system needs some tweaking to cooperate with makepp πŸ˜”. And listen: even if some options described here make something buildable, it is still recommended to adapt things slightly, so they become compatible out of the box with both makes. Future you will thank present you, no cap. πŸ’–


Forcing more POSIX or GNU make compatibility

okay so bestie, here's the tea β˜• β€” these are all the command-line levers you can pull to drag a crusty legacy build system along without touching a single file. They make makepp cosplay GNU make's behavior, like, precisely. πŸ’…


Compatibility via the option: --build-check=target_newer

So by default makepp is out here being thorough: it'll try to rebuild all the targets if any of the dependencies changed since the last build, or if the command itself changed (go peek at makepp_build_check for the full lore). And honestly? That's usually the move. That's what you want. 🫢

But sometimes you're like... no bestie, I do not want that target rebuilt just because it got touched outside makepp's watch (you edited it, you ran some program by hand to make the file, whatever). In that case you can force makepp into the old-school traditional make algorithm β€” the one that only rebuilds when targets are newer than their dependencies β€” by just adding this option to the command line. ✨

Compatibility via the option: --dont-build=config.status

Some packages get all up in their own business and try to autoconfigure themselves, or do other stuff that gmake straight-up ignores unless you explicitly ask for it. Like this vibe:

config.status : configure
    ./config.status --recheck

configure : configure.in aclocal.m4
    autoconf

Most people don't even have autoconf installed, so if you conscientiously do everything by the rules β€” which, hi, that's makepp's whole personality β€” it will fail. 😭 This option stops that from happening... if you can figure out what not to build. iykyk.

Compatibility via the option: --last-chance-rules

Default rules (that's pattern rules with no pattern dependencies) are not normally supported, bestie. Makepp instantiates all rules based on the existing files, so it's aware of every single file that could be generated. The tradeoff? This way it has no clue how to instantiate a pattern rule with no pattern dependency. πŸ™ˆ The :last_chance mechanism partially remedies that. And where that's good enough for your legacy makefiles, this option lets you turn it on globally. slay.

Compatibility via the option: --no-warn

Ngl, this one doesn't actually improve the result at all. It's just a vibe fix. Makepp will throw warning messages for a whole bunch of things that traditional Unix make accepts without flinching β€” and that's because there are better ways to do those things with makepp. But if those warnings are lowkey annoying you, you can silence them with this option. 🀫

Compatibility via the option: --hybrid-recursive-make

Recursive invocations of make are often considered an unsafe practice (peep "Better system for hierarchical builds" in makepp for the details), but fr fr they are extremely common in existing makefiles. Makepp supports recursive make for backward compatibility; but for new makefiles, it is much better to use the load_makefile statement, or makepp's implicit makefile loading mechanism. πŸ‘‘

So here's the thing: in order to be able to use repositories for variant builds, and to help make recursive invocations of make safer, makepp normally does not actually invoke itself recursively even if you tell it to. Instead, a subprocess communicates with the parent process, and the actual build gets done by the parent process.

This works in most cases, but you may not invoke several makefiles from the same directory. E.g., the following will not work:

target: dependencies
    $(MAKE) -f other_makefile targets

In this case makepp notices it is loading a 2nd makefile and complains. 😀 With this option instead, it will fall back to the traditional way of building from additional makefiles β€” in a separate makepp process each.

Note: Technically loading several makefiles would be no problem, but they usually have the same phony target names. Keeping that apart would mean a complete redesign of makepp internals. However, this will work, but it is not equivalent:
target: dependencies
    cd subdir && $(MAKE) -f other_makefile targets

Compatibility via the option: --traditional-recursive-make

Sometimes the previous option is just not enough, bestie β€” especially if the recursive invocations use contradictory options. Makepp uses only one set of global options, so a submake is not allowed to modify them, since that would also pertain to other makefiles. 🚫

Adding this option to the command line has the following undesirable side effects:

  • Recursive makes do not internally execute in parallel, even if the parent does. Unlike gmake there is no overall coordination of the number of processes. This will not be implemented, because this way of working is not a design goal of makepp.
  • Recursive make processes do not know anything about repositories.
  • Each recursive make process produces its own log file, in the directory it is invoked in, instead of producing one log file for the entire build.
  • Since makepp usually builds more than traditional make deems necessary, and since many build systems provide recursive calls in all directions, this may lead to endless recursion. Makepp will pull the brake after 50 rounds and tell you how to increase that, in case you really have such deep nesting. πŸ’€

Even with the --traditional-recursive-make option, the environment variables MAKEOVERRIDES and MFLAGS are not set up, and ignored, so makefiles that depend on those will not work.

A Premake generated Makefile is only a funny little wrapper to a sub-make invocation in the same directory. If you have some project target XYZ it will have a line like

@${MAKE} --no-print-directory -C . -f XYZ.make

In this case you can avoid the --traditional-recursive-make option by directly invoking makepp with that -f XYZ.make option. 🌈

Compatibility without the option: --jobs=n

Legacy makefiles will sometimes not list all their dependencies, relying on the order of execution to make them in time. πŸ™ƒ In this situation makepp may manage to call a rule before its dependencies have all been made. So results may be better with less, or even no, parallel execution.

Compatibility via the variable: makepp_simple_concatenation=1

Rc-style substitution is the default way makepp performs variable substitution into text strings, because it very rarely breaks legacy makefiles and is often useful in new makefiles. But it does introduce occasional incompatibilities in the substitution of variables not surrounded by spaces. For example:

INCLUDE_PREFIX := -I/some/include/dir -I
INCLUDES := $(INCLUDE_PREFIX)/other/include/dir

...will set INCLUDES to -I/some/include/dir/other/include/dir -I/other/include/dir if rc-style substitution is enabled, whereas GNU make would set it to -I/some/include/dir -I/other/include/dir. E.g., when compiling Redis 2.6.5 it tries to run printfgcc. 😭 Such a funny concatenation of two commands is a strong indication that this variable is needed to fall back to make semantics.

There is also an incompatibility in the handling of whitespace in a variable:

null :=
T := -o $(null)             # T contains -o followed by one space.
OUTFILE = $(T)outfile

...will set OUTFILE to -ooutfile if rc-style substitution is enabled, whereas GNU make would set it to -o outfile.

Both of these incompatibilities are removed by setting the makepp_simple_concatenation variable. Note, however, that even with makepp_simple_concatenation, makepp still treats whitespace incompatibly in some situations:

T := -o # Don't delete this comment.

GNU make sets T to contain -o followed by a space, whereas makepp strips out the trailing space anyway. 😀 If you want that trailing space, you must set makepp_simple_concatenation and also set T using the technique involving a dummy variable such as null, as shown above.

Workaround option --no-remake-makefiles

Typical open source requires calling configure to create the makefiles. But then those makefiles can contain rules to remake the makefile, by calling some command. Makepp will happily comply and update it according to the rule. 🫠 But sometimes this is harmful, so just skip it.

Compatibility via the variable: makepp_percent_subdirs=1

By default, % in a pattern rule does not match directories. This means that a rule like this:

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

...will not be applied to files like ../shared/xyz.c. If you want it to match files in subdirectories too, then set the variable makepp_percent_subdirs=1 on the command line or near the beginning of a makefile. πŸŽ€

Compatibility via the environment variable: $MAKEPP_IGNORE_OPTS

Sometimes legacy recursive invocations pass options that makepp doesn't understand. Hopefully the option is not important, but it prevents makepp from running. 😩 With this environment variable you can ask makepp to silently ignore certain options. The value shall be a space separated list of options, which can come in 4 variants:

  • --long=x β€” A long option that expects an argument. This fact must be declared through the equals sign, though the actual use may also separated by whitespace, either --long=bla or --long bla.
  • --long β€” A long option without an argument.
  • -sx β€” A short option that expects an argument. This fact must be declared by adding something directly after the option, though the actual use may also separated by whitespace, either -sbla or -s bla.
  • -s β€” A short option without an argument.

E.g. override makepp's -R option by one without an argument and accept gmake's debug option with an argument:

export MAKEPP_IGNORE_OPTS='-R --debug=x'

πŸ’– Incompatibilities that require Makefile changes πŸ’–

Okay bestie, this is the part where your old makefiles might need a lil glow-up. Not everything GNU make does, makepp does β€” and some of it is on purpose. Let's go one by one, no skipping, I promise. 🫢

  • Makefiles that explicitly call make prevent makepp from building everything itself. Alas Perl's own "ExtUtils::MakeMaker" commits the second of the following two forms of this mistake up to version 6.56 (Perl 5.12.1):
subdir:
    cd subdir; make

MAKE = make
  • Setting the "VPATH" variable to some value implicitly calls "vpath % value". Those "vpath" statements get emulated with the repository mechanism. So where gmake substitutes the path to the file found in the vpath, makepp will instead link it symbolically to where it is needed. Which means makepp hands you an unmodified string β€” usually not a problem, but like, know that it's happening. πŸ¦‹

Targets in a vpath are not supported. (Gmake considers them if they are newer than their dependencies, but if not, the target will be recreated in the current directory β€” rather inconsistent, honestly.) Unsetting vpaths is also not supported.

  • A pattern rule present later in a makefile overrides one that is present earlier. This is literally backwards from GNU make. Yes, for real.
  • The set of builtin implicit rules is somewhat different from those for GNU make, though the variable names are largely compatible. The builtin rules should successfully compile C/C++/Fortran programs, and in fact may be able to guess the proper libraries in some cases too. Support for Modula-2 and RatFor and other rare languages is deliberately not present, because I kept running into problems with GNU make's rules when I accidentally reused the extensions for those languages. 😭
  • An action prefix of "+" is silently ignored. Not a typo, not a warning, just... gone. πŸ’€
  • Archive members are not supported, and neither are the associated automatic variables $%, "$(%D)", and "$(%F)".
  • There is no SCCS support. Periodt. ✨
  • Leading and trailing whitespace in variable assignments is ignored (even if the whitespace is followed by a comment). For more details on whitespace handling incompatibilities, see "Whitespace in variables" in makepp_variables.
  • Makepp does not attempt to rebuild files included with the "include" statement unless the makefile contains a rule for building them before the include statement is seen. (It will attempt to rebuild the makefile itself, however.) This is normally used for handling include file dependencies, and is not as useful with makepp since you don't need to do that anyway. πŸŽ€
  • The "SHELL" variable is currently partially ignored. Makepp always uses /bin/sh unless /usr/xpg4/bin/sh or /sbin/xpg4/sh is found or unless you export the "SHELL" variable in your makefile. But if you do, the command parser might not fully understand what your shell command does. πŸ‘€ On Windows Strawberry or ActiveState Perl you must instead set your SHELL variable before calling makepp.
  • Dependencies of anything on the Makefile still work, but are usually unnecessary. This is usually used to force a rebuild when compilation options change. Makepp knows when build commands have changed without anything special in the makefile; it stores this on a file-by-file basis. If you change the makefile, it knows exactly which files need recompilation. Iconic behaviour, honestly. πŸ‘‘
  • Intermediate files are not deleted. (Because makepp insists on having all of the file dates be the same as they were on the last build, intermediate files must all be present or else rebuilds will occur.) There is no special status accorded to intermediate files.
  • The only special target that is supported is ".PHONY" and partially ".SUFFIXES". The remaining are simply ignored.

Specifically, GNU make has the following special targets:

  • .SUFFIXES β€” Makepp ignores ".SUFFIXES" except for the special case of ".SUFFIXES" with no dependencies, like this:
.SUFFIXES:

which tells it not to load any of its default rules.

  • .INTERMEDIATE, .SECONDARY, .PRECIOUS β€” No special status is accorded to intermediate files and so these targets are not meaningful.
  • .IGNORE β€” This target is ignored. If you want to ignore errors, put the word "ignore_error" (or a minus sign) in front of the command whose exit status is to be ignored.
  • .SILENT β€” This target is ignored. If you want commands not to echo, put the word "noecho" (or the "@" character) in front of the command which is not supposed to be echoed, or use the "--silent" option to makepp.
  • .DELETE_ON_ERROR, .EXPORT_ALL_VARIABLES, .NOEXPORT, .POSIX, .DEFAULT β€” These targets are not supported and are simply ignored. πŸ™ˆ
  • The GNU make functions "eval", "flavor" and "value" are not currently supported. You can achieve the same thing as eval in a more straight-forward way with "$[...]" variable or function expansion. slay πŸ’…
  • Double colon rules are not fully supported. (They cannot be: in makepp's paradigm, there cannot be more than one way to update a target.) Currently, each successive double colon rule for a given target simply appends its command string and dependency list to the command string and dependency list for this target. For example, if you write this:
a :: b
    &cat b -o a

# Later in your makefile:
a :: c
    &cat c -o >>a

it is exactly the same as if you had written

a : b c
    &cat b -o a
    &cat c -o >>a

This is certainly not what double colon rules are intended for, and it will not always work, but it does work for targets like "clean" or for all the stuff that ExtUtils::MakeMaker puts into its makefiles. Don't count on it for anything other than legacy makefiles. ⚠️

  • The "$(wildcard )" function matches not only files which exist, but also files which do not yet exist, but which have a rule which makepp has seen at the time the "$(wildcard )" function is evaluated. Plot twist πŸŒ€
  • The "define" statement is supported, but handling of "@" preceding it is done differently. Currently in makepp, "@" in front of a variable which has a multi-line value will only suppress echoing of the first line. For example,
define echo-lines
&echo line1 -o $@
&echo line2 -o>>$@
endef

x:
    @$(echo-lines)

will not suppress printing of "&echo line2" as it does in GNU make; it will only suppress printing of "&echo line1". 😭

  • Makepp does not support the following environment variables (it does not set them up, and it just ignores them):
    • MAKEOVERRIDES
    • MFLAGS

🌈 Incompatibilities in order of expression expansion

  • In makepp, rule actions are expanded before all of the dependencies are guaranteed to have been built. You can work around this by changing rules such as this:
foo: bar
    genfoo < $(shell cat bar)

to this:

foo: bar
    genfoo < `cat bar`

or this, which will make the file during the expansion:

foo: bar
    genfoo < $(&cat $(make bar))

This is preferable here, because the file listed in bar is also a dependency of this rule, and makepp can now catch it when lexically analyzing the redirection. 🧚

  • Though I have not seen this used, GNU make allows the following:
colon = :
a$(colon) b
    echo $^

Makepp expands "$(colon)" too late for this to work. However it offers the alternative "$[colon]" syntax, which can do much more than GNU make, because it is expanded very early. πŸ’«

πŸ’œ "$(MAKE)" may include spaces

In an uninstalled makepp or if the platform doesn't seem to support starting a Perl script by magic number or with "--traditional-recursive-make" this variable will include at least one space. That is not a problem when using it as a command. But when passing it as an unquoted parameter to a script (as the Perl 5.14.0 build system does), it will tear it apart into separate parameters, leading to confusion. 😡 So as a parameter it is safer to quote it as '$(MAKE)'. which doesn't break backward compatibility.

πŸ¦‹ Target-specific assignments don't propagate

Makepp's target-specific variables are slightly different from GNU make's in that they only apply to the rule for the one file mentioned, and not to any of its predecessors; see Target-specific assignments.

🌸 Parentheses or braces don't nest

Makepp ends expressions at the first matching parenthesis or brace. So instead of this

$(somefunction ... ( ) ...) # GNU make style

you must use either of these

${somefunction ... ( ) ...} # GNU make compatible
$((somefunction ... ( ) ...)) # Makepp extension

This will probably be fixed in version 2.1, maybe optionally. 🀞

⭐ Minor points

πŸŽ€ Pattern dependencies don't match phony targets

%.a: %.b; ...
$(phony x.b): ; ...         # does not provide a way to build x.a

πŸŽ€ Comments don't have continuation lines

# This is \
NOT a 2-line comment

πŸ”₯ Command line incompatibilities πŸ”₯

Makepp supports a few of make's more useful command line options. The following, however, are not supported:

  • -d or --debug
  • -f - β€” Makepp's internal makefile objects are linked to file objects, so it can't handle stdin.
  • -i
  • -l or --load-average or --max-load
  • -m β€” Makepp's "-m" option has to do with signature method selection, whereas GNU make ignores -m.
  • -p or --print-data-base
  • -q or --question
  • -R or --no-builtin-variables β€” Makepp's "-R" option actually does something completely different.
  • -S, --no-keep-going or --stop β€” The "--stop" option stops (puts to sleep) makepp after learning all the rules, so you can continue editing.
  • -t or --touch
  • -w or --print-directory β€” This happens automatically.
  • --warn-undefined-variables

Some of these can be easily supported if anyone cares. πŸ‘€


🌈 Variable incompatibilities 🌈

Makepp looks in $PATH for a matching command to return for variables like "$(CC)" or "$(CXX)", while GNU make has static defaults. Also makepp gives preference to "gcc" and "g++" while surprisingly GNU make returns "cc" for the former, but the same for the latter. You can override these in the makefile, on the command line or by exporting a variable of the same name before invoking makepp. 🫢


and that's a wrap on the incompatibilities, bestie πŸ’– go forth and make responsibly

This page is from perl v5.32.0, dated 2021-01-06, and it's MAKEPP_INCOMPATIBILITIES(1) β€” that's the whole vibe, thanks for reading to the end, you ate. πŸ‘‘βœ¨

πŸ“¦ where this page comes from