#!/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() { 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() { 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() { 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 | check | recreate | update | upgrade]"; exit; } }