You can use "make -q" to probe targets less intrusively.  This also works
for a makefile with default rule like this one:

%:
        echo $@

This matches the dummy target the other approach uses, so it doesn't work.

Using '-q' also seems less brittle than trying to pattern match output.

I've attached an updated version of the script which uses this approach,
though the description needs updating to match.

Hope this is useful.

Cheers,
    Olly
#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Long;

=head1 NAME

make-first-existing-target - runs make on one of several targets

=head1 SYNOPSIS

make-first-existing-target [-c cmd] target1 [target2 ...] -- [make-options]

=cut

sub usage {
        print STDERR "usage: make-first-existing-target [-c cmd] target1 
[target2 ...] -- [make-options]\n";
        exit 1;
}

=head1 DESCRIPTION

The design of L<make(1)> causes difficulty when you know that a Makefile
probably has one of several standardized target names, and want build
machinery to run exactly one of them, propigating any errors. L<make(1)>
will exit 2 if a taget does not exist, but an existing target may also
exit 2 due to some other failure. Makefiles cannot be reliably parsed
to find targets by anything less turing complete than make; and make itself
does not provide a way to enumerate the targets in a Makefile. It may not
even be possible to enumerate the targets in a Makefile without executing
part of it. (Proof of this is left as an exercise for the reader.)

This program avoids the problems described above, by attempting to call
each specified target in turn, until it observes make actually doing
something for one of them.

=head1 OPTIONS

=over 4

=item -c cmd

This can be used to specify the make command to run. Default is "make".

=back

=cut

my (@targets, @makeopts);
my $makecmd="make";

getopt();

foreach my $target (@targets) {
        make($target);
}
error("*** No rules to make targets: @targets");

sub make {
        # Only returns if the target appears not to exist.
        my $target=shift;

        system "\Q$makecmd\E -q ".join(' ', map quotemeta, @makeopts)." 
\Q$target\E 2>/dev/null";
        exit -1 if $? == -1;
        my $code = $? >> 8;
        return if ($code == 2);

        exec $makecmd, @makeopts, $target;
        exit -1;
}

sub error {
        print STDERR "make-first-existing-target: @_\n";
        exit 2;
}

sub getopt {
        GetOptions(
                "h|help" => \&usage,
                "c=s" => \$makecmd,
        ) || usage();

        # remainder are targets, possibly followed by makeopts  
        my $end=0;
        foreach my $a (@ARGV) {
                if ($end || $a=~/^-/) {
                        $end=1;
                        push @makeopts, $a;
                }
                else {
                        push @targets, $a;
                }
        }

        @targets || usage();
}

=head1 EXIT STATUS

The exit status is 0 if at least one target existed and was successfully
run, and nonzero otherwise.

=head1 AUTHOR

Joey Hess <j...@kitenet.net>

=head1 LICENSE

Same as GNU make.

=head1 SEE ALSO

L<make(1)>

=cut

Attachment: signature.asc
Description: Digital signature

Reply via email to