Rewrite the CPAN generator to Perl
Also: - It's now installable by doing "nix-env -i nix-generate-from-cpan". - It maps dependencies to the correct attribute (e.g. HTML::HeadParser is mapped to HTMLParser). - It automatically selects buildPerlPackage or buildPerlModule. - It's documented in the manual.
This commit is contained in:
parent
a79076b7aa
commit
629daa2102
6 changed files with 231 additions and 123 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -2,6 +2,8 @@
|
|||
,*
|
||||
.*.swp
|
||||
.*.swo
|
||||
cpan-info
|
||||
cpan_tmp/
|
||||
result
|
||||
doc/NEWS.html
|
||||
doc/NEWS.txt
|
||||
doc/manual.html
|
||||
doc/manual.pdf
|
||||
|
|
|
@ -21,7 +21,7 @@ standard <varname>Makefile.PL</varname>. It’s implemented in <link
|
|||
xlink:href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/perl-modules/generic"><filename>pkgs/development/perl-modules/generic</filename></link>.</para>
|
||||
|
||||
<para>Perl packages from CPAN are defined in <link
|
||||
xlink:href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/perl-packages.nix"><filename>pkgs/perl-packages.nix</filename></link>,
|
||||
xlink:href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/perl-packages.nix"><filename>pkgs/top-level/perl-packages.nix</filename></link>,
|
||||
rather than <filename>pkgs/all-packages.nix</filename>. Most Perl
|
||||
packages are so straight-forward to build that they are defined here
|
||||
directly, rather than having a separate function for each package
|
||||
|
@ -151,6 +151,43 @@ ClassC3Componentised = buildPerlPackage rec {
|
|||
|
||||
</para>
|
||||
|
||||
<section><title>Generation from CPAN</title>
|
||||
|
||||
<para>Nix expressions for Perl packages can be generated (almost)
|
||||
automatically from CPAN. This is done by the program
|
||||
<command>nix-generate-from-cpan</command>, which can be installed
|
||||
as follows:</para>
|
||||
|
||||
<screen>
|
||||
$ nix-env -i nix-generate-from-cpan
|
||||
</screen>
|
||||
|
||||
<para>This program takes a Perl module name, looks it up on CPAN,
|
||||
fetches and unpacks the corresponding package, and prints a Nix
|
||||
expression on standard output. For example:
|
||||
|
||||
<screen>
|
||||
$ nix-generate-from-cpan XML::Simple
|
||||
XMLSimple = buildPerlPackage {
|
||||
name = "XML-Simple-2.20";
|
||||
src = fetchurl {
|
||||
url = mirror://cpan/authors/id/G/GR/GRANTM/XML-Simple-2.20.tar.gz;
|
||||
sha256 = "5cff13d0802792da1eb45895ce1be461903d98ec97c9c953bc8406af7294434a";
|
||||
};
|
||||
propagatedBuildInputs = [ XMLNamespaceSupport XMLSAX XMLSAXExpat ];
|
||||
meta = {
|
||||
description = "Easily read/write XML (esp config files)";
|
||||
license = "perl";
|
||||
};
|
||||
};
|
||||
</screen>
|
||||
|
||||
The output can be pasted into
|
||||
<filename>pkgs/top-level/perl-packages.nix</filename> or wherever else
|
||||
you need it.</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
#! /bin/sh -e
|
||||
|
||||
name="$1"
|
||||
[ -n "$name" ] || { echo "no name"; exit 1; }
|
||||
|
||||
cpan -D "$name" > cpan-info
|
||||
|
||||
url="$(echo $(cat cpan-info | sed '6!d'))"
|
||||
[ -n "$url" ] || { echo "no URL"; exit 1; }
|
||||
url="mirror://cpan/authors/id/$url"
|
||||
echo "URL = $url" >&2
|
||||
|
||||
version=$(cat cpan-info | grep 'CPAN: ' | awk '{ print $2 }')
|
||||
echo "VERSION = $version"
|
||||
|
||||
declare -a xs=($(PRINT_PATH=1 nix-prefetch-url "$url"))
|
||||
hash=${xs[0]}
|
||||
path=${xs[1]}
|
||||
echo "HASH = $hash" >&2
|
||||
|
||||
namedash="$(echo $name | sed s/::/-/g)-$version"
|
||||
|
||||
attr=$(echo $name | sed s/:://g)
|
||||
|
||||
rm -rf cpan_tmp
|
||||
mkdir cpan_tmp
|
||||
tar xf "$path" -C cpan_tmp
|
||||
|
||||
shopt -s nullglob
|
||||
meta=$(echo cpan_tmp/*/META.json)
|
||||
if [ -z "$meta" ]; then
|
||||
yaml=$(echo cpan_tmp/*/META.yml)
|
||||
[ -n "$yaml" ] || { echo "no meta file"; exit 1; }
|
||||
meta=$(echo $yaml | sed s/\.yml$/.json/)
|
||||
perl -e '
|
||||
use YAML;
|
||||
use JSON;
|
||||
local $/;
|
||||
$x = YAML::Load(<>);
|
||||
print encode_json $x;
|
||||
' < $yaml > $meta
|
||||
fi
|
||||
|
||||
description="$(json abstract < $meta | perl -e '$x = <>; print uc(substr($x, 0, 1)), substr($x, 1);')"
|
||||
homepage="$(json resources.homepage < $meta)"
|
||||
if [ -z "$homepage" ]; then
|
||||
#homepage="$(json meta-spec.url < $meta)"
|
||||
true
|
||||
fi
|
||||
|
||||
license="$(json license < $meta | json -a 2> /dev/null || true)"
|
||||
if [ -z "$license" ]; then
|
||||
license="$(json -a license < $meta)"
|
||||
fi
|
||||
license="$(echo $license | sed s/perl_5/perl5/)"
|
||||
|
||||
f() {
|
||||
local type="$1"
|
||||
perl -e '
|
||||
use JSON;
|
||||
local $/;
|
||||
$x = decode_json <>;
|
||||
if (defined $x->{prereqs}) {
|
||||
$x2 = $x->{prereqs}->{'$type'}->{requires};
|
||||
} elsif ("'$type'" eq "runtime") {
|
||||
$x2 = $x->{requires};
|
||||
} elsif ("'$type'" eq "configure") {
|
||||
$x2 = $x->{configure_requires};
|
||||
} elsif ("'$type'" eq "build") {
|
||||
$x2 = $x->{build_requires};
|
||||
}
|
||||
foreach my $y (keys %{$x2}) {
|
||||
next if $y eq "perl";
|
||||
eval "use $y;";
|
||||
if (!$@) {
|
||||
print STDERR "skipping Perl-builtin module $y\n";
|
||||
next;
|
||||
}
|
||||
print $y, "\n";
|
||||
};
|
||||
' < $meta | sed s/:://g
|
||||
}
|
||||
|
||||
confdeps=$(f configure)
|
||||
builddeps=$(f build)
|
||||
testdeps=$(f test)
|
||||
runtimedeps=$(f runtime)
|
||||
|
||||
buildInputs=$(echo $(for i in $confdeps $builddeps $testdeps; do echo $i; done | sort | uniq))
|
||||
propagatedBuildInputs=$(echo $(for i in $runtimedeps; do echo $i; done | sort | uniq))
|
||||
|
||||
echo "===" >&2
|
||||
|
||||
cat <<EOF
|
||||
$attr = buildPerlPackage {
|
||||
name = "$namedash";
|
||||
src = fetchurl {
|
||||
url = $url;
|
||||
sha256 = "$hash";
|
||||
};
|
||||
EOF
|
||||
if [ -n "$buildInputs" ]; then
|
||||
cat <<EOF
|
||||
buildInputs = [ $buildInputs ];
|
||||
EOF
|
||||
fi
|
||||
if [ -n "$propagatedBuildInputs" ]; then
|
||||
cat <<EOF
|
||||
propagatedBuildInputs = [ $propagatedBuildInputs ];
|
||||
EOF
|
||||
fi
|
||||
cat <<EOF
|
||||
meta = {
|
||||
homepage = $homepage;
|
||||
description = "$description";
|
||||
license = "$license";
|
||||
};
|
||||
};
|
||||
EOF
|
||||
|
22
maintainers/scripts/nix-generate-from-cpan.nix
Normal file
22
maintainers/scripts/nix-generate-from-cpan.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{ stdenv, makeWrapper, perl, perlPackages }:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "nix-generate-from-cpan-1";
|
||||
|
||||
buildInputs = [ makeWrapper perl perlPackages.YAML perlPackages.JSON ];
|
||||
|
||||
unpackPhase = "true";
|
||||
buildPhase = "true";
|
||||
|
||||
installPhase =
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
cp ${./nix-generate-from-cpan.pl} $out/bin/nix-generate-from-cpan
|
||||
wrapProgram $out/bin/nix-generate-from-cpan --set PERL5LIB $PERL5LIB
|
||||
'';
|
||||
|
||||
meta = {
|
||||
maintainers = [ stdenv.lib.maintainers.eelco ];
|
||||
description = "Utility to generate a Nix expression for a Perl package from CPAN";
|
||||
};
|
||||
}
|
162
maintainers/scripts/nix-generate-from-cpan.pl
Executable file
162
maintainers/scripts/nix-generate-from-cpan.pl
Executable file
|
@ -0,0 +1,162 @@
|
|||
#! /run/current-system/sw/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use CPANPLUS::Backend;
|
||||
use YAML;
|
||||
use JSON;
|
||||
|
||||
my $module_name = $ARGV[0];
|
||||
die "syntax: $0 <MODULE-NAME>\n" unless defined $module_name;
|
||||
|
||||
my $cb = CPANPLUS::Backend->new;
|
||||
|
||||
my @modules = $cb->search(type => "name", allow => [$module_name]);
|
||||
die "module $module_name not found\n" if scalar @modules == 0;
|
||||
die "multiple packages that match module $module_name\n" if scalar @modules > 1;
|
||||
my $module = $modules[0];
|
||||
|
||||
sub pkg_to_attr {
|
||||
my ($pkg_name) = @_;
|
||||
my $attr_name = $pkg_name;
|
||||
$attr_name =~ s/-\d.*//; # strip version
|
||||
return "LWP" if $attr_name eq "libwww-perl";
|
||||
$attr_name =~ s/-//g;
|
||||
return $attr_name;
|
||||
}
|
||||
|
||||
sub get_pkg_name {
|
||||
my ($module) = @_;
|
||||
my $pkg_name = $module->package;
|
||||
$pkg_name =~ s/\.tar.*//;
|
||||
$pkg_name =~ s/\.zip//;
|
||||
return $pkg_name;
|
||||
}
|
||||
|
||||
my $pkg_name = get_pkg_name $module;
|
||||
my $attr_name = pkg_to_attr $pkg_name;
|
||||
|
||||
print STDERR "attribute name: ", $attr_name, "\n";
|
||||
print STDERR "module: ", $module->module, "\n";
|
||||
print STDERR "version: ", $module->version, "\n";
|
||||
print STDERR "package: ", $module->package, , " (", $pkg_name, ", ", $attr_name, ")\n";
|
||||
print STDERR "path: ", $module->path, "\n";
|
||||
|
||||
my $tar_path = $module->fetch();
|
||||
print STDERR "downloaded to: $tar_path\n";
|
||||
print STDERR "sha-256: ", $module->status->checksum_value, "\n";
|
||||
|
||||
my $pkg_path = $module->extract();
|
||||
print STDERR "unpacked to: $pkg_path\n";
|
||||
|
||||
my $meta;
|
||||
if (-e "$pkg_path/META.yml") {
|
||||
$meta = YAML::LoadFile("$pkg_path/META.yml");
|
||||
}
|
||||
|
||||
print STDERR "metadata: ", encode_json($meta), "\n";
|
||||
|
||||
# Map a module to the attribute corresponding to its package
|
||||
# (e.g. HTML::HeadParser will be mapped to HTMLParser, because that
|
||||
# module is in the HTML-Parser package).
|
||||
sub module_to_pkg {
|
||||
my ($module_name) = @_;
|
||||
my @modules = $cb->search(type => "name", allow => [$module_name]);
|
||||
if (scalar @modules == 0) {
|
||||
# Fallback.
|
||||
$module_name =~ s/:://g;
|
||||
return $module_name;
|
||||
}
|
||||
my $module = $modules[0];
|
||||
my $attr_name = pkg_to_attr(get_pkg_name $module);
|
||||
print STDERR "mapped dep $module_name to $attr_name\n";
|
||||
return $attr_name;
|
||||
}
|
||||
|
||||
sub get_deps {
|
||||
my ($type) = @_;
|
||||
my $deps;
|
||||
if (defined $meta->{prereqs}) {
|
||||
die "unimplemented";
|
||||
} elsif ($type eq "runtime") {
|
||||
$deps = $meta->{requires};
|
||||
} elsif ($type eq "configure") {
|
||||
$deps = $meta->{configure_requires};
|
||||
} elsif ($type eq "build") {
|
||||
$deps = $meta->{build_requires};
|
||||
}
|
||||
my @res;
|
||||
foreach my $n (keys %{$deps}) {
|
||||
next if $n eq "perl";
|
||||
# Hacky way to figure out if this module is part of Perl.
|
||||
if ($n !~ /^JSON/ && $n !~ /^YAML/) {
|
||||
eval "use $n;";
|
||||
if (!$@) {
|
||||
print STDERR "skipping Perl-builtin module $n\n";
|
||||
next;
|
||||
}
|
||||
}
|
||||
push @res, module_to_pkg($n);
|
||||
}
|
||||
return @res;
|
||||
}
|
||||
|
||||
sub uniq {
|
||||
return keys %{{ map { $_ => 1 } @_ }};
|
||||
}
|
||||
|
||||
my @build_deps = sort(uniq(get_deps("configure"), get_deps("build"), get_deps("test")));
|
||||
print STDERR "build deps: @build_deps\n";
|
||||
|
||||
my @runtime_deps = sort(uniq(get_deps("runtime")));
|
||||
print STDERR "runtime deps: @runtime_deps\n";
|
||||
|
||||
my $homepage = $meta->{resources}->{homepage};
|
||||
print STDERR "homepage: $homepage\n" if defined $homepage;
|
||||
|
||||
my $description = $meta->{abstract};
|
||||
$description = uc(substr($description, 0, 1)) . substr($description, 1); # capitalise first letter
|
||||
$description =~ s/\.$//; # remove period at the end
|
||||
$description =~ s/\s*$//;
|
||||
$description =~ s/^\s*//;
|
||||
print STDERR "description: $description\n";
|
||||
|
||||
my $license = $meta->{license};
|
||||
if (defined $license) {
|
||||
$license = "perl5" if $license eq "perl_5";
|
||||
print STDERR "license: $license\n";
|
||||
}
|
||||
|
||||
my $build_fun = -e "$pkg_path/Build.PL" && ! -e "$pkg_path/Makefile.PL" ? "buildPerlModule" : "buildPerlPackage";
|
||||
|
||||
print STDERR "===\n";
|
||||
|
||||
print <<EOF;
|
||||
$attr_name = $build_fun {
|
||||
name = "$pkg_name";
|
||||
src = fetchurl {
|
||||
url = mirror://cpan/${\$module->path}/${\$module->package};
|
||||
sha256 = "${\$module->status->checksum_value}";
|
||||
};
|
||||
EOF
|
||||
print <<EOF if scalar @build_deps > 0;
|
||||
buildInputs = [ @build_deps ];
|
||||
EOF
|
||||
print <<EOF if scalar @runtime_deps > 0;
|
||||
propagatedBuildInputs = [ @runtime_deps ];
|
||||
EOF
|
||||
print <<EOF;
|
||||
meta = {
|
||||
EOF
|
||||
print <<EOF if defined $homepage;
|
||||
homepage = $homepage;
|
||||
EOF
|
||||
print <<EOF;
|
||||
description = "$description";
|
||||
EOF
|
||||
print <<EOF if defined $license;
|
||||
license = "$license";
|
||||
EOF
|
||||
print <<EOF;
|
||||
};
|
||||
};
|
||||
EOF
|
|
@ -202,6 +202,11 @@ let
|
|||
stringsWithDeps = lib.stringsWithDeps;
|
||||
|
||||
|
||||
### Nixpkgs maintainer tools
|
||||
|
||||
nix-generate-from-cpan = callPackage ../../maintainers/scripts/nix-generate-from-cpan.nix { };
|
||||
|
||||
|
||||
### STANDARD ENVIRONMENT
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue