Perl's Alien ecosystem has always been synonymous with one thing: fine grained access to shared libraries. You write an Alien module so that FFI::Platypus can call zlibVersion(), or so that Inline::C can link against libpng. The package metadata is full of cflags, libs, and linkdirs. Any tools you might need to build the libs will need their own Aliens which may be unmaintained or maybe not even exist.
What we really need is an Alien that can automatically provide tools: ninja to drive a build, cmake to configure one, python to run a code generator, meson, go, rust, node... Until now, a Perl distribution that depended on a tool had to either require the system administrator to install it or write a fragile Alien toolchain recipe.
Alien::Xrepo fixes this by treating binaries and libraries as first-class citizens of the same package store. Install libpng and ninja in the same line; run them both from Perl.
What is a "binary" package?
When you ask xrepo to install ninja, the registry hands you a package with a kind of binary rather than library. It has no header files, no shared object, no -l flags. Instead it ships executables — and Alien::Xrepo knows exactly where they live:
use v5.40;
use Alien::Xrepo;
use Config;
my $repo = Alien::Xrepo->new;
my $ninja = $repo->install('ninja');
# Where the executables live
my @bins = $ninja->bin_dir;
say 'ninja bin: ' . $bins[0];
# The full installation root (bin/, share/, etc.)
say 'root: ' . $ninja->installdir;
# What kind of package is it?
say 'kind: ' . $ninja->kind; # "binary"
The key difference from a library package is that bin_dir points to a bin/ directory full of executables, not a lib/ directory full of .dll/.so files.
Running a binary tool
There are two common patterns for calling an installed tool from Perl.
Option A: run it directly
Resolve the exact executable name (.exe on Windows, bare name on Unix) inside bin_dir:
use Path::Tiny;
my ($exe) = grep { -e $_ }
map { path($_)->child($^O eq 'MSWin32' ? 'ninja.exe' : 'ninja') }
$ninja->bin_dir;
say `$exe --version`;
Option B: put bin_dir on PATH
Prepend bin_dir to PATH so that every system call in this process (and every child it spawns) finds the tool:
local $ENV{PATH} = join $Config{path_sep}, $ninja->bin_dir, $ENV{PATH};
system 'ninja', '--version'; # resolves to the installed ninja
Option B is especially useful when a build tool (like meson) invokes another tool (like ninja) as a subprocess.
Libraries and binaries in the same store
You are free to install a library and a tool side by side — they share the same package store:
my $repo = Alien::Xrepo->new;
my $png = $repo->install('libpng'); # kind: library
my $ninja = $repo->install('ninja'); # kind: binary
There is no conflict: libpng lives under lib/ and include/, while ninja lives under bin/. Both report their kind correctly so you can branch on it in your code:
for my $pkg ($png, $ninja) {
if ($pkg->kind eq 'library') {
say $pkg->libpath; # shared object path
} else {
say $pkg->bin_dir; # directory containing executables
}
}
A practical example: build a C project against an installed library
If you are building a small C program and want to link it against libpng while driving the build with ninja, you can fetch the compiler flags from one and the build tool from the other:
use v5.40;
use Alien::Xrepo;
use Path::Tiny;
use Config;
my $repo = Alien::Xrepo->new;
# Libraries
$repo->install('libpng');
my $cflags = $repo->fetch('libpng', undef, cflags => 1);
my $ldflags = $repo->fetch('libpng', undef, ldflags => 1);
# Build tool
$repo->install('ninja');
local $ENV{PATH} = join $Config{path_sep}, $repo->install('ninja')->bin_dir, $ENV{PATH};
# Write a minimal build.ninja
my $src = path('prog.c')->absolute;
my $out = path('prog')->absolute;
path('build.ninja')->spew(<<NINJA);
build $out: link $src
rule = gcc \$cflags \$in -o \$out \$ldflags
NINJA
system 'ninja';
No system-level apt install or brew install required — everything is resolved by Perl at runtime.
When is this useful?
- Build-time tools in a CPAN distribution: Your
Build.PLorMakefile.PLcan pullmesonandninjaautomatically, so a user doesn't need them on the system PATH. - CI reproducibility: Pinning an exact
ninjaorcmakeversion in acpanfile(requires 'ninja', '1.13';) ensures every CI runner uses the same build infrastructure. - Interpreter pipelines: Install
pythonornodeto run a code-generation step without depending on the system interpreter being the right version.
Binary packages close the last mile: Perl now controls both what it links against and what drives the build.
Comments