If your Perl distribution contains C code that needs to be compiled (an .xs extension, a helper tool, or a shared library for FFI) you have traditionally reached for ExtUtils::MakeMaker, Module::Build, or Alien::Build. Each drives make or cmake through its own abstraction.

Alien::Xmake takes a different approach: it is xmake, directly callable from Perl. If your C code already has (or could have) an xmake.lua build description, you can skip the MakeMaker layer entirely and let xmake handle the build, test, install, and packaging all invoked from a two-line Build.PL or Makefile.PL.

What is xmake?

xmake is a cross-platform, Lua-scriptable build system. It supports C, C++, Rust, Go, and more; runs on Windows, macOS, and Linux; and integrates directly with xrepo (its package manager). A single xmake.lua describes your targets, dependencies, and build rules.

Driving xmake from Perl

Alien::Xmake gives you the xmake binary, a convenience constructor, and a one-to-one method for every xmake action:

use v5.40;
use Alien::Xmake;

my $xmake = Alien::Xmake->new( verbose => 1 );

# The raw xmake binary path
say 'xmake: ' . $xmake->exe;

# Version
say 'version: ' . $xmake->config('version');

# Build, test, install
$xmake->build;
$xmake->test;
$xmake->install;

Every method corresponds to a command-line invocation (xmake build, xmake test, etc.). The verbose flag prints the commands as they run, which is invaluable during development.

The task() escape hatch

If a method doesn't exist for a particular xmake command (and I haven't noticed such an upstream change yet), task() is the escape hatch to run the command manually:

$xmake->task('run', config => 'release');
$xmake->task('show', { list = true });

You can also write to xmake's persistent configuration:

$xmake->global(vs => '2022');
$xmake->global(ndk => '/path/to/android-ndk');

A practical example: the nuklear single-header library

Nuklear is a single-header GUI library. There is no prebuilt nuklear.so anywhere, you have to synthesize one. An xmake.lua that does this is just a few lines:

target("nuklear")
    set_kind("shared")
    add_files("nk_win.c")
    add_files("nuklear_gdi.h")

Driving this from Perl looks like this:

use Alien::Xmake;
use Path::Tiny;

my $proj  = path('eg/nuklear');
my $xmake = Alien::Xmake->new( verbose => 1 );

# Build
chdir $proj;
$xmake->build;

# Install to a local directory
$xmake->install( prefix => path('dist')->absolute );

No Makefile.PL, no alienfile, no ExtUtils::CBuilder, just a three line Perl driver on top of a four line xmake.lua.

Dependency graphs

If your xmake.lua declares xrepo dependencies, you can ask xmake to print the full dependency graph in Graphviz DOT format:

my $dot = $xmake->info( undef, depgraph => 1, format => 'dot' );
path('deps.dot')->spew($dot);

# Render: dot -Tpng deps.dot -o deps.png

This is the same graph that xmake itself renders. It may be useful for understanding a complex native dependency tree without leaving Perl.

When xmake makes sense

For the right project, xmake replaces an entire build toolchain with a single Lua file and a two-line Perl driver.