Giter Club home page Giter Club logo

cmake-build.el's Introduction

cmake-build.el

This is an emacs extension for building projects using cmake. It uses projectile for a few basic features. Main features:

  • Support for multiple build profiles/directories
  • Support for multiple targets
  • Support for multiple run configurations
  • Quick commands for doing various things (compile, run, cmake clean, etc)
  • Graphical menu for selecting profiles, configurations, etc
  • Calls cmake rather than relying on specific build systems, so anything should work (e.g., GNU make, ninja, etc)

Menu:

Menu screenshot

Build window:

Full screenshot

Why

Basically, see the feature list. Projectile has some basic support for "build" but no comprehension of more complex project builds. I almost always have numerous build configurations with multiple compilers (clang, gcc for each of Release, Debug, etc.). My projects often have numerous targets and sometimes multiple configurations I switch between regularly... for instance, a viewer might be configured to run with multiple different test files.

Basic Setup

This comes with a very tiny example project, in example/. You should be able to load mytest.c and use the commands, hotkeys (if you bind them), or menu (likewise) to invoke cmake-build and compile/run.

Projectile

First, need a project root. This uses projectile.

I highly recommend creating the file .projectile-root in the root of your project, and using the following configuration:

(setq projectile-project-root-files-top-down-recurring
  '(".projectile-root"))
(setq projectile-project-root-files-bottom-up nil)

This causes projectile to base the root directory off one thing: the existence of the file .projectile-root.

cmake-build.el

Next, you need a .cmake-build.el in the project root. This describes a number of things:

  • Options to pass to cmake
  • How to configure each of your build directories
  • Additional targets you want to consider (clean is builtin)
  • Run configurations: a target to build, and a binary to run, where to run it, and with what options.

For example, suppose we have a simple setup that builds mytest. We might set up the following .cmake-build.el:

((cmake-build-cmake-profiles
  (release "-DCMAKE_BUILD_TYPE=Release")
  (debug "-DCMAKE_BUILD_TYPE=Debug"))

 (cmake-build-run-configs
  (mytest
   (:build "mytest")
   (:run "" "./mytest" ""))))

First, you should do M-x cmake-build-set-cmake-profile, and type or select the profile you want (release or debug).

Now you can run M-x cmake-build-clear-cache-and-configure. Since this is a mouthful to type (fingerful?), I generally bind it to C-S-F7. This will create build.release (or build.debug if you selected that), if it doesn't exist, remove the cmake cache, and run cmake with your configuration.

If you don't want to fully reconfigure, M-x cmake-build-run-cmake, which I bind to S-F7, will just run cmake with the current profile. This requires the directory etc exist.

Note: Unlike emacs's "project-local variables," nothing is evaluated from .cmake-build.el. This is READ only (in the lisp sense). Emacs's reader does not appear to allow arbitrary evaulation during read (e.g. Common Lisp's #.(form)).

Usage

Building and Running

Before you build, you need to select a target. Target management is done through run configurations, as set up above. cmake-build.el does not parse your CMakeLists.txt.

  • Pick your run configuration using M-x cmake-build-set-config
  • (Optional) Build using M-x cmake-build-current
  • Run using M-x cmake-build-run

I bind build to F7 and run to F5. By default, with cmake-build-before-run set to t, running will first invoke build, so if you want to run you can skip the build step.

Either of these commands will open a small shell process window below the current one (size configurable via cmake-build-run-window-size) showing the output.

Graphical Menu

Most of the commands are available via the graphical menu. This is available by using M-x cmake-build-menu, which I bind to C-c b. This will let you graphically select profiles, run configurations, other targets, and a few basic "tools" (such as the clean target).

Debugging

A simple option has been added to call the emacs M-x gdb with the current target and default/basic options. One can run this by M-x cmake-build-debug or via the cmake-build menu.

There are no options for this right now; perhaps in the future.

Extended Configuration

Run configurations

Run configurations pair a target and a command invocation. Target configuration is straightforward enough:

(:build "targetname")

Where, of course, "targetname" should correspond to a target cmake understands.

Run configurations are slightly more complex:

(:run "path" "command" "parameters")

The "path" parameter will be the current working directory. This should be relative to the build root. (Mechanically, it will be bound to default-directory during the invocation, i.e., it is the actual cwd, and not prepended to the command).

The "command" parameter is what is passed to the shell, along with "parameters". For instance, we could have multiple configurations for mytest:

(( .. )

 (cmake-build-run-configs
  (mytest
   (:build "mytest")
   (:run "" "./mytest" ""))
  (mytest-with-data
   (:build "mytest")
   (:run "" "./mytest" "data.txt"))
  (mytest-output
   (:build "mytest")
   (:run "" "./mytest" "data.txt -o output.txt"))
  : .. and so on ..
  )

Additionally, one can specify (:env ...) to set various environment variables:

  (mytest
   (:build "mytest")
   (:run "" "./mytest" "")
   (:env "LSAN_OPTIONS=suppressions=..." "SOMEVAR=...")

This applies only while running (or debugging).

Other targets

It is often useful to build "other" targets with cmake; for instance, you may create a target with cmake's add_custom_target(TARGET-NAME ..). You can specify this in .cmake-build.el:

;;; Order doesn't matter here; context for illustration

((cmake-build-cmake-profiles ...)
 :
 (cmake-build-other-targets "some-target" "another-target" ...)

 : etc
 )

This will populate the "Other targets" menu.

Non-Project-Root CMakeLists.txt

You may wish to have a project root that is not the source root, and does not have a CMakeLists.txt. You can configure this in the .cmake-build.el using cmake-build-source-root:

(...
 (cmake-build-source-root "src")
 ...)

This will append src/ to the project root path, and search for your root CMakeLists.txt there.

Note: If you merely wish to build out of the project tree, see the next option.

Out-of-tree builds

You may wish to build in another location. By default, builds happen in a profile-specific build directory in the root of the project. You may set a new root for builds by using M-x cmake-build-set-project-build-root and specifying a path. This is also available under the "Tools" menu with "Set project build root".

Projectile modeline

If you want to show the current run configuration in the modeline, you could use projectile's modeline function:

(defun projectile-custom-mode-line-function ()
  (if-let ((name (cmake-build-get-run-config-name)))
      (format "[%s:%s]"
              (projectile-project-name)
              (cmake-build-get-run-config-name))
    (projectile-default-mode-line)))

(setq projectile-mode-line-function 'projectile-custom-mode-line-function)

The critical call here is (cmake-build-get-run-config-name).

Alternate build directory name format

By default, the build directory is named in the form build.<profile>. For instance, if your profile is gcc-debug, the build directory is build.gcc-debug.

If you wish to alter this, you may write a new elisp function in the following form:

;;; This will make the build directory "gcc-debug.build" instead
(defun my-dir-name-function (project-root profile)
  (concat profile ".build"))

Note that this should be a single filename without / or other path. The project root is given for convenience in case one wishes to do anything interesting based on the source (e.g., look up a VCS tag or revision).

Custom variables

A number of variables are available for customization:

  • cmake-build-local-options-file: This is the file where "local" options are stored in your ~/.emacs.d/.
  • cmake-build-run-window-autoswitch: (Default: set) If set, this will automatically switch between compile and run output.
  • cmake-build-before-run: (Default: set) If set, this will try to build before running. If build fails, it will not run.
  • cmake-build-display-type: (Default: split) How to display cmake-build output; 'split' will split the window (using cmake-build window splitting options), 'frame' will create a new frame. In all cases, the buffers will be reused if they are visible, regardless of current display type.
  • cmake-build-run-window-size: (Default: 20) This is the size in lines of the split build/run windows.
  • cmake-build-split-threshold: (Default: 40%) Percentage after which the window will not be split; e.g. if the build window is set to 20, and the current window is 25, this is 80% of the current window. By default, it would simply use the other window in this case.
  • cmake-build-never-split: (Default: nil) If set, this will never split the window, and just use the default.
  • cmake-build-switch-to-build: (Default: nil) If set, this will also make the build window current. By default, it leaves the current window active.
  • cmake-build-export-compile-commands: (Default: nil) If set, this will ask cmake to export the compile commands during configuration, and create a symbolic link from project-root to the compile_commands.json file created in the build directory. This will erase any previously created compile_commands.json file in project-root directory.

Local settings

A number of local settings are stored by default in your ~/.emacs.d/cmake-build-options.el. These are separate from custom variables (which are stored in whatever custom file you've configured). This is to support "shared" custom variables (i.e., you check them into a repository), but "local" cmake-build.el settings. You can exclude this file from VCS, and not have a dirty tree everytime you switch configurations and profiles.

Note this file is similar to custom data and is not intended to be directly edited. It is likely to be overwritten if you make changes.

The variables are as follows:

  • cmake-build-profile: The current profile
  • cmake-build-options: Additional options passed to cmake, e.g. -j 4. You can set this via the menu or M-x cmake-build-set-options.
  • cmake-build-run-config: The current run configuration.
  • cmake-build-project-root: This, if set, will force a project root, ignoring projectile. This may be useful if you're working in multiple directories but only want to build in one specific root. You can set this by M-x cmake-build-set-project-root. Note, this does not override projectile itself, just cmake-build.el.

Credits

  • @dlyr: initial implementation of COMPILE, used as a reference, contribs
  • @laurynas-biveinis: General testing, correctness, contribs

cmake-build.el's People

Contributors

dlyr avatar laurynas-biveinis avatar rpav avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

cmake-build.el's Issues

Symlink compile-commands.json automatically

If CMake is configured with CMAKE_EXPORT_COMPILE_COMMANDS, then it will produce compile_commands.json in the build directory. If Emacs is configured with LSP support, e.g. with clangd, then opening a C or C++ file in the project will start the language server, which will look for compile_commands.json in the same directory (and not find it since it's in the build dir).

If cmake-build.el symlinked compile_commands.json from the build dir to the source dir on cmake-build-set-cmake-profile invocations, then we'd have a seamless LSP/cmake-build.el integration.

cmake-build-options.el not persistent

Hi,
If I understand well, I can set some persistent local (to my computer) options in the file ~/emacs.config.dir.d./cmake-build-options.el
This file was created the fist time I open a project after cmake-build installation.
I set some variable, ie replace (:build-options "") by `(:build-options "-j4"), save and quit emacs.
But this is not taken into account, and overwritten when I close emacs after having a project open.

Auto run exec/config

I have adapted my old "run target" emacs package to work with cmake-build.
You can have a look here file rt-cmake-build.el
It works flawlessly with my wip branch of cmake-build (with two alternative for run and debug that takes command and working dir as input) here
I think the integration should be done easily, but without having to redefine you function, just add one version with command and working dir as argument, and a helper that call this function with default parameter to work as now in master.
Also I hardcoded the generator, which is not a good idea, but for testing only. Since I read the target from a codeblock file, I need it to be generated (only once) maybe a good solution is to add one call to cmake to generate such file ?

you have to include rt-cmake-build.el to your emacs configuration to test, then call rt-run or rt-run-debug and have auto completion of available programs.

Feel free to comment so I can improve the package/integration (you can also simple add this functionality to cmake-build from my code, in this case reference will be appreciated)

If only one run config defined, default to it

If a project .cmake-build.el has a single run config defined, then cmake-build-run will still refuse to run it without going through cmake-build-set-config first (which would present two options: "nil" and that sole defined config). IMHO it would follow the principle of least surprise to simply default to that config instead, saving a command invocation.

Show current CMake configuration and test target in the Projectile mode line

First of all, thank you for this package. I have been looking for this functionality for quite some time.

It might be hard to remember which CMake configuration and which test target is currently active (i.e. what will happen on cmake-build-current and cmake-build-run). Since this package already integrates with Projectile somewhat for getting project roots, it could integrate with its mode line facilities, using projectile-mode-line-function, as well to show current configuration too (right now it shows "[project name:cmake]"). For example "[project name:cmake:debug:all-tests]".

Is compilation buffer in comint mode on purpose?

Regular M-x compile creates a non-comint, that is, non-interactive compilation buffer. C-u M-x compile creates a comint one, i.e. where input is possible too.
cmake-build.el always sets up the latter - comint compile, is it on purpose?

One practical difference between the two is that compilation-filter defun and associated compilation-filter-hook do not get called in comint mode.

If it's not on purpose, please consider using regular compile instead:

modified   cmake-build.el
@@ -278,7 +278,7 @@ use Projectile to determine the root on a buffer-local basis, instead.")
                  (symbol-name cmake-build-profile))
       ;; compile saves buffers; rely on this now
       (let* ((compilation-buffer-name-function #'cmake-build--build-buffer-name))
-        (cl-flet ((run-compile () (compile (concat "time " command) t)))
+        (cl-flet ((run-compile () (compile (concat "time " command))))
           (let ((w (get-buffer-window buffer-name t)))
             (if (and w (not (eql (get-buffer-window) w)))
                 (if cmake-build-switch-to-build

multiple compilations buffer creation fail

I don't know what is the expected bahavior, but when I open multiple projects, the first compilation create a buffer in the bottom, the second one open another buffer on the right (even if compilation is finished, maybe not enough space to split the window, but I wouldn't expect another window to raise).
Screenshot below. After some tests I find that if the emacs window is narrow, then it works ... each compiles replace bottom buffer (if it was not on the right beforehand, i.e. if I call cmake-build-delete-current-windows)

Capture d’écran de 2020-02-17 20-21-37

cmake-build-never-split nil errors in cmake-build--compile again

The return of #10:

Debugger entered--Lisp error: (error "No buffer named *Build unodb/gcc-debug-asan: test*")
  set-buffer("*Build unodb/gcc-debug-asan: test*")
  (save-current-buffer (set-buffer buffer-name) (set (make-local-variable (quote compilation-directory)) actual-directory) (set (make-local-variable (quote default-directory)) actual-directory))
  (if (get-buffer-process buffer-name) (message "Already building %s/%s" (projectile-project-name) (symbol-name cmake-build-profile)) (save-current-buffer (set-buffer buffer-name) (set (make-local-variable (quote compilation-directory)) actual-directory) (set (make-local-variable (quote default-directory)) actual-directory)) (let* ((compilation-buffer-name-function (function cmake-build--build-buffer-name))) (let* ((--cl-run-compile-- (function (lambda nil (compile ...))))) (progn (let ((w (get-buffer-window buffer-name t))) (if (and w (not ...)) (if cmake-build-switch-to-build (progn ... ...) (let ... ...)) (funcall --cl-run-compile--))))) (if sentinel (progn (let ((process (get-buffer-process buffer-name))) (if (process-live-p process) (progn (set-process-sentinel process ...)))))) (save-current-buffer (set-buffer buffer-name) (mapcar (function (lambda (w) (set-window-point w (point-max)))) (get-buffer-window-list buffer-name nil t)) (visual-line-mode 1))))
  (let* ((did-split (cmake-build--split-to-buffer buffer-name other-buffer-name)) (display-buffer-alist (if did-split (cons (list buffer-name (function display-buffer-no-window)) display-buffer-alist) display-buffer-alist)) (actual-directory default-directory)) (if (get-buffer-process buffer-name) (message "Already building %s/%s" (projectile-project-name) (symbol-name cmake-build-profile)) (save-current-buffer (set-buffer buffer-name) (set (make-local-variable (quote compilation-directory)) actual-directory) (set (make-local-variable (quote default-directory)) actual-directory)) (let* ((compilation-buffer-name-function (function cmake-build--build-buffer-name))) (let* ((--cl-run-compile-- (function (lambda nil ...)))) (progn (let ((w ...)) (if (and w ...) (if cmake-build-switch-to-build ... ...) (funcall --cl-run-compile--))))) (if sentinel (progn (let ((process ...)) (if (process-live-p process) (progn ...))))) (save-current-buffer (set-buffer buffer-name) (mapcar (function (lambda (w) (set-window-point w ...))) (get-buffer-window-list buffer-name nil t)) (visual-line-mode 1)))))

The culprit is recently added

      (with-current-buffer buffer-name
        (setq-local compilation-directory actual-directory)
        (setq-local default-directory actual-directory))

where buffer-name is nil.

If I am not misunderstanding how elisp local bindings of global vars work, the following should work (it resolves the immediate error for me, but gives "Unused lexical variable `variable compilation-directory'"):

modified   cmake-build.el
@@ -278,11 +278,10 @@ use Projectile to determine the root on a buffer-local basis, instead.")
         (message "Already building %s/%s"
                  (projectile-project-name)
                  (symbol-name cmake-build-profile))
-      (with-current-buffer buffer-name
-        (setq-local compilation-directory actual-directory)
-        (setq-local default-directory actual-directory))
       ;; compile saves buffers; rely on this now
-      (let* ((compilation-buffer-name-function #'cmake-build--build-buffer-name))
+      (let* ((compilation-buffer-name-function #'cmake-build--build-buffer-name)
+             (compilation-directory actual-directory)
+             (default-directory actual-directory))
         (cl-flet ((run-compile () (compile (concat "time " command))))
           (let ((w (get-buffer-window buffer-name t)))
             (if (and w (not (eql (get-buffer-window) w)))

cmake-build-never-split set to nil results in nil buffer window in cmake-build--compile

My frame geometry is two vertical windows. I set cmake-build-never-split to nil thinking that it will give me M-x compile behaviour. But what happens instead in cmake-build--compile is that split does not happen but the defun assumes the existence of the compilation window, which was never created. Then (let ((w (get-buffer-window buffer-name t)) results in w set to nil, which is invalid arg value for with-selected-window, throwing a Lisp error somewhere in it.

For me the easiest workaround to get fully-M-x-compile-like behaviour and never split windows appears to run (run-compile) without any window/buffer manipulation at all. Obviously this would need hiding behind some customisation:

      ;; compile saves buffers; rely on this now
      (let* ((compilation-buffer-name-function #'cmake-build--build-buffer-name))
        (cl-flet ((run-compile () (compile (concat "time " command) t)))
          (run-compile))

Feature request: out of source build dir and git branch tag

I usually set my build directory on another hard drive (ssd)
I do not figure out how to do that with cmake-build.el any idea ?
Also I want to add the current git branch name to the build dir (like the release type), maybe this feature is not used by alot of people, but I work on project where I had to switch and build different git branches frequently.
I'm not sure on how to add theses features, but I can take some time to try it if PR are welcome and if I had some hints on how to go in the right direction.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.