mirror of
https://git.in.rschanz.org/ryan77627/guix.git
synced 2024-12-24 21:38:07 -05:00
364 lines
13 KiB
EmacsLisp
364 lines
13 KiB
EmacsLisp
|
;;; guix-hydra.el --- Common code for interacting with Hydra -*- lexical-binding: t -*-
|
|||
|
|
|||
|
;; Copyright © 2015 Alex Kost <alezost@gmail.com>
|
|||
|
|
|||
|
;; This file is part of GNU Guix.
|
|||
|
|
|||
|
;; GNU Guix is free software; you can redistribute it and/or modify
|
|||
|
;; it under the terms of the GNU General Public License as published by
|
|||
|
;; the Free Software Foundation, either version 3 of the License, or
|
|||
|
;; (at your option) any later version.
|
|||
|
|
|||
|
;; GNU Guix is distributed in the hope that it will be useful,
|
|||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
;; GNU General Public License for more details.
|
|||
|
|
|||
|
;; You should have received a copy of the GNU General Public License
|
|||
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|
|||
|
;;; Commentary:
|
|||
|
|
|||
|
;; This file provides some general code for 'list'/'info' interfaces for
|
|||
|
;; Hydra (Guix build farm).
|
|||
|
|
|||
|
;;; Code:
|
|||
|
|
|||
|
(require 'json)
|
|||
|
(require 'guix-buffer)
|
|||
|
(require 'guix-entry)
|
|||
|
(require 'guix-utils)
|
|||
|
(require 'guix-help-vars)
|
|||
|
|
|||
|
(guix-define-groups hydra)
|
|||
|
|
|||
|
(defvar guix-hydra-job-regexp
|
|||
|
(concat ".*\\." (regexp-opt guix-help-system-types) "\\'")
|
|||
|
"Regexp matching a full name of Hydra job (including system).")
|
|||
|
|
|||
|
(defun guix-hydra-message (entries search-type &rest _)
|
|||
|
"Display a message after showing Hydra ENTRIES."
|
|||
|
;; XXX Add more messages maybe.
|
|||
|
(when (null entries)
|
|||
|
(if (eq search-type 'fake)
|
|||
|
(message "The update is impossible due to lack of Hydra API.")
|
|||
|
(message "Hydra has returned no results."))))
|
|||
|
|
|||
|
(defun guix-hydra-list-describe (ids)
|
|||
|
"Describe 'hydra' entries with IDS (list of identifiers)."
|
|||
|
(guix-buffer-display-entries
|
|||
|
(guix-entries-by-ids ids (guix-buffer-current-entries))
|
|||
|
'info (guix-buffer-current-entry-type)
|
|||
|
;; Hydra does not provide an API to receive builds/jobsets by
|
|||
|
;; IDs/names, so we use a 'fake' search type.
|
|||
|
'(fake)
|
|||
|
'add))
|
|||
|
|
|||
|
|
|||
|
;;; Readers
|
|||
|
|
|||
|
(defvar guix-hydra-projects
|
|||
|
'("gnu" "guix")
|
|||
|
"List of available Hydra projects.")
|
|||
|
|
|||
|
(guix-define-readers
|
|||
|
:completions-var guix-hydra-projects
|
|||
|
:single-reader guix-hydra-read-project
|
|||
|
:single-prompt "Project: ")
|
|||
|
|
|||
|
(guix-define-readers
|
|||
|
:single-reader guix-hydra-read-jobset
|
|||
|
:single-prompt "Jobset: ")
|
|||
|
|
|||
|
(guix-define-readers
|
|||
|
:single-reader guix-hydra-read-job
|
|||
|
:single-prompt "Job: ")
|
|||
|
|
|||
|
(guix-define-readers
|
|||
|
:completions-var guix-help-system-types
|
|||
|
:single-reader guix-hydra-read-system
|
|||
|
:single-prompt "System: ")
|
|||
|
|
|||
|
|
|||
|
;;; Defining URLs
|
|||
|
|
|||
|
(defvar guix-hydra-url "http://hydra.gnu.org"
|
|||
|
"URL of the Hydra build farm.")
|
|||
|
|
|||
|
(defun guix-hydra-url (&rest url-parts)
|
|||
|
"Return Hydra URL."
|
|||
|
(apply #'concat guix-hydra-url "/" url-parts))
|
|||
|
|
|||
|
(defun guix-hydra-api-url (type args)
|
|||
|
"Return URL for receiving data using Hydra API.
|
|||
|
TYPE is the name of an allowed method.
|
|||
|
ARGS is alist of (KEY . VALUE) pairs.
|
|||
|
Skip ARG, if VALUE is nil or an empty string."
|
|||
|
(declare (indent 1))
|
|||
|
(let* ((fields (mapcar
|
|||
|
(lambda (arg)
|
|||
|
(pcase arg
|
|||
|
(`(,key . ,value)
|
|||
|
(unless (or (null value)
|
|||
|
(equal "" value))
|
|||
|
(concat (guix-hexify key) "="
|
|||
|
(guix-hexify value))))
|
|||
|
(_ (error "Wrong argument '%s'" arg))))
|
|||
|
args))
|
|||
|
(fields (mapconcat #'identity (delq nil fields) "&")))
|
|||
|
(guix-hydra-url "api/" type "?" fields)))
|
|||
|
|
|||
|
|
|||
|
;;; Receiving data from Hydra
|
|||
|
|
|||
|
(defun guix-hydra-receive-data (url)
|
|||
|
"Return output received from URL and processed with `json-read'."
|
|||
|
(with-temp-buffer
|
|||
|
(url-insert-file-contents url)
|
|||
|
(goto-char (point-min))
|
|||
|
(let ((json-key-type 'symbol)
|
|||
|
(json-array-type 'list)
|
|||
|
(json-object-type 'alist))
|
|||
|
(json-read))))
|
|||
|
|
|||
|
(defun guix-hydra-get-entries (entry-type search-type &rest args)
|
|||
|
"Receive ENTRY-TYPE entries from Hydra.
|
|||
|
SEARCH-TYPE is one of the types defined by `guix-hydra-define-interface'."
|
|||
|
(unless (eq search-type 'fake)
|
|||
|
(let* ((url (apply #'guix-hydra-search-url
|
|||
|
entry-type search-type args))
|
|||
|
(raw-entries (guix-hydra-receive-data url))
|
|||
|
(entries (guix-hydra-filter-entries
|
|||
|
raw-entries
|
|||
|
(guix-hydra-filters entry-type))))
|
|||
|
entries)))
|
|||
|
|
|||
|
|
|||
|
;;; Filters for processing raw entries
|
|||
|
|
|||
|
(defun guix-hydra-filter-entries (entries filters)
|
|||
|
"Filter ENTRIES using FILTERS.
|
|||
|
Call `guix-modify' on each entry from ENTRIES."
|
|||
|
(mapcar (lambda (entry)
|
|||
|
(guix-modify entry filters))
|
|||
|
entries))
|
|||
|
|
|||
|
(defun guix-hydra-filter-names (entry name-alist)
|
|||
|
"Replace names of ENTRY parameters using NAME-ALIST.
|
|||
|
Each element of NAME-ALIST is (OLD-NAME . NEW-NAME) pair."
|
|||
|
(mapcar (lambda (param)
|
|||
|
(pcase param
|
|||
|
(`(,name . ,val)
|
|||
|
(let ((new-name (guix-assq-value name-alist name)))
|
|||
|
(if new-name
|
|||
|
(cons new-name val)
|
|||
|
param)))))
|
|||
|
entry))
|
|||
|
|
|||
|
(defun guix-hydra-filter-boolean (entry params)
|
|||
|
"Convert number PARAMS (0/1) of ENTRY to boolean values (nil/t)."
|
|||
|
(mapcar (lambda (param)
|
|||
|
(pcase param
|
|||
|
(`(,name . ,val)
|
|||
|
(if (memq name params)
|
|||
|
(cons name (guix-number->bool val))
|
|||
|
param))))
|
|||
|
entry))
|
|||
|
|
|||
|
|
|||
|
;;; Wrappers for defined variables
|
|||
|
|
|||
|
(defvar guix-hydra-entry-type-data nil
|
|||
|
"Alist with hydra entry type data.
|
|||
|
This alist is filled by `guix-hydra-define-entry-type' macro.")
|
|||
|
|
|||
|
(defun guix-hydra-entry-type-value (entry-type symbol)
|
|||
|
"Return SYMBOL's value for ENTRY-TYPE from `guix-hydra'."
|
|||
|
(symbol-value (guix-assq-value guix-hydra-entry-type-data
|
|||
|
entry-type symbol)))
|
|||
|
|
|||
|
(defun guix-hydra-search-url (entry-type search-type &rest args)
|
|||
|
"Return URL to receive ENTRY-TYPE entries from Hydra."
|
|||
|
(apply (guix-assq-value (guix-hydra-entry-type-value
|
|||
|
entry-type 'search-types)
|
|||
|
search-type)
|
|||
|
args))
|
|||
|
|
|||
|
(defun guix-hydra-filters (entry-type)
|
|||
|
"Return a list of filters for ENTRY-TYPE."
|
|||
|
(guix-hydra-entry-type-value entry-type 'filters))
|
|||
|
|
|||
|
|
|||
|
;;; Interface definers
|
|||
|
|
|||
|
(defmacro guix-hydra-define-entry-type (entry-type &rest args)
|
|||
|
"Define general code for ENTRY-TYPE.
|
|||
|
Remaining arguments (ARGS) should have a form [KEYWORD VALUE] ...
|
|||
|
|
|||
|
Required keywords:
|
|||
|
|
|||
|
- `:search-types' - default value of the generated
|
|||
|
`guix-ENTRY-TYPE-search-types' variable.
|
|||
|
|
|||
|
Optional keywords:
|
|||
|
|
|||
|
- `:filters' - default value of the generated
|
|||
|
`guix-ENTRY-TYPE-filters' variable.
|
|||
|
|
|||
|
- `:filter-names' - if specified, a generated
|
|||
|
`guix-ENTRY-TYPE-filter-names' function for filtering these
|
|||
|
names will be added to `guix-ENTRY-TYPE-filters' variable.
|
|||
|
|
|||
|
- `:filter-boolean-params' - if specified, a generated
|
|||
|
`guix-ENTRY-TYPE-filter-boolean' function for filtering these
|
|||
|
names will be added to `guix-ENTRY-TYPE-filters' variable.
|
|||
|
|
|||
|
The rest keyword arguments are passed to
|
|||
|
`guix-define-entry-type' macro."
|
|||
|
(declare (indent 1))
|
|||
|
(let* ((entry-type-str (symbol-name entry-type))
|
|||
|
(prefix (concat "guix-" entry-type-str))
|
|||
|
(search-types-var (intern (concat prefix "-search-types")))
|
|||
|
(filters-var (intern (concat prefix "-filters")))
|
|||
|
(get-fun (intern (concat prefix "-get-entries"))))
|
|||
|
(guix-keyword-args-let args
|
|||
|
((search-types-val :search-types)
|
|||
|
(filters-val :filters)
|
|||
|
(filter-names-val :filter-names)
|
|||
|
(filter-bool-val :filter-boolean-params))
|
|||
|
`(progn
|
|||
|
(defvar ,search-types-var ,search-types-val
|
|||
|
,(format "\
|
|||
|
Alist of search types and according URL functions.
|
|||
|
Functions are used to define URL to receive '%s' entries."
|
|||
|
entry-type-str))
|
|||
|
|
|||
|
(defvar ,filters-var ,filters-val
|
|||
|
,(format "\
|
|||
|
List of filters for '%s' parameters.
|
|||
|
Each filter is a function that should take an entry as a single
|
|||
|
argument, and should also return an entry."
|
|||
|
entry-type-str))
|
|||
|
|
|||
|
,(when filter-bool-val
|
|||
|
(let ((filter-bool-var (intern (concat prefix
|
|||
|
"-filter-boolean-params")))
|
|||
|
(filter-bool-fun (intern (concat prefix
|
|||
|
"-filter-boolean"))))
|
|||
|
`(progn
|
|||
|
(defvar ,filter-bool-var ,filter-bool-val
|
|||
|
,(format "\
|
|||
|
List of '%s' parameters that should be transformed to boolean values."
|
|||
|
entry-type-str))
|
|||
|
|
|||
|
(defun ,filter-bool-fun (entry)
|
|||
|
,(format "\
|
|||
|
Run `guix-hydra-filter-boolean' with `%S' variable."
|
|||
|
filter-bool-var)
|
|||
|
(guix-hydra-filter-boolean entry ,filter-bool-var))
|
|||
|
|
|||
|
(setq ,filters-var
|
|||
|
(cons ',filter-bool-fun ,filters-var)))))
|
|||
|
|
|||
|
;; Do not move this clause up!: name filtering should be
|
|||
|
;; performed before any other filtering, so this filter should
|
|||
|
;; be consed after the boolean filter.
|
|||
|
,(when filter-names-val
|
|||
|
(let* ((filter-names-var (intern (concat prefix
|
|||
|
"-filter-names")))
|
|||
|
(filter-names-fun filter-names-var))
|
|||
|
`(progn
|
|||
|
(defvar ,filter-names-var ,filter-names-val
|
|||
|
,(format "\
|
|||
|
Alist of '%s' parameter names returned by Hydra API and names
|
|||
|
used internally by the elisp code of this package."
|
|||
|
entry-type-str))
|
|||
|
|
|||
|
(defun ,filter-names-fun (entry)
|
|||
|
,(format "\
|
|||
|
Run `guix-hydra-filter-names' with `%S' variable."
|
|||
|
filter-names-var)
|
|||
|
(guix-hydra-filter-names entry ,filter-names-var))
|
|||
|
|
|||
|
(setq ,filters-var
|
|||
|
(cons ',filter-names-fun ,filters-var)))))
|
|||
|
|
|||
|
(defun ,get-fun (search-type &rest args)
|
|||
|
,(format "\
|
|||
|
Receive '%s' entries.
|
|||
|
See `guix-hydra-get-entries' for details."
|
|||
|
entry-type-str)
|
|||
|
(apply #'guix-hydra-get-entries
|
|||
|
',entry-type search-type args))
|
|||
|
|
|||
|
(guix-alist-put!
|
|||
|
'((search-types . ,search-types-var)
|
|||
|
(filters . ,filters-var))
|
|||
|
'guix-hydra-entry-type-data ',entry-type)
|
|||
|
|
|||
|
(guix-define-entry-type ,entry-type
|
|||
|
:parent-group guix-hydra
|
|||
|
:parent-faces-group guix-hydra-faces
|
|||
|
,@%foreign-args)))))
|
|||
|
|
|||
|
(defmacro guix-hydra-define-interface (buffer-type entry-type &rest args)
|
|||
|
"Define BUFFER-TYPE interface for displaying ENTRY-TYPE entries.
|
|||
|
|
|||
|
This macro should be called after calling
|
|||
|
`guix-hydra-define-entry-type' with the same ENTRY-TYPE.
|
|||
|
|
|||
|
ARGS are passed to `guix-BUFFER-TYPE-define-interface' macro."
|
|||
|
(declare (indent 2))
|
|||
|
(let* ((entry-type-str (symbol-name entry-type))
|
|||
|
(buffer-type-str (symbol-name buffer-type))
|
|||
|
(get-fun (intern (concat "guix-" entry-type-str
|
|||
|
"-get-entries")))
|
|||
|
(definer (intern (concat "guix-" buffer-type-str
|
|||
|
"-define-interface"))))
|
|||
|
`(,definer ,entry-type
|
|||
|
:get-entries-function ',get-fun
|
|||
|
:message-function 'guix-hydra-message
|
|||
|
,@args)))
|
|||
|
|
|||
|
(defmacro guix-hydra-info-define-interface (entry-type &rest args)
|
|||
|
"Define 'info' interface for displaying ENTRY-TYPE entries.
|
|||
|
See `guix-hydra-define-interface'."
|
|||
|
(declare (indent 1))
|
|||
|
`(guix-hydra-define-interface info ,entry-type
|
|||
|
,@args))
|
|||
|
|
|||
|
(defmacro guix-hydra-list-define-interface (entry-type &rest args)
|
|||
|
"Define 'list' interface for displaying ENTRY-TYPE entries.
|
|||
|
Remaining arguments (ARGS) should have a form [KEYWORD VALUE] ...
|
|||
|
|
|||
|
Optional keywords:
|
|||
|
|
|||
|
- `:describe-function' - default value of the generated
|
|||
|
`guix-ENTRY-TYPE-list-describe-function' variable (if not
|
|||
|
specified, use `guix-hydra-list-describe').
|
|||
|
|
|||
|
The rest keyword arguments are passed to
|
|||
|
`guix-hydra-define-interface' macro."
|
|||
|
(declare (indent 1))
|
|||
|
(guix-keyword-args-let args
|
|||
|
((describe-val :describe-function))
|
|||
|
`(guix-hydra-define-interface list ,entry-type
|
|||
|
:describe-function ,(or describe-val ''guix-hydra-list-describe)
|
|||
|
,@args)))
|
|||
|
|
|||
|
|
|||
|
(defvar guix-hydra-font-lock-keywords
|
|||
|
(eval-when-compile
|
|||
|
`((,(rx "(" (group (or "guix-hydra-define-entry-type"
|
|||
|
"guix-hydra-define-interface"
|
|||
|
"guix-hydra-info-define-interface"
|
|||
|
"guix-hydra-list-define-interface"))
|
|||
|
symbol-end)
|
|||
|
. 1))))
|
|||
|
|
|||
|
(font-lock-add-keywords 'emacs-lisp-mode guix-hydra-font-lock-keywords)
|
|||
|
|
|||
|
(provide 'guix-hydra)
|
|||
|
|
|||
|
;;; guix-hydra.el ends here
|