guix-hydra.el 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. ;;; guix-hydra.el --- Common code for interacting with Hydra -*- lexical-binding: t -*-
  2. ;; Copyright © 2015–2018 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 some general code for 'list'/'info' interfaces for
  18. ;; Hydra (Guix build farm).
  19. ;;; Code:
  20. (require 'json)
  21. (require 'bui)
  22. (require 'guix nil t)
  23. (require 'guix-utils)
  24. (require 'guix-help-vars)
  25. (guix-define-groups hydra)
  26. (defvar guix-hydra-job-regexp
  27. (concat ".*\\." (regexp-opt guix-help-system-types) "\\'")
  28. "Regexp matching a full name of Hydra job (including system).")
  29. (defun guix-hydra-job-name-specification (name version)
  30. "Return Hydra's job name specification by NAME and VERSION."
  31. (concat name "-" version))
  32. (defun guix-hydra-message (entries search-type &rest _)
  33. "Display a message after showing Hydra ENTRIES."
  34. ;; XXX Add more messages maybe.
  35. (when (null entries)
  36. (if (eq search-type 'fake)
  37. (message "The update is impossible due to lack of Hydra API.")
  38. (message "Hydra has returned no results."))))
  39. (defun guix-hydra-list-describe (&rest ids)
  40. "Describe 'hydra' entries with IDS (list of identifiers)."
  41. (bui-display-entries
  42. (bui-entries-by-ids (bui-current-entries) ids)
  43. (bui-current-entry-type) 'info
  44. ;; Hydra does not provide an API to receive builds/jobsets by
  45. ;; IDs/names, so we use a 'fake' search type.
  46. '(fake)
  47. 'add))
  48. ;;; Readers
  49. (defvar guix-hydra-projects
  50. '("gnu" "guix")
  51. "List of available Hydra projects.")
  52. (guix-define-readers
  53. :completions-var guix-hydra-projects
  54. :single-reader guix-hydra-read-project
  55. :single-prompt "Project: ")
  56. (guix-define-readers
  57. :require-match nil
  58. :single-reader guix-hydra-read-jobset
  59. :single-prompt "Jobset: ")
  60. (guix-define-readers
  61. :require-match nil
  62. :single-reader guix-hydra-read-job
  63. :single-prompt "Job: ")
  64. (guix-define-readers
  65. :completions-var guix-help-system-types
  66. :single-reader guix-hydra-read-system
  67. :single-prompt "System: ")
  68. ;;; Defining URLs
  69. (defvar guix-hydra-urls
  70. '("https://hydra.gnu.org"
  71. "https://berlin.guixsd.org"
  72. "https://hydra.nixos.org")
  73. "List of URLs of the available build farms.")
  74. (defcustom guix-hydra-url (car guix-hydra-urls)
  75. "URL of the default build farm."
  76. :type `(choice ,@(mapcar (lambda (url) (list 'const url))
  77. guix-hydra-urls)
  78. (string :tag "Other URL"))
  79. :group 'guix-hydra)
  80. (defun guix-hydra-url (&rest url-parts)
  81. "Return Hydra URL."
  82. (apply #'concat guix-hydra-url "/" url-parts))
  83. (defun guix-hydra-api-url (type args)
  84. "Return URL for receiving data using Hydra API.
  85. TYPE is the name of an allowed method.
  86. ARGS is alist of (KEY . VALUE) pairs.
  87. Skip ARG, if VALUE is nil or an empty string."
  88. (declare (indent 1))
  89. (let* ((fields (mapcar
  90. (lambda (arg)
  91. (pcase arg
  92. (`(,key . ,value)
  93. (unless (or (null value)
  94. (equal "" value))
  95. (concat (guix-hexify key) "="
  96. (guix-hexify value))))
  97. (_ (error "Wrong argument '%s'" arg))))
  98. args))
  99. (fields (mapconcat #'identity (delq nil fields) "&")))
  100. (guix-hydra-url "api/" type "?" fields)))
  101. ;;; Receiving data from Hydra
  102. (defun guix-hydra-receive-data (url)
  103. "Return output received from URL and processed with `json-read'."
  104. (with-temp-buffer
  105. (url-insert-file-contents url)
  106. (goto-char (point-min))
  107. (let ((json-key-type 'symbol)
  108. (json-array-type 'list)
  109. (json-object-type 'alist))
  110. (json-read))))
  111. (defun guix-hydra-get-entries (entry-type search-type &rest args)
  112. "Receive ENTRY-TYPE entries from Hydra.
  113. SEARCH-TYPE is one of the types defined by `guix-hydra-define-interface'."
  114. (unless (eq search-type 'fake)
  115. (let* ((url (apply #'guix-hydra-search-url
  116. entry-type search-type args))
  117. (raw-entries (guix-hydra-receive-data url))
  118. (entries (apply #'guix-modify-objects
  119. raw-entries
  120. (guix-hydra-filters entry-type))))
  121. entries)))
  122. ;;; Filters for processing raw entries
  123. (defun guix-hydra-filter-names (entry name-alist)
  124. "Replace names of ENTRY parameters using NAME-ALIST.
  125. Each element of NAME-ALIST is (OLD-NAME . NEW-NAME) pair."
  126. (mapcar (lambda (param)
  127. (pcase param
  128. (`(,name . ,val)
  129. (let ((new-name (bui-assq-value name-alist name)))
  130. (if new-name
  131. (cons new-name val)
  132. param)))))
  133. entry))
  134. (defun guix-hydra-filter-boolean (entry params)
  135. "Convert number PARAMS (0/1) of ENTRY to boolean values (nil/t)."
  136. (mapcar (lambda (param)
  137. (pcase param
  138. (`(,name . ,val)
  139. (if (memq name params)
  140. (cons name (guix-number->bool val))
  141. param))))
  142. entry))
  143. ;;; Wrappers for defined variables
  144. (defun guix-hydra-symbol (&rest symbols)
  145. "Return `guix-SYMBOLS-...' symbol."
  146. (apply #'guix-make-symbol 'hydra symbols))
  147. (defun guix-hydra-symbol-value (entry-type symbol)
  148. "Return SYMBOL's value for ENTRY-TYPE."
  149. (symbol-value (guix-hydra-symbol entry-type symbol)))
  150. (defun guix-hydra-search-url (entry-type search-type &rest args)
  151. "Return URL to receive ENTRY-TYPE entries from Hydra."
  152. (apply (bui-assq-value (guix-hydra-symbol-value
  153. entry-type 'search-types)
  154. search-type)
  155. args))
  156. (defun guix-hydra-filters (entry-type)
  157. "Return a list of filters for ENTRY-TYPE."
  158. (guix-hydra-symbol-value entry-type 'filters))
  159. ;;; Interface definers
  160. (defmacro guix-hydra-define-entry-type (entry-type &rest args)
  161. "Define general code for ENTRY-TYPE.
  162. Remaining arguments (ARGS) should have a form [KEYWORD VALUE] ...
  163. Required keywords:
  164. - `:search-types' - default value of the generated
  165. `guix-hydra-ENTRY-TYPE-search-types' variable.
  166. Optional keywords:
  167. - `:filters' - default value of the generated
  168. `guix-hydra-ENTRY-TYPE-filters' variable.
  169. - `:filter-names' - if specified, a generated
  170. `guix-hydra-ENTRY-TYPE-filter-names' function for filtering
  171. these names will be added to `guix-hydra-ENTRY-TYPE-filters'
  172. variable.
  173. - `:filter-boolean-params' - if specified, a generated
  174. `guix-hydra-ENTRY-TYPE-filter-boolean' function for filtering
  175. these names will be added to `guix-hydra-ENTRY-TYPE-filters'
  176. variable.
  177. The rest keyword arguments are passed to
  178. `bui-define-entry-type' macro."
  179. (declare (indent 1))
  180. (let* ((entry-type-str (symbol-name entry-type))
  181. (full-entry-type (guix-hydra-symbol entry-type))
  182. (prefix (concat "guix-hydra-" entry-type-str))
  183. (search-types-var (intern (concat prefix "-search-types")))
  184. (filters-var (intern (concat prefix "-filters")))
  185. (get-fun (intern (concat prefix "-get-entries"))))
  186. (bui-plist-let args
  187. ((search-types-val :search-types)
  188. (filters-val :filters)
  189. (filter-names-val :filter-names)
  190. (filter-bool-val :filter-boolean-params))
  191. `(progn
  192. (defvar ,search-types-var ,search-types-val
  193. ,(format "\
  194. Alist of search types and according URL functions.
  195. Functions are used to define URL to receive '%s' entries."
  196. entry-type-str))
  197. (defvar ,filters-var ,filters-val
  198. ,(format "\
  199. List of filters for '%s' parameters.
  200. Each filter is a function that should take an entry as a single
  201. argument, and should also return an entry."
  202. entry-type-str))
  203. ,(when filter-bool-val
  204. (let ((filter-bool-var (intern (concat prefix
  205. "-filter-boolean-params")))
  206. (filter-bool-fun (intern (concat prefix
  207. "-filter-boolean"))))
  208. `(progn
  209. (defvar ,filter-bool-var ,filter-bool-val
  210. ,(format "\
  211. List of '%s' parameters that should be transformed to boolean values."
  212. entry-type-str))
  213. (defun ,filter-bool-fun (entry)
  214. ,(format "\
  215. Run `guix-hydra-filter-boolean' with `%S' variable."
  216. filter-bool-var)
  217. (guix-hydra-filter-boolean entry ,filter-bool-var))
  218. (setq ,filters-var
  219. (cons ',filter-bool-fun ,filters-var)))))
  220. ;; Do not move this clause up!: name filtering should be
  221. ;; performed before any other filtering, so this filter should
  222. ;; be consed after the boolean filter.
  223. ,(when filter-names-val
  224. (let* ((filter-names-var (intern (concat prefix
  225. "-filter-names")))
  226. (filter-names-fun filter-names-var))
  227. `(progn
  228. (defvar ,filter-names-var ,filter-names-val
  229. ,(format "\
  230. Alist of '%s' parameter names returned by Hydra API and names
  231. used internally by the elisp code of this package."
  232. entry-type-str))
  233. (defun ,filter-names-fun (entry)
  234. ,(format "\
  235. Run `guix-hydra-filter-names' with `%S' variable."
  236. filter-names-var)
  237. (guix-hydra-filter-names entry ,filter-names-var))
  238. (setq ,filters-var
  239. (cons ',filter-names-fun ,filters-var)))))
  240. (defun ,get-fun (search-type &rest args)
  241. ,(format "\
  242. Receive '%s' entries.
  243. See `guix-hydra-get-entries' for details."
  244. entry-type-str)
  245. (apply #'guix-hydra-get-entries
  246. ',entry-type search-type args))
  247. (bui-define-groups ,full-entry-type
  248. :parent-group guix-hydra
  249. :parent-faces-group guix-hydra-faces)
  250. (bui-define-entry-type ,full-entry-type
  251. :message-function 'guix-hydra-message
  252. ,@%foreign-args)))))
  253. (defmacro guix-hydra-define-interface (entry-type buffer-type &rest args)
  254. "Define BUFFER-TYPE interface for displaying ENTRY-TYPE hydra entries.
  255. This macro should be called after calling
  256. `guix-hydra-define-entry-type' with the same ENTRY-TYPE.
  257. ARGS are passed to `bui-define-interface' macro."
  258. (declare (indent 2))
  259. `(bui-define-interface ,(guix-hydra-symbol entry-type) ,buffer-type
  260. :get-entries-function ',(guix-hydra-symbol entry-type 'get-entries)
  261. ,@args))
  262. (defvar guix-hydra-font-lock-keywords
  263. (eval-when-compile
  264. `((,(rx "(" (group (or "guix-hydra-define-entry-type"
  265. "guix-hydra-define-interface"))
  266. symbol-end)
  267. . 1))))
  268. (font-lock-add-keywords 'emacs-lisp-mode guix-hydra-font-lock-keywords)
  269. (provide 'guix-hydra)
  270. ;;; guix-hydra.el ends here