guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

02/02: guix: Add beginnings of "guix potluck"


From: Andy Wingo
Subject: 02/02: guix: Add beginnings of "guix potluck"
Date: Wed, 5 Apr 2017 11:20:23 -0400 (EDT)

wingo pushed a commit to branch wip-potluck
in repository guix.

commit 09cf44da1e1dd3b46a14e19faf940602f252e1ee
Author: Andy Wingo <address@hidden>
Date:   Wed Apr 5 17:12:31 2017 +0200

    guix: Add beginnings of "guix potluck"
    
    * guix/potluck/build-systems.scm:
    * guix/potluck/licenses.scm:
    * guix/potluck/packages.scm:
    * guix/scripts/potluck.scm: New files.
    * guix/scripts/build.scm (load-package-or-derivation-from-file):
    (options->things-to-build, options->derivations): Add "potluck-package" and
    "potluck-source" to environment of file.  Lower potluck packages to Guix
    packages.
---
 Makefile.am                    |   4 +
 guix/potluck/build-systems.scm |  54 +++++
 guix/potluck/licenses.scm      |  41 ++++
 guix/potluck/packages.scm      | 399 +++++++++++++++++++++++++++++++++++
 guix/scripts/build.scm         |  54 +++--
 guix/scripts/potluck.scm       | 458 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 990 insertions(+), 20 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 06c85e9..33b23de 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -123,6 +123,9 @@ MODULES =                                   \
   guix/build/make-bootstrap.scm                        \
   guix/search-paths.scm                                \
   guix/packages.scm                            \
+  guix/potluck/build-systems.scm               \
+  guix/potluck/licenses.scm                    \
+  guix/potluck/packages.scm                    \
   guix/import/utils.scm                                \
   guix/import/gnu.scm                          \
   guix/import/snix.scm                         \
@@ -160,6 +163,7 @@ MODULES =                                   \
   guix/scripts/graph.scm                       \
   guix/scripts/container.scm                   \
   guix/scripts/container/exec.scm              \
+  guix/scripts/potluck.scm                     \
   guix.scm                                     \
   $(GNU_SYSTEM_MODULES)
 
