From 629daa2102abbcb368cd46ac1f0a85c0bbe104f5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 13 Jun 2013 15:39:08 +0200 Subject: 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. --- .gitignore | 6 +- doc/language-support.xml | 39 +++++- maintainers/scripts/generate-cpan-package | 120 ------------------ maintainers/scripts/nix-generate-from-cpan.nix | 22 ++++ maintainers/scripts/nix-generate-from-cpan.pl | 162 +++++++++++++++++++++++++ pkgs/top-level/all-packages.nix | 5 + 6 files changed, 231 insertions(+), 123 deletions(-) delete mode 100755 maintainers/scripts/generate-cpan-package create mode 100644 maintainers/scripts/nix-generate-from-cpan.nix create mode 100755 maintainers/scripts/nix-generate-from-cpan.pl diff --git a/.gitignore b/.gitignore index b2d5a9aa5bd..165e92c7fc3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ ,* .*.swp .*.swo -cpan-info -cpan_tmp/ result +doc/NEWS.html +doc/NEWS.txt +doc/manual.html +doc/manual.pdf diff --git a/doc/language-support.xml b/doc/language-support.xml index 6cc028c0b0a..cb40be4bf57 100644 --- a/doc/language-support.xml +++ b/doc/language-support.xml @@ -21,7 +21,7 @@ standard Makefile.PL. It’s implemented in pkgs/development/perl-modules/generic. Perl packages from CPAN are defined in pkgs/perl-packages.nix, +xlink:href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/perl-packages.nix">pkgs/top-level/perl-packages.nix, rather than pkgs/all-packages.nix. 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 { +
Generation from CPAN + +Nix expressions for Perl packages can be generated (almost) +automatically from CPAN. This is done by the program +nix-generate-from-cpan, which can be installed +as follows: + + +$ nix-env -i nix-generate-from-cpan + + +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: + + +$ 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"; + }; + }; + + +The output can be pasted into +pkgs/top-level/perl-packages.nix or wherever else +you need it. + +
+ diff --git a/maintainers/scripts/generate-cpan-package b/maintainers/scripts/generate-cpan-package deleted file mode 100755 index 2817e23e2fa..00000000000 --- a/maintainers/scripts/generate-cpan-package +++ /dev/null @@ -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 <\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 <path}/${\$module->package}; + sha256 = "${\$module->status->checksum_value}"; + }; +EOF +print < 0; + buildInputs = [ @build_deps ]; +EOF +print < 0; + propagatedBuildInputs = [ @runtime_deps ]; +EOF +print <