9

When I run M-x compile it spawns a new subshell to execute my compile command. As soon as the compile command returns, the shell process is killed. I can see why this would be desirable in most cases, but I am currently in a situation where it is not helpful.

I am working in a specialized build environment right now which requires that I take some initial steps to setup the build before running the compiler. As long as the environment persists,I only need to do the setup steps once. But when I use M-x compile it means that I have to do the steps each time I want to compile or recompile.

Is there a way that I can spawn a subshell that will persist in the background? One that M-x compile and M-x gdb can use each time they need to run a shell process?


Motivation:

I have a program (which we will call xcc) which builds C code for special platforms. In order to build my code, I first start xcc from the tcsh prompt:

$ xcc

The program takes 10+ seconds to load, and then I can enter commands at its interactive prompt

xcc>> add target myprogram
xcc>> set source myprogram $PROJDIR/src/
xcc>> set includes myprogram $PROJDIR/include/
xcc>> set type myprogram primitive
xcc>> set inputs myprogram int8,int8
xcc>> set outputs myprogram fix16,fix16
xcc>> build myprogram

The above steps can be built into a custom macro buildmyprog.macro so that I can run it directly from the shell, or from emacs with M-x compile

$ xcc buildmyprog.macro

The main problem with this approach is the fact that it takes the xcc program 10 seconds to load, before compilation even begins. I got tired enough of waiting the extra 10 seconds every time I compiled that I have started running xcc in an ansi-term in a separate buffer. Now after I modify and save the code, I switch over to the ansi-term buffer and run

xcc>> build myprogram

This works fine, but every time I switch over to that buffer I think, "Wouldn't it be great if I could just push F7 and my compile command would get sent to the already-running instance of xcc?"

8
  • What effect does your setup have on the environment? Environment variables, temporary files?
    – T. Verron
    Oct 12, 2014 at 13:47
  • The environment is its own program. While it does make some use of environment variables and temporary files, the program itself maintains its own intenral states which go away when the program closes.
    – nispio
    Oct 12, 2014 at 13:50
  • 3
    Based on the edit, I think you need a comint derived mode for your environment. If you are feeling adventurous, here is a guide for writing one.
    – Vamsi
    Oct 13, 2014 at 15:00
  • @Vamsi I am feeling adventurous, and that looks like a great lead. Thanks.
    – nispio
    Oct 13, 2014 at 16:29
  • 1
    The easiest and dirtiest way to send a command to a comint buffer is to insert it into the buffer and call comint-send-input. That's basically what you are doing by hand, converting it to elisp shouldn't be very hard (especially compared to setting up the comint).
    – T. Verron
    Oct 23, 2014 at 13:52

3 Answers 3

2

Can you do the setup in your shell before you start emacs? The compile sub-shell should inherit the environment from its grandparent via emacs.

1
  • 1
    Imagine that the "environment" that I am running is Python. (It is not Python.) Even though I may be able to start Emacs form the Python interpreter, M-x compile does not sent its commands back out to the python interpreter.
    – nispio
    Oct 13, 2014 at 5:13
0

Use an actual shell inside Emacs then, like eshell or ansi-term. You should use compile for compiling, although it can be used to execute any command.

I don't think it's possible with compile command, but maybe I am missing something. Otherwise, you could put every setup steps into a shell script and execute that script with M-x compile.

4
  • 3
    Well, yes, but the question is then how to hook it up to M-x compile or some alternate command that will compile the current file. Oct 11, 2014 at 23:00
  • @Gilles the OP wants a consistent shell environment between M-x compile calls, and that's what an actual shell does. I don't think it's possible with compile command, but maybe I am missing something. Otherwise, the OP could put every setup steps into a shell script and execute that script with M-x compile
    – Tu Do
    Oct 12, 2014 at 6:19
  • I'm not sure I understand your first suggestion. Are you saying "Use eshell for shell things, and compile for compile things"? That defeats the whole point.
    – nispio
    Oct 12, 2014 at 13:27
  • I have tried the second suggestion, but had a few problems. (Which led me to post this question.) It is too slow because it adds lots of extra overhead to each compile. This is needless, because I should only have to set up the environment once. It also ends up not being dynamic enough, because I can't just tweak one thing in the current envrionment and try compiling again. The build environment is a program, so I can't write a shell script to control it. I have to write a compiled macro script for the environment, and include the compile command itself in the macro.
    – nispio
    Oct 12, 2014 at 13:33
