clojure-utils.scm 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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-utils)
  19. #:use-module (guix build utils)
  20. #:use-module (ice-9 ftw)
  21. #:use-module (ice-9 match)
  22. #:use-module (srfi srfi-1)
  23. #:use-module (srfi srfi-8)
  24. #:use-module (srfi srfi-26)
  25. #:export (@*
  26. @@*
  27. define-with-docs
  28. %doc-regex
  29. install-doc
  30. %source-dirs
  31. %java-source-dirs
  32. %test-dirs
  33. %compile-dir
  34. %java-compile-dir
  35. package-name->jar-names
  36. %main-class
  37. %omit-source?
  38. %aot-include
  39. %aot-exclude
  40. %tests?
  41. %test-include
  42. %test-exclude
  43. %clojure-regex
  44. canonicalize-relative-path
  45. find-files*
  46. file-sans-extension
  47. relative-path->clojure-lib-string
  48. find-clojure-libs
  49. compiled-from?
  50. include-list\exclude-list
  51. eval-with-clojure
  52. create-jar))
  53. (define-syntax-rule (@* module name)
  54. "Like (@ MODULE NAME), but resolves at run time."
  55. (module-ref (resolve-interface 'module) 'name))
  56. (define-syntax-rule (@@* module name)
  57. "Like (@@ MODULE NAME), but resolves at run time."
  58. (module-ref (resolve-module 'module) 'name))
  59. (define-syntax-rule (define-with-docs name docs val)
  60. "Create top-level variable named NAME with doc string DOCS and value VAL."
  61. (begin (define name val)
  62. (set-object-property! name 'documentation docs)))
  63. (define-with-docs %doc-regex
  64. "Default regex for matching the base name of top-level documentation files."
  65. "^(README.*|.*\\.html|.*\\.org|.*\\.md|\\.markdown|\\.txt)$")
  66. (define* (install-doc #:key
  67. doc-dirs
  68. (doc-regex %doc-regex)
  69. outputs
  70. #:allow-other-keys)
  71. "Install the following to the default documentation directory:
  72. 1. Top-level files with base name matching DOC-REGEX.
  73. 2. All files (recursively) inside DOC-DIRS.
  74. DOC-REGEX can be compiled or uncompiled."
  75. (let* ((out (assoc-ref outputs "out"))
  76. (doc (assoc-ref outputs "doc"))
  77. (name-ver (strip-store-file-name out))
  78. (dest-dir (string-append (or doc out) "/share/doc/" name-ver "/"))
  79. (doc-regex* (if (string? doc-regex)
  80. (make-regexp doc-regex)
  81. doc-regex)))
  82. (for-each (cut install-file <> dest-dir)
  83. (remove (compose file-exists?
  84. (cut string-append dest-dir <>))
  85. (scandir "./" (cut regexp-exec doc-regex* <>))))
  86. (for-each (cut copy-recursively <> dest-dir)
  87. doc-dirs)
  88. #t))
  89. (define-with-docs %source-dirs
  90. "A default list of source directories."
  91. '("src/"))
  92. (define-with-docs %java-source-dirs
  93. "A default list of java source directories."
  94. '())
  95. (define-with-docs %test-dirs
  96. "A default list of test directories."
  97. '("test/"))
  98. (define-with-docs %compile-dir
  99. "Default directory for holding class files."
  100. "classes/")
  101. (define-with-docs %java-compile-dir
  102. "Default directory for holding java class files."
  103. "java-classes/")
  104. (define (package-name->jar-names name)
  105. "Given NAME, a package name like \"foo-0.9.1b\",
  106. return the list of default jar names: (\"foo-0.9.1b.jar\" \"foo.jar\")."
  107. (map (cut string-append <> ".jar")
  108. (list name
  109. (receive (base-name _)
  110. (package-name->name+version name)
  111. base-name))))
  112. (define-with-docs %main-class
  113. "Default name for main class. It should be a symbol or #f."
  114. #f)
  115. (define-with-docs %omit-source?
  116. "Include source in jars by default."
  117. #f)
  118. (define-with-docs %aot-include
  119. "A default list of symbols deciding what to compile. Note that the exclude
  120. list has priority over the include list. The special keyword #:all represents
  121. all libraries found under the source directories."
  122. '(#:all))
  123. (define-with-docs %aot-exclude
  124. "A default list of symbols deciding what not to compile.
  125. See the doc string of '%aot-include' for more details."
  126. '(data-readers))
  127. (define-with-docs %tests?
  128. "Enable tests by default."
  129. #t)
  130. (define-with-docs %test-include
  131. "A default list of symbols deciding what tests to include. Note that the
  132. exclude list has priority over the include list. The special keyword #:all
  133. represents all tests found under the test directories."
  134. '(#:all))
  135. (define-with-docs %test-exclude
  136. "A default list of symbols deciding what tests to exclude.
  137. See the doc string of '%test-include' for more details."
  138. '())
  139. (define-with-docs %clojure-regex
  140. "Default regex for matching the base name of clojure source files."
  141. "\\.cljc?$")
  142. (define-with-docs canonicalize-relative-path
  143. "Like 'canonicalize-path', but for relative paths.
  144. Canonicalizations requiring the path to exist are omitted."
  145. (let ((remove.. (lambda (ls)
  146. (fold-right (match-lambda*
  147. (((and comp (not "..")) (".." comps ...))
  148. comps)
  149. ((comp (comps ...))
  150. (cons comp comps)))
  151. '()
  152. ls))))
  153. (compose (match-lambda
  154. (() ".")
  155. (ls (string-join ls "/")))
  156. remove..
  157. (cut remove (cut member <> '("" ".")) <>)
  158. (cut string-split <> #\/))))
  159. (define (find-files* base-dir . args)
  160. "Similar to 'find-files', but with BASE-DIR stripped and result
  161. canonicalized."
  162. (map canonicalize-relative-path
  163. (with-directory-excursion base-dir
  164. (apply find-files "./" args))))
  165. ;;; FIXME: should be moved to (guix build utils)
  166. (define (file-sans-extension file) ;TODO: factorize
  167. "Return the substring of FILE without its extension, if any."
  168. (let ((dot (string-rindex file #\.)))
  169. (if dot
  170. (substring file 0 dot)
  171. file)))
  172. (define (relative-path->clojure-lib-string path)
  173. "Convert PATH to a clojure library string."
  174. (string-map (match-lambda
  175. (#\/ #\.)
  176. (#\_ #\-)
  177. (chr chr))
  178. (file-sans-extension path)))
  179. (define* (find-clojure-libs base-dir
  180. #:key (clojure-regex %clojure-regex))
  181. "Return the list of clojure libraries found under BASE-DIR.
  182. CLOJURE-REGEX can be compiled or uncompiled."
  183. (map (compose string->symbol
  184. relative-path->clojure-lib-string)
  185. (find-files* base-dir clojure-regex)))
  186. (define (compiled-from? class lib)
  187. "Given class file CLASS and clojure library symbol LIB, decide if CLASS
  188. results from compiling LIB."
  189. (string-prefix? (symbol->string lib)
  190. (relative-path->clojure-lib-string class)))
  191. (define* (include-list\exclude-list include-list exclude-list
  192. #:key all-list)
  193. "Given INCLUDE-LIST and EXCLUDE-LIST, replace all occurrences of #:all by
  194. slicing ALL-LIST into them and compute their list difference."
  195. (define (replace-#:all ls all-ls)
  196. (append-map (match-lambda
  197. (#:all all-ls)
  198. (x (list x)))
  199. ls))
  200. (let ((include-list* (replace-#:all include-list all-list))
  201. (exclude-list* (replace-#:all exclude-list all-list)))
  202. (lset-difference equal? include-list* exclude-list*)))
  203. (define (eval-with-clojure expr extra-paths)
  204. "Evaluate EXPR with clojure.
  205. EXPR must be a s-expression writable by guile and readable by clojure.
  206. For examples, '(require '[clojure.string]) will not work,
  207. because the guile writer converts brackets to parentheses.
  208. EXTRA-PATHS is a list of paths which will be appended to $CLASSPATH."
  209. (let* ((classpath (getenv "CLASSPATH"))
  210. (classpath* (string-join (cons classpath extra-paths) ":")))
  211. (invoke "java"
  212. "-classpath" classpath*
  213. "clojure.main"
  214. "--eval" (object->string expr))))
  215. (define* (create-jar output-jar dir-files-alist
  216. #:key
  217. (verbose? #t)
  218. (compress? #f)
  219. (main-class %main-class))
  220. "Given DIR-FILES-ALIST, an alist of the form: ((DIR . FILES) ...)
  221. Create jar named OUTPUT-JAR from FILES with DIR stripped."
  222. (let ((grouped-options (string-append "c"
  223. (if verbose? "v" "")
  224. "f"
  225. (if compress? "" "0")
  226. (if main-class "e" ""))))
  227. (apply invoke `("jar"
  228. ,grouped-options
  229. ,output-jar
  230. ,@(if main-class (list (symbol->string main-class)) '())
  231. ,@(append-map (match-lambda
  232. ((dir . files)
  233. (append-map (lambda (file)
  234. `("-C" ,dir ,file))
  235. files)))
  236. dir-files-alist)))))