Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A script that will create updated "starter" Makefiles for OpenBSD perl ports available on the CPAN. Should only require things in the base system, but does like an up-to-date /usr/ports path.
#!/usr/bin/perl
use strict;
use warnings;
# Copyright (c) 2013 Andrew Fresh <andrew@afresh1.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use 5.010;
use Carp;
use Cwd;
use File::Path qw( make_path );
use JSON::PP qw( decode_json );
use constant (
TABLENGTH => 8,
);
my $base_dir = '/tmp/generated_ports';
my $base_url = 'http://api.metacpan.org/v0';
my @get_cmd = qw( lynx --dump );
my $makefile_template = '/usr/ports/infrastructure/templates/Makefile.template';
my @distributions = @ARGV;
# A useful command:
# diff -Pbru /usr/ports/ . | grep -v 'Only in' | less
foreach my $distribution (@distributions) {
state %seen;
next if $seen{$distribution}++;
say "Making $distribution";
my $dist_info = get_dist_info($distribution);
next if dist_is_up_to_date($dist_info);
make_port($dist_info);
}
## no critic 'subroutine prototypes'
sub uniq(@) { my %seen; return grep { !$seen{$_}++ } @_; }
sub make_port {
my ($di) = @_;
my $old_cwd = getcwd();
my $dir = make_portdir($di);
chdir $dir or croak "Couldn't chdir $dir: $!";
make_makefile($di);
make_descr( $di ) if $dir =~ m{/CPAN/}x; # Only works most of the time
chdir $old_cwd or croak "Couldn't chdir $old_cwd: $!";
}
sub find_portdir { return "$base_dir/" . port_for_dist(@_) }
sub make_portdir {
my ($di) = @_;
my $port = port_for_dist($di);
my $port_dir = find_portdir($di);
make_path($port_dir) or die "Couldn't make_path $port_dir: $!"
unless -e $port_dir;
if (-e "/usr/ports/$port") {
(my $dst = $port_dir) =~ s{/[^/]+$}{};
cp('-r', "/usr/ports/$port/", $dst);
rename( "$port_dir/Makefile", "$port_dir/Makefile.orig" );
rename( "$port_dir/pkg/PLIST", "$port_dir/pkg/PLIST.orig" );
unlink( "$port_dir/distinfo" );
}
return $port_dir;
}
sub parse_makefile {
my ($path) = @_;
return unless -e $path;
my @makefile;
my %vars;
my $parse = sub {
state $line = '';
$line .= shift;
return if /\\\n$/x;
if ($line =~ /^(\#?) \s* ([A-Z_]+) \s* = (\s*) (.*)/xms) {
my ($comment, $key, $spaces, $value) = ($1, $2, $3, $4);
my $tabs = $spaces =~ tr/\t/\t/;
push @makefile, {
key => $key,
value => $value,
tabs => $tabs,
commented => $comment ? 1 : 0,
};
$vars{$key} = $value;
}
else {
push @makefile, $line;
}
$line = '';
};
open my $fh, '<', $path or croak("Couldn't open $path: $!");
$parse->($_) while <$fh>;
close $fh;
return {
makefile => \@makefile,
vars => \%vars,
}
}
sub make_makefile {
my ($di) = @_;
my $port = port_for_dist($di);
my ($category) = split m{/}x, $port;
my $old_port = parse_makefile("Makefile.orig") || {};
my @makefile = @{ $old_port->{makefile} || [] };
@makefile = (
'# $OpenBSD$' . "\n",
grep { $_ !~ /^\#/x }
@{ parse_makefile($makefile_template)->{makefile} }
) unless @makefile;
my %configs = (
COMMENT => "$di->{abstract}\n",
MODULES => "cpan\n",
CATEGORIES => "$category\n",
MAINTAINER => "ports\@openbsd.org\n",
CPAN_AUTHOR => "$di->{author}\n",
%{ $old_port->{vars} || {} },
);
# foreach my $config ( keys %configs ) {
# delete $configs{$config} if $configs{$config} =~ /\?\?\?/;
# my $value = port_value( $di, $config );
# $configs{$config} = "$value\n" if $value;
# }
delete $configs{HOMEPAGE}
if $configs{HOMEPAGE}
&& $configs{HOMEPAGE} =~ /search\.cpan\.org/x;
# Picked up from elsewhere
$configs{MODULES} =~ s/ perl// if $configs{MODULES};
$configs{CATEGORIES} =~ s/ perl5// if $configs{CATEGORIES};
$configs{MAINTAINER} = "Andrew Fresh <andrew\@cpan.org>\n"
if !$configs{MAINTAINER}
|| $configs{MAINTAINER} =~ /ports\@openbsd.org/x;
my $distname = $di->{archive};
if ( $distname =~ s/\.tar\.gz$//x ) {
# do nothing
# Eventually, if we strip other types
# It could become an EXTRACT_SUFX
}
$configs{DISTNAME} = $distname . "\n";
makefile_depends( $di, \%configs );
my $license = join ' ',
ref $di->{license} ? @{ $di->{license} || [] } : ( $di->{license} );
$configs{PERMIT_PACKAGE_CDROM} = "Yes\n";
open my $fh, '>', 'Makefile' or die "Couldn't open Makefile: $!";
my $last_blank;
foreach (@makefile) {
my $is_blank = /^[\s\n]*$/xms;
next if $is_blank && $last_blank;
if (/\.include \s+ <bsd.port.mk>/x) {
foreach my $key (sort keys %configs) {
my $value = $configs{$key};
next unless $value;
print $fh "$key = $value";
}
}
if ( ref $_ eq 'HASH' ) {
my $key = $_->{key};
my $value = delete $configs{$key};
next unless $value;
my $tabs = "\t" x ($_->{tabs} || 1);
print $fh "# $license\n"
if $key eq 'PERMIT_PACKAGE_CDROM' && $license;
print $fh "$key =$tabs$value";
}
else {
print $fh $_;
}
$last_blank = $is_blank;
}
close $fh;
}
sub makefile_depends {
my ($di, $config) = @_;
my %prereqs = %{ $di->{metadata}->{prereqs} || {} };
#CONFIGURE_STYLE=modbuild
my %depend_map = (
BUILD_DEPENDS => [ 'configure', 'build' ],
RUN_DEPENDS => ['runtime'],
TEST_DEPENDS => ['test'],
);
my %depends;
foreach my $type ( sort keys %depend_map ) {
my @ports;
foreach my $key ( @{ $depend_map{$type} } ) {
next unless $prereqs{$key};
my $requires = $prereqs{$key}{requires};
foreach my $module ( sort keys %{ $requires || {} } ) {
next if module_is_in_base($module);
my $dist = get_dist_for_module($module);
my $port = port_for_dist($dist);
say ". $port [$module]";
push @{ $depends{$type} }, $port;
# Silly place to recurse . . .
push @distributions, $dist;
}
}
}
# People like to hide test depends in build depends because
# some tools don't make the distinction.
$depends{TEST_DEPENDS} = delete $depends{BUILD_DEPENDS}
if grep { /Test/ } @{ $depends{BUILD_DEPENDS} || [] };
foreach my $type ( sort keys %depends ) {
my @ports = @{ $depends{$type} };
my $tabs = 2 - int( length("$type =") / TABLENGTH );
$config->{$type} = ( "\t" x $tabs )
. join( " \\\n\t\t", sort( uniq( @ports ) ) ) . "\n";
}
}
sub make_descr {
my ($di) = @_;
make_path('pkg') unless -e 'pkg';
my @readme = split /\n/x, get_readme_for_dist( $di );
my $descr = q{};
my $in_descr = 0;
foreach (@readme) {
if (/^(\S+)/x) {
last if $in_descr;
$in_descr = $1 eq 'DESCRIPTION';
next;
}
if ($in_descr) {
s/^\s+//x;
$descr .= "$_\n";
}
}
open my $fh, '>', 'pkg/DESCR' or die "Couldn't open readme: $!";
print $fh $descr;
close $fh;
}
sub port_for_dist {
my ($dist) = @_;
$dist = ref $dist ? $dist->{distribution} : $dist;
$dist = {
MailTools => 'Mail-Tools',
TimeDate => 'Time-TimeDate',
'YAML-LibYAML' => 'YAML-XS',
Mojolicious => 'Mojo',
'libwww-perl' => 'libwww',
}->{$dist} || $dist;
my ($dir) = glob("/usr/ports/*/p5-$dist");
$dir = "CPAN/p5-$dist" unless $dir && $dir !~ /\*/;
$dir =~ s{^/usr/ports/+}{};
return $dir;
}
sub module_is_in_base {
my ($module) = @_;
return 1 if $module eq 'perl';
my $module_path = $module;
$module_path =~ s{::}{/}gx;
$module_path .= '.pm';
foreach (@INC) {
next unless m{/usr/libdata/}x;
my $path = "$_/$module_path";
return 1 if -e $path;
}
return 0;
}
sub dist_is_up_to_date {
my ($di) = @_;
my $current_distname = port_value( $di, 'DISTFILES' );
return $current_distname eq $di->{archive};
}
sub get_dist_info {
my ($distribution) = @_;
$distribution = get_dist_for_module($distribution)
if $distribution =~ /::/x;
return _get_json("release/$distribution");
}
sub get_dist_for_module {
my ($module) = @_;
return _get_json("module/$module?fields=distribution")->{distribution};
}
sub get_readme_for_dist {
my ($di) = @_;
my $path = join '/', 'source', @{$di}{qw( author name )}, 'README';
return _get($path);
}
sub _get {
my ($url) = @_;
for ( 0 .. 2 ) {
open my $fh, '-|', @get_cmd, "$base_url/$url" or die "$!";
my $content = do { local $/ = undef; <$fh> };
close $fh;
return $content if $content;
sleep 2 * $_;
}
croak "Failed to get $base_url/$url";
}
sub _get_json { return decode_json( _get(@_) ) }
sub port_value {
my ( $di, $variable ) = @_;
my $value = _make_in_port( $di, "show=$variable" );
chomp $value;
return $value;
}
sub _make_in_port {
my ( $di, @args ) = @_;
my $old_cwd = getcwd();
my $port = port_for_dist($di);
my $dir = "/usr/ports/$port";
return '' unless -e $dir;
chdir $dir or die "Couldn't chdir $dir: $!";
open my $fh, '-|', 'make', @args or die "Couldn't launch make";
my $output = do { local $/ = undef; <$fh> };
close $fh;
chdir $old_cwd or die "Couldn't chdir $old_cwd: $!";
return $output;
}
sub cp {
my (@args) = @_;
system('/bin/cp', @args);
}
#!/bin/sh
# I use this to try to make sure all BUILD and TEST DEPENDS are
# correctly setup. It does remove all packages, so you will want
# to make sure to run it on a machine dedicated to testing.
#
# If your ports have updated requirements, you may need to add
# this to your /etc/mk.conf
# PORTSDIR_PATH=${PORTSDIR}/mystuff:${PORTSDIR}
root=`pwd`
objdir=
for d in `printf "%s\n" */*/ |
perl -MList::Util=shuffle -e 'print shuffle(<>)'`; do
sudo pkg_delete /var/db/pkg/*
log=`echo $d | tr '/' '_' | sed -e 's/_$//'`.log
(
cd $root/$d
[ "$objdir" ] || objdir=`make show=BSDOBJDIR`
[ "$objdir" ] || exit
rm -rf $objdir/*
make fetch
[ -e distinfo ] || make makesum
[ -e pkg/PLIST ] || make update-plist
make clean=depends
make test
make clean=depends
) 2>&1 | tee $log
done
sudo pkg_delete /var/db/pkg/*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.