2014-09-01 02:13:21 +02:00
|
|
|
|
;;; GNU Guix --- Functional package management for GNU
|
|
|
|
|
;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com>
|
2014-10-22 16:20:20 +02:00
|
|
|
|
;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
|
2015-01-26 00:19:04 +01:00
|
|
|
|
;;; Copyright © 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
|
2014-09-01 02:13:21 +02:00
|
|
|
|
;;;
|
|
|
|
|
;;; This file is part of GNU Guix.
|
|
|
|
|
;;;
|
|
|
|
|
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
|
|
|
|
;;; under the terms of the GNU General Public License as published by
|
|
|
|
|
;;; the Free Software Foundation; either version 3 of the License, or (at
|
|
|
|
|
;;; your option) any later version.
|
|
|
|
|
;;;
|
|
|
|
|
;;; GNU Guix is distributed in the hope that it will be useful, but
|
|
|
|
|
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;;; GNU General Public License for more details.
|
|
|
|
|
;;;
|
|
|
|
|
;;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
(define-module (guix scripts lint)
|
|
|
|
|
#:use-module (guix base32)
|
2014-12-29 04:38:15 +01:00
|
|
|
|
#:use-module (guix download)
|
2015-01-28 19:49:53 +01:00
|
|
|
|
#:use-module (guix ftp-client)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
#:use-module (guix packages)
|
|
|
|
|
#:use-module (guix records)
|
|
|
|
|
#:use-module (guix ui)
|
|
|
|
|
#:use-module (guix utils)
|
2014-11-07 17:34:15 +01:00
|
|
|
|
#:use-module (guix gnu-maintenance)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
#:use-module (gnu packages)
|
|
|
|
|
#:use-module (ice-9 match)
|
2014-10-22 20:47:01 +02:00
|
|
|
|
#:use-module (ice-9 regex)
|
|
|
|
|
#:use-module (ice-9 format)
|
2014-12-28 17:55:16 +01:00
|
|
|
|
#:use-module (web uri)
|
|
|
|
|
#:use-module ((guix build download)
|
2014-12-29 04:38:15 +01:00
|
|
|
|
#:select (maybe-expand-mirrors
|
|
|
|
|
open-connection-for-uri))
|
2014-12-28 17:55:16 +01:00
|
|
|
|
#:use-module (web request)
|
|
|
|
|
#:use-module (web response)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
#:use-module (srfi srfi-1)
|
|
|
|
|
#:use-module (srfi srfi-9)
|
|
|
|
|
#:use-module (srfi srfi-11)
|
2014-12-29 04:38:15 +01:00
|
|
|
|
#:use-module (srfi srfi-26)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
#:use-module (srfi srfi-37)
|
|
|
|
|
#:export (guix-lint
|
2014-09-27 23:00:44 +02:00
|
|
|
|
check-description-style
|
2014-09-01 02:13:21 +02:00
|
|
|
|
check-inputs-should-be-native
|
|
|
|
|
check-patches
|
2014-12-29 20:39:58 +01:00
|
|
|
|
check-synopsis-style
|
|
|
|
|
check-home-page))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Helpers
|
|
|
|
|
;;;
|
|
|
|
|
(define* (emit-warning package message #:optional field)
|
|
|
|
|
;; Emit a warning about PACKAGE, printing the location of FIELD if it is
|
|
|
|
|
;; given, the location of PACKAGE otherwise, the full name of PACKAGE and the
|
|
|
|
|
;; provided MESSAGE.
|
|
|
|
|
(let ((loc (or (package-field-location package field)
|
|
|
|
|
(package-location package))))
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(format (guix-warning-port) "~a: ~a: ~a~%"
|
2014-09-03 09:01:28 +02:00
|
|
|
|
(location->string loc)
|
|
|
|
|
(package-full-name package)
|
|
|
|
|
message)))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Checkers
|
|
|
|
|
;;;
|
|
|
|
|
(define-record-type* <lint-checker>
|
|
|
|
|
lint-checker make-lint-checker
|
|
|
|
|
lint-checker?
|
|
|
|
|
;; TODO: add a 'certainty' field that shows how confident we are in the
|
|
|
|
|
;; checker. Then allow users to only run checkers that have a certain
|
|
|
|
|
;; 'certainty' level.
|
|
|
|
|
(name lint-checker-name)
|
|
|
|
|
(description lint-checker-description)
|
|
|
|
|
(check lint-checker-check))
|
|
|
|
|
|
|
|
|
|
(define (list-checkers-and-exit)
|
|
|
|
|
;; Print information about all available checkers and exit.
|
|
|
|
|
(format #t (_ "Available checkers:~%"))
|
|
|
|
|
(for-each (lambda (checker)
|
|
|
|
|
(format #t "- ~a: ~a~%"
|
|
|
|
|
(lint-checker-name checker)
|
2014-11-19 22:44:22 +01:00
|
|
|
|
(_ (lint-checker-description checker))))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
%checkers)
|
|
|
|
|
(exit 0))
|
|
|
|
|
|
2014-10-25 18:29:34 +02:00
|
|
|
|
(define (properly-starts-sentence? s)
|
2014-11-07 17:32:56 +01:00
|
|
|
|
(string-match "^[(\"'[:upper:][:digit:]]" s))
|
2014-09-27 23:00:44 +02:00
|
|
|
|
|
2014-11-07 17:44:30 +01:00
|
|
|
|
(define (starts-with-abbreviation? s)
|
|
|
|
|
"Return #t if S starts with what looks like an abbreviation or acronym."
|
|
|
|
|
(string-match "^[A-Z][A-Z0-9]+\\>" s))
|
|
|
|
|
|
2014-09-27 23:00:44 +02:00
|
|
|
|
(define (check-description-style package)
|
|
|
|
|
;; Emit a warning if stylistic issues are found in the description of PACKAGE.
|
2014-10-22 20:48:55 +02:00
|
|
|
|
(define (check-not-empty description)
|
|
|
|
|
(when (string-null? description)
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "description should not be empty")
|
2014-10-22 20:48:55 +02:00
|
|
|
|
'description)))
|
|
|
|
|
|
2014-10-25 18:29:34 +02:00
|
|
|
|
(define (check-proper-start description)
|
2014-10-26 18:36:42 +01:00
|
|
|
|
(unless (or (properly-starts-sentence? description)
|
|
|
|
|
(string-prefix-ci? (package-name package) description))
|
2014-10-22 20:47:01 +02:00
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "description should start with an upper-case letter or digit")
|
2014-10-22 20:47:01 +02:00
|
|
|
|
'description)))
|
|
|
|
|
|
|
|
|
|
(define (check-end-of-sentence-space description)
|
|
|
|
|
"Check that an end-of-sentence period is followed by two spaces."
|
|
|
|
|
(let ((infractions
|
|
|
|
|
(reverse (fold-matches
|
|
|
|
|
"\\. [A-Z]" description '()
|
|
|
|
|
(lambda (m r)
|
|
|
|
|
;; Filter out matches of common abbreviations.
|
|
|
|
|
(if (find (lambda (s)
|
|
|
|
|
(string-suffix-ci? s (match:prefix m)))
|
|
|
|
|
'("i.e" "e.g" "a.k.a" "resp"))
|
|
|
|
|
r (cons (match:start m) r)))))))
|
|
|
|
|
(unless (null? infractions)
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(format #f (_ "sentences in description should be followed ~
|
|
|
|
|
by two spaces; possible infraction~p at ~{~a~^, ~}")
|
2014-10-22 20:47:01 +02:00
|
|
|
|
(length infractions)
|
|
|
|
|
infractions)
|
|
|
|
|
'description))))
|
|
|
|
|
|
|
|
|
|
(let ((description (package-description package)))
|
|
|
|
|
(when (string? description)
|
2014-10-25 18:29:34 +02:00
|
|
|
|
(check-not-empty description)
|
|
|
|
|
(check-proper-start description)
|
|
|
|
|
(check-end-of-sentence-space description))))
|
2014-09-27 23:00:44 +02:00
|
|
|
|
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(define (check-inputs-should-be-native package)
|
|
|
|
|
;; Emit a warning if some inputs of PACKAGE are likely to belong to its
|
|
|
|
|
;; native inputs.
|
|
|
|
|
(let ((inputs (package-inputs package)))
|
|
|
|
|
(match inputs
|
|
|
|
|
(((labels packages . _) ...)
|
|
|
|
|
(when (member "pkg-config"
|
|
|
|
|
(map package-name (filter package? packages)))
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "pkg-config should probably be a native input")
|
2014-09-01 02:13:21 +02:00
|
|
|
|
'inputs))))))
|
|
|
|
|
|
2014-11-07 14:09:19 +01:00
|
|
|
|
(define (package-name-regexp package)
|
|
|
|
|
"Return a regexp that matches PACKAGE's name as a word at the beginning of a
|
|
|
|
|
line."
|
|
|
|
|
(make-regexp (string-append "^" (regexp-quote (package-name package))
|
|
|
|
|
"\\>")
|
|
|
|
|
regexp/icase))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
|
|
|
|
(define (check-synopsis-style package)
|
|
|
|
|
;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE.
|
2014-10-22 20:48:55 +02:00
|
|
|
|
(define (check-not-empty synopsis)
|
|
|
|
|
(when (string-null? synopsis)
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "synopsis should not be empty")
|
2014-10-22 20:48:55 +02:00
|
|
|
|
'synopsis)))
|
|
|
|
|
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(define (check-final-period synopsis)
|
|
|
|
|
;; Synopsis should not end with a period, except for some special cases.
|
2014-10-22 19:01:04 +02:00
|
|
|
|
(when (and (string-suffix? "." synopsis)
|
|
|
|
|
(not (string-suffix? "etc." synopsis)))
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "no period allowed at the end of the synopsis")
|
2014-10-22 19:01:04 +02:00
|
|
|
|
'synopsis)))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
2014-11-07 17:34:15 +01:00
|
|
|
|
(define check-start-article
|
|
|
|
|
;; Skip this check for GNU packages, as suggested by Karl Berry's reply to
|
|
|
|
|
;; <http://lists.gnu.org/archive/html/bug-womb/2014-11/msg00000.html>.
|
|
|
|
|
(if (false-if-exception (gnu-package? package))
|
|
|
|
|
(const #t)
|
|
|
|
|
(lambda (synopsis)
|
|
|
|
|
(when (or (string-prefix-ci? "A " synopsis)
|
|
|
|
|
(string-prefix-ci? "An " synopsis))
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "no article allowed at the beginning of \
|
|
|
|
|
the synopsis")
|
2014-11-07 17:34:15 +01:00
|
|
|
|
'synopsis)))))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
2014-09-27 21:39:19 +02:00
|
|
|
|
(define (check-synopsis-length synopsis)
|
2014-10-22 19:01:04 +02:00
|
|
|
|
(when (>= (string-length synopsis) 80)
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "synopsis should be less than 80 characters long")
|
2014-10-22 19:01:04 +02:00
|
|
|
|
'synopsis)))
|
2014-09-27 21:39:19 +02:00
|
|
|
|
|
2014-10-25 18:29:34 +02:00
|
|
|
|
(define (check-proper-start synopsis)
|
|
|
|
|
(unless (properly-starts-sentence? synopsis)
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "synopsis should start with an upper-case letter or digit")
|
2014-10-25 18:29:34 +02:00
|
|
|
|
'synopsis)))
|
2014-09-27 23:00:44 +02:00
|
|
|
|
|
2014-09-27 23:24:12 +02:00
|
|
|
|
(define (check-start-with-package-name synopsis)
|
2014-11-07 17:44:30 +01:00
|
|
|
|
(when (and (regexp-exec (package-name-regexp package) synopsis)
|
|
|
|
|
(not (starts-with-abbreviation? synopsis)))
|
2014-10-22 16:20:20 +02:00
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "synopsis should not start with the package name")
|
2014-10-22 16:20:20 +02:00
|
|
|
|
'synopsis)))
|
2014-09-27 23:24:12 +02:00
|
|
|
|
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(let ((synopsis (package-synopsis package)))
|
2014-10-22 19:01:04 +02:00
|
|
|
|
(when (string? synopsis)
|
2014-10-25 18:29:34 +02:00
|
|
|
|
(check-not-empty synopsis)
|
|
|
|
|
(check-proper-start synopsis)
|
|
|
|
|
(check-final-period synopsis)
|
|
|
|
|
(check-start-article synopsis)
|
|
|
|
|
(check-start-with-package-name synopsis)
|
|
|
|
|
(check-synopsis-length synopsis))))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
2014-12-28 17:55:16 +01:00
|
|
|
|
(define (probe-uri uri)
|
|
|
|
|
"Probe URI, a URI object, and return two values: a symbol denoting the
|
|
|
|
|
probing status, such as 'http-response' when we managed to get an HTTP
|
|
|
|
|
response from URI, and additional details, such as the actual HTTP response."
|
|
|
|
|
(define headers
|
|
|
|
|
'((User-Agent . "GNU Guile")
|
|
|
|
|
(Accept . "*/*")))
|
|
|
|
|
|
|
|
|
|
(let loop ((uri uri)
|
|
|
|
|
(visited '()))
|
|
|
|
|
(match (uri-scheme uri)
|
|
|
|
|
((or 'http 'https)
|
|
|
|
|
(catch #t
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((port (open-connection-for-uri uri))
|
|
|
|
|
(request (build-request uri #:headers headers)))
|
|
|
|
|
(define response
|
|
|
|
|
(dynamic-wind
|
|
|
|
|
(const #f)
|
|
|
|
|
(lambda ()
|
|
|
|
|
(write-request request port)
|
|
|
|
|
(force-output port)
|
|
|
|
|
(read-response port))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(close port))))
|
|
|
|
|
|
|
|
|
|
(case (response-code response)
|
|
|
|
|
((301 302 307)
|
|
|
|
|
(let ((location (response-location response)))
|
|
|
|
|
(if (or (not location) (member location visited))
|
|
|
|
|
(values 'http-response response)
|
|
|
|
|
(loop location (cons location visited))))) ;follow the redirect
|
|
|
|
|
(else
|
|
|
|
|
(values 'http-response response)))))
|
|
|
|
|
(lambda (key . args)
|
|
|
|
|
(case key
|
|
|
|
|
((bad-header bad-header-component)
|
|
|
|
|
;; This can happen if the server returns an invalid HTTP header,
|
|
|
|
|
;; as is the case with the 'Date' header at sqlite.org.
|
|
|
|
|
(values 'invalid-http-response #f))
|
|
|
|
|
((getaddrinfo-error system-error gnutls-error)
|
|
|
|
|
(values key args))
|
|
|
|
|
(else
|
|
|
|
|
(apply throw key args))))))
|
2015-01-28 19:49:53 +01:00
|
|
|
|
('ftp
|
|
|
|
|
(catch #t
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((port (ftp-open (uri-host uri) 21)))
|
|
|
|
|
(define response
|
|
|
|
|
(dynamic-wind
|
|
|
|
|
(const #f)
|
|
|
|
|
(lambda ()
|
|
|
|
|
(ftp-chdir port (dirname (uri-path uri)))
|
|
|
|
|
(ftp-size port (basename (uri-path uri))))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(ftp-close port))))
|
|
|
|
|
(values 'ftp-response #t)))
|
|
|
|
|
(lambda (key . args)
|
|
|
|
|
(case key
|
|
|
|
|
((or ftp-error)
|
|
|
|
|
(values 'ftp-response #f))
|
|
|
|
|
((getaddrinfo-error system-error gnutls-error)
|
|
|
|
|
(values key args))
|
|
|
|
|
(else
|
|
|
|
|
(apply throw key args))))))
|
2014-12-28 17:55:16 +01:00
|
|
|
|
(_
|
2015-01-28 19:49:53 +01:00
|
|
|
|
(values 'unknown-protocol #f)))))
|
2014-12-28 17:55:16 +01:00
|
|
|
|
|
2014-12-29 04:38:15 +01:00
|
|
|
|
(define (validate-uri uri package field)
|
|
|
|
|
"Return #t if the given URI can be reached, otherwise emit a
|
|
|
|
|
warning for PACKAGE mentionning the FIELD."
|
|
|
|
|
(let-values (((status argument)
|
|
|
|
|
(probe-uri uri)))
|
|
|
|
|
(case status
|
|
|
|
|
((http-response)
|
2015-01-26 00:19:04 +01:00
|
|
|
|
(or (= 200 (response-code argument))
|
|
|
|
|
(emit-warning package
|
|
|
|
|
(format #f
|
|
|
|
|
(_ "URI ~a not reachable: ~a (~s)")
|
|
|
|
|
(uri->string uri)
|
|
|
|
|
(response-code argument)
|
|
|
|
|
(response-reason-phrase argument))
|
|
|
|
|
field)))
|
2015-01-28 19:49:53 +01:00
|
|
|
|
((ftp-response)
|
|
|
|
|
(when (not argument)
|
|
|
|
|
(emit-warning package
|
|
|
|
|
(format #f
|
|
|
|
|
(_ "URI ~a not reachable")
|
|
|
|
|
(uri->string uri)))))
|
2014-12-29 04:38:15 +01:00
|
|
|
|
((getaddrinfo-error)
|
|
|
|
|
(emit-warning package
|
|
|
|
|
(format #f
|
|
|
|
|
(_ "URI ~a domain not found: ~a")
|
|
|
|
|
(uri->string uri)
|
|
|
|
|
(gai-strerror (car argument)))
|
2015-01-26 00:19:04 +01:00
|
|
|
|
field)
|
|
|
|
|
#f)
|
2014-12-29 04:38:15 +01:00
|
|
|
|
((system-error)
|
|
|
|
|
(emit-warning package
|
|
|
|
|
(format #f
|
|
|
|
|
(_ "URI ~a unreachable: ~a")
|
|
|
|
|
(uri->string uri)
|
|
|
|
|
(strerror
|
|
|
|
|
(system-error-errno
|
|
|
|
|
(cons status argument))))
|
2015-01-26 00:19:04 +01:00
|
|
|
|
field)
|
|
|
|
|
#f)
|
2014-12-29 04:38:15 +01:00
|
|
|
|
((invalid-http-response gnutls-error)
|
|
|
|
|
;; Probably a misbehaving server; ignore.
|
|
|
|
|
#f)
|
2015-01-28 19:49:53 +01:00
|
|
|
|
((unknown-protocol) ;nothing we can do
|
2014-12-29 04:38:15 +01:00
|
|
|
|
#f)
|
|
|
|
|
(else
|
2015-01-26 00:19:04 +01:00
|
|
|
|
(error "internal linter error" status)))))
|
2014-12-29 04:38:15 +01:00
|
|
|
|
|
2014-12-28 17:55:16 +01:00
|
|
|
|
(define (check-home-page package)
|
|
|
|
|
"Emit a warning if PACKAGE has an invalid 'home-page' field, or if that
|
|
|
|
|
'home-page' is not reachable."
|
|
|
|
|
(let ((uri (and=> (package-home-page package) string->uri)))
|
|
|
|
|
(cond
|
|
|
|
|
((uri? uri)
|
2014-12-29 04:38:15 +01:00
|
|
|
|
(validate-uri uri package 'home-page))
|
2014-12-28 17:55:16 +01:00
|
|
|
|
((not (package-home-page package))
|
|
|
|
|
(unless (or (string-contains (package-name package) "bootstrap")
|
|
|
|
|
(string=? (package-name package) "ld-wrapper"))
|
|
|
|
|
(emit-warning package
|
|
|
|
|
(_ "invalid value for home page")
|
|
|
|
|
'home-page)))
|
|
|
|
|
(else
|
|
|
|
|
(emit-warning package (format #f (_ "invalid home page URL: ~s")
|
|
|
|
|
(package-home-page package))
|
|
|
|
|
'home-page)))))
|
|
|
|
|
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(define (check-patches package)
|
|
|
|
|
;; Emit a warning if the patches requires by PACKAGE are badly named.
|
|
|
|
|
(let ((patches (and=> (package-source package) origin-patches))
|
|
|
|
|
(name (package-name package))
|
|
|
|
|
(full-name (package-full-name package)))
|
2014-10-22 19:01:04 +02:00
|
|
|
|
(when (and patches
|
|
|
|
|
(any (match-lambda
|
|
|
|
|
((? string? patch)
|
|
|
|
|
(let ((filename (basename patch)))
|
|
|
|
|
(not (or (eq? (string-contains filename name) 0)
|
|
|
|
|
(eq? (string-contains filename full-name)
|
|
|
|
|
0)))))
|
|
|
|
|
(_
|
|
|
|
|
;; This must be an <origin> or something like that.
|
|
|
|
|
#f))
|
|
|
|
|
patches))
|
|
|
|
|
(emit-warning package
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "file names of patches should start with \
|
|
|
|
|
the package name")
|
2014-10-22 19:01:04 +02:00
|
|
|
|
'patches))))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
2014-11-19 22:52:14 +01:00
|
|
|
|
(define (escape-quotes str)
|
|
|
|
|
"Replace any quote character in STR by an escaped quote character."
|
|
|
|
|
(list->string
|
|
|
|
|
(string-fold-right (lambda (chr result)
|
|
|
|
|
(match chr
|
|
|
|
|
(#\" (cons* #\\ #\"result))
|
|
|
|
|
(_ (cons chr result))))
|
|
|
|
|
'()
|
|
|
|
|
str)))
|
|
|
|
|
|
|
|
|
|
(define official-gnu-packages*
|
|
|
|
|
(memoize
|
|
|
|
|
(lambda ()
|
|
|
|
|
"A memoizing version of 'official-gnu-packages' that returns the empty
|
|
|
|
|
list when something goes wrong, such as a networking issue."
|
|
|
|
|
(let ((gnus (false-if-exception (official-gnu-packages))))
|
|
|
|
|
(or gnus '())))))
|
|
|
|
|
|
|
|
|
|
(define (check-gnu-synopsis+description package)
|
|
|
|
|
"Make sure that, if PACKAGE is a GNU package, it uses the synopsis and
|
|
|
|
|
descriptions maintained upstream."
|
|
|
|
|
(match (find (lambda (descriptor)
|
|
|
|
|
(string=? (gnu-package-name descriptor)
|
|
|
|
|
(package-name package)))
|
|
|
|
|
(official-gnu-packages*))
|
|
|
|
|
(#f ;not a GNU package, so nothing to do
|
|
|
|
|
#t)
|
|
|
|
|
(descriptor ;a genuine GNU package
|
|
|
|
|
(let ((upstream (gnu-package-doc-summary descriptor))
|
|
|
|
|
(downstream (package-synopsis package))
|
|
|
|
|
(loc (or (package-field-location package 'synopsis)
|
|
|
|
|
(package-location package))))
|
|
|
|
|
(unless (and upstream (string=? upstream downstream))
|
|
|
|
|
(format (guix-warning-port)
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "~a: ~a: proposed synopsis: ~s~%")
|
2014-11-19 22:52:14 +01:00
|
|
|
|
(location->string loc) (package-full-name package)
|
|
|
|
|
upstream)))
|
|
|
|
|
|
|
|
|
|
(let ((upstream (gnu-package-doc-description descriptor))
|
|
|
|
|
(downstream (package-description package))
|
|
|
|
|
(loc (or (package-field-location package 'description)
|
|
|
|
|
(package-location package))))
|
|
|
|
|
(when (and upstream
|
|
|
|
|
(not (string=? (fill-paragraph upstream 100)
|
|
|
|
|
(fill-paragraph downstream 100))))
|
|
|
|
|
(format (guix-warning-port)
|
2014-11-19 22:42:23 +01:00
|
|
|
|
(_ "~a: ~a: proposed description:~% \"~a\"~%")
|
2014-11-19 22:52:14 +01:00
|
|
|
|
(location->string loc) (package-full-name package)
|
|
|
|
|
(fill-paragraph (escape-quotes upstream) 77 7)))))))
|
|
|
|
|
|
2014-12-29 04:38:15 +01:00
|
|
|
|
(define (check-source package)
|
|
|
|
|
"Emit a warning if PACKAGE has an invalid 'source' field, or if that
|
|
|
|
|
'source' is not reachable."
|
|
|
|
|
(let ((origin (package-source package)))
|
|
|
|
|
(when (and origin
|
|
|
|
|
(eqv? (origin-method origin) url-fetch))
|
|
|
|
|
(let* ((strings (origin-uri origin))
|
|
|
|
|
(uris (if (list? strings)
|
|
|
|
|
(map string->uri strings)
|
|
|
|
|
(list (string->uri strings)))))
|
2015-01-26 00:19:04 +01:00
|
|
|
|
;; Just make sure that at least one of the URIs is valid.
|
|
|
|
|
(any (cut validate-uri <> package 'source)
|
|
|
|
|
(append-map (cut maybe-expand-mirrors <> %mirrors)
|
|
|
|
|
uris))))))
|
2014-12-29 04:38:15 +01:00
|
|
|
|
|
|
|
|
|
|
2014-11-19 22:52:14 +01:00
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; List of checkers.
|
|
|
|
|
;;;
|
|
|
|
|
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(define %checkers
|
|
|
|
|
(list
|
2014-09-27 23:00:44 +02:00
|
|
|
|
(lint-checker
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(name 'description)
|
2014-09-27 23:00:44 +02:00
|
|
|
|
(description "Validate package descriptions")
|
|
|
|
|
(check check-description-style))
|
2014-11-19 22:52:14 +01:00
|
|
|
|
(lint-checker
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(name 'gnu-description)
|
2014-11-19 22:52:14 +01:00
|
|
|
|
(description "Validate synopsis & description of GNU packages")
|
|
|
|
|
(check check-gnu-synopsis+description))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(lint-checker
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(name 'inputs-should-be-native)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(description "Identify inputs that should be native inputs")
|
|
|
|
|
(check check-inputs-should-be-native))
|
|
|
|
|
(lint-checker
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(name 'patch-filenames)
|
2014-11-19 22:45:47 +01:00
|
|
|
|
(description "Validate file names of patches")
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(check check-patches))
|
2014-12-28 17:55:16 +01:00
|
|
|
|
(lint-checker
|
|
|
|
|
(name 'home-page)
|
|
|
|
|
(description "Validate home-page URLs")
|
|
|
|
|
(check check-home-page))
|
2014-12-29 04:38:15 +01:00
|
|
|
|
(lint-checker
|
|
|
|
|
(name 'source)
|
|
|
|
|
(description "Validate source URLs")
|
|
|
|
|
(check check-source))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(lint-checker
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(name 'synopsis)
|
2014-11-19 22:45:47 +01:00
|
|
|
|
(description "Validate package synopses")
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(check check-synopsis-style))))
|
|
|
|
|
|
2014-10-12 01:58:29 +02:00
|
|
|
|
(define (run-checkers package checkers)
|
|
|
|
|
;; Run the given CHECKERS on PACKAGE.
|
2014-12-28 18:21:53 +01:00
|
|
|
|
(let ((tty? (isatty? (current-error-port)))
|
|
|
|
|
(name (package-full-name package)))
|
|
|
|
|
(for-each (lambda (checker)
|
|
|
|
|
(when tty?
|
|
|
|
|
(format (current-error-port) "checking ~a [~a]...\r"
|
|
|
|
|
name (lint-checker-name checker))
|
|
|
|
|
(force-output (current-error-port)))
|
|
|
|
|
((lint-checker-check checker) package))
|
|
|
|
|
checkers)))
|
2014-10-12 01:58:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Command-line options.
|
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
(define %default-options
|
|
|
|
|
;; Alist of default option values.
|
|
|
|
|
'())
|
|
|
|
|
|
|
|
|
|
(define (show-help)
|
|
|
|
|
(display (_ "Usage: guix lint [OPTION]... [PACKAGE]...
|
|
|
|
|
Run a set of checkers on the specified package; if none is specified, run the checkers on all packages.\n"))
|
|
|
|
|
(display (_ "
|
|
|
|
|
-c, --checkers=CHECKER1,CHECKER2...
|
|
|
|
|
only run the specificed checkers"))
|
|
|
|
|
(display (_ "
|
|
|
|
|
-h, --help display this help and exit"))
|
|
|
|
|
(display (_ "
|
|
|
|
|
-l, --list-checkers display the list of available lint checkers"))
|
|
|
|
|
(display (_ "
|
|
|
|
|
-V, --version display version information and exit"))
|
|
|
|
|
(newline)
|
|
|
|
|
(show-bug-report-information))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(define %options
|
|
|
|
|
;; Specification of the command-line options.
|
|
|
|
|
;; TODO: add some options:
|
|
|
|
|
;; * --certainty=[low,medium,high]: only run checkers that have at least this
|
|
|
|
|
;; 'certainty'.
|
|
|
|
|
(list (option '(#\c "checkers") #t #f
|
2015-01-28 14:00:58 +01:00
|
|
|
|
(lambda (opt name arg result)
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(let ((names (map string->symbol (string-split arg #\,))))
|
2014-10-12 01:58:29 +02:00
|
|
|
|
(for-each (lambda (c)
|
2014-11-19 22:37:29 +01:00
|
|
|
|
(unless (memq c
|
|
|
|
|
(map lint-checker-name
|
|
|
|
|
%checkers))
|
|
|
|
|
(leave (_ "~a: invalid checker~%") c)))
|
2014-10-12 01:58:29 +02:00
|
|
|
|
names)
|
2015-01-28 14:00:58 +01:00
|
|
|
|
(alist-cons 'checkers
|
|
|
|
|
(filter (lambda (checker)
|
|
|
|
|
(member (lint-checker-name checker)
|
|
|
|
|
names))
|
|
|
|
|
%checkers)
|
|
|
|
|
result))))
|
2014-10-12 01:58:29 +02:00
|
|
|
|
(option '(#\h "help") #f #f
|
|
|
|
|
(lambda args
|
|
|
|
|
(show-help)
|
|
|
|
|
(exit 0)))
|
|
|
|
|
(option '(#\l "list-checkers") #f #f
|
|
|
|
|
(lambda args
|
|
|
|
|
(list-checkers-and-exit)))
|
|
|
|
|
(option '(#\V "version") #f #f
|
|
|
|
|
(lambda args
|
|
|
|
|
(show-version-and-exit "guix lint")))))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Entry Point
|
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
(define (guix-lint . args)
|
|
|
|
|
(define (parse-options)
|
|
|
|
|
;; Return the alist of option values.
|
|
|
|
|
(args-fold* args %options
|
2015-01-28 14:00:58 +01:00
|
|
|
|
(lambda (opt name arg result)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(leave (_ "~A: unrecognized option~%") name))
|
2015-01-28 14:00:58 +01:00
|
|
|
|
(lambda (arg result)
|
2014-09-01 02:13:21 +02:00
|
|
|
|
(alist-cons 'argument arg result))
|
2015-01-28 14:00:58 +01:00
|
|
|
|
%default-options))
|
2014-09-01 02:13:21 +02:00
|
|
|
|
|
|
|
|
|
(let* ((opts (parse-options))
|
|
|
|
|
(args (filter-map (match-lambda
|
|
|
|
|
(('argument . value)
|
|
|
|
|
value)
|
|
|
|
|
(_ #f))
|
2014-10-12 01:58:29 +02:00
|
|
|
|
(reverse opts)))
|
|
|
|
|
(checkers (or (assoc-ref opts 'checkers) %checkers)))
|
|
|
|
|
(if (null? args)
|
|
|
|
|
(fold-packages (lambda (p r) (run-checkers p checkers)) '())
|
|
|
|
|
(for-each (lambda (spec)
|
|
|
|
|
(run-checkers (specification->package spec) checkers))
|
|
|
|
|
args))))
|