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:
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
-
$repo->install('libpng')This command invokes the externalxrepotool. It checks iflibpngis cached. If not, it downloads the source, compiles it as a shared library (critical for FFI), and returns an object containing the paths to the binary and headers. -
$pkg->includedirsLibraries often depend on other libraries (e.g.,libpngneedszlib).xrepohandles this dependency tree. When we pass these directories toAffix::Wrap, we ensure that the Clang or Regex parser can findzlib.hwhen it parsespng.h. -
The Math While we use C for the I/O, we kept the pixel logic in Perl. We use a Linear Projection to calculate where every pixel falls along the angled line defined by
$angle. We then map that position to our array of@stopsand interpolate the RGB values. This demonstrates that Perl is more than capable of handling complex logic, while Affix handles the heavy lifting of interfacing with system libraries.
Why use Alien::Xrepo?
- Cross-Platform:
xmakeworks on Windows, Linux, and macOS. - Huge Repository: It supports thousands of libraries via
conan,vcpkg,brew, and its own repository. - Compilation: Unlike system package managers (apt/yum) which often only provide static
.afiles for development,xrepocan be forced to build.dll/.sofiles, 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.