ant-build-system.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016, 2018 Ricardo Wurmus <rekado@elephly.net>
  3. ;;; Copyright © 2019 Björn Höfling <bjoern.hoefling@bjoernhoefling.de>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix build ant-build-system)
  20. #:use-module ((guix build gnu-build-system) #:prefix gnu:)
  21. #:use-module (guix build syscalls)
  22. #:use-module (guix build utils)
  23. #:use-module (sxml simple)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 ftw)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (srfi srfi-26)
  28. #:export (%standard-phases
  29. ant-build))
  30. ;; Commentary:
  31. ;;
  32. ;; Builder-side code of the standard build procedure for Java packages using
  33. ;; Ant.
  34. ;;
  35. ;; Code:
  36. (define* (default-build.xml jar-name prefix #:optional
  37. (source-dir ".") (test-dir "./test") (main-class #f)
  38. (test-include '("**/*Test.java"))
  39. (test-exclude '("**/Abstract*Test.java")))
  40. "Create a simple build.xml with standard targets for Ant."
  41. (call-with-output-file "build.xml"
  42. (lambda (port)
  43. (sxml->xml
  44. `(project (@ (basedir ".")
  45. (name ,jar-name))
  46. (property (@ (name "classes.dir")
  47. (value "${basedir}/build/classes")))
  48. (property (@ (name "manifest.dir")
  49. (value "${basedir}/build/manifest")))
  50. (property (@ (name "manifest.file")
  51. (value "${manifest.dir}/MANIFEST.MF")))
  52. (property (@ (name "jar.dir")
  53. (value "${basedir}/build/jar")))
  54. (property (@ (name "dist.dir")
  55. (value ,prefix)))
  56. (property (@ (name "test.home")
  57. (value ,test-dir)))
  58. (property (@ (name "test.classes.dir")
  59. (value "${basedir}/build/test-classes")))
  60. ;; respect the CLASSPATH environment variable
  61. (property (@ (name "build.sysclasspath")
  62. (value "first")))
  63. (property (@ (environment "env")))
  64. (path (@ (id "classpath"))
  65. (pathelement (@ (location "${env.CLASSPATH}"))))
  66. (target (@ (name "manifest"))
  67. (mkdir (@ (dir "${manifest.dir}")))
  68. (manifest (@ (file "${manifest.file}"))
  69. ,(if main-class
  70. `(attribute (@ (name "Main-Class")
  71. (value ,main-class)))
  72. "")))
  73. (target (@ (name "compile"))
  74. (mkdir (@ (dir "${classes.dir}")))
  75. (javac (@ (includeantruntime "false")
  76. (srcdir ,source-dir)
  77. (destdir "${classes.dir}")
  78. (classpath (@ (refid "classpath"))))))
  79. (target (@ (name "compile-tests"))
  80. (mkdir (@ (dir "${test.classes.dir}")))
  81. (javac (@ (includeantruntime "false")
  82. (srcdir ,test-dir)
  83. (destdir "${test.classes.dir}"))
  84. (classpath
  85. (pathelement (@ (path "${env.CLASSPATH}")))
  86. (pathelement (@ (location "${classes.dir}")))
  87. (pathelement (@ (location "${test.classes.dir}"))))))
  88. (target (@ (name "check")
  89. (depends "compile-tests"))
  90. (mkdir (@ (dir "${test.home}/test-reports")))
  91. (junit (@ (printsummary "true")
  92. (showoutput "true")
  93. (fork "yes")
  94. (haltonfailure "yes"))
  95. (classpath
  96. (pathelement (@ (path "${env.CLASSPATH}")))
  97. (pathelement (@ (location "${test.home}/resources")))
  98. (pathelement (@ (location "${classes.dir}")))
  99. (pathelement (@ (location "${test.classes.dir}"))))
  100. (formatter (@ (type "plain")
  101. (usefile "true")))
  102. (batchtest (@ (fork "yes")
  103. (todir "${test.home}/test-reports"))
  104. (fileset (@ (dir "${test.home}/java"))
  105. ,@(map (lambda (file)
  106. `(include (@ (name ,file))))
  107. test-include)
  108. ,@(map (lambda (file)
  109. `(exclude (@ (name ,file))))
  110. test-exclude)))))
  111. (target (@ (name "jar")
  112. (depends "compile, manifest"))
  113. (mkdir (@ (dir "${jar.dir}")))
  114. (jar (@ (destfile ,(string-append "${jar.dir}/" jar-name))
  115. (manifest "${manifest.file}")
  116. (basedir "${classes.dir}"))))
  117. (target (@ (name "install"))
  118. (copy (@ (todir "${dist.dir}"))
  119. (fileset (@ (dir "${jar.dir}"))
  120. (include (@ (name "**/*.jar")))))))
  121. port)))
  122. (utime "build.xml" 0 0)
  123. #t)
  124. (define (generate-classpath inputs)
  125. "Return a colon-separated string of full paths to jar files found among the
  126. INPUTS."
  127. (string-join
  128. (apply append (map (match-lambda
  129. ((_ . dir)
  130. (find-files dir "\\.jar$")))
  131. inputs)) ":"))
  132. (define* (unpack #:key source #:allow-other-keys)
  133. "Unpack the jar archive SOURCE. When SOURCE is not a jar archive fall back
  134. to the default GNU unpack strategy."
  135. (if (string-suffix? ".jar" source)
  136. (begin
  137. (mkdir "src")
  138. (with-directory-excursion "src"
  139. (invoke "jar" "-xf" source))
  140. #t)
  141. ;; Use GNU unpack strategy for things that aren't jar archives.
  142. ((assq-ref gnu:%standard-phases 'unpack) #:source source)))
  143. (define* (configure #:key inputs outputs (jar-name #f)
  144. (source-dir "src")
  145. (test-dir "src/test")
  146. (main-class #f)
  147. (test-include '("**/*Test.java"))
  148. (test-exclude '("**/Abstract*.java")) #:allow-other-keys)
  149. (when jar-name
  150. (default-build.xml jar-name
  151. (string-append (assoc-ref outputs "out")
  152. "/share/java")
  153. source-dir test-dir main-class test-include test-exclude))
  154. (setenv "JAVA_HOME" (assoc-ref inputs "jdk"))
  155. (setenv "CLASSPATH" (generate-classpath inputs))
  156. #t)
  157. (define* (build #:key (make-flags '()) (build-target "jar")
  158. #:allow-other-keys)
  159. (apply invoke `("ant" ,build-target ,@make-flags)))
  160. (define (regular-jar-file-predicate file stat)
  161. "Predicate returning true if FILE is ending on '.jar'
  162. and STAT indicates it is a regular file."
  163. (and ((file-name-predicate "\\.jar$") file stat)
  164. (eq? 'regular (stat:type stat))))
  165. (define* (generate-jar-indices #:key outputs #:allow-other-keys)
  166. "Generate file \"META-INF/INDEX.LIST\". This file does not use word wraps
  167. and is preferred over \"META-INF/MANIFEST.MF\", which does use word wraps,
  168. by Java when resolving dependencies. So we make sure to create it so that
  169. grafting works - and so that the garbage collector doesn't collect
  170. dependencies of this jar file."
  171. (define (generate-index jar)
  172. (invoke "jar" "-i" jar))
  173. (for-each (match-lambda
  174. ((output . directory)
  175. (for-each generate-index
  176. (find-files
  177. directory
  178. regular-jar-file-predicate))))
  179. outputs)
  180. #t)
  181. (define* (strip-jar-timestamps #:key outputs
  182. #:allow-other-keys)
  183. "Unpack all jar archives, reset the timestamp of all contained files, and
  184. repack them. This is necessary to ensure that archives are reproducible."
  185. (define (repack-archive jar)
  186. (format #t "repacking ~a\n" jar)
  187. (let* ((dir (mkdtemp! "jar-contents.XXXXXX"))
  188. (manifest (string-append dir "/META-INF/MANIFEST.MF")))
  189. (with-directory-excursion dir
  190. (invoke "jar" "xf" jar))
  191. (delete-file jar)
  192. ;; XXX: copied from (gnu build install)
  193. (for-each (lambda (file)
  194. (let ((s (lstat file)))
  195. (unless (eq? (stat:type s) 'symlink)
  196. (utime file 0 0 0 0))))
  197. (find-files dir #:directories? #t))
  198. ;; The jar tool will always set the timestamp on the manifest file
  199. ;; and the containing directory to the current time, even when we
  200. ;; reuse an existing manifest file. To avoid this we use "zip"
  201. ;; instead of "jar". It is important that the manifest appears
  202. ;; first.
  203. (with-directory-excursion dir
  204. (let* ((files (find-files "." ".*" #:directories? #t))
  205. ;; To ensure that the reference scanner can detect all
  206. ;; store references in the jars we disable compression
  207. ;; with the "-0" option.
  208. (command (if (file-exists? manifest)
  209. `("zip" "-0" "-X" ,jar ,manifest ,@files)
  210. `("zip" "-0" "-X" ,jar ,@files))))
  211. (apply invoke command)))
  212. (utime jar 0 0)
  213. #t))
  214. (for-each (match-lambda
  215. ((output . directory)
  216. (for-each repack-archive
  217. (find-files directory regular-jar-file-predicate))))
  218. outputs)
  219. #t)
  220. (define* (check #:key target (make-flags '()) (tests? (not target))
  221. (test-target "check")
  222. #:allow-other-keys)
  223. (if tests?
  224. (apply invoke `("ant" ,test-target ,@make-flags))
  225. (format #t "test suite not run~%"))
  226. #t)
  227. (define* (install #:key (make-flags '()) #:allow-other-keys)
  228. (apply invoke `("ant" "install" ,@make-flags)))
  229. (define %standard-phases
  230. (modify-phases gnu:%standard-phases
  231. (replace 'unpack unpack)
  232. (delete 'bootstrap)
  233. (replace 'configure configure)
  234. (replace 'build build)
  235. (replace 'check check)
  236. (replace 'install install)
  237. (add-after 'install 'reorder-jar-content
  238. strip-jar-timestamps)
  239. (add-after 'reorder-jar-content 'generate-jar-indices generate-jar-indices)
  240. (add-after 'generate-jar-indices 'strip-jar-timestamps
  241. strip-jar-timestamps)))
  242. (define* (ant-build #:key inputs (phases %standard-phases)
  243. #:allow-other-keys #:rest args)
  244. "Build the given Java package, applying all of PHASES in order."
  245. (apply gnu:gnu-build #:inputs inputs #:phases phases args))
  246. ;;; ant-build-system.scm ends here