style.scm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021-2023 Ludovic Courtès <ludo@gnu.org>
  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. ;;; Commentary:
  19. ;;;
  20. ;;; This script updates package definitions so they use the "simplified" style
  21. ;;; for input lists, as in:
  22. ;;;
  23. ;;; (package
  24. ;;; ;; ...
  25. ;;; (inputs (list foo bar baz)))
  26. ;;;
  27. ;;; Code:
  28. (define-module (guix scripts style)
  29. #:autoload (gnu packages) (specification->package fold-packages)
  30. #:use-module (guix scripts)
  31. #:use-module ((guix scripts build) #:select (%standard-build-options))
  32. #:use-module (guix ui)
  33. #:use-module (guix packages)
  34. #:use-module (guix utils)
  35. #:use-module (guix i18n)
  36. #:use-module (guix diagnostics)
  37. #:use-module (guix read-print)
  38. #:use-module (ice-9 control)
  39. #:use-module (ice-9 match)
  40. #:use-module (srfi srfi-1)
  41. #:use-module (srfi srfi-9)
  42. #:use-module (srfi srfi-26)
  43. #:use-module (srfi srfi-34)
  44. #:use-module (srfi srfi-37)
  45. #:export (guix-style))
  46. ;;;
  47. ;;; Simplifying input expressions.
  48. ;;;
  49. (define (label-matches? label name)
  50. "Return true if LABEL matches NAME, a package name."
  51. (or (string=? label name)
  52. (and (string-prefix? "python-" label)
  53. (string-prefix? "python2-" name)
  54. (string=? (string-drop label (string-length "python-"))
  55. (string-drop name (string-length "python2-"))))))
  56. (define* (simplify-inputs location package str inputs
  57. #:key (label-matches? label-matches?))
  58. "Simplify the inputs field of PACKAGE (a string) at LOCATION; its current
  59. value is INPUTS the corresponding source code is STR. Return a string to
  60. replace STR."
  61. (define (simplify-input-expression return)
  62. (match-lambda
  63. ((label ('unquote symbol)) symbol)
  64. ((label ('unquote symbol) output)
  65. (list 'quasiquote
  66. (list (list 'unquote symbol) output)))
  67. (_
  68. ;; Expression doesn't look like a simple input.
  69. (warning location (G_ "~a: complex expression, \
  70. bailing out~%")
  71. package)
  72. (return str))))
  73. (define (simplify-input exp input return)
  74. (define package* package)
  75. (match input
  76. ((or ((? string? label) (? package? package))
  77. ((? string? label) (? package? package)
  78. (? string?)))
  79. ;; If LABEL doesn't match PACKAGE's name, then simplifying would incur
  80. ;; a rebuild, and perhaps it would break build-side code relying on
  81. ;; this specific label.
  82. (if (label-matches? label (package-name package))
  83. ((simplify-input-expression return) exp)
  84. (begin
  85. (warning location (G_ "~a: input label \
  86. '~a' does not match package name, bailing out~%")
  87. package* label)
  88. (return str))))
  89. (_
  90. (warning location (G_ "~a: non-trivial input, \
  91. bailing out~%")
  92. package*)
  93. (return str))))
  94. (define (simplify-expressions exp inputs return)
  95. ;; Simplify the expressions in EXP, which correspond to INPUTS, and return
  96. ;; a list of expressions. Call RETURN with a string when bailing out.
  97. (let loop ((result '())
  98. (exp exp)
  99. (inputs inputs))
  100. (match exp
  101. (((? blank? head) . rest)
  102. (loop (cons head result) rest inputs))
  103. ((head . rest)
  104. (match inputs
  105. ((input . inputs)
  106. ;; HEAD (an sexp) and INPUT (an input tuple) are correlated.
  107. (loop (cons (simplify-input head input return) result)
  108. rest inputs))
  109. (()
  110. ;; If EXP and INPUTS have a different length, that
  111. ;; means EXP is a non-trivial input list, for example
  112. ;; with input-splicing, conditionals, etc.
  113. (warning location (G_ "~a: input expression is too short~%")
  114. package)
  115. (return str))))
  116. (()
  117. ;; It's possible for EXP to contain fewer elements than INPUTS, for
  118. ;; example in the case of input splicing. No bailout here. (XXX)
  119. (reverse result)))))
  120. (define inputs-exp
  121. (call-with-input-string str read-with-comments))
  122. (match inputs-exp
  123. (('list _ ...) ;already done
  124. str)
  125. (('modify-inputs _ ...) ;already done
  126. str)
  127. (('quasiquote ;prepending inputs
  128. (exp ...
  129. ('unquote-splicing
  130. ((and symbol (or 'package-inputs 'package-native-inputs
  131. 'package-propagated-inputs))
  132. arg))))
  133. (let/ec return
  134. (object->string*
  135. (let ((things (simplify-expressions exp inputs return)))
  136. `(modify-inputs (,symbol ,arg)
  137. (prepend ,@things)))
  138. (location-column location))))
  139. (('quasiquote ;replacing an input
  140. ((and exp ((? string? to-delete) ('unquote replacement)))
  141. ('unquote-splicing
  142. ('alist-delete (? string? to-delete)
  143. ((and symbol
  144. (or 'package-inputs 'package-native-inputs
  145. 'package-propagated-inputs))
  146. arg)))))
  147. (let/ec return
  148. (object->string*
  149. (let ((things (simplify-expressions (list exp)
  150. (list (car inputs))
  151. return)))
  152. `(modify-inputs (,symbol ,arg)
  153. (replace ,to-delete ,replacement)))
  154. (location-column location))))
  155. (('quasiquote ;removing an input
  156. (exp ...
  157. ('unquote-splicing
  158. ('alist-delete (? string? to-delete)
  159. ((and symbol
  160. (or 'package-inputs 'package-native-inputs
  161. 'package-propagated-inputs))
  162. arg)))))
  163. (let/ec return
  164. (object->string*
  165. (let ((things (simplify-expressions exp inputs return)))
  166. `(modify-inputs (,symbol ,arg)
  167. (delete ,to-delete)
  168. (prepend ,@things)))
  169. (location-column location))))
  170. (('fold 'alist-delete ;removing several inputs
  171. ((and symbol
  172. (or 'package-inputs 'package-native-inputs
  173. 'package-propagated-inputs))
  174. arg)
  175. ('quote ((? string? to-delete) ...)))
  176. (object->string*
  177. `(modify-inputs (,symbol ,arg)
  178. (delete ,@to-delete))
  179. (location-column location)))
  180. (('quasiquote ;removing several inputs and adding others
  181. (exp ...
  182. ('unquote-splicing
  183. ('fold 'alist-delete
  184. ((and symbol
  185. (or 'package-inputs 'package-native-inputs
  186. 'package-propagated-inputs))
  187. arg)
  188. ('quote ((? string? to-delete) ...))))))
  189. (let/ec return
  190. (object->string*
  191. (let ((things (simplify-expressions exp inputs return)))
  192. `(modify-inputs (,symbol ,arg)
  193. (delete ,@to-delete)
  194. (prepend ,@things)))
  195. (location-column location))))
  196. (('quasiquote (exp ...))
  197. (let/ec return
  198. (object->string*
  199. `(list ,@(simplify-expressions exp inputs return))
  200. (location-column location))))
  201. (_
  202. (warning location (G_ "~a: unsupported input style, \
  203. bailing out~%")
  204. package)
  205. str)))
  206. (define (edit-expression/dry-run properties rewrite-string)
  207. "Like 'edit-expression' but display what would be edited without actually
  208. doing it."
  209. (edit-expression properties
  210. (lambda (str)
  211. (unless (string=? (rewrite-string str) str)
  212. (info (source-properties->location properties)
  213. (G_ "would be edited~%")))
  214. str)))
  215. (define (trivial-package-arguments? package)
  216. "Return true if PACKAGE has zero arguments or only \"trivial\" arguments
  217. guaranteed not to refer to input labels."
  218. (let loop ((arguments (package-arguments package)))
  219. (match arguments
  220. (()
  221. #t)
  222. (((? keyword?) value rest ...)
  223. (and (or (boolean? value) (number? value) (string? value))
  224. (loop rest))))))
  225. (define* (simplify-package-inputs package
  226. #:key (policy 'silent)
  227. (edit-expression edit-expression))
  228. "Edit the source code of PACKAGE to simplify its inputs field if needed.
  229. POLICY is a symbol that defines whether to simplify inputs; it can one of
  230. 'silent (change only if the resulting derivation is the same), 'safe (change
  231. only if semantics are known to be unaffected), and 'always (fearlessly
  232. simplify inputs!). Call EDIT-EXPRESSION to actually edit the source of
  233. PACKAGE."
  234. (for-each (lambda (field-name field)
  235. (match (field package)
  236. (()
  237. #f)
  238. (inputs
  239. (match (package-field-location package field-name)
  240. (#f
  241. ;; If the location of FIELD-NAME is not found, it may be
  242. ;; that PACKAGE inherits from another package.
  243. #f)
  244. (location
  245. (edit-expression
  246. (location->source-properties (absolute-location location))
  247. (lambda (str)
  248. (define matches?
  249. (match policy
  250. ('silent
  251. ;; Simplify inputs only when the label matches
  252. ;; perfectly, such that the resulting derivation
  253. ;; is unchanged.
  254. label-matches?)
  255. ('safe
  256. ;; If PACKAGE has no arguments, labels are known
  257. ;; to have no effect: this is a "safe" change, but
  258. ;; it may change the derivation.
  259. (if (trivial-package-arguments? package)
  260. (const #t)
  261. label-matches?))
  262. ('always
  263. ;; Assume it's gonna be alright.
  264. (const #t))))
  265. (simplify-inputs location
  266. (package-name package)
  267. str inputs
  268. #:label-matches? matches?))))))))
  269. '(inputs native-inputs propagated-inputs)
  270. (list package-inputs package-native-inputs
  271. package-propagated-inputs)))
  272. ;;;
  273. ;;; Gexpifying package arguments.
  274. ;;;
  275. (define (unquote->ungexp value)
  276. "Replace 'unquote' and 'unquote-splicing' in VALUE with their gexp
  277. counterpart."
  278. ;; Replace 'unquote only on the first quasiquotation level.
  279. (let loop ((value value)
  280. (quotation 1))
  281. (match value
  282. (('unquote x)
  283. (if (= quotation 1)
  284. `(ungexp ,x)
  285. value))
  286. (('unquote-splicing x)
  287. (if (= quotation 1)
  288. `(ungexp-splicing x)
  289. value))
  290. (('quasiquote x)
  291. (list 'quasiquote (loop x (+ quotation 1))))
  292. (('quote x)
  293. (list 'quote (loop x (+ quotation 1))))
  294. ((lst ...)
  295. (map (cut loop <> quotation) lst))
  296. (x x))))
  297. (define (gexpify-argument-value value quotation)
  298. "Turn VALUE, an sexp, into its gexp equivalent. QUOTATION is a symbol that
  299. indicates in what quotation context VALUE is to be interpreted: 'quasiquote,
  300. 'quote, or 'none."
  301. (match quotation
  302. ('none
  303. (match value
  304. (('quasiquote value)
  305. (gexpify-argument-value value 'quasiquote))
  306. (('quote value)
  307. (gexpify-argument-value value 'quote))
  308. (value value)))
  309. ('quote
  310. `(gexp ,value))
  311. ('quasiquote
  312. `(gexp ,(unquote->ungexp value)))))
  313. (define (quote-argument-value value quotation)
  314. "Quote VALUE, an sexp. QUOTATION is a symbol that indicates in what
  315. quotation context VALUE is to be interpreted: 'quasiquote, 'quote, or 'none."
  316. (define (self-quoting? x)
  317. (or (boolean? x) (number? x) (string? x) (char? x)
  318. (keyword? x)))
  319. (match quotation
  320. ('none
  321. (match value
  322. (('quasiquote value)
  323. (quote-argument-value value 'quasiquote))
  324. (('quote value)
  325. (quote-argument-value value 'quote))
  326. (value value)))
  327. ('quote
  328. (if (self-quoting? value)
  329. value
  330. (list 'quote value)))
  331. ('quasiquote
  332. (match value
  333. (('unquote x) x)
  334. ((? self-quoting? x) x)
  335. (_ (list 'quasiquote value))))))
  336. (define %gexp-keywords
  337. ;; Package argument keywords that must be followed by a gexp.
  338. '(#:phases #:configure-flags #:make-flags #:strip-flags))
  339. (define (gexpify-argument-tail sexp)
  340. "Gexpify SEXP, an unquoted argument tail."
  341. (match sexp
  342. (('substitute-keyword-arguments lst clauses ...)
  343. `(substitute-keyword-arguments ,lst
  344. ,@(map (match-lambda
  345. ((((? keyword? keyword) identifier) body)
  346. `((,keyword ,identifier)
  347. ,(if (memq keyword %gexp-keywords)
  348. (gexpify-argument-value body 'none)
  349. (quote-argument-value body 'none))))
  350. ((((? keyword? keyword) identifier default) body)
  351. `((,keyword ,identifier
  352. ,(if (memq keyword %gexp-keywords)
  353. (gexpify-argument-value default 'none)
  354. (quote-argument-value default 'none)))
  355. ,(if (memq keyword %gexp-keywords)
  356. (gexpify-argument-value body 'none)
  357. (quote-argument-value body 'none))))
  358. (clause clause))
  359. clauses)))
  360. (_ sexp)))
  361. (define* (gexpify-package-arguments package
  362. #:key
  363. (policy 'none)
  364. (edit-expression edit-expression))
  365. "Rewrite the 'arguments' field of PACKAGE to use gexps where applicable."
  366. (define (gexpify location str)
  367. (match (call-with-input-string str read-with-comments)
  368. ((rest ...)
  369. (let ((blanks (take-while blank? rest))
  370. (value (drop-while blank? rest)))
  371. (define-values (quotation arguments tail)
  372. (match value
  373. (('quote (arguments ...)) (values 'quote arguments '()))
  374. (('quasiquote (arguments ... ('unquote-splicing tail)))
  375. (values 'quasiquote arguments tail))
  376. (('quasiquote (arguments ...)) (values 'quasiquote arguments '()))
  377. (('list arguments ...) (values 'none arguments '()))
  378. (arguments (values 'none '() arguments))))
  379. (define (append-tail sexp)
  380. (if (null? tail)
  381. sexp
  382. (let ((tail (gexpify-argument-tail tail)))
  383. (if (null? arguments)
  384. tail
  385. `(append ,sexp ,tail)))))
  386. (let/ec return
  387. (object->string*
  388. (append-tail
  389. `(list ,@(let loop ((arguments arguments)
  390. (result '()))
  391. (match arguments
  392. (() (reverse result))
  393. (((? keyword? keyword) value rest ...)
  394. (when (eq? quotation 'none)
  395. (match value
  396. (('gexp _) ;already gexpified
  397. (return str))
  398. (_ #f)))
  399. (loop rest
  400. (cons* (if (memq keyword %gexp-keywords)
  401. (gexpify-argument-value value
  402. quotation)
  403. (quote-argument-value value quotation))
  404. keyword result)))
  405. (((? blank? blank) rest ...)
  406. (loop rest (cons blank result)))
  407. (_
  408. ;; Something like: ,@(package-arguments xyz).
  409. (warning location
  410. (G_ "unsupported argument style; \
  411. bailing out~%"))
  412. (return str))))))
  413. (location-column location)))))
  414. (_
  415. (warning location
  416. (G_ "unsupported argument field; bailing out~%"))
  417. str)))
  418. (unless (null? (package-arguments package))
  419. (match (package-field-location package 'arguments)
  420. (#f
  421. #f)
  422. (location
  423. (edit-expression
  424. (location->source-properties (absolute-location location))
  425. (lambda (str)
  426. (gexpify location str)))))))
  427. ;;;
  428. ;;; Formatting package definitions.
  429. ;;;
  430. (define* (format-package-definition package
  431. #:key policy
  432. (edit-expression edit-expression))
  433. "Reformat the definition of PACKAGE."
  434. (unless (package-definition-location package)
  435. (leave (package-location package)
  436. (G_ "no definition location for package ~a~%")
  437. (package-full-name package)))
  438. (edit-expression
  439. (location->source-properties
  440. (absolute-location (package-definition-location package)))
  441. (lambda (str)
  442. (let ((exp (call-with-input-string str
  443. read-with-comments)))
  444. (object->string* exp
  445. (location-column
  446. (package-definition-location package))
  447. #:format-comment canonicalize-comment
  448. #:format-vertical-space canonicalize-vertical-space)))))
  449. (define (package-location<? p1 p2)
  450. "Return true if P1's location is \"before\" P2's."
  451. (let ((loc1 (package-location p1))
  452. (loc2 (package-location p2)))
  453. (and loc1 loc2
  454. (if (string=? (location-file loc1) (location-file loc2))
  455. (< (location-line loc1) (location-line loc2))
  456. (string<? (location-file loc1) (location-file loc2))))))
  457. ;;;
  458. ;;; Whole-file formatting.
  459. ;;;
  460. (define* (format-whole-file file #:rest rest)
  461. "Reformat all of FILE."
  462. (with-fluids ((%default-port-encoding "UTF-8"))
  463. (let ((lst (call-with-input-file file read-with-comments/sequence
  464. #:guess-encoding #t)))
  465. (with-atomic-file-output file
  466. (lambda (port)
  467. (apply pretty-print-with-comments/splice port lst
  468. #:format-comment canonicalize-comment
  469. #:format-vertical-space canonicalize-vertical-space
  470. rest))))))
  471. ;;;
  472. ;;; Options.
  473. ;;;
  474. (define %options
  475. ;; Specification of the command-line options.
  476. (list (find (lambda (option)
  477. (member "load-path" (option-names option)))
  478. %standard-build-options)
  479. (option '(#\n "dry-run") #f #f
  480. (lambda (opt name arg result)
  481. (alist-cons 'dry-run? #t result)))
  482. (option '(#\e "expression") #t #f
  483. (lambda (opt name arg result)
  484. (alist-cons 'expression arg result)))
  485. (option '(#\f "whole-file") #f #f
  486. (lambda (opt name arg result)
  487. (alist-cons 'whole-file? #t result)))
  488. (option '(#\S "styling") #t #f
  489. (lambda (opt name arg result)
  490. (alist-cons 'styling-procedure
  491. (match arg
  492. ("inputs" simplify-package-inputs)
  493. ("arguments" gexpify-package-arguments)
  494. ("format" format-package-definition)
  495. (_ (leave (G_ "~a: unknown styling~%")
  496. arg)))
  497. result)))
  498. (option '("input-simplification") #t #f
  499. (lambda (opt name arg result)
  500. (let ((symbol (string->symbol arg)))
  501. (unless (memq symbol '(silent safe always))
  502. (leave (G_ "~a: invalid input simplification policy~%")
  503. arg))
  504. (alist-cons 'input-simplification-policy symbol
  505. result))))
  506. (option '(#\h "help") #f #f
  507. (lambda args
  508. (show-help)
  509. (exit 0)))
  510. (option '(#\l "list-stylings") #f #f
  511. (lambda args
  512. (show-stylings)
  513. (exit 0)))
  514. (option '(#\V "version") #f #f
  515. (lambda args
  516. (show-version-and-exit "guix style")))))
  517. (define (show-stylings)
  518. (display (G_ "Available styling rules:\n"))
  519. (display (G_ "- format: Format the given package definition(s)\n"))
  520. (display (G_ "- inputs: Rewrite package inputs to the “new style”\n"))
  521. (display (G_ "- arguments: Rewrite package arguments to G-expressions\n")))
  522. (define (show-help)
  523. (display (G_ "Usage: guix style [OPTION]... [PACKAGE]...
  524. Update package definitions to the latest style.\n"))
  525. (display (G_ "
  526. -S, --styling=RULE apply RULE, a styling rule"))
  527. (display (G_ "
  528. -l, --list-stylings display the list of available style rules"))
  529. (newline)
  530. (display (G_ "
  531. -n, --dry-run display files that would be edited but do nothing"))
  532. (display (G_ "
  533. -L, --load-path=DIR prepend DIR to the package module search path"))
  534. (display (G_ "
  535. -e, --expression=EXPR consider the package EXPR evaluates to"))
  536. (display (G_ "
  537. --input-simplification=POLICY
  538. follow POLICY for package input simplification, one
  539. of 'silent', 'safe', or 'always'"))
  540. (newline)
  541. (display (G_ "
  542. -f, --whole-file format the entire contents of the given file(s)"))
  543. (newline)
  544. (display (G_ "
  545. -h, --help display this help and exit"))
  546. (display (G_ "
  547. -V, --version display version information and exit"))
  548. (newline)
  549. (show-bug-report-information))
  550. (define %default-options
  551. ;; Alist of default option values.
  552. `((input-simplification-policy . silent)
  553. (styling-procedure . ,format-package-definition)))
  554. ;;;
  555. ;;; Entry point.
  556. ;;;
  557. (define-command (guix-style . args)
  558. (category packaging)
  559. (synopsis "update the style of package definitions")
  560. (define (parse-options)
  561. ;; Return the alist of option values.
  562. (parse-command-line args %options (list %default-options)
  563. #:build-options? #f))
  564. (let* ((opts (parse-options))
  565. (edit (if (assoc-ref opts 'dry-run?)
  566. edit-expression/dry-run
  567. edit-expression))
  568. (style (assoc-ref opts 'styling-procedure))
  569. (policy (assoc-ref opts 'input-simplification-policy)))
  570. (with-error-handling
  571. (if (assoc-ref opts 'whole-file?)
  572. (let ((files (filter-map (match-lambda
  573. (('argument . file) file)
  574. (_ #f))
  575. opts)))
  576. (unless (eq? format-package-definition style)
  577. (warning (G_ "'--styling' option has no effect in whole-file mode~%")))
  578. (for-each format-whole-file files))
  579. (let ((packages (filter-map (match-lambda
  580. (('argument . spec)
  581. (specification->package spec))
  582. (('expression . str)
  583. (read/eval str))
  584. (_ #f))
  585. opts)))
  586. (for-each (lambda (package)
  587. (style package #:policy policy
  588. #:edit-expression edit))
  589. ;; Sort package by source code location so that we start
  590. ;; editing files from the bottom and going upward. That
  591. ;; way, the 'location' field of <package> records is not
  592. ;; invalidated as we modify files.
  593. (sort (if (null? packages)
  594. (fold-packages cons '() #:select? (const #t))
  595. packages)
  596. (negate package-location<?))))))))