Manpages - Alien::Build::Manual::AlienAuthor.3pm
Table of Contents
NAME
Alien::Build::Manual::AlienAuthor - Alien author documentation
VERSION
version 2.44
SYNOPSIS
perldoc Alien::Build::Manual::AlienAuthor
DESCRIPTION
Note: Please read the entire document before you get started in writing your own alienfile. The section on dynamic vs. static libraries will likely save you a lot of grief if you read it now!
This document is intended to teach Alien authors how to build their own Alien distribution using Alien::Build and Alien::Base. Such an Alien distribution consists of three essential parts:
- An alienfile
- This is a recipe for how to 1) detect an already installed version of the library or tool you are alienizing 2) download and build the library or tool that you are alienizing and 3) gather the configuration settings necessary for the use of that library or tool.
- (no term)
- An installer “Makefile.PL” or “Build.PL” or a “dist.ini” if you are using Dist::Zilla :: This is a thin layer between your alienfile recipe, and the Perl installer (either ExtUtils::MakeMaker or Module::Build.
- A Perl class (.pm file) that inherits from Alien::Base
- For most Aliens this does not need to be customized at all, since Alien::Base usually does what you need.
For example if you were alienizing a library called libfoo, you might have these files:
Alien-Libfoo-1.00/Makefile.PL Alien-Libfoo-1.00/alienfile Alien-Libfoo-1.00/lib/Alien/Libfoo.pm
This document will focus mainly on instructing you how to construct an
alienfile, but we will also briefly cover making a simple Makefile.PL
or dist.ini
to go along with it. We will also touch on when you might
want to extend your subclass to add non-standard functionality.
Using commands
Most software libraries and tools will come with instructions for how to
install them in the form of commands that you are intended to type into
a shell manually. The easiest way to automate those instructions is to
just put the commands in your alienfile. For example, lets suppose that
libfoo is built using autoconf and provides a pkg-config
.pc
file.
We will also later discuss plugins. For common build systems like autoconf or CMake, it is usually better to use the appropriate plugin because they will handle corner cases better than a simple set of commands. We’re going to take a look at commands first because it’s easier to understand the different phases with commands.
(Aside, autoconf is a series of tools and macros used to configure (usually) a C or C++ library or tool by generating any number of Makefiles. It is the C equivalent to ExtUtils::MakeMaker, if you will. Basically, if your library or tool instructions start with ’./configure’ it is most likely an autoconf based library or tool).
(Aside2, pkg-config
is a standard-ish way to provide the compiler and
linker flags needed for compiling and linking against the library. If
your tool installs a .pc
file, usually in $PREFIX/lib/pkgconfig
then, your tool uses pkg-config
).
Here is the alienfile that you might have:
use alienfile; probe [ pkg-config –exists libfoo ]; share { start_url http://www.libfoo.org/src/libfoo-1.00.tar.gz; download [ wget %{.meta.start_url} ]; extract [ tar zxf %{.install.download} ]; build [ [ ./configure –prefix=%{.install.prefix} –disable-shared ], [ %{make} ], [ %{make} install ], ]; }; gather [ [ pkg-config –modversion libfoo, \%{.runtime.version} ], [ pkg-config –cflags libfoo, \%{.runtime.cflags} ], [ pkg-config –libs libfoo, \%{.runtime.libs} ], ];
There is a lot going on here, so lets decode it a little bit. An alienfile is just some Perl with some alien specific sugar. The first line
use alienfile;
imports the sugar into the alienfile. It also is a flag for the reader to see that this is an alienfile and not some other kind of Perl script.
The second line is the probe directive:
probe [ pkg-config –exists libfoo ];
is used to see if the library is already installed on the target system.
If pkg-config
is in the path, and if libfoo is installed, this should
exit with a success (0) and tell Alien::Build to use the system library.
If either pkg-config
in the PATH, or if libfoo is not installed, then
it will exist with non-success (!= 0) and tells Alien::Build to download
and build from source.
You can provide as many probe directives as you want. This is useful if
there are different ways to probe for the system. Alien::Build will stop
on the first successfully found system library found. Say our library
libfoo comes with a .pc
file for use with pkg-config
and also
provides a foo-config
program to find the same values. You could then
specify this in your alienfile
probe [ pkg-config –exists libfoo ]; probe [ foo-config –version ];
Other directives can be specified multiple times if there are different methods that can be tried for the various steps.
Sometimes it is easier to probe for a library from Perl rather than with
a command. For that you can use a code reference. For example, another
way to call pkg-config
would be from Perl:
probe sub { my($build) = @_; # $build is the Alien::Build instance. system pkg-config –exists libfoo; $? == 0 ? system : share; };
The Perl code should return ’system’ if the library is installed, and
’share’ if not. (Other directives should return a true value on success,
and a false value). You can also throw an exception with die
to
indicate a failure.
The next part of the alienfile is the share
block, which is used to
group the directives which are used to download and install the library
or tool in the event that it is not already installed.
share { start_url http://www.libfoo.org/src/libfoo-1.00.tar.gz; download [ wget %{.meta.start_url} ]; extract [ tar zxf %{.install.download} ]; build [ [ ./configure –prefix=%{.install.prefix} –disable-shared ], [ %{make} ], [ %{make} install ], ]; };
The start_url specifies where to find the package that you are
alienizing. It should be either a tarball (or zip file, or what have
you) or an HTML index. The download directive as you might imagine
specifies how to download the library or tool. The extract directive
specifies how to extract the archive once it is downloaded. In the
extract step, you can use the variable %{.install.download}
as a
placeholder for the archive that was downloaded in the download step.
This is also accessible if you use a code reference from the
Alien::Build instance:
share { … requires Archive::Extract; extract sub { my($build) = @_; my $tarball = $build->install_prop->{download}; my $ae = Archive::Extract->new( archive => $tarball ); $ae->extract; 1; } … };
The build directive specifies how to build the library or tool once it
has been downloaded and extracted. Note the special variable
%{.install.prefix}
is the location where the library should be
installed. %{make}
is a helper which will be replaced by the
appropriate make
, which may be called something different on some
platforms (on Windows for example, it frequently may be called nmake
or dmake
).
The final part of the alienfile has a gather directive which specifies
how to get the details on how to compile and link against the library.
For this, once again we use the pkg-config
command:
gather [ [ pkg-config –modversion libfoo, \%{.runtime.version} ], [ pkg-config –cflags libfoo, \%{.runtime.cflags} ], [ pkg-config –libs libfoo, \%{.runtime.libs} ], ];
The scalar reference as the final item in the command list tells
Alien::Build that the output from the command should be stored in the
given variable. The runtime variables are the ones that will be
available to Alien::Libfoo
once it is installed. (Install properties,
which are the ones that we have seen up till now are thrown away once
the Alien distribution is installed.
You can also provide a sys
block for directives that should be used
when a system install is detected. Normally you only need to do this if
the gather step is different between share and system installs. For
example, the above is equivalent to:
build { … gather [ [ pkg-config –modversion libfoo, \%{.runtime.version} ], [ pkg-config –cflags libfoo, \%{.runtime.cflags} ], [ pkg-config –libs libfoo, \%{.runtime.libs} ], ]; }; sys { gather [ [ pkg-config –modversion libfoo, \%{.runtime.version} ], [ pkg-config –cflags libfoo, \%{.runtime.cflags} ], [ pkg-config –libs libfoo, \%{.runtime.libs} ], ]; };
(Aside3, the reason it is called sys
and not system
is so that it
does not conflict with the built in system
function)!
Using plugins
The first example is a good way of showing the full manual path that you
can choose, but there is a lot of repetition, if you are doing many
Aliens that use autoconf and pkg-config
(which are quite common.
alienfile allows you to use plugins. See Alien::Build::Plugin for a list
of some of the plugin categories.
For now, I will just show you how to write the alienfile for libfoo above using Alien::Build::Plugin::Build::Autoconf, Alien::Build::Plugin::PkgConfig::Negotiate, Alien::Build::Plugin::Download::Negotiate, and Alien::Build::Plugin::Extract::Negotiate
use alienfile; plugin PkgConfig => ( pkg_name => libfoo, ); share { start_url http://www.libfoo.org/src; plugin Download => ( filter => qr/^libfoo-[0-9\.]+\.tar\.gz$/, version => qr/^libfoo-([0-9\.]+)\.tar\.gz$/, ); plugin Extract => tar.gz; plugin Build::Autoconf; build [ %{configure} –disable-shared, %{make}, %{make} install, ]; };
The first plugin that we use is the pkg-config
negotiation plugin. A
negotiation plugin is one which doesn’t do the actual work but selects
the best one from a set of plugins depending on your platform and
environment. (In the case of Alien::Build::Plugin::PkgConfig::Negotiate,
it may choose to use command line tools, a pure Perl implementation
(PkgConfig), or libpkgconf, depending on what is available). When using
negotiation plugins you may omit the ::Negotiate
suffix. So as you can
see using the plugin here is an advantage because it is more reliable
than just specifying a command which may not be installed!
Next we use the download negotiation plugin. This is also better than
the version above, because again, wget
my not be installed on the
target system. Also you can specify a URL which will be scanned for
links, and use the most recent version.
We use the Extract negotiation plugin to use either command line tools, or Perl libraries to extract from the archive once it is downloaded.
Finally we use the Autoconf plugin (Alien::Build::Plugin::Build::Autoconf). This is a lot more sophisticated and reliable than in the previous example, for a number of reasons. This version will even work on Windows assuming the library or tool you are alienizing supports that platform!
Strictly speaking the build directive is not necessary, because the autoconf plugin provides a default which is reasonable. The only reason that you would want to include it is if you need to provide additional flags to the configure step.
share { … build [ %{configure} –enable-bar –enable-baz –disable-shared, %{make}, %{make} install, ]; };
Multiple .pc files
Some packages come with multiple libraries paired with multiple .pc
files. In this case you want to provide the
Alien::Build::Plugin::PkgConfig::Negotiate with an array reference of
package names.
plugin PkgConfig => ( pkg_name => [ foo, bar, baz ], );
All packages must be found in order for the system
install to succeed.
Once installed the first pkg_name
will be used by default (in this
example foo
), and you can retrieve any other pkg_name
using the
Alien::Base alt method.
A note about dynamic vs. static libraries
If you are using your Alien to build an XS module, it is important that you use static libraries if possible. If you have a package that refuses to build a static library, then you can use Alien::Role::Dino.
Actually let me back up a minute. For a share
install it is best to
use static libraries to build your XS extension. This is because if your
Alien is ever upgraded to a new version it can break your existing XS
modules. For a system
install shared libraries are usually best
because you can often get security patches without having to re-build
anything in perl land.
If you looked closely at the Using commands and Using plugins sections
above, you may notice that we went out of our way where possible to tell
Autotools to build only static libraries using the --disable-shared
command. The Autoconf plugin also does this by default.
Sometimes though you will have a package that builds both, or maybe you want both static and dynamic libraries to work with XS and FFI. For that case, there is the Alien::Build::Plugin::Gather::IsolateDynamic plugin.
use alienfile; … plugin Gather::IsolateDynamic;
What it does, is that it moves the dynamic libraries (usually .so on Unix and .DLL on Windows) to a place where they can be found by FFI, and where they won’t be used by the compiler for building XS. It usually doesn’t do any harm to include this plugin, so if you are just starting out you might want to add it anyway. Arguably it should have been the default behavior from the beginning.
If you have already published an Alien that does not isolate its dynamic
libraries, then you might get some fails from old upgraded aliens
because the share directory isn’t cleaned up by default (this is perhaps
a design bug in the way that share directories work, but it is a long
standing characteristic). One work around for this is to use the
clean_install
property on Alien::Build::MM, which will clean out the
share directory on upgrade, and possibly save you a lot of grief.
Verifying and debugging your alienfile
You could feed your alienfile directly into Alien::Build, or
Alien::Build::MM, but it is sometimes useful to test your alienfile
using the af
command (it does not come with Alien::Build, you need to
install App::af). By default af
will use the alienfile
in the
current directory (just as make
uses the Makefile
in the current
directory; just like make
you can use the -f
option to specify a
different alienfile).
You can test your alienfile in dry run mode:
% af install –dry-run Alien::Build::Plugin::Core::Legacy> adding legacy hash to config Alien::Build::Plugin::Core::Gather> mkdir -p /tmp/I2YXRyxb0r/_alien — cflags: cflags_static: install_type: system legacy: finished_installing: 1 install_type: system name: libfoo original_prefix: /tmp/7RtAusykNN version: 1.2.3 libs: -lfoo libs_static: -lfoo prefix: /tmp/7RtAusykNN version: 1.2.3
You can use the --type
option to force a share install (download and
build from source):
% af install –type=share –dry-run Alien::Build::Plugin::Core::Download> decoding html Alien::Build::Plugin::Core::Download> candidate *https://www.libfoo.org/download/libfoo-1.2.4.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.3.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.2.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.1.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.0.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.1.9.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.1.8.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.1.7.tar.gz Alien::Build::Plugin::Core::Download> candidate … Alien::Build::Plugin::Core::Download> setting version based on archive to 1.2.4 Alien::Build::Plugin::Core::Download> downloaded libfoo-1.2.4.tar.gz Alien::Build::CommandSequence> + ./configure –prefix=/tmp/P22WEXj80r –with-pic –disable-shared … snip … Alien::Build::Plugin::Core::Gather> mkdir -p /tmp/WsoLAQ889w/_alien — cflags: cflags_static: install_type: share legacy: finished_installing: 1 install_type: share original_prefix: /tmp/P22WEXj80r version: 1.2.4 libs: -L/tmp/P22WEXj80r/lib -lfoo libs_static: -L/tmp/P22WEXj80r/lib -lfoo prefix: /tmp/P22WEXj80r version: 1.2.4
You can also use the --before
and --after
options to take a peek at
what the build environment looks like at different stages as well, which
can sometimes be useful:
% af install –dry-run –type=share –before build bash Alien::Build::Plugin::Core::Download> decoding html Alien::Build::Plugin::Core::Download> candidate *https://www.libfoo.org/download/libfoo-1.2.4.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.3.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.2.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.1.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.2.0.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.1.9.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.1.8.tar.gz Alien::Build::Plugin::Core::Download> candidate https://www.libfoo.org/download/libfoo-1.1.7.tar.gz Alien::Build::Plugin::Core::Download> candidate … Alien::Build::Plugin::Core::Download> setting version based on archive to 1.2.4 Alien::Build::Plugin::Core::Download> downloaded libfoo-1.2.4.tar.gz App::af::install> [ before build ] + bash /tmp/fbVPu4LRTs/build_5AVn/libfoo-1.2.4$ ls CHANGES Makefile autoconf.ac lib /tmp/fbVPu4LRTs/build_5AVn/libfoo-1.2.4$
There are a lot of other useful things that you can do with the af
command. See af for details.
Integrating with MakeMaker
Once you have a working alienfile you can write your Makefile.PL
.
use ExtUtils::MakeMaker; use Alien::Build::MM; my $abmm = Alien::Build::MM->new; WriteMakefile($abmm->mm_args( ABSTRACT => Discover or download and install libfoo, DISTNAME => Alien-Libfoo, NAME => Alien::Libfoo, VERSION_FROM => lib/Alien/Libfoo.pm, CONFIGURE_REQUIRES => { Alien::Build::MM => 0, }, BUILD_REQUIRES => { Alien::Build::MM => 0, }, PREREQ_PM => { Alien::Base => 0, }, # If you are going to write the recommended # tests you will will want these: TEST_REQUIRES => { Test::Alien => 0, Test2::V0 => 0, }, )); sub MY::postamble { $abmm->mm_postamble; }
The lib/Alien/Libfoo.pm
that goes along with it is very simple:
package Alien::Libfoo; use strict; use warnings; use parent qw( Alien::Base ); 1;
You are done and can install it normally:
% perl Makefile.PL % make % make test % make install
Integrating with Module::Build
Please don’t! Okay if you have to there is Alien::Build::MB.
Non standard configuration
Alien::Base support most of the things that your Alien will need, like compiler flags (cflags), linker flags (libs) and binary directory (bin_dir). Your library or tool may have other configuration items which are not supported by default. You can store the values in the alienfile into the runtime properties:
gather [ # standard: [ foo-config –version libfoo, \%{.runtime.version} ], [ foo-config –cflags libfoo, \%{.runtime.cflags} ], [ foo-config –libs libfoo, \%{.runtime.libs} ], # non-standard [ foo-config –bar-baz libfoo, \%{.runtime.bar_baz} ], ];
then you can expose them in your Alien::Base subclass:
package Alien::Libfoo; use strict; use warnings; use parent qw( Alien::Base ); sub bar_baz { my($self) = @_; $self->runtime_prop->{bar_baz}, }; 1;
Testing
(optional, but highly recommended)
You should write a test using Test::Alien to make sure that your alien will work with any XS modules that are going to use it:
use Test2::V0; use Test::Alien; use Alien::Libfoo; alien_ok Alien::Libfoo; xs_ok do { local $/; <DATA> }, with_subtest { is Foo::something(), 1, Foo::something() returns 1; }; done_testing; _ DATA _ #include “EXTERN.h” #include “perl.h” #include “XSUB.h” #include <foo.h> MODULE = Foo PACKAGE = Foo int something()
You can also use Test::Alien to test tools instead of libraries:
use Test2::V0; use Test::Alien; use Alien::Libfoo; alien_ok Alien::Libfoo; run_ok([foo, –version]) ->exit_is(0); done_testing;
You can also write tests specifically for FFI::Platypus, if your alien is going to be used to write FFI bindings. (the test below is the FFI equivalent to the XS example above).
use Test2::V0; use Test::Alien; use Alien::Libfoo; alien_ok Alien::Libfoo; ffi_ok { symbols => [ something ] }, with_subtest { # $ffi is an instance of FFI::Platypus with the lib # set appropriately. my($ffi) = @_; my $something = $ffi->function( something => [] => int ); is $something->call(), 1, Foo::something() returns 1; };
If you do use ffi_ok
you want to make sure that your alien reliably
produces dynamic libraries. If it isn’t consistent (if for example some
platforms tend not to provide or build dynamic libraries), you can check
that dynamic_libs
doesn’t return an empty list.
… alien_ok Alien::Libfoo; SKIP: { skip “This test requires a dynamic library” unless Alien::Libfoo->dynamic_libs; ffi_ok { symbols [ something ] }, with_subtest { … }; }
More details on testing Alien modules can be found in the Test::Alien documentation.
You can also run the tests that come with the package that you are
alienizing, by using a test
block in your alienfile. Keep in mind that
some packages use testing tools or have other prerequisites that will
not be available on your users machines when they attempt to install
your alien. So you do not want to blindly add a test block without
checking what the prereqs are. For Autoconf style packages you typically
test a package using the make check
command:
use alienfile; plugin PkgConfig => libfoo; share { … # standard build steps. test [ %{make} check ]; };
Dist::Zilla
(optional, mildly recommended)
You can also use the Alien::Build Dist::Zilla plugin Dist::Zilla::Plugin::AlienBuild:
name = Alien-Libfoo author = E. Xavier Ample <example@cpan.org> license = Perl_5 copyright_holder = E. Xavier Ample <example@cpan.org> copyright_year = 2017 version = 0.01 [@Basic] [AlienBuild]
The plugin takes care of a lot of details like making sure that the correct minimum versions of Alien::Build and Alien::Base are used. See the plugin documentation for additional details.
Using your Alien
Once you have installed you can use your Alien. See Alien::Build::Manual::AlienUser for guidance on that.
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)
COPYRIGHT AND LICENSE
This software is copyright (c) 2011-2020 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.
Information about Alien::Build::Manual::AlienAuthor.3pm is found in manpage for: it does, is that it moves the dynamic libraries (usually .so on