++ed by:
ZMUGHAL PABLROD

2 PAUSE users
2 non-PAUSE users.

Graham Ollis 🔥🐉
and 3 contributors

NAME

Alien::Build::Manual::AlienAuthor - Alien author documentation

VERSION

version 0.66

SYNOPSIS

 perldoc Alien::Build::Manual::AlienAuthor

DESCRIPTION

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.

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.

(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 {
   
   download [ 'wget http://www.libfoo.org/src/libfoo-1.00.tar.gz' ];
   
   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 {
   download [ 'wget http://www.libfoo.org/src/libfoo-1.00.tar.gz' ];
   extract [ 'tar zxf %{.install.download}' ];
   build [
     [ './configure --prefix=%{.install.prefix} --disable-shared' ],
     [ '%{make}' ],
     [ '%{make} install' ],
   ];
 };

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::Buld::Plugin::Download::Negotiate, and Alien::Build::Plugin::Extract::Negotiate

 use alienfile;
 
 plugin 'PkgConfig' => (
   pkg_name => 'libfoo',
 );
 
 share {
   plugin 'Download' => (
     url => 'http://www.libfoo.org/src',
     match => 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 that 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',
   ];
 };

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 directly (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',
 ));
 
 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 base 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 base 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 { 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 <libdontpanic.h>
 
 MODULE = Foo PACKAGE = Foo
 
 int something(class)

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;

More details on testing Alien modules can be found in the Test::Alien documentation.

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

Ilya Pavlov

COPYRIGHT AND LICENSE

This software is copyright (c) 2017 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.