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