Alien-Xrepo
view release on metacpan or search on metacpan
lib/Alien/Xrepo/Runtime.pm view on Meta::CPAN
use feature qw[class try];
no warnings 'experimental::class';
#
class Alien::Xrepo::Runtime v1.0.0 {
use Alien::Xrepo;
use Alien::Xrepo::Build::Recipe;
use JSON::PP qw[decode_json];
use Path::Tiny;
use File::ShareDir ();
#
field $pkg_name : param = undef;
field $recipe : param = undef; # Recipe object (or file/dir) - alternative to inline pkg_name
field $root : param = undef;
field $verbose : param = 0;
field $repo : param //= undef; # injectable engine
field $snapshot : param //= undef; # path to a snapshot JSON (hermetic mode)
field $autodetect_snapshot : param //= 1; # look for dist auto/share snapshots
field $install_opts : param = {}; # ambient profile merged under every package def
field $cache : param //= 1; # forward the engine's warm-start cache to Alien::Xrepo
#
field $recipe_obj = undef; # resolved Alien::Xrepo::Build::Recipe
field $infos = {}; # package => Alien::Xrepo::PackageInfo
field $snap = {}; # package => raw snapshot data
field $r = undef; # resolved Alien::Xrepo engine
field $snap_install_type = undef;
#
ADJUST {
if ( !defined $pkg_name && !defined $recipe && $self->can('pkg_name') ) {
$pkg_name = $self->pkg_name;
}
die "pkg_name or a recipe is required" if !defined $pkg_name && !defined $recipe;
if ( !keys %$install_opts && $self->can('install_opts') ) {
$install_opts = { $self->install_opts };
}
$r = $repo // Alien::Xrepo->new( root => $root, verbose => $verbose, cache => $cache );
if ( defined $recipe ) {
$recipe_obj = ref $recipe
? $recipe
: Alien::Xrepo::Build::Recipe->new(
defined $recipe && -d $recipe ? ( dir => $recipe ) : ( file => $recipe ),
);
}
else {
$recipe_obj = Alien::Xrepo::Build::Recipe->new( packages => $pkg_name );
}
$self->_load_snapshot;
}
#
method package_names () { $recipe_obj->packages }
method package_defs () { $recipe_obj->package_defs }
#
# Hermetic mode: a snapshot file wins entirely (no xrepo subprocess).
method _load_snapshot () {
my $file = $snapshot;
if ( !defined $file && $autodetect_snapshot ) {
for my $cand ( __PACKAGE__->_snapshot_candidates_for( ref $self ) ) {
if ( -e $cand ) { $file = $cand; last; }
}
}
return unless defined $file && -e $file;
my $data = eval { decode_json( path($file)->slurp_utf8 ) };
return unless ref $data eq 'HASH';
$snap = $data->{packages} // {};
$snap_install_type = $data->{install_type};
for my $name ( keys %$snap ) {
$infos->{$name} = Alien::Xrepo::PackageInfo->new( %{ $snap->{$name} } );
}
return;
}
# Where autodetect looks for a subclass: the installed dist's share dir then the source-tree build artifact layout.
# The snapshot written by the Build engine lives at <share>/<dist>/xrepo-snapshot.json, where <dist> is
# Alien-<module tail> (e.g. Alien::Zstandard -> Alien-Zstandard).
sub _snapshot_candidates_for ( $class, $instance ) {
my @parts = split /::/, $instance;
shift @parts if $parts[0] eq 'Alien';
my $dist = 'Alien-' . join '-', @parts;
my @cand = path('blib', 'lib', 'auto', 'share', 'dist', $dist)->child('xrepo-snapshot.json')->stringify;
my $share = eval { File::ShareDir::dist_dir($dist) };
unshift @cand, path($share)->child('xrepo-snapshot.json')->stringify if $share;
return @cand;
}
# Lazy resolution: so long as we are not serving a snapshot, fetch paths for a package the first time it is asked
# for and remember them. An unresolvable package settles at undef (safe defaults) instead of killing the caller, so
# a cold checkout (no snapshot, no store) yields safe defaults.
method _ensure_info ($pkg) {
my $info = $infos->{$pkg};
return $info if defined $info;
my $version = $recipe_obj->version_for($pkg);
my %opts = $recipe_obj->opts_for( $pkg, %$install_opts );
my $resolved;
try { $resolved = $r->fetch( $pkg, $version, %opts ) }
catch ($e) {
warn "[!] could not resolve $pkg: $e\n" if $verbose;
$resolved = undef;
}
$infos->{$pkg} = $resolved;
return $resolved;
}
#
method _pkg_info ( $pkg = undef ) {
my $name = $pkg // ( $recipe_obj->packages )[0];
die "Unknown package '$name'. Known: @{[ $recipe_obj->packages ]}" unless grep { $_ eq $name } $recipe_obj->packages;
return $self->_ensure_info($name);
}
#
method libpath ( $pkg = undef ) { my $i = $self->_pkg_info($pkg); $i ? $i->libpath : undef }
method ffi_lib ( $pkg = undef ) { $self->libpath($pkg) }
method bin_dir ( $pkg = undef ) { my $i = $self->_pkg_info($pkg); $i ? $i->bin_dir : () }
method version ( $pkg = undef ) { my $i = $self->_pkg_info($pkg); $i ? $i->version : undef }
method kind ( $pkg = undef ) { my $i = $self->_pkg_info($pkg); $i ? $i->kind : undef }
#
method cflags ( $pkg = undef ) {
my $i = $self->_pkg_info($pkg);
return '' unless $i;
return join ' ', map {"-I$_"} @{ $i->includedirs };
}
method cflags_static ( $pkg = undef ) { $self->cflags($pkg) }
#
method libs ( $pkg = undef ) {
( run in 0.624 second using v1.01-cache-2.11-cpan-364913b4093 )