How to set the default-directory of compilation in Emacs?
Asked Answered
H

6

21

I am coding OCaml under Emacs, I have one makefile in the working folder, and several sub-folders containing .ml files. If I launch M-x compile and make works fine on a buffer of makefile, but does not work on a buffer of a .ml file, it gives me an error:

-*- mode: compilation; default-directory: "..." -*-
Compilation started at Fri Jan 27 18:51:35

make -k
make: *** No targets specified and no makefile found.  Stop.

Compilation exited abnormally with code 2 at Fri Jan 27 18:51:35

It is understandable because the default-directory is sub-folder which does not contain makefile. Does anyone know how to set the folder of makefile always as the default-directory of compilation?

Hotbox answered 27/1, 2012 at 17:58 Comment(0)
B
1

Matthias Puech has a solution involving a .dir-local file in the project root directory:

((nil . ((eval . (setq default-directory 
                  (locate-dominating-file buffer-file-name
                   ".dir-locals.el")
)))))

It is presumably also possible to use something like: (shell-command-to-string "git rev-parse --show-toplevel") as that innermost bit.

Beep answered 27/1, 2012 at 17:58 Comment(1)
No no no :/ This will break everything, including find-file.Saretta
C
22

You can call make with the right arguments:

make -C .. -k

where .. is the path to your Makefile

Clave answered 27/1, 2012 at 18:18 Comment(6)
Hah, was looking around for this question, and your answer is the most obvious one in hindsight, I would much prefer it to the define-your-own-custom-function-with-a-hook-and-change-it-when-you-need-a-different-path answers above. Thanks!Keeling
However, build tools will print error messages with paths relative to the directory they're running at, and if that doesn't match the directory of the *compilation* buffer, clicking on the file:line in Emacs won't work?Auld
Aha! Emacs parses the make: Entering directory '..' lines (but only in English locale) so clicking on the file:line links still works! If not using make, you can manually print them: Compile command: echo "make: Entering directory '$DIR'"; cd $DIR; ...Auld
The parsing is governed by compilation-directory-matcher var: git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/…. See https://mcmap.net/q/659688/-how-to-adjust-the-path-that-emacs-39-compile-goto-error-gets-from-the-compilation-buffer/239657.Auld
It should be the accepted answer since its much more convenient and actually work, at the opposite of the question above.Trews
@Trews I disagree as this answer doesn't answer the question. The question specifically pertains to Emacs, not Makefile.Petrarch
C
9

You can control this from within emacs by writing a function that (temporarily) sets default-directory and calls compile.

