From 1d07decd4b69af21af382d697e3b79e48efc23d1 Mon Sep 17 00:00:00 2001 From: akater <nuclearspace@gmail.com> Date: Mon, 21 Sep 2020 09:03:11 +0000 Subject: [PATCH] Provide ebuild-run-mode and employ it in ebuild-run-command Signed-off-by: Dmitrii Neskoromnyi <nuclearspace@gmail.com> --- ebuild-mode.el | 4 +- ebuild-run-mode.el | 361 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 ebuild-run-mode.el diff --git a/ebuild-mode.el b/ebuild-mode.el index ea61045..07e1da4 100644 --- a/ebuild-mode.el +++ b/ebuild-mode.el @@ -366,7 +366,9 @@ Optional argument LIMIT restarts collection after that number of elements." ;;(compilation-mode-hook (lambda () (setq truncate-lines t))) (compilation-buffer-name-function (list 'lambda '(mode) (concat "*ebuild " command "*")))) - (compile (format "ebuild %s %s" file command)))) + (let ((ebuild-command (format "ebuild %s %s" file command))) + (if (featurep 'xemacs) (compile ebuild-command) + (compile ebuild-command 'ebuild-run-mode))))) ;;; Modify package keywords. diff --git a/ebuild-run-mode.el b/ebuild-run-mode.el new file mode 100644 index 0000000..bb76d19 --- /dev/null +++ b/ebuild-run-mode.el @@ -0,0 +1,361 @@ +;;; ebuild-run-mode.el --- major mode for buffers running ebuild commands + +;; Copyright 2006-2021 Gentoo Authors + +;; Author: Dima Akater <nuclearspace@gmail.com> +;; Maintainer: <emacs@gentoo.org> +;; Keywords: compilation, processes + +;; This file 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 2 of the License, or +;; (at your option) any later version. + +;; This file 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file should be ignored in app-xemacs/ebuild-mode ebuild +;; as the feature is not supported in XEmacs. +;; +;; It is not difficult to support; the issue is, `ebuild-run-command' +;; is not working correctly in XEmacs via Tramp at the moment, +;; in the first place. +;; +;; Accessing the build environment normally requires Tramp, +;; we use it extensively here, and possible future features will likely +;; require it too. So no need to complicate the code until XEmacs supports +;; `ebuild-run-command' fully. + +;; This file is lexical-binding compatible. We do not set it because +;; - ebuild-mode rules demand lexical-binding: t to be set at the end +;; of the file which triggers a compiler warning +;; - lexical-binding: t is not necessary here + +;; More relevant issues: +;; - A naming scheme is needed that would follow names outlined in +;; man ebuild(5) while still looking good in Lisp code + +;;; Code: + +;;; Dependencies: + +(eval-when-compile + (defmacro emacs-version-between (min max) + `(and (<= ,min emacs-major-version) + ;; this can be replaced with a single <= form + ;; when support for Emacs 23 is dropped + ;; subsequently, + ;; emacs-version-between can be macroexpanded in-place everywhere + ;; into a single <= form and dropped too + (<= emacs-major-version ,max)))) + +(eval-when-compile + (if (<= 24 emacs-major-version) (require 'cl-macs) + (load "cl-macs") + (defalias 'cl-symbol-macrolet 'symbol-macrolet) + (defalias 'cl-loop 'loop) + (defalias 'cl-ecase 'ecase)) + (when (with-no-warnings + (emacs-version-between 23 26)) + (declare-function tramp-compat-file-local-name "ebuild-run-mode"))) +(eval-when-compile (require 'rx)) +(require 'compile) +(require 'tramp) +(when (= 23 emacs-major-version) (require 'cl)) + +;;; Macros: + +(defmacro ebuild-macs-expand-file-namef (place filename) + "Update PLACE (a directory) to the full path to FILENAME, relative to PLACE. + +Useful in imperative algorithms involving movement across file system(s)." + `(setf ,place (expand-file-name ,filename ,place))) + +(eval-and-compile + ;; We include dependencies from future Emacs almost verbatim, + ;; namely from Emacs 28.0 + + ;; Strengthen conditions in the following two forms + ;; as support for old Emacs drops. + + (when (= 23 emacs-major-version) + + (defun file-name-base (&optional filename) + "Return the base name of the FILENAME: no directory, no extension." + (declare (advertised-calling-convention (filename) "27.1")) + (file-name-sans-extension + (file-name-nondirectory (or filename (buffer-file-name))))) + + (eval-when-compile + (defmacro defvar-local (var val &optional docstring) + "Define VAR as a buffer-local variable with default value VAL. +Like `defvar' but additionally marks the variable as being automatically +buffer-local wherever it is set." + (declare (debug defvar) (doc-string 3)) + ;; Can't use backquote here, it's too early in the bootstrap. + (list 'progn (list 'defvar var val docstring) + (list 'make-variable-buffer-local (list 'quote var)))))) + + (when (emacs-version-between 24 25) + + (defvar gensym-counter 0 + "Number used to construct the name of the next symbol created by +`gensym'.") + + (defun gensym (&optional prefix) + "Return a new uninterned symbol. +The name is made by appending `gensym-counter' to PREFIX. +PREFIX is a string, and defaults to \"g\"." + (let ((num (prog1 gensym-counter + (setq gensym-counter (1+ gensym-counter))))) + (make-symbol (format "%s%d" (or prefix "g") num))))) + + (when (emacs-version-between 23 25) + + (defun file-local-name (file) + "Return the local name component of FILE. +This function removes from FILE the specification of the remote host +and the method of accessing the host, leaving only the part that +identifies FILE locally on the remote system. +The returned file name can be used directly as argument of +`process-file', `start-file-process', or `shell-command'." + (or (file-remote-p file 'localname) file))) + + (when (emacs-version-between 23 26) + + (defalias 'tramp-compat-file-local-name 'file-local-name) + + (defun tramp-file-local-name (name) + "Return the local name component of NAME. +This function removes from NAME the specification of the remote +host and the method of accessing the host, leaving only the part +that identifies NAME locally on the remote system. If NAME does +not match `tramp-file-name-regexp', just `file-local-name' is +called. The returned file name can be used directly as argument +of `process-file', `start-file-process', or `shell-command'." + (or (and (tramp-tramp-file-p name) + (string-match (nth 0 tramp-file-name-structure) name) + (match-string (nth 4 tramp-file-name-structure) name)) + (tramp-compat-file-local-name name))))) + +(defmacro with-file-buffer/ebuild-macs (filename &rest body) + "Visit FILENAME unless already visited. Set the buffer as current, +evaluate BODY forms. Kill the buffer if it did not exist initially." + ;; We want a with- name but we also need ebuild- namespace, + ;; and with-ebuild-macs-file-buffer is ugly + ;; while with-ebuild-file-buffer is misleading + (declare (indent 1)) + (let ((o-o-filename (gensym "filename-")) + (existing-buffer-g (gensym "existing-buffer-")) + (buffer-g (gensym "buffer-"))) + `(let* ((,o-o-filename ,filename) + (,existing-buffer-g (get-file-buffer ,o-o-filename)) + (,buffer-g (or ,existing-buffer-g + (find-file-noselect ,o-o-filename)))) + (unwind-protect (with-current-buffer ,buffer-g ,@body) + (unless ,existing-buffer-g (kill-buffer ,buffer-g)))))) + +(defmacro with-ebuild-compilation-buffer (buffer &rest body) + "Evaluate BODY forms in buffer BUFFER, presumbaly compilation-mode buffer +where ebuild command is being executed. The following variables are captured: +- COMMAND as is, e.g. ebuild frobnicate-99999999.ebuild ‹command› +- FILENAME passed to COMMAND as the first argument +- PACKAGE-VERSION: counterpart of ebuild's ${PV}, e.g. frobnicate-99999999 +- EXPANDED-FILENAME: full path to FILENAME +- PORTAGE-HOME/CATEGORY/PV: full path to PACKAGE-VERSION dir in Portage build + directory." + (declare (indent 1)) + `(with-current-buffer ,buffer + (let ((command (car compilation-arguments))) + (if (string-match (rx line-start + "ebuild" (one-or-more whitespace) + (group (one-or-more (not whitespace)))) + command) + (let* ((filename (match-string-no-properties 1 command)) + (package-version (file-name-base filename)) + (expanded-filename (expand-file-name filename)) + (category (file-name-base + (ebuild-util-up-directory expanded-filename 2))) + (portage-home/category/pv + (ebuild-util-file-name-join + (ebuild-run-mode-tramp-portage-homedir) + (list category package-version)))) + ;; Arguably, it would be convenient to also provide access + ;; to environment file and maybe other objects. + ;; Things to consider prior to doing this: + ;; - naming conventions: we should follow Gentoo naming scheme + ;; but this can be done in different ways, e.g., we could use + ;; elisp symbol `pv', or `PV', or `$PV', or `$pv'. + ;; Or we could have them all and give them different meaning + ;; - permissions burden: depending on the method for determining + ;; relevant values, we might need to access Portage environment + ;; via tramp; such events should be isolated, at least, + ;; and minimized, at most. + ,@body) + (error + "Does not look like ebuild command in %s; maybe `%s' value is broken" + (current-buffer) 'compilation-arguments))))) + + +;;; Utilities: + +(defun ebuild-util-file-name-join (root names) + "Join file name. + +Examples: + +ELISP> (ebuild-util-file-name-join \"/a/b\" '(\"c\" \"d\")) +\"/a/b/c/d\" + +ELISP> (ebuild-util-file-name-join \"/a/b\" '(\"c\" \"d/\")) +\"/a/b/c/d/\" + +ELISP> (ebuild-util-file-name-join \"/a/b\" '(\"c/\" \"d\")) +\"/a/b/c/d\" + +ELISP> (ebuild-util-file-name-join \"/a/b/\" '(\"c\" \"d\")) +\"/a/b/c/d\"" + (dolist (name names root) (ebuild-macs-expand-file-namef root name))) + +(defun ebuild-util-up-directory (root &optional n) + "Return file name of directory N (1 by default) times upwards the directory +tree. + +File name is returned without trailing slash so that `file-name-base' works +with it hassle-free. + +Examples: + +ELISP> (ebuild-util-up-directory \"/a/b/c/d\") +\"/a/b/c\" + +ELISP> (ebuild-util-up-directory \"/a/b/c/d\" 2) +\"/a/b\" + +ELISP> (ebuild-util-up-directory \"/a/b/c/d\" 3) +\"/a\"" + (cl-loop repeat (or n 1) do (ebuild-macs-expand-file-namef root "..")) + root) + +(defun ebuild-util-maybe-remove-quotes (string) + "Remove quotes at the beginning and at the end of STRING, if present." + (let ((length (length string)) long-enough first-quote-p) + (cl-symbol-macrolet ((last-quote-p + (char-equal ?\" (aref string (1- length))))) + (if (and (setq long-enough (> length 1)) + (setq first-quote-p (char-equal ?\" (aref string 0))) + last-quote-p) + (substring string 1 -1) + (when long-enough + (warn "Missing %s quote in string “%s”" + (if first-quote-p 'trailing 'leading) string)) + string)))) + + +;;; Main part: + +(defun ebuild-run-mode-tramp-prefix () + "In `ebuild-run-mode', return tramp prefix necessary to access build files +and portage cache, as seen in `default-directory' of the buffer." + (cl-ecase major-mode + (ebuild-run-mode + (substring-no-properties default-directory + 0 (- (length (tramp-file-local-name + default-directory))))))) + +(defvar ebuild-run-mode-portage-homedir "/var/tmp/portage/" + "Absolute path to main portage build directory.") + +(defun ebuild-run-mode-tramp-portage-homedir () + "Path to main portage build directory, with all tramp prefixes necessary +to access build files." + (concat (ebuild-run-mode-tramp-prefix) ebuild-run-mode-portage-homedir)) + +(defun ebuild-run-mode-tramp-default-current-sourcedir (&optional buffer) + "For buffer BUFFER where ebuild command is being executed, return +corresponding default temporary build directory (default value for ${S}, as +specified e.g. in man ebuild(5)), derived from `compilation-arguments' of +BUFFER. + +The retuned directory name is prefixed with appropriate `tramp' prefix." + (with-ebuild-compilation-buffer (or buffer (current-buffer)) + (file-name-as-directory + (ebuild-util-file-name-join portage-home/category/pv + (list "work" package-version))))) + +(defun ebuild-run-mode-tramp-current-sourcedir (&optional buffer) + "For buffer BUFFER where ebuild command is being executed, return +corresponding temporary build directory (counterpart of ${S}), derived from +`compilation-arguments' of BUFFER. + +The returned directory name is prefixed with appropriate `tramp' prefix." + (with-ebuild-compilation-buffer (or buffer (current-buffer)) + (file-name-as-directory + (concat + (ebuild-run-mode-tramp-prefix) + (ebuild-util-maybe-remove-quotes + (let ((env-file + (ebuild-util-file-name-join portage-home/category/pv + (list "temp" + "environment")))) + (with-file-buffer/ebuild-macs env-file + (save-excursion + (goto-char (point-min)) + (re-search-forward + (rx line-start "declare -x S=" + (group (one-or-more (not whitespace))) + line-end))) + (match-string-no-properties 1)))))))) + +(defun ebuild-run-mode-goto-error () + "Visit the source for the error message at point. +Use this command in `ebuild-run-mode' buffers." + (interactive) + (let ((default-directory + (or (ebuild-run-mode-tramp-current-sourcedir) + (progn + (warn "Current Portage Sourcedir is unknown.") + (ebuild-run-mode-tramp-default-current-sourcedir))))) + (compile-goto-error))) + +(defvar ebuild-run-font-lock-keywords + '(("^Ebuild \\(finished\\).*" + (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) + (1 compilation-info-face)) + ("^>>> Source \\(compiled\\).*" + (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) + (1 compilation-info-face)) + ("^Ebuild \\(exited abnormally\\|interrupt\\|killed\\|terminated\ +\\|segmentation fault\\)\\(?:.*with code \\([0-9]+\\)\\)?.*" + (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) + (1 compilation-error-face) + (2 compilation-error-face nil t))) + "Substrings to highlight in `ebuild-run-mode'.") + +(defvar-local ebuild-run-mode nil + "Set to t when `ebuild-run-mode' is enabled.") + +;;;###autoload +(define-compilation-mode ebuild-run-mode "Ebuild" + "Major mode for non-interactive buffers spawned by `ebuild-run-command'." + (setq buffer-read-only t) + (font-lock-add-keywords nil ebuild-run-font-lock-keywords) + (setq ebuild-run-mode t)) + +(define-key ebuild-run-mode-map [remap compile-goto-error] + #'ebuild-run-mode-goto-error) + +(provide 'ebuild-run-mode) + +;; Local Variables: +;; coding: utf-8 +;; End: + +;;; ebuild-run-mode.el ends here -- 2.31.1