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
- Your C code already uses xmake: If you maintain an xmake project, driving it from Perl is a no-brainer.
- You need cross-compilation without MakeMaker pain: xmake's
-p,-a,--toolchain,--ndkflags map directly toAlien::Xmake->build(). - You want packaging:
xmake packagecreates a distributable archive;xmake packproduces.deb/.rpm/.nsiinstallers all callable via$xmake->package(). - You're tired of Makefile generation: xmake's Lua config is a quarter the size of an equivalent
Makefile.PLfor anything beyond trivial C code.
For the right project, xmake replaces an entire build toolchain with a single Lua file and a two-line Perl driver.
Comments