chiark / gitweb /
src/cross-run.in: New tool to run command in multiple cross-build envs.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 19 Dec 2018 12:41:41 +0000 (12:41 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 22 Dec 2018 10:13:01 +0000 (10:13 +0000)
Possibly in parallel.  (Ab)uses GNU make for the parallelism, so that
descendent `make's share the available job slots properly.

Makefile
src/cross-run.in [new file with mode: 0644]

index 5c1ffc178ac9364d26b1183d5776f1ab4e208086..cc63a45554adeecd3c8a01c6a55e68000804fc53 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,7 @@ $(SETUPS): etc/%-setup.sh: $$(call setup-flavour,$$*)-setup.sh.in Makefile
        $(call v_tag,SED)sed 's/@arch@/$($*_ARCH)/g' $< >$@.new && \
                mv $@.new $@
 
-SCRIPTS                         = mdw-cross crosstool
+SCRIPTS                         = mdw-cross crosstool cross-run
 BINSCRIPTS              = $(addprefix bin/,$(SCRIPTS))
 TARGETS                        += $(BINSCRIPTS)
 $(BINSCRIPTS): bin/%: src/%.in
@@ -73,4 +73,24 @@ all:: $(TARGETS)
 CLEANFILES             += $(TARGETS)
 clean::; rm -f $(CLEANFILES)
 
+run-program = exec 8>&1 && rc=$$( exec 7>&1 && \
+       { exec 9>&1 && \
+         { ($1 9>&-; echo $$? >&7) | \
+               while IFS= read -r line; \
+               do printf >&9 "%-21s | %s\n" "$2" "$$line"; done; } 2>&1 | \
+         while IFS= read -r line; \
+         do printf >&9 "%-21s * %s\n" "$2" "$$line"; done; } | cat >&8 ) && \
+       exit $$rc
+
+run: $(addprefix run/,$(ARCHS))
+$(addprefix run/,$(ARCHS)): run/%:
+       @cd $(DIR) && \
+       $(call run-program,$(CROSSDIR)/bin/mdw-cross $* $(ARGS),$*)
+.PHONY: run $(addprefix run/,$(ARCHS))
+
+run-make: $(addprefix run-make/,$(ARCHS))
+$(addprefix run-make/,$(ARCHS)): run-make/%:
+       @+$(call run-program,$(CROSSDIR)/bin/mdw-cross $* $(MAKE) -C$(DIR) $(ARGS),$*)
+.PHONY: run-make $(addprefix run-make/,$(ARCHS))
+
 show:; : $(VAR)
diff --git a/src/cross-run.in b/src/cross-run.in
new file mode 100644 (file)
index 0000000..0124572
--- /dev/null
@@ -0,0 +1,122 @@
+#! /usr/bin/perl
+
+use Data::Dumper;
+
+use File::FnMatch qw(fnmatch);
+use MdwOpt;
+
+our $CROSSDIR = "@crossdir@";
+(our $ME = $0) =~ s:^.*/::;
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+sub usage (*) {
+  my ($fh) = @_;
+  print $fh
+    "usage: $ME [-j N] =ARCHGLOB|:FLAVOUR ... [--] CMD [ARGS ...]\n";
+}
+sub barf ($) {
+  my ($msg) = @_;
+  print STDERR "$0: $msg\n";
+  exit 2;
+}
+
+###--------------------------------------------------------------------------
+### Read and prepare the configuration.
+
+our %C = ();
+open my $fh, "<", "$CROSSDIR/etc/config.sh" or barf "open config: $!";
+while (<$fh>) {
+  chomp;
+  if (/^\s*(#|$)/) { next; }
+  if (!/^([^=]+)='(.*)'$/) { barf "bad config line `$_'"; }
+  my ($k, $v) = ($1, $2);
+  $v =~ s/'\\''/'/g;
+  $C{$k} = $v;
+}
+
+our @ALLARCH;
+our %FLAVOUR;
+for my $f (split ' ', $C{"FLAVOURS"}) {
+  (my $v = $f) =~ tr/-/_/;
+  my @a = split ' ', $C{"${v}_ARCHS"};
+  @{$FLAVOUR{$f}} = @a;
+  push @ALLARCH, @a;
+}
+
+###--------------------------------------------------------------------------
+### Parse the command line.
+
+our @ARCH = ();
+our $JOBS = undef;
+our $DIR = ".";
+our $MARKER = "{}";
+our @CMD = ();
+
+my $mo = new MdwOpt "hC:j:m:",
+  { "help" => { 'return' => "h" },
+    "directory" => { 'return' => 'C', 'arg' => 'req' },
+    "jobs" => { 'return' => "j", 'arg' => 'req' },
+    "marker" => { 'return' => "m", 'arg' => 'req' } },
+  \@ARGV,
+  ['inorder'];
+my $bad = 0;
+OPT: for (;;) {
+  my ($opt, $arg) = $mo->read;
+  last OPT unless defined $opt;
+  if ($opt eq "h") { usage STDOUT; exit 0; }
+  elsif ($opt eq "C") { $DIR = $arg; }
+  elsif ($opt eq "j")
+    { $arg =~ /^[+]?\d+$/ or barf "bad integer `$arg'"; $JOBS = $arg; }
+  elsif ($opt eq "m") { $MARKER = $arg; }
+  elsif ($opt eq '') {
+    if ($arg =~ /^=(.*)$/) {
+      my $pat = $1;
+      my $match = 0;
+      ARCH: for my $a (@ALLARCH) {
+       next ARCH unless fnmatch $pat, $a;
+       $match = 1; push @ARCH, $a;
+      }
+      $match or barf "no architectures match `$pat'";
+    } elsif ($arg =~ /^:(.*)$/) {
+      my $f = $1;
+      exists $FLAVOUR{$f} or barf "no architeture flavour `$f'";
+      push @ARCH, @{%FLAVOUR{$f}};
+    } else {
+      push @CMD, $arg;
+      last OPT;
+    }
+  } else {
+    $bad = 1;
+  }
+}
+$bad = 1 unless @ARCH && @CMD;
+push @CMD, $mo->rest, @{$mo->{argv}};
+if ($bad) { usage STDERR; exit 2; }
+
+###--------------------------------------------------------------------------
+### Hack the arguments and run `make'.
+
+sub hack ($) {
+  my ($x) = @_;
+  $x =~ s/'/'\\''/g;
+  $x =~ s/\$/\$\$/g;
+  $x =~ s/\Q$MARKER/\$*/g;
+  return "'$x'";
+}
+
+our $TARGET = "run";
+if ($CMD[0] eq 'make') { $TARGET = "run-make"; shift @CMD; }
+our @MAKE = ("make");
+push @MAKE, "-f$CROSSDIR/Makefile";
+push @MAKE, "-j$JOBS" if defined $JOBS;
+push @MAKE, $TARGET;
+push @MAKE, "ARCHS=" . join " ", @ARCH;
+push @MAKE, "ARGS=" . join " ", map { hack $_ } @CMD;
+push @MAKE, "CROSSDIR=$CROSSDIR";
+push @MAKE, "DIR=" . hack $DIR;
+exec @MAKE;
+barf "make: $!";
+
+###----- That's all, folks --------------------------------------------------