Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lisp function to run compilation? #1553

Open
mohkale opened this issue Jun 23, 2020 · 11 comments
Open

lisp function to run compilation? #1553

mohkale opened this issue Jun 23, 2020 · 11 comments

Comments

@mohkale
Copy link
Contributor

mohkale commented Jun 23, 2020

Type

Feature Request

Description

Allow users to specify a compilation function which takes care of compiling a project.

At the moment registering a function to any of :test, :run or :compile in projectile-register-project-type seems to call the function to generate a command string which is then run to do the appropriate task. I'd like to register a function which performs the compilation itself.

eg.

;; this
(projectile-register-project-type 'rake '("rakefile")
                                  :compile "rake build")

;; is equivalent to
(defun my-rake-compile-command (&rest _)
  "rake build")

(projectile-register-project-type 'rake '("rakefile")
                                  :compile #'my-rake-compile-command)

with the added benefit that the function approach can dynamically build a command line to compile your project.

My issue here is that I'm using rustic mode for rust programming and it provides it's own excellent commands for running compilations, testing etc. I'd like projectile to use those lisp-functions to compile, build and test my project but I can't register them because projectile only seems to accept command lines or functions that return command lines.

This isn't valid:

(projectile-register-project-type 'rust-cargo '("Cargo.toml")
                                  :compile #'rustic-compile)

Environment & Version information

Projectile version information

Projectile version: 20200616.1659

Emacs version

GNU Emacs 27.0.91 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.20, cairo version 1.17.3) of 2020-06-05

Operating system

Linux mk-desktop 5.7.3-arch1-1 #1 SMP PREEMPT Wed, 17 Jun 2020 19:42:12 +0000 x86_64 GNU/Linux

@bbatsov
Copy link
Owner

bbatsov commented Jun 24, 2020

I think that's a good idea and it should pretty easy to implement it.

@mohkale
Copy link
Contributor Author

mohkale commented Jun 24, 2020

How would you advise implementing it? I thought if the function set using projectile-register-project is interactive, then run it as the compilation function. Otherwise use it to build a command line and then run that instead. I doubt a user would use an interactive function to build a compilation command.

Thoughts?

@mohkale
Copy link
Contributor Author

mohkale commented Jun 24, 2020

This seems to be working for me:

@@ -3843,8 +3843,11 @@
      ((not command) nil)
      ((stringp command) command)
      ((functionp command)
-      (if (fboundp command)
-          (funcall (symbol-function command))))
+      (when (fboundp command)
+        (if (commandp command)
+            (prog1 nil
+              (funcall-interactively command))
+          (funcall (symbol-function command)))))
      (t
       (error "The value for: %s in project-type: %s was neither a function nor a string" command-type project-type)))))

alongside:

