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?"

nispio
  • 8,175
  • 2
  • 35
  • 73
  • What effect does your setup have on the environment? Environment variables, temporary files? – T. Verron Oct 12 '14 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 '14 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](http://www.masteringemacs.org/articles/2013/07/31/comint-writing-command-interpreter/) is a guide for writing one. – Vamsi Oct 13 '14 at 15:00
  • @Vamsi I *am* feeling adventurous, and that looks like a great lead. Thanks. – nispio Oct 13 '14 at 16:29
  • @Vamsi I have a `comint` derived mode up and running for `xcc`. Any tips on where to go next so that I can send compile commands to it? – nispio Oct 13 '14 at 17:54
  • I suggest looking at other modes that use comint such as `python.el`. I have no prior experience with this but I think adding compile should not prove too difficult. – Vamsi Oct 13 '14 at 23:55
  • 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 '14 at 13:52
  • @T.Verron yes, that's basically the spirit of the answer I posted below (except I tried doing fancier things like waiting for a prompt before trying to send an input). – François Févotte Oct 23 '14 at 14:48

3 Answers3

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.

P.T.
  • 203
  • 2
  • 5
  • 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 '14 at 05: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.

Tu Do
  • 6,772
  • 20
  • 39
  • 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. – Gilles 'SO- stop being evil' Oct 11 '14 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 '14 at 06: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 '14 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 '14 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))
François Févotte
  • 5,917
  • 1
  • 24
  • 37