gnu: packages: Use 'search-patches' everywhere.
[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>
50f5c46d 3;;; Copyright © 2014, 2015 Eric Bavier <bavier@member.fsf.org>
f888c0b1 4;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
90ca9186 5;;; Copyright © 2015, 2016 Mathieu Lirzin <mthl@gnu.org>
b4f5e0e8
CR
6;;;
7;;; This file is part of GNU Guix.
8;;;
9;;; GNU Guix is free software; you can redistribute it and/or modify it
10;;; under the terms of the GNU General Public License as published by
11;;; the Free Software Foundation; either version 3 of the License, or (at
12;;; your option) any later version.
13;;;
14;;; GNU Guix is distributed in the hope that it will be useful, but
15;;; WITHOUT ANY WARRANTY; without even the implied warranty of
16;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;;; GNU General Public License for more details.
18;;;
19;;; You should have received a copy of the GNU General Public License
20;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
21
22(define-module (guix scripts lint)
14d6ca3e 23 #:use-module ((guix store) #:hide (close-connection))
b4f5e0e8 24 #:use-module (guix base32)
17a7b75c 25 #:use-module (guix download)
c9815b5d 26 #:use-module (guix ftp-client)
4ce783a2 27 #:use-module (guix http-client)
b4f5e0e8 28 #:use-module (guix packages)
52b9efe3 29 #:use-module (guix licenses)
b4f5e0e8
CR
30 #:use-module (guix records)
31 #:use-module (guix ui)
32 #:use-module (guix utils)
88981dd3 33 #:use-module (guix scripts)
105c260f 34 #:use-module (guix gnu-maintenance)
2b5115f8 35 #:use-module (guix monads)
5432734b 36 #:use-module (guix cve)
b4f5e0e8
CR
37 #:use-module (gnu packages)
38 #:use-module (ice-9 match)
574e847b
EB
39 #:use-module (ice-9 regex)
40 #:use-module (ice-9 format)
a3bf0969
LC
41 #:use-module (web uri)
42 #:use-module ((guix build download)
17a7b75c 43 #:select (maybe-expand-mirrors
14d6ca3e
LC
44 open-connection-for-uri
45 close-connection))
a3bf0969
LC
46 #:use-module (web request)
47 #:use-module (web response)
b4f5e0e8 48 #:use-module (srfi srfi-1)
2b5115f8 49 #:use-module (srfi srfi-6) ;Unicode string ports
b4f5e0e8
CR
50 #:use-module (srfi srfi-9)
51 #:use-module (srfi srfi-11)
17a7b75c 52 #:use-module (srfi srfi-26)
b210b35d
LC
53 #:use-module (srfi srfi-34)
54 #:use-module (srfi srfi-35)
b4f5e0e8 55 #:use-module (srfi srfi-37)
40a7d4e5 56 #:use-module (ice-9 rdelim)
b4f5e0e8 57 #:export (guix-lint
8202a513 58 check-description-style
b4f5e0e8 59 check-inputs-should-be-native
56b1b74c 60 check-patch-file-names
907c98ac 61 check-synopsis-style
002c57c6 62 check-derivation
950d2ea4 63 check-home-page
40a7d4e5 64 check-source
50f5c46d 65 check-source-file-name
52b9efe3 66 check-license
5432734b 67 check-vulnerabilities
521a11e0 68 check-formatting
e04741f1 69 run-checkers
521a11e0
AK
70
71 %checkers
72 lint-checker
73 lint-checker?
74 lint-checker-name
75 lint-checker-description
76 lint-checker-check))
b4f5e0e8
CR
77
78\f
b4f5e0e8
CR
79;;;
80;;; Helpers
81;;;
82(define* (emit-warning package message #:optional field)
83 ;; Emit a warning about PACKAGE, printing the location of FIELD if it is
84 ;; given, the location of PACKAGE otherwise, the full name of PACKAGE and the
85 ;; provided MESSAGE.
86 (let ((loc (or (package-field-location package field)
87 (package-location package))))
836f02bf 88 (format (guix-warning-port) "~a: ~a: ~a~%"
b002e9d0
LC
89 (location->string loc)
90 (package-full-name package)
91 message)))
b4f5e0e8 92
2b5115f8
LC
93(define (call-with-accumulated-warnings thunk)
94 "Call THUNK, accumulating any warnings in the current state, using the state
95monad."
96 (let ((port (open-output-string)))
97 (mlet %state-monad ((state (current-state))
98 (result -> (parameterize ((guix-warning-port port))
99 (thunk)))
100 (warning -> (get-output-string port)))
101 (mbegin %state-monad
102 (munless (string=? "" warning)
103 (set-current-state (cons warning state)))
104 (return result)))))
105
106(define-syntax-rule (with-accumulated-warnings exp ...)
107 "Evaluate EXP and accumulate warnings in the state monad."
108 (call-with-accumulated-warnings
109 (lambda ()
110 exp ...)))
111
b4f5e0e8
CR
112\f
113;;;
114;;; Checkers
115;;;
116(define-record-type* <lint-checker>
117 lint-checker make-lint-checker
118 lint-checker?
119 ;; TODO: add a 'certainty' field that shows how confident we are in the
120 ;; checker. Then allow users to only run checkers that have a certain
121 ;; 'certainty' level.
122 (name lint-checker-name)
123 (description lint-checker-description)
124 (check lint-checker-check))
125
126(define (list-checkers-and-exit)
127 ;; Print information about all available checkers and exit.
128 (format #t (_ "Available checkers:~%"))
129 (for-each (lambda (checker)
130 (format #t "- ~a: ~a~%"
131 (lint-checker-name checker)
db6dcf81 132 (_ (lint-checker-description checker))))
b4f5e0e8
CR
133 %checkers)
134 (exit 0))
135
903581f9 136(define (properly-starts-sentence? s)
23464bfd 137 (string-match "^[(\"'`[:upper:][:digit:]]" s))
8202a513 138
15a6d433
LC
139(define (starts-with-abbreviation? s)
140 "Return #t if S starts with what looks like an abbreviation or acronym."
141 (string-match "^[A-Z][A-Z0-9]+\\>" s))
142
8202a513
CR
143(define (check-description-style package)
144 ;; Emit a warning if stylistic issues are found in the description of PACKAGE.
334c43e3
EB
145 (define (check-not-empty description)
146 (when (string-null? description)
147 (emit-warning package
836f02bf 148 (_ "description should not be empty")
334c43e3
EB
149 'description)))
150
2748ee3b
ML
151 (define (check-texinfo-markup description)
152 "Check that DESCRIPTION can be parsed as a Texinfo fragment. If the
153markup is valid return a plain-text version of DESCRIPTION, otherwise #f."
cd8b7cfb
ML
154 (catch #t
155 (lambda () (texi->plain-text description))
156 (lambda (keys . args)
157 (emit-warning package
158 (_ "Texinfo markup in description is invalid")
159 'description)
160 #f)))
3500e659 161
903581f9 162 (define (check-proper-start description)
3c42965b
EB
163 (unless (or (properly-starts-sentence? description)
164 (string-prefix-ci? (package-name package) description))
574e847b 165 (emit-warning package
836f02bf 166 (_ "description should start with an upper-case letter or digit")
574e847b
EB
167 'description)))
168
169 (define (check-end-of-sentence-space description)
170 "Check that an end-of-sentence period is followed by two spaces."
171 (let ((infractions
172 (reverse (fold-matches
173 "\\. [A-Z]" description '()
174 (lambda (m r)
175 ;; Filter out matches of common abbreviations.
176 (if (find (lambda (s)
177 (string-suffix-ci? s (match:prefix m)))
178 '("i.e" "e.g" "a.k.a" "resp"))
179 r (cons (match:start m) r)))))))
180 (unless (null? infractions)
181 (emit-warning package
836f02bf
LC
182 (format #f (_ "sentences in description should be followed ~
183by two spaces; possible infraction~p at ~{~a~^, ~}")
574e847b
EB
184 (length infractions)
185 infractions)
186 'description))))
187
188 (let ((description (package-description package)))
189 (when (string? description)
903581f9 190 (check-not-empty description)
2748ee3b
ML
191 ;; Use raw description for this because Texinfo rendering automatically
192 ;; fixes end of sentence space.
193 (check-end-of-sentence-space description)
194 (and=> (check-texinfo-markup description)
195 check-proper-start))))
8202a513 196
b4f5e0e8
CR
197(define (check-inputs-should-be-native package)
198 ;; Emit a warning if some inputs of PACKAGE are likely to belong to its
199 ;; native inputs.
200 (let ((inputs (package-inputs package)))
201 (match inputs
202 (((labels packages . _) ...)
203 (when (member "pkg-config"
204 (map package-name (filter package? packages)))
205 (emit-warning package
836f02bf 206 (_ "pkg-config should probably be a native input")
b4f5e0e8
CR
207 'inputs))))))
208
17854ef9
LC
209(define (package-name-regexp package)
210 "Return a regexp that matches PACKAGE's name as a word at the beginning of a
211line."
212 (make-regexp (string-append "^" (regexp-quote (package-name package))
213 "\\>")
214 regexp/icase))
b4f5e0e8
CR
215
216(define (check-synopsis-style package)
217 ;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE.
334c43e3
EB
218 (define (check-not-empty synopsis)
219 (when (string-null? synopsis)
220 (emit-warning package
836f02bf 221 (_ "synopsis should not be empty")
334c43e3
EB
222 'synopsis)))
223
b4f5e0e8
CR
224 (define (check-final-period synopsis)
225 ;; Synopsis should not end with a period, except for some special cases.
c04b82ff
EB
226 (when (and (string-suffix? "." synopsis)
227 (not (string-suffix? "etc." synopsis)))
228 (emit-warning package
836f02bf 229 (_ "no period allowed at the end of the synopsis")
c04b82ff 230 'synopsis)))
b4f5e0e8 231
105c260f
LC
232 (define check-start-article
233 ;; Skip this check for GNU packages, as suggested by Karl Berry's reply to
234 ;; <http://lists.gnu.org/archive/html/bug-womb/2014-11/msg00000.html>.
235 (if (false-if-exception (gnu-package? package))
236 (const #t)
237 (lambda (synopsis)
238 (when (or (string-prefix-ci? "A " synopsis)
239 (string-prefix-ci? "An " synopsis))
240 (emit-warning package
836f02bf
LC
241 (_ "no article allowed at the beginning of \
242the synopsis")
105c260f 243 'synopsis)))))
b4f5e0e8 244
5622953d 245 (define (check-synopsis-length synopsis)
c04b82ff
EB
246 (when (>= (string-length synopsis) 80)
247 (emit-warning package
836f02bf 248 (_ "synopsis should be less than 80 characters long")
c04b82ff 249 'synopsis)))
5622953d 250
903581f9
EB
251 (define (check-proper-start synopsis)
252 (unless (properly-starts-sentence? synopsis)
253 (emit-warning package
836f02bf 254 (_ "synopsis should start with an upper-case letter or digit")
903581f9 255 'synopsis)))
8202a513 256
3c762a13 257 (define (check-start-with-package-name synopsis)
15a6d433
LC
258 (when (and (regexp-exec (package-name-regexp package) synopsis)
259 (not (starts-with-abbreviation? synopsis)))
86a41263 260 (emit-warning package
836f02bf 261 (_ "synopsis should not start with the package name")
86a41263 262 'synopsis)))
3c762a13 263
b4f5e0e8 264 (let ((synopsis (package-synopsis package)))
c04b82ff 265 (when (string? synopsis)
903581f9
EB
266 (check-not-empty synopsis)
267 (check-proper-start synopsis)
268 (check-final-period synopsis)
269 (check-start-article synopsis)
270 (check-start-with-package-name synopsis)
271 (check-synopsis-length synopsis))))
b4f5e0e8 272
bd7e1ffa 273(define* (probe-uri uri #:key timeout)
a3bf0969
LC
274 "Probe URI, a URI object, and return two values: a symbol denoting the
275probing status, such as 'http-response' when we managed to get an HTTP
bd7e1ffa
LC
276response from URI, and additional details, such as the actual HTTP response.
277
278TIMEOUT is the maximum number of seconds (possibly an inexact number) to wait
279for connections to complete; when TIMEOUT is #f, wait as long as needed."
a3bf0969
LC
280 (define headers
281 '((User-Agent . "GNU Guile")
282 (Accept . "*/*")))
283
284 (let loop ((uri uri)
285 (visited '()))
286 (match (uri-scheme uri)
287 ((or 'http 'https)
288 (catch #t
289 (lambda ()
bd7e1ffa 290 (let ((port (open-connection-for-uri uri #:timeout timeout))
a3bf0969
LC
291 (request (build-request uri #:headers headers)))
292 (define response
293 (dynamic-wind
294 (const #f)
295 (lambda ()
296 (write-request request port)
297 (force-output port)
298 (read-response port))
299 (lambda ()
14d6ca3e 300 (close-connection port))))
a3bf0969
LC
301
302 (case (response-code response)
303 ((301 302 307)
304 (let ((location (response-location response)))
305 (if (or (not location) (member location visited))
306 (values 'http-response response)
307 (loop location (cons location visited))))) ;follow the redirect
308 (else
309 (values 'http-response response)))))
310 (lambda (key . args)
311 (case key
312 ((bad-header bad-header-component)
313 ;; This can happen if the server returns an invalid HTTP header,
314 ;; as is the case with the 'Date' header at sqlite.org.
315 (values 'invalid-http-response #f))
316 ((getaddrinfo-error system-error gnutls-error)
317 (values key args))
318 (else
319 (apply throw key args))))))
c9815b5d
CR
320 ('ftp
321 (catch #t
322 (lambda ()
862d2479 323 (let ((conn (ftp-open (uri-host uri) #:timeout timeout)))
c9815b5d
CR
324 (define response
325 (dynamic-wind
326 (const #f)
327 (lambda ()
284fe313
LC
328 (ftp-chdir conn (dirname (uri-path uri)))
329 (ftp-size conn (basename (uri-path uri))))
c9815b5d 330 (lambda ()
284fe313 331 (ftp-close conn))))
661c99a4 332 (values 'ftp-response '(ok))))
c9815b5d
CR
333 (lambda (key . args)
334 (case key
661c99a4
LC
335 ((ftp-error)
336 (values 'ftp-response `(error ,@args)))
c9815b5d
CR
337 ((getaddrinfo-error system-error gnutls-error)
338 (values key args))
339 (else
340 (apply throw key args))))))
a3bf0969 341 (_
c9815b5d 342 (values 'unknown-protocol #f)))))
a3bf0969 343
17a7b75c 344(define (validate-uri uri package field)
91a0b9cc 345 "Return #t if the given URI can be reached, otherwise return #f and emit a
17a7b75c
CR
346warning for PACKAGE mentionning the FIELD."
347 (let-values (((status argument)
bd7e1ffa 348 (probe-uri uri #:timeout 3))) ;wait at most 3 seconds
17a7b75c
CR
349 (case status
350 ((http-response)
06aac933 351 (or (= 200 (response-code argument))
91a0b9cc
LC
352 (begin
353 (emit-warning package
354 (format #f
355 (_ "URI ~a not reachable: ~a (~s)")
356 (uri->string uri)
357 (response-code argument)
358 (response-reason-phrase argument))
359 field)
360 #f)))
c9815b5d 361 ((ftp-response)
661c99a4
LC
362 (match argument
363 (('ok) #t)
364 (('error port command code message)
365 (emit-warning package
366 (format #f
367 (_ "URI ~a not reachable: ~a (~s)")
368 (uri->string uri)
91a0b9cc
LC
369 code (string-trim-both message)))
370 #f)))
17a7b75c
CR
371 ((getaddrinfo-error)
372 (emit-warning package
373 (format #f
374 (_ "URI ~a domain not found: ~a")
375 (uri->string uri)
376 (gai-strerror (car argument)))
06aac933
LC
377 field)
378 #f)
17a7b75c
CR
379 ((system-error)
380 (emit-warning package
381 (format #f
382 (_ "URI ~a unreachable: ~a")
383 (uri->string uri)
384 (strerror
385 (system-error-errno
386 (cons status argument))))
06aac933
LC
387 field)
388 #f)
17a7b75c
CR
389 ((invalid-http-response gnutls-error)
390 ;; Probably a misbehaving server; ignore.
391 #f)
c9815b5d 392 ((unknown-protocol) ;nothing we can do
17a7b75c
CR
393 #f)
394 (else
06aac933 395 (error "internal linter error" status)))))
17a7b75c 396
a3bf0969
LC
397(define (check-home-page package)
398 "Emit a warning if PACKAGE has an invalid 'home-page' field, or if that
399'home-page' is not reachable."
400 (let ((uri (and=> (package-home-page package) string->uri)))
401 (cond
402 ((uri? uri)
17a7b75c 403 (validate-uri uri package 'home-page))
a3bf0969
LC
404 ((not (package-home-page package))
405 (unless (or (string-contains (package-name package) "bootstrap")
406 (string=? (package-name package) "ld-wrapper"))
407 (emit-warning package
408 (_ "invalid value for home page")
409 'home-page)))
410 (else
411 (emit-warning package (format #f (_ "invalid home page URL: ~s")
412 (package-home-page package))
413 'home-page)))))
414
56b1b74c 415(define (check-patch-file-names package)
b210b35d
LC
416 "Emit a warning if the patches requires by PACKAGE are badly named or if the
417patch could not be found."
f3044a4b 418 (guard (c ((message-condition? c) ;raised by 'search-patch'
b210b35d
LC
419 (emit-warning package (condition-message c)
420 'patch-file-names)))
f3044a4b
ML
421 (unless (every (match-lambda ;patch starts with package name?
422 ((? string? patch)
423 (and=> (string-contains (basename patch)
424 (package-name package))
425 zero?))
426 (_ #f)) ;must be an <origin> or something like that.
427 (or (and=> (package-source package) origin-patches)
428 '()))
429 (emit-warning
430 package
431 (_ "file names of patches should start with the package name")
432 'patch-file-names))))
b4f5e0e8 433
37627ffa
LC
434(define (escape-quotes str)
435 "Replace any quote character in STR by an escaped quote character."
436 (list->string
437 (string-fold-right (lambda (chr result)
438 (match chr
439 (#\" (cons* #\\ #\"result))
440 (_ (cons chr result))))
441 '()
442 str)))
443
444(define official-gnu-packages*
445 (memoize
446 (lambda ()
447 "A memoizing version of 'official-gnu-packages' that returns the empty
448list when something goes wrong, such as a networking issue."
449 (let ((gnus (false-if-exception (official-gnu-packages))))
450 (or gnus '())))))
451
452(define (check-gnu-synopsis+description package)
453 "Make sure that, if PACKAGE is a GNU package, it uses the synopsis and
454descriptions maintained upstream."
455 (match (find (lambda (descriptor)
456 (string=? (gnu-package-name descriptor)
457 (package-name package)))
458 (official-gnu-packages*))
459 (#f ;not a GNU package, so nothing to do
460 #t)
461 (descriptor ;a genuine GNU package
462 (let ((upstream (gnu-package-doc-summary descriptor))
463 (downstream (package-synopsis package))
464 (loc (or (package-field-location package 'synopsis)
465 (package-location package))))
466 (unless (and upstream (string=? upstream downstream))
467 (format (guix-warning-port)
836f02bf 468 (_ "~a: ~a: proposed synopsis: ~s~%")
37627ffa
LC
469 (location->string loc) (package-full-name package)
470 upstream)))
471
472 (let ((upstream (gnu-package-doc-description descriptor))
473 (downstream (package-description package))
474 (loc (or (package-field-location package 'description)
475 (package-location package))))
476 (when (and upstream
477 (not (string=? (fill-paragraph upstream 100)
478 (fill-paragraph downstream 100))))
479 (format (guix-warning-port)
836f02bf 480 (_ "~a: ~a: proposed description:~% \"~a\"~%")
37627ffa
LC
481 (location->string loc) (package-full-name package)
482 (fill-paragraph (escape-quotes upstream) 77 7)))))))
483
17a7b75c
CR
484(define (check-source package)
485 "Emit a warning if PACKAGE has an invalid 'source' field, or if that
486'source' is not reachable."
2b5115f8
LC
487 (define (try-uris uris)
488 (run-with-state
489 (anym %state-monad
490 (lambda (uri)
491 (with-accumulated-warnings
492 (validate-uri uri package 'source)))
493 (append-map (cut maybe-expand-mirrors <> %mirrors)
494 uris))
495 '()))
496
17a7b75c
CR
497 (let ((origin (package-source package)))
498 (when (and origin
499 (eqv? (origin-method origin) url-fetch))
500 (let* ((strings (origin-uri origin))
501 (uris (if (list? strings)
502 (map string->uri strings)
503 (list (string->uri strings)))))
2b5115f8 504
06aac933 505 ;; Just make sure that at least one of the URIs is valid.
2b5115f8
LC
506 (call-with-values
507 (lambda () (try-uris uris))
508 (lambda (success? warnings)
509 ;; When everything fails, report all of WARNINGS, otherwise don't
510 ;; report anything.
511 ;;
512 ;; XXX: Ideally we'd still allow warnings to be raised if *some*
513 ;; URIs are unreachable, but distinguish that from the error case
514 ;; where *all* the URIs are unreachable.
515 (unless success?
516 (emit-warning package
517 (_ "all the source URIs are unreachable:")
518 'source)
519 (for-each (lambda (warning)
520 (display warning (guix-warning-port)))
521 (reverse warnings)))))))))
17a7b75c 522
50f5c46d
EB
523(define (check-source-file-name package)
524 "Emit a warning if PACKAGE's origin has no meaningful file name."
525 (define (origin-file-name-valid? origin)
526 ;; Return #t if the source file name contains only a version or is #f;
527 ;; indicates that the origin needs a 'file-name' field.
528 (let ((file-name (origin-actual-file-name origin))
529 (version (package-version package)))
530 (and file-name
531 (not (or (string-prefix? version file-name)
532 ;; Common in many projects is for the filename to start
533 ;; with a "v" followed by the version,
534 ;; e.g. "v3.2.0.tar.gz".
535 (string-prefix? (string-append "v" version) file-name))))))
536
537 (let ((origin (package-source package)))
538 (unless (or (not origin) (origin-file-name-valid? origin))
539 (emit-warning package
540 (_ "the source file name should contain the package name")
541 'source))))
542
002c57c6
LC
543(define (check-derivation package)
544 "Emit a warning if we fail to compile PACKAGE to a derivation."
545 (catch #t
546 (lambda ()
547 (guard (c ((nix-protocol-error? c)
548 (emit-warning package
549 (format #f (_ "failed to create derivation: ~a")
550 (nix-protocol-error-message c))))
551 ((message-condition? c)
552 (emit-warning package
553 (format #f (_ "failed to create derivation: ~a")
554 (condition-message c)))))
555 (with-store store
937690f9
LC
556 ;; Disable grafts since it can entail rebuilds.
557 (package-derivation store package #:graft? #f)
558
559 ;; If there's a replacement, make sure we can compute its
560 ;; derivation.
561 (match (package-replacement package)
562 (#f #t)
563 (replacement
564 (package-derivation store replacement #:graft? #f))))))
002c57c6
LC
565 (lambda args
566 (emit-warning package
567 (format #f (_ "failed to create derivation: ~s~%")
568 args)))))
569
52b9efe3
LC
570(define (check-license package)
571 "Warn about type errors of the 'license' field of PACKAGE."
572 (match (package-license package)
573 ((or (? license?)
574 ((? license?) ...))
575 #t)
576 (x
577 (emit-warning package (_ "invalid license field")
578 'license))))
579
4e70fe4d
LC
580(define (patch-file-name patch)
581 "Return the basename of PATCH's file name, or #f if the file name could not
582be determined."
583 (match patch
584 ((? string?)
585 (basename patch))
586 ((? origin?)
587 (and=> (origin-actual-file-name patch) basename))))
588
5432734b
LC
589(define (package-name->cpe-name name)
590 "Do a basic conversion of NAME, a Guix package name, to the corresponding
591Common Platform Enumeration (CPE) name."
592 (match name
593 ("icecat" "firefox") ;or "firefox_esr"
c5b303c5 594 ("grub" "grub2")
5432734b
LC
595 ;; TODO: Add more.
596 (_ name)))
597
4ce783a2
LC
598(define (current-vulnerabilities*)
599 "Like 'current-vulnerabilities', but return the empty list upon networking
600or HTTP errors. This allows network-less operation and makes problems with
601the NIST server non-fatal.."
602 (guard (c ((http-get-error? c)
603 (warning (_ "failed to retrieve CVE vulnerabilities \
604from ~s: ~a (~s)~%")
605 (uri->string (http-get-error-uri c))
606 (http-get-error-code c)
607 (http-get-error-reason c))
608 (warning (_ "assuming no CVE vulnerabilities~%"))
609 '()))
610 (catch 'getaddrinfo-error
611 (lambda ()
612 (current-vulnerabilities))
613 (lambda (key errcode)
614 (warning (_ "failed to lookup NIST host: ~a~%")
615 (gai-strerror errcode))
616 (warning (_ "assuming no CVE vulnerabilities~%"))
617 '()))))
618
5432734b
LC
619(define package-vulnerabilities
620 (let ((lookup (delay (vulnerabilities->lookup-proc
4ce783a2 621 (current-vulnerabilities*)))))
5432734b
LC
622 (lambda (package)
623 "Return a list of vulnerabilities affecting PACKAGE."
624 ((force lookup)
625 (package-name->cpe-name (package-name package))
626 (package-version package)))))
627
628(define (check-vulnerabilities package)
629 "Check for known vulnerabilities for PACKAGE."
630 (match (package-vulnerabilities package)
631 (()
632 #t)
633 ((vulnerabilities ...)
4e70fe4d
LC
634 (let* ((patches (filter-map patch-file-name
635 (or (and=> (package-source package)
636 origin-patches)
637 '())))
638 (unpatched (remove (lambda (vuln)
639 (find (cute string-contains
640 <> (vulnerability-id vuln))
641 patches))
642 vulnerabilities)))
643 (unless (null? unpatched)
644 (emit-warning package
645 (format #f (_ "probably vulnerable to ~a")
646 (string-join (map vulnerability-id unpatched)
647 ", "))))))))
5432734b 648
40a7d4e5
LC
649\f
650;;;
651;;; Source code formatting.
652;;;
653
654(define (report-tabulations package line line-number)
655 "Warn about tabulations found in LINE."
656 (match (string-index line #\tab)
657 (#f #t)
658 (index
659 (emit-warning package
660 (format #f (_ "tabulation on line ~a, column ~a")
661 line-number index)))))
662
663(define (report-trailing-white-space package line line-number)
664 "Warn about trailing white space in LINE."
665 (unless (or (string=? line (string-trim-right line))
666 (string=? line (string #\page)))
667 (emit-warning package
668 (format #f
669 (_ "trailing white space on line ~a")
670 line-number))))
671
672(define (report-long-line package line line-number)
673 "Emit a warning if LINE is too long."
674 ;; Note: We don't warn at 80 characters because sometimes hashes and URLs
675 ;; make it hard to fit within that limit and we want to avoid making too
676 ;; much noise.
677 (when (> (string-length line) 90)
678 (emit-warning package
679 (format #f (_ "line ~a is way too long (~a characters)")
680 line-number (string-length line)))))
681
e0566f12
LC
682(define %hanging-paren-rx
683 (make-regexp "^[[:blank:]]*[()]+[[:blank:]]*$"))
684
685(define (report-lone-parentheses package line line-number)
686 "Emit a warning if LINE contains hanging parentheses."
687 (when (regexp-exec %hanging-paren-rx line)
688 (emit-warning package
689 (format #f
690 (_ "line ~a: parentheses feel lonely, \
691move to the previous or next line")
692 line-number))))
693
40a7d4e5
LC
694(define %formatting-reporters
695 ;; List of procedures that report formatting issues. These are not separate
696 ;; checkers because they would need to re-read the file.
697 (list report-tabulations
698 report-trailing-white-space
e0566f12
LC
699 report-long-line
700 report-lone-parentheses))
40a7d4e5
LC
701
702(define* (report-formatting-issues package file starting-line
703 #:key (reporters %formatting-reporters))
704 "Report white-space issues in FILE starting from STARTING-LINE, and report
705them for PACKAGE."
706 (define last-line
707 ;; Number of the presumed last line.
708 ;; XXX: Ideally we'd stop at the boundaries of the surrounding sexp, but
709 ;; for now just use this simple heuristic.
710 (+ starting-line 60))
711
712 (call-with-input-file file
713 (lambda (port)
714 (let loop ((line-number 1))
715 (let ((line (read-line port)))
716 (or (eof-object? line)
717 (> line-number last-line)
718 (begin
719 (unless (< line-number starting-line)
720 (for-each (lambda (report)
721 (report package line line-number))
722 reporters))
723 (loop (+ 1 line-number)))))))))
724
725(define (check-formatting package)
726 "Check the formatting of the source code of PACKAGE."
727 (let ((location (package-location package)))
728 (when location
729 (and=> (search-path %load-path (location-file location))
730 (lambda (file)
731 ;; Report issues starting from the line before the 'package'
732 ;; form, which usually contains the 'define' form.
733 (report-formatting-issues package file
734 (- (location-line location) 1)))))))
17a7b75c 735
37627ffa
LC
736\f
737;;;
738;;; List of checkers.
739;;;
740
b4f5e0e8
CR
741(define %checkers
742 (list
8202a513 743 (lint-checker
f4d5bca3 744 (name 'description)
8202a513
CR
745 (description "Validate package descriptions")
746 (check check-description-style))
37627ffa 747 (lint-checker
f4d5bca3 748 (name 'gnu-description)
37627ffa
LC
749 (description "Validate synopsis & description of GNU packages")
750 (check check-gnu-synopsis+description))
b4f5e0e8 751 (lint-checker
f4d5bca3 752 (name 'inputs-should-be-native)
b4f5e0e8
CR
753 (description "Identify inputs that should be native inputs")
754 (check check-inputs-should-be-native))
755 (lint-checker
56b1b74c 756 (name 'patch-file-names)
b210b35d 757 (description "Validate file names and availability of patches")
56b1b74c 758 (check check-patch-file-names))
a3bf0969
LC
759 (lint-checker
760 (name 'home-page)
761 (description "Validate home-page URLs")
762 (check check-home-page))
17a7b75c 763 (lint-checker
52b9efe3
LC
764 (name 'license)
765 ;; TRANSLATORS: <license> is the name of a data type and must not be
766 ;; translated.
767 (description "Make sure the 'license' field is a <license> \
768or a list thereof")
769 (check check-license))
770 (lint-checker
17a7b75c
CR
771 (name 'source)
772 (description "Validate source URLs")
773 (check check-source))
50f5c46d
EB
774 (lint-checker
775 (name 'source-file-name)
776 (description "Validate file names of sources")
777 (check check-source-file-name))
002c57c6
LC
778 (lint-checker
779 (name 'derivation)
780 (description "Report failure to compile a package to a derivation")
781 (check check-derivation))
b4f5e0e8 782 (lint-checker
f4d5bca3 783 (name 'synopsis)
8b9019a6 784 (description "Validate package synopses")
40a7d4e5 785 (check check-synopsis-style))
5432734b
LC
786 (lint-checker
787 (name 'cve)
788 (description "Check the Common Vulnerabilities and Exposures\
789 (CVE) database")
790 (check check-vulnerabilities))
40a7d4e5
LC
791 (lint-checker
792 (name 'formatting)
793 (description "Look for formatting issues in the source")
794 (check check-formatting))))
b4f5e0e8 795
e04741f1
AK
796(define* (run-checkers package #:optional (checkers %checkers))
797 "Run the given CHECKERS on PACKAGE."
c79c6e59
LC
798 (let ((tty? (isatty? (current-error-port)))
799 (name (package-full-name package)))
800 (for-each (lambda (checker)
801 (when tty?
802 (format (current-error-port) "checking ~a [~a]...\r"
803 name (lint-checker-name checker))
804 (force-output (current-error-port)))
805 ((lint-checker-check checker) package))
806 checkers)))
dd7c013d
CR
807
808\f
809;;;
810;;; Command-line options.
811;;;
812
813(define %default-options
814 ;; Alist of default option values.
815 '())
816
817(define (show-help)
818 (display (_ "Usage: guix lint [OPTION]... [PACKAGE]...
2a4e2e4b
AK
819Run a set of checkers on the specified package; if none is specified,
820run the checkers on all packages.\n"))
dd7c013d
CR
821 (display (_ "
822 -c, --checkers=CHECKER1,CHECKER2...
69b4ffcf 823 only run the specified checkers"))
dd7c013d
CR
824 (display (_ "
825 -h, --help display this help and exit"))
826 (display (_ "
827 -l, --list-checkers display the list of available lint checkers"))
828 (display (_ "
829 -V, --version display version information and exit"))
830 (newline)
831 (show-bug-report-information))
832
833
834(define %options
835 ;; Specification of the command-line options.
836 ;; TODO: add some options:
837 ;; * --certainty=[low,medium,high]: only run checkers that have at least this
838 ;; 'certainty'.
839 (list (option '(#\c "checkers") #t #f
8fbf5302 840 (lambda (opt name arg result)
f4d5bca3 841 (let ((names (map string->symbol (string-split arg #\,))))
dd7c013d 842 (for-each (lambda (c)
f4d5bca3
LC
843 (unless (memq c
844 (map lint-checker-name
845 %checkers))
846 (leave (_ "~a: invalid checker~%") c)))
dd7c013d 847 names)
8fbf5302
LC
848 (alist-cons 'checkers
849 (filter (lambda (checker)
850 (member (lint-checker-name checker)
851 names))
852 %checkers)
853 result))))
dd7c013d
CR
854 (option '(#\h "help") #f #f
855 (lambda args
856 (show-help)
857 (exit 0)))
858 (option '(#\l "list-checkers") #f #f
859 (lambda args
860 (list-checkers-and-exit)))
861 (option '(#\V "version") #f #f
862 (lambda args
863 (show-version-and-exit "guix lint")))))
b4f5e0e8
CR
864
865\f
866;;;
867;;; Entry Point
868;;;
869
870(define (guix-lint . args)
871 (define (parse-options)
872 ;; Return the alist of option values.
873 (args-fold* args %options
8fbf5302 874 (lambda (opt name arg result)
b4f5e0e8 875 (leave (_ "~A: unrecognized option~%") name))
8fbf5302 876 (lambda (arg result)
b4f5e0e8 877 (alist-cons 'argument arg result))
8fbf5302 878 %default-options))
b4f5e0e8
CR
879
880 (let* ((opts (parse-options))
881 (args (filter-map (match-lambda
882 (('argument . value)
883 value)
884 (_ #f))
dd7c013d
CR
885 (reverse opts)))
886 (checkers (or (assoc-ref opts 'checkers) %checkers)))
887 (if (null? args)
888 (fold-packages (lambda (p r) (run-checkers p checkers)) '())
889 (for-each (lambda (spec)
890 (run-checkers (specification->package spec) checkers))
891 args))))