clojure-build-system.scm 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018 Alex Vong <alexvong1995@gmail.com>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix build clojure-build-system)
  19. #:use-module ((guix build ant-build-system)
  20. #:select ((%standard-phases . %standard-phases@ant)
  21. ant-build))
  22. #:use-module (guix build clojure-utils)
  23. #:use-module (guix build java-utils)
  24. #:use-module (guix build utils)
  25. #:use-module (ice-9 match)
  26. #:use-module (ice-9 regex)
  27. #:use-module (srfi srfi-1)
  28. #:use-module (srfi srfi-26)
  29. #:export (%standard-phases
  30. clojure-build
  31. reset-class-timestamps))
  32. ;; Commentary:
  33. ;;
  34. ;; Builder-side code of the standard build procedure for Clojure packages.
  35. ;;
  36. ;; Code:
  37. (define* (compile-java #:key
  38. java-source-dirs java-compile-dir
  39. #:allow-other-keys)
  40. "Compile java sources for use in clojure-build-system."
  41. (let ((java-files (append-map (lambda (dir)
  42. (find-files dir "\\.java$"))
  43. java-source-dirs)))
  44. (mkdir-p java-compile-dir)
  45. (when (not (null? java-files))
  46. (apply invoke
  47. "javac"
  48. "-verbose"
  49. "-d" java-compile-dir
  50. java-files))))
  51. (define* (build #:key
  52. source-dirs java-source-dirs
  53. compile-dir java-compile-dir
  54. jar-names main-class omit-source?
  55. aot-include aot-exclude
  56. #:allow-other-keys)
  57. "Standard 'build' phase for clojure-build-system."
  58. (let* ((libs (append-map find-clojure-libs source-dirs))
  59. (libs* (include-list\exclude-list aot-include
  60. aot-exclude
  61. #:all-list libs)))
  62. (mkdir-p compile-dir)
  63. (eval-with-clojure `(run! compile ',libs*)
  64. (cons* compile-dir
  65. java-compile-dir
  66. source-dirs))
  67. (let ((source-dir-files-alist (map (lambda (dir)
  68. (cons dir (find-files* dir)))
  69. (append source-dirs
  70. java-source-dirs)))
  71. ;; workaround transitive compilation in Clojure
  72. (classes (filter (lambda (class)
  73. (any (cut compiled-from? class <>)
  74. libs*))
  75. (find-files* compile-dir))))
  76. (for-each (cut create-jar <> (cons* (cons compile-dir classes)
  77. (cons java-compile-dir
  78. (find-files* java-compile-dir))
  79. (if omit-source?
  80. '()
  81. source-dir-files-alist))
  82. #:main-class main-class)
  83. jar-names)
  84. #t)))
  85. (define* (check #:key
  86. test-dirs
  87. jar-names
  88. tests? test-include test-exclude
  89. #:allow-other-keys)
  90. "Standard 'check' phase for clojure-build-system. Note that TEST-EXCLUDE has
  91. priority over TEST-INCLUDE."
  92. (if tests?
  93. (let* ((libs (append-map find-clojure-libs test-dirs))
  94. (libs* (include-list\exclude-list test-include
  95. test-exclude
  96. #:all-list libs)))
  97. (for-each (lambda (jar)
  98. (eval-with-clojure `(do (apply require
  99. '(clojure.test ,@libs*))
  100. (if (clojure.test/successful?
  101. (apply clojure.test/run-tests
  102. ',libs*))
  103. (System/exit 0)
  104. (System/exit 1)))
  105. (cons jar test-dirs)))
  106. jar-names)))
  107. #t)
  108. (define (regular-jar-file? file stat)
  109. "Predicate returning true if FILE is ending on '.jar'
  110. and STAT indicates it is a regular file."
  111. (and (string-suffix? ".jar" file)
  112. (eq? 'regular (stat:type stat))))
  113. ;; XXX: The only difference compared to 'strip-jar-timestamps' in
  114. ;; ant-build-system.scm is the date. TODO: Adjust and factorize.
  115. (define* (reset-class-timestamps #:key outputs #:allow-other-keys)
  116. "Unpack all jar archives, reset the timestamp of all contained class files,
  117. and repack them. This is necessary to ensure that archives are reproducible."
  118. (define (repack-archive jar)
  119. (format #t "resetting class timestamps and repacking ~a\n" jar)
  120. ;; Note: .class files need to be strictly newer than source files,
  121. ;; otherwise the Clojure compiler will recompile sources.
  122. (let* ((early-1980 315619200) ; 1980-01-02 UTC
  123. (dir (mkdtemp "jar-contents.XXXXXX"))
  124. (manifest (string-append dir "/META-INF/MANIFEST.MF")))
  125. (with-directory-excursion dir
  126. (invoke "jar" "xf" jar))
  127. (delete-file jar)
  128. (for-each (lambda (file)
  129. (let ((s (lstat file)))
  130. (unless (eq? (stat:type s) 'symlink)
  131. (when (string-match "^(.*)\\.class$" file)
  132. (utime file early-1980 early-1980)))))
  133. (find-files dir #:directories? #t))
  134. ;; The jar tool will always set the timestamp on the manifest file
  135. ;; and the containing directory to the current time, even when we
  136. ;; reuse an existing manifest file. To avoid this we use "zip"
  137. ;; instead of "jar". It is important that the manifest appears
  138. ;; first.
  139. (with-directory-excursion dir
  140. (let* ((files (find-files "." ".*" #:directories? #t))
  141. ;; To ensure that the reference scanner can detect all
  142. ;; store references in the jars we disable compression
  143. ;; with the "-0" option.
  144. (command (if (file-exists? manifest)
  145. `("zip" "-0" "-X" ,jar ,manifest ,@files)
  146. `("zip" "-0" "-X" ,jar ,@files))))
  147. (apply invoke command)))
  148. (utime jar 0 0)))
  149. (for-each (match-lambda
  150. ((output . directory)
  151. (for-each repack-archive
  152. (find-files directory regular-jar-file?))))
  153. outputs))
  154. (define-with-docs install
  155. "Standard 'install' phase for clojure-build-system."
  156. (install-jars "./"))
  157. (define-with-docs %standard-phases
  158. "Standard build phases for clojure-build-system."
  159. (modify-phases %standard-phases@ant
  160. (add-before 'build 'compile-java compile-java)
  161. (replace 'build build)
  162. (replace 'check check)
  163. (replace 'install install)
  164. (add-after 'install-license-files 'install-doc install-doc)
  165. (add-after 'reset-gzip-timestamps 'reset-class-timestamps reset-class-timestamps)))
  166. (define* (clojure-build #:key
  167. inputs
  168. (phases %standard-phases)
  169. #:allow-other-keys
  170. #:rest args)
  171. "Build the given Clojure package, applying all of PHASES in order."
  172. (apply ant-build
  173. #:inputs inputs
  174. #:phases phases
  175. args))
  176. ;;; clojure-build-system.scm ends here