0

A possible solution would be to have a permanent interpreter live in a dedicated buffer (sort-of like the *compilation* buffer, but where the underlying process would never return). You could then have a recompile-like command to send a predefined compilation command to the interpreter, and display the results.

Below is a tentative implementation of such a strategy. The persistent-compile command takes care both of the initialization of the process (when it is called for the first time or with a prefix argument), and the recompilation (if the interpreter process is already up and running). Compilation results are displayed in a *persistent-compilation* buffer, put in compilation-shell-minor-mode to benefit from the usual compilation-mode features to navigate between errors.

Here is an example usage, along with the resulting contents of the *persistent-compilation* buffer:

  1. M-xpersistent-compileRET /bin/bashRET FOO=barRET echo $FOORET

    $ FOO=bar
    $ echo $FOO
    bar
    $
    
  2. M-xpersistent-compileRET

    $ echo $FOO
    bar
    $
    

Beware, the following code wasn't tested much. For example, I'm not sure what happens when you try recompiling before the termination of the previous compilation.

(defvar persistent-compile-interpreter "/bin/bash"
  "Interpreter to be used for persistent compilations.")

(defvar persistent-compile-init ""
  "Initialization command for persistent compilations.")

(defvar persistent-compile-command "make -k"
  "Compilation command for persistent compilations.")

;; Local variable in the persistent compilation buffer
(defvar persistent-compile--next-action)

(defun persistent-compile (&optional edit-command)
  "(Re-)run a persistent compilation.

The first time a persistent compilation is run, the user is asked
for an interpreter, an initialization command and a compilation
command.
The interpreter is started and optionally set up with the
initialization command.  The compilation command is then sent to
the interpreter.

All subsequent recompilations are sent to the same,
already-initialized interpreter, so as to keep the customized
environment.

If EDIT-COMMAND is non-nil, the user can edit the
parameters (interpreter, initialization command and compilation
command) and the interpreter is restarted."
  (interactive "P")
  (when (or edit-command
            (null (get-buffer "*persistent-compilation*"))
            (not persistent-compile-interpreter)
            (not persistent-compile-init)
            (not persistent-compile-command))
    (setq persistent-compile-interpreter (read-from-minibuffer "Interpreter: "
                                                               persistent-compile-interpreter)
          persistent-compile-init        (read-from-minibuffer "Initialization command: "
                                                               persistent-compile-init)
          persistent-compile-command     (read-from-minibuffer "Command: "
                                                               persistent-compile-command)))
  (with-current-buffer (get-buffer-create "*persistent-compilation*")
    (if (and edit-command
             (get-buffer-process (current-buffer)))
        ;; Kill an existing process and schedule for a recompilation with
        ;; the new parameters
        (progn
          (set-process-sentinel
           (get-buffer-process (current-buffer))
           (lambda (process event)
             (persistent-recompile nil)))
          (kill-process (get-buffer-process (current-buffer))))

      (if (not (get-buffer-process (current-buffer)))
          ;; Start and initialize a new process
          (progn
            (erase-buffer)
            (make-comint-in-buffer "persistent-compile" (current-buffer) persistent-compile-interpreter)
            (compilation-shell-minor-mode 1)
            (make-local-variable 'persistent-compile--next-action)
            (setq persistent-compile--next-action 'persistent-compile--initialize)
            (add-hook 'comint-output-filter-functions 'persistent-compile--at-prompt))

        ;; Run command
        (erase-buffer)
        (persistent-compile--send-command)))))

(defun persistent-compile--at-prompt (&optional output)
  (when persistent-compile--next-action
    ;; There is probably a better way of checking whether we are
    ;; just after a prompt, but I didn't find it...
    (let ((p1 (point))
          (p2 (save-excursion (comint-bol))))
      (unless (= p1 p2)
        (let ((action persistent-compile--next-action))
          (setq persistent-compile--next-action nil)
          (funcall action))))))

(defun persistent-compile--initialize ()
  (setq persistent-compile--next-action 'persistent-compile--send-command)
  (display-buffer (current-buffer))
  (insert persistent-compile-init)
  (comint-send-input nil t))

(defun persistent-compile--send-command ()
  (display-buffer (current-buffer))
  (insert persistent-compile-command)
  (comint-send-input nil t))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.