git-download.scm 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2017 Mathieu Lirzin <mthl@gnu.org>
  4. ;;; Copyright © 2017 Christopher Baines <mail@cbaines.net>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix git-download)
  21. #:use-module (guix gexp)
  22. #:use-module (guix store)
  23. #:use-module (guix monads)
  24. #:use-module (guix records)
  25. #:use-module (guix packages)
  26. #:use-module (guix modules)
  27. #:autoload (guix build-system gnu) (standard-packages)
  28. #:use-module (git)
  29. #:use-module (ice-9 match)
  30. #:use-module (ice-9 vlist)
  31. #:use-module (srfi srfi-1)
  32. #:export (git-reference
  33. git-reference?
  34. git-reference-url
  35. git-reference-commit
  36. git-reference-recursive?
  37. git-fetch
  38. git-version
  39. git-file-name
  40. git-predicate))
  41. ;;; Commentary:
  42. ;;;
  43. ;;; An <origin> method that fetches a specific commit from a Git repository.
  44. ;;; The repository URL and commit hash are specified with a <git-reference>
  45. ;;; object.
  46. ;;;
  47. ;;; Code:
  48. (define-record-type* <git-reference>
  49. git-reference make-git-reference
  50. git-reference?
  51. (url git-reference-url)
  52. (commit git-reference-commit)
  53. (recursive? git-reference-recursive? ; whether to recurse into sub-modules
  54. (default #f)))
  55. (define (git-package)
  56. "Return the default Git package."
  57. (let ((distro (resolve-interface '(gnu packages version-control))))
  58. (module-ref distro 'git-minimal)))
  59. (define* (git-fetch ref hash-algo hash
  60. #:optional name
  61. #:key (system (%current-system)) (guile (default-guile))
  62. (git (git-package)))
  63. "Return a fixed-output derivation that fetches REF, a <git-reference>
  64. object. The output is expected to have recursive hash HASH of type
  65. HASH-ALGO (a symbol). Use NAME as the file name, or a generic name if #f."
  66. (define inputs
  67. ;; When doing 'git clone --recursive', we need sed, grep, etc. to be
  68. ;; available so that 'git submodule' works.
  69. (if (git-reference-recursive? ref)
  70. (standard-packages)
  71. ;; The 'swh-download' procedure requires tar and gzip.
  72. `(("gzip" ,(module-ref (resolve-interface '(gnu packages compression))
  73. 'gzip))
  74. ("tar" ,(module-ref (resolve-interface '(gnu packages base))
  75. 'tar)))))
  76. (define zlib
  77. (module-ref (resolve-interface '(gnu packages compression)) 'zlib))
  78. (define guile-json
  79. (module-ref (resolve-interface '(gnu packages guile)) 'guile-json-3))
  80. (define gnutls
  81. (module-ref (resolve-interface '(gnu packages tls)) 'gnutls))
  82. (define config.scm
  83. (scheme-file "config.scm"
  84. #~(begin
  85. (define-module (guix config)
  86. #:export (%libz))
  87. (define %libz
  88. #+(file-append zlib "/lib/libz")))))
  89. (define modules
  90. (cons `((guix config) => ,config.scm)
  91. (delete '(guix config)
  92. (source-module-closure '((guix build git)
  93. (guix build utils)
  94. (guix build download-nar)
  95. (guix swh))))))
  96. (define build
  97. (with-imported-modules modules
  98. (with-extensions (list guile-json gnutls) ;for (guix swh)
  99. #~(begin
  100. (use-modules (guix build git)
  101. (guix build utils)
  102. (guix build download-nar)
  103. (guix swh)
  104. (ice-9 match))
  105. (define recursive?
  106. (call-with-input-string (getenv "git recursive?") read))
  107. ;; The 'git submodule' commands expects Coreutils, sed,
  108. ;; grep, etc. to be in $PATH.
  109. (set-path-environment-variable "PATH" '("bin")
  110. (match '#+inputs
  111. (((names dirs outputs ...) ...)
  112. dirs)))
  113. (setvbuf (current-output-port) 'line)
  114. (setvbuf (current-error-port) 'line)
  115. (or (git-fetch (getenv "git url") (getenv "git commit")
  116. #$output
  117. #:recursive? recursive?
  118. #:git-command (string-append #+git "/bin/git"))
  119. (download-nar #$output)
  120. ;; As a last resort, attempt to download from Software Heritage.
  121. ;; XXX: Currently recursive checkouts are not supported.
  122. (and (not recursive?)
  123. (begin
  124. (format (current-error-port)
  125. "Trying to download from Software Heritage...~%")
  126. (swh-download (getenv "git url") (getenv "git commit")
  127. #$output))))))))
  128. (mlet %store-monad ((guile (package->derivation guile system)))
  129. (gexp->derivation (or name "git-checkout") build
  130. ;; Use environment variables and a fixed script name so
  131. ;; there's only one script in store for all the
  132. ;; downloads.
  133. #:script-name "git-download"
  134. #:env-vars
  135. `(("git url" . ,(git-reference-url ref))
  136. ("git commit" . ,(git-reference-commit ref))
  137. ("git recursive?" . ,(object->string
  138. (git-reference-recursive? ref))))
  139. #:leaked-env-vars '("http_proxy" "https_proxy"
  140. "LC_ALL" "LC_MESSAGES" "LANG"
  141. "COLUMNS")
  142. #:system system
  143. #:local-build? #t ;don't offload repo cloning
  144. #:hash-algo hash-algo
  145. #:hash hash
  146. #:recursive? #t
  147. #:guile-for-build guile)))
  148. (define (git-version version revision commit)
  149. "Return the version string for packages using git-download."
  150. (string-append version "-" revision "." (string-take commit 7)))
  151. (define (git-file-name name version)
  152. "Return the file-name for packages using git-download."
  153. (string-append name "-" version "-checkout"))
  154. ;;;
  155. ;;; 'git-predicate'.
  156. ;;;
  157. (define (git-file-list directory)
  158. "Return the list of files checked in in the Git repository at DIRECTORY.
  159. The result is similar to that of the 'git ls-files' command, except that it
  160. also includes directories, not just regular files. The returned file names
  161. are relative to DIRECTORY, which is not necessarily the root of the checkout."
  162. (let* (;; 'repository-working-directory' always returns a trailing "/",
  163. ;; so add one here to ease the comparisons below.
  164. (directory (string-append (canonicalize-path directory) "/"))
  165. (dot-git (repository-discover directory))
  166. (repository (repository-open dot-git))
  167. (workdir (repository-working-directory repository))
  168. (head (repository-head repository))
  169. (oid (reference-target head))
  170. (commit (commit-lookup repository oid))
  171. (tree (commit-tree commit))
  172. (files (tree-list tree)))
  173. (repository-close! repository)
  174. (if (string=? workdir directory)
  175. files
  176. (let ((relative (string-drop directory (string-length workdir))))
  177. (filter-map (lambda (file)
  178. (and (string-prefix? relative file)
  179. (string-drop file (string-length relative))))
  180. files)))))
  181. (define (git-predicate directory)
  182. "Return a predicate that returns true if a file is part of the Git checkout
  183. living at DIRECTORY. If DIRECTORY does not lie within a Git checkout, and
  184. upon Git errors, return #f instead of a predicate.
  185. The returned predicate takes two arguments FILE and STAT where FILE is an
  186. absolute file name and STAT is the result of 'lstat'."
  187. (catch 'git-error
  188. (lambda ()
  189. (let* ((files (git-file-list directory))
  190. (inodes (fold (lambda (file result)
  191. (let ((stat
  192. (lstat (string-append directory "/"
  193. file))))
  194. (vhash-consv (stat:ino stat) (stat:dev stat)
  195. result)))
  196. vlist-null
  197. files)))
  198. (lambda (file stat)
  199. ;; Comparing file names is always tricky business so we rely on inode
  200. ;; numbers instead.
  201. (match (vhash-assv (stat:ino stat) inodes)
  202. ((_ . dev) (= dev (stat:dev stat)))
  203. (#f #f)))))
  204. (const #f)))
  205. ;;; git-download.scm ends here