build-farm-url.el 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. ;;; build-farm-url.el --- Build farm URLs -*- lexical-binding: t -*-
  2. ;; Copyright © 2015–2018 Alex Kost <alezost@gmail.com>
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;;
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; This file provides the code to determine various URLs of the build
  17. ;; farms and to receive data from them.
  18. ;;; Code:
  19. (require 'url-handlers)
  20. (require 'url-expand)
  21. (require 'json)
  22. (require 'build-farm-utils)
  23. (eval-when-compile (require 'subr-x))
  24. (defvar build-farm-url-alist
  25. '(("https://hydra.nixos.org" . hydra)
  26. ("https://hydra.gnu.org" . hydra)
  27. ("https://ci.guix.info" . cuirass)
  28. ("https://berlin.guixsd.org" . cuirass))
  29. "Alist of URLs and their types of the available build farms.")
  30. (defun build-farm-guess-url ()
  31. "Return URL of a build farm that a user probably wants to use."
  32. (if (eq 'guix build-farm-preferred-package-manager)
  33. "https://ci.guix.info"
  34. "https://hydra.nixos.org"))
  35. (defun build-farm-urls ()
  36. "Return a list of available build farm URLs."
  37. (mapcar #'car build-farm-url-alist))
  38. (defcustom build-farm-url (build-farm-guess-url)
  39. "URL of the default build farm."
  40. :type `(choice ,@(mapcar (lambda (url) (list 'const url))
  41. (build-farm-urls))
  42. (string :tag "Other URL"))
  43. :group 'build-farm)
  44. (defun build-farm-read-url ()
  45. "Read from minibuffer and return build farm URL."
  46. (completing-read "Build farm URL: "
  47. (build-farm-urls)
  48. nil nil nil nil
  49. build-farm-url))
  50. ;;;###autoload
  51. (defun build-farm-set-url (url)
  52. "Set variable `build-farm-url' to URL.
  53. Interactively, prompt for URL."
  54. (interactive (list (build-farm-read-url)))
  55. (setq build-farm-url url))
  56. (defun build-farm-url-type (&optional url)
  57. "Return build farm type by its URL.
  58. If URL is nil, use variable `build-farm-url'."
  59. (or (bui-assoc-value build-farm-url-alist
  60. (or url build-farm-url))
  61. (let ((type (if (string-match-p "cuirass" url)
  62. 'cuirass
  63. 'hydra)))
  64. (message "Unknown URL: <%s>.
  65. Consider adding it to `build-farm-url-alist'.
  66. Arbitrarily choosing `%S' type for this URL."
  67. url type)
  68. type)))
  69. (defun build-farm-url-package-manager (&optional url)
  70. "Return a package manager for the build farm URL.
  71. The returned value is either `nix' or `guix' symbols or nil, if
  72. the package manager cannot be determined.
  73. If URL is nil, use variable `build-farm-url'."
  74. (or url (setq url build-farm-url))
  75. (cond ((or (string-match-p (regexp-opt '("gnu" "guix")) url)
  76. (eq 'cuirass (build-farm-url-type url)))
  77. 'guix)
  78. ((string-match-p "nix" url)
  79. 'nix)))
  80. (defun build-farm-url (&optional root-url &rest url-parts)
  81. "Return build farm ROOT-URL with URL-PARTS concatenated to it.
  82. If ROOT-URL is nil, use variable `build-farm-url'."
  83. (url-expand-file-name (mapconcat #'identity url-parts "")
  84. (or root-url build-farm-url)))
  85. (cl-defun build-farm-api-url (type args &key root-url)
  86. "Return URL for receiving data using build farm API.
  87. See function `build-farm-url' for the meaning of ROOT-URL.
  88. TYPE is the name of an allowed method.
  89. ARGS is alist of (KEY . VALUE) pairs.
  90. Skip ARG, if VALUE is nil or an empty string."
  91. (let* ((fields (mapcar
  92. (lambda (arg)
  93. (pcase arg
  94. (`(,key . ,value)
  95. (unless (or (null value)
  96. (equal "" value))
  97. (concat (build-farm-hexify key) "="
  98. (build-farm-hexify value))))
  99. (_ (error "Wrong argument '%s'" arg))))
  100. args))
  101. (fields (mapconcat #'identity (delq nil fields) "&")))
  102. (build-farm-url root-url "api/" type "?" fields)))
  103. (cl-defun build-farm-build-url (id &key root-url)
  104. "Return URL of a build ID.
  105. See function `build-farm-url' for the meaning of ROOT-URL."
  106. (build-farm-url root-url "build/" (number-to-string id)))
  107. (cl-defun build-farm-build-log-url (id &key root-url raw)
  108. "Return URL of the build log of a build ID.
  109. If RAW is non-nil, return url of the raw build log file.
  110. See function `build-farm-url' for the meaning of ROOT-URL."
  111. (concat (build-farm-build-url id :root-url root-url)
  112. "/log"
  113. (if raw "/raw" "")))
  114. (cl-defun build-farm-build-latest-api-url
  115. (number &key root-url project jobset job system)
  116. "Return API URL to receive latest NUMBER of builds.
  117. See function `build-farm-url' for the meaning of ROOT-URL."
  118. (build-farm-api-url
  119. "latestbuilds"
  120. `(("nr" . ,number)
  121. ("project" . ,project)
  122. ("jobset" . ,jobset)
  123. ("job" . ,job)
  124. ("system" . ,system))
  125. :root-url root-url))
  126. (cl-defun build-farm-build-queue-api-url (number &key root-url)
  127. "Return API URL to receive the NUMBER of queued builds.
  128. See function `build-farm-url' for the meaning of ROOT-URL."
  129. (build-farm-api-url
  130. "queue"
  131. `(("nr" . ,number))
  132. :root-url root-url))
  133. (cl-defun build-farm-jobset-url (&key root-url project jobset jobset-id)
  134. "Return URL of a build farm JOBSET.
  135. For Cuirass farm, you should not use PROJECT, so you can specify
  136. either JOBSET or JOBSET-ID.
  137. For Hydra farm, you should specify either a single JOBSET-ID
  138. argument (it should have a form 'project/jobset') or PROJECT and
  139. JOBSET arguments.
  140. See function `build-farm-url' for the meaning of ROOT-URL."
  141. (build-farm-url root-url "/jobset/"
  142. (if project
  143. (concat project "/" jobset)
  144. ;; JOBSET-ID for Cuirass contains leading "/".
  145. (or (string-trim-left jobset-id "/")
  146. jobset))))
  147. (cl-defun build-farm-hydra-jobset-api-url (project &key root-url)
  148. "Return API URL for Hydra jobsets by PROJECT.
  149. See function `build-farm-url' for the meaning of ROOT-URL."
  150. (build-farm-api-url
  151. "jobsets"
  152. `(("project" . ,project))
  153. :root-url root-url))
  154. (cl-defun build-farm-cuirass-jobsets-url (&key root-url)
  155. "Return URL with all Cuirass JOBSETS.
  156. See function `build-farm-url' for the meaning of ROOT-URL."
  157. (build-farm-url root-url "/jobsets"))
  158. (cl-defun build-farm-project-url (&key root-url project)
  159. "Return URL with build farm PROJECT.
  160. If PROJECT is nil, return URL with all projects.
  161. See function `build-farm-url' for the meaning of ROOT-URL."
  162. (if project
  163. (build-farm-url root-url "project/" project)
  164. (build-farm-url root-url)))
  165. (cl-defun build-farm-evaluation-url (&key root-url evaluation)
  166. "Return URL with build farm EVALUATION (number or string).
  167. See function `build-farm-url' for the meaning of ROOT-URL."
  168. (build-farm-url root-url "eval/"
  169. (if (stringp evaluation)
  170. evaluation
  171. (number-to-string evaluation))))
  172. (cl-defun build-farm-latest-evaluations-api-url (number &key root-url)
  173. "Return API URL to receive the NUMBER of latest evaluations.
  174. See function `build-farm-url' for the meaning of ROOT-URL."
  175. (build-farm-api-url
  176. "evaluations"
  177. `(("nr" . ,number))
  178. :root-url root-url))
  179. ;;; Receiving data from a build farm
  180. (defvar url-http-codes)
  181. (defun build-farm-retrieve-url (url)
  182. "Retrieve URL synchronously and return buffer containing the data.
  183. This function is similar to `url-retrieve-synchronously' but it
  184. also raises an error if URL has not been retrieved properly."
  185. ;; This code is taken from `url-insert-file-contents'.
  186. (let ((buffer (url-retrieve-synchronously url)))
  187. (unless buffer
  188. (signal 'file-error (list url "No Data")))
  189. (with-current-buffer buffer
  190. (when (bound-and-true-p url-http-response-status)
  191. (unless (and (>= url-http-response-status 200)
  192. (< url-http-response-status 300))
  193. (let ((desc (nth 2 (assq url-http-response-status
  194. url-http-codes))))
  195. (kill-buffer buffer)
  196. (signal 'file-error (list url desc))))))
  197. buffer))
  198. (defun build-farm-receive-data (url)
  199. "Return output received from URL and processed with `json-read'."
  200. (let* ((url-request-extra-headers '(("Accept" . "application/json")))
  201. (url-buffer (build-farm-retrieve-url url))
  202. (content-type (buffer-local-value 'url-http-content-type
  203. url-buffer)))
  204. ;; Do not use `string=' here because the content type may look like
  205. ;; this: "application/json;charset=utf-8".
  206. (unless (string-match-p "application/json" content-type)
  207. ;; Currently Cuirass does not support "Accept" extra header, so it
  208. ;; does not return json data from "non-api" URLs.
  209. (if (eq (build-farm-url-type) 'cuirass)
  210. (error "Sorry, Cuirass does not support this API")
  211. (error "\
  212. The server has not returned 'application/json' content type.
  213. Perhaps, API has changed:\n%s"
  214. url)))
  215. (with-temp-buffer
  216. (url-insert-buffer-contents url-buffer url)
  217. (goto-char (point-min))
  218. (let ((json-false nil) ; default value is `:json-false'
  219. (json-key-type 'symbol)
  220. (json-array-type 'list)
  221. (json-object-type 'alist))
  222. (json-read)))))
  223. (provide 'build-farm-url)
  224. ;;; build-farm-url.el ends here