apkbuild-cpan.in 10.8 KB
Newer Older
1 2 3 4 5
#!/usr/bin/perl
# apk add perl-libwww perl-json

use strict;
use warnings;
6 7 8 9
use 5.016;
use feature "switch";
no if $] >= 5.018, warnings => "experimental::smartmatch";

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
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';
27 28
# Automatically generated by apkbuild-cpan, template 2
[% authors %]
29 30 31 32 33
pkgname=[% pkgname %]
_pkgreal=[% pkgreal %]
pkgver=[% pkgver %]
pkgrel=0
pkgdesc="Perl module for [% pkgreal %]"
Timothy Legge's avatar
Timothy Legge committed
34
url="https://metacpan.org/release/[% pkgreal %]/"
35 36 37 38
arch="noarch"
license="GPL PerlArtistic"
cpandepends=""
cpanmakedepends=""
39
cpancheckdepends=""
40 41
depends="$cpandepends"
makedepends="perl-dev $cpanmakedepends"
42
checkdepends="$cpancheckdepends"
43 44
subpackages="$pkgname-doc"
source="[% source %]"
45
builddir="$srcdir/$_pkgreal-$pkgver"
46 47

prepare() {
tmpfile's avatar
tmpfile committed
48
	default_prepare
49

50
	cd "$builddir"
51
	if [ -e Build.PL ]; then
tmpfile's avatar
tmpfile committed
52
		perl Build.PL installdirs=vendor
53
	else
54
		PERL_MM_USE_DEFAULT=1 perl -I. Makefile.PL INSTALLDIRS=vendor
55 56 57 58 59 60 61 62 63 64 65
	fi
}

build() {
	:
}

package() {
	:
}

66 67 68 69
check() {
	:
}

70 71 72 73 74 75 76
EOF

our $ua = LWP::UserAgent->new();
our $json = JSON->new;
$ua->env_proxy;
$ua->conn_cache(LWP::ConnCache->new());

77
sub read_file {
78 79 80 81 82 83 84 85
	my ($filename) = @_;
	local $/;
	open my $fh, "<", $filename or die "could not open $filename: $!";
	return <$fh>;
}

