#!/usr/bin/perl use strict; use warnings; use autodie qw(:all); use feature qw(:5.14); use LWP::UserAgent; use LWP::ConnCache; use JSON; our $packager = ''; my %pkgmap = (); my %licenses = (); my $template = <<'EOF'; # Automatically generated by apkbuild-pypi, template 3 [% authors %] pkgname=[% pkgname %] #_pkgreal is used by apkbuild-pypi to find modules at PyPI _pkgreal=[% pkgreal %] pkgver=[% pkgver %] pkgrel=[% pkgrel %] pkgdesc="[% pkgdesc %]" provides="[% provides %]" replaces="[% replaces %]" url="http://packages.python.org/pypi/[% pkgreal %]" arch="noarch" license="[% license %]" depends="python3" checkdepends="python3-dev" makedepends="py3-setuptools" subpackages="" source="[% source %]" builddir="$srcdir/$_pkgreal-$pkgver" build() { python3 setup.py build } check() { python3 setup.py test } package() { PYTHONPATH=$pkgdir$(python3 -c "import site; print(site.getsitepackages()[0])") \ python3 setup.py install \ --prefix=/usr \ --root="$pkgdir" \ --single-version-externally-managed } EOF my $ua = LWP::UserAgent->new(); my $json = JSON->new; $ua->env_proxy; $ua->conn_cache(LWP::ConnCache->new); sub read_file { my ($filename) = @_; open my $fh, '<:utf8', $filename; local $/; my $text = <$fh>; return $text; } 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_pypi_to_apk { my ($pypi) = @_; return $pkgmap{$pypi} unless !exists($pkgmap{$pypi}); return 'py3-'.lc($pypi); } sub map_license { my ($license) = @_; $license =~ s/ or / /g; return $license; } sub get_source { my ($distdata) = @_; for my $url (@{$distdata->{urls}}) { return $url->{url} if $url->{python_version} eq 'source' } die "Unable to locate sources for $distdata->{name}.\n"; } sub read_apkbuild { return read_assignments_from_file('APKBUILD'); } sub write_apkbuild { my ($distdata, $apkbuild) = @_; my $replaces = undef; my $provides = undef; my $authors = undef; my $pkgrel = 0; if (defined $apkbuild) { $authors = $apkbuild->{authors}; $provides = $apkbuild->{provides}; $replaces = $apkbuild->{replaces}; $pkgrel = $apkbuild->{pkgrel}; if ($apkbuild->{pkgver} eq $distdata->{info}{version}) { $pkgrel++; } } my %repl = ( authors => ($authors or "# Contributor: $packager\n# Maintainer: $packager"), pkgname => map_pypi_to_apk($distdata->{info}{name}), pkgreal => $distdata->{info}{name}, pkgver => $distdata->{info}{version}, pkgrel => $pkgrel, source => get_source($distdata), license => map_license($distdata->{info}{license}), pkgdesc => $distdata->{info}{summary}, provides => ($provides or ''), replaces => ($replaces or ''), ); $template =~ s/\[% (.*?) %\]/$repl{$1}/g; open my $fh, '>:utf8', 'APKBUILD'; print {$fh} $template; close $fh; say "Wrote $repl{pkgname}/APKBUILD"; return \%repl; } sub prepare_tree { system('abuild checksum unpack prepare'); } sub find_package_name { my ($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}; } elsif (exists $apkbuild->{source}) { $pkgreal = $apkbuild->{source}; $pkgreal =~ m/(\w+)-/; $pkgreal = $1; } else { print "No pkg real found\n"; die; } return $pkgreal; } sub get_data { my ($package) = @_; my $response = $ua->get("https://pypi.python.org/pypi/$package/json"); $response->is_success or die $response->status_line; my $distdata = $json->decode($response->decoded_content); return $distdata; } sub get_deps { my ($data) = @_; chdir "src/$data->{pkgreal}-$data->{pkgver}"; my $reqs = `python3 ./setup.py --requires`; my @reqs = split /\n/, $reqs; foreach my $i (0 .. $#reqs) { $reqs[$i] =~ s/(\(|\s+).*$//; $reqs[$i] = map_pypi_to_apk($reqs[$i]); } chdir '../..'; my $apk = read_file('APKBUILD'); $reqs = join ' ', @reqs; $apk =~ s/pymakedepends=""/pymakedepends="$reqs"/; # remove empty variables $apk =~ s/.*=""\n//g; open my $fh, '>:utf8', 'APKBUILD'; print $fh $apk; say "Requires: $reqs"; } 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}; sub usage { say <<'EOF'; Usage: apkbuild-pypi [create | check | recreate | upgrade | update] In the repository root: create : Creates an APKBUILD for In the package root: check : Reports current & latest version of the package recreate : Recreates the APKBUILD upgrade : Upgrades to the latest version of the package update : Updates APKBUILD metadata EOF } if (! defined $ARGV[0]) { die usage; } elsif ($ARGV[0] eq 'create') { my $package = $ARGV[1]; $package or die usage; my $distdata = get_data($package); my $apkname = map_pypi_to_apk($package); mkdir $apkname; chdir $apkname; my $data = write_apkbuild($distdata, undef); prepare_tree; get_deps($data); } elsif ($ARGV[0] eq 'recreate') { my $apkbuild = read_apkbuild; if (! defined $apkbuild->{_pkgreal}) { $apkbuild->{_pkgreal} = find_package_name($apkbuild); } my $distdata = get_data($apkbuild->{_pkgreal}); my $pkgver = $distdata->{info}{version} =~ s/^[^0-9]+//r; if ($pkgver ne $apkbuild->{pkgver}) { #Reset pkgrel on upgrade on recreate say "Upgrading PyPI module from $apkbuild->{pkgver} to $pkgver"; $apkbuild->{pkgrel}=0; } my $data = write_apkbuild($distdata, $apkbuild); prepare_tree; get_deps($data); } elsif ($ARGV[0] eq 'upgrade') { my $apkbuild = read_apkbuild; if (! defined $apkbuild->{_pkgreal}) { $apkbuild->{_pkgreal} = find_package_name($apkbuild); } my $distdata = get_data($apkbuild->{_pkgreal}); my $pkgver = $distdata->{info}{version}; if ($pkgver ne $apkbuild->{pkgver}) { say "Upgrading PyPI package from $apkbuild->{pkgver} to $pkgver"; my $text = read_file('APKBUILD'); $text =~ s/^(pkgver)=.*$/$1=$pkgver/mg or die "Can't find pkgver line in APKBUILD"; $text =~ s/^(pkgrel)=.*$/$1=0/mg or die "Can't find pkgrel line in APKBUILD"; open my $fh, '>:utf8', 'APKBUILD'; print $fh $text; close $fh; } else { say "Already up to date with PyPI"; } } elsif ($ARGV[0] eq 'check') { my $apkbuild = read_apkbuild; if (! defined $apkbuild->{_pkgreal}) { $apkbuild->{_pkgreal} = find_package_name($apkbuild); } my $distdata = get_data($apkbuild->{_pkgreal}); my $pkgver = $distdata->{info}{version}; say "$apkbuild->{pkgname}: Latest version: $pkgver Packaged version: $apkbuild->{pkgver}"; if ($pkgver ne $apkbuild->{pkgver}) { exit(1); } } elsif ($ARGV[0] eq 'update') { prepare_tree; } else { die usage; }