Line 0
Link Here
|
0 |
- |
1 |
;;; ebuild-run-mode.el --- major mode for Compilation buffers running ebuild commands -*- lexical-binding: t -*- |
|
|
2 |
|
3 |
;; Copyright 2006-2020 Gentoo Authors |
4 |
|
5 |
;; Author: Dima Akater <nuclearspace@gmail.com> |
6 |
;; Maintainer: <emacs@gentoo.org> |
7 |
;; Version: 1.51 |
8 |
;; Keywords: compilation, processes |
9 |
|
10 |
;; This file is free software: you can redistribute it and/or modify |
11 |
;; it under the terms of the GNU General Public License as published by |
12 |
;; the Free Software Foundation, either version 2 of the License, or |
13 |
;; (at your option) any later version. |
14 |
|
15 |
;; This file is distributed in the hope that it will be useful, |
16 |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 |
;; GNU General Public License for more details. |
19 |
|
20 |
;; You should have received a copy of the GNU General Public License |
21 |
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
22 |
|
23 |
;;; Commentary: |
24 |
|
25 |
;; This file should be ignored in app-xemacs/ebuild-mode ebuild |
26 |
;; as the feature is not supported in XEmacs |
27 |
|
28 |
;; Some issues: |
29 |
;; - A naming scheme is needed that would follow names outlined in |
30 |
;; man ebuild(5) while still looking good in Lisp code |
31 |
|
32 |
;;; Code: |
33 |
|
34 |
;;; Dependencies: |
35 |
|
36 |
(eval-when-compile (require 'cl-macs)) |
37 |
(eval-when-compile (require 'rx)) |
38 |
(require 'compile) |
39 |
(require 'tramp) |
40 |
|
41 |
|
42 |
;;; Macros: |
43 |
|
44 |
(defmacro ebuild-macs-expand-file-namef (place filename) |
45 |
"Update PLACE (a directory) to the full path to FILENAME, relative to PLACE. |
46 |
|
47 |
Useful in imperative algorithms involving movement across file system(s)." |
48 |
`(setf ,place (expand-file-name ,filename ,place))) |
49 |
|
50 |
(eval-and-compile |
51 |
(when (and (version<= "24.0" emacs-version) |
52 |
;; Strengthen these conditions as support for older Emacs drops. |
53 |
;; Eventually, this will turn into (when nil ..); |
54 |
;; then the whole `eval-and-compile' toplevel form can be dropped. |
55 |
(version<= emacs-version "25.3.1")) |
56 |
|
57 |
;; We include dependencies from future Emacs verbatim, |
58 |
;; namely from Emacs 28.0 |
59 |
|
60 |
(defvar gensym-counter 0 |
61 |
"Number used to construct the name of the next symbol created by `gensym'.") |
62 |
|
63 |
(defun gensym (&optional prefix) |
64 |
"Return a new uninterned symbol. |
65 |
The name is made by appending `gensym-counter' to PREFIX. |
66 |
PREFIX is a string, and defaults to \"g\"." |
67 |
(let ((num (prog1 gensym-counter |
68 |
(setq gensym-counter (1+ gensym-counter))))) |
69 |
(make-symbol (format "%s%d" (or prefix "g") num)))) |
70 |
|
71 |
(defun file-local-name (file) |
72 |
"Return the local name component of FILE. |
73 |
This function removes from FILE the specification of the remote host |
74 |
and the method of accessing the host, leaving only the part that |
75 |
identifies FILE locally on the remote system. |
76 |
The returned file name can be used directly as argument of |
77 |
`process-file', `start-file-process', or `shell-command'." |
78 |
(or (file-remote-p file 'localname) file)) |
79 |
|
80 |
(defun tramp-file-local-name (name) |
81 |
"Return the local name component of NAME. |
82 |
This function removes from NAME the specification of the remote |
83 |
host and the method of accessing the host, leaving only the part |
84 |
that identifies NAME locally on the remote system. If NAME does |
85 |
not match `tramp-file-name-regexp', just `file-local-name' is |
86 |
called. The returned file name can be used directly as argument |
87 |
of `process-file', `start-file-process', or `shell-command'." |
88 |
(or (and (tramp-tramp-file-p name) |
89 |
(string-match (nth 0 tramp-file-name-structure) name) |
90 |
(match-string (nth 4 tramp-file-name-structure) name)) |
91 |
(tramp-compat-file-local-name name))) |
92 |
|
93 |
(defalias 'tramp-compat-file-local-name 'file-local-name))) |
94 |
|
95 |
(defmacro with-file-buffer/ebuild-macs (filename &rest body) |
96 |
"Visit FILENAME unless already visited. Set the buffer as current, |
97 |
evaluate BODY forms. Kill the buffer if it did not exist initially." |
98 |
;; We want a with- name but we also need ebuild- namespace, |
99 |
;; and with-ebuild-macs-file-buffer is ugly |
100 |
;; while with-ebuild-file-buffer is misleading |
101 |
(declare (indent 1)) |
102 |
(let ((o-o-filename (gensym "filename-")) |
103 |
(existing-buffer-g (gensym "existing-buffer-")) |
104 |
(buffer-g (gensym "buffer-"))) |
105 |
`(let* ((,o-o-filename ,filename) |
106 |
(,existing-buffer-g (get-file-buffer ,o-o-filename)) |
107 |
(,buffer-g (or ,existing-buffer-g (find-file ,o-o-filename)))) |
108 |
(unwind-protect (with-current-buffer ,buffer-g ,@body) |
109 |
(unless ,existing-buffer-g (kill-buffer ,buffer-g)))))) |
110 |
|
111 |
(defmacro with-ebuild-compilation-buffer (buffer &rest body) |
112 |
"Evaluate BODY forms in buffer BUFFER, presumbaly compilation-mode buffer |
113 |
where ebuild command is being executed. The following variables are captured: |
114 |
- COMMAND as is, e.g. ebuild frobnicate-99999999.ebuild ‹command› |
115 |
- FILENAME passed to COMMAND as the first argument |
116 |
- PACKAGE-VERSION: counterpart of ebuild's ${PV}, e.g. frobnicate-99999999 |
117 |
- EXPANDED-FILENAME: full path to FILENAME |
118 |
- PORTAGE-HOME/CATEGORY/PV: full path to PACKAGE-VERSION dir in Portage build |
119 |
directory." |
120 |
(declare (indent 1)) |
121 |
`(with-current-buffer ,buffer |
122 |
(let ((command (car compilation-arguments))) |
123 |
(if (string-match (rx line-start |
124 |
"ebuild" (one-or-more whitespace) |
125 |
(group (one-or-more (not whitespace)))) |
126 |
command) |
127 |
(let* ((filename (match-string-no-properties 1 command)) |
128 |
(package-version (file-name-base filename)) |
129 |
(expanded-filename (expand-file-name filename)) |
130 |
(category (file-name-base |
131 |
(ebuild-util-up-directory expanded-filename 2))) |
132 |
(portage-home/category/pv |
133 |
(ebuild-util-file-name-join |
134 |
(ebuild-run-mode-tramp-portage-homedir) |
135 |
(list category package-version)))) |
136 |
;; Arguably, it would be convenient to also provide access |
137 |
;; to environment file and maybe other objects. |
138 |
;; Things to consider prior to doing this: |
139 |
;; - naming conventions: we should follow Gentoo naming scheme |
140 |
;; but this can be done in different ways, e.g., we could use |
141 |
;; elisp symbol `pv', or `PV', or `$PV', or `$pv'. |
142 |
;; Or we could have them all and give them different meaning |
143 |
;; - permissions burden: depending on the method for determining |
144 |
;; relevant values, we might need to access Portage environment |
145 |
;; via tramp; such events should be isolated, at least, |
146 |
;; and minimized, at most. |
147 |
,@body) |
148 |
(error "Does not look like ebuild command in %s; maybe `compilation-arguments' value is broken" (current-buffer)))))) |
149 |
|
150 |
|
151 |
;;; Utilities: |
152 |
|
153 |
(defun ebuild-util-file-name-join (root names) |
154 |
"Join file name. |
155 |
|
156 |
Examples: |
157 |
|
158 |
ELISP> (ebuild-util-file-name-join \"/a/b\" '(\"c\" \"d\")) |
159 |
\"/a/b/c/d\" |
160 |
|
161 |
ELISP> (ebuild-util-file-name-join \"/a/b\" '(\"c\" \"d/\")) |
162 |
\"/a/b/c/d/\" |
163 |
|
164 |
ELISP> (ebuild-util-file-name-join \"/a/b\" '(\"c/\" \"d\")) |
165 |
\"/a/b/c/d\" |
166 |
|
167 |
ELISP> (ebuild-util-file-name-join \"/a/b/\" '(\"c\" \"d\")) |
168 |
\"/a/b/c/d\"" |
169 |
(dolist (name names root) (ebuild-macs-expand-file-namef root name))) |
170 |
|
171 |
(defun ebuild-util-up-directory (root &optional n) |
172 |
"Return file name of directory N (1 by default) times upwards the directory |
173 |
tree. |
174 |
|
175 |
File name is returned without trailing slash so that `file-name-base' works |
176 |
with it hassle-free. |
177 |
|
178 |
Examples: |
179 |
|
180 |
ELISP> (ebuild-util-up-directory \"/a/b/c/d\") |
181 |
\"/a/b/c\" |
182 |
|
183 |
ELISP> (ebuild-util-up-directory \"/a/b/c/d\" 2) |
184 |
\"/a/b\" |
185 |
|
186 |
ELISP> (ebuild-util-up-directory \"/a/b/c/d\" 3) |
187 |
\"/a\"" |
188 |
(cl-loop repeat (or n 1) do (ebuild-macs-expand-file-namef root "..")) |
189 |
root) |
190 |
|
191 |
(defun ebuild-util-maybe-remove-quotes (string) |
192 |
"Remove quotes at the beginning and at the end of STRING, if present." |
193 |
(let ((length (length string)) long-enough first-quote-p) |
194 |
(cl-symbol-macrolet ((last-quote-p |
195 |
(char-equal ?\" (aref string (1- length))))) |
196 |
(if (and (setq long-enough (> length 1)) |
197 |
(setq first-quote-p (char-equal ?\" (aref string 0))) |
198 |
last-quote-p) |
199 |
(substring string 1 -1) |
200 |
(when long-enough |
201 |
(warn "Missing %s quote in string “%s”" |
202 |
(if first-quote-p 'trailing 'leading) string)) |
203 |
string)))) |
204 |
|
205 |
|
206 |
;;; Main part: |
207 |
|
208 |
(defun ebuild-run-mode-tramp-prefix () |
209 |
"In `ebuild-run-mode', return tramp prefix necessary to access build files |
210 |
and portage cache, as seen in `default-directory' of the buffer." |
211 |
(cl-ecase major-mode |
212 |
(ebuild-run-mode |
213 |
(substring-no-properties default-directory |
214 |
0 (- (length (tramp-file-local-name |
215 |
default-directory))))))) |
216 |
|
217 |
(defvar ebuild-run-mode-portage-homedir "/var/tmp/portage/" |
218 |
"Absolute path to main portage build directory.") |
219 |
|
220 |
(defun ebuild-run-mode-tramp-portage-homedir () |
221 |
"Path to main portage build directory, with all tramp prefixes necessary |
222 |
to access build files." |
223 |
(concat (ebuild-run-mode-tramp-prefix) ebuild-run-mode-portage-homedir)) |
224 |
|
225 |
(defun ebuild-run-mode-tramp-default-current-sourcedir (&optional buffer) |
226 |
"For buffer BUFFER where ebuild command is being executed, return |
227 |
corresponding default temporary build directory (default value for ${S}, as |
228 |
specified e.g. in man ebuild(5)), derived from `compilation-arguments' of |
229 |
BUFFER. |
230 |
|
231 |
The retuned directory name is prefixed with appropriate `tramp' prefix." |
232 |
(with-ebuild-compilation-buffer (or buffer (current-buffer)) |
233 |
(file-name-as-directory |
234 |
(ebuild-util-file-name-join portage-home/category/pv |
235 |
(list "work" package-version))))) |
236 |
|
237 |
(defun ebuild-run-mode-tramp-current-sourcedir (&optional buffer) |
238 |
"For buffer BUFFER where ebuild command is being executed, return |
239 |
corresponding temporary build directory (counterpart of ${S}), derived from |
240 |
`compilation-arguments' of BUFFER. |
241 |
|
242 |
The returned directory name is prefixed with appropriate `tramp' prefix." |
243 |
(with-ebuild-compilation-buffer (or buffer (current-buffer)) |
244 |
(file-name-as-directory |
245 |
(concat |
246 |
(ebuild-run-mode-tramp-prefix) |
247 |
(ebuild-util-maybe-remove-quotes |
248 |
(let ((env-file |
249 |
(ebuild-util-file-name-join portage-home/category/pv |
250 |
(list "temp" |
251 |
"environment")))) |
252 |
(with-file-buffer/ebuild-macs env-file |
253 |
(save-excursion |
254 |
(goto-char (point-min)) |
255 |
(re-search-forward |
256 |
(rx line-start "declare -x S=" |
257 |
(group (one-or-more (not whitespace))) |
258 |
line-end))) |
259 |
(match-string-no-properties 1)))))))) |
260 |
|
261 |
(defun ebuild-run-mode-goto-error () |
262 |
"Visit the source for the error message at point. |
263 |
Use this command in `ebuild-run-mode' buffers." |
264 |
(interactive) |
265 |
(let ((default-directory |
266 |
(or (ebuild-run-mode-tramp-current-sourcedir) |
267 |
(progn |
268 |
(warn "Current Portage Sourcedir is unknown.") |
269 |
(ebuild-run-mode-tramp-default-current-sourcedir))))) |
270 |
(compile-goto-error))) |
271 |
|
272 |
(defvar ebuild-run-font-lock-keywords |
273 |
'(("^Ebuild \\(finished\\).*" |
274 |
(0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) |
275 |
(1 compilation-info-face)) |
276 |
("^>>> Source \\(compiled\\).*" |
277 |
(0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) |
278 |
(1 compilation-info-face)) |
279 |
("^Ebuild \\(exited abnormally\\|interrupt\\|killed\\|terminated\\|segmentation fault\\)\\(?:.*with code \\([0-9]+\\)\\)?.*" |
280 |
(0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) |
281 |
(1 compilation-error-face) |
282 |
(2 compilation-error-face nil t))) |
283 |
"Substrings to highlight in `ebuild-run-mode'.") |
284 |
|
285 |
(eval-and-compile |
286 |
(when (version= "27.1" emacs-version) |
287 |
(defvar ebuild-run-mode nil "Set to t when `ebuild-run-mode' is enabled."))) |
288 |
|
289 |
(define-compilation-mode ebuild-run-mode "Ebuild" |
290 |
"Major mode for non-interactive buffers spawned by `ebuild-run-command'." |
291 |
(setq buffer-read-only t) |
292 |
(font-lock-add-keywords nil ebuild-run-font-lock-keywords) |
293 |
(setq-local ebuild-run-mode t)) |
294 |
|
295 |
(define-key ebuild-run-mode-map [remap compile-goto-error] |
296 |
#'ebuild-run-mode-goto-error) |
297 |
|
298 |
(provide 'ebuild-run-mode) |
299 |
|
300 |
;;; ebuild-run-mode.el ends here |