gnu: r-qtl2: Move to (gnu packages cran).
[jackhill/guix/guix.git] / guix / lint.scm
index 6336cf4..ec43a4d 100644 (file)
@@ -1,14 +1,15 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com>
 ;;; Copyright © 2014, 2015 Eric Bavier <bavier@member.fsf.org>
-;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015, 2016 Mathieu Lirzin <mthl@gnu.org>
 ;;; Copyright © 2016 Danny Milosavljevic <dannym+a@scratchpost.org>
 ;;; Copyright © 2016 Hartmut Goebel <h.goebel@crazy-compilers.com>
 ;;; Copyright © 2017 Alex Kost <alezost@gmail.com>
 ;;; Copyright © 2017 Tobias Geerinckx-Rice <me@tobias.gr>
-;;; Copyright © 2017, 2018 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2017, 2018, 2020 Efraim Flashner <efraim@flashner.co.il>
 ;;; Copyright © 2018, 2019 Arun Isaac <arunisaac@systemreboot.net>
+;;; Copyright © 2020 Chris Marusich <cmmarusich@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -26,7 +27,7 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (guix lint)
-  #:use-module ((guix store) #:hide (close-connection))
+  #:use-module (guix store)
   #:use-module (guix base32)
   #:use-module (guix diagnostics)
   #:use-module (guix download)
   #:use-module (guix upstream)
   #:use-module (guix utils)
   #:use-module (guix memoization)
+  #:use-module (guix profiles)
+  #:use-module (guix monads)
   #:use-module (guix scripts)
   #:use-module ((guix ui) #:select (texi->plain-text fill-paragraph))
   #:use-module (guix gnu-maintenance)
   #:use-module (guix cve)
   #:use-module ((guix swh) #:hide (origin?))
-  #:autoload   (guix git-download) (git-reference?)
+  #:autoload   (guix git-download) (git-reference?
+                                    git-reference-url git-reference-commit)
   #:use-module (ice-9 match)
   #:use-module (ice-9 regex)
   #:use-module (ice-9 format)
@@ -54,8 +58,7 @@
   #:use-module ((guix build download)
                 #:select (maybe-expand-mirrors
                           (open-connection-for-uri
-                           . guix:open-connection-for-uri)
-                          close-connection))
+                           . guix:open-connection-for-uri)))
   #:use-module (web request)
   #:use-module (web response)
   #:use-module (srfi srfi-1)
@@ -83,6 +86,7 @@
             check-for-updates
             check-formatting
             check-archival
+            check-profile-collisions
 
             lint-warning
             lint-warning?
             lint-checker?
             lint-checker-name
             lint-checker-description
