Skip to content
Alastair Rankine edited this page Mar 16, 2014 · 2 revisions

For project management I rely on the Emacs Development Environment (EDE), which is part of CEDET. This library provides a way to define projects and an interface to perform common operations on them. EDE supports different project types, some of which can be auto-loaded whenever a corresponding file is detected in the source tree (i.e. the same directory as a source file, or in a parent).

The EDE project type I use is my own, EDE-compdb. This is a package which can instantiate an EDE project based on the presence of a Compilation Database. The compilation database is in turn provided by Ninja, the build tool.

EDE-compdb is configured to load whenever a CMakeLists.txt file is detected, as follows:

(ede-add-project-autoload
 (ede-project-autoload "CMake"
                       :file 'ede-compdb
                       :proj-file "CMakeLists.txt"
                       :proj-root 'vc-project-root
                       :load-type 'my-load-cmake-project
                       :class-sym 'ede-compdb-project))

There are two functions here which are relevant, proj-root which is used to locate the root directory for a given directory, and load-type which is used to actually create the EDE project for a given directory. In the case of EDE-compdb there is a single object per project directory tree.

For proj-root I call out to the built-in vc package. This is generally sufficient for locating the root directory for my projects. However I just need to wrap it in a trivial function, as follows.

(defun vc-project-root (dir)
  "Return the root of project in DIR."
  (require 'vc)
  (let* ((default-directory dir)
         (backend (vc-deduce-backend)))
    (or (and backend (vc-call-backend backend 'root dir))
        dir)))

Creating the project is then a matter of instantiating an ede-ninja-project. This mild complexity here is due to the need to locate a build directory for the new project. With some projects I have a build subdirectory beneath the project root. With other large projects I have an entirely separate (“out of source”) directory which is on a separate filesystem. As a further complication, there may be multiple build configurations such as “Debug” and “Release”, each of which has a corresponding build directory.

My project loading function is hence rather complicated, but it does an important job. It searches for a build directory and configuration type for the given project, and instantiates the project with the relevant settings.

(defvar my-project-build-directories
  '(("None" . "build")
    ("Debug" . "build.dbg")
    ("Release" . "build.rel")
    ("RelWithDebInfo" . "build.r+d")))

(defun my-load-cmake-project (dir)
  "Creates a project for the given directory sourced at dir"
  (let* ((default-directory dir)
         (projname (file-name-nondirectory (directory-file-name dir)))
         (config-and-build-dirs
          (mapcar (lambda (c)
                    (cons (car c)
                          ;; Expand directory
                          (if (file-name-absolute-p (cdr c)) (expand-file-name projname (cdr c))
                            (expand-file-name (cdr c) dir))))
                  my-project-build-directories))
         (active-config-and-dir
          (car (cl-member-if (lambda (c)
                               (file-readable-p (expand-file-name "rules.ninja" (cdr c))))
                             config-and-build-dirs))))
    (unless active-config-and-dir
      (message "Couldn't determine build directory for project at %s" dir))
    (ede-add-project-to-global-list
     (ede-ninja-project 
      projname
      :file (expand-file-name "CMakeLists.txt" dir)
      :compdb-file (expand-file-name "rules.ninja" (cdr active-config-and-dir))
      :configuration-default (or (car active-config-and-dir) (car (car my-project-build-directories)))
      :configuration-directories (mapcar #'cdr config-and-build-dirs)
      :configurations (mapcar #'car config-and-build-dirs)
      ))))

So, when I visit a new file in Emacs, this is what happens:

  1. EDE detects the presence of a CMakeLists.txt file, which in turn triggers my project autoload code
  2. The vc-project-root function is invoked to find the project root directory
  3. The my-load-cmake-project function searches for existing build directories based on the templates in the my-project-build-directories list. The first one that contains a rules.ninja file is used.
  4. The ede-ninja-project object is instantiated with the relevant project root and build directories.
  5. The compilation database is loaded and used to ensure that the edit buffer is configured correctly for parsing

In other words, loading of the EDE project is an implicit part of visiting a file which is part of that project.

Note that EDE employs caching to ensure that subsequent files within the same project are loaded quickly without going through all of these steps.

This probably all sounds moderately complicated, but it is important to ensure that the compilation database is available when editing source files. This ensures that important compiler settings, such as the project include path, are made available to the Semantic parser and to other packages.

Clone this wiki locally