lint.scm 59 KB

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