ports/infrastructure/lib/OpenBSD/PortGen/Port/Go.pm

321 lines
7.3 KiB
Perl

# $OpenBSD: Go.pm,v 1.13 2023/08/21 13:45:54 paco Exp $
#
# Copyright (c) 2019 Aaron Bieber <abieber@openbsd.org>
#
# 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.
package OpenBSD::PortGen::Port::Go;
use 5.028;
use utf8;
use warnings;
use strict;
use warnings qw(FATAL utf8); # fatalize encoding glitches
use open qw(:std :encoding(UTF-8)); # undeclared streams in UTF-8
use OpenBSD::PackageName;
use OpenBSD::PortGen::Utils qw( fetch module_in_ports );
use parent 'OpenBSD::PortGen::Port';
use Carp;
use Cwd;
use File::Temp qw/ tempdir /;
use OpenBSD::PortGen::Dependency;
my $license_map = {
'BSD-2-Clause' => 'BSD',
'BSD-3-Clause' => 'BSD3',
};
sub ecosystem_prefix
{
my $self = shift;
return '';
}
sub base_url
{
my $self = shift;
return 'https://proxy.golang.org/';
}
sub _go_lic_info
{
my ( $self, $module ) = @_;
my $html = fetch("https://pkg.go.dev/mod/" . $module);
my $license = "unknown";
if ($html =~ m/<a.+tab=licenses.+>(.+)<\/a>/) {
$license = $1;
$license = $license_map->{$license} || $license;
}
return $license;
}
sub _go_determine_name
{
# Some modules end in "v1" or "v2", if we find one of these, we need
# to set PKGNAME to something up a level
my ( $self, $module ) = @_;
my $json = $self->get_ver_info($module);
$module = $json->{Module};
if ($module =~ m/v\d$/) {
$json->{Name} = ( split '/', $module )[-2];
} else {
$json->{Name} = ( split '/', $module )[-1];
}
return $json;
}
sub get_dist_info
{
my ( $self, $module ) = @_;
my $json = $self->_go_determine_name($module);
my ($dist, $mods) = $self->_go_mod_info($json);
$json->{License} = $self->_go_lic_info($json->{Module});
$json->{Dist} = $dist if @$dist > 0;
$json->{Mods} = $mods if @$mods > 0;
return $json;
}
sub _run
{
my ($self, $dir, @cmd) = @_;
my $fh;
my $pid = open($fh, "-|");
if (!defined $pid) {
die "unable to fork";
}
if ($pid == 0) {
chdir $dir or die "Unable to chdir '$dir': $!";
$ENV{GOPATH} = "$dir/go";
$ENV{GO111MODULE} = "on";
# Outputs: "dep version"
$DB::inhibit_exit = 0;
exec @cmd;
die "exec didn't work: $?";
}
my @output = <$fh>;
chomp @output;
my $c = join(" ", @cmd);
close $fh or die "Unable to close pipe '$c': $!";
return @output;
}
sub _go_mod_info
{
my ($self, $json) = @_;
my $dir = tempdir(CLEANUP => 0);
my $mod = $self->get($self->_go_mod_normalize($json->{Module}) . "/\@v/$json->{Version}.mod");
croak "Can not find go.mod file for $json->{Module}" if $mod eq "";
my ($module) = $mod =~ /\bmodule\s+(.*?)\s/;
unless ( $json->{Module} eq $module ) {
my $msg = "Module $json->{Module} doesn't match $module";
croak $msg;
}
open my $fh, '>', $dir . "/go.mod" or die $!;
print $fh $mod;
close $fh;
# Outputs: "dep version"
my @raw_deps = $self->_run($dir, qw(go list -mod=mod -m all));
my @deps;
my $all_deps = {};
foreach my $dep (@raw_deps) {
next if $dep eq $json->{Module};
if ($dep =~ m/=>/) {
foreach my $d (split(/ => /, $dep)) {
my $smod = $self->_go_mod_normalize($d);
push @deps, $smod unless defined $all_deps->{$smod};
$all_deps->{$smod} = 1;
}
} else {
my $nmod = $self->_go_mod_normalize($dep);
push @deps, $nmod unless defined $all_deps->{$nmod};
$all_deps->{$nmod} = 1;
}
}
# Outputs: "dep@version subdep@version"
my @raw_mods = $self->_run($dir, qw(go mod graph));
my @mods;
foreach my $mod (@raw_mods) {
foreach my $m (split(/ /, $mod)) {
$m =~ s/@/ /;
next if $m eq $json->{Module};
next if $m =~ /^(go|toolchain) /;
$m = $self->_go_mod_normalize($m);
if (! defined $all_deps->{$m}) {
push @mods, $m;
$all_deps->{$m} = 1;
}
}
}
foreach my $fl ( \@deps, \@mods ) {
next unless @$fl > 0; # if there aren't any, don't try
my @s = map {
my @f = split(/ /, $_);
[$f[0], $f[1]];
} @$fl;
my ($length) = sort { $b <=> $a } map { length $_->[0] } @s;
my $n = ( 1 + int $length / 8 );
@s = map {
my $tabs = "\t" x ( $n - int( length($_->[0]) / 8 ) );
"\t$_->[0]$tabs $_->[1]"
} @s;
@$fl = @s;
}
@deps = sort @deps;
@mods = sort @mods;
return ( \@deps, \@mods );
}
sub _go_mod_normalize
{
my ( $self, $line) = @_;
chomp $line;
$line =~ s/\p{Upper}/!\L$&/g;
$line =~ s/\s+/ /g;
return $line;
}
sub get_ver_info
{
my ( $self, $module ) = @_;
# We already ran, skip re-running.
return $self->{version_info} if defined $self->{version_info};
my $version_list = do { local $@; eval { local $SIG{__DIE__};
$self->get( $module . '/@v/list' ) } };
# Versions can be specified on the command line with a
# '@<version>' suffix, which defaults to '@latest':
my $at_version = 'latest';
if ($module =~ /@/) {
($module, $at_version) = split(/@/, $module);
}
my $version_info;
if ($version_list) {
my %v = ( o => OpenBSD::PackageName::version->from_string("v0.0.0") );
for my $v ( split "\n", $version_list ) {
my $o = OpenBSD::PackageName::version->from_string($v);
if ( $v{o}->compare($o) == -1 ) {
%v = ( Version => $v, o => $o );
}
}
if ($v{Version}) {
$version_info = { Module => $module, Version => $v{Version} };
}
else {
croak "Unable to determine version for $module!";
}
}
else {
my $endpoint = '/@latest';
if ($at_version ne 'latest') {
$endpoint = '/@v/' . "${at_version}.info";
}
$version_info = $self->get_json( $self->_go_mod_normalize($module) . $endpoint );
$version_info->{Module} = $module;
}
return $self->{version_info} = $version_info;
}
sub name_new_port
{
my ( $self, $di ) = @_;
my $name = $di->{Name};
$name = $self->SUPER::name_new_port($name);
if ( my $p = module_in_ports( $name, 'go-' ) || module_in_ports( $name, '' ) ) {
$name = $p;
} else {
$name = "go/$name" unless $name =~ m{/};
}
return $name;
}
sub fill_in_makefile
{
my ( $self, $di, $vi ) = @_;
$self->set_modules('lang/go');
$self->set_comment("todo");
$self->set_descr("TODO");
$self->set_license($di->{License});
$self->set_other( MODGO_MODNAME => $di->{Module} );
$self->set_other( MODGO_VERSION => $di->{Version} . ' # this goes in Makefile, not modules.inc' );
$self->set_distname($di->{Name} . '-${MODGO_VERSION}');
my @parts = split("-", $di->{Version});
if (@parts > 1) {
$self->set_pkgname($di->{Name} . "-" . $parts[1])
if $parts[1] =~ m/\d{6}/;
} else {
$parts[0] =~ s/^v//;
$self->set_pkgname($di->{Name} . "-" . $parts[0]);
}
$self->set_other( MODGO_MODULES => "\\\n" . join(" \\\n", @{$di->{Dist}})) if $di->{Dist};
$self->set_other( MODGO_MODFILES => "\\\n" . join(" \\\n", @{$di->{Mods}})) if $di->{Mods};
}
sub try_building
{
my $self = shift;
$self->make_fake();
}
sub postextract
{
}
sub get_deps
{
my ( $self, $di, $wrksrc ) = @_;
my $deps = OpenBSD::PortGen::Dependency->new();
return $deps->format;
}
sub get_config_style
{
}
1;