guix-hydra-build.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. ;;; guix-hydra-build.el --- Interface for Hydra builds -*- lexical-binding: t -*-
  2. ;; Copyright © 2015–2017 Alex Kost <alezost@gmail.com>
  3. ;; This file is part of Emacs-Guix.
  4. ;; Emacs-Guix is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; Emacs-Guix is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with Emacs-Guix. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This file provides an interface for displaying Hydra builds in
  18. ;; 'list' and 'info' buffers.
  19. ;;; Code:
  20. (require 'cl-lib)
  21. (require 'bui)
  22. (require 'guix-hydra)
  23. (require 'guix-utils)
  24. (guix-hydra-define-entry-type build
  25. :search-types '((latest . guix-hydra-build-latest-api-url)
  26. (queue . guix-hydra-build-queue-api-url))
  27. :filters '(guix-hydra-build-filter-status)
  28. :filter-names '((nixname . name)
  29. (buildstatus . build-status)
  30. (timestamp . time))
  31. :filter-boolean-params '(finished busy))
  32. (defun guix-hydra-build-get-display (search-type &rest args)
  33. "Search for Hydra builds and show results."
  34. (apply #'bui-list-get-display-entries
  35. 'guix-hydra-build search-type args))
  36. (cl-defun guix-hydra-build-latest-prompt-args (&key project jobset
  37. job system)
  38. "Prompt for and return a list of 'latest builds' arguments."
  39. (let* ((number (read-number "Number of latest builds: "))
  40. (project (if current-prefix-arg
  41. (guix-hydra-read-project nil project)
  42. project))
  43. (jobset (if current-prefix-arg
  44. (guix-hydra-read-jobset nil jobset)
  45. jobset))
  46. (job-or-name (if current-prefix-arg
  47. (guix-hydra-read-job nil job)
  48. job))
  49. (job (and job-or-name
  50. (string-match-p guix-hydra-job-regexp
  51. job-or-name)
  52. job-or-name))
  53. (system (if (and (not job)
  54. (or current-prefix-arg
  55. (and job-or-name (not system))))
  56. (if job-or-name
  57. (guix-while-null
  58. (guix-hydra-read-system
  59. (concat job-or-name ".") system))
  60. (guix-hydra-read-system nil system))
  61. system))
  62. (job (or job
  63. (and job-or-name
  64. (concat job-or-name "." system)))))
  65. (list number
  66. :project project
  67. :jobset jobset
  68. :job job
  69. :system system)))
  70. (declare-function guix-build-log-find-file "guix-build-log" (file))
  71. (defun guix-hydra-build-view-log (id)
  72. "View build log of a hydra build ID."
  73. (require 'guix-build-log)
  74. (guix-build-log-find-file (guix-hydra-build-log-url id)))
  75. ;;; Defining URLs
  76. (defun guix-hydra-build-url (id)
  77. "Return Hydra URL of a build ID."
  78. (guix-hydra-url "build/" (number-to-string id)))
  79. (defun guix-hydra-build-log-url (id)
  80. "Return Hydra URL of the log file of a build ID."
  81. (concat (guix-hydra-build-url id) "/log/raw"))
  82. (cl-defun guix-hydra-build-latest-api-url
  83. (number &key project jobset job system)
  84. "Return Hydra API URL to receive latest NUMBER of builds."
  85. (guix-hydra-api-url "latestbuilds"
  86. `(("nr" . ,number)
  87. ("project" . ,project)
  88. ("jobset" . ,jobset)
  89. ("job" . ,job)
  90. ("system" . ,system))))
  91. (defun guix-hydra-build-queue-api-url (number)
  92. "Return Hydra API URL to receive the NUMBER of queued builds."
  93. (guix-hydra-api-url "queue"
  94. `(("nr" . ,number))))
  95. ;;; Filters for processing raw entries
  96. (defun guix-hydra-build-filter-status (entry)
  97. "Add 'status' parameter to 'hydra-build' ENTRY."
  98. (let ((status (if (bui-entry-non-void-value entry 'finished)
  99. (guix-hydra-build-status-number->name
  100. (bui-entry-non-void-value entry 'build-status))
  101. (if (bui-entry-non-void-value entry 'busy)
  102. 'running
  103. 'scheduled))))
  104. (cons `(status . ,status)
  105. entry)))
  106. ;;; Build status
  107. (defface guix-hydra-build-status-running
  108. '((t :inherit bold))
  109. "Face used if hydra build is not finished."
  110. :group 'guix-hydra-build-faces)
  111. (defface guix-hydra-build-status-scheduled
  112. '((t))
  113. "Face used if hydra build is scheduled."
  114. :group 'guix-hydra-build-faces)
  115. (defface guix-hydra-build-status-succeeded
  116. '((t :inherit success))
  117. "Face used if hydra build succeeded."
  118. :group 'guix-hydra-build-faces)
  119. (defface guix-hydra-build-status-cancelled
  120. '((t :inherit warning))
  121. "Face used if hydra build was cancelled."
  122. :group 'guix-hydra-build-faces)
  123. (defface guix-hydra-build-status-failed
  124. '((t :inherit error))
  125. "Face used if hydra build failed."
  126. :group 'guix-hydra-build-faces)
  127. (defvar guix-hydra-build-status-alist
  128. '((0 . succeeded)
  129. (1 . failed-build)
  130. (2 . failed-dependency)
  131. (3 . failed-other)
  132. (4 . cancelled))
  133. "Alist of hydra build status numbers and status names.
  134. Status numbers are returned by Hydra API, names (symbols) are
  135. used internally by the elisp code of this package.")
  136. (defun guix-hydra-build-status-number->name (number)
  137. "Convert build status number to a name.
  138. See `guix-hydra-build-status-alist'."
  139. (bui-assq-value guix-hydra-build-status-alist number))
  140. (defun guix-hydra-build-status-string (status)
  141. "Return a human readable string for build STATUS."
  142. (cl-case status
  143. (scheduled
  144. (bui-get-string "Scheduled" 'guix-hydra-build-status-scheduled))
  145. (running
  146. (bui-get-string "Running" 'guix-hydra-build-status-running))
  147. (succeeded
  148. (bui-get-string "Succeeded" 'guix-hydra-build-status-succeeded))
  149. (cancelled
  150. (bui-get-string "Cancelled" 'guix-hydra-build-status-cancelled))
  151. (failed-build
  152. (guix-hydra-build-status-fail-string))
  153. (failed-dependency
  154. (guix-hydra-build-status-fail-string "dependency"))
  155. (failed-other
  156. (guix-hydra-build-status-fail-string "other"))))
  157. (defun guix-hydra-build-status-fail-string (&optional reason)
  158. "Return a string for a failed build."
  159. (let ((base (bui-get-string "Failed" 'guix-hydra-build-status-failed)))
  160. (if reason
  161. (concat base " (" reason ")")
  162. base)))
  163. (defun guix-hydra-build-finished? (entry)
  164. "Return non-nil, if hydra build was finished."
  165. (bui-entry-non-void-value entry 'finished))
  166. (defun guix-hydra-build-running? (entry)
  167. "Return non-nil, if hydra build is running."
  168. (eq (bui-entry-non-void-value entry 'status)
  169. 'running))
  170. (defun guix-hydra-build-scheduled? (entry)
  171. "Return non-nil, if hydra build is scheduled."
  172. (eq (bui-entry-non-void-value entry 'status)
  173. 'scheduled))
  174. (defun guix-hydra-build-succeeded? (entry)
  175. "Return non-nil, if hydra build succeeded."
  176. (eq (bui-entry-non-void-value entry 'status)
  177. 'succeeded))
  178. (defun guix-hydra-build-cancelled? (entry)
  179. "Return non-nil, if hydra build was cancelled."
  180. (eq (bui-entry-non-void-value entry 'status)
  181. 'cancelled))
  182. (defun guix-hydra-build-failed? (entry)
  183. "Return non-nil, if hydra build failed."
  184. (memq (bui-entry-non-void-value entry 'status)
  185. '(failed-build failed-dependency failed-other)))
  186. ;;; Hydra build 'info'
  187. (guix-hydra-define-interface build info
  188. :mode-name "Hydra-Build-Info"
  189. :buffer-name "*Guix Hydra Build Info*"
  190. :format '((name nil (simple bui-info-heading))
  191. nil
  192. guix-hydra-build-info-insert-url
  193. (time format (time))
  194. (status format guix-hydra-build-info-insert-status)
  195. (project format (format guix-hydra-build-project))
  196. (jobset format (format guix-hydra-build-jobset))
  197. (job format (format guix-hydra-build-job))
  198. (system format (format guix-hydra-build-system))
  199. (priority format (format))))
  200. (defface guix-hydra-build-info-project
  201. '((t :inherit link))
  202. "Face for project names."
  203. :group 'guix-hydra-build-info-faces)
  204. (defface guix-hydra-build-info-jobset
  205. '((t :inherit link))
  206. "Face for jobsets."
  207. :group 'guix-hydra-build-info-faces)
  208. (defface guix-hydra-build-info-job
  209. '((t :inherit link))
  210. "Face for jobs."
  211. :group 'guix-hydra-build-info-faces)
  212. (defface guix-hydra-build-info-system
  213. '((t :inherit link))
  214. "Face for system names."
  215. :group 'guix-hydra-build-info-faces)
  216. (defmacro guix-hydra-build-define-button (name)
  217. "Define `guix-hydra-build-NAME' button."
  218. (let* ((name-str (symbol-name name))
  219. (button-name (intern (concat "guix-hydra-build-" name-str)))
  220. (face-name (intern (concat "guix-hydra-build-info-" name-str)))
  221. (keyword (intern (concat ":" name-str))))
  222. `(define-button-type ',button-name
  223. :supertype 'bui
  224. 'face ',face-name
  225. 'help-echo ,(format "\
  226. Show latest builds for this %s (with prefix, prompt for all parameters)"
  227. name-str)
  228. 'action (lambda (btn)
  229. (let ((args (guix-hydra-build-latest-prompt-args
  230. ,keyword (button-label btn))))
  231. (apply #'guix-hydra-build-get-display
  232. 'latest args))))))
  233. (guix-hydra-build-define-button project)
  234. (guix-hydra-build-define-button jobset)
  235. (guix-hydra-build-define-button job)
  236. (guix-hydra-build-define-button system)
  237. (defun guix-hydra-build-info-insert-url (entry)
  238. "Insert Hydra URL for the build ENTRY."
  239. (bui-insert-button (guix-hydra-build-url (bui-entry-id entry))
  240. 'bui-url)
  241. (when (guix-hydra-build-finished? entry)
  242. (bui-insert-indent)
  243. (bui-insert-action-button
  244. "Build log"
  245. (lambda (btn)
  246. (guix-hydra-build-view-log (button-get btn 'id)))
  247. "View build log"
  248. 'id (bui-entry-id entry)))
  249. (bui-newline))
  250. (defun guix-hydra-build-info-insert-status (status &optional _)
  251. "Insert a string with build STATUS."
  252. (insert (guix-hydra-build-status-string status)))
  253. ;;; Hydra build 'list'
  254. (guix-hydra-define-interface build list
  255. :describe-function 'guix-hydra-list-describe
  256. :mode-name "Hydra-Build-List"
  257. :buffer-name "*Guix Hydra Builds*"
  258. :format '((name nil 30 t)
  259. (system nil 16 t)
  260. (status guix-hydra-build-list-get-status 20 t)
  261. (project nil 10 t)
  262. (jobset nil 17 t)
  263. (time bui-list-get-time 20 t))
  264. :hint 'guix-hydra-build-list-hint)
  265. (let ((map guix-hydra-build-list-mode-map))
  266. (define-key map (kbd "B") 'guix-hydra-build-list-latest-builds)
  267. (define-key map (kbd "L") 'guix-hydra-build-list-view-log))
  268. (defvar guix-hydra-build-list-default-hint
  269. '(("\\[guix-hydra-build-list-latest-builds]")
  270. " show latest builds of the current job;\n"
  271. ("\\[guix-hydra-build-list-view-log]") " show build log;\n"))
  272. (defun guix-hydra-build-list-hint ()
  273. (bui-format-hints
  274. guix-hydra-build-list-default-hint
  275. (bui-default-hint)))
  276. (defun guix-hydra-build-list-get-status (status &optional _)
  277. "Return a string for build STATUS."
  278. (guix-hydra-build-status-string status))
  279. (defun guix-hydra-build-list-latest-builds (number &rest args)
  280. "Display latest NUMBER of Hydra builds of the current job.
  281. Interactively, prompt for NUMBER. With prefix argument, prompt
  282. for all ARGS."
  283. (interactive
  284. (let ((entry (bui-list-current-entry)))
  285. (guix-hydra-build-latest-prompt-args
  286. :project (bui-entry-non-void-value entry 'project)
  287. :jobset (bui-entry-non-void-value entry 'name)
  288. :job (bui-entry-non-void-value entry 'job)
  289. :system (bui-entry-non-void-value entry 'system))))
  290. (apply #'guix-hydra-latest-builds number args))
  291. (defun guix-hydra-build-list-view-log ()
  292. "View build log of the current Hydra build."
  293. (interactive)
  294. (guix-hydra-build-view-log (bui-list-current-id)))
  295. ;;; Interactive commands
  296. ;;;###autoload
  297. (defun guix-hydra-latest-builds (number &rest args)
  298. "Display latest NUMBER of Hydra builds.
  299. ARGS are the same arguments as for `guix-hydra-build-latest-api-url'.
  300. Interactively, prompt for NUMBER. With prefix argument, prompt
  301. for all ARGS."
  302. (interactive (guix-hydra-build-latest-prompt-args))
  303. (apply #'guix-hydra-build-get-display
  304. 'latest number args))
  305. ;;;###autoload
  306. (defun guix-hydra-queued-builds (number)
  307. "Display the NUMBER of queued Hydra builds."
  308. (interactive "NNumber of queued builds: ")
  309. (guix-hydra-build-get-display 'queue number))
  310. (provide 'guix-hydra-build)
  311. ;;; guix-hydra-build.el ends here