diff --git a/guix/potluck/build-systems.scm b/guix/potluck/build-systems.scm
new file mode 100644
index 0000000..45bd402
--- /dev/null
+++ b/guix/potluck/build-systems.scm
@@ -0,0 +1,54 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017 Andy Wingo <address@hidden>
+;;;
+;;; 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 potluck build-systems)
+  #:use-module ((guix build-system) #:select (build-system?))
+  #:use-module ((gnu packages) #:select (scheme-modules))
+  #:use-module (ice-9 match)
+  #:export (build-system-by-name all-potluck-build-system-names))
+
+(define all-build-systems
+  (delay
+    (let* ((gbs (resolve-module '(guix build-system)))
+           (root (dirname (dirname (module-filename gbs))))
+           (by-name (make-hash-table)))
+      (for-each (lambda (iface)
+                  (module-for-each
+                   (lambda (k var)
+                     (let* ((str (symbol->string k))
+                            (pos (string-contains str "-build-system"))
+                            (val (variable-ref var)))
+                       (when (and pos (build-system? val))
+                         (let* ((head (substring str 0 pos))
+                                (tail (substring str
+                                                 (+ pos (string-length
+                                                         "-build-system"))))
+                                (name (string->symbol
+                                       (string-append head tail))))
+                           (hashq-set! by-name name val)))))
+                   iface))
+                (scheme-modules root "guix/build-system"))
+      by-name)))
+
+(define (all-potluck-build-system-names)
+  (sort
+   (hash-map->list (lambda (k v) k) (force all-build-systems))
+   (lambda (a b) (string<? (symbol->string a) (symbol->string b)))))
+
+(define (build-system-by-name name)
+  (hashq-ref (force all-build-systems) name))
diff --git a/guix/potluck/licenses.scm b/guix/potluck/licenses.scm
new file mode 100644
index 0000000..6efeee2
--- /dev/null
+++ b/guix/potluck/licenses.scm
@@ -0,0 +1,41 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017 Andy Wingo <address@hidden>
+;;;
+;;; 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 potluck licenses)
+  #:use-module ((guix licenses) #:select (license?))
+  #:use-module (ice-9 match)
+  #:export (license-by-name all-potluck-license-names))
+
+(define all-licenses
+  (delay
+    (let ((iface (resolve-interface '(guix licenses)))
+          (by-name (make-hash-table)))
+      (module-for-each (lambda (k var)
+                         (let ((val (variable-ref var)))
+                           (when (license? val)
+                             (hashq-set! by-name k val))))
+                       (resolve-interface '(guix licenses)))
+      by-name)))
+
+(define (all-potluck-license-names)
+  (sort
+   (hash-map->list (lambda (k v) k) (force all-licenses))
+   (lambda (a b) (string<? (symbol->string a) (symbol->string b)))))
+
+(define (license-by-name name)
+  (hashq-ref (force all-licenses) name))
diff --git a/guix/potluck/packages.scm b/guix/potluck/packages.scm
new file mode 100644
index 0000000..0f26553
--- /dev/null
+++ b/guix/potluck/packages.scm
@@ -0,0 +1,399 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès 
<address@hidden>
+;;; Copyright © 2014, 2015 Mark H Weaver <address@hidden>
+;;; Copyright © 2015 Eric Bavier <address@hidden>
+;;; Copyright © 2016 Alex Kost <address@hidden>
+;;; Copyright © 2017 Andy Wingo <address@hidden>
+;;;
+;;; 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 potluck packages)
+  #:use-module (gnu packages)
+  #:use-module (guix base32)
+  #:use-module (guix git-download)
+  #:use-module (guix packages)
+  #:use-module (guix potluck build-systems)
+  #:use-module (guix potluck licenses)
+  #:use-module (guix records)
+  #:use-module (guix utils)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (srfi srfi-9 gnu)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
+  #:use-module (web uri)
+  #:export (potluck-source
+            potluck-source?
+            potluck-source-git-uri
+            potluck-source-git-commit
+            potluck-source-sha256
+            potluck-source-snippet
+
+            potluck-package
+            potluck-package?
+            potluck-package-name
+            potluck-package-version
+            potluck-package-source
+            potluck-package-build-system
+            potluck-package-arguments
+            potluck-package-inputs
+            potluck-package-native-inputs
+            potluck-package-propagated-inputs
+            potluck-package-synopsis
+            potluck-package-description
+            potluck-package-license
+            potluck-package-home-page
+            potluck-package-location
+            potluck-package-field-location
+
+            pretty-print-potluck-source
+            pretty-print-potluck-package
+
+            validate-potluck-package
+
+            lower-potluck-source
+            lower-potluck-package))
+
+;;; Commentary:
+;;;
+;;; This module provides a facility to define "potluck packages" in a
+;;; Guix-based distribution, and a facility to translate those packages to
+;;; "normal" Guix packages.
+;;;
+;;; Code:
+
+(define-record-type* <potluck-source>
+  potluck-source make-potluck-source
+  potluck-source?
+  (git-uri    potluck-source-git-uri)               ; uri string
+  (git-commit potluck-source-git-commit)            ; git sha1 string
+  (sha256     potluck-source-sha256)                ; base32 string
+  (snippet    potluck-source-snippet (default #f))) ; sexp or #f
+
+(define-record-type* <potluck-package>
+  potluck-package make-potluck-package
+  potluck-package?
+  (name               potluck-package-name)         ; string
+  (version            potluck-package-version)      ; string
+  (source             potluck-package-source)       ; <potluck-source>
+                                                    ; instance
+  (build-system       potluck-package-build-system) ; build system name as
+                                                    ; symbol
+  (arguments          potluck-package-arguments     ; arguments for the build
+                                                    ; method
+                      (default '()) (thunked))
+  (inputs             potluck-package-inputs        ; input packages or
+                                                    ; derivations
+                      (default '()) (thunked))
+  (propagated-inputs  potluck-package-propagated-inputs ; same, but propagated
+                      (default '()) (thunked))
+  (native-inputs      potluck-package-native-inputs ; native input packages or
+                                                    ; derivations
+                      (default '()) (thunked))
+  (synopsis           potluck-package-synopsis)     ; one-line description
+  (description        potluck-package-description)  ; one or two paragraphs
+  (license            potluck-package-license)
+  (home-page          potluck-package-home-page)
+  (location           potluck-package-location
+                      (default (and=> (current-source-location)
+                                      source-properties->location))
+                      (innate)))
+
+;; Printers.
+
+(define (print-potluck-source potluck-source port)
+  "Write a concise representation of POTLUCK-SOURCE to PORT."
+  (match potluck-source
+    (($ <potluck-source> git-uri git-commit sha256 snippet)
+     (simple-format port "#<potluck-source address@hidden ~a ~a>"
+                    git-uri git-commit sha256
+                    (number->string (object-address potluck-source) 16)))))
+
+(define (print-potluck-package package port)
+  (let ((loc    (potluck-package-location package))
+        (format simple-format))
+    (format port "#<potluck-package address@hidden ~a~a>"
+            (potluck-package-name package)
+            (potluck-package-version package)
+            (if loc
+                (format #f "~a:~a "
+                        (location-file loc)
+                        (location-line loc))
+                "")
+            (number->string (object-address
+                             package)
+                            16))))
+
+(set-record-type-printer! <potluck-source> print-potluck-source)
+(set-record-type-printer! <potluck-package> print-potluck-package)
+
+;; Pretty-printers.
+
+(define* (pretty-print-potluck-source port source #:key (prefix "")
+                                      (suffix "\n"))
+  (let ((uri (potluck-source-git-uri source))
+        (commit (potluck-source-git-commit source))
+        (sha256 (potluck-source-sha256 source))
+        (snippet (potluck-source-snippet source)))
+    (format port "~a(potluck-source" prefix)
+    (format port "\n~a  (git-uri ~s)" prefix uri)
+    (format port "\n~a  (git-commit ~s)" prefix commit)
+    (format port "\n~a  (sha256 ~s)" prefix sha256)
+    (when snippet
+      (format port "\n~a  (snippet '~s)" prefix snippet))
+    (format port ")~a" suffix)))
+
+(define* (pretty-print-potluck-package port pkg #:key (prefix ""))
+  (let ((name (potluck-package-name pkg))
+        (version (potluck-package-version pkg))
+        (source (potluck-package-source pkg))
+        (build-system (potluck-package-build-system pkg))
+        (inputs (potluck-package-inputs pkg))
+        (native-inputs (potluck-package-native-inputs pkg))
+        (propagated-inputs (potluck-package-propagated-inputs pkg))
+        (arguments (potluck-package-arguments pkg))
+        (home-page (potluck-package-home-page pkg))
+        (synopsis (potluck-package-synopsis pkg))
+        (description (potluck-package-description pkg))
+        (license (potluck-package-license pkg)))
+    (format port "~a(potluck-package\n" prefix)
+    (format port "~a  (name ~s)\n" prefix name)
+    (format port "~a  (version ~s)\n" prefix version)
+    (format port "~a  (source\n" prefix)
+    (pretty-print-potluck-source port source #:prefix
+                                 (string-append prefix "    ")
+                                 #:suffix ")\n")
+    (format port "~a  (build-system '~s)\n" prefix build-system)
+    (format port "~a  (inputs '~s)\n" prefix inputs)
+    (format port "~a  (native-inputs '~s)\n" prefix native-inputs)
+    (format port "~a  (propagated-inputs '~s)\n" prefix propagated-inputs)
+    (match arguments
+      (()
+       (format port "~a  (arguments '())\n" prefix))
+      (arguments
+       (pretty-print `(arguments ',arguments) port
+                     #:per-line-prefix (format #f "~a  " prefix))))
+    (format port "~a  (home-page ~s)\n" prefix home-page)
+    (format port "~a  (synopsis ~s)\n" prefix synopsis)
+    (format port "~a  (description ~s)\n" prefix description)
+    (format port "~a  (license '~s))\n" prefix license)))
+
+;; Editing.
+
+(define (potluck-package-field-location package field)
+  "Return the source code location of the definition of FIELD for PACKAGE, or
+#f if it could not be determined."
+  (define (goto port line column)
+    (unless (and (= (port-column port) (- column 1))
+                 (= (port-line port) (- line 1)))
+      (unless (eof-object? (read-char port))
+        (goto port line column))))
+
+  (match (potluck-package-location package)
+    (($ <location> file line column)
+     (catch 'system
+       (lambda ()
+         ;; In general we want to keep relative file names for modules.
+         (with-fluids ((%file-port-name-canonicalization 'relative))
+           (call-with-input-file (search-path %load-path file)
+             (lambda (port)
+               (goto port line column)
+               (match (read port)
+                 (('potluck-package inits ...)
+                  (let ((field (assoc field inits)))
+                    (match field
+                      ((_ value)
+                       ;; Put the `or' here, and not in the first argument of
+                       ;; `and=>', to work around a compiler bug in 2.0.5.
+                       (or (and=> (source-properties value)
+                                  source-properties->location)
+                           (and=> (source-properties field)
+                                  source-properties->location)))
+                      (_
+                       #f))))
+                 (_
+                  #f))))))
+       (lambda _
+         #f)))
+    (_ #f)))
+
+;; Lower potluck packages to Guix packages.
+
+(define-condition-type &potluck-package-error &error
+  potluck-package-error?
+  (potluck-package potluck-package-error-potluck-package))
+
+(define-condition-type &potluck-package-validation-error &potluck-package-error
+  potluck-package-validation-error?
+  (field-name potluck-package-validation-error-field-name)
+  (assertion potluck-package-validation-error-assertion)
+  (value potluck-package-validation-error-value))
+
+(define (assertion-failed pkg field-name assertion value)
+  (raise (condition (&potluck-package-validation-error
+                     (potluck-package pkg)
+                     (field-name field-name)
+                     (assertion assertion)
+                     (value value)))))
+
+(define* (validate-public-uri pkg field-name str #:key (schemes '(http https)))
+  (define (public-host? host)
+    ;; There are other ways to spell "localhost" using raw IPv4 or IPv6
+    ;; addresses; this is just a sanity check.
+    (not (member host '("localhost" "127.0.0.1" "[::1]"))))
+  (let ((uri (and (string? str) (string->uri str))))
+    (unless (and uri
+                 (memq (uri-scheme uri) schemes)
+                 (not (uri-fragment uri))
+                 (public-host? (uri-host uri)))
+      (assertion-failed pkg field-name "public URI" str))))
+
+(define (validate-git-commit pkg field-name commit)
+  (unless (and (string? commit)
+               (= (string-length commit) 40)
+               (string-every (string->char-set "abcdef0123456789") commit))
+    (assertion-failed pkg field-name "full git commit SHA1 hash" commit)))
+
+(define (validate-base32-sha256 pkg field-name str)
+  (unless (and (string? str)
+               (= (string-length str) 52)
+               (false-if-exception (nix-base32-string->bytevector str)))
+    (assertion-failed pkg field-name "sha256 hash as a base32 string" str)))
+
+(define (validate-potluck-source pkg field-name source)
+  (validate-public-uri pkg field-name (potluck-source-git-uri source)
+                       #:schemes '(git http https))
+  (validate-git-commit pkg field-name (potluck-source-git-commit source))
+  (validate-base32-sha256 pkg field-name (potluck-source-sha256 source))
+  (validate-snippet pkg field-name (potluck-source-snippet source)))
+
+(define (validate-snippet pkg field-name snippet)
+  (match snippet
+    (#f #t)
+    ((_ ...) #t)
+    (_ (assertion-failed pkg field-name "valid snippet" snippet))))
+
+(define (validate-non-empty-string pkg field-name str)
+  (unless (and (string? str)
+               (not (string-null? str)))
+    (assertion-failed pkg field-name "non-empty string" str)))
+
+(define (validate-build-system pkg field-name sym)
+  (unless (build-system-by-name sym)
+    (assertion-failed pkg field-name "build system name as symbol" sym)))
+
+(define (validate-package-list pkg field-name l)
+  (unless (and (list? l) (and-map string? l))
+    (assertion-failed pkg field-name
+                      "list of package or address@hidden strings" l)))
+
+(define* (validate-keyword-arguments pkg field-name l #:optional (valid-kw? 
(const #t)))
+  (define validate-1
+    (case-lambda
+      (() #t)
+      ((k v . rest)
+       (unless (and (keyword? k) (valid-kw? k))
+         (assertion-failed pkg field-name "keyword" k))
+       (apply validate-1 rest))
+      (_ (assertion-failed pkg field-name "keyword argument list" l))))
+  (apply validate-1 l))
+
+(define (validate-arguments pkg field-name arguments)
+  (validate-keyword-arguments pkg field-name arguments))
+
+(define (validate-synopsis pkg field-name str)
+  (validate-non-empty-string pkg field-name str)
+  ;; The synopsis set by "guix potluck init".
+  (when (equal? str "Declarative synopsis here")
+    (assertion-failed pkg field-name "updated synopsis" str)))
+
+(define (validate-description pkg field-name str)
+  (validate-non-empty-string pkg field-name str)
+  ;; The description set by "guix potluck init".
+  (when (string-suffix? "..." str)
+    (assertion-failed pkg field-name "updated description" str)))
+
+(define (validate-license pkg field-name sym)
+  (unless (license-by-name sym)
+    (assertion-failed pkg field-name "license name as symbol" sym)))
+
+(define (validate-potluck-package pkg)
+  (validate-non-empty-string pkg 'name (potluck-package-name pkg))
+  (validate-non-empty-string pkg 'version (potluck-package-version pkg))
+  (validate-potluck-source pkg 'source (potluck-package-source pkg))
+  (validate-build-system pkg 'build-system (potluck-package-build-system pkg))
+  (validate-package-list pkg 'inputs (potluck-package-inputs pkg))
+  (validate-package-list pkg 'native-inputs
+                         (potluck-package-native-inputs pkg))
+  (validate-package-list pkg 'propagated-inputs
+                         (potluck-package-propagated-inputs pkg))
+  (validate-arguments pkg 'arguments (potluck-package-arguments pkg))
+  (validate-public-uri pkg 'home-page (potluck-package-home-page pkg))
+  (validate-synopsis pkg 'synopsis (potluck-package-synopsis pkg))
+  (validate-description pkg 'description (potluck-package-description pkg))
+  (validate-license pkg 'license (potluck-package-license pkg)))
+
+(define (lower-potluck-source o)
+  (let ((uri (potluck-source-git-uri o))
+        (commit (potluck-source-git-commit o))
+        (sha256 (potluck-source-sha256 o))
+        (snippet (potluck-source-snippet o)))
+    (origin
+      (method git-fetch)
+      (uri (git-reference
+            (url uri)
+            (commit commit)))
+      (snippet snippet)
+      (sha256 (base32 sha256)))))
+
+(define (lower-input input)
+  (call-with-values (lambda () (specification->package+output input))
+    (lambda (pkg output)
+      (cons* (package-name pkg) pkg
+             (if (equal? output "out")
+                 '()
+                 (list output))))))
+
+(define (lower-inputs inputs)
+  (map lower-input inputs))
+
+(define (lower-potluck-package pkg)
+  (validate-potluck-package pkg)
+  (let ((name (potluck-package-name pkg))
+        (version (potluck-package-version pkg))
+        (source (potluck-package-source pkg))
+        (build-system (potluck-package-build-system pkg))
+        (inputs (potluck-package-inputs pkg))
+        (native-inputs (potluck-package-native-inputs pkg))
+        (propagated-inputs (potluck-package-propagated-inputs pkg))
+        (arguments (potluck-package-arguments pkg))
+        (home-page (potluck-package-home-page pkg))
+        (synopsis (potluck-package-synopsis pkg))
+        (description (potluck-package-description pkg))
+        (license (potluck-package-license pkg)))
+    (package
+      (name name)
+      (version version)
+      (source (lower-potluck-source source))
+      (build-system (build-system-by-name build-system))
+      (inputs (lower-inputs inputs))
+      (native-inputs (lower-inputs native-inputs))
+      (propagated-inputs (lower-inputs propagated-inputs))
+      (arguments arguments)
+      (home-page home-page)
+      (synopsis synopsis)
+      (description description)
+      (license (license-by-name license)))))
diff --git a/guix/scripts/build.scm b/guix/scripts/build.scm
index 68402fd..3915476 100644
--- a/guix/scripts/build.scm
+++ b/guix/scripts/build.scm
@@ -23,6 +23,7 @@
   #:use-module (guix store)
   #:use-module (guix derivations)
   #:use-module (guix packages)
+  #:use-module (guix potluck packages)
   #:use-module (guix grafts)
 
   ;; Use the procedure that destructures "NAME-VERSION" forms.
@@ -577,11 +578,20 @@ must be one of 'package', 'all', or 'transitive'~%")
          (append %transformation-options
                  %standard-build-options)))
 
+(define (load-package-or-derivation-from-file file)
+  (let ((mod (make-user-module '())))
+    ;; Expose potluck-package and potluck-source to the file.
+    (module-use! mod (resolve-interface
+                      '(guix potluck packages)
+                      #:select '(potluck-package potluck-source)))
+    (load* file mod)))
+
 (define (options->things-to-build opts)
   "Read the arguments from OPTS and return a list of high-level objects to
 build---packages, gexps, derivations, and so on."
   (define (validate-type x)
-    (unless (or (package? x) (derivation? x) (gexp? x) (procedure? x))
+    (unless (or (package? x) (potluck-package? x)
+                (derivation? x) (gexp? x) (procedure? x))
       (leave (_ "~s: not something we can build~%") x)))
 
   (define (ensure-list x)
@@ -601,7 +611,7 @@ build---packages, gexps, derivations, and so on."
                        (else
                         (list (specification->package spec)))))
                 (('file . file)
-                 (ensure-list (load* file (make-user-module '()))))
+                 (ensure-list (load-package-or-derivation-from-file file)))
                 (('expression . str)
                  (ensure-list (read/eval str)))
                 (('argument . (? derivation? drv))
@@ -625,27 +635,31 @@ build."
   (define system (assoc-ref opts 'system))
   (define graft? (assoc-ref opts 'graft?))
 
+  (define (package->derivation-list p)
+    (let ((p (or (and graft? (package-replacement p)) p)))
+      (match src
+        (#f
+         (list (package->derivation store p system)))
+        (#t
+         (match (package-source p)
+           (#f
+            (format (current-error-port)
+                    (_ "~a: warning: package '~a' has no source~%")
+                    (location->string (package-location p))
+                    (package-name p))
+            '())
+           (s
+            (list (package-source-derivation store s)))))
+        (proc
+         (map (cut package-source-derivation store <>)
+              (proc p))))))
+
   (parameterize ((%graft? graft?))
     (append-map (match-lambda
                   ((? package? p)
-                   (let ((p (or (and graft? (package-replacement p)) p)))
-                     (match src
-                       (#f
-                        (list (package->derivation store p system)))
-                       (#t
-                        (match (package-source p)
-                          (#f
-                           (format (current-error-port)
-                                   (_ "~a: warning: \
-package '~a' has no source~%")
-                                   (location->string (package-location p))
-                                   (package-name p))
-                           '())
-                          (s
-                           (list (package-source-derivation store s)))))
-                       (proc
-                        (map (cut package-source-derivation store <>)
-                             (proc p))))))
+                   (package->derivation-list p))
+                  ((? potluck-package? p)
+                   (package->derivation-list (lower-potluck-package p)))
                   ((? derivation? drv)
                    (list drv))
                   ((? procedure? proc)
diff --git a/guix/scripts/potluck.scm b/guix/scripts/potluck.scm
new file mode 100644
index 0000000..8836798
--- /dev/null
+++ b/guix/scripts/potluck.scm
@@ -0,0 +1,458 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017 Andy Wingo <address@hidden>
+;;;
+;;; 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 scripts potluck)
+  #:use-module (guix config)
+  #:use-module (guix base32)
+  #:use-module ((guix build-system) #:select (build-system-description))
+  #:use-module ((guix licenses) #:select (license-uri))
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix potluck packages)
+  #:use-module (guix potluck build-systems)
+  #:use-module (guix potluck licenses)
+  #:use-module (guix scripts)
+  #:use-module (guix scripts hash)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (web uri)
+  #:export (guix-potluck))
+
+
+;;;
+;;; git utilities
+;;;
+
+(define-condition-type &git-condition &condition git-condition?
+  (argv git-condition-argv)
+  (output git-condition-output)
+  (status git-condition-status))
+
+(define-syntax false-if-git-error
+  (syntax-rules ()
+    ((_ body0 body ...)
+     (guard (c ((git-condition? c) #f))
+        body0 body ...))))
+
+(define (shell:quote str)
+  (with-output-to-string
+    (lambda ()
+      (display #\')
+      (string-for-each (lambda (ch)
+                         (if (eqv? ch #\')
+                             (begin (display #\\) (display #\'))
+                             (display ch)))
+                       str)
+      (display #\'))))
+
+(define (run env input-file args)
+  (define (prepend-env args)
+    (if (null? env)
+        args
+        (cons "env" (append env args))))
+  (define (redirect-input args)
+    (if input-file
+        (list "sh" "-c"
+              (string-append (string-join (map shell:quote args) " ")
+                             "<" input-file))
+        args))
+  (let* ((real-args (redirect-input (prepend-env args)))
+         (pipe (apply open-pipe* OPEN_READ real-args))
+         (output (get-string-all pipe))
+         (ret (close-pipe pipe)))
+    (case (status:exit-val ret)
+      ((0) output)
+      (else (raise (condition (&git-condition
+                               (argv real-args)
+                               (output output)
+                               (status ret))))))))
+
+(define* (git* args #:key (input #f) (env '()))
+  (if input
+      (call-with-temporary-output-file
+       (lambda (file-name file-port)
+         (display input file-port)
+         (close-port file-port)
+         (run env file-name (cons* "git" args))))
+      (run env #f (cons* "git" args))))
+
+(define (git . args)
+  (git* args))
+
+(define (git-rev-parse rev)
+  (string-trim-both (git "rev-parse" rev)))
+
+(define (git-config key)
+  (string-trim-both (git "config" key)))
+
+(define* (git-describe #:optional (ref "HEAD"))
+  (string-trim-both (git "describe")))
+
+
+;;;
+;;; guix potluck init
+;;;
+
+(define* (init-potluck host-url remote-git-url #:key
+                       (build-system 'gnu) (autoreconf? #f)
+                       (license 'gplv3+))
+  (let* ((cwd (getcwd))
+         (dot-git (in-vicinity cwd ".git"))
+         (potluck-dir (in-vicinity cwd "potluck"))
+         (package-name (basename cwd)))
+    (unless (and (file-exists? dot-git)
+                 (file-is-directory? dot-git))
+      (leave (_ "init: must be run from the root of a git checkout~%")))
+    (when (file-exists? potluck-dir)
+      (leave (_ "init: ~a already exists~%") potluck-dir))
+    (let* ((user-name (git-config "user.name"))
+           (pkg-name (basename cwd))
+           (pkg-commit (git-rev-parse "HEAD"))
+           (pkg-version
+            (catch #t
+              (lambda () (git-describe pkg-commit))
+              (lambda _
+                (format (current-error-port)
+                        "guix potluck init: git describe failed\n")
+                (format (current-error-port)
+                        "Add a tag so that git can compute a version.\n")
+                (exit 1))))
+           ;; FIXME: Race condition if HEAD changes between git-rev-parse and
+           ;; here.
+           (pkg-sha256 (guix-hash-git-checkout cwd)))
+      (format #t (_ "Creating potluck/~%"))
+      (mkdir potluck-dir)
+      (format #t (_ "Creating potluck/README.md~%"))
+      (call-with-output-file (in-vicinity potluck-dir "README.md")
+        (lambda (port)
+          (format port
+                  "\
+This directory defines potluck packages.  Each file in this directory should
+define one package.  See https://potluck.guixsd.org/ for more information.
+")))
+      (format #t (_ "Creating potluck/~a.scm~%") package-name)
+      (call-with-output-file (in-vicinity potluck-dir
+                                          (string-append package-name ".scm"))
+        (lambda (port)
+          
+          (define-syntax-rule (dsp exp) (display exp port))
+          (dsp ";;; guix potluck package\n")
+          (dsp ";;; Copyright (C) 2017 ")
+          (dsp user-name)
+          (dsp "\n")
+          (dsp "
+;;; This file 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.  No warranty.  See
+;;; https://www.gnu.org/licenses/gpl.html for a copy of the GPLv3.
+
+")
+          (pretty-print-potluck-package
+           port
+           (potluck-package
+            (name pkg-name)
+            (version pkg-version)
+            (source
+             (potluck-source
+              (git-uri remote-git-url)
+              (git-commit pkg-commit)
+              (sha256 (bytevector->nix-base32-string pkg-sha256))))
+            (build-system build-system)
+            (inputs '())
+            (native-inputs
+             (if autoreconf?
+                 '("autoconf" "automake" "libtool" "pkg-config")
+                 '()))
+            (arguments
+             (if autoreconf?
+                 '(#:phases (modify-phases %standard-phases
+                              (add-before 'configure 'autoconf
+                                (lambda _
+                                  (zero?
+                                   (system* "autoreconf" "-vfi"))))))
+                 '()))
+            (home-page remote-git-url)
+            (synopsis "Declarative synopsis here")
+            (description
+             (string-append (string-titlecase pkg-name)
+                            " is a ..."))
+            (license license)))))
+      (format #t (_ "
+Done.  Now open potluck/~a.scm in your editor, fill out its \"synopsis\" and
+\"description\" fields, add dependencies to the 'inputs' field, and try to
+build with
+
+  guix build --file=potluck/~a.scm
+
+When you get that working, commit your results to git via:
+
+  git add potluck && git commit -m 'Add initial Guix potluck files.'
+") pkg-name pkg-name))))
+
+
+;;;
+;;; guix potluck register
+;;;
+
+(define (register-potluck host-url git-url branch)
+  #t)
+
+
+;;;
+;;; guix potluck host-channel
+;;;
+
+(define (host-potluck host-url local-port local-git-checkout-dir)
+  #t)
+
+
+;;;
+;;; guix potluck request-update
+;;;
+
+(define (request-potluck-update host-url git-url branch)
+  #t)
+
+
+;;;
+;;; Options.
+;;;
+
+(define (show-help)
+  (display (_ "Usage: guix potluck [OPTION ...] ACTION [ARG ...]
+Create \"potluck\" packages, register them with a central service, and arrange
+to serve those packages as a Guix channel. Some ACTIONS require additional
+ARGS.\n"))
+  (newline)
+  (display (_ "The valid values for ACTION are:\n"))
+  (newline)
+  (display (_ "\
+   init             create potluck recipe for current working directory\n"))
+  (display (_ "\
+   register         register remote git branch with potluck host\n"))
+  (display (_ "\
+   host-channel     run web service providing potluck packages as Guix 
channel\n"))
+  (display (_ "\
+   request-update   ask potluck host to update a potluck package\n"))
+
+  (newline)
+  (display (_ "The available OPTION flags are:\n"))
+  (display (_ "
+      --host-url=URL     for 'register', 'host-channel', and 'request-update',
+                         the URL of the channel host
+                         (default: https://potluck.guixsd.org/)"))
+  (display (_ "
+      --port=PORT        for 'host-channel', the local TCP port on which to
+                         listen for HTTP connections
+                         (default: 8080)"))
+  (display (_ "
+      --build-system=SYS for 'init', specify the build system.  Use
+                         --build-system=help for all available options."))
+  (display (_ "
+      --autotools        for 'init', like --build-system=gnu but additionally
+                         indicating that the package needs autoreconf before
+                         running ./configure"))
+  (display (_ "
+      --license=LICENSE  for 'init', specify the license of the package.  Use
+                         --license=help for all available options."))
+  (display (_ "
+      --verbosity=LEVEL  use the given verbosity LEVEL"))
+  (newline)
+  (display (_ "
+  -h, --help             display this help and exit"))
+  (display (_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix potluck")))
+        (option '("build-system") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'build-system arg result)))
+        (option '("autotools") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'autoreconf? #t
+                              (alist-cons 'build-system "gnu" result))))
+        (option '("license") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'license arg result)))
+        (option '("host-url") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'host-url arg result)))
+        (option '("port") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'port arg result)))
+        (option '("verbosity") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'verbosity (string->number arg) result)))))
+
+(define %default-options
+  ;; Alist of default option values.
+  `((host-url . "https://potluck.guixsd.org/";)
+    (port . "8080")
+    (verbosity . 0)))
+
+(define (parse-url url-str)
+  (unless (string->uri url-str)
+    (leave (_ "invalid url: ~a~%") url-str))
+  url-str)
+
+(define (parse-port port-str)
+  (let ((port (string->number port-str)))
+    (cond
+     ((and port (exact-integer? port) (<= 0 port #xffff))
+      port)
+     (else
+      (leave (_ "invalid port: ~a~%") port-str)))))
+
+(define (parse-build-system sys-str)
+  (unless sys-str
+    (leave (_ "\
+init: missing --build-system; try --build-system=help for options~%")))
+  (let ((sys (string->symbol (string-downcase sys-str))))
+    (when (eq? sys 'help)
+      (format #t "guix potluck: Available build systems:~%")
+      (for-each
+       (lambda (name)
+         (let ((sys (build-system-by-name name)))
+           (format #t "  ~a ~25t~a~%" name (build-system-description sys))))
+       (all-potluck-build-system-names))
+      (exit 0))
+    (unless (build-system-by-name sys)
+      (leave (_ "invalid build system: ~a; try --build-system=help~%") sys))
+    sys))
+
+(define (parse-license license-str)
+  (unless license-str
+    (leave (_ "init: missing --license; try --license=help for options~%")))
+  (let ((license (string->symbol (string-downcase license-str))))
+    (when (eq? license 'help)
+      (format #t "guix potluck: Available licenses:~%")
+      (for-each
+       (lambda (name)
+         (let ((license (license-by-name name)))
+           (format #t "  ~a ~25t~a~%" name (license-uri license))))
+       (all-potluck-license-names))
+      (format #t "
+If your package's license is not in this list, add it to Guix first.~%")
+      (exit 0))
+    (unless (license-by-name license)
+      (leave (_ "invalid license: ~a; try --license=help~%") license))
+    license))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-potluck . args)
+  (define (parse-sub-command arg result)
+    (if (assoc-ref result 'action)
+        (alist-cons 'argument arg result)
+        (alist-cons 'action (string->symbol arg) result)))
+
+  (define (match-pair car)
+    ;; Return a procedure that matches a pair with CAR.
+    (match-lambda
+      ((head . tail)
+       (and (eq? car head) tail))
+      (_ #f)))
+
+  (with-error-handling
+    (let* ((opts     (parse-command-line args %options
+                                         (list %default-options)
+                                         #:argument-handler
+                                         parse-sub-command))
+           (action   (assoc-ref opts 'action))
+           (args     (reverse (filter-map (match-pair 'argument) opts))))
+      (define (see-help)
+        (format (current-error-port)
+                (_ "Try 'guix potluck --help' for more information.~%")))
+      (define (wrong-number-of-args usage)
+        (format (current-error-port)
+                (_ "guix potluck ~a: wrong number of arguments~%")
+                action)
+        (display usage (current-error-port))
+        (newline (current-error-port))
+        (see-help)
+        (exit 1))
+      (match action
+        (#f
+         (format (current-error-port)
+                 (_ "guix potluck: missing command name~%"))
+         (see-help)
+         (exit 1))
+        ('init
+         (match args
+           ((remote-git-url)
+            (init-potluck (parse-url (assoc-ref opts 'host-url))
+                          (parse-url remote-git-url)
+                          #:build-system (parse-build-system
+                                          (assoc-ref opts 'build-system))
+                          #:autoreconf? (assoc-ref opts 'autoreconf?)
+                          #:license (parse-license
+                                     (assoc-ref opts 'license))))
+           (args
+            (wrong-number-of-args
+             (_ "usage: guix potluck init [OPT...] REMOTE-GIT-URL")))))
+        ('register
+         (match args
+           ((remote-git-url branch)
+            (register-potluck (parse-url (assoc-ref opts 'host-url))
+                              (parse-url remote-git-url)
+                              branch))
+           (args
+            (wrong-number-of-args
+             (_ "usage: guix potluck register REMOTE-GIT-URL BRANCH-NAME")))))
+        ('host-channel
+         (match args
+           ((local-git-checkout)
+            (host-potluck (parse-url (assoc-ref opts 'host-url))
+                          (parse-port (assoc-ref opts 'port))
+                          local-git-checkout))
+           (args
+            (wrong-number-of-args
+             (_ "usage: guix potluck host-channel CHANNEL-DIRECTORY"))
+            (exit 1))))
+        ('request-update
+         (match args
+           ((remote-git-url branch)
+            (request-potluck-update (parse-url (assoc-ref opts 'host-url))
+                                    (parse-url remote-git-url)
+                                    branch))
+           (args
+            (wrong-number-of-args
+             (_ "usage: guix potluck request-update REMOTE-GIT-URL 
BRANCH-NAME")))))
+        (action
+         (leave (_ "~a: unknown action~%") action))))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]