pom.scm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2019, 2020 Julien Lepiller <julien@lepiller.eu>
  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 maven pom)
  19. #:use-module (sxml simple)
  20. #:use-module (system foreign)
  21. #:use-module (ice-9 match)
  22. #:use-module (srfi srfi-1)
  23. #:export (get-pom
  24. pom-ref
  25. pom-description
  26. pom-name
  27. pom-version
  28. pom-artifactid
  29. pom-groupid
  30. pom-dependencies
  31. group->dir
  32. fix-pom-dependencies))
  33. (define (get-pom file)
  34. "Return the content of a @file{.pom} file."
  35. (let ((pom-content (call-with-input-file file xml->sxml)))
  36. (match pom-content
  37. (('*TOP* _ (_ ('@ _ ...) content ...))
  38. content)
  39. (('*TOP* (_ ('@ _ ...) content ...))
  40. content)
  41. (('*TOP* _ (_ content ...))
  42. content)
  43. (('*TOP* (_ content ...))
  44. content))))
  45. (define (pom-ref content attr)
  46. "Gets a value associated to @var{attr} in @var{content}, an sxml value that
  47. represents a @file{.pom} file content, or parts of it."
  48. (or
  49. (assoc-ref
  50. content
  51. (string->symbol
  52. (string-append "http://maven.apache.org/POM/4.0.0:" attr)))
  53. (assoc-ref content (string->symbol attr))))
  54. (define (get-parent content)
  55. (pom-ref content "parent"))
  56. (define* (find-parent content inputs #:optional local-packages)
  57. "Find the parent pom for the pom file with @var{content} in a package's
  58. @var{inputs}. When the parent pom cannot be found in @var{inputs}, but
  59. @var{local-packages} is defined, the parent pom is looked up in it.
  60. @var{local-packages} is an association list of groupID to an association list
  61. of artifactID to version number.
  62. The result is an sxml document that describes the content of the parent pom, or
  63. of an hypothetical parent pom if it was generated from @var{local-packages}.
  64. If no result is found, the result is @code{#f}."
  65. (let ((parent (pom-ref content "parent")))
  66. (if parent
  67. (let* ((groupid (car (pom-ref parent "groupId")))
  68. (artifactid (car (pom-ref parent "artifactId")))
  69. (version (car (pom-ref parent "version")))
  70. (pom-file (string-append "lib/m2/" (group->dir groupid)
  71. "/" artifactid "/" version "/"
  72. artifactid "-" version ".pom"))
  73. (java-inputs (filter
  74. (lambda (input)
  75. (file-exists? (string-append input "/" pom-file)))
  76. inputs))
  77. (java-inputs (map (lambda (input) (string-append input "/" pom-file))
  78. java-inputs)))
  79. (if (null? java-inputs)
  80. (let ((version (assoc-ref (assoc-ref local-packages groupid) artifactid)))
  81. (if version
  82. `((groupId ,groupid)
  83. (artifactId ,artifactid)
  84. (version ,version))
  85. #f))
  86. (get-pom (car java-inputs))))
  87. #f)))
  88. (define* (pom-groupid content inputs #:optional local-packages)
  89. "Find the groupID of a pom file, potentially looking at its parent pom file.
  90. See @code{find-parent} for the meaning of the arguments."
  91. (if content
  92. (let ((res (or (pom-ref content "groupId")
  93. (pom-groupid (find-parent content inputs local-packages)
  94. inputs))))
  95. (cond
  96. ((string? res) res)
  97. ((null? res) #f)
  98. ((list? res) (car res))
  99. (else #f)))
  100. #f))
  101. (define (pom-artifactid content)
  102. "Find the artifactID of a pom file, from its sxml @var{content}."
  103. (let ((res (pom-ref content "artifactId")))
  104. (if (and res (>= (length res) 1))
  105. (car res)
  106. #f)))
  107. (define* (pom-version content inputs #:optional local-packages)
  108. "Find the version of a pom file, potentially looking at its parent pom file.
  109. See @code{find-parent} for the meaning of the arguments."
  110. (if content
  111. (let ((res (or (pom-ref content "version")
  112. (pom-version (find-parent content inputs local-packages)
  113. inputs))))
  114. (cond
  115. ((string? res) res)
  116. ((null? res) #f)
  117. ((list? res) (car res))
  118. (else #f)))
  119. #f))
  120. (define (pom-name content)
  121. "Return the name of the package as contained in the sxml @var{content} of the
  122. pom file."
  123. (let ((res (pom-ref content "name")))
  124. (if (and res (>= (length res) 1))
  125. (car res)
  126. #f)))
  127. (define (pom-description content)
  128. "Return the description of the package as contained in the sxml @var{content}
  129. of the pom file."
  130. (let ((res (pom-ref content "description")))
  131. (if (and res (>= (length res) 1))
  132. (car res)
  133. #f)))
  134. (define (pom-dependencies content)
  135. "Return the list of dependencies listed in the sxml @var{content} of the pom
  136. file."
  137. (filter
  138. (lambda (a) a)
  139. (map
  140. (match-lambda
  141. ((? string? _) #f)
  142. (('http://maven.apache.org/POM/4.0.0:dependency content ...)
  143. (let loop ((content content) (groupid #f) (artifactid #f) (version #f) (scope #f))
  144. (match content
  145. ('()
  146. `(dependency
  147. (groupId ,groupid)
  148. (artifactId ,artifactid)
  149. (version ,version)
  150. ,@(if scope `((scope ,scope)) '())))
  151. (((? string? _) content ...)
  152. (loop content groupid artifactid version scope))
  153. ((('http://maven.apache.org/POM/4.0.0:scope scope) content ...)
  154. (loop content groupid artifactid version scope))
  155. ((('http://maven.apache.org/POM/4.0.0:groupId groupid) content ...)
  156. (loop content groupid artifactid version scope))
  157. ((('http://maven.apache.org/POM/4.0.0:artifactId artifactid) content ...)
  158. (loop content groupid artifactid version scope))
  159. ((('http://maven.apache.org/POM/4.0.0:version version) content ...)
  160. (loop content groupid artifactid version scope))
  161. ((_ content ...)
  162. (loop content groupid artifactid version scope))))))
  163. (pom-ref content "dependencies"))))
  164. (define version-compare
  165. (let ((strverscmp
  166. (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
  167. (error "could not find `strverscmp' (from GNU libc)"))))
  168. (pointer->procedure int sym (list '* '*)))))
  169. (lambda (a b)
  170. "Return '> when A denotes a newer version than B,
  171. '< when A denotes a older version than B,
  172. or '= when they denote equal versions."
  173. (let ((result (strverscmp (string->pointer a) (string->pointer b))))
  174. (cond ((positive? result) '>)
  175. ((negative? result) '<)
  176. (else '=))))))
  177. (define (version>? a b)
  178. "Return #t when A denotes a version strictly newer than B."
  179. (eq? '> (version-compare a b)))
  180. (define (fix-maven-xml sxml)
  181. "When writing an xml file from an sxml representation, it is not possible to
  182. use namespaces in tag names. This procedure takes an @var{sxml} representation
  183. of a pom file and removes the namespace uses. It also adds the required bits
  184. to re-declare the namespaces in the top-level element."
  185. (define (fix-xml sxml)
  186. (match sxml
  187. ((tag ('@ opts ...) rest ...)
  188. (if (> (string-length (symbol->string tag))
  189. (string-length "http://maven.apache.org/POM/4.0.0:"))
  190. (let* ((tag (symbol->string tag))
  191. (tag (substring tag (string-length
  192. "http://maven.apache.org/POM/4.0.0:")))
  193. (tag (string->symbol tag)))
  194. `(,tag (@ ,@opts) ,@(map fix-xml rest)))
  195. `(,tag (@ ,@opts) ,@(map fix-xml rest))))
  196. ((tag (rest ...))
  197. (if (> (string-length (symbol->string tag))
  198. (string-length "http://maven.apache.org/POM/4.0.0:"))
  199. (let* ((tag (symbol->string tag))
  200. (tag (substring tag (string-length
  201. "http://maven.apache.org/POM/4.0.0:")))
  202. (tag (string->symbol tag)))
  203. `(,tag ,@(map fix-xml rest)))
  204. `(,tag ,@(map fix-xml rest))))
  205. ((tag rest ...)
  206. (if (> (string-length (symbol->string tag))
  207. (string-length "http://maven.apache.org/POM/4.0.0:"))
  208. (let* ((tag (symbol->string tag))
  209. (tag (substring tag (string-length
  210. "http://maven.apache.org/POM/4.0.0:")))
  211. (tag (string->symbol tag)))
  212. `(,tag ,@(map fix-xml rest)))
  213. `(,tag ,@(map fix-xml rest))))
  214. (_ sxml)))
  215. `((*TOP* (*PI* xml "version=\"1.0\" encoding=\"UTF-8\"")
  216. (project (@ (xmlns "http://maven.apache.org/POM/4.0.0")
  217. (xmlns:xsi "http://www.w3.org/2001/XMLSchema-instance")
  218. (xmlns:schemaLocation "http://maven.apache.org/POM/4.0.0
  219. http://maven.apache.org/xsd/maven-4.0.0.xsd"))
  220. ,(map fix-xml sxml)))))
  221. (define (group->dir group)
  222. "Convert a group ID to a directory path."
  223. (string-join (string-split group #\.) "/"))
  224. (define* (fix-pom-dependencies pom-file inputs
  225. #:key with-plugins? with-build-dependencies?
  226. (excludes '()) (local-packages '()))
  227. "Open @var{pom-file}, and override its content, rewritting its dependencies
  228. to set their version to the latest version available in the @var{inputs}.
  229. @var{#:with-plugins?} controls whether plugins are also overridden.
  230. @var{#:with-build-dependencies?} controls whether build dependencies (whose
  231. scope is not empty) are also overridden. By default build dependencies and
  232. plugins are not overridden.
  233. @var{#:excludes} is an association list of groupID to a list of artifactIDs.
  234. When a pair (groupID, artifactID) is present in the list, its entry is
  235. removed instead of being overridden. If the entry is ignored because of the
  236. previous arguments, the entry is not removed.
  237. @var{#:local-packages} is an association list that contains additional version
  238. information for packages that are not in @var{inputs}. If the package is
  239. not found in @var{inputs}, information from this list is used instead to determine
  240. the latest version of the package. This is an association list of group IDs
  241. to another association list of artifact IDs to a version number.
  242. Returns nothing, but overrides the @var{pom-file} as a side-effect."
  243. (define pom (get-pom pom-file))
  244. (define (ls dir)
  245. (let ((dir (opendir dir)))
  246. (let loop ((res '()))
  247. (let ((entry (readdir dir)))
  248. (if (eof-object? entry)
  249. res
  250. (loop (cons entry res)))))))
  251. (define fix-pom
  252. (match-lambda
  253. ('() '())
  254. ((tag rest ...)
  255. (match tag
  256. (('http://maven.apache.org/POM/4.0.0:dependencies deps ...)
  257. `((http://maven.apache.org/POM/4.0.0:dependencies ,(fix-deps deps))
  258. ,@(fix-pom rest)))
  259. (('http://maven.apache.org/POM/4.0.0:dependencyManagement deps ...)
  260. `((http://maven.apache.org/POM/4.0.0:dependencyManagement
  261. ,(fix-dep-management deps))
  262. ,@(fix-pom rest)))
  263. (('http://maven.apache.org/POM/4.0.0:build build ...)
  264. (if with-plugins?
  265. `((http://maven.apache.org/POM/4.0.0:build ,(fix-build build))
  266. ,@(fix-pom rest))
  267. (cons tag (fix-pom rest))))
  268. (tag (cons tag (fix-pom rest)))))))
  269. (define fix-dep-management
  270. (match-lambda
  271. ('() '())
  272. ((tag rest ...)
  273. (match tag
  274. (('http://maven.apache.org/POM/4.0.0:dependencies deps ...)
  275. `((http://maven.apache.org/POM/4.0.0:dependencies ,(fix-deps deps #t))
  276. ,@(fix-dep-management rest)))
  277. (tag (cons tag (fix-dep-management rest)))))))
  278. (define* (fix-deps deps #:optional optional?)
  279. (match deps
  280. ('() '())
  281. ((tag rest ...)
  282. (match tag
  283. (('http://maven.apache.org/POM/4.0.0:dependency dep ...)
  284. `((http://maven.apache.org/POM/4.0.0:dependency ,(fix-dep dep optional?))
  285. ,@(fix-deps rest optional?)))
  286. (tag (cons tag (fix-deps rest optional?)))))))
  287. (define fix-build
  288. (match-lambda
  289. ('() '())
  290. ((tag rest ...)
  291. (match tag
  292. (('http://maven.apache.org/POM/4.0.0:pluginManagement management ...)
  293. `((http://maven.apache.org/POM/4.0.0:pluginManagement
  294. ,(fix-management management))
  295. ,@(fix-build rest)))
  296. (('http://maven.apache.org/POM/4.0.0:plugins plugins ...)
  297. `((http://maven.apache.org/POM/4.0.0:plugins
  298. ,(fix-plugins plugins))
  299. ,@(fix-build rest)))
  300. (tag (cons tag (fix-build rest)))))))
  301. (define fix-management
  302. (match-lambda
  303. ('() '())
  304. ((tag rest ...)
  305. (match tag
  306. (('http://maven.apache.org/POM/4.0.0:plugins plugins ...)
  307. `((http://maven.apache.org/POM/4.0.0:plugins
  308. ,(fix-plugins plugins #t))
  309. ,@(fix-management rest)))
  310. (tag (cons tag (fix-management rest)))))))
  311. (define* (fix-plugins plugins #:optional optional?)
  312. (match plugins
  313. ('() '())
  314. ((tag rest ...)
  315. (match tag
  316. (('http://maven.apache.org/POM/4.0.0:plugin plugin ...)
  317. (let ((group (or (pom-groupid plugin inputs) "org.apache.maven.plugins"))
  318. (artifact (pom-artifactid plugin)))
  319. (if (member artifact (or (assoc-ref excludes group) '()))
  320. (fix-plugins rest optional?)
  321. `((http://maven.apache.org/POM/4.0.0:plugin
  322. ,(fix-plugin plugin optional?))
  323. ,@(fix-plugins rest optional?)))))
  324. (tag (cons tag (fix-plugins rest optional?)))))))
  325. (define* (fix-plugin plugin #:optional optional?)
  326. (let* ((artifact (pom-artifactid plugin))
  327. (group (or (pom-groupid plugin inputs) "org.apache.maven.plugins"))
  328. (version (or (assoc-ref (assoc-ref local-packages group) artifact)
  329. (find-version inputs group artifact optional?)
  330. (pom-version plugin inputs))))
  331. (if (pom-version plugin inputs)
  332. (map
  333. (lambda (tag)
  334. (match tag
  335. (('http://maven.apache.org/POM/4.0.0:version _)
  336. `(http://maven.apache.org/POM/4.0.0:version ,version))
  337. (('version _)
  338. `(http://maven.apache.org/POM/4.0.0:version ,version))
  339. (tag tag)))
  340. plugin)
  341. (cons `(http://maven.apache.org/POM/4.0.0:version ,version) plugin))))
  342. (define* (fix-dep dep #:optional optional?)
  343. (let* ((artifact (pom-artifactid dep))
  344. (group (or (pom-groupid dep inputs) (pom-groupid pom inputs)))
  345. (scope (pom-ref dep "scope"))
  346. (is-optional? (equal? (pom-ref dep "optional") '("true"))))
  347. (format (current-error-port) "maven: ~a:~a :: ~a (optional: ~a)~%"
  348. group artifact scope optional?)
  349. (if (or (and (not (equal? scope '("test"))) (not is-optional?))
  350. with-build-dependencies?)
  351. (let ((version (or (assoc-ref (assoc-ref local-packages group) artifact)
  352. (find-version inputs group artifact optional?)
  353. (pom-version dep inputs))))
  354. (if (pom-version dep inputs)
  355. (map
  356. (lambda (tag)
  357. (match tag
  358. (('http://maven.apache.org/POM/4.0.0:version _)
  359. `(http://maven.apache.org/POM/4.0.0:version ,version))
  360. (('version _)
  361. `(http://maven.apache.org/POM/4.0.0:version ,version))
  362. (_ tag)))
  363. dep)
  364. (cons `(http://maven.apache.org/POM/4.0.0:version ,version) dep)))
  365. dep)))
  366. (define* (find-version inputs group artifact #:optional optional?)
  367. (let* ((directory (string-append "lib/m2/" (group->dir group)
  368. "/" artifact))
  369. (java-inputs (filter
  370. (lambda (input)
  371. (file-exists? (string-append input "/" directory)))
  372. inputs))
  373. (java-inputs (map (lambda (input) (string-append input "/" directory))
  374. java-inputs))
  375. (versions (append-map ls java-inputs))
  376. (versions (sort versions version>?)))
  377. (if (null? versions)
  378. (if optional?
  379. #f
  380. (begin
  381. (format (current-error-port) "maven: ~a:~a is missing from inputs~%"
  382. group artifact)
  383. (throw 'no-such-input group artifact)))
  384. (car versions))))
  385. (let ((tmpfile (string-append pom-file ".tmp")))
  386. (with-output-to-file pom-file
  387. (lambda _
  388. (sxml->xml (fix-maven-xml (fix-pom pom)))))))