(defmacro assoc-pop! (key alist)
  `(let ((result (assoc ,key ,alist)))
     (setq ,alist (delete result ,alist))
     result))

(assoc-pop! 'rust-cargo projectile-project-types)

(projectile-register-project-type 'rust-cargo '("Cargo.toml")
                                  :compile #'rustic-compile
                                  :test    #'rustic-cargo-test
                                  :run     #'rustic-cargo-run)

on a slight tangent, does projectile have a function to reassign fields in projectile-project-types. I've been removing entire configurations and then re-registering but I imagine there has to be a better way.

@mohkale
Copy link
Contributor Author

mohkale commented Jun 24, 2020

Kay, my earlier fix doesn't seem to be working. Now I'm getting the following stacktrace when debug-on-error is true, which I can only presume is because projectile expects commands to be strings and returning nil to skip evaluating isn't valid.

Debugger entered--Lisp error: (wrong-type-argument stringp nil)
  string-match("\\`\\s *cd\\(?:\\s +\\(\\S +?\\|'[^']*'\\|\"\\(?:[^\"`$\\]\\|\\\\..." nil)
  #f(compiled-function (command &optional mode name-function highlight-regexp) "Run compilation command COMMAND (low level interface).\nIf COMMAND starts with a cd command, that becomes the `default-directory'.\nThe rest of the arguments are optional; for them, nil means use the default.\n\nMODE is the major mode to set in the compilation buffer.  Mode\nmay also be t meaning use `compilation-shell-minor-mode' under `comint-mode'.\n\nIf NAME-FUNCTION is non-nil, call it with one argument (the mode name)\nto determine the buffer name.  Otherwise, the default is to\nreuses the current buffer if it has the proper major mode,\nelse use or create a buffer with name based on the major mode.\n\nIf HIGHLIGHT-REGEXP is non-nil, `next-error' will temporarily highlight\nthe matching section of the visited source line; the default is to use the\nglobal value of `compilation-highlight-regexp'.\n\nReturns the compilation buffer created." #<bytecode 0x15616979e699>)(nil nil)
  apply(#f(compiled-function (command &optional mode name-function highlight-regexp) "Run compilation command COMMAND (low level interface).\nIf COMMAND starts with a cd command, that becomes the `default-directory'.\nThe rest of the arguments are optional; for them, nil means use the default.\n\nMODE is the major mode to set in the compilation buffer.  Mode\nmay also be t meaning use `compilation-shell-minor-mode' under `comint-mode'.\n\nIf NAME-FUNCTION is non-nil, call it with one argument (the mode name)\nto determine the buffer name.  Otherwise, the default is to\nreuses the current buffer if it has the proper major mode,\nelse use or create a buffer with name based on the major mode.\n\nIf HIGHLIGHT-REGEXP is non-nil, `next-error' will temporarily highlight\nthe matching section of the visited source line; the default is to use the\nglobal value of `compilation-highlight-regexp'.\n\nReturns the compilation buffer created." #<bytecode 0x15616979e699>) (nil nil))
  compilation-start(nil nil)
  compile(nil)
  (if (functionp cmd) (funcall cmd) (compile cmd))
  projectile-run-compilation(nil)
  (let* ((project-root (projectile-project-root)) (default-directory (projectile-compilation-dir)) (command (projectile-maybe-read-command show-prompt command prompt-prefix))) (if command-map (progn (puthash default-directory command command-map) (ring-insert (projectile--get-command-history project-root) command))) (if save-buffers (progn (save-some-buffers (not compilation-ask-about-save) #'(lambda nil (projectile-project-buffer-p (current-buffer) project-root))))) (if (file-directory-p default-directory) nil (mkdir default-directory)) (projectile-run-compilation command) command)
  (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:show-prompt :prompt-prefix :save-buffers :allow-other-keys)) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ... --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:show-prompt :prom..." (car --cl-keys--)))))) (let* ((project-root (projectile-project-root)) (default-directory (projectile-compilation-dir)) (command (projectile-maybe-read-command show-prompt command prompt-prefix))) (if command-map (progn (puthash default-directory command command-map) (ring-insert (projectile--get-command-history project-root) command))) (if save-buffers (progn (save-some-buffers (not compilation-ask-about-save) #'(lambda nil (projectile-project-buffer-p ... project-root))))) (if (file-directory-p default-directory) nil (mkdir default-directory)) (projectile-run-compilation command) command))
  (let* ((show-prompt (car (cdr (plist-member --cl-rest-- ':show-prompt)))) (prompt-prefix (car (cdr (plist-member --cl-rest-- ':prompt-prefix)))) (save-buffers (car (cdr (plist-member --cl-rest-- ':save-buffers))))) (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '...) (setq --cl-keys-- (cdr ...))) ((car (cdr ...)) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:show-prompt :prom..." (car --cl-keys--)))))) (let* ((project-root (projectile-project-root)) (default-directory (projectile-compilation-dir)) (command (projectile-maybe-read-command show-prompt command prompt-prefix))) (if command-map (progn (puthash default-directory command command-map) (ring-insert (projectile--get-command-history project-root) command))) (if save-buffers (progn (save-some-buffers (not compilation-ask-about-save) #'(lambda nil ...)))) (if (file-directory-p default-directory) nil (mkdir default-directory)) (projectile-run-compilation command) command)))
  projectile--run-project-cmd(nil #<hash-table equal 1/65 0x156169a00e9d> :show-prompt nil :prompt-prefix "Compile command: " :save-buffers t)
  (let ((command (projectile-compilation-command (projectile-compilation-dir)))) (projectile--run-project-cmd command projectile-compilation-cmd-map :show-prompt arg :prompt-prefix "Compile command: " :save-buffers t))
  (closure (projectile-grep-find-unignored-patterns projectile-grep-find-ignored-patterns projectile-grep-find-unignored-paths projectile-grep-find-ignored-paths t) (arg) "Run project compilation command.\n\nNormally you'll ..." (interactive "P") (let ((command (projectile-compilation-command (projectile-compilation-dir)))) (projectile--run-project-cmd command projectile-compilation-cmd-map :show-prompt arg :prompt-prefix "Compile command: " :save-buffers t)))(nil)
  funcall((closure (projectile-grep-find-unignored-patterns projectile-grep-find-ignored-patterns projectile-grep-find-unignored-paths projectile-grep-find-ignored-paths t) (arg) "Run project compilation command.\n\nNormally you'll ..." (interactive "P") (let ((command (projectile-compilation-command (projectile-compilation-dir)))) (projectile--run-project-cmd command projectile-compilation-cmd-map :show-prompt arg :prompt-prefix "Compile command: " :save-buffers t))) nil)
  (if (and (consp prefix) (setq prefix (car prefix)) (>= prefix 16)) (let* ((vnew (symbol-function 'compile)) (old (symbol-function 'actual-compile))) (unwind-protect (progn (fset 'actual-compile vnew) (let* ((vnew #'...) (old (symbol-function ...))) (unwind-protect (progn (fset ... vnew) (funcall func ...)) (fset 'compile old)))) (fset 'actual-compile old))) (funcall func prefix))
  (let ((prefix current-prefix-arg)) (if (and (consp prefix) (setq prefix (car prefix)) (>= prefix 16)) (let* ((vnew (symbol-function 'compile)) (old (symbol-function 'actual-compile))) (unwind-protect (progn (fset 'actual-compile vnew) (let* ((vnew ...) (old ...)) (unwind-protect (progn ... ...) (fset ... old)))) (fset 'actual-compile old))) (funcall func prefix)))
  projectile-compile--double-prefix-means-run-comint((closure (projectile-grep-find-unignored-patterns projectile-grep-find-ignored-patterns projectile-grep-find-unignored-paths projectile-grep-find-ignored-paths t) (arg) "Run project compilation command.\n\nNormally you'll ..." (interactive "P") (let ((command (projectile-compilation-command (projectile-compilation-dir)))) (projectile--run-project-cmd command projectile-compilation-cmd-map :show-prompt arg :prompt-prefix "Compile command: " :save-buffers t))) nil)
  apply(projectile-compile--double-prefix-means-run-comint (closure (projectile-grep-find-unignored-patterns projectile-grep-find-ignored-patterns projectile-grep-find-unignored-paths projectile-grep-find-ignored-paths t) (arg) "Run project compilation command.\n\nNormally you'll ..." (interactive "P") (let ((command (projectile-compilation-command (projectile-compilation-dir)))) (projectile--run-project-cmd command projectile-compilation-cmd-map :show-prompt arg :prompt-prefix "Compile command: " :save-buffers t))) nil)
  projectile-compile-project(nil)
  funcall-interactively(projectile-compile-project nil)
  call-interactively(projectile-compile-project nil nil)
  command-execute(projectile-compile-project)

checking whether a command is nil before trying to run it seems to work,

@@ -4039,10 +4039,11 @@
 with a prefix ARG."
   (interactive "P")
   (let ((command (projectile-compilation-command (projectile-compilation-dir))))
-    (projectile--run-project-cmd command projectile-compilation-cmd-map
-                                 :show-prompt arg
-                                 :prompt-prefix "Compile command: "
-                                 :save-buffers t)))
+    (when command
+      (projectile--run-project-cmd command projectile-compilation-cmd-map
+                                   :show-prompt arg
+                                   :prompt-prefix "Compile command: "
+                                   :save-buffers t))))
 
 ;;;###autoload
 (defun projectile-test-project (arg)
@@ -4053,10 +4054,11 @@
 with a prefix ARG."
   (interactive "P")
   (let ((command (projectile-test-command (projectile-compilation-dir))))
-    (projectile--run-project-cmd command projectile-test-cmd-map
-                                 :show-prompt arg
-                                 :prompt-prefix "Test command: "
-                                 :save-buffers t)))
+    (when command
+      (projectile--run-project-cmd command projectile-test-cmd-map
+                                   :show-prompt arg
+                                   :prompt-prefix "Test command: "
+                                   :save-buffers t))))
 
 ;;;###autoload
 (defun projectile-run-project (arg)
@@ -4067,9 +4069,10 @@
 with a prefix ARG."
   (interactive "P")
   (let ((command (projectile-run-command (projectile-compilation-dir))))
-    (projectile--run-project-cmd command projectile-run-cmd-map
-                                 :show-prompt arg
-                                 :prompt-prefix "Run command: ")))
+    (when command
+      (projectile--run-project-cmd command projectile-run-cmd-map
+                                   :show-prompt arg
+                                   :prompt-prefix "Run command: "))))
 
 ;;;###autoload
 (defun projectile-repeat-last-command (show-prompt)

but I don't think this is a good way to do this. maybe if projectile-default-generic-command returns t we should take it to mean the compilation has been done for us. if it returns nil, that's an error. if it returns a string that's a command-line projectile should run. I'm not sure. What do you think ❓.

@mohkale
Copy link
Contributor Author

mohkale commented Mar 26, 2021

@bbatsov

Have you had more time to consider how this should be approached? I was getting round to implementing it only to find that projectile has a caching mechanism in place for compilation commands and I'm not sure how this would interact with that?

@pataquets
Copy link

@bbatsov Do you have any feedback on this? I'm interested on it and, if my Elisp abilities are good enough to tackle it, I'd like to take a stab at it.
Also, looks like there is already some interest.

@mohkale
Copy link
Contributor Author

mohkale commented Sep 18, 2023

@pataquets You may be interested in https://github.com/mohkale/projection. It's a WIP projectile replacement for myself where I'm also experimenting with some more IDE like features. It supports compilation commands as functions out of the box (specifically for rustic mode). Not really public facing yet since some of the extensions aren't on MELPA but I've been using it on and off for a bit.

@pataquets
Copy link

pataquets commented Sep 18, 2023

Thank you very much, @mohkale. I wasn't aware of projection, and I've read the rationale behind it and learnt a lot. Specially, I'll take a look at multi-compile packages.
As of now, switching packages it's off the table, mainly because I use Spacemacs, which comes with projectile built-in and also seems quite an effort now.
However, I wonder if projectile's development is only in maintenance mode (no new feats), which I think it should be made visible in the readme, or it's still in development and accepting features.

EDIT: Replied to wrong user initially, my apologies for the noise. Time to go to sleep.

@bbatsov
Copy link
Owner

bbatsov commented Sep 19, 2023

However, I wonder if projectile's development is only in maintenance mode (no new feats), which I think it should be made visible in the readme, or it's still in development and accepting features.

FYI - Projectile's development is just as active as ever. (at least from my perspective)

@pataquets
Copy link

However, I wonder if projectile's development is only in maintenance mode (no new feats), which I think it should be made visible in the readme, or it's still in development and accepting features.

FYI - Projectile's development is just as active as ever. (at least from my perspective)

Sorry, @bbatsov. Wrongly replied to you initially. Apologies for the noise.
Anyway, do you have any implementation advice to @mohkale's attempt to implement it? I'm also interested and, if it's within my abilities I could take a stab at it with some guidance or maybe someone else might be ready to jump in.

@rileyrg
Copy link

rileyrg commented Dec 27, 2023

Just a little "beep here!. I'd love to be able to specifx run or compile as elisp functions. I dont think its currently possible, but am hoping to be corrected : primarily I want to run the new "eat" terminal and exec a posix script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants