Every project names its build target differently, and make-first-existing-target runs whichever one actually exists in this checkout. It does not parse the Makefile itself: it watches make try each candidate and stops at the first one that is not rejected as missing. Budget about ten minutes.
You need a shell, GNU make and a Makefile you can read. The examples use the version installed here: Debian package make 4.3-4.1build2. The wrapper itself is a Perl script at /usr/bin/make-first-existing-target, and its manual page identifies it as version 1.0.
This is a read-only check, no sudo required. Confirm the expected executable is first on your PATH:
$ command -v make-first-existing-target
/usr/bin/make-first-existing-target
$ make --version | head -n 1
GNU Make 4.3
Checkpoint: if the first command prints nothing, install or enable the package through your normal system-management process before continuing. Do not substitute a similarly named script from an untrusted directory.
Positional arguments are tried left to right. The first target that appears to exist wins, so lead with whichever name is most correct or most portable:
$ make-first-existing-target build all package --
On a Makefile with only all defined, a successful run prints that recipe's own output:
all ran
The wrapper suppresses the probe it runs to learn how this installation reports an unknown target. That is why an absent candidate normally produces no visible complaint before the next one is tried.
Exit status 0 means one candidate existed and its recipe finished successfully, nothing more. Capture it before a later command overwrites $?:
$ make-first-existing-target build all package --
$ status=$?
$ printf 'selected target status: %s\n' "$status"
selected target status: 0
The wrapper never prints which target it picked. If a log or a CI job needs to know, make every candidate's recipe announce itself, or have each one produce a distinct artefact you can check afterwards.
Checkpoint: run the same command against a clean working tree or a disposable checkout first. A target can create files or kick off a build step of its own, and the wrapper does nothing to make that reversible.
Options after -- go to make untouched. Keep the target list before the separator and put the alternate path in a make option:
$ make-first-existing-target build all -- -f Makefile.ci
build ran
Quote any path with whitespace in it, since the wrapper hands it to make as-is:
$ make-first-existing-target test check -- -f 'Makefile for CI'
Only inspect Makefiles you trust. A Makefile can run arbitrary shell commands, so never run a downloaded or modified one purely to see what target names it defines.
Pass -c when the binary you want is not the default make, for example a project-specific wrapper or a separately installed copy:
$ make-first-existing-target -c /usr/bin/make build all -- -f Makefile.ci
-c takes exactly one executable name or path, not a shell command line, so writing -c "make -f Makefile.ci" will not work. Arguments for the selected executable go after --.
Checkpoint: verify a custom executable before trusting it:
$ command -v make
$ readlink -f /path/to/custom-make
A missing target and a broken existing one look different to the wrapper. It compares make's diagnostic for each candidate against the diagnostic a deliberately missing target produces; if they differ, it treats the candidate as real and propagates that run's failure rather than quietly trying the next name.
So if build exists but its recipe fails, the wrapper stops there instead of falling through to all. Inspect the full stderr and rerun the underlying command directly for a clearer diagnosis:
$ make build
$ printf 'make status: %s\n' "$?"
Warning: do not bolt a broad || make-first-existing-target ... fallback onto a build step without understanding this. It can mask a genuine failure by silently starting a different target. It also relies on this installation's exact diagnostic wording, so an unusual make build or a wrapper that rewrites stderr may not behave the same way.
If every candidate is rejected as missing, the wrapper exits non-zero, normally status 2, and names them all in its own error:
$ make-first-existing-target release package install --
make-first-existing-target: No rules to make targets: release package install
$ printf 'status: %s\n' "$?"
status: 2
Exact make wording can vary, but the wrapper's closing message lists every candidate it tried. Check the working directory, the Makefile make would pick by its normal search rules, spelling, and any -f option. Adding a target to a shared Makefile as a quick fix without reading its recipe and side effects first is how a working build gets a new, silent step.