scripts: add guix lint
[jackhill/guix/guix.git] / guix / scripts / lint.scm
CommitLineData
b4f5e0e8
CR
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com>
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
19(define-module (guix scripts lint)
20 #:use-module (guix base32)
21 #:use-module (guix packages)
22 #:use-module (guix records)
23 #:use-module (guix ui)
24 #:use-module (guix utils)
25 #:use-module (gnu packages)
26 #:use-module (ice-9 match)
27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-9)
29 #:use-module (srfi srfi-11)
30 #:use-module (srfi srfi-37)
31 #:export (guix-lint
32 check-inputs-should-be-native
33 check-patches
34 check-synopsis-style))
35
36\f
37;;;
38;;; Command-line options.
39;;;
40
41(define %default-options
42 ;; Alist of default option values.
43 '())
44
45(define (show-help)
46 (display (_ "Usage: guix lint [OPTION]... [PACKAGE]...
47Run a set of checkers on the specified package; if none is specified, run the checkers on all packages.\n"))
48 (display (_ "
49 -h, --help display this help and exit"))
50 (display (_ "
51 -l, --list-checkers display the list of available lint checkers"))
52 (display (_ "
53 -V, --version display version information and exit"))
54 (newline)
55 (show-bug-report-information))
56
57(define %options
58 ;; Specification of the command-line options.
59 ;; TODO: add some options:
60 ;; * --checkers=checker1,checker2...: only run the specified checkers
61 ;; * --certainty=[low,medium,high]: only run checkers that have at least this
62 ;; 'certainty'.
63 (list (option '(#\h "help") #f #f
64 (lambda args
65 (show-help)
66 (exit 0)))
67 (option '(#\l "list-checkers") #f #f
68 (lambda args
69 (list-checkers-and-exit)))
70 (option '(#\V "version") #f #f
71 (lambda args
72 (show-version-and-exit "guix lint")))))
73
74\f
75;;;
76;;; Helpers
77;;;
78(define* (emit-warning package message #:optional field)
79 ;; Emit a warning about PACKAGE, printing the location of FIELD if it is
80 ;; given, the location of PACKAGE otherwise, the full name of PACKAGE and the
81 ;; provided MESSAGE.
82 (let ((loc (or (package-field-location package field)
83 (package-location package))))
84 (warning (_ "~a: ~a: ~a~%")
85 (location->string loc)
86 (package-full-name package)
87 message)))
88
89\f
90;;;
91;;; Checkers
92;;;
93(define-record-type* <lint-checker>
94 lint-checker make-lint-checker
95 lint-checker?
96 ;; TODO: add a 'certainty' field that shows how confident we are in the
97 ;; checker. Then allow users to only run checkers that have a certain
98 ;; 'certainty' level.
99 (name lint-checker-name)
100 (description lint-checker-description)
101 (check lint-checker-check))
102
103(define (list-checkers-and-exit)
104 ;; Print information about all available checkers and exit.
105 (format #t (_ "Available checkers:~%"))
106 (for-each (lambda (checker)
107 (format #t "- ~a: ~a~%"
108 (lint-checker-name checker)
109 (lint-checker-description checker)))
110 %checkers)
111 (exit 0))
112
113(define (check-inputs-should-be-native package)
114 ;; Emit a warning if some inputs of PACKAGE are likely to belong to its
115 ;; native inputs.
116 (let ((inputs (package-inputs package)))
117 (match inputs
118 (((labels packages . _) ...)
119 (when (member "pkg-config"
120 (map package-name (filter package? packages)))
121 (emit-warning package
122 "pkg-config should probably be a native input"
123 'inputs))))))
124
125
126(define (check-synopsis-style package)
127 ;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE.
128 (define (check-final-period synopsis)
129 ;; Synopsis should not end with a period, except for some special cases.
130 (if (and (string=? (string-take-right synopsis 1) ".")
131 (not (string=? (string-take-right synopsis 4) "etc.")))
132 (emit-warning package
133 "no period allowed at the end of the synopsis"
134 'synopsis)))
135
136 (define (check-start-article synopsis)
137 (if (or (string=? (string-take synopsis 2) "A ")
138 (string=? (string-take synopsis 3) "An "))
139 (emit-warning package
140 "no article allowed at the beginning of the synopsis"
141 'synopsis)))
142
143 (let ((synopsis (package-synopsis package)))
144 (if (string? synopsis)
145 (begin
146 (check-final-period synopsis)
147 (check-start-article synopsis)))))
148
149(define (check-patches package)
150 ;; Emit a warning if the patches requires by PACKAGE are badly named.
151 (let ((patches (and=> (package-source package) origin-patches))
152 (name (package-name package))
153 (full-name (package-full-name package)))
154 (if (and patches
155 (any (lambda (patch)
156 (let ((filename (basename patch)))
157 (not (or (eq? (string-contains filename name) 0)
158 (eq? (string-contains filename full-name) 0)))))
159 patches))
160 (emit-warning package
161 "file names of patches should start with the package name"
162 'patches))))
163
164(define %checkers
165 (list
166 (lint-checker
167 (name "inputs-should-be-native")
168 (description "Identify inputs that should be native inputs")
169 (check check-inputs-should-be-native))
170 (lint-checker
171 (name "patch-filenames")
172 (description "Validate filenames of patches")
173 (check check-patches))
174 (lint-checker
175 (name "synopsis")
176 (description "Validate package synopsis")
177 (check check-synopsis-style))))
178
179(define (run-checkers package)
180 ;; Run all the checkers on PACKAGE.
181 (for-each (lambda (checker)
182 ((lint-checker-check checker) package))
183 %checkers))
184
185\f
186;;;
187;;; Entry Point
188;;;
189
190(define (guix-lint . args)
191 (define (parse-options)
192 ;; Return the alist of option values.
193 (args-fold* args %options
194 (lambda (opt name arg result)
195 (leave (_ "~A: unrecognized option~%") name))
196 (lambda (arg result)
197 (alist-cons 'argument arg result))
198 %default-options))
199
200 (let* ((opts (parse-options))
201 (args (filter-map (match-lambda
202 (('argument . value)
203 value)
204 (_ #f))
205 (reverse opts))))
206
207
208 (if (null? args)
209 (fold-packages (lambda (p r) (run-checkers p)) '())
210 (for-each
211 (lambda (spec)
212 (run-checkers spec))
213 (map specification->package args)))))