gnu: linux-libre 4.19: Update to 4.19.196.
[jackhill/guix/guix.git] / guix / lint.scm
CommitLineData
f363c836
CB
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com>
3;;; Copyright © 2014, 2015 Eric Bavier <bavier@member.fsf.org>
069bb95a 4;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
f363c836
CB
5;;; Copyright © 2015, 2016 Mathieu Lirzin <mthl@gnu.org>
6;;; Copyright © 2016 Danny Milosavljevic <dannym+a@scratchpost.org>
7;;; Copyright © 2016 Hartmut Goebel <h.goebel@crazy-compilers.com>
8;;; Copyright © 2017 Alex Kost <alezost@gmail.com>
1126bb9c 9;;; Copyright © 2017, 2021 Tobias Geerinckx-Rice <me@tobias.gr>
d95252ba 10;;; Copyright © 2017, 2018, 2020 Efraim Flashner <efraim@flashner.co.il>
f363c836 11;;; Copyright © 2018, 2019 Arun Isaac <arunisaac@systemreboot.net>
21887021 12;;; Copyright © 2020 Chris Marusich <cmmarusich@gmail.com>
464b1fff 13;;; Copyright © 2020 Timothy Sample <samplet@ngyro.com>
c68070e4 14;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
82b0e27d 15;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
f363c836
CB
16;;;
17;;; This file is part of GNU Guix.
18;;;
19;;; GNU Guix is free software; you can redistribute it and/or modify it
20;;; under the terms of the GNU General Public License as published by
21;;; the Free Software Foundation; either version 3 of the License, or (at
22;;; your option) any later version.
23;;;
24;;; GNU Guix is distributed in the hope that it will be useful, but
25;;; WITHOUT ANY WARRANTY; without even the implied warranty of
26;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27;;; GNU General Public License for more details.
28;;;
29;;; You should have received a copy of the GNU General Public License
30;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
31
32(define-module (guix lint)
f4cde9ac 33 #:use-module (guix store)
bc4d81d2 34 #:autoload (guix base16) (bytevector->base16-string)
f363c836
CB
35 #:use-module (guix base32)
36 #:use-module (guix diagnostics)
37 #:use-module (guix download)
38 #:use-module (guix ftp-client)
39 #:use-module (guix http-client)
40 #:use-module (guix packages)
41 #:use-module (guix i18n)
4f156c25 42 #:use-module ((guix gexp)
5532371a
MD
43 #:select (gexp? local-file? local-file-absolute-file-name
44 gexp->approximate-sexp))
f363c836
CB
45 #:use-module (guix licenses)
46 #:use-module (guix records)
47 #:use-module (guix grafts)
48 #:use-module (guix upstream)
49 #:use-module (guix utils)
50 #:use-module (guix memoization)
993023a2
LC
51 #:use-module (guix profiles)
52 #:use-module (guix monads)
f363c836
CB
53 #:use-module (guix scripts)
54 #:use-module ((guix ui) #:select (texi->plain-text fill-paragraph))
55 #:use-module (guix gnu-maintenance)
f363c836 56 #:use-module (guix cve)
55549c7b 57 #:use-module ((guix swh) #:hide (origin?))
6a7c4636
LC
58 #:autoload (guix git-download) (git-reference?
59 git-reference-url git-reference-commit)
464b1fff 60 #:use-module (guix import stackage)
f363c836
CB
61 #:use-module (ice-9 match)
62 #:use-module (ice-9 regex)
63 #:use-module (ice-9 format)
64 #:use-module (web client)
65 #:use-module (web uri)
66 #:use-module ((guix build download)
67 #:select (maybe-expand-mirrors
68 (open-connection-for-uri
f4cde9ac 69 . guix:open-connection-for-uri)))
f363c836
CB
70 #:use-module (web request)
71 #:use-module (web response)
468a5f86 72 #:autoload (gnutls) (error->string)
f363c836
CB
73 #:use-module (srfi srfi-1)
74 #:use-module (srfi srfi-6) ;Unicode string ports
75 #:use-module (srfi srfi-9)
76 #:use-module (srfi srfi-11)
77 #:use-module (srfi srfi-26)
78 #:use-module (srfi srfi-34)
79 #:use-module (srfi srfi-35)
80 #:use-module (ice-9 rdelim)
81 #:export (check-description-style
82 check-inputs-should-be-native
83 check-inputs-should-not-be-an-input-at-all
84 check-patch-file-names
4f156c25 85 check-patch-headers
f363c836
CB
86 check-synopsis-style
87 check-derivation
88 check-home-page
c68070e4 89 check-name
f363c836
CB
90 check-source
91 check-source-file-name
92 check-source-unstable-tarball
5532371a 93 check-optional-tests
f363c836
CB
94 check-mirror-url
95 check-github-url
96 check-license
97 check-vulnerabilities
98 check-for-updates
99 check-formatting
55549c7b 100 check-archival
993023a2 101 check-profile-collisions
464b1fff 102 check-haskell-stackage
82b0e27d 103 check-tests-true
f363c836
CB
104
105 lint-warning
106 lint-warning?
107 lint-warning-package
108 lint-warning-message
109 lint-warning-message-text
110 lint-warning-message-data
111 lint-warning-location
112
38f3176a
CB
113 %local-checkers
114 %network-dependent-checkers
115 %all-checkers
f363c836
CB
116
117 lint-checker
118 lint-checker?
119 lint-checker-name
120 lint-checker-description
3874dc5e
CB
121 lint-checker-check
122 lint-checker-requires-store?))
f363c836
CB
123
124\f
125;;;
126;;; Warnings
127;;;
128
129(define-record-type* <lint-warning>
130 lint-warning make-lint-warning
131 lint-warning?
132 (package lint-warning-package)
133 (message-text lint-warning-message-text)
134 (message-data lint-warning-message-data
135 (default '()))
136 (location lint-warning-location
137 (default #f)))
138
139(define (lint-warning-message warning)
140 (apply format #f
141 (G_ (lint-warning-message-text warning))
142 (lint-warning-message-data warning)))
143
144(define (package-file package)
145 (location-file
146 (package-location package)))
147
148(define* (%make-warning package message-text
149 #:optional (message-data '())
150 #:key field location)
151 (make-lint-warning
152 package
153 message-text
154 message-data
155 (or location
68193624 156 (and field (package-field-location package field))
f363c836
CB
157 (package-location package))))
158
159(define-syntax make-warning
160 (syntax-rules (G_)
161 ((_ package (G_ message) rest ...)
162 (%make-warning package message rest ...))))
163
164\f
165;;;
166;;; Checkers
167;;;
168
169(define-record-type* <lint-checker>
170 lint-checker make-lint-checker
171 lint-checker?
172 ;; TODO: add a 'certainty' field that shows how confident we are in the
173 ;; checker. Then allow users to only run checkers that have a certain
174 ;; 'certainty' level.
175 (name lint-checker-name)
176 (description lint-checker-description)
3874dc5e
CB
177 (check lint-checker-check)
178 (requires-store? lint-checker-requires-store?
179 (default #f)))
f363c836 180
1126bb9c
TGRBGG
181(define (check-name package)
182 "Check whether PACKAGE's name matches our guidelines."
183 (let ((name (package-name package)))
c68070e4
XC
184 (cond
185 ;; Currently checks only whether the name is too short.
186 ((and (<= (string-length name) 1)
187 (not (string=? name "r"))) ; common-sense exception
188 (list
189 (make-warning package
190 (G_ "name should be longer than a single character")
191 #:field 'name)))
192 ((string-index name #\_)
193 (list
194 (make-warning package
195 (G_ "name should use hyphens instead of underscores")
196 #:field 'name)))
197 (else '()))))
1126bb9c 198
82b0e27d
MD
199(define (check-tests-true package)
200 "Check whether PACKAGE explicitly requests to run tests, which is
201superfluous when building natively and incorrect when cross-compiling."
202 (define (tests-explicitly-enabled?)
203 (apply (lambda* (#:key tests? #:allow-other-keys)
204 (eq? tests? #t))
205 (package-arguments package)))
206 (if (and (tests-explicitly-enabled?)
207 ;; Some packages, e.g. gnutls, set #:tests?
208 ;; differently depending on whether it is being
209 ;; cross-compiled.
210 (parameterize ((%current-target-system "aarch64-linux-gnu"))
211 (tests-explicitly-enabled?)))
212 (list (make-warning package
213 ;; TRANSLATORS: #:tests? and #t are Scheme constants
214 ;; and must not be translated.
215 (G_ "#:tests? must not be explicitly set to #t")
216 #:field 'arguments))
217 '()))
218
f363c836
CB
219(define (properly-starts-sentence? s)
220 (string-match "^[(\"'`[:upper:][:digit:]]" s))
221
222(define (starts-with-abbreviation? s)
223 "Return #t if S starts with what looks like an abbreviation or acronym."
224 (string-match "^[A-Z][A-Z0-9]+\\>" s))
225
226(define %quoted-identifier-rx
227 ;; A quoted identifier, like 'this'.
228 (make-regexp "['`][[:graph:]]+'"))
229
230(define (check-description-style package)
231 ;; Emit a warning if stylistic issues are found in the description of PACKAGE.
232 (define (check-not-empty description)
233 (if (string-null? description)
234 (list
235 (make-warning package
236 (G_ "description should not be empty")
237 #:field 'description))
238 '()))
239
240 (define (check-texinfo-markup description)
241 "Check that DESCRIPTION can be parsed as a Texinfo fragment. If the
242markup is valid return a plain-text version of DESCRIPTION, otherwise #f."
243 (catch #t
244 (lambda () (texi->plain-text description))
245 (lambda (keys . args)
246 (make-warning package
247 (G_ "Texinfo markup in description is invalid")
248 #:field 'description))))
249
250 (define (check-trademarks description)
251 "Check that DESCRIPTION does not contain '™' or '®' characters. See
252http://www.gnu.org/prep/standards/html_node/Trademarks.html."
253 (match (string-index description (char-set #\™ #\®))
254 ((and (? number?) index)
255 (list
256 (make-warning package
257 (G_ "description should not contain ~
258trademark sign '~a' at ~d")
259 (list (string-ref description index) index)
260 #:field 'description)))
261 (else '())))
262
263 (define (check-quotes description)
264 "Check whether DESCRIPTION contains single quotes and suggest @code."
265 (if (regexp-exec %quoted-identifier-rx description)
266 (list
267 (make-warning package
268 ;; TRANSLATORS: '@code' is Texinfo markup and must be kept
269 ;; as is.
270 (G_ "use @code or similar ornament instead of quotes")
271 #:field 'description))
272 '()))
273
274 (define (check-proper-start description)
275 (if (or (string-null? description)
276 (properly-starts-sentence? description)
277 (string-prefix-ci? (package-name package) description))
278 '()
279 (list
280 (make-warning
281 package
282 (G_ "description should start with an upper-case letter or digit")
283 #:field 'description))))
284
285 (define (check-end-of-sentence-space description)
286 "Check that an end-of-sentence period is followed by two spaces."
287 (let ((infractions
288 (reverse (fold-matches
289 "\\. [A-Z]" description '()
290 (lambda (m r)
291 ;; Filter out matches of common abbreviations.
292 (if (find (lambda (s)
293 (string-suffix-ci? s (match:prefix m)))
294 '("i.e" "e.g" "a.k.a" "resp"))
295 r (cons (match:start m) r)))))))
296 (if (null? infractions)
297 '()
298 (list
299 (make-warning package
300 (G_ "sentences in description should be followed ~
301by two spaces; possible infraction~p at ~{~a~^, ~}")
302 (list (length infractions)
303 infractions)
304 #:field 'description)))))
305
93d85dea
XC
306 (define (check-no-trailing-whitespace description)
307 "Check that DESCRIPTION doesn't have trailing whitespace."
308 (if (string-suffix? " " description)
309 (list
310 (make-warning package
311 (G_ "description contains trailing whitespace")
312 #:field 'description))
313 '()))
314
f363c836
CB
315 (let ((description (package-description package)))
316 (if (string? description)
317 (append
318 (check-not-empty description)
319 (check-quotes description)
320 (check-trademarks description)
321 ;; Use raw description for this because Texinfo rendering
322 ;; automatically fixes end of sentence space.
323 (check-end-of-sentence-space description)
93d85dea 324 (check-no-trailing-whitespace description)
f363c836
CB
325 (match (check-texinfo-markup description)
326 ((and warning (? lint-warning?)) (list warning))
327 (plain-description
328 (check-proper-start plain-description))))
329 (list
330 (make-warning package
331 (G_ "invalid description: ~s")
332 (list description)
333 #:field 'description)))))
334
335(define (package-input-intersection inputs-to-check input-names)
336 "Return the intersection between INPUTS-TO-CHECK, the list of input tuples
337of a package, and INPUT-NAMES, a list of package specifications such as
338\"glib:bin\"."
339 (match inputs-to-check
340 (((labels packages . outputs) ...)
341 (filter-map (lambda (package output)
342 (and (package? package)
343 (let ((input (string-append
344 (package-name package)
345 (if (> (length output) 0)
346 (string-append ":" (car output))
347 ""))))
348 (and (member input input-names)
349 input))))
350 packages outputs))))
351
352(define (check-inputs-should-be-native package)
353 ;; Emit a warning if some inputs of PACKAGE are likely to belong to its
354 ;; native inputs.
d95252ba
EF
355 (let ((inputs (append (package-inputs package)
356 (package-propagated-inputs package)))
f363c836
CB
357 (input-names
358 '("pkg-config"
bbd9063a
DM
359 "autoconf"
360 "automake"
361 "bison"
f363c836 362 "cmake"
bbd9063a
DM
363 "dejagnu"
364 "desktop-file-utils"
365 "doxygen"
f363c836 366 "extra-cmake-modules"
bbd9063a
DM
367 "flex"
368 "gettext"
f363c836 369 "glib:bin"
bbd9063a
DM
370 "gobject-introspection"
371 "googletest-source"
372 "groff"
1e44ae6f 373 "gtk-doc"
bbd9063a 374 "help2man"
f363c836
CB
375 "intltool"
376 "itstool"
bbd9063a 377 "libtool"
9ac6d378 378 "m4"
f363c836 379 "qttools"
e8c6644a 380 "yasm" "nasm" "fasm"
f363c836
CB
381 "python-coverage" "python2-coverage"
382 "python-cython" "python2-cython"
383 "python-docutils" "python2-docutils"
384 "python-mock" "python2-mock"
385 "python-nose" "python2-nose"
386 "python-pbr" "python2-pbr"
387 "python-pytest" "python2-pytest"
388 "python-pytest-cov" "python2-pytest-cov"
389 "python-setuptools-scm" "python2-setuptools-scm"
bbd9063a 390 "python-sphinx" "python2-sphinx"
3e000955 391 "scdoc"
bbd9063a
DM
392 "swig"
393 "qmake"
394 "qttools"
395 "texinfo"
396 "xorg-server-for-tests"
397 "yelp-tools")))
f363c836
CB
398 (map (lambda (input)
399 (make-warning
400 package
401 (G_ "'~a' should probably be a native input")
402 (list input)
403 #:field 'inputs))
404 (package-input-intersection inputs input-names))))
405
406(define (check-inputs-should-not-be-an-input-at-all package)
407 ;; Emit a warning if some inputs of PACKAGE are likely to should not be
408 ;; an input at all.
409 (let ((input-names '("python-setuptools"
410 "python2-setuptools"
411 "python-pip"
412 "python2-pip")))
413 (map (lambda (input)
414 (make-warning
415 package
416 (G_ "'~a' should probably not be an input at all")
417 (list input)
418 #:field 'inputs))
419 (package-input-intersection (package-direct-inputs package)
420 input-names))))
421
422(define (package-name-regexp package)
423 "Return a regexp that matches PACKAGE's name as a word at the beginning of a
424line."
425 (make-regexp (string-append "^" (regexp-quote (package-name package))
426 "\\>")
427 regexp/icase))
428
429(define (check-synopsis-style package)
430 ;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE.
431 (define (check-final-period synopsis)
432 ;; Synopsis should not end with a period, except for some special cases.
433 (if (and (string-suffix? "." synopsis)
434 (not (string-suffix? "etc." synopsis)))
435 (list
436 (make-warning package
437 (G_ "no period allowed at the end of the synopsis")
438 #:field 'synopsis))
439 '()))
440
441 (define check-start-article
442 ;; Skip this check for GNU packages, as suggested by Karl Berry's reply to
443 ;; <http://lists.gnu.org/archive/html/bug-womb/2014-11/msg00000.html>.
444 (if (false-if-exception (gnu-package? package))
445 (const '())
446 (lambda (synopsis)
447 (if (or (string-prefix-ci? "A " synopsis)
448 (string-prefix-ci? "An " synopsis))
449 (list
450 (make-warning package
451 (G_ "no article allowed at the beginning of \
452the synopsis")
453 #:field 'synopsis))
454 '()))))
455
456 (define (check-synopsis-length synopsis)
457 (if (>= (string-length synopsis) 80)
458 (list
459 (make-warning package
460 (G_ "synopsis should be less than 80 characters long")
461 #:field 'synopsis))
462 '()))
463
464 (define (check-proper-start synopsis)
465 (if (properly-starts-sentence? synopsis)
466 '()
467 (list
468 (make-warning package
469 (G_ "synopsis should start with an upper-case letter or digit")
470 #:field 'synopsis))))
471
472 (define (check-start-with-package-name synopsis)
473 (if (and (regexp-exec (package-name-regexp package) synopsis)
474 (not (starts-with-abbreviation? synopsis)))
475 (list
476 (make-warning package
477 (G_ "synopsis should not start with the package name")
478 #:field 'synopsis))
479 '()))
480
481 (define (check-texinfo-markup synopsis)
482 "Check that SYNOPSIS can be parsed as a Texinfo fragment. If the
483markup is valid return a plain-text version of SYNOPSIS, otherwise #f."
484 (catch #t
485 (lambda ()
486 (texi->plain-text synopsis)
487 '())
488 (lambda (keys . args)
489 (list
490 (make-warning package
491 (G_ "Texinfo markup in synopsis is invalid")
492 #:field 'synopsis)))))
493
04afb769
XC
494 (define (check-no-trailing-whitespace synopsis)
495 "Check that SYNOPSIS doesn't have trailing whitespace."
496 (if (string-suffix? " " synopsis)
497 (list
498 (make-warning package
499 (G_ "synopsis contains trailing whitespace")
500 #:field 'synopsis))
501 '()))
502
f363c836
CB
503 (define checks
504 (list check-proper-start
505 check-final-period
506 check-start-article
507 check-start-with-package-name
508 check-synopsis-length
04afb769
XC
509 check-texinfo-markup
510 check-no-trailing-whitespace))
f363c836
CB
511
512 (match (package-synopsis package)
513 (""
514 (list
515 (make-warning package
516 (G_ "synopsis should not be empty")
517 #:field 'synopsis)))
518 ((? string? synopsis)
519 (append-map
520 (lambda (proc)
521 (proc synopsis))
522 checks))
523 (invalid
524 (list
525 (make-warning package
526 (G_ "invalid synopsis: ~s")
527 (list invalid)
528 #:field 'synopsis)))))
529
530(define* (probe-uri uri #:key timeout)
531 "Probe URI, a URI object, and return two values: a symbol denoting the
532probing status, such as 'http-response' when we managed to get an HTTP
533response from URI, and additional details, such as the actual HTTP response.
534
535TIMEOUT is the maximum number of seconds (possibly an inexact number) to wait
536for connections to complete; when TIMEOUT is #f, wait as long as needed."
537 (define headers
538 '((User-Agent . "GNU Guile")
539 (Accept . "*/*")))
540
541 (let loop ((uri uri)
542 (visited '()))
543 (match (uri-scheme uri)
544 ((or 'http 'https)
545 (catch #t
546 (lambda ()
547 (let ((port (guix:open-connection-for-uri
548 uri #:timeout timeout))
549 (request (build-request uri #:headers headers)))
550 (define response
551 (dynamic-wind
552 (const #f)
553 (lambda ()
554 (write-request request port)
555 (force-output port)
556 (read-response port))
557 (lambda ()
f4cde9ac 558 (close-port port))))
f363c836
CB
559
560 (case (response-code response)
561 ((302 ; found (redirection)
562 303 ; see other
563 307 ; temporary redirection
564 308) ; permanent redirection
565 (let ((location (response-location response)))
566 (if (or (not location) (member location visited))
567 (values 'http-response response)
568 (loop location (cons location visited))))) ;follow the redirect
569 ((301) ; moved permanently
570 (let ((location (response-location response)))
571 ;; Return RESPONSE, unless the final response as we follow
572 ;; redirects is not 200.
573 (if location
574 (let-values (((status response2)
575 (loop location (cons location visited))))
576 (case status
577 ((http-response)
578 (values 'http-response
579 (if (= 200 (response-code response2))
580 response
581 response2)))
582 (else
583 (values status response2))))
584 (values 'http-response response)))) ;invalid redirect
585 (else
586 (values 'http-response response)))))
587 (lambda (key . args)
588 (case key
589 ((bad-header bad-header-component)
590 ;; This can happen if the server returns an invalid HTTP header,
591 ;; as is the case with the 'Date' header at sqlite.org.
592 (values 'invalid-http-response #f))
593 ((getaddrinfo-error system-error
594 gnutls-error tls-certificate-error)
595 (values key args))
596 (else
597 (apply throw key args))))))
598 ('ftp
599 (catch #t
600 (lambda ()
601 (let ((conn (ftp-open (uri-host uri) #:timeout timeout)))
602 (define response
603 (dynamic-wind
604 (const #f)
605 (lambda ()
606 (ftp-chdir conn (dirname (uri-path uri)))
607 (ftp-size conn (basename (uri-path uri))))
608 (lambda ()
609 (ftp-close conn))))
610 (values 'ftp-response '(ok))))
611 (lambda (key . args)
612 (case key
613 ((ftp-error)
614 (values 'ftp-response `(error ,@args)))
615 ((getaddrinfo-error system-error gnutls-error)
616 (values key args))
617 (else
618 (apply throw key args))))))
619 (_
620 (values 'unknown-protocol #f)))))
621
8a81ae61
LC
622(define (call-with-networking-fail-safe message error-value proc)
623 "Call PROC catching any network-related errors. Upon a networking error,
624display a message including MESSAGE and return ERROR-VALUE."
625 (guard (c ((http-get-error? c)
626 (warning (G_ "~a: HTTP GET error for ~a: ~a (~s)~%")
627 message
628 (uri->string (http-get-error-uri c))
629 (http-get-error-code c)
630 (http-get-error-reason c))
631 error-value))
632 (catch #t
633 proc
634 (match-lambda*
635 (('getaddrinfo-error errcode)
636 (warning (G_ "~a: host lookup failure: ~a~%")
637 message
638 (gai-strerror errcode))
639 error-value)
640 (('tls-certificate-error args ...)
641 (warning (G_ "~a: TLS certificate error: ~a")
642 message
643 (tls-certificate-error-string args))
644 error-value)
645 (('gnutls-error error function _ ...)
646 (warning (G_ "~a: TLS error in '~a': ~a~%")
647 message
648 function (error->string error))
649 error-value)
650 ((and ('system-error _ ...) args)
651 (let ((errno (system-error-errno args)))
652 (if (member errno (list ECONNRESET ECONNABORTED ECONNREFUSED))
653 (let ((details (call-with-output-string
654 (lambda (port)
655 (print-exception port #f (car args)
656 (cdr args))))))
657 (warning (G_ "~a: ~a~%") message details)
658 error-value)
659 (apply throw args))))
660 (args
661 (apply throw args))))))
662
663(define-syntax-rule (with-networking-fail-safe message error-value exp ...)
664 (call-with-networking-fail-safe message error-value
665 (lambda () exp ...)))
666
f363c836
CB
667(define (tls-certificate-error-string args)
668 "Return a string explaining the 'tls-certificate-error' arguments ARGS."
669 (call-with-output-string
670 (lambda (port)
671 (print-exception port #f
672 'tls-certificate-error args))))
673
674(define (validate-uri uri package field)
675 "Return #t if the given URI can be reached, otherwise return a warning for
7f694149 676PACKAGE mentioning the FIELD."
f363c836
CB
677 (let-values (((status argument)
678 (probe-uri uri #:timeout 3))) ;wait at most 3 seconds
679 (case status
680 ((http-response)
681 (cond ((= 200 (response-code argument))
682 (match (response-content-length argument)
683 ((? number? length)
684 ;; As of July 2016, SourceForge returns 200 (instead of 404)
685 ;; with a small HTML page upon failure. Attempt to detect
686 ;; such malicious behavior.
687 (or (> length 1000)
688 (make-warning package
689 (G_ "URI ~a returned \
690suspiciously small file (~a bytes)")
691 (list (uri->string uri)
692 length)
693 #:field field)))
694 (_ #t)))
695 ((= 301 (response-code argument))
696 (if (response-location argument)
697 (make-warning package
698 (G_ "permanent redirect from ~a to ~a")
699 (list (uri->string uri)
700 (uri->string
701 (response-location argument)))
702 #:field field)
703 (make-warning package
704 (G_ "invalid permanent redirect \
705from ~a")
706 (list (uri->string uri))
707 #:field field)))
708 (else
709 (make-warning package
710 (G_ "URI ~a not reachable: ~a (~s)")
711 (list (uri->string uri)
712 (response-code argument)
713 (response-reason-phrase argument))
714 #:field field))))
715 ((ftp-response)
716 (match argument
717 (('ok) #t)
718 (('error port command code message)
719 (make-warning package
720 (G_ "URI ~a not reachable: ~a (~s)")
721 (list (uri->string uri)
722 code (string-trim-both message))
723 #:field field))))
724 ((getaddrinfo-error)
725 (make-warning package
726 (G_ "URI ~a domain not found: ~a")
727 (list (uri->string uri)
728 (gai-strerror (car argument)))
729 #:field field))
730 ((system-error)
731 (make-warning package
732 (G_ "URI ~a unreachable: ~a")
733 (list (uri->string uri)
734 (strerror
735 (system-error-errno
736 (cons status argument))))
737 #:field field))
738 ((tls-certificate-error)
739 (make-warning package
740 (G_ "TLS certificate error: ~a")
741 (list (tls-certificate-error-string argument))
742 #:field field))
743 ((invalid-http-response gnutls-error)
744 ;; Probably a misbehaving server; ignore.
745 #f)
746 ((unknown-protocol) ;nothing we can do
747 #f)
748 (else
749 (error "internal linter error" status)))))
750
751(define (check-home-page package)
752 "Emit a warning if PACKAGE has an invalid 'home-page' field, or if that
753'home-page' is not reachable."
754 (let ((uri (and=> (package-home-page package) string->uri)))
755 (cond
756 ((uri? uri)
757 (match (validate-uri uri package 'home-page)
758 ((and (? lint-warning? warning) warning)
759 (list warning))
760 (_ '())))
761 ((not (package-home-page package))
762 (if (or (string-contains (package-name package) "bootstrap")
763 (string=? (package-name package) "ld-wrapper"))
764 '()
765 (list
766 (make-warning package
767 (G_ "invalid value for home page")
768 #:field 'home-page))))
769 (else
770 (list
771 (make-warning package
772 (G_ "invalid home page URL: ~s")
773 (list (package-home-page package))
774 #:field 'home-page))))))
775
776(define %distro-directory
777 (mlambda ()
778 (dirname (search-path %load-path "gnu.scm"))))
779
780(define (check-patch-file-names package)
781 "Emit a warning if the patches requires by PACKAGE are badly named or if the
782patch could not be found."
b7398136 783 (guard (c ((formatted-message? c) ;raised by 'search-patch'
d51bfe24 784 (list (%make-warning package
b7398136
LC
785 (formatted-message-string c)
786 (formatted-message-arguments c)
787 #:field 'source))))
f363c836 788 (define patches
37c3e0bb
LC
789 (match (package-source package)
790 ((? origin? origin) (origin-patches origin))
791 (_ '())))
f363c836 792
21887021
CM
793 (define (starts-with-package-name? file-name)
794 (and=> (string-contains file-name (package-name package))
795 zero?))
796
f363c836
CB
797 (append
798 (if (every (match-lambda ;patch starts with package name?
799 ((? string? patch)
21887021
CM
800 (starts-with-package-name? (basename patch)))
801 ((? origin? patch)
db6290af 802 (starts-with-package-name? (origin-actual-file-name patch)))
21887021 803 (_ #f)) ;must be some other file-like object
f363c836
CB
804 patches)
805 '()
806 (list
807 (make-warning
808 package
809 (G_ "file names of patches should start with the package name")
810 #:field 'patch-file-names)))
811
812 ;; Check whether we're reaching tar's maximum file name length.
813 (let ((prefix (string-length (%distro-directory)))
92587f8e 814 (margin (string-length "guix-2.0.0rc3-10000-1234567890/"))
f363c836
CB
815 (max 99))
816 (filter-map (match-lambda
817 ((? string? patch)
818 (if (> (+ margin (if (string-prefix? (%distro-directory)
819 patch)
820 (- (string-length patch) prefix)
821 (string-length patch)))
822 max)
823 (make-warning
824 package
825 (G_ "~a: file name is too long")
826 (list (basename patch))
827 #:field 'patch-file-names)
828 #f))
829 (_ #f))
830 patches)))))
831
4f156c25
LC
832(define (check-patch-headers package)
833 "Check that PACKAGE's patches start with a comment. Return a list of
834warnings."
835 (define (blank? str)
836 (string-every char-set:blank str))
837
838 (define (patch-header-warnings patch)
839 (call-with-input-file patch
840 (lambda (port)
841 ;; Read from PORT until a non-blank line is found or EOF is reached.
842 (let loop ()
843 (let ((line (read-line port)))
844 (cond ((eof-object? line)
845 (list (make-warning package
846 (G_ "~a: empty patch")
847 (list (basename patch))
848 #:field 'source)))
849 ((blank? line)
850 (loop))
851 ((or (string-prefix? "--- " line)
c5b1b48f
LC
852 (string-prefix? "+++ " line)
853 (string-prefix? "diff --git " line))
4f156c25
LC
854 (list (make-warning package
855 (G_ "~a: patch lacks comment and \
856upstream status")
857 (list (basename patch))
858 #:field 'source)))
859 (else
860 '())))))))
861
862 (guard (c ((formatted-message? c) ;raised by 'search-patch'
863 (list (%make-warning package
864 (formatted-message-string c)
865 (formatted-message-arguments c)
866 #:field 'source))))
867 (let ((patches (if (origin? (package-source package))
868 (origin-patches (package-source package))
869 '())))
870 (append-map (lambda (patch)
871 ;; Dismiss PATCH if it's an origin or similar.
872 (cond ((string? patch)
873 (patch-header-warnings patch))
874 ((local-file? patch)
875 (patch-header-warnings
876 (local-file-absolute-file-name patch)))
877 (else
878 '())))
879 patches))))
880
f363c836
CB
881(define (escape-quotes str)
882 "Replace any quote character in STR by an escaped quote character."
883 (list->string
884 (string-fold-right (lambda (chr result)
885 (match chr
886 (#\" (cons* #\\ #\"result))
887 (_ (cons chr result))))
888 '()
889 str)))
890
891(define official-gnu-packages*
892 (mlambda ()
893 "A memoizing version of 'official-gnu-packages' that returns the empty
894list when something goes wrong, such as a networking issue."
895 (let ((gnus (false-if-exception (official-gnu-packages))))
896 (or gnus '()))))
897
898(define (check-gnu-synopsis+description package)
899 "Make sure that, if PACKAGE is a GNU package, it uses the synopsis and
900descriptions maintained upstream."
901 (match (find (lambda (descriptor)
902 (string=? (gnu-package-name descriptor)
903 (package-name package)))
904 (official-gnu-packages*))
905 (#f ;not a GNU package, so nothing to do
906 '())
907 (descriptor ;a genuine GNU package
908 (append
909 (let ((upstream (gnu-package-doc-summary descriptor))
910 (downstream (package-synopsis package)))
911 (if (and upstream
912 (or (not (string? downstream))
913 (not (string=? upstream downstream))))
914 (list
915 (make-warning package
916 (G_ "proposed synopsis: ~s~%")
917 (list upstream)
918 #:field 'synopsis))
919 '()))
920
921 (let ((upstream (gnu-package-doc-description descriptor))
922 (downstream (package-description package)))
923 (if (and upstream
924 (or (not (string? downstream))
925 (not (string=? (fill-paragraph upstream 100)
926 (fill-paragraph downstream 100)))))
927 (list
928 (make-warning
929 package
930 (G_ "proposed description:~% \"~a\"~%")
931 (list (fill-paragraph (escape-quotes upstream) 77 7))
932 #:field 'description))
933 '()))))))
934
935(define (origin-uris origin)
936 "Return the list of URIs (strings) for ORIGIN."
937 (match (origin-uri origin)
938 ((? string? uri)
939 (list uri))
940 ((uris ...)
941 uris)))
942
943(define (check-source package)
944 "Emit a warning if PACKAGE has an invalid 'source' field, or if that
945'source' is not reachable."
946 (define (warnings-for-uris uris)
674b9df3
LC
947 (let loop ((uris uris)
948 (warnings '()))
949 (match uris
950 (()
951 (reverse warnings))
952 ((uri rest ...)
953 (match (validate-uri uri package 'source)
954 (#t
955 ;; We found a working URL, so stop right away.
956 '())
7caa3506
LDB
957 (#f
958 ;; Unsupported URL or other error, skip.
959 (loop rest warnings))
674b9df3
LC
960 ((? lint-warning? warning)
961 (loop rest (cons warning warnings))))))))
f363c836
CB
962
963 (let ((origin (package-source package)))
c1052667
LC
964 (if (origin? origin)
965 (cond
966 ((eq? (origin-method origin) url-fetch)
967 (let* ((uris (append-map (cut maybe-expand-mirrors <> %mirrors)
968 (map string->uri (origin-uris origin))))
969 (warnings (warnings-for-uris uris)))
970
971 ;; Just make sure that at least one of the URIs is valid.
972 (if (= (length uris) (length warnings))
973 ;; When everything fails, report all of WARNINGS, otherwise don't
974 ;; report anything.
975 ;;
976 ;; XXX: Ideally we'd still allow warnings to be raised if *some*
977 ;; URIs are unreachable, but distinguish that from the error case
978 ;; where *all* the URIs are unreachable.
979 (cons*
980 (make-warning package
981 (G_ "all the source URIs are unreachable:")
982 #:field 'source)
983 warnings)
984 '())))
985 ((git-reference? (origin-uri origin))
986 (warnings-for-uris
987 (list (string->uri (git-reference-url (origin-uri origin))))))
988 (else
e5c00059
LC
989 '()))
990 '())))
f363c836
CB
991
992(define (check-source-file-name package)
993 "Emit a warning if PACKAGE's origin has no meaningful file name."
994 (define (origin-file-name-valid? origin)
995 ;; Return #f if the source file name contains only a version or is #f;
996 ;; indicates that the origin needs a 'file-name' field.
997 (let ((file-name (origin-actual-file-name origin))
998 (version (package-version package)))
999 (and file-name
1000 ;; Common in many projects is for the filename to start
1001 ;; with a "v" followed by the version,
1002 ;; e.g. "v3.2.0.tar.gz".
1003 (not (string-match (string-append "^v?" version) file-name)))))
1004
1005 (let ((origin (package-source package)))
37c3e0bb 1006 (if (or (not (origin? origin)) (origin-file-name-valid? origin))
f363c836
CB
1007 '()
1008 (list
1009 (make-warning package
1010 (G_ "the source file name should contain the package name")
1011 #:field 'source)))))
1012
1013(define (check-source-unstable-tarball package)
1014 "Emit a warning if PACKAGE's source is an autogenerated tarball."
1015 (define (check-source-uri uri)
1016 (if (and (string=? (uri-host (string->uri uri)) "github.com")
1017 (match (split-and-decode-uri-path
1018 (uri-path (string->uri uri)))
1019 ((_ _ "archive" _ ...) #t)
1020 (_ #f)))
1021 (make-warning package
1022 (G_ "the source URI should not be an autogenerated tarball")
1023 #:field 'source)
1024 #f))
1025
1026 (let ((origin (package-source package)))
1027 (if (and (origin? origin)
1028 (eqv? (origin-method origin) url-fetch))
1029 (filter-map check-source-uri
1030 (origin-uris origin))
1031 '())))
1032
1033(define (check-mirror-url package)
1034 "Check whether PACKAGE uses source URLs that should be 'mirror://'."
1035 (define (check-mirror-uri uri) ;XXX: could be optimized
1036 (let loop ((mirrors %mirrors))
1037 (match mirrors
1038 (()
1039 #f)
1040 (((mirror-id mirror-urls ...) rest ...)
1041 (match (find (cut string-prefix? <> uri) mirror-urls)
1042 (#f
1043 (loop rest))
1044 (prefix
1045 (make-warning package
1046 (G_ "URL should be \
1047'mirror://~a/~a'")
1048 (list mirror-id
1049 (string-drop uri (string-length prefix)))
1050 #:field 'source)))))))
1051
1052 (let ((origin (package-source package)))
1053 (if (and (origin? origin)
1054 (eqv? (origin-method origin) url-fetch))
1055 (let ((uris (origin-uris origin)))
1056 (filter-map check-mirror-uri uris))
1057 '())))
1058
1059(define* (check-github-url package #:key (timeout 3))
1060 "Check whether PACKAGE uses source URLs that redirect to GitHub."
1061 (define (follow-redirect url)
1062 (let* ((uri (string->uri url))
1063 (port (guix:open-connection-for-uri uri #:timeout timeout))
1064 (response (http-head uri #:port port)))
1065 (close-port port)
1066 (case (response-code response)
1067 ((301 302)
1068 (uri->string (assoc-ref (response-headers response) 'location)))
1069 (else #f))))
1070
1071 (define (follow-redirects-to-github uri)
1072 (cond
1073 ((string-prefix? "https://github.com/" uri) uri)
1074 ((string-prefix? "http" uri)
1075 (and=> (follow-redirect uri) follow-redirects-to-github))
1076 ;; Do not attempt to follow redirects on URIs other than http and https
1077 ;; (such as mirror, file)
1078 (else #f)))
1079
1080 (let ((origin (package-source package)))
1081 (if (and (origin? origin)
1082 (eqv? (origin-method origin) url-fetch))
1083 (filter-map
1084 (lambda (uri)
8a81ae61
LC
1085 (and=> (with-networking-fail-safe
1086 (format #f (G_ "while accessing '~a'") uri)
1087 #f
1088 (follow-redirects-to-github uri))
f363c836 1089 (lambda (github-uri)
8a81ae61
LC
1090 (and (not (string=? github-uri uri))
1091 (make-warning
1092 package
1093 (G_ "URL should be '~a'")
1094 (list github-uri)
1095 #:field 'source)))))
f363c836
CB
1096 (origin-uris origin))
1097 '())))
1098
82d8ab01
LC
1099;; Guile 3.0.0 does not export this predicate.
1100(define exception-with-kind-and-args?
1101 (exception-predicate &exception-with-kind-and-args))
fd4c832b 1102
5532371a
MD
1103(define (check-optional-tests package)
1104 "Emit a warning if the test suite is run unconditionally."
1105 (define (sexp-contains-atom? sexp atom)
1106 "Test if SEXP contains ATOM."
1107 (if (pair? sexp)
1108 (or (sexp-contains-atom? (car sexp) atom)
1109 (sexp-contains-atom? (cdr sexp) atom))
1110 (eq? sexp atom)))
1111 (define (sexp-uses-tests?? sexp)
1112 "Test if SEXP contains the symbol 'tests?'."
1113 (sexp-contains-atom? sexp 'tests?))
1114 (define (check-check-procedure expression)
1115 (match expression
1116 (`(,(or 'let 'let*) . ,_)
1117 (check-check-procedure (car (last-pair expression))))
1118 (`(,(or 'lambda 'lambda*) ,_ . ,code)
1119 (if (sexp-uses-tests?? code)
1120 '()
1121 (list (make-warning package
1122 ;; TRANSLATORS: check and #:tests? are a
1123 ;; Scheme symbol and keyword respectively
1124 ;; and should not be translated.
1125 (G_ "the 'check' phase should respect #:tests?")
1126 #:field 'arguments))))
1127 (_ '())))
1128 (define (check-phases-delta delta)
1129 (match delta
1130 (`(replace 'check ,expression)
1131 (check-check-procedure expression))
1132 (_ '())))
1133 (define (check-phases-deltas deltas)
1134 (match deltas
1135 (() '())
1136 ((head . tail)
1137 (append (check-phases-delta head)
1138 (check-phases-deltas tail)))
1139 (_ (list (make-warning package
1140 ;; TRANSLATORS: modify-phases is a Scheme
1141 ;; syntax and must not be translated.
1142 (G_ "incorrect call to ‘modify-phases’")
1143 #:field 'arguments)))))
1144 (apply (lambda* (#:key phases #:allow-other-keys)
1145 (define phases/sexp
1146 (if (gexp? phases)
1147 (gexp->approximate-sexp phases)
1148 phases))
1149 (match phases/sexp
1150 (`(modify-phases ,_ . ,changes)
1151 (check-phases-deltas changes))
1152 (_ '())))
1153 (package-arguments package)))
1154
7826fbc0 1155(define* (check-derivation package #:key store)
f363c836 1156 "Emit a warning if we fail to compile PACKAGE to a derivation."
7826fbc0 1157 (define (try store system)
5bcb4f8a
LC
1158 (guard (c ((store-protocol-error? c)
1159 (make-warning package
1160 (G_ "failed to create ~a derivation: ~a")
1161 (list system
1162 (store-protocol-error-message c))))
1163 ((exception-with-kind-and-args? c)
1164 (make-warning package
1165 (G_ "failed to create ~a derivation: ~s")
1166 (list system
1167 (cons (exception-kind c)
1168 (exception-args c)))))
1169 ((message-condition? c)
1170 (make-warning package
1171 (G_ "failed to create ~a derivation: ~a")
1172 (list system
1173 (condition-message c))))
1174 ((formatted-message? c)
1175 (let ((str (apply format #f
1176 (formatted-message-string c)
1177 (formatted-message-arguments c))))
1178 (make-warning package
1179 (G_ "failed to create ~a derivation: ~a")
1180 (list system str)))))
1181 (parameterize ((%graft? #f))
1182 (package-derivation store package system #:graft? #f)
1183
1184 ;; If there's a replacement, make sure we can compute its
1185 ;; derivation.
1186 (match (package-replacement package)
1187 (#f #t)
1188 (replacement
1189 (package-derivation store replacement system
1190 #:graft? #f))))))
f363c836 1191
7826fbc0
CB
1192 (define (check-with-store store)
1193 (filter lint-warning?
1194 (map (cut try store <>) (package-supported-systems package))))
1195
1196 ;; For backwards compatability, don't rely on store being set
1197 (or (and=> store check-with-store)
1198 (with-store store
1199 (check-with-store store))))
f363c836 1200
993023a2
LC
1201(define* (check-profile-collisions package #:key store)
1202 "Check for collisions that would occur when installing PACKAGE as a result
1203of the propagated inputs it pulls in."
1204 (define (do-check store)
1205 (guard (c ((profile-collision-error? c)
1206 (let ((first (profile-collision-error-entry c))
1207 (second (profile-collision-error-conflict c)))
1208 (define format
1209 (if (string=? (manifest-entry-version first)
1210 (manifest-entry-version second))
1211 manifest-entry-item
1212 (lambda (entry)
1213 (string-append (manifest-entry-name entry) "@"
1214 (manifest-entry-version entry)))))
1215
1216 (list (make-warning package
1217 (G_ "propagated inputs ~a and ~a collide")
1218 (list (format first)
1219 (format second)))))))
1220 ;; Disable grafts to avoid building PACKAGE and its dependencies.
1221 (parameterize ((%graft? #f))
1222 (run-with-store store
1223 (mbegin %store-monad
1224 (check-for-collisions (packages->manifest (list package))
1225 (%current-system))
1226 (return '()))))))
1227
1228 (if store
1229 (do-check store)
1230 (with-store store
1231 (do-check store))))
1232
f363c836
CB
1233(define (check-license package)
1234 "Warn about type errors of the 'license' field of PACKAGE."
1235 (match (package-license package)
1236 ((or (? license?)
1237 ((? license?) ...))
1238 '())
1239 (x
1240 (list
1241 (make-warning package (G_ "invalid license field")
1242 #:field 'license)))))
1243
f363c836
CB
1244(define (current-vulnerabilities*)
1245 "Like 'current-vulnerabilities', but return the empty list upon networking
1246or HTTP errors. This allows network-less operation and makes problems with
1247the NIST server non-fatal."
1248 (with-networking-fail-safe (G_ "while retrieving CVE vulnerabilities")
1249 '()
baa4a2ef 1250 (current-vulnerabilities #:timeout 4)))
f363c836
CB
1251
1252(define package-vulnerabilities
1253 (let ((lookup (delay (vulnerabilities->lookup-proc
1254 (current-vulnerabilities*)))))
1255 (lambda (package)
1256 "Return a list of vulnerabilities affecting PACKAGE."
1257 ;; First we retrieve the Common Platform Enumeration (CPE) name and
1258 ;; version for PACKAGE, then we can pass them to LOOKUP.
1259 (let ((name (or (assoc-ref (package-properties package)
1260 'cpe-name)
1261 (package-name package)))
1262 (version (or (assoc-ref (package-properties package)
1263 'cpe-version)
1264 (package-version package))))
1265 ((force lookup) name version)))))
1266
fcb2318e
LC
1267(define* (check-vulnerabilities package
1268 #:optional (package-vulnerabilities
1269 package-vulnerabilities))
1270 "Check for known vulnerabilities for PACKAGE. Obtain the list of
1271vulnerability records for PACKAGE by calling PACKAGE-VULNERABILITIES."
f363c836
CB
1272 (let ((package (or (package-replacement package) package)))
1273 (match (package-vulnerabilities package)
1274 (()
1275 '())
1276 ((vulnerabilities ...)
1277 (let* ((patched (package-patched-vulnerabilities package))
1278 (known-safe (or (assq-ref (package-properties package)
1279 'lint-hidden-cve)
1280 '()))
1281 (unpatched (remove (lambda (vuln)
1282 (let ((id (vulnerability-id vuln)))
1283 (or (member id patched)
1284 (member id known-safe))))
1285 vulnerabilities)))
1286 (if (null? unpatched)
1287 '()
1288 (list
1289 (make-warning
1290 package
1291 (G_ "probably vulnerable to ~a")
1292 (list (string-join (map vulnerability-id unpatched)
1293 ", "))))))))))
1294
1295(define (check-for-updates package)
1296 "Check if there is an update available for PACKAGE."
709f30b8
LC
1297 (match (lookup-updater package)
1298 (#f
1299 (list (make-warning package (G_ "no updater for ~a")
1300 (list (package-name package))
1301 #:field 'source)))
1302 ((? upstream-updater? updater)
1303 (match (with-networking-fail-safe
1304 (format #f (G_ "while retrieving upstream info for '~a'")
1305 (package-name package))
1306 #f
1307 (package-latest-release package))
1308 ((? upstream-source? source)
1309 (if (version>? (upstream-source-version source)
1310 (package-version package))
1311 (list
1312 (make-warning package
1313 (G_ "can be upgraded to ~a")
1314 (list (upstream-source-version source))
1315 #:field 'version))
1316 '()))
1317 (#f ;cannot find upstream release
1318 (list (make-warning package
1319 (G_ "updater '~a' failed to find \
1320upstream releases")
1321 (list (upstream-updater-name updater))
1322 #:field 'source)))))))
f363c836 1323
55549c7b 1324
bc4d81d2
LC
1325(define (lookup-disarchive-spec hash)
1326 "If Disarchive mirrors have a spec for HASH, return the list of SWH
1327directory identifiers the spec refers to. Otherwise return #f."
1328 (define (extract-swh-id spec)
1329 ;; Return the list of SWH directory identifiers SPEC refers to, where SPEC
1330 ;; is a Disarchive sexp. Instead of attempting to parse it, traverse it
1331 ;; in a pretty unintelligent fashion.
1332 (let loop ((sexp spec)
1333 (ids '()))
1334 (match sexp
1335 ((? string? str)
1336 (let ((prefix "swh:1:dir:"))
1337 (if (string-prefix? prefix str)
1338 (cons (string-drop str (string-length prefix)) ids)
1339 ids)))
1340 ((head tail ...)
1341 (loop tail (loop head ids)))
1342 (_ ids))))
1343
1344 (any (lambda (mirror)
1345 (with-networking-fail-safe
1346 (format #f (G_ "failed to access Disarchive database at ~a")
1347 mirror)
1348 #f
1349 (guard (c ((http-get-error? c) #f))
1350 (let* ((url (string-append mirror
1351 (symbol->string
1352 (content-hash-algorithm hash))
1353 "/"
1354 (bytevector->base16-string
1355 (content-hash-value hash))))
1356 (port (http-fetch (string->uri url) #:text? #t))
1357 (spec (read port)))
1358 (close-port port)
1359 (extract-swh-id spec)))))
1360 %disarchive-mirrors))
1361
55549c7b
LC
1362(define (check-archival package)
1363 "Check whether PACKAGE's source code is archived on Software Heritage. If
1364it's not, and if its source code is a VCS snapshot, then send a \"save\"
1365request to Software Heritage.
1366
1367Software Heritage imposes limits on the request rate per client IP address.
1368This checker prints a notice and stops doing anything once that limit has been
1369reached."
1370 (define (response->warning url method response)
1371 (if (request-rate-limit-reached? url method)
1372 (list (make-warning package
1373 (G_ "Software Heritage rate limit reached; \
1374try again later")
1375 #:field 'source))
1376 (list (make-warning package
1377 (G_ "'~a' returned ~a")
1378 (list url (response-code response))
1379 #:field 'source))))
1380
1381 (define skip-key (gensym "skip-archival-check"))
1382
1383 (define (skip-when-limit-reached url method)
1384 (or (not (request-rate-limit-reached? url method))
1385 (throw skip-key #t)))
1386
1387 (parameterize ((%allow-request? skip-when-limit-reached))
1388 (catch #t
1389 (lambda ()
1390 (match (and (origin? (package-source package))
1391 (package-source package))
1392 (#f ;no source
1393 '())
1394 ((= origin-uri (? git-reference? reference))
1395 (define url
1396 (git-reference-url reference))
1397 (define commit
1398 (git-reference-commit reference))
1399
1400 (match (if (commit-id? commit)
1401 (or (lookup-revision commit)
1402 (lookup-origin-revision url commit))
1403 (lookup-origin-revision url commit))
1404 ((? revision? revision)
1405 '())
1406 (#f
1407 ;; Revision is missing from the archive, attempt to save it.
1408 (catch 'swh-error
1409 (lambda ()
1410 (save-origin (git-reference-url reference) "git")
1411 (list (make-warning
1412 package
1413 ;; TRANSLATORS: "Software Heritage" is a proper noun
1414 ;; that must remain untranslated. See
1415 ;; <https://www.softwareheritage.org>.
1416 (G_ "scheduled Software Heritage archival")
1417 #:field 'source)))
1418 (lambda (key url method response . _)
1419 (cond ((= 429 (response-code response))
1420 (list (make-warning
1421 package
1422 (G_ "archival rate limit exceeded; \
1423try again later")
1424 #:field 'source)))
1425 (else
1426 (response->warning url method response))))))))
1427 ((? origin? origin)
1428 ;; Since "save" origins are not supported for non-VCS source, all
1429 ;; we can do is tell whether a given tarball is available or not.
4ac3e010
LC
1430 (if (and=> (origin-hash origin) ;XXX: for ungoogled-chromium
1431 content-hash-value) ;& icecat
feea1d0e
LC
1432 (let ((hash (origin-hash origin)))
1433 (match (lookup-content (content-hash-value hash)
1434 (symbol->string
1435 (content-hash-algorithm hash)))
1436 (#f
bc4d81d2
LC
1437 ;; If SWH doesn't have HASH as is, it may be because it's
1438 ;; a hand-crafted tarball. In that case, check whether
1439 ;; the Disarchive database has an entry for that tarball.
1440 (match (lookup-disarchive-spec hash)
1441 (#f
1442 (list (make-warning package
1443 (G_ "source not archived on Software \
1444Heritage and missing from the Disarchive database")
1445 #:field 'source)))
1446 (directory-ids
1447 (match (find (lambda (id)
1448 (not (lookup-directory id)))
1449 directory-ids)
1450 (#f '())
1451 (id
1452 (list (make-warning package
1453 (G_ "
1454Disarchive entry refers to non-existent SWH directory '~a'")
1455 (list id)
1456 #:field 'source)))))))
feea1d0e
LC
1457 ((? content?)
1458 '())))
55549c7b
LC
1459 '()))))
1460 (match-lambda*
d2fde340 1461 (('swh-error url method response)
55549c7b
LC
1462 (response->warning url method response))
1463 ((key . args)
1464 (if (eq? key skip-key)
1465 '()
c2b2c8e9
LC
1466 (with-networking-fail-safe
1467 (G_ "while connecting to Software Heritage")
1468 '()
1469 (apply throw key args))))))))
55549c7b 1470
464b1fff
TS
1471(define (check-haskell-stackage package)
1472 "Check whether PACKAGE is a Haskell package ahead of the current
1473Stackage LTS version."
1474 (match (with-networking-fail-safe
1475 (format #f (G_ "while retrieving upstream info for '~a'")
1476 (package-name package))
1477 #f
1478 (package-latest-release package (list %stackage-updater)))
1479 ((? upstream-source? source)
1480 (if (version>? (package-version package)
1481 (upstream-source-version source))
1482 (list
1483 (make-warning package
1484 (G_ "ahead of Stackage LTS version ~a")
1485 (list (upstream-source-version source))
1486 #:field 'version))
1487 '()))
1488 (#f '())))
1489
f363c836
CB
1490\f
1491;;;
1492;;; Source code formatting.
1493;;;
1494
1495(define (report-tabulations package line line-number)
1496 "Warn about tabulations found in LINE."
1497 (match (string-index line #\tab)
7d09f2e8 1498 (#f #f)
f363c836
CB
1499 (index
1500 (make-warning package
1501 (G_ "tabulation on line ~a, column ~a")
1502 (list line-number index)
1503 #:location
1504 (location (package-file package)
1505 line-number
1506 index)))))
1507
1508(define (report-trailing-white-space package line line-number)
1509 "Warn about trailing white space in LINE."
7d09f2e8
LC
1510 (and (not (or (string=? line (string-trim-right line))
1511 (string=? line (string #\page))))
1512 (make-warning package
1513 (G_ "trailing white space on line ~a")
1514 (list line-number)
1515 #:location
1516 (location (package-file package)
1517 line-number
1518 0))))
f363c836
CB
1519
1520(define (report-long-line package line line-number)
1521 "Emit a warning if LINE is too long."
1522 ;; Note: We don't warn at 80 characters because sometimes hashes and URLs
1523 ;; make it hard to fit within that limit and we want to avoid making too
1524 ;; much noise.
7d09f2e8
LC
1525 (and (> (string-length line) 90)
1526 (make-warning package
1527 (G_ "line ~a is way too long (~a characters)")
1528 (list line-number (string-length line))
1529 #:location
1530 (location (package-file package)
1531 line-number
1532 0))))
f363c836
CB
1533
1534(define %hanging-paren-rx
1535 (make-regexp "^[[:blank:]]*[()]+[[:blank:]]*$"))
1536
1537(define (report-lone-parentheses package line line-number)
1538 "Emit a warning if LINE contains hanging parentheses."
7d09f2e8
LC
1539 (and (regexp-exec %hanging-paren-rx line)
1540 (make-warning package
1541 (G_ "parentheses feel lonely, \
f363c836 1542move to the previous or next line")
7d09f2e8
LC
1543 (list line-number)
1544 #:location
1545 (location (package-file package)
1546 line-number
1547 0))))
f363c836
CB
1548
1549(define %formatting-reporters
1550 ;; List of procedures that report formatting issues. These are not separate
1551 ;; checkers because they would need to re-read the file.
1552 (list report-tabulations
1553 report-trailing-white-space
1554 report-long-line
1555 report-lone-parentheses))
1556
1557(define* (report-formatting-issues package file starting-line
1558 #:key (reporters %formatting-reporters))
1559 "Report white-space issues in FILE starting from STARTING-LINE, and report
1560them for PACKAGE."
1561 (define (sexp-last-line port)
1562 ;; Return the last line of the sexp read from PORT or an estimate thereof.
1563 (define &failure (list 'failure))
1564
1565 (let ((start (ftell port))
1566 (start-line (port-line port))
1567 (sexp (catch 'read-error
1568 (lambda () (read port))
1569 (const &failure))))
1570 (let ((line (port-line port)))
1571 (seek port start SEEK_SET)
1572 (set-port-line! port start-line)
1573 (if (eq? sexp &failure)
1574 (+ start-line 60) ;conservative estimate
1575 line))))
1576
1577 (call-with-input-file file
1578 (lambda (port)
1579 (let loop ((line-number 1)
1580 (last-line #f)
1581 (warnings '()))
1582 (let ((line (read-line port)))
1583 (if (or (eof-object? line)
1584 (and last-line (> line-number last-line)))
1585 warnings
1586 (if (and (= line-number starting-line)
1587 (not last-line))
1588 (loop (+ 1 line-number)
1589 (+ 1 (sexp-last-line port))
1590 warnings)
1591 (loop (+ 1 line-number)
1592 last-line
1593 (append
1594 warnings
1595 (if (< line-number starting-line)
1596 '()
7d09f2e8
LC
1597 (filter-map (lambda (report)
1598 (report package line line-number))
1599 reporters)))))))))))
f363c836
CB
1600
1601(define (check-formatting package)
1602 "Check the formatting of the source code of PACKAGE."
1603 (let ((location (package-location package)))
1604 (if location
d10474c3
LC
1605 ;; Report issues starting from the line before the 'package'
1606 ;; form, which usually contains the 'define' form.
1607 (let ((line (- (location-line location) 1)))
1608 (match (search-path %load-path (location-file location))
1609 ((? string? file)
1610 (report-formatting-issues package file line))
1611 (#f
1612 ;; It could be that LOCATION lists a "true" relative file
1613 ;; name--i.e., not relative to an element of %LOAD-PATH.
1614 (let ((file (location-file location)))
1615 (if (file-exists? file)
1616 (report-formatting-issues package file line)
1617 (list (make-warning package
1618 (G_ "source file not found"))))))))
f363c836
CB
1619 '())))
1620
1621\f
1622;;;
1623;;; List of checkers.
1624;;;
1625
38f3176a 1626(define %local-checkers
f363c836 1627 (list
1126bb9c
TGRBGG
1628 (lint-checker
1629 (name 'name)
1630 (description "Validate package names")
1631 (check check-name))
82b0e27d
MD
1632 (lint-checker
1633 (name 'tests-true)
1634 (description "Check if tests are explicitly enabled")
1635 (check check-tests-true))
f363c836
CB
1636 (lint-checker
1637 (name 'description)
1638 (description "Validate package descriptions")
1639 (check check-description-style))
f363c836
CB
1640 (lint-checker
1641 (name 'inputs-should-be-native)
1642 (description "Identify inputs that should be native inputs")
1643 (check check-inputs-should-be-native))
1644 (lint-checker
1645 (name 'inputs-should-not-be-input)
1646 (description "Identify inputs that shouldn't be inputs at all")
1647 (check check-inputs-should-not-be-an-input-at-all))
f363c836
CB
1648 (lint-checker
1649 (name 'license)
1650 ;; TRANSLATORS: <license> is the name of a data type and must not be
1651 ;; translated.
1652 (description "Make sure the 'license' field is a <license> \
1653or a list thereof")
1654 (check check-license))
5532371a
MD
1655 (lint-checker
1656 (name 'optional-tests)
1657 (description "Make sure tests are only run when requested")
1658 (check check-optional-tests))
f363c836
CB
1659 (lint-checker
1660 (name 'mirror-url)
1661 (description "Suggest 'mirror://' URLs")
1662 (check check-mirror-url))
f363c836
CB
1663 (lint-checker
1664 (name 'source-file-name)
1665 (description "Validate file names of sources")
1666 (check check-source-file-name))
1667 (lint-checker
1668 (name 'source-unstable-tarball)
1669 (description "Check for autogenerated tarballs")
1670 (check check-source-unstable-tarball))
1671 (lint-checker
d84ad6a2
CB
1672 (name 'derivation)
1673 (description "Report failure to compile a package to a derivation")
1674 (check check-derivation)
1675 (requires-store? #t))
993023a2
LC
1676 (lint-checker
1677 (name 'profile-collisions)
1678 (description "Report collisions that would occur due to propagated inputs")
1679 (check check-profile-collisions)
1680 (requires-store? #t))
38f3176a
CB
1681 (lint-checker
1682 (name 'patch-file-names)
1683 (description "Validate file names and availability of patches")
1684 (check check-patch-file-names))
4f156c25
LC
1685 (lint-checker
1686 (name 'patch-headers)
1687 (description "Validate patch headers")
1688 (check check-patch-headers))
38f3176a
CB
1689 (lint-checker
1690 (name 'formatting)
1691 (description "Look for formatting issues in the source")
1692 (check check-formatting))))
1693
1694(define %network-dependent-checkers
1695 (list
f363c836
CB
1696 (lint-checker
1697 (name 'synopsis)
1698 (description "Validate package synopses")
1699 (check check-synopsis-style))
38f3176a
CB
1700 (lint-checker
1701 (name 'gnu-description)
1702 (description "Validate synopsis & description of GNU packages")
1703 (check check-gnu-synopsis+description))
1704 (lint-checker
1705 (name 'home-page)
1706 (description "Validate home-page URLs")
1707 (check check-home-page))
1708 (lint-checker
1709 (name 'source)
1710 (description "Validate source URLs")
1711 (check check-source))
1712 (lint-checker
1713 (name 'github-url)
1714 (description "Suggest GitHub URLs")
1715 (check check-github-url))
9efa2c28
LC
1716 (lint-checker
1717 (name 'cve)
1718 (description "Check the Common Vulnerabilities and Exposures\
1719 (CVE) database")
1720 (check check-vulnerabilities))
f363c836
CB
1721 (lint-checker
1722 (name 'refresh)
1723 (description "Check the package for new upstream releases")
55549c7b
LC
1724 (check check-for-updates))
1725 (lint-checker
1726 (name 'archival)
1727 (description "Ensure source code archival on Software Heritage")
464b1fff
TS
1728 (check check-archival))
1729 (lint-checker
1730 (name 'haskell-stackage)
1731 (description "Ensure Haskell packages use Stackage LTS versions")
1732 (check check-haskell-stackage))))
38f3176a
CB
1733
1734(define %all-checkers
1735 (append %local-checkers
1736 %network-dependent-checkers))