sub read_assignments_from_file {
	my ($filename) = @_;
86
	return () if ( ! -e $filename );
87 88 89 90
	my $text = read_file($filename);
	my %sline = $text =~ /^(\w+)\s*=\s*([^\"\n]*)$/mg;
	my %mline = $text =~ /^(\w+)\s*=\s*\"([^\"]*)\"$/mg;
	my %hash = ( %sline, %mline );
91 92 93 94

	my $authors = join("\n", $text =~ /^# Contributor: .*$/mg, $text =~ /^# Maintainer: .*$/mg);
	$hash{'authors'} = $authors if length($authors) > 1;

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
	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 {
111
	my ($distdata, $authors) = @_;
112

Timothy Legge's avatar
Timothy Legge committed
113
	my $cpanid = $distdata->{releases}[0]->{id};
114
	$cpanid = substr($cpanid, 0, 1) . "/" . substr($cpanid, 0, 2) . "/$cpanid";
115

116
	my %repl = (
117
		authors  => ($authors or "# Contributor: $packager\n# Maintainer: $packager"),
Timothy Legge's avatar
Timothy Legge committed
118 119
		pkgname  => map_cpan_to_apk($distdata->{metadata}{name}),
		pkgreal  => $distdata->{metadata}{name},
120
		pkgver   => $distdata->{metadata}{version},
Timothy Legge's avatar
Timothy Legge committed
121
		source   => $distdata->{download_url},
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
	);
	$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
Timothy Legge's avatar
Timothy Legge committed
146
		$response = $ua->get("https://fastapi.metacpan.org/module/$module");
147 148 149
		$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";
Timothy Legge's avatar
Timothy Legge committed
150
		$distfiles->{$moddata->{distribution}} = $moddata;
151 152 153
	}

	# map package names to alpine packages
154
	foreach ( keys %{ $distfiles } ) {
Timothy Legge's avatar
Timothy Legge committed
155
		$response = $ua->get("https://fastapi.metacpan.org/releases/$_");
156 157 158 159 160 161 162
		$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 ";
	}
163 164
	$deps =~ s/\h+/ /g;
	$deps =~ s/ $//;
165 166 167 168 169 170 171 172 173 174 175 176
	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'} . "/";
177
	my $prepare_func;
178
	my $build_func;
179
	my $check_func;
180 181 182 183 184 185
	my $package_func;

	my $text = read_file "APKBUILD";
	if (-e "$metaprefix/Build.PL" ) {
		$prepare_func = <<'EOF';
prepare() {
tmpfile's avatar
tmpfile committed
186
	default_prepare
187

188
	cd "$builddir"
189
	export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
tmpfile's avatar
tmpfile committed
190
	perl Build.PL installdirs=vendor
191 192 193 194
}
EOF
		$build_func = <<'EOF';
build() {
195
	cd "$builddir"
196
	export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
197
	./Build
198 199 200 201
}
EOF
		$package_func = <<'EOF';
package() {
202
	cd "$builddir"
tmpfile's avatar
tmpfile committed
203
	./Build install destdir="$pkgdir"
204 205
	find "$pkgdir" \( -name perllocal.pod -o -name .packlist \) -delete
}
206 207 208 209 210 211
EOF
		$check_func = <<'EOF';
check() {
	cd "$builddir"
	./Build test
}
212 213 214 215
EOF
	} else {
		$prepare_func = <<'EOF';
prepare() {
tmpfile's avatar
tmpfile committed
216
	default_prepare
217

218
	cd "$builddir"
219
	export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
220
	PERL_MM_USE_DEFAULT=1 perl -I. Makefile.PL INSTALLDIRS=vendor
221 222 223 224
}
EOF
		$build_func = <<'EOF';
build() {
225
	cd "$builddir"
226
	export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
227
	make
228 229 230 231
}
EOF
		$package_func = <<'EOF';
package() {
232
	cd "$builddir"
tmpfile's avatar
tmpfile committed
233
	make DESTDIR="$pkgdir" install
234 235
	find "$pkgdir" \( -name perllocal.pod -o -name .packlist \) -delete
}
236 237 238 239 240 241 242
EOF
		$check_func = <<'EOF';
check() {
	cd "$builddir"
	export CFLAGS=$(perl -MConfig -E 'say $Config{ccflags}')
	make test
}
243 244 245 246 247 248 249 250 251
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";
252 253
	$text =~ s/^check\(\) \{.*?^\}\n/$check_func/smg or
		die "Can't replace check function APKBUILD";
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281

	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";
282
	say "Recommend: " . parse_deps $meta->effective_prereqs->requirements_for('runtime', 'recommends');
283

284
	my $makedeps = parse_deps($meta->effective_prereqs->requirements_for('build', 'requires'), $meta->effective_prereqs->requirements_for('build', 'recommends'));
285
	say "CPAN build deps: $makedeps";
286 287 288 289 290
	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";
291 292 293 294 295 296

	my $text = read_file "APKBUILD";
	if ($abstract) {
		$text =~ s/^pkgdesc=\"([^\"]*)\"$/pkgdesc=\"$abstract\"/mg or
			die "Can't find cpandepends line in APKBUILD";
	}
297 298 299 300
	if (length(`find $metaprefix -name '*.xs'`)) {
		$text =~ s/^arch=\"([^\"]*)\"$/arch="all"/mg or
			die "Can't find arch line in APKBUILD";
	}
301 302 303 304
	if ($license ne 'unknown') {
		$text =~ s/^license=\"([^\"]*)\"$/license=\"$license\"/mg or
			die "Can't find license line in APKBUILD";
	}
305 306 307 308
	$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";
309 310
	$text =~ s/^cpancheckdepends=\"([^\"]*)\"$/cpancheckdepends=\"$checkdeps\"/mg or
		die "Can't find cpancheckdepends line in APKBUILD";
311 312 313 314 315 316

	open my $fh, '>', "APKBUILD" or die;
	print $fh $text;
	close $fh;
}

317 318
sub get_data {
	my $apkbuild = read_apkbuild;
319
	$apkbuild->{_pkgreal} or die "Not apkbuild-cpan generated APKBUILD";
Timothy Legge's avatar
Timothy Legge committed
320
	my $response = $ua->get("https://fastapi.metacpan.org/release/$apkbuild->{_pkgreal}");
321 322 323 324 325 326 327
	$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);
}

328 329 330
my $abuild_conf = read_assignments_from_file("/etc/abuild.conf");
$packager = $abuild_conf->{PACKAGER} if $abuild_conf->{PACKAGER};

331 332 333
my $user_abuild_conf = read_assignments_from_file($ENV{"HOME"} . "/.abuild/abuild.conf");
$packager = $user_abuild_conf->{PACKAGER} if $user_abuild_conf->{PACKAGER};

334 335 336 337 338 339
given ( $ARGV[0] ) {
	when ("create") {
		my $module = $ARGV[1];
		my $response;
		$module or die "Module name is a mandatory argument";

Timothy Legge's avatar
Timothy Legge committed
340
		$response = $ua->get("https://fastapi.metacpan.org/module/$module");
341 342 343 344
		$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";

Timothy Legge's avatar
Timothy Legge committed
345
		$response = $ua->get("https://fastapi.metacpan.org/release/$moddata->{distribution}");
346 347 348 349
		$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";

Timothy Legge's avatar
Timothy Legge committed
350
		my $apkname = map_cpan_to_apk $distdata->{metadata}{name};
351 352 353 354 355 356 357 358
		mkdir $apkname;
		chdir $apkname;
		write_apkbuild($distdata);
		prepare_tree;
		update_functions;
		do_depends;
	}
	when ("recreate") {
359
		my ($apkbuild, $distdata) = get_data;
360
		write_apkbuild($distdata, $apkbuild->{authors});
361 362 363 364 365
		prepare_tree;
		update_functions;
		do_depends;
	}
	when ("upgrade") {
366
		my ($apkbuild, $distdata) = get_data;
367

368
		my $pkgver = $distdata->{metadata}{version};
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
		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";
		}
	}
386 387 388
	when ('check') {
		my ($apkbuild, $distdata) = get_data;
		my $pkgver = $distdata->{releases}[0]->{version};
389 390 391 392
		say "$apkbuild->{pkgname}: Latest version: $pkgver Packaged version: $apkbuild->{pkgver}";
		if ($pkgver ne $apkbuild->{pkgver}) {
			exit(1);
		}
393
	}
394 395 396 397 398
	when ("update") {
		prepare_tree;
		do_depends;
	}
	default {
399
		say "Usage: apkbuild-cpan [create <Module::Name> | check | recreate | update | upgrade]";
400 401 402
		exit;
	}
}