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