From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use 5.010;
use Path::Tiny qw /path/;
use File::Which qw /which/;
use FFI::CheckLib;
my $on_windows = $^O =~ /mswin/i;
my $on_automated_rig
= $ENV{PERL_CPAN_REPORTER_DIR}
|| $ENV{PERL_CPAN_REPORTER_CONFIG}
|| $ENV{AUTOMATED_TESTING}
|| $ENV{TRAVIS}
|| $ENV{APPVEYOR}
|| $ENV{CI};
use Cwd;
my $base_dir = getcwd();
use Env qw( @PATH @PKG_CONFIG_PATH );
unshift @PATH, Alien::sqlite->bin_dir;
my @dep_aliens = ('Alien::sqlite');
my %have;
my $lib = 'Alien::curl';
$have{$lib} = eval "require $lib";
if ($have{$lib}) {
unshift @PATH, $lib->dist_dir . '/dynamic';
}
$lib = 'Alien::libtiff';
$have{$lib} = eval "require $lib";
if ($have{$lib}) {
#say 'Adding libtiff dependency';
unshift @PATH, $lib->bin_dir;
push @dep_aliens, $lib if $lib->install_type eq 'share';
my $p;
if ($on_windows && $lib->install_type eq 'system') {
# dirty hack for strawberry perl
$p = path ($^X)->parent->parent->parent . '/c/lib/pkgconfig';
}
elsif ($lib->install_type eq 'share') {
$p = path ($lib->dist_dir, 'lib', 'pkgconfig');
}
if ($p && -e $p) {
push @PKG_CONFIG_PATH, $p;
say join ' ', @PKG_CONFIG_PATH;
}
}
say "Alien::sqlite has sqlite version " . Alien::sqlite->version;
my $min_target_version = '7.1';
plugin 'PkgConfig' => (
pkg_name => 'proj',
minimum_version => $min_target_version,
);
if ($on_windows) {
# possible GIS Internals distribution
if (my $path = `where geos_c.dll`) {
Alien::Build->log ('Found possible GIS Internals build');
$path = Path::Tiny::path ($path)->parent;
# could be several variants
my @proj_dlls
= map {$_->[0]}
sort { versioncmp($a->[-1], $b->[-1]) }
map { $_ =~ /(\d+)_(\d+)/; [$_, "$1.$2"] }
glob ("$path/proj*.dll");
$proj_dlls[-1] =~ /(\d+)_(\d+)/;
my $version = "$1.$2";
if (versioncmp ($version, $min_target_version) > 0) {
$path = Path::Tiny::path($proj_dlls[-1])->parent->stringify;
$path =~ s|\\|/|g; # path separators
Alien::Build->log ("Probe::CBuilder checking $path");
plugin 'Probe::CBuilder' => (
cflags => "-I$path/include",
libs => "-L$path/lib -lproj",
);
}
}
}
share {
# see if this helps with cirrus bsd builds
#$ENV{SQLITE3_CFLAGS} = Alien::sqlite->cflags;
#$ENV{SQLITE3_LIBS} = Alien::sqlite->libs;
#say "sqlite cflags: " . Alien::sqlite->cflags;
#say "sqlite libs: " . Alien::sqlite->libs;
#if ($have{'Alien::libtiff'}) {
# my $p = path ('Alien::libtiff'->dist_dir, 'lib');
# $ENV{TIFF_LIBS}
# ||= (-e $p
# ? "-L$p "
# : ''
# )
# . 'Alien::libtiff'->libs;
# say "libtiff libs: $ENV{TIFF_LIBS}";
#}
plugin 'Build::SearchDep' => (
aliens => [ grep {$_->install_type eq 'share'} @dep_aliens ],
public_I => 1,
public_l => 1,
);
if ($on_windows) {
# there are issues with strawberry perl's gcc
plugin 'Prefer::BadVersion' => '7.1.0';
}
#start_url "file://$base_dir"; # debug
plugin Download => (
filter => qr/^proj-([0-9\.]+)\.tar\.gz$/,
version => qr/^proj-([0-9\.]+)\.tar\.gz$/,
);
my $proj_version = get_proj_version() // 'not yet defined';
say "Downloaded proj version is $proj_version";
die "Downloaded proj version $proj_version is too low "
. "(should be >= $min_target_version).\n"
. "Please update your Alien::Build::Fetch::Cache if using one."
if defined $proj_version
&& versioncmp ($proj_version, $min_target_version) < 0;
plugin Extract => (format => 'tar.gz');
plugin 'Build::CMake' => ();
plugin 'Build::MSYS';
if ($^O =~ /bsd|dragonfly/) {
plugin 'Build::Make' => 'gmake';
if (!-e '/usr/local/include/sqlite3.h' && Alien::sqlite->install_type eq 'system') {
warn '/usr/local/include/sqlite3.h does not exist, '
. 'you might need to install the sqlite package for your system, '
. 'or install a share version of Alien::sqlite';
}
}
elsif ($^O eq 'MSWin32') {
plugin 'Build::Make' => 'gmake';
}
my $make_cmd = '%{make}';
my $make_inst_cmd = '%{make} install';
my @make_clean;
# try not to exceed the cpan-testers log limits
if ($on_automated_rig) {
say "Running under CI or automated testing";
#$make_cmd .= q/ | perl -ne "BEGIN {$|=1; open our $log, q|>|, q|build.log|}; print qq|\n| if 0 == ($. %% 100); print q|.|; print {$log} $_;" || type build.log/;
#$make_inst_cmd .= q/ | perl -ne "BEGIN {$|=1; open our $log, q|>|, q|install.log|}; print qq|\n| if 0 == ($. %% 100); print q|.|; print {$log} $_;" || type install.log/;
#if (!$on_windows) {
# $make_cmd =~ s/%%/%/;
# $make_cmd =~ s/type/cat/;
# $make_cmd =~ s/"/'/g;
# $make_inst_cmd =~ s/%%/%/;
# $make_inst_cmd =~ s/type/cat/;
# $make_inst_cmd =~ s/"/'/g;
#}
#if (! ($ENV{TRAVIS} || $ENV{APPVEYOR})) {
# push @make_clean, '%{make} clean';
#}
# clean up the build dir on cpan testers etc
# but not github workflows
if (!$ENV{CI}) {
plugin 'Cleanse::BuildDir';
}
}
my $config_args = $ENV{ALIEN_PROJ_CONFIG_ARGS} // '';
$config_args =~ s/[^-\s\w,=]//g; # overkill?
my $enable_tiff = 'OFF';
my $enable_curl = 'OFF';
my @curl_extra_args;
my @tiff_extra_args;
my @alien_rpaths;
if ($proj_version ge 7) {
if (!$have{'Alien::libtiff'} or $config_args =~ /--(without-tiff|with-tiff=no)/) {
$enable_tiff = 'OFF';
Alien::Build->log ('Disabling TIFF support');
}
else {
$enable_tiff = 'ON';
Alien::Build->log ('Enabling TIFF support');
# might need this but it depends on a few things
my $libtiff_include;
my $libtiff_rpath = ();
if (Alien::libtiff->install_type ('share')) {
$libtiff_include = path ('Alien::libtiff'->dist_dir . '/include')->stringify;
$libtiff_rpath = Alien::libtiff->dist_dir . '/lib';
}
else {
if ($on_windows) {
my $p = path ($^X)->parent->parent->parent . '/c/include';
if (path($p)->exists) {
$libtiff_include = $p;
}
}
else {
my $libfile = find_lib (lib => 'tiff');
push @tiff_extra_args, "-DTIFF_LIBRARY=$libfile";
}
}
if ($libtiff_include) {
my $libfile = find_lib_file (
lib => 'tiff',
alien => 'Alien::libtiff',
);
push @tiff_extra_args, (
"-DTIFF_LIBRARY=$libfile",
"-DTIFF_INCLUDE_DIR=$libtiff_include",
);
push @alien_rpaths, $libtiff_rpath;
}
if (!@tiff_extra_args) {
Alien::Build->log ('Disabling TIFF support');
$enable_tiff = 'OFF';
}
}
if (!$have{'Alien::curl'} or $config_args =~ /--(without-curl|with-curl=no)/ ) {
$enable_curl = 'OFF';
}
else {
my $dynamic_config = path ('Alien::curl'->dist_dir . '/dynamic/curl-config');
if (-e $dynamic_config) {
Alien::Build->log ("Adding curl support from " . 'Alien::curl'->dist_dir . "/dynamic");
$enable_curl = "ON";
push @PATH, path ('Alien::curl'->dist_dir . '/dynamic');
my $curl_include = path ('Alien::curl'->dist_dir . '/include')->stringify;
my $libfile = find_lib_file (
lib => 'curl',
alien => 'Alien::curl',
);
push @curl_extra_args, (
"-DCURL_LIBRARY=$libfile",
"-DCURL_INCLUDE_DIR=$curl_include",
);
push @alien_rpaths, Alien::curl->dist_dir . '/dynamic';
}
elsif ('Alien::curl'->install_type eq 'system') {
if ('Alien::curl'->cflags =~ /STATIC_LIB/) {
$enable_curl = 'OFF';
Alien::Build->log ('Disabling curl support. You have Alien::curl but it lacks a dynamic curl-config');
}
else {
Alien::Build->log ('Adding curl support from system');
$enable_curl = "ON";
my $libfile = find_lib_file (
lib => 'curl',
);
my $curl_include = Alien::curl->cflags;
$curl_include =~s/^-I//g; # hack
push @curl_extra_args, (
"-DCURL_LIBRARY=$libfile",
"-DCURL_INCLUDE_DIR=$curl_include",
);
}
}
else {
Alien::Build->log ('Disabling curl support. You have Alien::curl, but it lacks a dynamic curl-config');
# until we depend on an Alien::libcurl that provides a dynamic curl-config
$enable_curl = 'OFF';
}
}
}
my @sqlite_extra_args;
if (Alien::sqlite->install_type ('share')) {
Alien::Build->log ('Shared Alien::sqlite found, adding sqlite3 components to configure call');
# system install should have these in the relevant paths
my $sqlite_include = path ('Alien::sqlite'->dist_dir . '/include')->stringify;
my $libfile = find_lib_file (
lib => 'sqlite3',
alien => 'Alien::sqlite',
);
my $exe_extension = ($on_windows ? '.exe' : '');
my $sqlite_exe = path ('Alien::sqlite'->dist_dir . "/bin/sqlite3$exe_extension");
push @sqlite_extra_args, (
"-DEXE_SQLITE3=$sqlite_exe",
"-DSQLite3_LIBRARY=$libfile",
"-DSQLite3_INCLUDE_DIR=$sqlite_include",
);
push @alien_rpaths, Alien::sqlite->dist_dir . '/lib';
}
plugin 'PkgConfig::PPWrapper';
meta->around_hook( build => \&remove_gitfw_from_path );
meta->around_hook( build => \&update_cmake_lists_file );
meta->around_hook(
build => sub {
my ($orig, $build, @args) = @_;
$build->log("Setting CCACHE_BASEDIR to " . getcwd());
local $ENV{CCACHE_BASEDIR} = getcwd();
$orig->($build, @args);
}
);
# silence an undef warning
Alien::Build->log ('PKG_CONFIG_PATH:' . join ' ', grep {defined} @PKG_CONFIG_PATH);
foreach my $env_var (qw /LD_LIBRARY_PATH LDFLAGS CFLAGS CXXFLAGS/) {
say "ENV: $env_var is " . ($ENV{$env_var} // '');
}
# try to fix some rpath issues
# See also:
my $h = get_alien_state_hash();
my $install_path = $h->{install}{prefix};
my @rpath;
if (!$on_windows && defined $install_path && @alien_rpaths) {
my $origin_string = $^O =~ /darwin/ ? '@loader_path' : '\$ORIGIN';
my $alien_rpath_text
= join ':',
(map {$origin_string . '/../' . path ($_)->relative($install_path)->stringify} @alien_rpaths),
@alien_rpaths;
@rpath = (qq{-DCMAKE_INSTALL_RPATH='$alien_rpath_text'});
}
my $cmake_cmd = [
'%{cmake}',
-G => '%{cmake_generator}',
'-DCMAKE_MAKE_PROGRAM=%{make}',
#'-DBUILD_DOCUMENTATION=NO',
'-DBUILD_TESTING=OFF',
'-DBUILD_SHARED_LIBS=ON',
@sqlite_extra_args,
"-DENABLE_CURL=$enable_curl",
$enable_curl eq 'ON' ? @curl_extra_args : (),
"-DENABLE_TIFF=$enable_tiff",
$enable_tiff eq 'ON' ? @tiff_extra_args : (),
'-DBUILD_PROJSYNC=OFF',
#'-DENABLE_IPO=ON',
#'-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=true',
'-DCMAKE_INSTALL_PREFIX:PATH=%{.install.prefix}',
'-DCMAKE_BUILD_TYPE=Release',
@rpath,
#'-DCMAKE_VERBOSE=ON',
'..'
];
build [
#\&pause,
'mkdir _build',
'cd _build',
$cmake_cmd,
$make_cmd,
#($run_tests ? '%{make} test' : ()),
$make_inst_cmd,
];
};
# git for windows clashes with MSYS
# if its /usr/bin dir is in the path
sub remove_gitfw_from_path {
my ($orig, $build, @args) = @_;
return $orig->($build, @args)
if !$on_windows;
local $ENV{PATH} = $ENV{PATH};
my $msys_path = eval {
path('Alien::MSYS'->msys_path())
};
return if !defined $msys_path;
my $count = @PATH;
@PATH
= grep {path($_)->stringify =~ m|/usr/bin$| && path($_) ne $msys_path ? () : $_}
@PATH;
my $removed = $count - @PATH;
if ($removed) {
$build->log ("$removed additional .../usr/bin dirs were removed from the path for compilation");
}
$orig->($build, @args);
}
sub update_pkg_conf_path {
return;
return if !$on_windows;
# should be a before or around hook
use Env qw /@PKG_CONFIG_PATH/;
say 'Modifying drive paths in PKG_CONFIG_PATH';
say $ENV{PKG_CONFIG_PATH};
# msys-ificate drive paths
@PKG_CONFIG_PATH = map {my $r=$_; $r=~s{^([a-z]):}{/$1}i; $r} @PKG_CONFIG_PATH;
# make sure we get the dynamic libcurl
# (although the proj configure script does not currently use it)
@PKG_CONFIG_PATH
= map {my $r=$_; $r=~s{Alien-curl[/\\]lib[/\\]pkgconfig}{Alien-curl/dynamic/pkgconfig}i; $r}
@PKG_CONFIG_PATH;
$ENV{PKG_CONFIG_PATH} = join ':', @PKG_CONFIG_PATH;
say $ENV{PKG_CONFIG_PATH};
return;
}
sub pause {
return; # re-enable in case of debug
return if $on_automated_rig;
return if !$on_windows;
say "CONTINUE?";
my $response = <>;
while (not $response =~ /yes/) {
$response = <>;
}
}
sub update_cmake_lists_file {
my ($orig, $build, @args) = @_;
# only needed on windows for v9.0.0 or earlier
return $orig->($build, @args)
if $on_windows and versioncmp (get_proj_version(), '9.0.0') < 0;
$build->log ('updating CMakeLists.txt to properly handle proj.pc');
#my $h = $build->install_prop;
#use Data::Printer;
#p $h;
my ($cmake_lists_file)
= File::Find::Rule
->file()
->name( 'CMakeLists.txt' )
->in( $build->install_prop->{extract} );
#$build->log ("====\ncmake file: $cmake_lists_file \n ====");
open my $fh , '<', $cmake_lists_file
or die "Could not open $cmake_lists_file for reading, $!";
my $file_contents;
my $define_check = <<EOD
if(NOT DEFINED CMAKE_INSTALL_LIBDIR)
set(CMAKE_INSTALL_LIBDIR lib)
endif(NOT DEFINED CMAKE_INSTALL_LIBDIR)
EOD
;
while (my $line = <$fh>) {
if ($line =~ /^\s*configure_proj_pc/) {
#say STDERR "ADDING TO CMAKE LISTS";
$line .= $define_check;
}
$file_contents .= $line;
}
$fh->close;
rename $cmake_lists_file, "$cmake_lists_file.bak";
open my $ofh, '>', $cmake_lists_file
or die "Could not open $cmake_lists_file for writing, $!";
print {$ofh} $file_contents;
$ofh->close or die $!;
$orig->($build, @args);
}
sub find_lib_file {
my %args = @_;
my(@lib_files) = find_lib(
%args,
systempath => $args{alien} ? [] : undef, # only search the alien if one is passed
);
# we grab the first, assuming they are
# all the same or point to the same origin
my $libfile = $lib_files[0];
$libfile =~ s|\\|/|g if $on_windows;
Alien::Build->log ("Found $args{lib} dynamic lib: $libfile");
return $libfile;
}
sub get_proj_version {
my $h = get_alien_state_hash();
return $h->{runtime}{version};
}
sub get_alien_state_hash {
use JSON::PP;
my $root = "$base_dir/_alien";
my $f = "$root/state.json";
my $h = {};
if (-e $f) {
open my $fh, '<', $f or die $!;
my $d = do {
local $/ = undef;
<$fh>;
};
$h = JSON::PP::decode_json($d);
}
return $h;
}