mirror of
https://gitlab.alpinelinux.org/alpine/abuild.git
synced 2024-12-23 23:42:35 +00:00
530 lines
15 KiB
Perl
Executable File
530 lines
15 KiB
Perl
Executable File
#!/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",
|
|
"mit" => "MIT",
|
|
};
|
|
|
|
my $package_mappings = {
|
|
"LWP" => "perl-libwww",
|
|
"libwww-perl" => "perl-libwww",
|
|
"TermReadKey" => "perl-term-readkey",
|
|
"perl-ldap" => "perl-ldap",
|
|
"PerlIO-gzip" => "perl-io-gzip",
|
|
};
|
|
our $packager = "";
|
|
my $template = <<'EOF';
|
|
# Automatically generated by apkbuild-cpan, template 3
|
|
[% authors %]
|
|
pkgname=[% pkgname %]
|
|
#_pkgreal is used by apkbuild-cpan to find modules at MetaCpan
|
|
_pkgreal=[% pkgreal %]
|
|
pkgver=[% pkgver %]
|
|
pkgrel=[% pkgrel %]
|
|
pkgdesc="Perl module for [% pkgreal %]"
|
|
provides="[% provides %]"
|
|
replaces="[% replaces %]"
|
|
url="https://metacpan.org/release/[% pkgreal %]/"
|
|
arch="noarch"
|
|
license="GPL-1.0-or-later OR Artistic-1.0-Perl"
|
|
depends="perl"
|
|
makedepends="perl-dev"
|
|
checkdepends=""
|
|
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;
|
|
|
|
my $provides = $text =~ m/^provides=\"(.*)\"".*$/mg;
|
|
$hash{'provides'} = $1 if length($provides) >= 1;
|
|
|
|
my $requires = $text =~ m/^requires=\"(.*)\"".*$/mg;
|
|
$hash{'requires'} = $1 if length($requires) >= 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, $apkbuild, $moddata) = @_;
|
|
|
|
my $cpanid = $distdata->{id};
|
|
$cpanid = substr($cpanid, 0, 1) . "/" . substr($cpanid, 0, 2) . "/$cpanid";
|
|
|
|
my $authors = undef;
|
|
my $replaces = undef;
|
|
my $provides = undef;
|
|
my $pkgrel = 0;
|
|
if (defined $apkbuild) {
|
|
$authors = $apkbuild->{authors};
|
|
$provides = $apkbuild->{provides};
|
|
$replaces = $apkbuild->{replaces};
|
|
$pkgrel = $apkbuild->{pkgrel};
|
|
if ($apkbuild->{pkgver} eq $moddata->{version}) {
|
|
$pkgrel++;
|
|
}
|
|
}
|
|
my %repl = (
|
|
authors => ($authors or "# Contributor: $packager\n# Maintainer: $packager"),
|
|
pkgname => map_cpan_to_apk($moddata->{distribution}),
|
|
pkgreal => $moddata->{distribution},
|
|
pkgver => $moddata->{version} =~ s/^[^0-9]+//r,
|
|
pkgrel => $pkgrel,
|
|
source => $moddata->{download_url},
|
|
pkgdesc => $distdata->{abstract},
|
|
provides => ($provides or ''),
|
|
replaces => ($replaces or ''),
|
|
);
|
|
$repl{source} =~ s/$repl{pkgver}/\$pkgver/g;
|
|
$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 = "";
|
|
my $reqmodlist = "";
|
|
|
|
#Get list of unique required modules from all parameters
|
|
foreach $reqs (@_) {
|
|
for my $module ($reqs->required_modules) {
|
|
$reqmodlist .= "$module " unless $reqmodlist =~ m/$module/;
|
|
}
|
|
}
|
|
|
|
# Check add the require module to $dep if not part of core perl
|
|
foreach my $module (split " ", $reqmodlist) {
|
|
next if $module eq 'perl';
|
|
if (Module::CoreList->is_core($module)) {
|
|
my $perlver = Module::CoreList->first_release($module);
|
|
say "$module is part of core perl since $perlver.";
|
|
next;
|
|
}
|
|
# 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->{$module} = $moddata->{distribution};
|
|
}
|
|
|
|
# map package names to alpine packages
|
|
foreach ( keys %{ $distfiles } ) {
|
|
$response = $ua->get("https://fastapi.metacpan.org/module/$_");
|
|
$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->{distribution});
|
|
$deps .= "$pkgname " unless $deps =~ m/$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 = '';
|
|
if ( exists $apkbuild->{'_realname'} ) {
|
|
$metaprefix = "src/" . $apkbuild->{'_realname'} . "-" . $apkbuild->{'pkgver'} . "/";
|
|
}
|
|
elsif ( exists $apkbuild->{'_pkgreal'} ) {
|
|
$metaprefix = "src/" . $apkbuild->{'_pkgreal'} . "-" . $apkbuild->{'pkgver'} . "/";
|
|
}
|
|
elsif ( exists $apkbuild->{'_pkgname'} ) {
|
|
$metaprefix = "src/" . $apkbuild->{'_pkgname'} . "-" . $apkbuild->{'pkgver'} . "/";
|
|
}
|
|
elsif ( exists $apkbuild->{'_name'} ) {
|
|
$metaprefix = "src/" . $apkbuild->{'_name'} . "-" . $apkbuild->{'pkgver'} . "/";
|
|
}
|
|
elsif ( exists $apkbuild->{'_realpkgname'} ) {
|
|
$metaprefix = "src/" . $apkbuild->{'_realpkgname'} . "-" . $apkbuild->{'pkgver'} . "/";
|
|
}
|
|
elsif ( exists $apkbuild->{'_pkg_real'} ) {
|
|
$metaprefix = "src/" . $apkbuild->{'_pkg_real'} . "-" . $apkbuild->{'pkgver'} . "/";
|
|
}
|
|
else {
|
|
die "Unable to find meta file directory - check APKBUILD Perl Module Name";
|
|
}
|
|
|
|
$metaprefix =~ s/-\$pkgver//g;
|
|
|
|
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 " OR ", map {$license_mappings->{$_} or $_} $meta->license;
|
|
say "License: $license";
|
|
|
|
my $deps = parse_deps $meta->effective_prereqs->requirements_for('runtime', 'requires');
|
|
|
|
if ($deps eq '') {
|
|
$deps = "perl";
|
|
} else {
|
|
$deps = "perl " . $deps;
|
|
}
|
|
say "CPAN deps: $deps";
|
|
say "Recommend: " . parse_deps $meta->effective_prereqs->requirements_for('runtime', 'recommends');
|
|
|
|
my $makedeps = parse_deps(
|
|
$meta->effective_prereqs->requirements_for('configure', 'requires'),
|
|
$meta->effective_prereqs->requirements_for('configure', 'recommends'),
|
|
$meta->effective_prereqs->requirements_for('build', 'requires'),
|
|
$meta->effective_prereqs->requirements_for('build', 'recommends')
|
|
);
|
|
|
|
if (-e "$metaprefix/Build.PL" ) {
|
|
$makedeps = "$makedeps perl-module-build";
|
|
}
|
|
|
|
if ($makedeps eq '') {
|
|
$makedeps = "perl-dev";
|
|
} else {
|
|
$makedeps = "perl-dev " . $makedeps;
|
|
}
|
|
say "CPAN build deps: $makedeps";
|
|
say "CPAN requires: " . parse_deps($meta->effective_prereqs->requirements_for('configure', 'requires'), $meta->effective_prereqs->requirements_for('build', 'requires'));
|
|
say "CPAN recommends: " . parse_deps($meta->effective_prereqs->requirements_for('configure', 'recommends'), $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: $checkdeps";
|
|
|
|
my $text = read_file "APKBUILD";
|
|
if ($abstract && $abstract ne 'unknown') {
|
|
$text =~ s/^pkgdesc=\"([^\"]*)\"$/pkgdesc=\"$abstract\"/mg or
|
|
die "Can't find pkgdesc 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/^depends=\"([^\"]*)\"$/depends=\"$deps\"/mg or
|
|
$text =~ s/(license=.*\n)/$1depends=\"$deps\"\n/gm or
|
|
die "Can't insert depends line in APKBUILD";
|
|
|
|
$text =~ s/^makedepends=\"([^\"]*)\"$/makedepends=\"$makedeps\"/mg or
|
|
$text =~ s/(depends=.*\n)/$1makedepends=\"$makedeps\"\n/gm or
|
|
die "Can't insert makedepends line in APKBUILD";
|
|
|
|
$text =~ s/^checkdepends=\"([^\"]*)\"$/checkdepends=\"$checkdeps\"/mg or
|
|
$text =~ s/(makedepends=.*\n)/$1checkdepends=\"$checkdeps\"\n/gm or
|
|
die "Can't insert checkdepends line in APKBUILD";
|
|
|
|
# remove empty variables
|
|
$text =~ s/.*=""\n//g;
|
|
|
|
open my $fh, '>', "APKBUILD" or die;
|
|
print $fh $text;
|
|
close $fh;
|
|
}
|
|
|
|
sub get_data {
|
|
my $apkbuild = read_apkbuild;
|
|
my $pkgreal = '';
|
|
|
|
if (exists $apkbuild->{_realname}) {
|
|
$pkgreal = $apkbuild->{_realname};
|
|
} elsif (exists $apkbuild->{_pkgreal}) {
|
|
$pkgreal = $apkbuild->{_pkgreal};
|
|
} elsif (exists $apkbuild->{_pkgname}) {
|
|
$pkgreal = $apkbuild->{_pkgname};
|
|
} elsif (exists $apkbuild->{_name}) {
|
|
$pkgreal = $apkbuild->{_name};
|
|
} elsif (exists $apkbuild->{_realpkgname}) {
|
|
$pkgreal = $apkbuild->{_realpkgname};
|
|
} elsif (exists $apkbuild->{_pkg_real}) {
|
|
$pkgreal = $apkbuild->{_pkg_real};
|
|
} else {
|
|
my $module = '';
|
|
my $distribution = '';
|
|
while ((my $key, my $value ) = each (%$apkbuild)) {
|
|
# Do not parse any depends lines to not find incorrect module
|
|
if ($key =~ m/.*depends.*/) {
|
|
next;
|
|
}
|
|
# Try to find a perl module name in APKBUILD
|
|
if ($value=~m/((\w+::)+\w+)/g) {
|
|
# Match Perl Module names containing ::
|
|
$module .= "$1 " unless $module =~ m/$1/;
|
|
}
|
|
elsif ($value =~ m/(([A-Z]\w+-)+\w+)/) {
|
|
# Match possible distribution names with -
|
|
$distribution .= "$1 " unless $distribution =~ m/ *$1 /;
|
|
}
|
|
elsif ( $value =~ m/.*release\/([A-Z]\w+).*/ ) {
|
|
# Match Single Word Perl Module Name after release in URL?
|
|
$distribution .= "$1 " unless $distribution =~ m/ *$1 /;
|
|
}
|
|
}
|
|
|
|
# Want to try the traditional Module::Name first
|
|
my $list = $module . $distribution;
|
|
foreach (split / /, $list) {
|
|
my $type = '';
|
|
if( $_ =~ m/::/ ) {
|
|
$type = 'module';
|
|
} else {
|
|
$type = 'release';
|
|
}
|
|
|
|
my $response = $ua->get("https://fastapi.metacpan.org/$type/$_");
|
|
$response->is_success or next;;
|
|
|
|
my $moddata = $json->decode($response->decoded_content);
|
|
$moddata->{error} and next;
|
|
|
|
$pkgreal = $moddata->{distribution};
|
|
last;
|
|
}
|
|
}
|
|
|
|
$pkgreal =~ s/-\$pkgver//g;
|
|
|
|
my $response = $ua->get("https://fastapi.metacpan.org/release/$pkgreal");
|
|
$response->is_success or
|
|
die $response->status_line . " unable to find $pkgreal verify Perl Module name in APKBUILD\n";
|
|
my $distdata = $json->decode($response->decoded_content);
|
|
$distdata->{error} and die "Error trying to locate $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];
|
|
$module or die "Module name is a mandatory argument";
|
|
|
|
my $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") {
|
|
#TODO: likely should keep pkgrel the same on recreate
|
|
my ($apkbuild, $distdata, $moddata) = get_data;
|
|
my $pkgver = $moddata->{version} =~ s/^[^0-9]+//r;
|
|
if ($pkgver ne $apkbuild->{pkgver}) {
|
|
#Reset pkgrel on upgrade on recreate
|
|
say "Upgrading CPAN module from $apkbuild->{pkgver} to $pkgver";
|
|
$apkbuild->{pkgrel}=0;
|
|
}
|
|
write_apkbuild($distdata, $apkbuild, $moddata);
|
|
prepare_tree;
|
|
update_functions;
|
|
do_depends;
|
|
}
|
|
when ("upgrade") {
|
|
my ($apkbuild, $distdata, $moddata) = get_data;
|
|
|
|
my $pkgver = $moddata->{version} =~ s/^[^0-9]+//r;
|
|
if ($pkgver ne $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} =~ s/^[^0-9]+//r;
|
|
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;
|
|
}
|
|
}
|