(defun compile-in-parent-directory ()
  (interactive)
  (let ((default-directory
          (if (string= (file-name-extension buffer-file-name) "ml")
              (concat default-directory "..")
            default-directory))))
  (call-interactively #'compile))

When using compile-in-parent-directory all ml files will be compiled in the parent directory of where they are. Of course if they are nested deeper you can change the logic to reflect that. In fact there is a version on the EmacsWiki which searches parent directories until it finds a makefile. I found this after I wrote this answer, otherwise I would have just pointed you there. sigh. The good thing about my method is that it's not specific to make so that you can use the same "trick" for other commands.

You can also change the call to compile to be non-interactive if you know exactly what you want the command to be. This would work particularly well if it's bound to a key in the appropriate mode hook.

Chaudfroid answered 28/1, 2012 at 11:35 Comment(5)
Thanks for your answer... I have added your code to .emacs, but after M-x, it can not find compile-in-parent-directoryHotbox
I have put the code on the EmacsWiki to .emacs, pressing F5 gives me an error: Symbol's function definition is void: loop.Hotbox
To make function available for interactive call with M-x, you must add (interactive) as first statement to your function.Broddy
Sorry about mine, I have fixed it. You need to add a (require 'cl) to get the definition of loop for the code on EmacsWiki.Chaudfroid
You also need to insert a ' in front of compile --- it needs to be (call-interactively 'compile). I'd edit it, but stack overflow wants at least 6 characters in an edit.Carberry
V
4

i use a script like this which allows me to run make from any sub-directory (assuming you are in a posix-like environment). just put this script in your PATH as something like "sub_make.sh" and invoke it the same way you would invoke make:

#!/bin/bash

# search for project base
INIT_DIR=`pwd`
while [ "$PWD" != "/" ] ; do
  if [ -e "makefile" ] ; then
    break
  fi

  cd ..
done

if [ ! -e "makefile" ] ; then
  echo "Couldn't find 'makefile'!"
  exit 1
fi

# indicate where we are now
echo "cd "`pwd`
echo make "$@"

# now run make for real
exec make "$@"
Vocal answered 27/1, 2012 at 18:33 Comment(1)
you can modify this as follows: if [ -e "makefile" ] || [ -e "Makefile" ] || [ -e "GNUmakefile" ] ; then to allow the ther standard makefile namesLiselisetta
B
4

That's what I have in some of my configs :)

(defun* get-closest-pathname (&optional (max-level 3) (file "Makefile"))
  (let* ((root (expand-file-name "/"))
         (level 0)
         (dir (loop
               for d = default-directory then (expand-file-name ".." d)
                 do (setq level (+ level 1))
               if (file-exists-p (expand-file-name file d))
                 return d
               if (> level max-level)
                 return nil
               if (equal d root)
                 return nil)))
    (if dir
        (expand-file-name file dir)
      nil)))

(add-hook 'c-mode-hook
          (lambda ()
            (unless (file-exists-p "Makefile")
              (set (make-local-variable 'compile-command)
                   (let ((file (file-name-nondirectory buffer-file-name))
                         (mkfile (get-closest-pathname)))
                     (if mkfile
                         (progn (format "cd %s; make -f %s"
                            (file-name-directory mkfile) mkfile))
                       (format "%s -c -o %s.o %s %s %s"
                               (or (getenv "CC") "gcc")
                               (file-name-sans-extension file)
                               (or (getenv "CPPFLAGS") "-DDEBUG=9")
                               (or (getenv "CFLAGS") "-ansi -pedantic -Wall -g")
                               file)))))))
Broddy answered 30/1, 2012 at 5:51 Comment(6)
Thanks for your answer... I have added your code to .emacs, but after launching emacs, it gives me an warning Warning (initialization): An error occurred while loading '~/.emacs': Symbol's function definition is void: defun*Hotbox
hm... try to add (require 'cl) before this functions.Broddy
Thanks Bad_ptr. I used your code to have a command that would execute make command in the main directory in any mode, not just C, also if the buffer was in a directory mode. I would like it to not do anything though if it did not find the main directory of Makefile. My function unfortunately still execute the make regardless. Could you advice? (global-set-key [f1] (lambda () (interactive) (compile (let ((mkfile (get-closest-pathname))) (if mkfile (format "cd %s; make " (file-name-directory (get-closest-pathname)))) ))))Stillness
@Ammari try this:(global-set-key [f1] (lambda () (interactive) (let ((mkfile (get-closest-pathname))) (if mkfile (compile (format "cd %s; make " (file-name-directory mkfile))))))) Broddy
I rtried it. It still executes make even if it did not fine the Makefile.Stillness
@Ammari ok. I edited get-closest-pathname function code. Try this version.Broddy
B
1

Matthias Puech has a solution involving a .dir-local file in the project root directory:

((nil . ((eval . (setq default-directory 
                  (locate-dominating-file buffer-file-name
                   ".dir-locals.el")
)))))

It is presumably also possible to use something like: (shell-command-to-string "git rev-parse --show-toplevel") as that innermost bit.

Beep answered 27/1, 2012 at 17:58 Comment(1)
No no no :/ This will break everything, including find-file.Saretta
H
0

Not a completely general solution w.r.t makefile location, but adding this here for posterity because it solved my particular use-case.

If you use projectile and your makefile is always in the root of your project directory, then you can use projectile-compile-project.

(In my case, I wanted to lint my project, so calling (compile "flake8") would only flake from the current buffer's directory downwards, whereas what I really wanted was linting of the entire project. projectile-compile-project achieves this.)

Highness answered 21/4, 2021 at 0:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.