Alien::...nAuthor(3)
okay bestie, welcome to Alien::Build::Manual::PluginAuthor(3) โ it's filed under User Contributed Perl Documentation, and it's the glow-up guide for anyone who wants to write their own Alien::Build plugins. Buckle up, it's a long one ๐
โจ NAME โจ
Alien::Build::Manual::PluginAuthor โ the Alien::Build plugin author documentation, aka your new hyperfixation ๐
๐ธ VERSION ๐ธ
version 2.88 โ yes queen, we're on 2.88 ๐
๐ฆ SYNOPSIS ๐ฆ
So this is what your plugin looks like, and no I will not be normal about it:
package Alien::Build::Plugin::Build::MyPlugin;
use strict;
use warnings;
use Alien::Build::Plugin;
has arg1 => 'default_for arg1';
has arg2 => sub { [ 'default', 'for', 'arg2' ] };
sub init
{
my($self, $meta) = @_;
...
}
1;
and then you call it from your alienfile like this:
use alienfile;
plugin 'Build::MyPlugin' => (
arg1 => 'override for arg1',
arg2 => [ 'something', 'else' ],
);
Here's the Perlish pseudo code for how plugins get called, lowkey the whole plot of the movie:
my $probe;
my $override = override();
if($override eq 'system') {
$probe = probe();
if($probe ne 'system') {
die 'system tool or library not found';
}
}
elsif($override eq 'default') {
$probe = probe();
} else { # $override eq 'share'
# note that in this instance the
# probe hook is never called
$probe = 'share';
}
if($probe eq 'system') {
gather_system();
} else { # $probe eq 'share'
download();
extract();
patch();
build();
gather_share();
# Check to see if there isa build_ffi hook
if(defined &build_ffi) {
patch_ffi();
build_ffi();
gather_ffi();
}
}
# By default this just returns the value of $ENV{ALIEN_INSTALL_TYPE}
sub override {
return $ENV{ALIEN_INSTALL_TYPE};
}
# Default download implementation; can be
# replaced by specifying a different download
# hook. See Alien::Build::Plugin::Core::Download
# for detailed implementation.
sub download {
my $response = fetch();
if($response->{type} eq 'html' || $response->{type} eq 'dir_listing') {
# decode will transform an HTML listing (html) or a FTP directory
# listing (dir_listing) into a regular list
$response = decode($response);
}
if($response->{type} eq 'list') {
# prefer will filter bad entries in the list
# and sort them so that the first one is
# the one that we want
$response = prefer($response);
my $first_preferred = $res->{list}->[0];
# prefer can sometimes infer the version from the
# filename.
if(defined $first_preferred->{version}) {
# not a hook
runtime_prop->{version} = $first_preferred->{version};
}
$response = fetch($first_preferred);
}
if($response->{type} eq 'file') {
# not a hook
write_file_to_disk $response;
}
}
๐ DESCRIPTION ๐
So this whole doc is here to teach you how to write Alien::Build plugins using the Alien::Build::Plugin base class. It's giving "tutorial but make it a whole vibe."
๐ Writing plugins ๐
Your plugins use Alien::Build::Plugin, which sorts out the right base class for you and hands you the has property builder. has takes two things: the name of the property, and the default value. (Just like Moose and Moo, if your default isn't a plain string you gotta pass a code reference instead.) And bestie, listen โ do NOT set this as your plugin's base class directly:
use parent qw( Alien::Build::Plugin ); # wrong
use Alien::Build::Plugin; # right
The only method you actually have to implement is init. From inside it you can add hooks that change how the alienfile recipe behaves. Here's a super simple probe hook example, with the actual probe logic removed (iykyk):
sub init
{
my($self, $meta) = @_;
$meta->register_hook(
probe => sub {
my($build) = @_;
if( ... )
{
return 'system';
}
else
{
return 'share';
}
},
);
}
Hooks get the Alien::Build instance as their first argument, and depending on which hook it is, they might get extra arguments too. No cap. ๐ซถ
๐ Modifying hooks ๐
You can also modify hooks using before_hook, around_hook and after_hook โ very Moose-modifier coded of them:
sub init
{
my($self, $meta) = @_;
$meta->before_hook(
build => sub {
my($build) = @_;
$build->log('this runs before the build');
},
);
$meta->after_hook(
build => sub {
my($build) = @_;
$build->log('this runs after the build');
},
);
$meta->around_hook(
build => sub {
my $orig = shift;
# around hooks are useful for setting environment variables
local $ENV{CPPFLAGS} = '-I/foo/include';
$orig->(@_);
},
);
}
โญ Testing plugins โญ
You can โ and honestly you should โ write tests for your plugin. The best way to do it is with Test::Alien::Build, which lets you write an inline alienfile right inside your test. Iconic behaviour. Here's an example:
use Test::V0;
use Test::Alien::Build;
my $build = alienfile_ok q{
use alienfile;
plugin 'Build::MyPlugin' => (
arg1 => 'override for arg1',
arg2 => [ 'something', 'else' ],
);
...
};
# you can interrogate $build, it is an instance of L<Alien::Build>.
my $alien = alien_build_ok;
# you can interrogate $alien, it is an instance of L<Alien::Base>.
๐ฆ Negotiator plugins ๐ฆ
A Negotiator plugin doesn't usually do anything on its own โ its whole thing is picking the best plugin for a particular goal. Main character energy: it shows up, makes the choice, dips. ๐
And "best" can change depending on the platform or what tools are already installed. For example, the download negotiator might pick the fetch plugin that leans on the command line curl, or it might pick the fetch plugin that leans on the Perl module HTTP::Tiny, depending on the platform and what's already there. (And for either of those to actually be useful, they have to support SSL.)
By convention, Negotiator plugins are named something like Alien::Build::Plugin::*::Negotiate, but you usually invoke them without the ::Negotiate suffix. Like:
plugin 'Download'; # is short for Alien::Build::Plugin::Download::Negotiator
Here's a lil example of a negotiator that picks curl if it's already installed, and HTTP::Tiny otherwise. (The real download plugin is way smarter and more complicated than this โ this is just the simplified, cute version.)
package Alien::Build::Plugin::Download::Negotiate;
use strict;
use warnings;
use Alien::Build::Plugin;
use File::Which qw( which );
sub init
{
my($self, $meta) = @_;
if(which('curl')) {
$meta->apply_plugin('Fetch::Curl');
} else {
$meta->apply_plugin('Fetch::HTTPTiny');
}
}
๐ Hooks ๐
The rest of this doc is a whole reference of the hooks you can register. Generally you can register any hook you want, but pay attention โ some hooks have default behaviour that gets overridden the moment you register your own. The hooks are listed in alphabetical order, and the execution order is shown in the flowchart above (if you're reading the HTML version of this doc), or in the Perlish pseudo code up in the synopsis section. Go look, it's there. ๐
๐ HOOKS ๐
๐ build hook ๐
$meta->register_hook( build => sub {
my($build) = @_;
...
});
This one does the main build of the alienized project and installs it into the staging area. The current directory is the build root. You need to run whatever tools the project needs, and install them into "$build->install_prop->{prefix}" (%{.install.prefix}). That's the assignment, bestie, no skipping. ๐ฅ
๐ build_ffi hook ๐
$meta->register_hook( build_ffi => sub {
my($build) = @_;
...
});
Same as build, except it only fires on a FFI build. That's it, that's the difference. โจ
๐ decode hook ๐
$meta->register_hook( decode => sub {
my($build, $res) = @_;
...
}
This hook takes a response hash reference from the fetch hook above with a type of html or dir_listing, and converts it into a response hash reference of type list. In short: it takes an HTML or FTP file listing response from a fetch hook and turns it into a list of filenames and links that the prefer hook can use to choose the right file to download. If you wanna know the exact spec of the input and response hash references, go check the fetch hook. ๐
๐ check_digest hook ๐
# implement the well known FOO-92 digest
$meta->register_hook( check_digest => sub {
my($build, $file, $algorithm, $digest) = @_;
if($algorithm ne 'FOO92') {
return 0;
}
my $actual = foo92_hex_digest($file);
if($actual eq $digest) {
return 1;
} else {
die "Digest FOO92 does not match: got $actual, expected $digest";
}
});
This hook should check that the given $file (same format as the fetch hook uses) matches the given $digest using the given $algorithm. If your plugin doesn't support that algorithm, return a false value โ no drama. If the digest doesn't match, throw an exception (yes, actually throw it). If the digest matches, return a true value. Simple as. ๐
๐ clean_install ๐
$meta->register_hook( clean_install => sub {
my($build) = @_;
});
This hook lets you remove files from the final install location before the files get installed by the installer layer (examples: Alien::Build::MM, Alien::Build::MB or App::af). Heads up: this hook is not called by default โ you have to enable it through the interface to the installer layer (example: "clean_install" in Alien::Build::MM).
This hook SHOULD NOT remove the _alien directory or its content from the install location. Do not do it. I'm watching. ๐
The default implementation removes all the files EXCEPT the _alien directory and its content.
๐ download hook ๐
$meta->register_hook( download => sub {
my($build) = @_;
...
});
This hook is how you download the source from the internet. Either as an archive (like tar, zip, etc), or as a directory of files (git clone, etc). When the hook gets called, the current working directory will be a brand new empty directory, so you can just save the download right there. If you store a single file in the directory, Alien::Build will assume it's an archive, and the extract hook will process it. If you store multiple files, Alien::Build will assume the current directory is the source root. And if you store no files at all? An exception with an appropriate diagnostic gets thrown. F in the chat. ๐ญ
Note: if you register this hook, then the fetch, decode and prefer hooks will NOT be called โ unless you call them yourself from inside this hook. Your move. ๐
๐ extract hook ๐
$meta->register_hook( extract => sub {
my($build, $archive) = @_;
...
});
Okay so this hook is the one that unpacks an archive you've already downloaded. ๐ Alien::Build already ships plugins for basically every common archive format out there, so ngl you'll probably only ever need this one if you're adding support for something new or genuinely novel. When this hook fires, your current working directory is a fresh empty directory, so you can just write the archive's content straight into it. If you write a single directory into the cwd, Alien::Build assumes that's the root directory of the package. If there are multiple files and/or directories sitting there, that means the cwd <em>itself</em> is the root of the package. The logic usually handles the default behavior correctly for tar (where packages typically extract into a subdirectory) and for zip (where packages typically extract into the current directory). ๐ฆ
fetch hook
package Alien::Build::Plugin::MyPlugin;
use strict;
use warnings;
use Alien::Build::Plugin;
use Carp ();
has '+url' => sub { Carp::croak "url is required property" };
sub init
{
my($self, $meta) = @_;
$meta->register_hook( fetch => sub {
my($build, $url, %options) = @_;
...
}
}
1;
This is the hook that goes and gets a resource. ๐ The first time it's called, it comes with no argument (or with $url set to undef), so the configuration used to find the resource has to come from the plugin's properties. On every call after that, the first argument is a URL.
The %options hash can contain these options:
http_headersโ HTTP request headers, if an appropriate protocol is being used. These get passed as an array reference of key/value pairs, which is what lets you have duplicate header keys with multiple values. ๐ If a non-HTTP protocol is used, or the plugin just can't send HTTP request headers, the plugin SHOULD issue a warning using the$build->logmethod โ but because this option wasn't part of the original spec, the plugin MAY skip that warning while ignoring it. ๐คทโโ๏ธ
Heads up bestie: versions of Alien::Build before 2.39 did not pass the options hash into the fetch plugin. ๐ญ
Normally the first fetch is going to be either a file or a directory listing. If it's a file, the content should come back as a hash reference with these keys:
# content of file stored in Perl
return {
type => 'file',
filename => $filename,
content => $content,
version => $version, # optional, if known
protocol => $protocol, # AB 2.60 optional, but recommended
};
# content of file stored in the filesystem
return {
type => 'file',
filename => $filename,
path => $path, # full file system path to file
version => $version, # optional, if known
tmp => $tmp, # optional
protocol => $protocol, # AB 2.60 optional, but recommended
};
$tmp, if you set it, says whether the file is temporary or not โ Alien::Build can use that to save a copy in some cases. The default is true, so if you don't say otherwise Alien::Build assumes the file or directory is temporary. The most common situation where you'd set tmp to false is when the file is bundled inside the Alien distribution. Peep Alien::Build::Plugin::Fetch::Local for an example. ๐
If the URL points at a directory listing, you should return it as a hash reference holding a list of files:
return {
type => 'list',
list => [
# filename: each filename should be just the
# filename portion, no path or url.
# url: each url should be the complete url
# needed to fetch the file.
# version: OPTIONAL, may be provided by some fetch or prefer
{ filename => $filename1, url => $url1, version => $version1 },
{ filename => $filename2, url => $url2, version => $version2 },
],
protocol => $protocol, # AB 2.60 optional, but recommended
};
or, if the listing is in HTML format, as a hash reference holding the HTML info:
return {
type => 'html',
charset => $charset, # optional
base => $base, # the base URL: used for computing relative URLs
content => $content, # the HTML content
protocol => $protocol, # optional, but recommended
};
or a directory listing (usually something an FTP server produces) as a hash reference:
return {
type => 'dir_listing',
base => $base,
content => $content,
protocol => $protocol, # AB 2.60 optional, but recommended
};
[version 2.60]
For all of these responses $protocol is optional, since it wasn't in the original spec โ but fr fr, it is strongly recommended that you include this field, because future versions of Alien::Build are going to use it to figure out whether a file was downloaded securely (that is, via a secure protocol like SSL). ๐
Some plugins (like decode plugins) translate a file hash from one type to another โ those ones should carry $protocol over from the old representation of the file to the new one. ๐ซ
gather_ffi hook
$meta->register_hook( gather_ffi => sub {
my($build) = @_;
$build->runtime_prop->{cflags} = ...;
$build->runtime_prop->{libs} = ...;
$build->runtime_prop->{version} = ...;
});
This hook gets called for an FFI build to work out the properties you need for using the library or tool. Those properties go in the runtime_prop hash, exactly like above. The typical ones you need for libraries are cflags and libs. And if there's any possible way, you should also try to figure out the version of the library or tool. ๐
gather_share hook
$meta->register_hook( gather_share => sub {
my($build) = @_;
$build->runtime_prop->{cflags} = ...;
$build->runtime_prop->{libs} = ...;
$build->runtime_prop->{version} = ...;
});
This hook gets called for a share install to work out the properties you need for using the library or tool. Those properties go in the runtime_prop hash, exactly like above. The typical ones you need for libraries are cflags and libs. And if there's any possible way, you should also try to figure out the version of the library or tool. ๐
gather_system hook
$meta->register_hook( gather_system => sub {
my($build) = @_;
$build->runtime_prop->{cflags} = ...;
$build->runtime_prop->{libs} = ...;
$build->runtime_prop->{version} = ...;
});
This hook gets called for a system install to work out the properties you need for using the library or tool. Those properties go in the runtime_prop hash, exactly like above. The typical ones you need for libraries are cflags and libs. And if there's any possible way, you should also try to figure out the version of the library or tool. ๐ธ
override hook
$meta->register_hook( override => sub {
my($build) = @_;
return $ENV{ALIEN_INSTALL_TYPE} || '';
});
This one lets you mess with the override logic. It should return one of "share", "system", "default" or ''. The default implementation is the one shown above. Alien::Build::Plugin::Probe::Override and Alien::Build::Plugin::Probe::OverrideCI are both examples of how you can use this hook. โจ
patch hook
$meta->register_hook( patch => sub {
my($build) = @_;
...
});
This hook is completely optional. If you register it, it fires after extraction and before build. It's your moment to apply any patches or make any modifications to the source if they're needed. ๐ฉน
patch_ffi hook
$meta->register_hook( patch_ffi => sub {
my($build) = @_;
...
});
This hook is exactly like the patch hook, except it only fires on an FFI build. ๐
prefer hook
$meta->register_hook( prefer => sub {
my($build, $res) = @_;
return {
type => 'list',
list => [sort @{ $res->{list} }],
};
}
This hook sorts candidates from a listing generated by either the "fetch" or "decode" hooks. It should return a new list hash reference with the candidates sorted best to worst. It's also allowed to straight-up remove candidates that are totally unacceptable. ๐ฎ
probe hook
$meta->register_hook( probe => sub {
my($build) = @_;
return 'system' if ...; # system install
return 'share'; # otherwise
});
$meta->register_hook( probe => [ $command ] );
This hook should return the string "system" if the operating system provides the library or tool. Otherwise it should return "share". ๐
You can also hand it a command that returns true when the tool or library is available. Like, for use with "pkg-config":
$meta->register_hook( probe =>
[ '%{pkgconf} --exists libfoo' ] );
Or if you needed a minimum version:
$meta->register_hook( probe =>
[ '%{pkgconf} --atleast-version=1.00 libfoo' ] );
Okay real talk: this hook SHOULD NOT gather system properties like cflags, libs, versions, etc, because the probe hook gets skipped in the event the environment variableALIEN_INSTALL_TYPEis set. The detection of those properties should be done by thegather_systemhook instead. ๐ค
You can give it multiple probe hooks, too. They get used in sequence, stopping at the first one that detects a system installation. ๐ฅ
๐ SEE ALSO ๐
- Alien::Build::Manual โ the other Alien::Build manuals. ๐
๐ AUTHOR ๐
Author: Graham Ollis <plicease@cpan.org>
Contributors:
- Diab Jerius (DJERIUS)
- Roy Storey (KIWIROY)
- Ilya Pavlov
- David Mertens (run4flat)
- Mark Nunberg (mordy, mnunberg)
- Christian Walde (Mithaldu)
- Brian Wightman (MidLifeXis)
- Zaki Mughal (zmughal)
- mohawk (mohawk2, ETJ)
- Vikas N Kumar (vikasnkumar)
- Flavio Poletti (polettix)
- Salvador Fandiรฑo (salva)
- Gianni Ceccarelli (dakkar)
- Pavel Shaydo (zwon, trinitum)
- Kang-min Liu (ๅๅบทๆฐ, gugod)
- Nicholas Shipp (nshp)
- Juan Juliรกn Merelo Guervรณs (JJ)
- Joel Berger (JBERGER)
- Petr Pรญsaล (ppisar)
- Lance Wicks (LANCEW)
- Ahmad Fatoum (a3f, ATHREEF)
- Josรฉ Joaquรญn Atria (JJATRIA)
- Duke Leto (LETO)
- Shoichi Kaji (SKAJI)
- Shawn Laffan (SLAFFAN)
- Paul Evans (leonerd, PEVANS)
- Hรฅkon Hรฆgland (hakonhagland, HAKONH)
- nick nauwelaerts (INPHOBIA)
- Florian Weimer
- Marcel Telka (mtelka)
๐ COPYRIGHT AND LICENSE ๐
This software is copyright (c) 2011-2022 by Graham Ollis.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
And that's the whole page, bestie!! ๐๐ This one came from perl v5.42.3, dated 2026-09-21 โ so if you're reading this in the future, hey from the past, hope your cflags are all set. ๐ฆโจ Periodt. ๐
๐ฆ where this page comes from
- fedora 43: perl-Alien-Build 2.88-1.fc43 ยท GPL-1.0-or-later OR Artistic-1.0-Perl
- fedora 44: perl-Alien-Build 2.88-1.fc44 ยท GPL-1.0-or-later OR Artistic-1.0-Perl
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms