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