20

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?

SoftTimur
  • 5,630
  • 38
  • 140
  • 292

6 Answers6

23

You can call make with the right arguments:

make -C .. -k

where .. is the path to your Makefile

Thomas
  • 5,047
  • 19
  • 30
  • 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! – agam Mar 01 '18 at 00:17
  • 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? – Beni Cherniavsky-Paskin Nov 28 '18 at 14:19
  • 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; ...` – Beni Cherniavsky-Paskin Nov 28 '18 at 14:25
  • The parsing is governed by `compilation-directory-matcher` var: https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/progmodes/compile.el?id=emacs-26.1#n597. See https://stackoverflow.com/q/10451242/239657. – Beni Cherniavsky-Paskin Nov 28 '18 at 14:39
  • It should be the accepted answer since its much more convenient and actually work, at the opposite of the question above. – Welgriv Jul 28 '20 at 09:03
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.

Ivan Andrus
  • 5,221
  • 24
  • 31
  • Thanks for your answer... I have added your code to `.emacs`, but after `M-x`, it can not find `compile-in-parent-directory` – SoftTimur Jan 31 '12 at 14:02
  • I have put the code on the EmacsWiki to `.emacs`, pressing `F5` gives me an error: `Symbol's function definition is void: loop`. – SoftTimur Jan 31 '12 at 14:11
  • To make function available for interactive call with M-x, you must add (interactive) as first statement to your function. – Bad_ptr Jan 31 '12 at 14:27
  • 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. – Ivan Andrus Jan 31 '12 at 16:53
  • 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. – razeh Dec 08 '15 at 18:48
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 "$@"
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • 1
    you can modify this as follows: `if [ -e "makefile" ] || [ -e "Makefile" ] || [ -e "GNUmakefile" ] ; then` to allow the ther standard makefile names – Osama Javed Mar 09 '13 at 17:32
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)))))))
Bad_ptr
  • 593
  • 3
  • 15
  • 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*` – SoftTimur Jan 31 '12 at 14:05
  • 2
    hm... try to add (require 'cl) before this functions. – Bad_ptr Jan 31 '12 at 14:23
  • 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)))) )))) – SFbay007 Aug 14 '13 at 18:11
  • 1
    @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))))))) ` – Bad_ptr Aug 15 '13 at 21:12
  • I rtried it. It still executes make even if it did not fine the Makefile. – SFbay007 Aug 15 '13 at 21:27
  • 1
    @Ammari ok. I edited `get-closest-pathname` function code. Try this version. – Bad_ptr Aug 15 '13 at 21:46
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.

Douglas Bagnall
  • 796
  • 7
  • 7
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.)

SLesslyTall
  • 261
  • 1
  • 3
  • 8