summaryrefslogtreecommitdiff
path: root/guix/build/ruby-build-system.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guix/build/ruby-build-system.scm')
-rw-r--r--guix/build/ruby-build-system.scm184
1 files changed, 156 insertions, 28 deletions
diff --git a/guix/build/ruby-build-system.scm b/guix/build/ruby-build-system.scm
index c2d2766279..09ae2390a5 100644
--- a/guix/build/ruby-build-system.scm
+++ b/guix/build/ruby-build-system.scm
@@ -21,14 +21,15 @@
(define-module (guix build ruby-build-system)
#:use-module ((guix build gnu-build-system) #:prefix gnu:)
#:use-module (guix build utils)
+ #:use-module (ice-9 ftw)
#:use-module (ice-9 match)
#:use-module (ice-9 popen)
+ #:use-module (ice-9 rdelim)
#:use-module (ice-9 regex)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:export (%standard-phases
- ruby-build
- gem-home))
+ ruby-build))
;; Commentary:
;;
@@ -129,43 +130,179 @@ GEM-FLAGS are passed to the 'gem' invokation, if present."
(assoc-ref inputs "ruby"))
1))
(out (assoc-ref outputs "out"))
- (gem-home (string-append out "/lib/ruby/gems/" ruby-version ".0"))
+ (vendor-dir (string-append out "/lib/ruby/vendor_ruby"))
(gem-file (first-matching-file "\\.gem$"))
(gem-file-basename (basename gem-file))
(gem-name (substring gem-file-basename
0
- (- (string-length gem-file-basename) 4)))
- (gem-directory (string-append gem-home "/gems/" gem-name)))
- (setenv "GEM_HOME" gem-home)
- (mkdir-p gem-home)
- (and (apply system* "gem" "install" gem-file
- "--local" "--ignore-dependencies"
- ;; Executables should go into /bin, not /lib/ruby/gems.
- "--bindir" (string-append out "/bin")
- gem-flags)
+ (- (string-length gem-file-basename) 4))))
+ (setenv "GEM_VENDOR" vendor-dir)
+ (and (let ((install-succeeded?
+ (zero?
+ (apply system* "gem" "install" gem-file
+ "--local" "--ignore-dependencies" "--vendor"
+ ;; Executables should go into /bin, not
+ ;; /lib/ruby/gems.
+ "--bindir" (string-append out "/bin")
+ gem-flags))))
+ (or install-succeeded?
+ (begin
+ (simple-format #t "installation failed\n")
+ (let ((failed-output-dir (string-append (getcwd) "/out")))
+ (mkdir failed-output-dir)
+ (copy-recursively out failed-output-dir))
+ #f)))
(begin
;; Remove the cached gem file as this is unnecessary and contains
;; timestamped files rendering builds not reproducible.
- (let ((cached-gem (string-append gem-home "/cache/" gem-file)))
+ (let ((cached-gem (string-append vendor-dir "/cache/" gem-file)))
(log-file-deletion cached-gem)
(delete-file cached-gem))
;; For gems with native extensions, several Makefile-related files
;; are created that contain timestamps or other elements making
;; them not reproducible. They are unnecessary so we remove them.
- (if (file-exists? (string-append gem-directory "/ext"))
+ (if (file-exists? (string-append vendor-dir "/ext"))
(begin
(for-each (lambda (file)
(log-file-deletion file)
(delete-file file))
(append
- (find-files (string-append gem-home "/doc")
+ (find-files (string-append vendor-dir "/doc")
"page-Makefile.ri")
- (find-files (string-append gem-home "/extensions")
+ (find-files (string-append vendor-dir "/extensions")
"gem_make.out")
- (find-files (string-append gem-directory "/ext")
+ (find-files (string-append vendor-dir "/ext")
"Makefile")))))
#t))))
+(define* (wrap-ruby-program prog #:key (gem-clear-paths #t) #:rest vars)
+ "Make a wrapper for PROG. VARS should look like this:
+
+ '(VARIABLE DELIMITER POSITION LIST-OF-DIRECTORIES)
+
+where DELIMITER is optional. ':' will be used if DELIMITER is not given.
+
+For example, this command:
+
+ (wrap-ruby-program \"foo\"
+ '(\"PATH\" \":\" = (\"/gnu/.../bar/bin\"))
+ '(\"CERT_PATH\" suffix (\"/gnu/.../baz/certs\"
+ \"/qux/certs\")))
+
+will copy 'foo' to '.real/fool' and create the file 'foo' with the following
+contents:
+
+ #!location/of/bin/ruby
+ ENV['PATH'] = \"/gnu/.../bar/bin\"
+ ENV['CERT_PATH'] = (ENV.key?('CERT_PATH') ? (ENV['CERT_PATH'] + ':') : '') + '/gnu/.../baz/certs:/qux/certs'
+ load location/of/.real/foo
+
+This is useful for scripts that expect particular programs to be in $PATH, for
+programs that expect particular gems to be in the GEM_PATH.
+
+This is preferable to wrap-program, which uses a bash script, as this prevents
+ruby scripts from being executed with @command{ruby -S ...}.
+
+If PROG has previously been wrapped by 'wrap-ruby-program', the wrapper is
+extended with definitions for VARS."
+ (define wrapped-file
+ (string-append (dirname prog) "/.real/" (basename prog)))
+
+ (define already-wrapped?
+ (file-exists? wrapped-file))
+
+ (define (last-line port)
+ ;; Return the last line read from PORT and leave PORT's cursor right
+ ;; before it.
+ (let loop ((previous-line-offset 0)
+ (previous-line "")
+ (position (seek port 0 SEEK_CUR)))
+ (match (read-line port 'concat)
+ ((? eof-object?)
+ (seek port previous-line-offset SEEK_SET)
+ previous-line)
+ ((? string? line)
+ (loop position line (+ (string-length line) position))))))
+
+ (define (export-variable lst)
+ ;; Return a string that exports an environment variable.
+ (match lst
+ ((var sep '= rest)
+ (format #f "ENV['~a'] = '~a'"
+ var (string-join rest sep)))
+ ((var sep 'prefix rest)
+ (format #f "ENV['~a'] = '~a' + (ENV.key?('~a') ? ('~a' + ENV['~a']) : '')"
+ var (string-join rest sep) var sep var))
+ ((var sep 'suffix rest)
+ (format #f "ENV['~a'] = (ENV.key?('~a') ? (ENV['~a'] + '~a') : '') + '~a'"
+ var var var sep (string-join rest sep)))
+ ((var '= rest)
+ (format #f "ENV['~a'] = '~a'"
+ var (string-join rest ":")))
+ ((var 'prefix rest)
+ (format #f "ENV['~a'] = '~a' + (ENV.key?('~a') ? (':' + ENV['~a']) : '')"
+ var (string-join rest ":") var var))
+ ((var 'suffix rest)
+ (format #f "ENV['~a'] = (ENV.key?('~a') ? (ENV['~a'] + ':') : '') + '~a'"
+ var var var (string-join rest ":")))))
+
+ (if already-wrapped?
+
+ ;; PROG is already a wrapper: add the new "export VAR=VALUE" lines just
+ ;; before the last line.
+ (let* ((port (open-file prog "r+"))
+ (last (last-line port)))
+ (for-each (lambda (var)
+ (display (export-variable var) port)
+ (newline port))
+ vars)
+ (display last port)
+ (close-port port))
+
+ ;; PROG is not wrapped yet: create a shell script that sets VARS.
+ (let ((prog-tmp (string-append wrapped-file "-tmp")))
+ (mkdir-p (dirname prog-tmp))
+ (link prog wrapped-file)
+
+ (call-with-output-file prog-tmp
+ (lambda (port)
+ (format port
+ "#!~a~%~a~%~a~%load '~a'~%"
+ (which "ruby")
+ (string-join (map export-variable vars) "\n")
+ ;; This ensures that if the GEM_PATH has been changed,
+ ;; then that change will be noticed.
+ (if gem-clear-paths "Gem.clear_paths" "")
+ (canonicalize-path wrapped-file))))
+
+ (chmod prog-tmp #o755)
+ (rename-file prog-tmp prog))))
+
+(define* (wrap #:key inputs outputs #:allow-other-keys)
+ (define (list-of-files dir)
+ (map (cut string-append dir "/" <>)
+ (or (scandir dir (lambda (f)
+ (let ((s (stat (string-append dir "/" f))))
+ (eq? 'regular (stat:type s)))))
+ '())))
+
+ (define bindirs
+ (append-map (match-lambda
+ ((_ . dir)
+ (list (string-append dir "/bin")
+ (string-append dir "/sbin"))))
+ outputs))
+
+ (let* ((out (assoc-ref outputs "out"))
+ (var `("GEM_PATH" prefix
+ (,(string-append out "/lib/ruby/vendor_ruby")
+ ,(getenv "GEM_PATH")))))
+ (for-each (lambda (dir)
+ (let ((files (list-of-files dir)))
+ (for-each (cut wrap-ruby-program <> var)
+ files)))
+ bindirs)))
+
(define (log-file-deletion file)
(display (string-append "deleting '" file "' for reproducibility\n")))
@@ -177,18 +314,9 @@ GEM-FLAGS are passed to the 'gem' invokation, if present."
(add-after 'extract-gemspec 'replace-git-ls-files replace-git-ls-files)
(replace 'build build)
(replace 'check check)
- (replace 'install install)))
+ (replace 'install install)
+ (add-after 'install 'wrap wrap)))
(define* (ruby-build #:key inputs (phases %standard-phases)
#:allow-other-keys #:rest args)
(apply gnu:gnu-build #:inputs inputs #:phases phases args))
-
-(define (gem-home store-path ruby-version)
- "Return a string to the gem home directory in the store given a STORE-PATH
-and the RUBY-VERSION used to build that ruby package"
- (string-append
- store-path
- "/lib/ruby/gems/"
- (regexp-substitute #f
- (string-match "^[0-9]+\\.[0-9]+" ruby-version)
- 0 ".0")))