configuration.scm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015 Andy Wingo <wingo@igalia.com>
  3. ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
  4. ;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org>
  5. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  6. ;;; Copyright © 2021, 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  7. ;;; Copyright © 2021 Andrew Tropin <andrew@trop.in>
  8. ;;; Copyright © 2022 Maxime Devos <maximedevos@telenet.be>
  9. ;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu>
  10. ;;;
  11. ;;; This file is part of GNU Guix.
  12. ;;;
  13. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  14. ;;; under the terms of the GNU General Public License as published by
  15. ;;; the Free Software Foundation; either version 3 of the License, or (at
  16. ;;; your option) any later version.
  17. ;;;
  18. ;;; GNU Guix is distributed in the hope that it will be useful, but
  19. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. ;;; GNU General Public License for more details.
  22. ;;;
  23. ;;; You should have received a copy of the GNU General Public License
  24. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  25. (define-module (gnu services configuration)
  26. #:use-module (guix packages)
  27. #:use-module (guix records)
  28. #:use-module (guix gexp)
  29. #:use-module ((guix utils) #:select (source-properties->location))
  30. #:use-module ((guix diagnostics)
  31. #:select (formatted-message location-file &error-location
  32. warning))
  33. #:use-module ((guix modules) #:select (file-name->module-name))
  34. #:use-module (guix i18n)
  35. #:autoload (texinfo) (texi-fragment->stexi)
  36. #:autoload (texinfo serialize) (stexi->texi)
  37. #:use-module (ice-9 curried-definitions)
  38. #:use-module (ice-9 format)
  39. #:use-module (ice-9 match)
  40. #:use-module (srfi srfi-1)
  41. #:use-module (srfi srfi-26)
  42. #:use-module (srfi srfi-34)
  43. #:use-module (srfi srfi-35)
  44. #:export (configuration-field
  45. configuration-field-name
  46. configuration-field-type
  47. configuration-missing-field
  48. configuration-field-error
  49. configuration-field-sanitizer
  50. configuration-field-serializer
  51. configuration-field-getter
  52. configuration-field-default-value-thunk
  53. configuration-field-documentation
  54. configuration-error?
  55. define-configuration
  56. define-configuration/no-serialization
  57. no-serialization
  58. serialize-configuration
  59. define-maybe
  60. define-maybe/no-serialization
  61. %unset-value
  62. maybe-value
  63. maybe-value-set?
  64. generate-documentation
  65. configuration->documentation
  66. empty-serializer
  67. serialize-package
  68. filter-configuration-fields
  69. interpose
  70. list-of
  71. list-of-strings?
  72. alist?
  73. serialize-file-like
  74. text-config?
  75. serialize-text-config
  76. generic-serialize-alist-entry
  77. generic-serialize-alist))
  78. ;;; Commentary:
  79. ;;;
  80. ;;; Syntax for creating Scheme bindings to complex configuration files.
  81. ;;;
  82. ;;; Code:
  83. (define-condition-type &configuration-error &error
  84. configuration-error?)
  85. (define (configuration-error message)
  86. (raise (condition (&message (message message))
  87. (&configuration-error))))
  88. (define (configuration-field-error loc field value)
  89. (raise (apply
  90. make-compound-condition
  91. (formatted-message (G_ "invalid value ~s for field '~a'")
  92. value field)
  93. (condition (&configuration-error))
  94. (if loc
  95. (list (condition
  96. (&error-location (location loc))))
  97. '()))))
  98. (define (configuration-missing-field kind field)
  99. (configuration-error
  100. (format #f "~a configuration missing required field ~a" kind field)))
  101. (define (configuration-missing-default-value kind field)
  102. (configuration-error
  103. (format #f "The field `~a' of the `~a' configuration record \
  104. does not have a default value" field kind)))
  105. (define-record-type* <configuration-field>
  106. configuration-field make-configuration-field configuration-field?
  107. (name configuration-field-name)
  108. (type configuration-field-type)
  109. (getter configuration-field-getter)
  110. (predicate configuration-field-predicate)
  111. (sanitizer configuration-field-sanitizer)
  112. (serializer configuration-field-serializer)
  113. (default-value-thunk configuration-field-default-value-thunk)
  114. (documentation configuration-field-documentation))
  115. (define (serialize-configuration config fields)
  116. #~(string-append
  117. #$@(map (lambda (field)
  118. ((configuration-field-serializer field)
  119. (configuration-field-name field)
  120. ((configuration-field-getter field) config)))
  121. fields)))
  122. (define-syntax-rule (id ctx parts ...)
  123. "Assemble PARTS into a raw (unhygienic) identifier."
  124. (datum->syntax ctx (symbol-append (syntax->datum parts) ...)))
  125. (define (define-maybe-helper serialize? prefix syn)
  126. (syntax-case syn ()
  127. ((_ stem)
  128. (with-syntax
  129. ((stem? (id #'stem #'stem #'?))
  130. (maybe-stem? (id #'stem #'maybe- #'stem #'?))
  131. (serialize-stem (if prefix
  132. (id #'stem prefix #'serialize- #'stem)
  133. (id #'stem #'serialize- #'stem)))
  134. (serialize-maybe-stem (if prefix
  135. (id #'stem prefix #'serialize-maybe- #'stem)
  136. (id #'stem #'serialize-maybe- #'stem))))
  137. #`(begin
  138. (define (maybe-stem? val)
  139. (or (not (maybe-value-set? val))
  140. (stem? val)))
  141. #,@(if serialize?
  142. (list #'(define (serialize-maybe-stem field-name val)
  143. (if (stem? val)
  144. (serialize-stem field-name val)
  145. "")))
  146. '()))))))
  147. (define-syntax define-maybe
  148. (lambda (x)
  149. (syntax-case x (no-serialization prefix)
  150. ((_ stem (no-serialization))
  151. (define-maybe-helper #f #f #'(_ stem)))
  152. ((_ stem (prefix serializer-prefix))
  153. (define-maybe-helper #t #'serializer-prefix #'(_ stem)))
  154. ((_ stem)
  155. (define-maybe-helper #t #f #'(_ stem))))))
  156. (define-syntax-rule (define-maybe/no-serialization stem)
  157. (define-maybe stem (no-serialization)))
  158. (define (normalize-field-type+def s)
  159. (syntax-case s ()
  160. ((field-type def)
  161. (identifier? #'field-type)
  162. (values #'(field-type def)))
  163. ((field-type)
  164. (identifier? #'field-type)
  165. (values #'(field-type %unset-value)))
  166. (field-type
  167. (identifier? #'field-type)
  168. (values #'(field-type %unset-value)))))
  169. (define (define-configuration-helper serialize? serializer-prefix syn)
  170. (define (normalize-extra-args s)
  171. "Extract and normalize arguments following @var{doc}."
  172. (let loop ((s s)
  173. (sanitizer* %unset-value)
  174. (serializer* %unset-value))
  175. (syntax-case s (sanitizer serializer empty-serializer)
  176. (((sanitizer proc) tail ...)
  177. (if (maybe-value-set? sanitizer*)
  178. (syntax-violation 'sanitizer "duplicate entry"
  179. #'proc)
  180. (loop #'(tail ...) #'proc serializer*)))
  181. (((serializer proc) tail ...)
  182. (if (maybe-value-set? serializer*)
  183. (syntax-violation 'serializer "duplicate or conflicting entry"
  184. #'proc)
  185. (loop #'(tail ...) sanitizer* #'proc)))
  186. ((empty-serializer tail ...)
  187. (if (maybe-value-set? serializer*)
  188. (syntax-violation 'empty-serializer
  189. "duplicate or conflicting entry" #f)
  190. (loop #'(tail ...) sanitizer* #'empty-serializer)))
  191. (() ; stop condition
  192. (values (list sanitizer* serializer*)))
  193. ((proc) ; TODO: deprecated, to be removed.
  194. (null? (filter-map maybe-value-set? (list sanitizer* serializer*)))
  195. (begin
  196. (warning #f (G_ "specifying serializers after documentation is \
  197. deprecated, use (serializer ~a) instead~%") (syntax->datum #'proc))
  198. (values (list %unset-value #'proc)))))))
  199. (syntax-case syn ()
  200. ((_ stem (field field-type+def doc extra-args ...) ...)
  201. (with-syntax
  202. ((((field-type def) ...)
  203. (map normalize-field-type+def #'(field-type+def ...)))
  204. (((sanitizer* serializer*) ...)
  205. (map normalize-extra-args #'((extra-args ...) ...))))
  206. (with-syntax
  207. (((field-getter ...)
  208. (map (lambda (field)
  209. (id #'stem #'stem #'- field))
  210. #'(field ...)))
  211. ((field-predicate ...)
  212. (map (lambda (type)
  213. (id #'stem type #'?))
  214. #'(field-type ...)))
  215. ((field-default ...)
  216. (map (match-lambda
  217. ((field-type default-value)
  218. default-value))
  219. #'((field-type def) ...)))
  220. ((field-sanitizer ...)
  221. (map maybe-value #'(sanitizer* ...)))
  222. ((field-serializer ...)
  223. (map (lambda (type proc)
  224. (and serialize?
  225. (or (maybe-value proc)
  226. (if serializer-prefix
  227. (id #'stem serializer-prefix #'serialize- type)
  228. (id #'stem #'serialize- type)))))
  229. #'(field-type ...)
  230. #'(serializer* ...))))
  231. (define (default-field-sanitizer name pred)
  232. ;; Define a macro for use as a record field sanitizer, where NAME
  233. ;; is the name of the field and PRED is the predicate that tells
  234. ;; whether a value is valid for this field.
  235. #`(define-syntax #,(id #'stem #'validate- #'stem #'- name)
  236. (lambda (s)
  237. ;; Make sure the given VALUE, for field NAME, passes PRED.
  238. (syntax-case s ()
  239. ((_ value)
  240. (with-syntax ((name #'#,name)
  241. (pred #'#,pred)
  242. (loc (datum->syntax #'value
  243. (syntax-source #'value))))
  244. #'(if (pred value)
  245. value
  246. (configuration-field-error
  247. (and=> 'loc source-properties->location)
  248. 'name value))))))))
  249. #`(begin
  250. ;; Define field validation macros.
  251. #,@(filter-map (lambda (name pred sanitizer)
  252. (if sanitizer
  253. #f
  254. (default-field-sanitizer name pred)))
  255. #'(field ...)
  256. #'(field-predicate ...)
  257. #'(field-sanitizer ...))
  258. (define-record-type* #,(id #'stem #'< #'stem #'>)
  259. stem
  260. #,(id #'stem #'make- #'stem)
  261. #,(id #'stem #'stem #'?)
  262. #,@(map (lambda (name getter def sanitizer)
  263. #`(#,name #,getter
  264. (default #,def)
  265. (sanitize
  266. #,(or sanitizer
  267. (id #'stem
  268. #'validate- #'stem #'- name)))))
  269. #'(field ...)
  270. #'(field-getter ...)
  271. #'(field-default ...)
  272. #'(field-sanitizer ...))
  273. (%location #,(id #'stem #'stem #'-source-location)
  274. (default (and=> (current-source-location)
  275. source-properties->location))
  276. (innate)))
  277. (define #,(id #'stem #'stem #'-fields)
  278. (list (configuration-field
  279. (name 'field)
  280. (type 'field-type)
  281. (getter field-getter)
  282. (predicate field-predicate)
  283. (sanitizer
  284. (or field-sanitizer
  285. (id #'stem #'validate- #'stem #'- #'field)))
  286. (serializer field-serializer)
  287. (default-value-thunk
  288. (lambda ()
  289. (if (maybe-value-set? (syntax->datum field-default))
  290. field-default
  291. (configuration-missing-default-value
  292. '#,(id #'stem #'% #'stem) 'field))))
  293. (documentation doc))
  294. ...))))))))
  295. (define no-serialization ;syntactic keyword for 'define-configuration'
  296. '(no serialization))
  297. (define-syntax define-configuration
  298. (lambda (s)
  299. (syntax-case s (no-serialization prefix)
  300. ((_ stem (field field-type+def doc custom-serializer ...) ...
  301. (no-serialization))
  302. (define-configuration-helper
  303. #f #f #'(_ stem (field field-type+def doc custom-serializer ...)
  304. ...)))
  305. ((_ stem (field field-type+def doc custom-serializer ...) ...
  306. (prefix serializer-prefix))
  307. (define-configuration-helper
  308. #t #'serializer-prefix #'(_ stem (field field-type+def
  309. doc custom-serializer ...)
  310. ...)))
  311. ((_ stem (field field-type+def doc custom-serializer ...) ...)
  312. (define-configuration-helper
  313. #t #f #'(_ stem (field field-type+def doc custom-serializer ...)
  314. ...))))))
  315. (define-syntax-rule (define-configuration/no-serialization
  316. stem (field field-type+def
  317. doc custom-serializer ...) ...)
  318. (define-configuration stem (field field-type+def
  319. doc custom-serializer ...) ...
  320. (no-serialization)))
  321. (define (empty-serializer field-name val) "")
  322. (define serialize-package empty-serializer)
  323. ;; Ideally this should be an implementation detail, but we export it
  324. ;; to provide a simpler API that enables unsetting a configuration
  325. ;; field that has a maybe type, but also a default value. We give it
  326. ;; a value that sticks out to the reader when something goes wrong.
  327. ;;
  328. ;; An example use-case would be something like a network application
  329. ;; that uses a default port, but the field can explicitly be unset to
  330. ;; request a random port at startup.
  331. (define %unset-value '%unset-marker%)
  332. (define (maybe-value-set? value)
  333. "Predicate to check whether a 'maybe' value was explicitly provided."
  334. (not (eq? %unset-value value)))
  335. ;; Ideally there should be a compiler macro for this predicate, that expands
  336. ;; to a conditional that only instantiates the default value when needed.
  337. (define* (maybe-value value #:optional (default #f))
  338. "Returns VALUE, unless it is the unset value, in which case it returns
  339. DEFAULT."
  340. (if (maybe-value-set? value)
  341. value
  342. default))
  343. ;; A little helper to make it easier to document all those fields.
  344. (define (generate-documentation documentation documentation-name)
  345. (define (str x) (object->string x))
  346. (define (package->symbol package)
  347. "Return the first symbol name of a package that matches PACKAGE, else #f."
  348. (let* ((module (file-name->module-name
  349. (location-file (package-location package))))
  350. (symbols (filter-map
  351. identity
  352. (module-map (lambda (symbol var)
  353. (and (equal? package (variable-ref var))
  354. symbol))
  355. (resolve-module module)))))
  356. (if (null? symbols)
  357. #f
  358. (first symbols))))
  359. (define (generate configuration-name)
  360. (match (assq-ref documentation configuration-name)
  361. ((fields . sub-documentation)
  362. `((deftp (% (category "Data Type") (name ,(str configuration-name)))
  363. (para "Available " (code ,(str configuration-name)) " fields are:")
  364. (table
  365. (% (formatter (asis)))
  366. ,@(map
  367. (lambda (f)
  368. (let ((field-name (configuration-field-name f))
  369. (field-type (configuration-field-type f))
  370. (field-docs (cdr (texi-fragment->stexi
  371. (configuration-field-documentation f))))
  372. (default (catch #t
  373. (configuration-field-default-value-thunk f)
  374. (lambda _ '%invalid))))
  375. (define (show-default? val)
  376. (or (string? val) (number? val) (boolean? val)
  377. (package? val)
  378. (and (symbol? val) (not (eq? val '%invalid)))
  379. (and (list? val) (and-map show-default? val))))
  380. (define (show-default val)
  381. (cond
  382. ((package? val)
  383. (symbol->string (package->symbol val)))
  384. (((list-of package?) val)
  385. (format #f "(~{~a~^ ~})" (map package->symbol val)))
  386. (else (str val))))
  387. `(entry (% (heading
  388. (code ,(str field-name))
  389. ,@(if (show-default? default)
  390. `(" (default: "
  391. (code ,(show-default default)) ")")
  392. '())
  393. " (type: " ,(str field-type) ")"))
  394. (para ,@field-docs)
  395. ,@(append-map
  396. generate
  397. (or (assq-ref sub-documentation field-name)
  398. '())))))
  399. fields)))))))
  400. (stexi->texi `(*fragment* . ,(generate documentation-name))))
  401. (define (configuration->documentation configuration-symbol)
  402. "Take CONFIGURATION-SYMBOL, the symbol corresponding to the name used when
  403. defining a configuration record with DEFINE-CONFIGURATION, and output the
  404. Texinfo documentation of its fields."
  405. ;; This is helper for a simple, straight-forward application of
  406. ;; GENERATE-DOCUMENTATION.
  407. (let ((fields-getter (module-ref (current-module)
  408. (symbol-append configuration-symbol
  409. '-fields))))
  410. (display (generate-documentation `((,configuration-symbol ,fields-getter))
  411. configuration-symbol))))
  412. (define* (filter-configuration-fields configuration-fields fields
  413. #:optional negate?)
  414. "Retrieve the fields listed in FIELDS from CONFIGURATION-FIELDS.
  415. If NEGATE? is @code{#t}, retrieve all fields except FIELDS."
  416. (filter (lambda (field)
  417. (let ((member? (member (configuration-field-name field) fields)))
  418. (if (not negate?) member? (not member?))))
  419. configuration-fields))
  420. (define* (interpose ls #:optional (delimiter "\n") (grammar 'infix))
  421. "Same as @code{string-join}, but without join and string, returns a
  422. DELIMITER interposed LS. Support 'infix and 'suffix GRAMMAR values."
  423. (when (not (member grammar '(infix suffix)))
  424. (raise
  425. (formatted-message
  426. (G_ "The GRAMMAR value must be 'infix or 'suffix, but ~a provided.")
  427. grammar)))
  428. (fold-right (lambda (e acc)
  429. (cons e
  430. (if (and (null? acc) (eq? grammar 'infix))
  431. acc
  432. (cons delimiter acc))))
  433. '() ls))
  434. (define (list-of pred?)
  435. "Return a procedure that takes a list and check if all the elements of
  436. the list result in @code{#t} when applying PRED? on them."
  437. (lambda (x)
  438. (if (list? x)
  439. (every pred? x)
  440. #f)))
  441. (define list-of-strings?
  442. (list-of string?))
  443. (define alist?
  444. (list-of pair?))
  445. (define serialize-file-like empty-serializer)
  446. (define (text-config? config)
  447. (list-of file-like?))
  448. (define (serialize-text-config field-name val)
  449. #~(string-append
  450. #$@(interpose
  451. (map
  452. (lambda (e)
  453. #~(begin
  454. (use-modules (ice-9 rdelim))
  455. (with-fluids ((%default-port-encoding "UTF-8"))
  456. (with-input-from-file #$e read-string))))
  457. val)
  458. "\n" 'suffix)))
  459. (define ((generic-serialize-alist-entry serialize-field) entry)
  460. "Apply the SERIALIZE-FIELD procedure on the field and value of ENTRY."
  461. (match entry
  462. ((field . val) (serialize-field field val))))
  463. (define (generic-serialize-alist combine serialize-field fields)
  464. "Generate a configuration from an association list FIELDS.
  465. SERIALIZE-FIELD is a procedure that takes two arguments, it will be
  466. applied on the fields and values of FIELDS using the
  467. @code{generic-serialize-alist-entry} procedure.
  468. COMBINE is a procedure that takes one or more arguments and combines
  469. all the alist entries into one value, @code{string-append} or
  470. @code{append} are usually good candidates for this."
  471. (apply combine
  472. (map (generic-serialize-alist-entry serialize-field) fields)))