pom.scm 21 KB

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