scripts: add guix lint
* guix/scripts/lint.scm: New file. Defines a 'lint' tool for Guix packages. * tests/lint.scm: New file. * Makefile.am (MODULES, SCM_TESTS): Add them. * po/guix/Makevars: Update appropriately. * po/guix/POTFILES.in: Update appropriately. * doc/guix.texi: Document "guix lint".
This commit is contained in:
parent
5e3b388b51
commit
b4f5e0e87c
@ -89,6 +89,7 @@ MODULES = \
|
|||||||
guix/scripts/authenticate.scm \
|
guix/scripts/authenticate.scm \
|
||||||
guix/scripts/refresh.scm \
|
guix/scripts/refresh.scm \
|
||||||
guix/scripts/system.scm \
|
guix/scripts/system.scm \
|
||||||
|
guix/scripts/lint.scm \
|
||||||
guix.scm \
|
guix.scm \
|
||||||
$(GNU_SYSTEM_MODULES)
|
$(GNU_SYSTEM_MODULES)
|
||||||
|
|
||||||
@ -159,7 +160,8 @@ SCM_TESTS = \
|
|||||||
tests/nar.scm \
|
tests/nar.scm \
|
||||||
tests/union.scm \
|
tests/union.scm \
|
||||||
tests/profiles.scm \
|
tests/profiles.scm \
|
||||||
tests/syscalls.scm
|
tests/syscalls.scm \
|
||||||
|
tests/lint.scm
|
||||||
|
|
||||||
SH_TESTS = \
|
SH_TESTS = \
|
||||||
tests/guix-build.sh \
|
tests/guix-build.sh \
|
||||||
|
@ -1459,7 +1459,10 @@ definitions like the one above may be automatically converted from the
|
|||||||
Nixpkgs distribution using the @command{guix import} command.}, the
|
Nixpkgs distribution using the @command{guix import} command.}, the
|
||||||
package may actually be built using the @code{guix build} command-line
|
package may actually be built using the @code{guix build} command-line
|
||||||
tool (@pxref{Invoking guix build}). @xref{Packaging Guidelines}, for
|
tool (@pxref{Invoking guix build}). @xref{Packaging Guidelines}, for
|
||||||
more information on how to test package definitions.
|
more information on how to test package definitions, and
|
||||||
|
@ref{Invoking guix lint}, for information on how to check a definition
|
||||||
|
for style conformance.
|
||||||
|
|
||||||
|
|
||||||
Eventually, updating the package definition to a new upstream version
|
Eventually, updating the package definition to a new upstream version
|
||||||
can be partly automated by the @command{guix refresh} command
|
can be partly automated by the @command{guix refresh} command
|
||||||
@ -2328,6 +2331,7 @@ programming interface of Guix in a convenient way.
|
|||||||
* Invoking guix download:: Downloading a file and printing its hash.
|
* Invoking guix download:: Downloading a file and printing its hash.
|
||||||
* Invoking guix hash:: Computing the cryptographic hash of a file.
|
* Invoking guix hash:: Computing the cryptographic hash of a file.
|
||||||
* Invoking guix refresh:: Updating package definitions.
|
* Invoking guix refresh:: Updating package definitions.
|
||||||
|
* Invoking guix lint:: Finding errors in package definitions.
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
@node Invoking guix build
|
@node Invoking guix build
|
||||||
@ -2705,6 +2709,29 @@ for in @code{$PATH}.
|
|||||||
|
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
|
@node Invoking guix lint
|
||||||
|
@section Invoking @command{guix lint}
|
||||||
|
The @command{guix lint} is meant to help package developers avoid common
|
||||||
|
errors and use a consistent style. It runs a few checks on a given set of
|
||||||
|
packages in order to find common mistakes in their definitions.
|
||||||
|
|
||||||
|
The general syntax is:
|
||||||
|
|
||||||
|
@example
|
||||||
|
guix lint @var{options} @var{package}@dots{}
|
||||||
|
@end example
|
||||||
|
|
||||||
|
If no package is given on the command line, then all packages are checked.
|
||||||
|
The @var{options} may be zero or more of the following:
|
||||||
|
|
||||||
|
@table @code
|
||||||
|
|
||||||
|
@item --list-checkers
|
||||||
|
@itemx -l
|
||||||
|
List and describe all the available checkers that will be run on packages
|
||||||
|
and exit.
|
||||||
|
|
||||||
|
@end table
|
||||||
|
|
||||||
@c *********************************************************************
|
@c *********************************************************************
|
||||||
@node GNU Distribution
|
@node GNU Distribution
|
||||||
|
213
guix/scripts/lint.scm
Normal file
213
guix/scripts/lint.scm
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
|
;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com>
|
||||||
|
;;;
|
||||||
|
;;; 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)
|
||||||
|
#:use-module (guix packages)
|
||||||
|
#:use-module (guix records)
|
||||||
|
#:use-module (guix ui)
|
||||||
|
#:use-module (guix utils)
|
||||||
|
#:use-module (gnu packages)
|
||||||
|
#:use-module (ice-9 match)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
|
#:use-module (srfi srfi-11)
|
||||||
|
#:use-module (srfi srfi-37)
|
||||||
|
#:export (guix-lint
|
||||||
|
check-inputs-should-be-native
|
||||||
|
check-patches
|
||||||
|
check-synopsis-style))
|
||||||
|
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; 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 (_ "
|
||||||
|
-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:
|
||||||
|
;; * --checkers=checker1,checker2...: only run the specified checkers
|
||||||
|
;; * --certainty=[low,medium,high]: only run checkers that have at least this
|
||||||
|
;; 'certainty'.
|
||||||
|
(list (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")))))
|
||||||
|
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; 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))))
|
||||||
|
(warning (_ "~a: ~a: ~a~%")
|
||||||
|
(location->string loc)
|
||||||
|
(package-full-name package)
|
||||||
|
message)))
|
||||||
|
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; 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)
|
||||||
|
(lint-checker-description checker)))
|
||||||
|
%checkers)
|
||||||
|
(exit 0))
|
||||||
|
|
||||||
|
(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
|
||||||
|
"pkg-config should probably be a native input"
|
||||||
|
'inputs))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define (check-synopsis-style package)
|
||||||
|
;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE.
|
||||||
|
(define (check-final-period synopsis)
|
||||||
|
;; Synopsis should not end with a period, except for some special cases.
|
||||||
|
(if (and (string=? (string-take-right synopsis 1) ".")
|
||||||
|
(not (string=? (string-take-right synopsis 4) "etc.")))
|
||||||
|
(emit-warning package
|
||||||
|
"no period allowed at the end of the synopsis"
|
||||||
|
'synopsis)))
|
||||||
|
|
||||||
|
(define (check-start-article synopsis)
|
||||||
|
(if (or (string=? (string-take synopsis 2) "A ")
|
||||||
|
(string=? (string-take synopsis 3) "An "))
|
||||||
|
(emit-warning package
|
||||||
|
"no article allowed at the beginning of the synopsis"
|
||||||
|
'synopsis)))
|
||||||
|
|
||||||
|
(let ((synopsis (package-synopsis package)))
|
||||||
|
(if (string? synopsis)
|
||||||
|
(begin
|
||||||
|
(check-final-period synopsis)
|
||||||
|
(check-start-article synopsis)))))
|
||||||
|
|
||||||
|
(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)))
|
||||||
|
(if (and patches
|
||||||
|
(any (lambda (patch)
|
||||||
|
(let ((filename (basename patch)))
|
||||||
|
(not (or (eq? (string-contains filename name) 0)
|
||||||
|
(eq? (string-contains filename full-name) 0)))))
|
||||||
|
patches))
|
||||||
|
(emit-warning package
|
||||||
|
"file names of patches should start with the package name"
|
||||||
|
'patches))))
|
||||||
|
|
||||||
|
(define %checkers
|
||||||
|
(list
|
||||||
|
(lint-checker
|
||||||
|
(name "inputs-should-be-native")
|
||||||
|
(description "Identify inputs that should be native inputs")
|
||||||
|
(check check-inputs-should-be-native))
|
||||||
|
(lint-checker
|
||||||
|
(name "patch-filenames")
|
||||||
|
(description "Validate filenames of patches")
|
||||||
|
(check check-patches))
|
||||||
|
(lint-checker
|
||||||
|
(name "synopsis")
|
||||||
|
(description "Validate package synopsis")
|
||||||
|
(check check-synopsis-style))))
|
||||||
|
|
||||||
|
(define (run-checkers package)
|
||||||
|
;; Run all the checkers on PACKAGE.
|
||||||
|
(for-each (lambda (checker)
|
||||||
|
((lint-checker-check checker) package))
|
||||||
|
%checkers))
|
||||||
|
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; Entry Point
|
||||||
|
;;;
|
||||||
|
|
||||||
|
(define (guix-lint . args)
|
||||||
|
(define (parse-options)
|
||||||
|
;; Return the alist of option values.
|
||||||
|
(args-fold* args %options
|
||||||
|
(lambda (opt name arg result)
|
||||||
|
(leave (_ "~A: unrecognized option~%") name))
|
||||||
|
(lambda (arg result)
|
||||||
|
(alist-cons 'argument arg result))
|
||||||
|
%default-options))
|
||||||
|
|
||||||
|
(let* ((opts (parse-options))
|
||||||
|
(args (filter-map (match-lambda
|
||||||
|
(('argument . value)
|
||||||
|
value)
|
||||||
|
(_ #f))
|
||||||
|
(reverse opts))))
|
||||||
|
|
||||||
|
|
||||||
|
(if (null? args)
|
||||||
|
(fold-packages (lambda (p r) (run-checkers p)) '())
|
||||||
|
(for-each
|
||||||
|
(lambda (spec)
|
||||||
|
(run-checkers spec))
|
||||||
|
(map specification->package args)))))
|
@ -10,7 +10,8 @@ top_builddir = ../..
|
|||||||
XGETTEXT_OPTIONS = \
|
XGETTEXT_OPTIONS = \
|
||||||
--language=Scheme --from-code=UTF-8 \
|
--language=Scheme --from-code=UTF-8 \
|
||||||
--keyword=_ --keyword=N_ \
|
--keyword=_ --keyword=N_ \
|
||||||
--keyword=message
|
--keyword=message \
|
||||||
|
--keyword=description
|
||||||
|
|
||||||
COPYRIGHT_HOLDER = Ludovic Courtès
|
COPYRIGHT_HOLDER = Ludovic Courtès
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ guix/scripts/pull.scm
|
|||||||
guix/scripts/substitute-binary.scm
|
guix/scripts/substitute-binary.scm
|
||||||
guix/scripts/authenticate.scm
|
guix/scripts/authenticate.scm
|
||||||
guix/scripts/system.scm
|
guix/scripts/system.scm
|
||||||
|
guix/scripts/lint.scm
|
||||||
guix/gnu-maintenance.scm
|
guix/gnu-maintenance.scm
|
||||||
guix/ui.scm
|
guix/ui.scm
|
||||||
guix/http-client.scm
|
guix/http-client.scm
|
||||||
|
110
tests/lint.scm
Normal file
110
tests/lint.scm
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
|
;;; Copyright © 2012, 2013 Cyril Roelandt <tipecaml@gmail.com>
|
||||||
|
;;;
|
||||||
|
;;; 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 (test-packages)
|
||||||
|
#:use-module (guix build download)
|
||||||
|
#:use-module (guix build-system gnu)
|
||||||
|
#:use-module (guix packages)
|
||||||
|
#:use-module (guix scripts lint)
|
||||||
|
#:use-module (guix ui)
|
||||||
|
#:use-module (gnu packages)
|
||||||
|
#:use-module (gnu packages pkg-config)
|
||||||
|
#:use-module (srfi srfi-64))
|
||||||
|
|
||||||
|
;; Test the linter.
|
||||||
|
|
||||||
|
|
||||||
|
(test-begin "lint")
|
||||||
|
|
||||||
|
(define-syntax-rule (dummy-package name* extra-fields ...)
|
||||||
|
(package extra-fields ... (name name*) (version "0") (source #f)
|
||||||
|
(build-system gnu-build-system)
|
||||||
|
(synopsis #f) (description #f)
|
||||||
|
(home-page #f) (license #f) ))
|
||||||
|
|
||||||
|
(define (call-with-warnings thunk)
|
||||||
|
(let ((port (open-output-string)))
|
||||||
|
(parameterize ((guix-warning-port port))
|
||||||
|
(thunk))
|
||||||
|
(get-output-string port)))
|
||||||
|
|
||||||
|
(test-assert "synopsis: ends with a period"
|
||||||
|
(->bool
|
||||||
|
(string-contains (call-with-warnings
|
||||||
|
(lambda ()
|
||||||
|
(let ((pkg (dummy-package "x"
|
||||||
|
(synopsis "Bad synopsis."))))
|
||||||
|
(check-synopsis-style pkg))))
|
||||||
|
"no period allowed at the end of the synopsis")))
|
||||||
|
|
||||||
|
(test-assert "synopsis: ends with 'etc.'"
|
||||||
|
(->bool
|
||||||
|
(string-null? (call-with-warnings
|
||||||
|
(lambda ()
|
||||||
|
(let ((pkg (dummy-package "x"
|
||||||
|
(synopsis "Foo, bar, etc."))))
|
||||||
|
(check-synopsis-style pkg)))))))
|
||||||
|
|
||||||
|
(test-assert "synopsis: starts with 'A'"
|
||||||
|
(->bool
|
||||||
|
(string-contains (call-with-warnings
|
||||||
|
(lambda ()
|
||||||
|
(let ((pkg (dummy-package "x"
|
||||||
|
(synopsis "A bad synopŝis"))))
|
||||||
|
(check-synopsis-style pkg))))
|
||||||
|
"no article allowed at the beginning of the synopsis")))
|
||||||
|
|
||||||
|
(test-assert "synopsis: starts with 'An'"
|
||||||
|
(->bool
|
||||||
|
(string-contains (call-with-warnings
|
||||||
|
(lambda ()
|
||||||
|
(let ((pkg (dummy-package "x"
|
||||||
|
(synopsis "An awful synopsis"))))
|
||||||
|
(check-synopsis-style pkg))))
|
||||||
|
"no article allowed at the beginning of the synopsis")))
|
||||||
|
|
||||||
|
(test-assert "inputs: pkg-config is probably a native input"
|
||||||
|
(->bool
|
||||||
|
(string-contains
|
||||||
|
(call-with-warnings
|
||||||
|
(lambda ()
|
||||||
|
(let ((pkg (dummy-package "x"
|
||||||
|
(inputs `(("pkg-config" ,pkg-config))))))
|
||||||
|
(check-inputs-should-be-native pkg))))
|
||||||
|
"pkg-config should probably be a native input")))
|
||||||
|
|
||||||
|
(test-assert "patches: file names"
|
||||||
|
(->bool
|
||||||
|
(string-contains
|
||||||
|
(call-with-warnings
|
||||||
|
(lambda ()
|
||||||
|
(let ((pkg (dummy-package "x"
|
||||||
|
(source
|
||||||
|
(origin
|
||||||
|
(method url-fetch)
|
||||||
|
(uri "someurl")
|
||||||
|
(sha256 "somesha")
|
||||||
|
(patches (list "/path/to/y.patch")))))))
|
||||||
|
(check-patches pkg))))
|
||||||
|
"file names of patches should start with the package name")))
|
||||||
|
|
||||||
|
(test-end "lint")
|
||||||
|
|
||||||
|
|
||||||
|
(exit (= (test-runner-fail-count (test-runner-current)) 0))
|
Loading…
Reference in New Issue
Block a user