unofficial mirror of guix-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [PATCH] build-system: Add haskell-build-system.
@ 2015-03-27  8:59 Federico Beffa
  2015-03-29 13:44 ` Ludovic Courtès
  0 siblings, 1 reply; 7+ messages in thread
From: Federico Beffa @ 2015-03-27  8:59 UTC (permalink / raw)
  To: Guix-devel

[-- Attachment #1: Type: text/plain, Size: 951 bytes --]

Please find attached a build-system for Haskell. Currently focusing on
GHC, but trying to be flexible for addition of support for other
compilers.

Please note that installed libraries need to be registered in a
compiler specific database. The database consists of a directory with
a configuration file for each library and *a single binary cache*
file. To avoid clashes in profiles, the build system does not create
the cache.

To overcome clash problems: at build time the build system creates a
temporary library database to make GHC find input libraries. However,
when a user installs a library in his profile we need to create a
database cache.

I would like to propose to use the same mechanism as used for the
"dir" file of texinfo.
WDYT?

Binary files seems to include absolute path to libraries and do not
require any special handling. This impression comes from inspecting
haddock's derivation created with this build system.

Regards,
Fede

[-- Attachment #2: 0001-build-system-Add-haskell-build-system.patch --]
[-- Type: text/x-diff, Size: 13698 bytes --]

From b8c6b21e01c6eb7d9c5c7e9ec28a4ac7d12b0628 Mon Sep 17 00:00:00 2001
From: Federico Beffa <beffa@fbengineering.ch>
Date: Fri, 27 Mar 2015 09:36:56 +0100
Subject: [PATCH] build-system: Add haskell-build-system.

* guix/build-system/haskell.scm: New file.
* guix/build/haskell-build-system.scm: New file.
---
 guix/build-system/haskell.scm       | 131 +++++++++++++++++++++++++
 guix/build/haskell-build-system.scm | 190 ++++++++++++++++++++++++++++++++++++
 2 files changed, 321 insertions(+)
 create mode 100644 guix/build-system/haskell.scm
 create mode 100644 guix/build/haskell-build-system.scm

diff --git a/guix/build-system/haskell.scm b/guix/build-system/haskell.scm
new file mode 100644
index 0000000..4c419f1
--- /dev/null
+++ b/guix/build-system/haskell.scm
@@ -0,0 +1,131 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 Federico Beffa <beffa@fbengineering.ch>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system haskell)
+  #:use-module (guix store)
+  #:use-module (guix utils)
+  #:use-module (guix packages)
+  #:use-module (guix derivations)
+  #:use-module (guix build-system)
+  #:use-module (guix build-system gnu)
+  #:use-module (ice-9 match)
+  #:use-module (srfi srfi-26)
+  #:export (haskell-build
+            haskell-build-system))
+
+;; Commentary:
+;;
+;; Standard build procedure for Haskell packages using 'Setup.hs'.  This is
+;; implemented as an extension of 'gnu-build-system'.
+;;
+;; Code:
+
+(define (default-haskell)
+  "Return the default Haskell package."
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (let ((haskell (resolve-interface '(gnu packages haskell))))
+    (module-ref haskell 'ghc)))
+
+(define* (lower name
+                #:key source inputs native-inputs outputs system target
+                (haskell (default-haskell))
+                #:allow-other-keys
+                #:rest arguments)
+  "Return a bag for NAME."
+  (define private-keywords
+    '(#:source #:target #:python #:inputs #:native-inputs))
+
+  (and (not target)                               ;XXX: no cross-compilation
+       (bag
+         (name name)
+         (system system)
+         (host-inputs `(,@(if source
+                              `(("source" ,source))
+                              '())
+                        ,@inputs
+
+                        ;; Keep the standard inputs of 'gnu-build-system'.
+                        ,@(standard-packages)))
+         (build-inputs `(("haskell" ,haskell)
+                         ,@native-inputs))
+         (outputs outputs)
+         (build haskell-build)
+         (arguments (strip-keyword-arguments private-keywords arguments)))))
+
+(define* (haskell-build store name inputs
+                        #:key
+                        (tests? #t)
+                        (test-target "test")
+                        (configure-flags ''())
+                        (phases '(@ (guix build haskell-build-system)
+                                    %standard-phases))
+                        (outputs '("out"))
+                        (search-paths '())
+                        (system (%current-system))
+                        (guile #f)
+                        (imported-modules '((guix build haskell-build-system)
+                                            (guix build gnu-build-system)
+                                            (guix build utils)))
+                        (modules '((guix build haskell-build-system)
+                                   (guix build utils))))
+  "Build SOURCE using HASKELL, and with INPUTS.  This assumes that SOURCE
+provides a 'Setup.hs' file as its build system."
+  (define builder
+    `(begin
+       (use-modules ,@modules)
+       (haskell-build #:name ,name
+                      #:source ,(match (assoc-ref inputs "source")
+                                  (((? derivation? source))
+                                   (derivation->output-path source))
+                                  ((source)
+                                   source)
+                                  (source
+                                   source))
+                      #:configure-flags ,configure-flags
+                      #:system ,system
+                      #:test-target ,test-target
+                      #:tests? ,tests?
+                      #:phases ,phases
+                      #:outputs %outputs
+                      #:search-paths ',(map search-path-specification->sexp
+                                            search-paths)
+                      #:inputs %build-inputs)))
+
+  (define guile-for-build
+    (match guile
+      ((? package?)
+       (package-derivation store guile system #:graft? #f))
+      (#f                                         ; the default
+       (let* ((distro (resolve-interface '(gnu packages commencement)))
+              (guile  (module-ref distro 'guile-final)))
+         (package-derivation store guile system #:graft? #f)))))
+
+  (build-expression->derivation store name builder
+                                #:inputs inputs
+                                #:system system
+                                #:modules imported-modules
+                                #:outputs outputs
+                                #:guile-for-build guile-for-build))
+
+(define haskell-build-system
+  (build-system
+    (name 'haskell)
+    (description "The standard Haskell build system")
+    (lower lower)))
+
+;;; haskell.scm ends here
diff --git a/guix/build/haskell-build-system.scm b/guix/build/haskell-build-system.scm
new file mode 100644
index 0000000..c8195ee
--- /dev/null
+++ b/guix/build/haskell-build-system.scm
@@ -0,0 +1,190 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 Federico Beffa <beffa@fbengineering.ch>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build haskell-build-system)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:use-module (guix build utils)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 match)
+  #:export (%standard-phases
+            haskell-build))
+
+;; Commentary:
+;;
+;; Builder-side code of the standard Haskell package build procedure.
+;;
+;; Code:
+
+;; Directory where we create the temporary libraries database with its cache
+;; as required by the compiler.
+(define %tmp-db-dir
+  (string-append (or (getenv "TMP") "/tmp")
+                 "/package.conf.d"))
+
+(define (call-setuphs command params)
+  (let ((setup-file (cond
+                     ((file-exists? "Setup.hs")
+                      "Setup.hs")
+                     ((file-exists? "Setup.lhs")
+                      "Setup.lhs")
+                     (else
+                      #f))))
+    (if setup-file
+        (begin
+          (format
+           #t
+           "running \"runhaskell Setup.hs\" with command ~s and parameters ~s~%"
+           command params)
+          (zero? (apply system* "runhaskell" setup-file command params)))
+        (error "no Setup.hs nor Setup.lhs found"))))
+
+(define* (configure #:key outputs inputs tests? (configure-flags '())
+                    #:allow-other-keys)
+  "Install a given Haskell package."
+  (let* ((out (assoc-ref outputs "out"))
+         (input-dirs (match inputs
+                       (((_ . dir) ...)
+                        dir)
+                       (_ '())))
+         (params (append `(,(string-append "--prefix=" out))
+                         `(,(string-append "--package-db=" %tmp-db-dir))
+                         '("--global")
+                         `(,(string-append
+                             "--extra-include-dirs="
+                             (list->search-path-as-string
+                              (search-path-as-list '("include") input-dirs)
+                              ":")))
+                         `(,(string-append
+                             "--extra-lib-dirs="
+                             (list->search-path-as-string
+                              (search-path-as-list '("lib") input-dirs)
+                              ":")))
+                         (if tests?
+                             '("--enable-tests")
+                             '())
+                         configure-flags)))
+    (call-setuphs "configure" params)))
+
+(define* (build #:rest empty)
+  "Build a given Haskell package."
+  (call-setuphs "build" '()))
+
+(define* (install #:rest empty)
+  "Install a given Haskell package."
+  (call-setuphs "copy" '()))
+
+(define (compiler-name-version haskell-input)
+  (let* ((base (basename haskell-input)))
+    (string-drop base
+                 (+ 1 (string-index base #\-)))))
+
+(define (grep rx port)
+  (let ((line (read-line port)))
+    (if (eof-object? line)
+        #f
+        (let ((rx-result (regexp-exec rx line)))
+          (if rx-result
+              (match:substring rx-result 1)
+              (grep rx port))))))
+
+(define* (setup-compiler #:key system inputs outputs #:allow-other-keys)
+  (let* ((haskell (assoc-ref inputs "haskell"))
+         (name-version (compiler-name-version haskell)))
+    (cond
+     ((string-match "ghc" name-version)
+      (setup-ghc system inputs outputs))
+     (else
+      (format #t
+              "Compiler ~a not supported~%" name-version)))))
+
+(define (setup-ghc system inputs outputs)
+  (let* ((haskell  (assoc-ref inputs "haskell"))
+         (input-dirs (match inputs
+                       (((_ . dir) ...)
+                        dir)
+                       (_ '())))
+         (conf-dirs (search-path-as-list
+                     `(,(string-append "lib/" system "-"
+                                       (compiler-name-version haskell)
+                                       "/package.conf.d"))
+                     input-dirs)))
+    (mkdir-p %tmp-db-dir)
+    (for-each
+     (lambda (dir)
+       (let ((conf-files
+              (find-files dir ".*\\.conf")))
+         (unless (null? conf-files)
+           (for-each
+            (lambda (file)
+              (copy-file file (string-append %tmp-db-dir "/" (basename file))))
+            conf-files))))
+     conf-dirs)
+    (zero? (system* "ghc-pkg"
+                    (string-append "--package-db=" %tmp-db-dir)
+                    "recache"))))
+
+(define* (register #:key name system inputs outputs #:allow-other-keys)
+  "Generate the compiler registration file for a given Haskell package.  Don't
+generate the cache as it would clash in user profiles."
+  (let* ((out (assoc-ref outputs "out"))
+         (haskell  (assoc-ref inputs "haskell"))
+         (lib (string-append out "/lib"))
+         (config-dir (string-append lib "/" system
+                                    "-" (compiler-name-version haskell)
+                                    "/package.conf.d"))
+         (id-rx (make-regexp "^id: *(.*)$"))
+         (lib-rx (make-regexp "lib.*\\.(a|so)"))
+         (config-file (string-append config-dir "/" name ".conf"))
+         (params
+          (list (string-append "--gen-pkg-config=" config-file))))
+    (unless (null? (find-files lib lib-rx))
+      (mkdir-p config-dir)
+      (call-setuphs "register" params)
+      (let ((config-file-name+id
+             (call-with-ascii-input-file config-file (cut grep id-rx <>))))
+        (rename-file config-file
+                     (string-append config-dir "/" config-file-name+id
+                                    ".conf"))))
+    #t))
+
+(define* (check #:key tests? test-target #:allow-other-keys)
+  "Run the test suite of a given Haskell package."
+  (if tests?
+      (call-setuphs test-target '())
+      #t))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (add-before configure setup-compiler setup-compiler)
+    (add-after install register register)
+    (replace install install)
+    (replace check check)
+    (replace build build)
+    (replace configure configure)))
+
+(define* (haskell-build #:key inputs (phases %standard-phases)
+                        #:allow-other-keys #:rest args)
+  "Build the given Haskell package, applying all of PHASES in order."
+  (apply gnu:gnu-build
+         #:inputs inputs #:phases phases
+         args))
+
+;;; haskell-build-system.scm ends here
-- 
2.2.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] build-system: Add haskell-build-system.
  2015-03-27  8:59 [PATCH] build-system: Add haskell-build-system Federico Beffa
@ 2015-03-29 13:44 ` Ludovic Courtès
  2015-03-29 14:00   ` Ricardo Wurmus
  0 siblings, 1 reply; 7+ messages in thread
From: Ludovic Courtès @ 2015-03-29 13:44 UTC (permalink / raw)
  To: Federico Beffa; +Cc: Guix-devel

Federico Beffa <beffa@ieee.org> skribis:

> Please note that installed libraries need to be registered in a
> compiler specific database. The database consists of a directory with
> a configuration file for each library and *a single binary cache*
> file. To avoid clashes in profiles, the build system does not create
> the cache.

Isn’t there any environment variable akin to LD_LIBRARY_PATH or
GUILE_LOAD_PATH?  That would greatly simplify things.

> To overcome clash problems: at build time the build system creates a
> temporary library database to make GHC find input libraries. However,
> when a user installs a library in his profile we need to create a
> database cache.
>
> I would like to propose to use the same mechanism as used for the
> "dir" file of texinfo.
> WDYT?

If there’s no environment variable to specify the search path, we could
do that, yes.  It would be nice to check how Nixpkgs handles it.

> Binary files seems to include absolute path to libraries and do not
> require any special handling. This impression comes from inspecting
> haddock's derivation created with this build system.

That’s nice (nicer than propagating everything like we do for Python
modules, Perl modules, etc.)

> From b8c6b21e01c6eb7d9c5c7e9ec28a4ac7d12b0628 Mon Sep 17 00:00:00 2001
> From: Federico Beffa <beffa@fbengineering.ch>
> Date: Fri, 27 Mar 2015 09:36:56 +0100
> Subject: [PATCH] build-system: Add haskell-build-system.
>
> * guix/build-system/haskell.scm: New file.
> * guix/build/haskell-build-system.scm: New file.

Overall LGTM (assuming there’s no environment variable search path we
could use.)

Some superficial comments below:

> +;; Directory where we create the temporary libraries database with its cache
> +;; as required by the compiler.
> +(define %tmp-db-dir

It would be nice to explain a bit, as you did in this message, what the
library database is and why we’re creating a temporary database.

> +(define (call-setuphs command params)

Rather ‘invoke-setuphs’ or ‘run-setuphs’ (‘call’ is more for a function,
I think.)

> +        (begin
> +          (format
> +           #t
> +           "running \"runhaskell Setup.hs\" with command ~s and parameters ~s~%"
> +           command params)

I would rather write it like this:

             (format #t "running \"runhaskell Setup.hs\" with command ~s \
and parameters ~s~%"
                     command params)

> +(define (compiler-name-version haskell-input)
> +  (let* ((base (basename haskell-input)))
> +    (string-drop base
> +                 (+ 1 (string-index base #\-)))))
> +
> +(define (grep rx port)
> +  (let ((line (read-line port)))
> +    (if (eof-object? line)
> +        #f
> +        (let ((rx-result (regexp-exec rx line)))
> +          (if rx-result
> +              (match:substring rx-result 1)
> +              (grep rx port))))))
> +
> +(define* (setup-compiler #:key system inputs outputs #:allow-other-keys)
> +  (let* ((haskell (assoc-ref inputs "haskell"))
> +         (name-version (compiler-name-version haskell)))
> +    (cond
> +     ((string-match "ghc" name-version)
> +      (setup-ghc system inputs outputs))
> +     (else
> +      (format #t
> +              "Compiler ~a not supported~%" name-version)))))
> +
> +(define (setup-ghc system inputs outputs)

Docstrings please.

Also s/setup-ghc/make-ghc-package-database/ or something like that.

> +  (let* ((haskell  (assoc-ref inputs "haskell"))
> +         (input-dirs (match inputs
> +                       (((_ . dir) ...)
> +                        dir)
> +                       (_ '())))
> +         (conf-dirs (search-path-as-list
> +                     `(,(string-append "lib/" system "-"
> +                                       (compiler-name-version haskell)
> +                                       "/package.conf.d"))
> +                     input-dirs)))
> +    (mkdir-p %tmp-db-dir)
> +    (for-each
> +     (lambda (dir)
> +       (let ((conf-files
> +              (find-files dir ".*\\.conf")))
> +         (unless (null? conf-files)
> +           (for-each
> +            (lambda (file)
> +              (copy-file file (string-append %tmp-db-dir "/" (basename file))))
> +            conf-files))))
> +     conf-dirs)

The nested ‘for-each’ is unpleasant to the eye, IMO, and difficult to
reason about.

What about first constructing the list of files, and then iterating over
them with a single ‘for-each’, like:

  (let ((conf-files (append-map (cut find-files <> "\\.conf$") conf-dirs)))
    (for-each (lambda (conf)
                (copy-file conf ...))
              conf-files))

(Note also that "\\.conf$" is more accurate and shorter than
".*\\.conf".)

> +(define* (check #:key tests? test-target #:allow-other-keys)
> +  "Run the test suite of a given Haskell package."
> +  (if tests?
> +      (call-setuphs test-target '())
> +      #t))

Would be nice to print a message when tests are skipped, as done in
gnu-build-system.scm.

Thanks!

Ludo’.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] build-system: Add haskell-build-system.
  2015-03-29 13:44 ` Ludovic Courtès
@ 2015-03-29 14:00   ` Ricardo Wurmus
  2015-03-29 14:45     ` Federico Beffa
  0 siblings, 1 reply; 7+ messages in thread
From: Ricardo Wurmus @ 2015-03-29 14:00 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: Guix-devel, Federico Beffa


Ludovic Courtès writes:

> Federico Beffa <beffa@ieee.org> skribis:
>
>> Please note that installed libraries need to be registered in a
>> compiler specific database. The database consists of a directory with
>> a configuration file for each library and *a single binary cache*
>> file. To avoid clashes in profiles, the build system does not create
>> the cache.
>
> Isn’t there any environment variable akin to LD_LIBRARY_PATH or
> GUILE_LOAD_PATH?  That would greatly simplify things.

There is the GHC_PACKAGE_PATH environment variable which is supposed to
be a colon-separated list of package databases.

See section 4.9.5.1 in the latest GHC user guide.
https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html

~~ Ricardo

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] build-system: Add haskell-build-system.
  2015-03-29 14:00   ` Ricardo Wurmus
@ 2015-03-29 14:45     ` Federico Beffa
  2015-03-30  8:16       ` Ludovic Courtès
  0 siblings, 1 reply; 7+ messages in thread
From: Federico Beffa @ 2015-03-29 14:45 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: Guix-devel

On Sun, Mar 29, 2015 at 4:00 PM, Ricardo Wurmus <rekado@elephly.net> wrote:
>
> Ludovic Courtès writes:
>
>> Isn’t there any environment variable akin to LD_LIBRARY_PATH or
>> GUILE_LOAD_PATH?  That would greatly simplify things.
>
> There is the GHC_PACKAGE_PATH environment variable which is supposed to
> be a colon-separated list of package databases.
>
> See section 4.9.5.1 in the latest GHC user guide.
> https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html

There are a couple of considerations about this:

* All Hackage packages are based on Cabal, not the GHC specific tools.
If GHC_PACKAGE_PATH is set, running the configure phase "runhaskell
Setup.hs configure" will stop with a message saying that Cabal is not
compatible with the use of this variable.

NixOS does the same: in the build phase they explicitly unset
GHC_PACKAGE_PATH and build a temporary database:

https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/generic-builder.nix

* AFAIU the directories listed in GHC_PACKAGE_PATH must still include
a database cache named 'package.cache' (section 4.9.5 of the GHC
manual). Therefore, the fix file name would clash in profiles and
prevent the use of libraries in profiles.

Having never used nixpkgs, I'm not sure how they handle the database
in profiles. Would you have a pointer to a "standard" mechanism (like
how they handle 'dir' files, icon caches, ...).

Regards,
Fede

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] build-system: Add haskell-build-system.
  2015-03-29 14:45     ` Federico Beffa
@ 2015-03-30  8:16       ` Ludovic Courtès
  2015-03-30 16:48         ` Federico Beffa
  0 siblings, 1 reply; 7+ messages in thread
From: Ludovic Courtès @ 2015-03-30  8:16 UTC (permalink / raw)
  To: Federico Beffa; +Cc: Guix-devel

Federico Beffa <beffa@ieee.org> skribis:

> Having never used nixpkgs, I'm not sure how they handle the database
> in profiles. Would you have a pointer to a "standard" mechanism (like
> how they handle 'dir' files, icon caches, ...).

AFAIK there’s no general mechanism for that.  For GHC, this is handled
by adding a wrapper for the ‘ghc’ binary:

  https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/compilers/ghc/wrapper.nix
  https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/compilers/ghcjs/wrapper.nix

Handling it during profile creation, as you suggest, and avoiding the
use of a wrapper sounds preferable to me.

If you look under
<https://github.com/NixOS/nixpkgs/tree/master/pkgs/desktops/gnome-3/3.12/core>,
many packages have an additional phase that does:

  rm $out/share/icons/hicolor/icon-theme.cache

Some also explicitly wrap executables to set XDG_DATA_DIRS etc. like our
‘glib-or-gtk-build-system’ does:

  wrapProgram $f \
    --prefix XDG_DATA_DIRS : "$XDG_ICON_DIRS:$GSETTINGS_SCHEMAS_PATH"

Thanks,
Ludo’.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] build-system: Add haskell-build-system.
  2015-03-30  8:16       ` Ludovic Courtès
@ 2015-03-30 16:48         ` Federico Beffa
  2015-04-02 21:31           ` Ludovic Courtès
  0 siblings, 1 reply; 7+ messages in thread
From: Federico Beffa @ 2015-03-30 16:48 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: Guix-devel

[-- Attachment #1: Type: text/plain, Size: 1042 bytes --]

On Mon, Mar 30, 2015 at 10:16 AM, Ludovic Courtès <ludo@gnu.org> wrote:
> Handling it during profile creation, as you suggest, and avoiding the
> use of a wrapper sounds preferable to me.

OK, lets go for that.

Please find attached an updated patch taking into account all of your
earlier comments.

In the mean time I've found that to generate the documentation an
additional step is required (phase haddock; the name is also the name
of the tool used to generate the doc). I've added a keyword argument
#:haddock? with default value of #t and #:haddock-flags for doc
specific flags.

The documentation is in the form of html files and does seems to
require a fair amount of space. Some random examples:

- mtl libs: 1.2MB / doc: 0.772MB
- text libs: 13MB / doc: 1.7MB
- HTTP libs: 3.9MB / doc: 0.804MB
- network-uri: libs: 1.5MB / doc: 0.168MB
- parsec libs: 3.1MB / doc: 1.1MB

Given that I'm starting to package a bunch of libraries, do we want to
generate a separate output for all of them?

Regards,
Fede

[-- Attachment #2: 0001-build-system-Add-haskell-build-system.patch --]
[-- Type: text/x-diff, Size: 15439 bytes --]

From 87f567c0c3c06da73bd70e2498f7829258ae7d61 Mon Sep 17 00:00:00 2001
From: Federico Beffa <beffa@fbengineering.ch>
Date: Fri, 27 Mar 2015 09:36:56 +0100
Subject: [PATCH 01/22] build-system: Add haskell-build-system.

* guix/build-system/haskell.scm: New file.
* guix/build/haskell-build-system.scm: New file.
---
 guix/build-system/haskell.scm       | 135 ++++++++++++++++++++++
 guix/build/haskell-build-system.scm | 220 ++++++++++++++++++++++++++++++++++++
 2 files changed, 355 insertions(+)
 create mode 100644 guix/build-system/haskell.scm
 create mode 100644 guix/build/haskell-build-system.scm

diff --git a/guix/build-system/haskell.scm b/guix/build-system/haskell.scm
new file mode 100644
index 0000000..79faa5a
--- /dev/null
+++ b/guix/build-system/haskell.scm
@@ -0,0 +1,135 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 Federico Beffa <beffa@fbengineering.ch>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system haskell)
+  #:use-module (guix store)
+  #:use-module (guix utils)
+  #:use-module (guix packages)
+  #:use-module (guix derivations)
+  #:use-module (guix build-system)
+  #:use-module (guix build-system gnu)
+  #:use-module (ice-9 match)
+  #:use-module (srfi srfi-26)
+  #:export (haskell-build
+            haskell-build-system))
+
+;; Commentary:
+;;
+;; Standard build procedure for Haskell packages using 'Setup.hs'.  This is
+;; implemented as an extension of 'gnu-build-system'.
+;;
+;; Code:
+
+(define (default-haskell)
+  "Return the default Haskell package."
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (let ((haskell (resolve-interface '(gnu packages haskell))))
+    (module-ref haskell 'ghc)))
+
+(define* (lower name
+                #:key source inputs native-inputs outputs system target
+                (haskell (default-haskell))
+                #:allow-other-keys
+                #:rest arguments)
+  "Return a bag for NAME."
+  (define private-keywords
+    '(#:target #:haskell #:inputs #:native-inputs))
+
+  (and (not target)                               ;XXX: no cross-compilation
+       (bag
+         (name name)
+         (system system)
+         (host-inputs `(,@(if source
+                              `(("source" ,source))
+                              '())
+                        ,@inputs
+
+                        ;; Keep the standard inputs of 'gnu-build-system'.
+                        ,@(standard-packages)))
+         (build-inputs `(("haskell" ,haskell)
+                         ,@native-inputs))
+         (outputs outputs)
+         (build haskell-build)
+         (arguments (strip-keyword-arguments private-keywords arguments)))))
+
+(define* (haskell-build store name inputs
+                        #:key source
+                        (haddock? #t)
+                        (haddock-flags ''())
+                        (tests? #t)
+                        (test-target "test")
+                        (configure-flags ''())
+                        (phases '(@ (guix build haskell-build-system)
+                                    %standard-phases))
+                        (outputs '("out"))
+                        (search-paths '())
+                        (system (%current-system))
+                        (guile #f)
+                        (imported-modules '((guix build haskell-build-system)
+                                            (guix build gnu-build-system)
+                                            (guix build utils)))
+                        (modules '((guix build haskell-build-system)
+                                   (guix build utils))))
+  "Build SOURCE using HASKELL, and with INPUTS.  This assumes that SOURCE
+provides a 'Setup.hs' file as its build system."
+  (define builder
+    `(begin
+       (use-modules ,@modules)
+       (haskell-build #:name ,name
+                      #:source ,(match (assoc-ref inputs "source")
+                                  (((? derivation? source))
+                                   (derivation->output-path source))
+                                  ((source)
+                                   source)
+                                  (source
+                                   source))
+                      #:configure-flags ,configure-flags
+                      #:haddock-flags ,haddock-flags
+                      #:system ,system
+                      #:test-target ,test-target
+                      #:tests? ,tests?
+                      #:haddock? ,haddock?
+                      #:phases ,phases
+                      #:outputs %outputs
+                      #:search-paths ',(map search-path-specification->sexp
+                                            search-paths)
+                      #:inputs %build-inputs)))
+
+  (define guile-for-build
+    (match guile
+      ((? package?)
+       (package-derivation store guile system #:graft? #f))
+      (#f                                         ; the default
+       (let* ((distro (resolve-interface '(gnu packages commencement)))
+              (guile  (module-ref distro 'guile-final)))
+         (package-derivation store guile system #:graft? #f)))))
+
+  (build-expression->derivation store name builder
+                                #:inputs inputs
+                                #:system system
+                                #:modules imported-modules
+                                #:outputs outputs
+                                #:guile-for-build guile-for-build))
+
+(define haskell-build-system
+  (build-system
+    (name 'haskell)
+    (description "The standard Haskell build system")
+    (lower lower)))
+
+;;; haskell.scm ends here
diff --git a/guix/build/haskell-build-system.scm b/guix/build/haskell-build-system.scm
new file mode 100644
index 0000000..d2aba25
--- /dev/null
+++ b/guix/build/haskell-build-system.scm
@@ -0,0 +1,220 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2015 Federico Beffa <beffa@fbengineering.ch>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build haskell-build-system)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:use-module (guix build utils)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 match)
+  #:export (%standard-phases
+            haskell-build))
+
+;; Commentary:
+;;
+;; Builder-side code of the standard Haskell package build procedure.
+;;
+;; The Haskell compiler, to find libraries, relies on a library database with
+;; a binary cache. For GHC the cache has to be named 'package.cache'. If every
+;; library would generate the cache at build time, then they would clash in
+;; profiles. For this reason we do not generate the cache when we generate
+;; libraries substitutes. Instead:
+;;
+;; - At build time we use the 'setup-compiler' phase to generate a temporary
+;;   library database and its cache.
+;;
+;; - We generate the cache when a profile is created.
+
+;; Code:
+;;
+;; Directory where we create the temporary libraries database with its cache
+;; as required by the compiler.
+(define %tmp-db-dir
+  (string-append (or (getenv "TMP") "/tmp")
+                 "/package.conf.d"))
+
+(define (run-setuphs command params)
+  (let ((setup-file (cond
+                     ((file-exists? "Setup.hs")
+                      "Setup.hs")
+                     ((file-exists? "Setup.lhs")
+                      "Setup.lhs")
+                     (else
+                      #f))))
+    (if setup-file
+        (begin
+          (format #t "running \"runhaskell Setup.hs\" with command ~s \
+and parameters ~s~%"
+                  command params)
+          (zero? (apply system* "runhaskell" setup-file command params)))
+        (error "no Setup.hs nor Setup.lhs found"))))
+
+(define* (configure #:key outputs inputs tests? (configure-flags '())
+                    #:allow-other-keys)
+  "Configure a given Haskell package."
+  (let* ((out (assoc-ref outputs "out"))
+         (input-dirs (match inputs
+                       (((_ . dir) ...)
+                        dir)
+                       (_ '())))
+         (params (append `(,(string-append "--prefix=" out))
+                         `(,(string-append
+                             "--docdir=" out "/share/doc/"
+                             (package-name-version out)))
+                         `(,(string-append "--package-db=" %tmp-db-dir))
+                         '("--global")
+                         `(,(string-append
+                             "--extra-include-dirs="
+                             (list->search-path-as-string
+                              (search-path-as-list '("include") input-dirs)
+                              ":")))
+                         `(,(string-append
+                             "--extra-lib-dirs="
+                             (list->search-path-as-string
+                              (search-path-as-list '("lib") input-dirs)
+                              ":")))
+                         (if tests?
+                             '("--enable-tests")
+                             '())
+                         configure-flags)))
+    (run-setuphs "configure" params)))
+
+(define* (build #:rest empty)
+  "Build a given Haskell package."
+  (run-setuphs "build" '()))
+
+(define* (install #:rest empty)
+  "Install a given Haskell package."
+  (run-setuphs "copy" '()))
+
+(define (package-name-version store-dir)
+  "Given a store directory STORE-DIR return 'name-version' of the package."
+  (let* ((base (basename store-dir)))
+    (string-drop base
+                 (+ 1 (string-index base #\-)))))
+
+(define (grep rx port)
+  "Given a regular-expression RX including a group, read from PORT until the
+first match and return the content of the group."
+  (let ((line (read-line port)))
+    (if (eof-object? line)
+        #f
+        (let ((rx-result (regexp-exec rx line)))
+          (if rx-result
+              (match:substring rx-result 1)
+              (grep rx port))))))
+
+(define* (setup-compiler #:key system inputs outputs #:allow-other-keys)
+  "Setup the compiler environment."
+  (let* ((haskell (assoc-ref inputs "haskell"))
+         (name-version (package-name-version haskell)))
+    (cond
+     ((string-match "ghc" name-version)
+      (make-ghc-package-database system inputs outputs))
+     (else
+      (format #t
+              "Compiler ~a not supported~%" name-version)))))
+
+(define (make-ghc-package-database system inputs outputs)
+  "Generate the GHC package database."
+  (let* ((haskell  (assoc-ref inputs "haskell"))
+         (input-dirs (match inputs
+                       (((_ . dir) ...)
+                        dir)
+                       (_ '())))
+         (conf-dirs (search-path-as-list
+                     `(,(string-append "lib/" system "-"
+                                       (package-name-version haskell)
+                                       "/package.conf.d"))
+                     input-dirs))
+         (conf-files (append-map (cut find-files <> "\\.conf$") conf-dirs)))
+    (mkdir-p %tmp-db-dir)
+    (for-each (lambda (file)
+                (copy-file file
+                           (string-append %tmp-db-dir "/" (basename file))))
+              conf-files)
+    (zero? (system* "ghc-pkg"
+                    (string-append "--package-db=" %tmp-db-dir)
+                    "recache"))))
+
+(define* (register #:key name system inputs outputs #:allow-other-keys)
+  "Generate the compiler registration file for a given Haskell package.  Don't
+generate the cache as it would clash in user profiles."
+  (let* ((out (assoc-ref outputs "out"))
+         (haskell  (assoc-ref inputs "haskell"))
+         (lib (string-append out "/lib"))
+         (config-dir (string-append lib "/" system
+                                    "-" (package-name-version haskell)
+                                    "/package.conf.d"))
+         (id-rx (make-regexp "^id: *(.*)$"))
+         (lib-rx (make-regexp "lib.*\\.(a|so)"))
+         (config-file (string-append config-dir "/" name ".conf"))
+         (params
+          (list (string-append "--gen-pkg-config=" config-file))))
+    (unless (null? (find-files lib lib-rx))
+      (mkdir-p config-dir)
+      (run-setuphs "register" params)
+      (let ((config-file-name+id
+             (call-with-ascii-input-file config-file (cut grep id-rx <>))))
+        (rename-file config-file
+                     (string-append config-dir "/" config-file-name+id
+                                    ".conf"))))
+    #t))
+
+(define* (check #:key tests? test-target #:allow-other-keys)
+  "Run the test suite of a given Haskell package."
+  (if tests?
+      (run-setuphs test-target '())
+      (begin
+        (format #t "test suite not run~%")
+        #t)))
+
+(define* (haddock #:key outputs haddock? haddock-flags #:allow-other-keys)
+  "Run the test suite of a given Haskell package."
+  (if haddock?
+      (let* ((out (assoc-ref outputs "out"))
+             (doc-src (string-append (getcwd) "/dist/doc"))
+             (doc-dest (string-append out "/share/doc/"
+                                      (package-name-version out))))
+        (if (run-setuphs "haddock" haddock-flags)
+            (begin
+              (copy-recursively doc-src doc-dest)
+              #t)
+            #f))
+      #t))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (add-before configure setup-compiler setup-compiler)
+    (add-after install haddock haddock)
+    (add-after install register register)
+    (replace install install)
+    (replace check check)
+    (replace build build)
+    (replace configure configure)))
+
+(define* (haskell-build #:key inputs (phases %standard-phases)
+                        #:allow-other-keys #:rest args)
+  "Build the given Haskell package, applying all of PHASES in order."
+  (apply gnu:gnu-build
+         #:inputs inputs #:phases phases
+         args))
+
+;;; haskell-build-system.scm ends here
-- 
2.2.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] build-system: Add haskell-build-system.
  2015-03-30 16:48         ` Federico Beffa
@ 2015-04-02 21:31           ` Ludovic Courtès
  0 siblings, 0 replies; 7+ messages in thread
From: Ludovic Courtès @ 2015-04-02 21:31 UTC (permalink / raw)
  To: Federico Beffa; +Cc: Guix-devel

Federico Beffa <beffa@ieee.org> skribis:

> On Mon, Mar 30, 2015 at 10:16 AM, Ludovic Courtès <ludo@gnu.org> wrote:
>> Handling it during profile creation, as you suggest, and avoiding the
>> use of a wrapper sounds preferable to me.
>
> OK, lets go for that.

Sometime we’ll have to make info-dir + cert + ghc stuff in (guix
profiles) less adhoc.

> In the mean time I've found that to generate the documentation an
> additional step is required (phase haddock; the name is also the name
> of the tool used to generate the doc). I've added a keyword argument
> #:haddock? with default value of #t and #:haddock-flags for doc
> specific flags.

Nice.

> The documentation is in the form of html files and does seems to
> require a fair amount of space. Some random examples:
>
> - mtl libs: 1.2MB / doc: 0.772MB
> - text libs: 13MB / doc: 1.7MB
> - HTTP libs: 3.9MB / doc: 0.804MB
> - network-uri: libs: 1.5MB / doc: 0.168MB
> - parsec libs: 3.1MB / doc: 1.1MB
>
> Given that I'm starting to package a bunch of libraries, do we want to
> generate a separate output for all of them?

Probably, yes.

> From 87f567c0c3c06da73bd70e2498f7829258ae7d61 Mon Sep 17 00:00:00 2001
> From: Federico Beffa <beffa@fbengineering.ch>
> Date: Fri, 27 Mar 2015 09:36:56 +0100
> Subject: [PATCH 01/22] build-system: Add haskell-build-system.
>
> * guix/build-system/haskell.scm: New file.
> * guix/build/haskell-build-system.scm: New file.

[...]

> +;; - We generate the cache when a profile is created.
> +
> +;; Code:
> +;;

“Code:” should be “attached” to the block before and followed by an
empty line.

Please push, thank you!

Ludo’.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2015-04-02 21:31 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-27  8:59 [PATCH] build-system: Add haskell-build-system Federico Beffa
2015-03-29 13:44 ` Ludovic Courtès
2015-03-29 14:00   ` Ricardo Wurmus
2015-03-29 14:45     ` Federico Beffa
2015-03-30  8:16       ` Ludovic Courtès
2015-03-30 16:48         ` Federico Beffa
2015-04-02 21:31           ` Ludovic Courtès

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).