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:

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:

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:

Alien::Xrepo is the superior choice if:

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.