In previous chapters, we compiled code directly or used Alien::Base to find system libraries. But sometimes you want a library that isn't installed on the system, isn't on CPAN, but is available in the wider C/C++ ecosystem.

This is where xmake and its package manager, xrepo, shine.

xrepo is a modern, cross-platform C/C++ package manager. It can download, compile, and install thousands of libraries (like libpng, zlib, pcre, ffmpeg) with a single command.

We have created a helper module, Alien::Xrepo, to bridge the gap. It asks xrepo to install a library, finds the resulting shared object (.dll / .so), and hands it to Affix::Wrap for binding.

The Recipe

We will install libpng using Alien::Xrepo and use it to generate a gradient image file. We'll set up the script so you can easily define the size and color range. The current config produces a PNG that looks like this:

test_affix

use v5.40;
use Affix::Wrap;
use Alien::Xrepo;
use List::Util qw[min max];
use constant PI => 3.14159265359;

# Configuration
my $width  = 400;
my $height = 400;
my $angle  = 135;    # Degrees (0 = Horizontal, 90 = Vertical)

# Define gradient stops (Red -> Yellow -> Green -> Blue)
my @stops = (
    [ 255, 0,   0 ],     # Red
    [ 255, 255, 0 ],     # Yellow
    [ 0,   255, 0 ],     # Green
    [ 0,   0,   255 ]    # Blue
);

# 1. Initialize the Repo Helper
my $repo = Alien::Xrepo->new( verbose => 1 );

# 2. Install Library
# This runs `xrepo install -k shared libpng`
# It downloads source, compiles a DLL/SO, and returns the paths.
my $pkg = $repo->install('libpng');
say 'Found libpng version ' . $pkg->version . ' at ' . $pkg->libpath;

# 3. Locate Header
# We need the full path to png.h to parse function signatures.
my $header = $pkg->find_header('png.h');

# 4. Wrap
# We must pass include_dirs so Affix::Wrap's parser can find zlib (which libpng depends on).
my $wrapper = Affix::Wrap->new( project_files => [$header], include_dirs => $pkg->includedirs );

# We define a helper type that png.h relies on but doesn't define clearly for FFI
$wrapper->wrap( $pkg->libpath );

# 5. Use the Library
# We will use the high-level API of libpng to write a file.
# A. Generate Data (Math!)
my $buffer = '';

# Convert angle to vector
my $rad = $angle * PI / 180;
my $dx  = cos($rad);
my $dy  = sin($rad);

# Calculate projection bounds to normalize the gradient
# We project the 4 corners of the image onto the gradient vector
my @corners = ( 0 * $dx + 0 * $dy, $width * $dx + 0 * $dy, 0 * $dx + $height * $dy, $width * $dx + $height * $dy );
my $min_p   = min @corners;
my $max_p   = max @corners;
my $dist    = $max_p - $min_p || 1;         # Avoid division by zero
for my $y ( 0 .. $height - 1 ) {
    for my $x ( 0 .. $width - 1 ) {
        my $p = $x * $dx + $y * $dy;        # Project pixel onto gradient line
        my $t = ( $p - $min_p ) / $dist;    # Normalize to 0.0 - 1.0 range

        # Find which two colors we are blending between
        # Map t (0..1) to index (0 .. #stops-1)
        my $pos     = $t * $#stops;
        my $idx     = int $pos;       # Lower color index
        my $local_t = $pos - $idx;    # Blend factor between lower and upper

        # Clamp for safety at the very edge
        if ( $idx >= $#stops ) { $idx = $#stops - 1; $local_t = 1.0; }
        my $c1 = $stops[$idx];
        my $c2 = $stops[ $idx + 1 ];

        # Lerp
        my $r = int( $c1->[0] + ( $c2->[0] - $c1->[0] ) * $local_t );
        my $g = int( $c1->[1] + ( $c2->[1] - $c1->[1] ) * $local_t );
        my $b = int( $c1->[2] + ( $c2->[2] - $c1->[2] ) * $local_t );
        $buffer .= pack( 'CCC', $r, $g, $b );
    }
}

# B. Setup Struct
# Affix lets us pass a Perl HashRef for the C struct 'png_image'.
my $image = {
    version => PNG_IMAGE_VERSION(),    # Macro wrapped by Affix
    width   => $width,
    height  => $height,
    format  => PNG_FORMAT_RGB(),       # Macro wrapped by Affix
    flags   => 0,
    opaque  => undef
};
say 'Writing to test_affix.png...';

# C. Call Function
# int png_image_write_to_file(png_imagep image, const char *file, ...);
my $result = png_image_write_to_file(
    $image, 'test_affix.png', 0,    # convert_to_8bit (auto)
    $buffer,                        # Raw pixel data
    0,                              # row_stride (auto)
    undef                           # colormap
);
#
say $result ? 'SUCCESS: Image written.' : 'FAILURE: ' . $image->{message};

How It Works

Why use Alien::Xrepo?

  1. Cross-Platform: xmake works on Windows, Linux, and macOS.
  2. Huge Repository: It supports thousands of libraries via conan, vcpkg, brew, and its own repository.
  3. Compilation: Unlike system package managers (apt/yum) which often only provide static .a files for development, xrepo can be forced to build .dll / .so files, which is exactly what dynamic FFI needs.

This approach gives you the power of CPAN's Alien:: namespace but with access to the entire C/C++ open source ecosystem instantly.