#!/usr/bin/perl # apk add perl-libwww perl-json use strict; use warnings; use 5.016; use feature "switch"; no if $] >= 5.018, warnings => "experimental::smartmatch"; use LWP::UserAgent; use LWP::ConnCache; use CPAN::Meta; use Module::CoreList; use JSON; my $license_mappings = { "perl_5" => "GPL-1.0-or-later OR Artistic-1.0-Perl", "artistic_2" => "Artistic-2.0", "gpl_3" => "GPL-3.0-only", }; my $package_mappings = { "LWP" => "perl-libwww", "TermReadKey" => "perl-term-readkey", }; our $packager = ""; my $template = <<'EOF'; # Automatically generated by apkbuild-cpan, template 2 [% authors %] pkgname=[% pkgname %] _pkgreal=[% pkgreal %] pkgver=[% pkgver %] pkgrel=0 pkgdesc="Perl module for [% pkgreal %]" url="https://metacpan.org/release/[% pkgreal %]/" arch="noarch" license="GPL-1.0-or-later Artistic-1.0-Perl" cpandepends="" cpanmakedepends="" cpancheckdepends="" depends="$cpandepends" makedepends="perl-dev $cpanmakedepends" checkdepends="$cpancheckdepends" subpackages="$pkgname-doc" source="[% source %]" builddir="$srcdir/$_pkgreal-$pkgver" build() { : } check() { : } 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) = @_; return () if ( ! -e $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 ); my $authors = join("\n", $text =~ /^# Contributor: .*$/mg, $text =~ /^# Maintainer: .*$/mg); $hash{'authors'} = $authors if length($authors) > 1; 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, $authors, $moddata) = @_; my $cpanid = $distdata->{id}; $cpanid = substr($cpanid, 0, 1) . "/" . substr($cpanid, 0, 2) . "/$cpanid"; my %repl = ( authors => ($authors or "# Contributor: $packager\n# Maintainer: $packager"), pkgname => map_cpan_to_apk($moddata->{distribution}), pkgreal => $moddata->{distribution}, pkgver => $moddata->{version}, source => $moddata->{download_url} =~ s/$moddata->{version}/\$pkgver/r, pkgdesc => $distdata->{abstract}, ); $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 (Module::CoreList->is_core($module)) { 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("https://fastapi.metacpan.org/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->{distribution}} = $moddata; } # map package names to alpine packages foreach ( keys %{ $distfiles } ) { $response = $ua->get("https://fastapi.metacpan.org/release/$_"); $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/\h+/ /g; $deps =~ 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 $build_func; my $check_func; my $package_func; my $text = read_file "APKBUILD"; if (-e "$metaprefix/Build.PL" ) { $build_func = <<'EOF'; build() { export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}') perl Build.PL installdirs=vendor ./Build } EOF $package_func = <<'EOF'; package() { ./Build install destdir="$pkgdir" find "$pkgdir" \( -name perllocal.pod -o -name .packlist \) -delete } EOF $check_func = <<'EOF'; check() { ./Build test } EOF } else { $build_func = <<'EOF'; build() { export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}') PERL_MM_USE_DEFAULT=1 perl -I. Makefile.PL INSTALLDIRS=vendor make } EOF $package_func = <<'EOF'; package() { make DESTDIR="$pkgdir" install find "$pkgdir" \( -name perllocal.pod -o -name .packlist \) -delete } EOF $check_func = <<'EOF'; check() { export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}') make test } EOF } $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"; $text =~ s/^check\(\) \{.*?^\}\n/$check_func/smg or die "Can't replace check 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'), $meta->effective_prereqs->requirements_for('build', 'recommends')); say "CPAN build deps: $makedeps"; say "CPAN requires: " . parse_deps($meta->effective_prereqs->requirements_for('build', 'requires')); say "CPAN recommds: " . parse_deps($meta->effective_prereqs->requirements_for('build', 'recommends')); my $checkdeps = parse_deps($meta->effective_prereqs->requirements_for('test', 'requires'), $meta->effective_prereqs->requirements_for('test', 'recommends')); say "CPAN check deps: $makedeps"; my $text = read_file "APKBUILD"; if ($abstract && $abstract ne 'unknown') { $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"; $text =~ s/^cpancheckdepends=\"([^\"]*)\"$/cpancheckdepends=\"$checkdeps\"/mg or die "Can't find cpancheckdepends line in APKBUILD"; open my $fh, '>', "APKBUILD" or die; print $fh $text; close $fh; } sub get_data { my $apkbuild = read_apkbuild; $apkbuild->{_pkgreal} or die "Not apkbuild-cpan generated APKBUILD"; my $response = $ua->get("https://fastapi.metacpan.org/release/$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"; $response = $ua->get("https://fastapi.metacpan.org/module/$distdata->{main_module}"); $response->is_success or die $response->status_line; my $moddata = $json->decode($response->decoded_content); $moddata->{error} and die "Error trying to locate $distdata->{main_module}: $moddata->{error}\n"; return ($apkbuild, $distdata, $moddata); } my $abuild_conf = read_assignments_from_file("/etc/abuild.conf"); $packager = $abuild_conf->{PACKAGER} if $abuild_conf->{PACKAGER}; my $user_abuild_conf = read_assignments_from_file($ENV{"HOME"} . "/.abuild/abuild.conf"); $packager = $user_abuild_conf->{PACKAGER} if $user_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("https://fastapi.metacpan.org/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("https://fastapi.metacpan.org/release/$moddata->{distribution}"); $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->{metadata}{name}; mkdir $apkname; chdir $apkname; write_apkbuild($distdata, undef, $moddata); prepare_tree; update_functions; do_depends; } when ("recreate") { my ($apkbuild, $distdata, $moddata) = get_data; write_apkbuild($distdata, $apkbuild->{authors}, $moddata); prepare_tree; update_functions; do_depends; } when ("upgrade") { my ($apkbuild, $distdata, $moddata) = get_data; my $pkgver = $moddata->{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, $moddata) = get_data; my $pkgver = $moddata->{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; } }