Alien-Xmake

 view release on metacpan or  search on metacpan

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

    field $pureperl      : param    //= 0;
    field $jobs          : param    //= 1;
    field $destdir       : param    //= '';
    field $prefix        : param    //= '';
    ADJUST {
        -e 'META.json' or die "No META information provided\n";
    }

    method Build_PL() {
        die "Pure perl Affix? Ha! You wish.\n" if $pureperl;
        say sprintf 'Creating new Build script for %s %s', $meta->name, $meta->version;

        # We must capture the current INC to ensure the builder finds itself
        # when running the generated script.
        my $inc_str = join( ' ', map {"-I$_"} @INC );
        $self->write_file( 'Build', sprintf <<'', $^X, $inc_str, __PACKAGE__, __PACKAGE__ );
#!%s %s
use lib 'builder';
use %s;
%s->new( @ARGV && $ARGV[0] =~ /\A\w+\z/ ? ( action => shift @ARGV ) : (),
    map { /^--/ ? ( shift(@ARGV) =~ s[^--][]r => 1 ) : /^-/ ? ( shift(@ARGV) =~ s[^-][]r => shift @ARGV ) : () } @ARGV )->Build();

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

            my $dynamic_parser = CPAN::Requirements::Dynamic->new();
            my $prereq         = $dynamic_parser->evaluate($dynamic);
            $meta_struct{prereqs} = $meta->effective_prereqs->with_merged_prereqs($prereq)->as_string_hash;
            $meta = CPAN::Meta->new( \%meta_struct );
        }
        $meta->save(@$_) for ['MYMETA.json'];
    }

    # Actions
    method ACTION_build ( ) {
        say 'Building Alien-Xmake...';

        # Prepare blib
        path('blib/lib')->mkpath;
        path('blib/arch')->mkpath;
        path('blib/script')->mkpath;
        path('blib/bin')->mkpath;

        # Copy Libs
        $self->_copy_libs();

        # Alien Logic: Check or Install Xmake
        my $config_data = $self->_resolve_xmake();

        # Generate ConfigData.pm
        $self->_write_config_data($config_data);
        say 'Build complete';
    }

    method ACTION_install ( ) {
        say 'Installing...';
        require ExtUtils::Install;
        ExtUtils::Install::install( { 'blib/lib' => $Config{installprivlib}, 'blib/arch' => $Config{installarchlib} }, 1, 0, 0 );
    }

    method ACTION_clean () {
        say 'Cleaning...';
        path('blib')->remove_tree;
        path('_build_xmake')->remove_tree;
        path('config.log')->remove;
        path('Build')->remove;
        path('_build_params')->remove;
    }

    method ACTION_test ( ) {
        $self->ACTION_build();
        say 'Running tests...';
        use Test::Harness;
        my @tests = glob('t/*.t');
        runtests(@tests) if @tests;
    }

    method _copy_libs ( ) {
        my $src_root = path('lib');
        return unless $src_root->exists;
        my $iter = $src_root->iterator( { recurse => 1 } );
        while ( my $file = $iter->() ) {

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

    }

    method _resolve_xmake ( ) {

        # Check for system install
        unless ($force) {
            my $sys_path = $self->_find_system_xmake();
            if ($sys_path) {
                my $ver = $self->_get_xmake_version($sys_path);
                if ( $self->_version_cmp( $ver, $target_version ) >= 0 ) {
                    say "Found suitable system Xmake: $sys_path ($ver)";
                    return { install_type => 'system', version => $ver, bin => "$sys_path" };
                }
                say "System Xmake found ($ver) but is older than required ($target_version).";
            }
        }

        # Check build dir (idempotency)
        my $install_dir = path('blib/lib/Alien/Xmake/share')->absolute;
        $install_dir->mkpath;
        my $bin_name = ( $^O eq 'MSWin32' ) ? 'xmake.exe' : 'xmake';
        my $blib_bin = $install_dir->child( 'bin', $bin_name );
        unless ( -x $blib_bin ) {
            my $fallback = $install_dir->child($bin_name);
            $blib_bin = $fallback if -x $fallback;
        }
        if ( -x $blib_bin ) {
            my $ver = $self->_get_xmake_version($blib_bin);
            if ( $self->_version_cmp( $ver, $target_version ) >= 0 ) {
                say "Alien-Xmake build up-to-date ($ver).";
                return $self->_generate_share_config( $blib_bin, $ver );
            }
        }

        # Check existing shared installation for upgrading
        my $existing = $self->_check_existing_share();
        if ($existing) {
            my $ex_ver = $existing->{version};
            my $ex_dir = path( $existing->{install_dir} )->absolute;
            if ( $self->_version_cmp( $ex_ver, $target_version ) >= 0 ) {
                say "Found valid private Xmake ($ex_ver) in $ex_dir";
                if ( $ex_dir->stringify ne $install_dir->stringify ) {
                    say 'Copying existing installation to build directory...';
                    $self->_copy_directory( $ex_dir, $install_dir );
                }

                # Re-locate binary in new dir
                my $bin_path = $install_dir->child( 'bin', $bin_name );
                unless ( -x $bin_path ) { $bin_path = $install_dir->child($bin_name); }
                return $self->_generate_share_config( $bin_path, $ex_ver );
            }
        }

        # Download and Install
        say 'Installing a private copy of Xmake...';
        if ( $^O eq 'MSWin32' ) {
            $self->_install_windows($install_dir);
        }
        else {
            $self->_install_unix($install_dir);
        }

        # Verify Install
        my $bin_path = $install_dir->child( 'bin', $bin_name );
        unless ( -x $bin_path ) {
            my $fallback = $install_dir->child($bin_name);
            $bin_path = $fallback if -x $fallback;
        }
        if ( !-x $bin_path ) {
            die "Installation finished, but binary not found at $bin_path";
        }
        my $ver = $self->_get_xmake_version($bin_path);
        say "Private install successful: $ver";
        return $self->_generate_share_config( $bin_path, $ver );
    }

    method _generate_share_config( $bin_path, $version ) {

        # Calculate relative path from Alien/xmake/ConfigData.pm to the binary
        # ConfigData is in lib/Alien/xmake/
        # Bin is in      lib/Alien/xmake/share/bin/
        my $lib_base = path('blib/lib/Alien/Xmake')->absolute;
        my $rel_bin  = $bin_path->relative($lib_base)->stringify;

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

            sub bin {
                my $bin = $config->{bin};
                return unless defined $bin;
                return $bin if $config->{install_type} eq 'system';
                File::Spec->rel2abs($bin, dirname(__FILE__))
            }
        };
        1;
        PERL
        $dest->spew_utf8($content);
        say "Generated $dest";
    }

    method _install_windows ($installdir) {
        my $temppath = path('_build_xmake');
        $temppath->mkpath;
        my $arch_env   = $ENV{PROCESSOR_ARCHITECTURE} // '';
        my $arch64_env = $ENV{PROCESSOR_ARCHITEW6432} // '';
        my $filename;

        # Check for ARM64

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

        }
        my $url     = "https://github.com/xmake-io/xmake/releases/download/$target_version/$filename";
        my $outfile = $temppath->child('xmake-installer.exe');
        if ( !$self->_download_file( $url, $outfile ) ) {
            die "Download failed for $url";
        }
        my $install_str = $installdir->stringify;
        $install_str =~ s{/}{\\}g;
        my $outfile_str = $outfile->stringify;
        $outfile_str =~ s{/}{\\}g;
        say "Installing to $install_str...";

        # /NOADMIN: Avoid UAC prompt if possible (installs to local user path if allowed)
        # /S: Silent
        # /D: Destination directory
        my $cmd = qq{"$outfile_str" /NOADMIN /S /D=$install_str};
        my $ret = system($cmd);
        die "Installer failed with code $ret" if $ret != 0;

        # Cleanup
        path('_build_xmake')->remove_tree;

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

        $build_dir->remove_tree;
        $build_dir->mkpath;
        my $sudo = '';
        if ( $> != 0 && $self->_run_cmd('sudo -n --version >/dev/null 2>&1') ) {
            $sudo = 'sudo';
        }
        unless ( $self->_test_tools() ) {

            # Do not auto-install system tools unless requested.
            if ( $ENV{ALIEN_INSTALL_SYSTEM_TOOLS} ) {
                say 'Attempting to install system tools via package manager...';
                if ( $self->_install_tools($sudo) ) {
                    $self->_test_tools() or $self->_raise_dep_error();
                }
                else {
                    $self->_raise_dep_error();
                }
            }
            else {
                $self->_raise_dep_error();
            }

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

        my $fasthost = $self->_get_fast_host();
        if ( $fasthost eq 'gitee.com' ) {
            @urls = ( $cdn_url, $gh_url );
        }
        else {
            @urls = ( $gh_url, $cdn_url );
        }
        my $outfile    = $build_dir->child('xmake.run');
        my $downloaded = 0;
        for my $url (@urls) {
            say "Attempting download from $url...";
            if ( $self->_download_file( $url, $outfile ) ) {
                $downloaded = 1;
                last;
            }
        }
        die 'All download attempts failed.' unless $downloaded;
        say 'Extracting source bundle...';
        $self->_run_cmd( 'sh', $outfile, '--noexec', '--quiet', '--target', $build_dir ) or die 'Failed to extract .run file';
        my $cwd = cwd();
        chdir $build_dir or die 'Cannot chdir to build dir';
        say 'Building Xmake...';

        # DETERMINE MAKE
        # On FreeBSD/NetBSD/OpenBSD/DragonFly, 'make' is BSD make.
        # Xmake generates GNU makefiles. We MUST use gmake.
        my $make_cmd = 'make';
        if ( $^O =~ /bsd/i || $^O eq 'dragonfly' ) {
            if ( $self->_run_cmd('gmake --version >/dev/null 2>&1') ) {
                $make_cmd = 'gmake';
            }
            else {
                # This should have been caught by _test_tools, but safe guard here
                die 'gmake is required on BSD systems to build Xmake.';
            }
        }
        elsif ( $self->_run_cmd('gmake --version >/dev/null 2>&1') ) {
            $make_cmd = 'gmake';
        }
        if ( -f 'configure' ) {
            say "Configuring with make=$make_cmd...";
            system( './configure', "--make=$make_cmd" ) == 0 or die 'Configure failed';
            system( $make_cmd,     '-j4' ) == 0              or die 'Make failed';
            say "Installing to $installdir...";
            system( $make_cmd, 'install', "PREFIX=$installdir" ) == 0 or die 'Install failed';
        }
        else {
            system( $make_cmd, 'build',   '-j4' ) == 0                or die 'Make build failed';
            system( $make_cmd, 'install', "prefix=$installdir" ) == 0 or die 'Make install failed';
        }
        chdir $cwd;
    }

    method _get_host_speed ($host) {

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

        if ( $output =~ /time=(\d+)/ ) {
            return $1;
        }
        return 65535;
    }

    method _get_fast_host ( ) {
        if ( $ENV{GITHUB_ACTIONS} ) {
            return 'github.com';
        }
        say 'Testing connection speed to github.com vs gitee.com...';
        my $speed_gitee  = $self->_get_host_speed('gitee.com');
        my $speed_github = $self->_get_host_speed('github.com');
        if ( $speed_gitee <= $speed_github ) {
            return 'gitee.com';
        }
        return 'github.com';
    }

    method _download_file ( $url, $dest ) {
        my $dest_str = "$dest";

        # Try HTTP::Tiny + IO::Socket::SSL
        if ( eval { require IO::Socket::SSL; 1 } ) {
            say 'Downloading with HTTP::Tiny...';
            my $http = HTTP::Tiny->new( verify_SSL => 1 );
            my $res  = $http->mirror( $url, $dest_str );
            if ( $res->{success} ) {
                return 1;
            }
            say "HTTP::Tiny failed: $res->{status} $res->{reason}";
        }
        else {
            say 'HTTP::Tiny skipped: IO::Socket::SSL not installed.';
        }

        # Try curl
        if ( $self->_run_cmd('curl --version >/dev/null 2>&1') ) {
            say 'Downloading with curl...';

            # -L: Follow redirects, -f: Fail on error, -o: Output
            if ( $self->_run_cmd( 'curl', '-L', '-f', '-o', $dest_str, $url ) ) {
                return 1;
            }
            say 'curl failed.';
        }

        # Try wget
        if ( $self->_run_cmd('wget --version >/dev/null 2>&1') ) {
            say 'Downloading with wget...';
            if ( $self->_run_cmd( 'wget', '--quiet', '-O', $dest_str, $url ) ) {
                return 1;
            }
            say 'wget failed.';
        }
        return 0;
    }

    method _test_tools ( ) {
        say 'Checking build tools...';
        my $ok = 1;
        if ( $self->_run_cmd('git --version >/dev/null 2>&1') ) {
            say ' - git: Found';
        }
        else {
            say ' - git: Missing';
            $ok = 0;
        }

        # GNU or BSD make
        my $found_make = 0;
        if ( $self->_run_cmd('gmake --version >/dev/null 2>&1') ) {
            say ' - make: Found (gmake)';
            $found_make = 1;
        }
        elsif ( $self->_run_cmd('make --version >/dev/null 2>&1') ) {
            say ' - make: Found (make - likely GNU compatible)';
            $found_make = 1;
        }
        elsif ( $self->_run_cmd('make -V MACHINE >/dev/null 2>&1') ) {
            say ' - make: Found (make - BSD)';

            # If we are on BSD, this is technically 'found', but we know it won't work for Xmake.
            # We must fail here to trigger the installer if we are on BSD.
            if ( $^O =~ /bsd/i || $^O eq 'dragonfly' ) {
                say '   ! Note: BSD make is not compatible with Xmake build (needs gmake).';
            }
            else {
                $found_make = 1;    # On non-BSD systems, maybe they have a different make setup.
            }
        }

        # STRICT CHECK for BSDs
        if ( $^O =~ /bsd/i || $^O eq 'dragonfly' ) {
            unless ( $self->_run_cmd('gmake --version >/dev/null 2>&1') ) {
                say ' - make: Missing gmake (Required on FreeBSD/BSD for Xmake build)';
                $found_make = 0;
                $ok         = 0;
            }
            else {
                $found_make = 1;
            }
        }
        unless ($found_make) {
            say ' - make: Missing';
            $ok = 0;
        }

        # Compiler
        my $found_cc = 0;
        my $prog     = "#include <stdio.h>\nint main(){return 0;}";
        my @compilers
            = ( [ 'cc', '-xc', '-', '-o', '/dev/null' ], [ 'gcc', '-xc', '-', '-o', '/dev/null' ], [ 'clang', '-xc', '-', '-o', '/dev/null' ] );
        for my $cmd_ref (@compilers) {
            my $name    = $cmd_ref->[0];
            my $cmd_str = join( ' ', @$cmd_ref );
            my $pid     = open( my $ph, '|-', "$cmd_str >/dev/null 2>&1" );
            if ($pid) {
                print $ph $prog;
                close $ph;
                if ( $? == 0 ) {
                    say " - compiler: Found ($name)";
                    $found_cc = 1;
                    last;
                }
            }
        }
        unless ($found_cc) {
            say ' - compiler: Missing (checked cc, gcc, clang)';
            $ok = 0;
        }
        return $ok;
    }

    method _install_tools ($sudo) {
        my @installers = (
            [ 'apt --version', 'apt install -y git build-essential libreadline-dev' ],
            [ 'dnf --version', 'dnf install -y git readline-devel bzip2 @development-tools' ],
            [ 'yum --version', qq[yum install -y git readline-devel bzip2 && $sudo yum groupinstall -y 'Development Tools'] ],

builder/Alien/Xmake/Builder.pm  view on Meta::CPAN

            [ 'pacman -V',              'pacman -S --noconfirm --needed git base-devel ncurses readline' ],
            [ 'emerge -V',              'emerge -atv dev-vcs/git' ],
            [ 'pkg list-installed',     'pkg install -y git gmake' ],
            [ 'nix-env --version',      'nix-env -i git gcc readline ncurses' ],
            [ 'apk --version',          'apk add git gcc g++ make readline-dev ncurses-dev libc-dev linux-headers' ],
            [ 'xbps-install --version', 'xbps-install -Sy git base-devel' ]
        );
        for my $pair (@installers) {
            my ( $check, $install ) = @$pair;
            if ( $self->_run_cmd( $check . ' >/dev/null 2>&1' ) ) {
                say "Detected package manager via: $check";
                say 'Attempting to install dependencies...';
                return $self->_run_cmd( $sudo . ' ' . $install );
            }
        }
        return 0;
    }

    method _raise_dep_error () {
        die <<~'MSG';
    Dependencies Installation Failed or Skipped.

eg/alien_xrepo.pl  view on Meta::CPAN


# Automatically wrap zlib as a whole with Affix::Wrap
use Affix;
use Affix::Wrap;
my $zlib = $repo->install('zlib');
Affix::Wrap->new(
    project_files => [ $zlib->find_header('zlib.h') ],
    include_dirs  => [ $zlib->includedirs ],
    types         => { gzFile_s => Pointer [Void] }
)->wrap( $zlib->libpath );
say 'zlib version:   ' . zlibVersion();

# Wrap a single function from sqlite3 with Affix
use Affix;
my $sqlite3 = $repo->install('sqlite3');
affix $sqlite3->libpath, 'sqlite3_libversion', [], String;
say 'SQLite version: ' . sqlite3_libversion();

# Wrap a single function from libpng with FFI::Platypus
use FFI::Platypus;
my $lz4 = $repo->install('lz4');
my $ffi = FFI::Platypus->new;
$ffi->lib( $lz4->libpath );
$ffi->attach( 'LZ4_versionString', [] => 'string' );
say 'LZ4 version:    ' . LZ4_versionString();

eg/xmake_demo.pl  view on Meta::CPAN

use v5.40;
use Alien::Xmake;
use Path::Tiny;

# Setup
my $xmake     = Alien::Xmake->new();
my $xmake_bin = $xmake->exe;
my $version   = $xmake->config('version');
say "Using xmake $version at $xmake_bin";

# Create a temporary project directory
my $project_dir = Path::Tiny->tempdir( CLEANUP => 1 );
say "Working in: $project_dir";

# Use 'xmake create' to generate a C shared library project
# -P .      : Create in current directory
# -l c      : Language C
# -t shared : Shared library
chdir $project_dir;
system( $xmake_bin, 'create', '-P', '.', '-l', 'c', '-t', 'shared', 'alien_lib' ) == 0 or die "Failed to create project";

# Build the project
say 'Building project...';
system($xmake_bin) == 0 or die "Build failed";

# Install to a local directory to verify artifacts
my $install_dir = $project_dir->child('dist');
say "Installing to $install_dir...";
system( $xmake_bin, 'install', '-o', $install_dir ) == 0 or die 'Install failed';

# Verify output
my $lib_dir = $install_dir->child('lib');
if ( $lib_dir->exists ) {
    $lib_dir->visit(
        sub {
            my ($path) = @_;
            return unless $path->is_file;
            say ' - Artifact found: ' . $path->relative($install_dir);
        },
        { recurse => 1 }
    );
}
else {
    warn 'No library directory found.';
}

eg/xrepo_demo.pl  view on Meta::CPAN

use Alien::Xmake;
use Path::Tiny;
#
my $xmake = Alien::Xmake->new();

# Locate xrepo: usually sits next to the xmake binary
my $xrepo_bin = $xmake->xrepo;
my $library   = 'zlib';

# Get Information about a package
say "Fetching info for $library...";
system $xrepo_bin, 'info', $library;

# Install the package
say "\nInstalling $library...";
system( $xrepo_bin, 'install', '-y', $library ) == 0 or die "Failed to install $library";

# Fetch integration flags (CFLAGS/LDFLAGS)
# This is useful if you want to use the library in a non-xmake build system (like MakeMaker)
say "\nFetching build flags for $library...";
my $flags = qx[$xrepo_bin fetch --cflags --ldflags $library];
if ($flags) {
    say 'Flags acquired:';
    say $flags;
}
else {
    warn 'Could not fetch flags.';
}

lib/Alien/Xrepo.pm  view on Meta::CPAN

no warnings 'experimental::class';
class Alien::Xrepo 0.08 {
    use Alien::Xmake;
    use JSON::PP;
    use Path::Tiny;
    use Config;
    use Capture::Tiny qw[capture];
    #
    field $verbose : param //= 0;
    field $xmake = Alien::Xmake->new;
    method blah ($msg) { return unless $verbose; say $msg; }
    #
    class Alien::Xrepo::PackageInfo {
        use Path::Tiny;
        field $includedirs : param : reader;
        field $libfiles    : param : reader;
        field $license     : param : reader;
        field $linkdirs    : param : reader;
        field $links       : param : reader;
        field $shared      : param : reader;
        field $static      : param : reader;

lib/Alien/Xrepo.pm  view on Meta::CPAN

                bindirs     => $bindirs
            }
        }
    }
    #
    method install ( $pkg_spec, $version //= (), %opts ) {
        my $full_spec = defined $version && length $version ? "$pkg_spec $version" : $pkg_spec;

        # Build common arguments for both install and fetch
        my @args = $self->_build_args( \%opts );
        say "[*] xrepo: ensuring $full_spec is installed..." if $verbose;

        # Install
        my @install_cmd = ( $xmake->exe, qw[lua private.xrepo], 'install', '-y', @args, $full_spec );
        $self->blah("Running: @install_cmd");
        system(@install_cmd) == 0 or die "xrepo install failed for $full_spec";

        # Fetch (must use same args to get correct paths for arch/mode)
        warn "[*] xrepo: fetching paths...\n" if $verbose;
        my @fetch_cmd = ( $xmake->exe, qw[lua private.xrepo], 'fetch', '--json', @args, $full_spec );
        $self->blah("Running: @fetch_cmd");

lib/Alien/Xrepo.pm  view on Meta::CPAN

        try { $data = decode_json($json_out); } catch ($e) {
            die "Failed to decode xrepo JSON output: $e\nOutput was: $json_out"
        };

        # xrepo might return a single object or a list.
        $self->_process_info( ( ref $data eq 'ARRAY' ) ? $data->[0] : $data );
    }

    method uninstall ( $pkg_spec, %opts ) {
        my @args = $self->_build_args( \%opts );
        say "[*] xrepo: uninstalling $pkg_spec..." if $verbose;
        system $xmake->exe, qw[lua private.xrepo], 'remove', '-y', @args, $pkg_spec;
    }

    method search ($query) {
        say "[*] xrepo: searching for $query..." if $verbose;
        system $xmake->exe, qw[lua private.xrepo], 'search', $query;
    }

    method clean () {
        say '[*] xrepo: cleaning cache...' if $verbose;
        system $xmake->exe, qw[lua private.xrepo], 'clean', '-y';
    }
    #
    method add_repo ( $name, $url, $branch //= () ) {
        say "[*] xrepo: adding repo $name..." if $verbose;
        my @cmd = ( $xmake->exe, qw[lua private.xrepo], 'add-repo', '-y', $name, $url );
        push @cmd, $branch if defined $branch;
        my ( $out, $err, $exit ) = capture { system @cmd };
        die "xrepo add-repo failed:\n$err" if $exit != 0;
        return 1;
    }

    method remove_repo ($name) {
        say "[*] xrepo: removing repo $name..." if $verbose;
        system $xmake->exe, qw[lua private.xrepo], 'remove-repo', '-y', $name;
    }

    method update_repo ( $name //= () ) {
        say '[*] xrepo: updating repositories...' if $verbose;
        my @cmd = ( $xmake->exe, qw[lua private.xrepo], 'update-repo', '-y' );
        push @cmd, $name if defined $name;
        system @cmd;
    }
    #
    method _build_args ($opts) {
        my @args;

        # Standard xmake/xrepo flags
        push @args, '-p', $opts->{plat} if $opts->{plat};                        # platform (iphoneos, android, etc)

lib/Alien/Xrepo.pod  view on Meta::CPAN


    # Automatically wrap zlib as a whole with Affix::Wrap
    use Affix;
    use Affix::Wrap;
    my $zlib = $repo->install('zlib');
    Affix::Wrap->new(
        project_files => [ $zlib->find_header('zlib.h') ],
        include_dirs  => [ $zlib->includedirs ],
        types         => { gzFile_s => Pointer [Void] }
    )->wrap( $zlib->libpath );
    say 'zlib version:   ' . zlibVersion();

    # Wrap a single function from sqlite3 with Affix
    use Affix;
    my $sqlite3 = $repo->install('sqlite3');
    affix $sqlite3->libpath, 'sqlite3_libversion', [], String;
    say 'SQLite version: ' . sqlite3_libversion();

    # Wrap a single function from libpng with FFI::Platypus
    use FFI::Platypus;
    my $lz4 = $repo->install('lz4');
    my $ffi = FFI::Platypus->new;
    $ffi->lib( $lz4->libpath );
    $ffi->attach( 'LZ4_versionString', [] => 'string' );
    say 'LZ4 version:    ' . LZ4_versionString();

=head1 DESCRIPTION

This module acts as an intelligent bridge between Perl and the C<xrepo> package manager.

While L<Affix> or L<FFI::Platypus> or L<Inline::C> can handle the binding or linking to native functions, Alien::Xrepo
handles the B<acquisition> of the libraries containing those functions. It automates the entire dependency lifecycle:

=over



( run in 4.220 seconds using v1.01-cache-2.11-cpan-483215c6ad5 )