Perl/XS developers have almost always faced a dependency problem: how do you ensure a CPAN module can find, download, and build a non-Perl library? Whether it's openssl, libpng, or nuklear, the traditional solution has been a complex dance of shell commands, pkg-config probes, and precarious make invocations.
Today, we have two primary philosophies for solving this. On one side stands Alien::Build, the mature, flexible Swiss Army knife of the Perl ecosystem. On the other stands Alien::Xrepo, a new contender that leverages the cross-platform power of the Xmake/Xrepo ecosystem.
Alien::Xrepo is really three layers, and all three are built with Perl 5.40's class syntax:
- Alien::Xrepo is a thin engine that drives
xmake's package manager,xrepofrom pure Perl. It installs, fetches, scans, downloads, imports, and exports packages, and hands back structured data. - Alien::Xrepo::Runtime is the consumer layer. You subclass this for your Alien module; it resolves and caches an installed package's paths on demand. No recipe, no build step, no probe — just accessors in the familiar
cflags/libs/libpathstyle. - Alien::Xrepo::Build and Alien::Xrepo::Build::Recipe make up the distribution authoring layer. A declarative recipe defines what the dist needs and the build engine runs a
configure -> probe -> install -> gather -> export -> testpipeline that leaves an isolated, completely self-contained snapshot behind for the runtime layer.
In this article, we'll compare the Alien::Build way with the Alien::Xrepo way.
Alien::Build: The Recipe Based Approach
Alien::Build (and its companion alienfile) is built on the idea of a recipe. As an author, you write a DSL that describes exactly how to fetch, build, and probe a library.
Example: A typical alienfile for nuklear
Nuklear is a single-header library, which sounds easy, but to use it with FFI, you must create a C wrapper to compile it into a shared library. There is no prebuilt nuklear.so/nuklear.dll anywhere; you always have to synthesize one for FFI, in Alien::Build or in a registry.
use alienfile;
share {
start_url => 'https://github.com/vurtun/nuklear/archive/refs/tags/v4.13.2.tar.gz';
# We have to manually write a C file to 'inject' the implementation
# so it can be compiled into a .so / .dll
patch [
'echo "#define NK_IMPLEMENTATION\n#include \"nuklear.h\"" > nuklear_wrapper.c'
];
build [
[ '%{cc}', '-shared', '-fPIC', '-o', 'libnuklear.%{so}', 'nuklear_wrapper.c' ],
[ 'mkdir', '-p', '%{.install.prefix}/lib', '%{.install.prefix}/include' ],
[ 'cp', 'libnuklear.%{so}', '%{.install.prefix}/lib/' ],
[ 'cp', 'nuklear.h', '%{.install.prefix}/include/' ]
];
};
gather sub {
my($build) = @_;
my $prefix = $build->install_prop->{prefix};
$build->runtime_prop->{cflags} = "-I$prefix/include";
$build->runtime_prop->{libs} = "-L$prefix/lib -lnuklear";
};
Alien::Xrepo: The Registry-Based Approach
Alien::Xrepo takes a different path. Instead of teaching Perl how to build a specific library, it delegates that responsibility to xrepo. When you use Alien::Xrepo, the three pillars of alienfile effectively disappear for the common case:
- No Download Step: xrepo handles mirrors, checksums, and git submodules. (
Alien::Xrepo->downloadis there when you need to fetch source without building.) - No Build Step: xrepo knows if a package needs
Autoconf,CMake,Meson,Bazel, or any other build tool. It handles compiler flags, cross-compilation, and dependency trees automatically. - No Gather Step: This is perhaps the biggest win. xrepo returns a structured JSON record containing the exact include directories, library paths, and linker flags. There is no "searching" for files; the registry provides the map.
Example: The consumer layer, Alien::Xrepo::Runtime
Writing the runtime half of an Alien module is just a subclass that names the package:
use v5.40;
use feature 'class';
no warnings 'experimental::class';
use Alien::Xrepo::Runtime;
class Alien::Zlib : isa(Alien::Xrepo::Runtime) {
# The xrepo package to resolve. The first named package is the primary.
method pkg_name { 'zlib' }
}
That's it. You don't need to know where the source code lives, how to run make, or how to find zlib1.dll / libz.so. When a consumer calls an accessor, Runtime lazily asks the registry to resolve the package and remembers the paths:
my $zlib = Alien::Zlib->new;
say $zlib->version; # 1.3.2
say $zlib->cflags; # -I.../libz/include
say $zlib->libs; # -L.../libz/lib -lz (or .lib on MSVC)
say $zlib->libpath; # path to the shared object for FFI
say $zlib->install_type; # "share"
The accessor surface follows Alien::Base conventions: cflags, cflags_static, libs, libs_static, libpath/ffi_lib, bin_dir, version, install_type, plus kind, alt, find_header, and package_info. Resolution is cached, so repeat access costs nothing. This is the consumer side: it assumes the package is already in a store (installed by the distribution's build phase or placed there by a direct $repo->install('zlib') call).
3. Comparing the Developer Experience
| Feature | Alien::Build / alienfile | Alien::Xrepo |
|---|---|---|
| Logic Location | Inside your Perl distribution. | Package recipes in the global xrepo registry; your dist carries only a small JSON recipe. |
| Naming a dependency | Write start_url, patch, build, gather. |
One line in xrepo.json / one pkg_name in a Runtime subclass. |
| Maintenance | You must update URLs and patches. | Community maintainers update xrepo recipes; you can pin a version. |
| Cross-Platform | Requires manual logic for Win32/Unix. | Handled by xmake's platform/toolchain layer. |
| Dependencies | Hard to chain multiple Aliens. | xrepo handles the full dependency tree (plat, arch, kind, configs per package). |
| Perl Version | Works on older Perls. | Requires Perl 5.40+ (for class). |
Integration with Distribution Builders
An Alien distribution built on Alien::Xrepo has exactly three moving parts: a recipe, the build pipeline, and the runtime subclass.
The recipe: xrepo.json
A recipe is a small JSON file that declares what the dist needs. Here is the whole recipe for Alien-Zstandard:
{
"name" : "Alien-Zstandard",
"packages" : [ { "name" : "zstd", "kind" : "shared" } ]
}
Per-package entries accept version, plat, arch, mode, kind, configs, and the other install options; a defaults block applies a profile to every package. Multi-package dists are just a longer list. For example, Alien-SDL3 would declare libsdl3, libsdl3_image, libsdl3_ttf, and libsdl3_mixer in one recipe, and xrepo resolves each package's own transitive dependencies.
Driving the build: Alien::Xrepo::Build
A Build.PL (or any build-time driver) runs the pipeline. From the real eg/examples/Alien-Zstandard:
use v5.40;
use FindBin qw[$Bin];
use Path::Tiny;
use Alien::Xrepo::Build;
my $dist = 'Alien-Zstandard';
my $snapshot = path($Bin)->child( 'blib', 'lib', 'auto', 'share', 'dist', $dist, 'xrepo-snapshot.json' );
Alien::Xrepo::Build->new( recipe => $Bin, snapshot => $snapshot )->run;
run performs configure -> probe -> install -> gather -> export -> test. configure merges the recipe's defaults into an install profile; probe checks what xrepo already reports for each package (and, under the default probe_policy => 'skip', installs are skipped when a probe is already satisfied); install and gather place/record each package in the store; export writes the runtime snapshot and, optionally, xrepo package archives. The engine can checkpoint progress and resume an interrupted run, and hooks (register_hook) let you run Perl code before any stage.
The snapshot written at blib/lib/auto/share/dist/<Alien-Dist>/xrepo-snapshot.json is what makes downstream installs hermetic: Alien::Xrepo::Runtime auto-detects that file in the installed dist's share dir and serves every accessor straight from it, with no xrepo subprocess at all.
The consumer: subclass Alien::Xrepo::Runtime
Alongside the recipe, the dist ships the runtime subclass (again from the real example):
use v5.40;
use feature 'class';
no warnings 'experimental::class';
use Alien::Xrepo::Runtime;
class Alien::Zstandard : isa(Alien::Xrepo::Runtime) {
method pkg_name {
return [ { name => 'zstd', kind => 'shared' } ];
}
}
Going direct with Alien::Xrepo (Makefile.PL / arbitrary build code)
If you'd rather not use the pipeline, the low-level engine is fully usable on its own. It installs into a store and hands back a PackageInfo you can read for Makefile.PL:
use ExtUtils::MakeMaker;
use Alien::Xrepo;
my $repo = Alien::Xrepo->new( root => 'blib/lib/share' ); # isolated store
my $zlib = $repo->install( 'zlib' ); # -> PackageInfo
my $INC = join ' ', map { "-I$_" } @{ $zlib->includedirs };
my $LIBS = join ' ', map { "-L$_" } @{ $zlib->linkdirs },
map { "-l$_" } @{ $zlib->links };
WriteMakefile(
NAME => 'My::Module',
VERSION_FROM => 'lib/My/Module.pm',
CONFIGURE_REQUIRES => { 'Alien::Xrepo' => 'v0.9.5' },
INC => $INC,
LIBS => [$LIBS]
);
Existing Alien::Build users can keep their alienfile and use this dist's Alien::Build::Plugin::Build::Xrepo plugin, which wires the probe/download/install hooks to Alien::Xrepo — recipes (in the shape of xmake packages) replace hand-written build steps inside the familiar Alien::Build toolchain.
The "Zero-Recipe" Advantage
The true strength of the registry approach is revealed when a library has complex dependencies of its own.
Imagine building libwebp. It might require libjpeg, libpng, and zlib. In the Alien::Build world, you might create four separate Alien distributions and coordinate their alienfiles so they find each other.
With Alien::Xrepo, you name libwebp once in a recipe. xrepo resolves the tree, builds all of the libraries in the correct order, links them together, and records a resolved PackageInfo per request. The Alien-SDL3 example above is the same idea at distribution scale: four sibling SDL libraries, each with its own transitive dependencies, all pulled and resolved by one recipe.
By basing your work on Alien::Xrepo, you aren't just writing a wrapper; you are tapping into a global effort to make C/C++ libraries as easy to install as CPAN modules.
Summary
Alien::Build remains the right choice if:
- You need to support Perls older than 5.40.
- The library you are wrapping is extremely obscure and doesn't exist in any package registry.
- You need highly bespoke patching logic that only Perl can provide.
Alien::Xrepo is the superior choice if:
- You want to leverage modern Perl 5.40 features.
- You want to support Windows, macOS, and Linux without writing three different sets of build logic.
- You want to spend your time writing FFI bindings, not debugging
Makefileerrors.
By moving the "Recipe" out of your Perl code and into a managed registry like xrepo, you reduce the surface area for bugs and ensure your Alien module stays working long after you've stopped updating the download URLs.
Comments