NAME
Alien::Xrepo::Build - An alienfile-like build engine where every stage uses xrepo
SYNOPSIS
A distribution ships a declarative recipe and lets the engine drive installs through xrepo:
# xrepo.json
{
"name" : "Exotic-Zstandard",
"defaults" : { "kind" : "shared" },
"packages" : [
{ "name" : "zstd" }
]
}
# Build.PL
use Path::Tiny;
use Alien::Xrepo::Build;
# 'snapshot' (and every other engine option) is a CONSTRUCTOR option; run()
# only folds ambient options like kind/mode/plat/arch into the recipe profile.
my $snapshot = path('blib/lib/auto/share/dist/Exotic-Zstandard/xrepo-snapshot.json');
Alien::Xrepo::Build->new( recipe => '.', snapshot => $snapshot )->run;
Set defaults => { kind => 'shared' } (or a per-package { name => 'zstd', kind => 'shared' }) when consumers bind the library with FFI (Affix, FFI::Platypus): xrepo builds these packages as static libraries by default, and libpath / ffi_lib must point to a real shared library for them to load successfully.
DESCRIPTION
Alien::Xrepo::Build is a build engine in the spirit of Alien::Build. It acts as a stage pipeline with hooks and three property buckets, all serialized as plain JSON. Unlike an alienfile, there are no download/extract/compile stages of our own. All of that is handled by xrepo via Alien::Xrepo. What remains is the essential orchestration:
probe: Ask
xrepowhether a package already satisfies the recipe and skip the install if it does.install: Run one
xrepo installper package, merging per-package version/flags/configs over the ambient profile and isolating failures.gather: Ensure every package has consumer-facing paths (
xrepo fetch --jsondata) recorded.export: Optionally export packages and/or write the runtime snapshot for hermetic consumers.
checkpoint/resume: Allow a failed run to be resumed without reinstalling what already succeeded.
Pipeline Stages
The pipeline runs configure, probe, install, gather, export, and test in order. Each stage method is idempotent per run (a checkpointed stage is skipped). Hooks run immediately before their stage body:
$build->register_hook( install => sub ($build) { ... } );
Properties
The three buckets mirror Alien::Build's meta/install/runtime split:
meta_prop: Recipe facts (name, packages, package defs, defaults, pkg_roots, local_repos, hooks).install_prop: State of this run (store root, ambient profile, probe results).runtime_prop: What consumers need (per-package include/lib/link data and anerrorsmap).
The Recipe
If it exists, xrepo.json is loaded and validated by Alien::Xrepo::Build::Recipe. Its packages array mirrors an Alien::Xrepo::Runtime pkg_name declaration:
A plain string is a bare
xrepopackage name.A hashref adds install flags, e.g.,
{ name => 'zstd', version => '1.5.6', kind => 'shared', configs => { legacy => true } }.
defaults holds ambient flags applied under every package (using the same key set, plus configs). pkg_roots, local_repos, hooks, and name are the remaining top-level keys.
The recipe may be given to new( ... ) as a path/dir, an Alien::Xrepo::Build::Recipe object, or a hashref of the same fields (which is exactly what an Alien::Xrepo::Runtime subclass's recipe() method returns). A dist that describes itself in the class therefore plugs straight into the engine: no xrepo.json is needed, and the file-based and inline recipes are entirely interchangeable.
pkg_roots maps a package name to an environment variable whose value names that package's root directory (mirroring Alien::Build's pkg_roots). When the variable is set and points to an existing directory, the package is treated as system-provided there. The probe reports it as satisfied, install/gather skip xrepo entirely, and the root's include/lib/bin subdirectories (when present) become the package's resolution data. A root-resolved package leaves install_type as 'system'.
hooks lists recipe hook module names. Each module is loaded once when the configure stage runs and must expose a class method register_hooks($build). The module typically calls the build's register_hook to attach its stage callbacks.
METHODS
new( ... )
Alien::Xrepo::Build->new(
recipe => $path_or_recipe, # xrepo.json path/dir, a Recipe object, or a hashref of recipe fields
root => $store_root, # XMAKE_PKG_INSTALLDIR
repo => $alien_xrepo, # injectable engine
cache => 0, # bypass Alien::Xrepo's warm-start cache
verbose => 1,
checkpoint => $state_file, # enables resume
snapshot => $snapshot_file, # write runtime data here during export
share_dir => $share_dir, # shallow-install packages into a self-contained sharedir
export_dir => $dir, # xrepo-export each package here
probe_policy => 'skip', # skip|always|off
resume => 1, # load checkpoint on construction
update_repo => 1 # refresh xrepo repositories once when an install fails
);
cache (default 1) is forwarded to the engine this class creates, so cache => 0 disables the on-disk resolution cache (see "Cache System" in Alien::Xrepo). If an engine is injected via repo => ..., it keeps its own cache setting.
share_dir switches the run from the ambient xmake cache to a self-contained install: every package is installed into <share_dir>/<pkg> (its installdir), so the dist can ship its own copy of the libraries and never depend on the xmake cache at runtime. When share_dir is set, the snapshot export records those paths relative to share_dir; Alien::Xrepo::Runtime then resolves them against the directory where the snapshot file itself lives, meaning the same binary share directory keeps working after ./Build install relocates it (e.g., from blib to the File::ShareDir install tree). Note that xrepo nests the actual payload under <share_dir>/<pkg>/l/<pkg>/<version>/<hash>.
run( %opts )
Runs the full pipeline: configure, probe, install, gather, export, test. Ambient options (kind, mode, plat, arch, configs, ...) are folded over the recipe defaults into the install profile.
Options that are wired through the constructor (snapshot, checkpoint, export_dir, resume, probe_policy, root) must be passed to new(), not to run(). They are not profile options, so ->run( snapshot => $snapshot ) does nothing.
During a resume, stages that have already successfully run are skipped.
configure( %opts )
Prepares the run: records the recipe facts into meta_prop, folds the ambient %opts over the recipe defaults into install_prop->{profile}, resolves install_prop->{store} (falling back to root), and registers every local_repos entry with the engine. install_type is reset to 'system' and the state is checkpointed. It is safe to call more than once: a stage that already ran is skipped.
probe( )
Asks the engine whether each package already satisfies the recipe and records the result as install_prop->{probed}{<name>} = { version => ..., satisfied => 0|1 }. A package with a usable pkg_roots entry is recorded as satisfied (with its root) without consulting the engine. Skipped entirely when probe_policy => 'off'.
install( )
Runs one xrepo install per package (with per-package options merged over the profile). A package with a usable pkg_roots entry is recorded directly from its root and never passed to the engine. A package the probe found satisfied is skipped when probe_policy => 'skip' (the default) unless force is set. A failing package generates a warning, is recorded in runtime_prop->{errors}, and does not abort the remaining packages. Each successful install is recorded in runtime_prop->{packages} and bumps install_type to 'share'. When update_repo => 1 is set, the repository index is refreshed (xrepo update-repo) once, and a failed package is retried a single time before the error is recorded; useful when a stale or corrupt registry (e.g., a precompiled artifact whose checksum changed on the mirror) is the cause of the failure.
gather( )
Ensures every package not already recorded (installed, root-provided, or failed) has consumer-facing paths. It fetches each one through the engine and stores the resolution data in runtime_prop->{packages}. A root-provided package is recorded from its pkg_roots entry directly. A failed fetch is recorded in runtime_prop->{errors}.
export( )
Optionally exports each successfully installed package into export_dir (xrepo export, one <name> subdirectory per package), and writes the hermetic snapshot JSON to snapshot. This includes the install_type, per-package resolution data, any errors, and a digest of the configured recipe. The snapshot is what Alien::Xrepo::Runtime serves to resolve absolute paths without invoking an xrepo process. When share_dir is set, recorded paths that live under it are written relative to it (see share_dir above).
test( )
Runs the test hooks. There is no built-in test body; attach your own with register_hook.
register_hook( $stage, $code )
Attaches a hook. Hooks are invoked as $code->($build) immediately before the stage body.
has_hook( $stage )
Returns true when at least one hook is registered for the specified stage.
meta_prop( )
Recipe facts: name, packages, package_defs, defaults, pkg_roots, local_repos, and hooks. Filled by the configure stage.
install_prop( )
This run's state: the root, the merged ambient profile, the resolved store, and the per-package probed results.
runtime_prop( )
What consumers need: per-package resolution data under packages and a failure map under errors. This is what the export stage serializes into the snapshot.
install_type( )
'share' once any package is resolved, 'system' before that. Restored from the checkpoint upon a resume.
packages( )
Recipe package names in declaration order.
package_defs( )
The normalized per-package definitions from the recipe.
engine( )
The underlying Alien::Xrepo instance (injectable via repo => ... for tests or alternative engines).
SEE ALSO
Alien::Xrepo::Runtime, Alien::Xrepo::Build::Recipe, Alien::Xrepo, Alien::Build, alienfile
LICENSE
Copyright (C) Sanko Robinson.
This library is free software; you can redistribute it and/or modify it under the terms found in the Artistic License 2. Other copyrights, terms, and conditions may apply to data transmitted through this module.
AUTHOR
Sanko Robinson https://github.com/sanko