Subject: Re: bug#77430: compilation-start should remember shell-file-name

From: Siyuan Chen <[email-masked]>
Date: Fri, 4 Apr 2025 16:15:06 +0800

> Sorry, I don't understand: are you saying that you cannot arrange for
the buffer-local value of shell-file-name to point to Bash and stay
that way for the subsequent commands?  If you need to use
with-current-buffer or something similar, it should be easy no?

The original problem is not about `compile' but `recompile', i.e. press 'g'
in the *compilation* buffer. The *compilation* buffer doesn't know anything
about the last shell-file-name, so it uses cmdproxy by default.

Anyway, this is no longer a problem now, see below.

> I think this is the cleanest solution for problems like this on
Windows, since you don't really want to override shell-file-name, you
just want to invoke the commands via Bash.

Your method really inspired me. Thank you so much!

Based on it, I've redesigned my compilation workflow. Basically, whenever
users invoke `sc-haskell-projectile-stack-compile' (a tailored compile
command integrated with projectile), it firstly generates two files in the
Haskell project folder:

.emacs_stack_launch.bat
```
set MSYSTEM=MINGW32
"c:/Users/Chansey/AppData/Local/Programs/stack/x86_64-windows/msys2-20230526/usr/bin/bash"
--login "e:/my-haskell-project/.emacs_stack_launch_script.sh" %*
```

.emacs_stack_launch_script.sh
```
export STACK_ROOT=/c/sr
export
PATH=/c/env/haskell/stack/v2.15.1:/c/Users/Chansey/AppData/Roaming/local/bin:/c/PROGRA~1/Git/cmd:$PATH
$*
```

then it calls (compile ".emacs_stack_launch.bat stack build").

This method is working great so far. Plus, the file generation brings
several benefits:

1. We can set local variables in .dir-locals (e.g.
msys2-path, environment-variables) per project for fine-grained control
over compilation.

2. No need to tailored `recompile'. It just uses the last generated files.
So it works pretty well when users press 'g' in the *compilation* buffer.

3. Support all the compile commands and opts, like stack build, stack run,
stack build --test, etc.

4. Easy debugging with .emacs_stack_launch.bat and
.emacs_stack_launch_script.sh.

The code is something like this:

```
(defun sc-haskell-projectile-stack--run-project-cmd (command)
  (let* ((default-directory (projectile-compilation-dir))
         (command (projectile-read-command
                   "Compile command: "
                   (or projectile-project-compilation-cmd
                       command))))
    (sc-haskell-projectile-stack--gen-launch-file)
    (sc-haskell-projectile-stack--gen-launch-script-file)
    (compile (format "%s %s"
                     ".emacs_stack_launch.bat"
                     command)
             projectile-compile-use-comint-mode)))

(defun sc-haskell-projectile-stack-compile ()
  (sc-haskell-projectile-stack--run-project-cmd "stack build"))

(defun sc-haskell-projectile-stack-test ()
  (sc-haskell-projectile-stack--run-project-cmd "stack build --test"))

(defun sc-haskell-projectile-stack-run ()
  (sc-haskell-projectile-stack--run-project-cmd "stack run"))
```

Additionally, I also created a projectile-based shell command, which has to
be different from projectile-based compile command.

```
(defun sc-haskell-projectile-stack-shell ()
  (interactive)
  (let ((current-prefix-arg 4)
        (explicit-shell-file-name
         (expand-file-name "usr/bin/bash"
sc-haskell-projectile-stack-msys2-path))
        (explicit-bash-args
         '("--login" "--noediting" "-i")))
    (sc-haskell-projectile-stack--gen-msys2-bashrc-init-file)
    (with-environment-variables (("MSYSTEM" "MINGW32"))
      (call-interactively 'projectile-run-shell))))
```

Unlike the previous method, this one still rebinds
explicit-shell-file-name, otherwise we have to call "bash --login
--noediting -i" in the .emacs_stack_launch_script.sh, which would invoke
bash twice — something I'd rather avoid (it still can work though).

My solution is to add a hook in .bashrc

```
if [ -f $HOME/.bashrc_init.sh ]; then
    . $HOME/.bashrc_init.sh
    rm $HOME/.bashrc_init.sh
fi
```

Basically, whenever users invoke `sc-haskell-projectile-stack-shell, it
generates .bashrc_init.sh in the msys2 home USER folder. The commands in
.bashrc_init.sh are similar to those in emacs_stack_launch_script and can,
of course, be configured via .dir-locals.el. It might seem a bit unusual,
but it works well so far.

Just like to share my current design here — any suggestions would be
greatly appreciated.

Thank you!

On Tue, Apr 1, 2025 at 11:57 PM Eli Zaretskii <[email-masked]> wrote:

> > Cc: [email-masked]
> > Date: Tue, 01 Apr 2025 18:50:36 +0300
> > From: Eli Zaretskii <[email-masked]>
> >
> > > From: Siyuan Chen <[email-masked]>
> > > Date: Tue, 1 Apr 2025 23:13:46 +0800
> > > Cc: [email-masked]
> > >
> > > >  Why cannot your my/compile function do this:
> > > >
> > > >  (setq-local shell-file-name "C:/msys64/usr/bin/bash")
> > > >
> > > > instead of let-binding it?
> > >
> > > This is only for the current file buffer.
> > >
> > > For example, if you focus on a project file buffer (e.g. test.hs) and
> M-x my/compile, it only sets
> > > `shell-file-name' for that file buffer. However, the `compile`
> actually using the `shell-file-name' belongs to the
> > > *compilation* buffer.
> > >
> > > ```
> > > (defun compilation-start (command &optional mode name-function
> highlight-regexp
> > >                                   continue)
> > >   ...
> > >   (with-current-buffer outbuf ; <--- The `outbuf' is the *compilation*
> buffer
> > >     ...
> > >     (comint-exec
> > >      outbuf (compilation--downcase-mode-name mode-name)
> > >      shell-file-name ; <--- use the buffer local variable
> shell-file-name in the *compilation* buffer
> > >      nil `(,shell-command-switch ,command))
> > >     ...
> > >     )
> > > ...
> > > )
> > > ```
> > >
> > > So the following code doesn't work:
> > >
> > > ```
> > > (defun my/compile ()
> > >   (interactive)
> > >   (let (
> > >         (compilation-environment
> > >
> "PATH=/mingw64/bin:/usr/local/bin:/usr/bin:/bin:/c/Windows/System32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl")
> > >
> > >         )
> > >     (setq-local shell-file-name "C:/msys64/usr/bin/bash")
> > >     (compile "ls")
> > >     ))
> > > ```
> > >
> > > M-x my/compile
> > >
> > > Compilation exited abnormally.
> >
> > Sorry, I don't understand: are you saying that you cannot arrange for
> > the buffer-local value of shell-file-name to point to Bash and stay
> > that way for the subsequent commands?  If you need to use
> > with-current-buffer or something similar, it should be easy no?
>
> Alternatively, how about making a Windows batch file, which would
> invoke the compilation command via Bash.  So you compile command would
> look like this:
>
>   M-x compile RET mycomp ls RET
>
> where mycomp.bat is a batch file which does
>
>   @echo off
>   C:\msys64\usr\bin\bash --login -c %*
>
> I think this is the cleanest solution for problems like this on
> Windows, since you don't really want to override shell-file-name, you
> just want to invoke the commands via Bash.
>