summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--doc/language-support.xml39
-rwxr-xr-xmaintainers/scripts/generate-cpan-package120
-rw-r--r--maintainers/scripts/nix-generate-from-cpan.nix22
-rwxr-xr-xmaintainers/scripts/nix-generate-from-cpan.pl162
-rw-r--r--pkgs/top-level/all-packages.nix5
6 files changed, 231 insertions, 123 deletions
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 <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>
 
 
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 <<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
-
diff --git a/maintainers/scripts/nix-generate-from-cpan.nix b/maintainers/scripts/nix-generate-from-cpan.nix
new file mode 100644
index 00000000000..59a9b89bbe7
--- /dev/null
+++ b/maintainers/scripts/nix-generate-from-cpan.nix
@@ -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";
+  };
+}
\ No newline at end of file
diff --git a/maintainers/scripts/nix-generate-from-cpan.pl b/maintainers/scripts/nix-generate-from-cpan.pl
new file mode 100755
index 00000000000..86749bcac69
--- /dev/null
+++ b/maintainers/scripts/nix-generate-from-cpan.pl
@@ -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
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 8d9517b6c5a..239c9a5a41b 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -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