How to set the default-directory of compilation in Emacs? - emacs

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?

You can call make with the right arguments:
make -C .. -k
where .. is the path to your Makefile

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.

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 "$#"

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)))))))

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.

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.)

Related

run a shell command from a specific directory in emacs

How do I execute a shell command (eg, git gui) from a specific directory? I'd prefer a cross-platform approach that doesn't depend shell operators like && or ; because I need this to run on Windows and unix. (For example, calling cd /path/to/dir && git gui won't work on Windows because && is not valid.)
I tried:
(async-shell-command (concat "cd \""
(replace-regexp-in-string
"/" "\\\\" (file-name-directory (buffer-file-name))) "\" ; git gui"))
That fails because for whatever reason the shell thinks ; git gui is part of the path, and it reports: The system cannot find the path specified.
So, I'd rather not deal with shell quirks and I'm hoping there's an elisp function that sets the directory for shell-command or async-shell-command. I tried:
(shell-process-pushd (file-name-directory (buffer-file-name)))
(shell-command "git gui")
(shell-process-popd nil)
That had no effect: git gui always opens in my home directory. (Also, shell-process-pushd/popd are not documented.)
This also doesn't work:
(start-process "gitgui" nil "git" "gui")
Neither does this:
(let '(bufdir (file-name-directory (buffer-file-name)))
(with-temp-buffer
(print bufdir)
(cd bufdir)
(shell-command "git gui")))
Are you sure you're using the trailing slash to indicate directory name in emacs-lisp?
(let ((default-directory "~/.emacs.d"))
(shell-command-to-string "echo $PWD"))
;;; ⇒ "/Users/me"
(let ((default-directory "~/.emacs.d/"))
(shell-command-to-string "echo $PWD"))
;;; ⇒ "/Users/me/.emacs.d"
(shell-command) does use the the buffer's default-directory, which means there is no reason to cd to the buffer's directory.
The problem in my case was that I was mistakenly running git gui on a buffer that didn't have an associated .git/ directory.
Try this:
(let ((default-directory "~/.emacs.d/")) (shell-command "ls"))

Waiting on compilation to finish

I am using compile to pull new files from source tree using mercurial "hg pull".
I am performing a save of all buffers before the pull and would like to "refresh all opened buffers" after the compilation "pulling" finishes.
I tried experimenting with compilation-finish-functions but found out that the functions added to the list will be executed after "every" compilation. Since I use compile to search IDs "gid" I don't want to refresh opened files on every search.
How can I wait on compilation to finish before refreshing opened files "only" while inside a command and not on every compile outside of the command.
Here is the code:
; From http://www.emacswiki.org/emacs/CompileCommand
(defun compile-pkg (&optional command startdir)
"Compile a package, moving up to the parent directory
containing configure.ac, if it exists. Start in startdir if defined,
else start in the current directory."
(interactive)
(let ((dirname) (dir-buffer nil))
(setq startdir (expand-file-name (if startdir startdir ".")))
(setq command (if command command compile-command))
(setq dirname (upward-find-file "Makefile" startdir))
; (setq dirname (if dirname dirname (upward-find-file "Makefile" startdir)))
; (setq dirname (if dirname dirname (expand-file-name ".")))
; We've now worked out where to start. Now we need to worry about
; calling compile in the right directory
(save-excursion
(setq dir-buffer (find-file-noselect dirname))
(set-buffer dir-buffer)
(compile command)
(kill-buffer dir-buffer)
)))
(defun upward-find-file (filename &optional startdir)
"Move up directories until we find a certain filename. If we
manage to find it, return the containing directory. Else if we
get to the toplevel directory and still can't find it, return
nil. Start at startdir or . if startdir not given"
(let ((dirname (expand-file-name
(if startdir startdir ".")))
(found nil) ; found is set as a flag to leave loop if we find it
(top nil)) ; top is set when we get
; to / so that we only check it once
; While we've neither been at the top last time nor have we found
; the file.
(while (not (or found top))
; If we're at / set top flag.
(if (string= (expand-file-name dirname) "/")
(setq top t))
; Check for the file
(if (file-exists-p (expand-file-name filename dirname))
(setq found t)
; If not, move up a directory
(setq dirname (expand-file-name ".." dirname))))
; return statement
(if found (concat dirname "/") nil)))
(defun compile-hgpull ()
(interactive)
(save-all-buffers)
(compile-pkg "hg pull -u")
; if (compile finished) -> (revert-all-buffers)
)
(global-set-key [f1] 'compile-hgpull)
compile is async. So, you have two choices.
One, don't use compile. Instead use one of the other ways to invoke a shell command, like shell-command or start-process or call-process. I think this is probably preferred; I don't see why you'd need to use compile here.
Two, set compilation-finish-function.
If you want to run a shell-command synchronously, and then see its output, it might be easier to use shell-command-to-string than compile.

Catching an error from byte-compile-file in batch mode

I'm trying to write a makefile snippet to compile .el files to .elc files. I have the following snippet:
.el.elc:
$(EMACS) -q -Q --batch \
--eval "(progn
(setq load-path (cons \"$(abs_srcdir)\" load-path))
(setq load-path (cons nil load-path))
(defun byte-compile-dest-file (f) \"$#\")
(condition-case nil
(byte-compile-file \"$<\")
(error (kill-emacs 1))))"
(I have formatted the snippet for presentation, make probably won't like it laid out this way.)
$(abs_srcdir) is being defined correctly, but I have two problems:
Firstly: With a directory layout like the following:
+- foo.el <- foo.el lives in $(abs_srcdir)
+- _build/
++- bar.el
++- Makefile
(That is, a VPATH build where foo.el lives in srcdir and I'm trying to build in _build.)
If bar.el depends on foo.el (via (require 'foo) and (provide 'foo)), attempts to compile bar.elc fail with the error:
In toplevel form:
../bar.el:1:1:Error: Cannot open load file: foo
This strikes me as odd because thought I was consing $(abs_srcdir) onto load-path. What's happened?
The second problem is that even if this happens, emacs is exiting with status 0, despite my efforts to wrap the (byte-compile-file) call in a (condition-case). What has happened here?
The first problem I had was that with a VPATH build, files in the build directory weren't being found (by calls to (require), for example). For some reason, consing nil onto load-path doesn't work, but consing $(abs_srcdir) works.
The second problem was that I couldn't catch the error raised by (byte-compile). This was solved by wvxvw: (byte-compile) doesn't raise errors, but returns t if the compilation succeeds. Therefore, something like:
(unless (byte-compile-file "$<") (kill-emacs 1))
works nicely.
I cleaned up the snippet you gave and did a test case in the directory ~/test.
I end up with the following three files.
Makefile:
abs_srcdir=~/test
all: foo.elc bar.elc
clean:
rm -rf *.elc
%.elc: %.el
emacs -Q --batch --eval "(progn (add-to-list 'load-path \"$(abs_srcdir)\") (defun byte-compile-dest-file (f) \"$#\") (condition-case nil (byte-compile-file\"$<\") (error (kill-emacs 1))))"
foo.el:
(defun foo-fun ()
(message "foo"))
(provide 'foo)
bar.el:
(require 'foo)
(defun bar-fun ()
(foo-fun))
(provide 'bar)
This example works for me and only fails if I give the wrong definition for abs_srcdir.

Using Emacs, is it possible to pin the compilation command to a specific buffer/directory?

Right now I am using the following to compile, when I'm in for example main.cpp
C-x b Makefile RET M-x compile RET RET
I actually have M-x compile as a keyboard shortcut, but the problem is I would really like not having to go through all that trouble to simply run my Makefile.
I need to visit Makefile to make sure the compile command is executed using the same directory. Is there any way to pin the directory so I can simply go M-x compile RET RET?
Best regards
Use recompile instead. C-u M-x recompile will let you edit the compile command first. Either way the compile will work out of the directory the last compile was done in.
See my answer here
Directory local variables provide an easy way to trigger the compile from a parent directory of any source file in a subdirectory.
I run emacs primarily on windows.
When I have a makefile that is in a parent directory of a C module, I use this as the compile command:
cd .. && nmake <arguments here>
for example:
cd .. && nmake CONFIG=Debug PLATFORM=x64 target
Beyond that, I find that specifying the make command line that I want to run for various modules is sort of a pain. I wanted a way to attach the default compile command to the buffer being edited. So I wrote a little elisp to handle that job. I figured to insert into the header comments of each buffer a line that would stipulate my preferred compile command, like this:
compile: cd .. && nmake CONFIG=Debug PLATFORM=x64 target
And then have a piece of elisp run, before I invoke M-x compile that grabs the line and proposes it as the compile command I would like to run.
This defun pulls a line out of the header comments:
(defun cheeso-c-get-value-from-comments (marker-string line-limit)
"gets a string from the header comments in the current buffer.
This is used to extract the compile command from the comments. It
could be used for other purposes too.
It looks for \"marker-string:\" and returns the string that
follows it, or returns nil if that string is not found.
eg, when marker-string is \"compile\", and the following
string is found at the top of the buffer:
compile: cl.exe /I uthash
...then this command will return the string
\"cl.exe /I uthash\"
It's ok to have whitespace between the marker and the following
colon.
"
(let (start search-limit found)
;; determine what lines to look in
(save-excursion
(save-restriction
(widen)
(cond ((> line-limit 0)
(goto-char (setq start (point-min)))
(forward-line line-limit)
(setq search-limit (point)))
((< line-limit 0)
(goto-char (setq search-limit (point-max)))
(forward-line line-limit)
(setq start (point)))
(t ;0 => no limit (use with care!)
(setq start (point-min))
(setq search-limit (point-max))))))
;; look in those lines
(save-excursion
(save-restriction
(widen)
(let ((re-string
(concat "\\b" marker-string "[ \t]*:[ \t]*\\(.+\\)$")))
(if (and start
(< (goto-char start) search-limit)
(re-search-forward re-string search-limit 'move))
(buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))))))
Ok, now I need something to invoke that before I invoke compile.
(defun cheeso-invoke-compile-interactively ()
"fn to wrap the `compile' function. This simply
checks to see if `compile-command' has been previously set, and
if not, invokes `cheeso-guess-compile-command' to set the value.
Then it invokes the `compile' function, interactively."
(interactive)
(cond
((not (boundp 'cheeso-local-compile-command-has-been-set))
(cheeso-guess-compile-command)
(set (make-local-variable 'cheeso-local-compile-command-has-been-set) t)))
;; local compile command has now been set
(call-interactively 'compile))
Then of course, the defun that guesses the compile command:
(defun cheeso-guess-compile-command ()
"set `compile-command' intelligently depending on the
current buffer, or the contents of the current directory."
(interactive)
(set (make-local-variable 'compile-command)
(cond
(buffer-file-name
(let ((filename (file-name-nondirectory buffer-file-name)))
(cond
;; editing a C-language source file - check for an
;; explicitly-specified command
((string-equal (substring buffer-file-name -2) ".c")
(let ((explicit-compile-command
(cheeso-c-get-value-from-comments "compile" 34)))
(or explicit-compile-command
(concat "nmake " ;; assume a makefile exists
(file-name-sans-extension filename)
".exe"))))
;; editing a makefile - just run nmake
((string-equal (substring buffer-file-name -8) "makefile")
"nmake ")
;; something else - do a typical .exe build
(t
(concat "nmake "
(file-name-sans-extension filename)
".exe")))))
(t
;; punt
"nmake "))))
The final bit is to bind C-x C-e , normally bound to compile, to the wrapper defun:
(global-set-key "\C-x\C-e" 'cheeso-invoke-compile-interactively)
Now, when I do C-x C-e in the buffer, it searches for the compile command, and proposes to me the command that it finds. I can edit the proposed compile command, then press ENTER and run it.

How can I access the path to the current directory in an emacs directory variable file?

According to the Emacs documentation, Directory Variables apply to all files below a directory that contains an .dir-locals.el file.
How can I, in that file, set a variable to the full path that contains the file? For example:
((nil . ((indent-tabs-mode . t)
(my-project-path **THIS_DIRECTORY**))))
I asked myself the same question and found no solution on the web, so I think this answer may help. Actually, it turns out we can reuse dir-locals-find-file to get the directory containing the .dir-locals.el file. So here's what I found for, e.g, setting up an aspell personal dictionary dedicated to a whole directory:
((nil . ((eval . (setq ispell-personal-dictionary
(expand-file-name
".aspell_words"
(file-name-directory
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d))))))))))
Also, it seems entries are evaluated in the order they are specified, so the following code should work:
((nil . ((eval . (set (make-local-variable 'my-project-path)
(file-name-directory
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d))))))
(eval . (message "Project directory set to `%s'." my-project-path)))))
Emacs will complain about unsafe local variables (due to the eval construct), yet one can still permanently mark it safe.
Update: Since Emacs ≥ 26.3 (and maybe older versions as well), it appears that one needs to use (dir-locals-find-file "./") instead of (dir-locals-find-file ".").
I think (file-name-directory (or load-file-name buffer-file-name)) should give you the directory path.
See Link
Edit: Except it won't, because any eval expressions are evaluated in the context of the buffer whose variables are being hacked.
In my case, I wanted to locate a file that was relative to my current working directory for my repository, and the .dir-locals.el file was checked into the root, so a file "local" to the .dir-locals.el was also a file "local" to the project root.
pajato0's answer above worked for some cases, but it was also breaking other modes (like magit). I got around the issue by using the projectile package's projectile-project-root function to find my the base path for me:
((nil . ((eval . (setq cmake-ide-build-dir
(concat (projectile-project-root) "/build-make"))
))))
I've found the locate-dominating-file procedure, which comes out-of-the-box with Emacs, useful to retreive the current directory of a known file. The example below sets the guix-directory variable to the topmost directory of the project containing a .dir-locals.el file.
((nil . ((eval . (setq guix-directory
(locate-dominating-file default-directory
".dir-locals.el"))))))
It's not a safe .dir-locals.el setting, due to relying on eval, but it gets the job done.
In case it still matters, to the OP or some other, I would suggest you create a function to generate the .dir-locals.el file. Then one could write something like:
(let ((path default-directory)
file)
(setq file (format "%s/.dir-locals.el" path))
(with-temp-buffer
(insert (format "((nil . ((indent-tabs-mode . t)
(my-project-path \"%s\"))))" path))
(when (file-writable-p file)
(write-region (point-min)
(point-max)
file))))
to be executed within the project home directory.
hack-local-variables is the main function for processing all local variables, and it calls hack-dir-local-variables to deal with the .dir-locals.el file (or a dir local class variable, if you're not using that file).
The code for establishing the directory is not isolated in its own function, so we'll have to copy it out into a new function (this from GNU Emacs 24.0.95.1):
(defun my-dir-locals-dir ()
"Return the directory local variables directory.
Code taken from `hack-dir-local-variables'."
(let ((variables-file (dir-locals-find-file (or (buffer-file-name) default-directory)))
(dir-name nil))
(cond
((stringp variables-file)
(setq dir-name (file-name-directory variables-file)))
((consp variables-file)
(setq dir-name (nth 0 variables-file))))
dir-name))
If you are working on *nix, you might get the work directory by the following elisp code,
(defun get-working-directory ()
(getenv "PWD))
Btw, I have to mentioned that, (shell-command "pwd") will result in the directory where file (which is corresponding to the buffer you are currently editing).