-            lint-checker-check))
+            lint-checker-check
+            lint-checker-requires-store?))
 
 \f
 ;;;
    message-text
    message-data
    (or location
-       (package-field-location package field)
+       (and field (package-field-location package field))
        (package-location package))))
 
 (define-syntax make-warning
   ;; 'certainty' level.
   (name        lint-checker-name)
   (description lint-checker-description)
-  (check       lint-checker-check))
+  (check       lint-checker-check)
+  (requires-store? lint-checker-requires-store?
+                   (default #f)))
 
 (define (properly-starts-sentence? s)
   (string-match "^[(\"'`[:upper:][:digit:]]" s))
@@ -283,15 +290,32 @@ of a package, and INPUT-NAMES, a list of package specifications such as
 (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))
+  (let ((inputs (append (package-inputs package)
+                        (package-propagated-inputs package)))
         (input-names
          '("pkg-config"
+            "autoconf"
+            "automake"
+            "bison"
             "cmake"
+            "dejagnu"
+            "desktop-file-utils"
+            "doxygen"
             "extra-cmake-modules"
+            "flex"
+            "gettext"
             "glib:bin"
+            "gobject-introspection"
+            "googletest-source"
+            "groff"
+            "gtk-doc"
+            "help2man"
             "intltool"
             "itstool"
+            "libtool"
+            "m4"
             "qttools"
+            "yasm" "nasm" "fasm"
             "python-coverage" "python2-coverage"
             "python-cython" "python2-cython"
             "python-docutils" "python2-docutils"
@@ -301,7 +325,14 @@ of a package, and INPUT-NAMES, a list of package specifications such as
             "python-pytest" "python2-pytest"
             "python-pytest-cov" "python2-pytest-cov"
             "python-setuptools-scm" "python2-setuptools-scm"
-            "python-sphinx" "python2-sphinx")))
+            "python-sphinx" "python2-sphinx"
+            "scdoc"
+            "swig"
+            "qmake"
+            "qttools"
+            "texinfo"
+            "xorg-server-for-tests"
+            "yelp-tools")))
     (map (lambda (input)
            (make-warning
             package
@@ -452,7 +483,7 @@ for connections to complete; when TIMEOUT is #f, wait as long as needed."
                    (force-output port)
                    (read-response port))
                  (lambda ()
-                   (close-connection port))))
+                   (close-port port))))
 
              (case (response-code response)
                ((302                    ; found (redirection)
@@ -637,18 +668,28 @@ patch could not be found."
               ;; Use %make-warning, as condition-mesasge is already
               ;; translated.
               (%make-warning package (condition-message c)
-                             #:field 'patch-file-names))))
+                             #:field 'patch-file-names)))
+            ((formatted-message? c)
+             (list (%make-warning package
+                                  (apply format #f
+                                         (G_ (formatted-message-string c))
+                                         (formatted-message-arguments c))))))
     (define patches
-      (or (and=> (package-source package) origin-patches)
-          '()))
+      (match (package-source package)
+        ((? origin? origin) (origin-patches origin))
+        (_ '())))
+
+    (define (starts-with-package-name? file-name)
+      (and=> (string-contains file-name (package-name package))
+             zero?))
 
     (append
      (if (every (match-lambda        ;patch starts with package name?
                   ((? string? patch)
-                   (and=> (string-contains (basename patch)
-                                           (package-name package))
-                          zero?))
-                  (_  #f))     ;must be an <origin> or something like that.
+                   (starts-with-package-name? (basename patch)))
+                  ((? origin? patch)
+                   (starts-with-package-name? (origin-actual-file-name patch)))
+                  (_  #f))     ;must be some other file-like object
                 patches)
          '()
          (list
@@ -659,7 +700,7 @@ patch could not be found."
 
      ;; Check whether we're reaching tar's maximum file name length.
      (let ((prefix (string-length (%distro-directory)))
-           (margin (string-length "guix-0.13.0-10-123456789/"))
+           (margin (string-length "guix-2.0.0rc3-10000-1234567890/"))
            (max    99))
        (filter-map (match-lambda
                      ((? string? patch)
@@ -753,30 +794,39 @@ descriptions maintained upstream."
            (#t
             ;; We found a working URL, so stop right away.
             '())
+           (#f
+            ;; Unsupported URL or other error, skip.
+            (loop rest warnings))
            ((? lint-warning? warning)
             (loop rest (cons warning warnings))))))))
 
   (let ((origin (package-source package)))
-    (if (and origin
-             (eqv? (origin-method origin) url-fetch))
-        (let* ((uris     (append-map (cut maybe-expand-mirrors <> %mirrors)
-                                     (map string->uri (origin-uris origin))))
-               (warnings (warnings-for-uris uris)))
-
-          ;; Just make sure that at least one of the URIs is valid.
-          (if (= (length uris) (length warnings))
-              ;; When everything fails, report all of WARNINGS, otherwise don't
-              ;; report anything.
-              ;;
-              ;; XXX: Ideally we'd still allow warnings to be raised if *some*
-              ;; URIs are unreachable, but distinguish that from the error case
-              ;; where *all* the URIs are unreachable.
-              (cons*
-               (make-warning package
-                             (G_ "all the source URIs are unreachable:")
-                             #:field 'source)
-               warnings)
-              '()))
+    (if (origin? origin)
+        (cond
+         ((eq? (origin-method origin) url-fetch)
+          (let* ((uris     (append-map (cut maybe-expand-mirrors <> %mirrors)
+                                       (map string->uri (origin-uris origin))))
+                 (warnings (warnings-for-uris uris)))
+
+            ;; Just make sure that at least one of the URIs is valid.
+            (if (= (length uris) (length warnings))
+                ;; When everything fails, report all of WARNINGS, otherwise don't
+                ;; report anything.
+                ;;
+                ;; XXX: Ideally we'd still allow warnings to be raised if *some*
+                ;; URIs are unreachable, but distinguish that from the error case
+                ;; where *all* the URIs are unreachable.
+                (cons*
+                 (make-warning package
+                               (G_ "all the source URIs are unreachable:")
+                               #:field 'source)
+                 warnings)
+                '())))
+         ((git-reference? (origin-uri origin))
+          (warnings-for-uris
+           (list (string->uri (git-reference-url (origin-uri origin))))))
+         (else
+          '()))
         '())))
 
 (define (check-source-file-name package)
@@ -793,7 +843,7 @@ descriptions maintained upstream."
            (not (string-match (string-append "^v?" version) file-name)))))
 
   (let ((origin (package-source package)))
-    (if (or (not origin) (origin-file-name-valid? origin))
+    (if (or (not (origin? origin)) (origin-file-name-valid? origin))
         '()
         (list
          (make-warning package
@@ -884,40 +934,98 @@ descriptions maintained upstream."
          (origin-uris origin))
         '())))
 
-(define (check-derivation package)
+(cond-expand
+  (guile-3
+   ;; Guile 3.0.0 does not export this predicate.
+   (define exception-with-kind-and-args?
+     (exception-predicate &exception-with-kind-and-args)))
+  (else                                           ;Guile 2
+   (define exception-with-kind-and-args?
+     (const #f))))
+
+(define* (check-derivation package #:key store)
   "Emit a warning if we fail to compile PACKAGE to a derivation."
-  (define (try system)
-    (catch #t
+  (define (try store system)
+    (catch #t     ;TODO: Remove 'catch' when Guile 2.x is no longer supported.
       (lambda ()
         (guard (c ((store-protocol-error? c)
                    (make-warning package
                                  (G_ "failed to create ~a derivation: ~a")
                                  (list system
                                        (store-protocol-error-message c))))
+                  ((exception-with-kind-and-args? c)
+                   (make-warning package
+                                 (G_ "failed to create ~a derivation: ~s")
+                                 (list system
+                                       (cons (exception-kind c)
+                                             (exception-args c)))))
                   ((message-condition? c)
                    (make-warning package
                                  (G_ "failed to create ~a derivation: ~a")
                                  (list system
-                                       (condition-message c)))))
-          (with-store store
-            ;; Disable grafts since it can entail rebuilds.
-            (parameterize ((%graft? #f))
-              (package-derivation store package system #:graft? #f)
-
-              ;; If there's a replacement, make sure we can compute its
-              ;; derivation.
-              (match (package-replacement package)
-                (#f #t)
-                (replacement
-                 (package-derivation store replacement system
-                                     #:graft? #f)))))))
+                                       (condition-message c))))
+                  ((formatted-message? c)
+                   (let ((str (apply format #f
+                                     (formatted-message-string c)
+                                     (formatted-message-arguments c))))
+                     (make-warning package
+                                   (G_ "failed to create ~a derivation: ~a")
+                                   (list system str)))))
+          (parameterize ((%graft? #f))
+            (package-derivation store package system #:graft? #f)
+
+            ;; If there's a replacement, make sure we can compute its
+            ;; derivation.
+            (match (package-replacement package)
+              (#f #t)
+              (replacement
+               (package-derivation store replacement system
+                                   #:graft? #f))))))
       (lambda args
         (make-warning package
                       (G_ "failed to create ~a derivation: ~s")
                       (list system args)))))
 
-  (filter lint-warning?
-          (map try (package-supported-systems package))))
+  (define (check-with-store store)
+    (filter lint-warning?
+            (map (cut try store <>) (package-supported-systems package))))
+
+  ;; For backwards compatability, don't rely on store being set
+  (or (and=> store check-with-store)
+      (with-store store
+        (check-with-store store))))
+
+(define* (check-profile-collisions package #:key store)
+  "Check for collisions that would occur when installing PACKAGE as a result
+of the propagated inputs it pulls in."
+  (define (do-check store)
+    (guard (c ((profile-collision-error? c)
+               (let ((first  (profile-collision-error-entry c))
+                     (second (profile-collision-error-conflict c)))
+                 (define format
+                   (if (string=? (manifest-entry-version first)
+                                 (manifest-entry-version second))
+                       manifest-entry-item
+                       (lambda (entry)
+                         (string-append (manifest-entry-name entry) "@"
+                                        (manifest-entry-version entry)))))
+
+                 (list (make-warning package
+                                     (G_ "propagated inputs ~a and ~a collide")
+                                     (list (format first)
+                                           (format second)))))))
+      ;; Disable grafts to avoid building PACKAGE and its dependencies.
+      (parameterize ((%graft? #f))
+        (run-with-store store
+          (mbegin %store-monad
+            (check-for-collisions (packages->manifest (list package))
+                                  (%current-system))
+            (return '()))))))
+
+  (if store
+      (do-check store)
+      (with-store store
+        (do-check store))))
 
 (define (check-license package)
   "Warn about type errors of the 'license' field of PACKAGE."
@@ -993,8 +1101,11 @@ the NIST server non-fatal."
                          (package-version package))))
         ((force lookup) name version)))))
 
-(define (check-vulnerabilities package)
-  "Check for known vulnerabilities for PACKAGE."
+(define* (check-vulnerabilities package
+                                #:optional (package-vulnerabilities
+                                            package-vulnerabilities))
+  "Check for known vulnerabilities for PACKAGE.  Obtain the list of
+vulnerability records for PACKAGE by calling PACKAGE-VULNERABILITIES."
   (let ((package (or (package-replacement package) package)))
     (match (package-vulnerabilities package)
       (()
@@ -1105,23 +1216,29 @@ try again later")
           ((? origin? origin)
            ;; Since "save" origins are not supported for non-VCS source, all
            ;; we can do is tell whether a given tarball is available or not.
-           (if (origin-sha256 origin)             ;XXX: for ungoogled-chromium
-               (match (lookup-content (origin-sha256 origin) "sha256")
-                 (#f
-                  (list (make-warning package
-                                      (G_ "source not archived on Software \
+           (if (origin-hash origin)               ;XXX: for ungoogled-chromium
+               (let ((hash (origin-hash origin)))
+                 (match (lookup-content (content-hash-value hash)
+                                        (symbol->string
+                                         (content-hash-algorithm hash)))
+                   (#f
+                    (list (make-warning package
+                                        (G_ "source not archived on Software \
 Heritage")
-                                      #:field 'source)))
-                 ((? content?)
-                  '()))
+                                        #:field 'source)))
+                   ((? content?)
+                    '())))
                '()))))
       (match-lambda*
-        ((key url method response)
+        (('swh-error url method response)
          (response->warning url method response))
         ((key . args)
          (if (eq? key skip-key)
              '()
-             (apply throw key args)))))))
+             (with-networking-fail-safe
+              (G_ "while connecting to Software Heritage")
+              '()
+              (apply throw key args))))))))
 
 \f
 ;;;
@@ -1238,12 +1355,20 @@ them for PACKAGE."
   "Check the formatting of the source code of PACKAGE."
   (let ((location (package-location package)))
     (if location
-        (and=> (search-path %load-path (location-file location))
-               (lambda (file)
-                 ;; Report issues starting from the line before the 'package'
-                 ;; form, which usually contains the 'define' form.
-                 (report-formatting-issues package file
-                                           (- (location-line location) 1))))
+        ;; Report issues starting from the line before the 'package'
+        ;; form, which usually contains the 'define' form.
+        (let ((line (- (location-line location) 1)))
+          (match (search-path %load-path (location-file location))
+            ((? string? file)
+             (report-formatting-issues package file line))
+            (#f
+             ;; It could be that LOCATION lists a "true" relative file
+             ;; name--i.e., not relative to an element of %LOAD-PATH.
+             (let ((file (location-file location)))
+               (if (file-exists? file)
+                   (report-formatting-issues package file line)
+                   (list (make-warning package
+                                       (G_ "source file not found"))))))))
         '())))
 
 \f
@@ -1285,9 +1410,15 @@ or a list thereof")
      (description "Check for autogenerated tarballs")
      (check       check-source-unstable-tarball))
    (lint-checker
-     (name        'derivation)
-     (description "Report failure to compile a package to a derivation")
-     (check       check-derivation))
+     (name            'derivation)
+     (description     "Report failure to compile a package to a derivation")
+     (check           check-derivation)
+     (requires-store? #t))
+   (lint-checker
+     (name            'profile-collisions)
+     (description     "Report collisions that would occur due to propagated inputs")
+     (check           check-profile-collisions)
+     (requires-store? #t))
    (lint-checker
     (name        'patch-file-names)
     (description "Validate file names and availability of patches")
@@ -1319,17 +1450,11 @@ or a list thereof")
      (name        'github-url)
      (description "Suggest GitHub URLs")
      (check       check-github-url))
-
-   ;; FIXME: Commented out as a consequence of the XML CVE feed retirement:
-   ;; <https://nvd.nist.gov/General/News/XML-Vulnerability-Feed-Retirement-Phase-3>.
-   ;; Reinstate it once the JSON feed is supported.
-
- ;;   (lint-checker
- ;;     (name        'cve)
- ;;     (description "Check the Common Vulnerabilities and Exposures\
- ;; (CVE) database")
- ;;     (check       check-vulnerabilities))
-
+   (lint-checker
+     (name        'cve)
+     (description "Check the Common Vulnerabilities and Exposures\
+ (CVE) database")
+     (check       check-vulnerabilities))
    (lint-checker
      (name        'refresh)
      (description "Check the package for new upstream releases")