#!/usr/bin/perl
# apk add perl-libwww perl-json
use strict;
use warnings;
use feature qw(:5.10);
use LWP::UserAgent;
use LWP::ConnCache;
use CPAN::Meta;
use Module::CoreList;
use JSON;
my $license_mappings = {
"perl_5" => "GPL PerlArtistic",
"artistic_2" => "Artistic-2",
};
my $package_mappings = {
"LWP" => "perl-libwww",
"TermReadKey" => "perl-term-readkey",
};
our $packager = "";
my $template = <<'EOF';
# Automatically generated by apkbuild-cpan, template 1
# Contributor: [% packager %]
# Maintainer: [% packager %]
pkgname=[% pkgname %]
_pkgreal=[% pkgreal %]
pkgver=[% pkgver %]
pkgrel=0
pkgdesc="Perl module for [% pkgreal %]"
url="http://search.cpan.org/dist/[% pkgreal %]/"
arch="noarch"
license="GPL PerlArtistic"
cpandepends=""
cpanmakedepends=""
depends="$cpandepends"
makedepends="perl-dev $cpanmakedepends"
subpackages="$pkgname-doc"
source="[% source %]"
builddir="$srcdir/$_pkgreal-$pkgver"
prepare() {
default_prepare || return 1
cd "$builddir"
if [ -e Build.PL ]; then
perl Build.PL installdirs=vendor || return 1
else
PERL_MM_USE_DEFAULT=1 perl Makefile.PL INSTALLDIRS=vendor || return 1
fi
}
build() {
:
}
package() {
:
}
EOF
our $ua = LWP::UserAgent->new();
our $json = JSON->new;
$ua->env_proxy;
$ua->conn_cache(LWP::ConnCache->new());
sub read_file
{
my ($filename) = @_;
local $/;
open my $fh, "<", $filename or die "could not open $filename: $!";
return <$fh>;
}
sub read_assignments_from_file {
my ($filename) = @_;
my $text = read_file($filename);
my %sline = $text =~ /^(\w+)\s*=\s*([^\"\n]*)$/mg;
my %mline = $text =~ /^(\w+)\s*=\s*\"([^\"]*)\"$/mg;
my %hash = ( %sline, %mline );
return \%hash;
}
sub map_cpan_to_apk {
my ($cpan_distrib) = @_;
return $package_mappings->{$cpan_distrib} if exists $package_mappings->{$cpan_distrib};
# most packages are named according to the
# distribution name
return 'perl-' . lc $cpan_distrib;
}
sub read_apkbuild {
return read_assignments_from_file("APKBUILD");
}
sub write_apkbuild {
my ($distdata) = @_;
my $cpanid = $distdata->{releases}[0]->{cpanid};
$cpanid = substr($cpanid, 0, 1) . "/" . substr($cpanid, 0, 2) . "/$cpanid";
my %repl = (
packager => $packager,
pkgname => map_cpan_to_apk($distdata->{name}),
pkgreal => $distdata->{name},
pkgver => $distdata->{releases}[0]->{version},
source => "http://search.cpan.org/CPAN/authors/id/$cpanid/\$_pkgreal-\$pkgver.tar.gz",
);
$template =~ s/\[% (.*?) %\]/$repl{$1}/g;
open my $fh, '>', "APKBUILD" or die;
print $fh $template;
close $fh;
say "Wrote $repl{pkgname}/APKBUILD";
}
sub parse_deps {
my ($reqs) = @_;
my $distfiles = {};
my $response;
my $deps = "";
for my $module ($reqs->required_modules) {
if (my $perlver = Module::CoreList->first_release($module)) {
say "$module is part of core perl since $perlver.";
next;
}
next if $module eq 'perl';
# map module name to package name
$response = $ua->get("http://search.cpan.org/api/module/$module");
$response->is_success or die $response->status_line;
my $moddata = $json->decode($response->decoded_content);
$moddata->{error} and die "Error trying to locate $module: $moddata->{error}\n";
$distfiles->{$moddata->{distvname}} = $moddata;
}
# map package names to alpine packages
foreach ( keys %{ $distfiles } ) {
$response = $ua->get("http://search.cpan.org/api/dist/$_");
$response->is_success or die $response->status_line;
my $distdata = $json->decode($response->decoded_content);
$distdata->{error} and die "Error trying to locate $_: $distdata->{error}\n";
my $pkgname = map_cpan_to_apk($distdata->{name});
$deps .= "$pkgname ";
}
$deps =~ s/\s+$//;
return $deps;
}
sub prepare_tree {
system("abuild checksum unpack prepare") == 0 or
die "abuild checksum failed";
}
sub update_functions {
my $apkbuild = read_apkbuild;
my $metaprefix = "src/" . $apkbuild->{'_pkgreal'} . "-" . $apkbuild->{'pkgver'} . "/";
my $prepare_func;
my $build_func;
my $package_func;
my $text = read_file "APKBUILD";
if (-e "$metaprefix/Build.PL" ) {
$prepare_func = <<'EOF';
prepare() {
default_prepare || return 1
cd "$builddir"
export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
perl Build.PL installdirs=vendor || return 1
}
EOF
$build_func = <<'EOF';
build() {
cd "$builddir"
export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
./Build && ./Build test
}
EOF
$package_func = <<'EOF';
package() {
cd "$builddir"
./Build install destdir="$pkgdir" || return 1
find "$pkgdir" \( -name perllocal.pod -o -name .packlist \) -delete
}
EOF
} else {
$prepare_func = <<'EOF';
prepare() {
default_prepare || return 1
cd "$builddir"
export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
PERL_MM_USE_DEFAULT=1 perl Makefile.PL INSTALLDIRS=vendor
}
EOF
$build_func = <<'EOF';
build() {
cd "$builddir"
export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
make && make test
}
EOF
$package_func = <<'EOF';
package() {
cd "$builddir"
make DESTDIR="$pkgdir" install || return 1
find "$pkgdir" \( -name perllocal.pod -o -name .packlist \) -delete
}
EOF
}
$text =~ s/^prepare\(\) \{.*?^\}\n/$prepare_func/smg or
die "Can't replace prepare function APKBUILD";
$text =~ s/^build\(\) \{.*?^\}\n/$build_func/smg or
die "Can't replace build function APKBUILD";
$text =~ s/^package\(\) \{.*?^\}\n/$package_func/smg or
die "Can't replace package function APKBUILD";
open my $fh, '>', "APKBUILD" or die;
print $fh $text;
close $fh;
}
sub do_depends {
my $apkbuild = read_apkbuild;
my $metaprefix = "src/" . $apkbuild->{'_pkgreal'} . "-" . $apkbuild->{'pkgver'} . "/";
my $meta;
foreach my $metafile ("MYMETA.json", "META.json", "MYMETA.yml", "META.yml") {
if (-e "$metaprefix$metafile") {
say "Using meta information from $metafile";
$meta = CPAN::Meta->load_file("$metaprefix$metafile");
last;
}
}
die "No dependency meta file found" unless $meta;
my $abstract = $meta->abstract;
say "Abstract: $abstract";
my $license = join " ", map {$license_mappings->{$_} or $_} $meta->license;
say "License: $license";
my $deps = parse_deps $meta->effective_prereqs->requirements_for('runtime', 'requires');
say "CPAN deps: $deps";
say "Recommend: " . parse_deps $meta->effective_prereqs->requirements_for('runtime', 'recommends');
my $makedeps = parse_deps $meta->effective_prereqs->requirements_for('build', 'requires');
$makedeps .= ' ' . parse_deps $meta->effective_prereqs->requirements_for('build', 'recommends');
$makedeps .= ' ' . parse_deps $meta->effective_prereqs->requirements_for('test', 'requires');
$makedeps .= ' ' . parse_deps $meta->effective_prereqs->requirements_for('test', 'recommends');
say "CPAN build deps: $makedeps";
my $text = read_file "APKBUILD";
if ($abstract) {
$text =~ s/^pkgdesc=\"([^\"]*)\"$/pkgdesc=\"$abstract\"/mg or
die "Can't find cpandepends line in APKBUILD";
}
if (length(`find $metaprefix -name '*.xs'`)) {
$text =~ s/^arch=\"([^\"]*)\"$/arch="all"/mg or
die "Can't find arch line in APKBUILD";
}
if ($license ne 'unknown') {
$text =~ s/^license=\"([^\"]*)\"$/license=\"$license\"/mg or
die "Can't find license line in APKBUILD";
}
$text =~ s/^cpandepends=\"([^\"]*)\"$/cpandepends=\"$deps\"/mg or
die "Can't find cpandepends line in APKBUILD";
$text =~ s/^cpanmakedepends=\"([^\"]*)\"$/cpanmakedepends=\"$makedeps\"/mg or
die "Can't find cpanmakedepends line in APKBUILD";
open my $fh, '>', "APKBUILD" or die;
print $fh $text;
close $fh;
}
sub get_data {
my $apkbuild = read_apkbuild;
my $response = $ua->get("http://search.cpan.org/api/dist/$apkbuild->{_pkgreal}");
$response->is_success or die $response->status_line;
my $distdata = $json->decode($response->decoded_content);
$distdata->{error} and die "Error trying to locate $apkbuild->{_pkgreal}: $distdata->{error}\n";
return ($apkbuild, $distdata);
}
my $abuild_conf = read_assignments_from_file("/etc/abuild.conf");
$packager = $abuild_conf->{PACKAGER} if $abuild_conf->{PACKAGER};
given ( $ARGV[0] ) {
when ("create") {
my $module = $ARGV[1];
my $response;
$module or die "Module name is a mandatory argument";
$response = $ua->get("http://search.cpan.org/api/module/$module");
$response->is_success or die $response->status_line;
my $moddata = $json->decode($response->decoded_content);
$moddata->{error} and die "Error trying to locate $module: $moddata->{error}\n";
$response = $ua->get("http://search.cpan.org/api/dist/$moddata->{distvname}");
$response->is_success or die $response->status_line;
my $distdata = $json->decode($response->decoded_content);
$distdata->{error} and die "Error trying to locate $module: $distdata->{error}\n";
my $apkname = map_cpan_to_apk $distdata->{name};
mkdir $apkname;
chdir $apkname;
write_apkbuild($distdata);
prepare_tree;
update_functions;
do_depends;
}
when ("recreate") {
my ($apkbuild, $distdata) = get_data;
write_apkbuild($distdata);
prepare_tree;
update_functions;
do_depends;
}
when ("upgrade") {
my ($apkbuild, $distdata) = get_data;
my $pkgver = $distdata->{releases}[0]->{version};
if ($pkgver != $apkbuild->{pkgver}) {
say "Upgrading CPAN module from $apkbuild->{pkgver} to $pkgver";
my $text = read_file "APKBUILD";
$text =~ s/^pkgver=(.*)$/pkgver=$pkgver/mg or
die "Can't find pkgver line in APKBUILD";
$text =~ s/^pkgrel=(.*)$/pkgrel=0/mg;
open my $fh, '>', "APKBUILD" or die;
say $fh $text;
close $fh;
prepare_tree;
do_depends;
} else {
say "Up-to-data with CPAN";
}
}
when ('check') {
my ($apkbuild, $distdata) = get_data;
my $pkgver = $distdata->{releases}[0]->{version};
say "$apkbuild->{pkgname}: Latest version: $pkgver Packaged version: $apkbuild->{pkgver}";
if ($pkgver ne $apkbuild->{pkgver}) {
exit(1);
}
}
when ("update") {
prepare_tree;
do_depends;
}
default {
say "Usage: apkbuild-cpan [create <Module::Name> | check | recreate | update | upgrade]";
exit;
}
}