OK, here’s an example. It’s a simple script, called ya-wrap-ocamlfind
(“yet another ocamlfind wrapper”, $(YAWRAP)
). I’d like to add this support to ocamlfind, but … ugh, I fear writing this code in OCaml would be a PITA. It took me a few minutes to write this script in Perl, and then some more time to integrate it into my most-recent OCaml project, where it materially simplified the Makefiles. I’ve wanted to have something like this for a good while, but always resisted b/c ocamlfind
already parses the command, builds a new command, and execs that. To put yet another wrapper ahead of ocamlfind seems like waste. But now that I have it, I think I’ll keep it, b/c it does simplify things a lot.
Here’s how you use it:
- suppose you have a Makefile line (in an implicit rule) like:
$(OCAMLFIND) ocamlc $(DEBUG) $(WARNERR) $(OCAMLCFLAGS) -package $(PACKAGES) -c $<
- Now, different files can be in different syntaxes (“camlp5o” or “camlp5r”) but ALSO, some files need special extra arguments (different ocamlfind packages, maybe extra arguments for the
ppx_import
(or pa_ppx_import
) PPX rewriter). Maybe for other PPX rewriters, too. So some files need a different Makefile build-line. But really, the most complicated it gets is to add
-syntax camlp5o -package pa_ppx.import,pa_ppx_migrate -ppopt -pa_import-I -ppopt .
to the build-line. So you could imagine that the file had this line in a start-of-file comment, viz.
(** -syntax camlp5o -package pa_ppx.import,pa_ppx_migrate -ppopt -pa_import-I -ppopt . *)
and then the makefile line was:
$(YAWRAP) $(OCAMLFIND) ocamlc $(DEBUG) $(WARNERR) $(OCAMLCFLAGS) -package $(PACKAGES) -c $<
And now $(YAWRAP)
will take the cmdline, treat the last argument as a file (or, if there’s an argument --
, then all args after that), pull out the first line of the file if a comment of the form above, and append the contents of the comment to the command. If there’s more than one file, it’ll do that one-by-one to each file. And then execute those commands. Cheap and easy.
I’m imagining going further: those extra arguments really could be shortened by using a Makefile/environment variable
export IMPORT_CFLAGS=-ppopt -pa_import-I -ppopt .
and using that environment variable in the comment – but that would require extending the script to do simple parsing for variable-occurrences and expanding them. Which is a few lines in Perl, but not sure how much it’d be in OCaml.
For reference, here’s the script.
#!/usr/bin/env perl
use strict ;
use String::ShellQuote ;
our @cmd ;
our @files ;
while (@ARGV) {
if ($ARGV[0] eq "--") { shift @ARGV ; @files = @ARGV ; last ; }
elsif (int(@ARGV) == 1) {
@files = @ARGV ;
last ;
}
else { push(@cmd, shift @ARGV) ; }
}
{
@cmd = map { shell_quote($_) } @cmd ;
foreach my $f (@files) {
my $extra = discover_args($f) ;
my $cmd = "@cmd $extra $f\n" ;
print STDERR $cmd ;
system($cmd) ;
}
}
sub discover_args {
my $f = shift ;
open(F, "<$f") || die "$0: cannot open $f for read (to sense extra args)" ;
my $line1 = <F> ;
close(F) ;
if ($line1 =~ m,^\(\*\*(.*?)\*\),) {
return $1 ;
}
else {
return "" ;
}
}
I’ll note that every variable is properly scoped, everything has a fixed type, so really all I’m getting from Perl here is concision.
P.S. it uses String::ShellQuote
to shell-quote the command itself before concating everything together into a string that it passes to the shell – so the command is effectively not reparsed, but the extra text is reparsed. Details, details, details.
P.P.S. This is pretty idiomatic Perl: I’m not using any cleverness – and typically I never use cleverness when writing Perl.
P.P.P.S. The “standard” PPX rewriters sometimes need extra arguments, too (besides ppx_import
which needs them to find CMO files and MLI files): for instance the “inline-test” rewriters need 'em (as I learned when implementing pa_ppx
workalikes – so I could run the originals and my workalikes and compare results).