Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CMake-presets. #1656

Merged
merged 1 commit into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* Add `projectile-update-project-type-function` for updating the properties of existing project types
* [#1658](https://github.com/bbatsov/projectile/pull/1658): New command `projectile-reset-known-projects`.
* [#1656](https://github.com/bbatsov/projectile/pull/1656): Add support for CMake configure, build and test presets. Enabled by setting `projectile-cmake-presets` to non-nil, disabled by default.

### Changes

Expand All @@ -14,6 +15,7 @@
* Rename `projectile-project-root-files-functions` to `projectile-project-root-functions`.
* [#1647](https://github.com/bbatsov/projectile/issues/1647): Use "-B" in the mvn commands to avoid ANSI coloring clutter in the compile buffer
* [#1657](https://github.com/bbatsov/projectile/pull/1657): Add project detection for Debian packaging directories.
* [#1656](https://github.com/bbatsov/projectile/pull/1656): CMake compilation-dir removed to accomodate preset support, commands adjusted to run from project-root, with "build" still being the default build-directory. The non-preset test-command now uses "cmake" with "--target test" instead of "ctest".

### Bugs fixed

Expand Down
8 changes: 8 additions & 0 deletions doc/modules/ROOT/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -501,3 +501,11 @@ NOTE: The project name & type will not appear when editing remote files
(via TRAMP), as recalculating the project name is a fairly slow operation there
and would slow down a bit opening the files. They will also not appear for
non-file buffers, as they get updated via `find-file-hook`.

== Project-type-specific Configuration

=== CMake

Projectile supports https://cmake.org/cmake/help/git-stage/manual/cmake-presets.7.html[CMake presets]. Preset support is disabled by default, but can be enabled by setting `projectile-enable-cmake-presets` to non-nil. With preset-support enabled Projectile will parse the preset files and present the command-specific presets when executing a lifecycle command. In addition a `*no preset*` option is included for entering the command manually.

NOTE: Preset support requires a CMake version that supports preset and for `json-parse-buffer` to be available.
3 changes: 3 additions & 0 deletions doc/modules/ROOT/pages/projects.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ are the configuration files of various build tools. Out of the box the following
| Makefile
| Make

| CMakeLists.txt
| CMake

| WORKSPACE
| Bazel workspace file

Expand Down
155 changes: 149 additions & 6 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -2776,6 +2776,150 @@ test/impl/other files as below:
:type 'function
:package-version '(projectile . "1.0.0"))

;;;; Constant signifying opting out of CMake preset commands.
(defconst projectile--cmake-no-preset "*no preset*")

(defun projectile--cmake-version ()
"Compute CMake version."
(let* ((string (shell-command-to-string "cmake --version"))
(match (string-match "^cmake version \\(.*\\)$" string)))
(when match
(version-to-list (match-string 1 string)))))

(defun projectile--cmake-check-version (version)
"Check if CMake version is at least VERSION."
(and
(version-list-<= version (projectile--cmake-version))))

(defconst projectile--cmake-command-presets-minimum-version-alist
'((:configure-command . (3 19))
(:compile-command . (3 20))
(:test-command . (3 20))))

(defun projectile--cmake-command-presets-supported (command-type)
"Check if CMake supports presets for COMMAND-TYPE."
(let ((minimum-version
(cdr (assoc command-type projectile--cmake-command-presets-minimum-version-alist))))
(projectile--cmake-check-version minimum-version)))

(defun projectile--cmake-read-preset (filename)
"Read CMake preset from FILENAME."
(when (file-exists-p filename)
(with-temp-buffer
(insert-file-contents filename)
(when (functionp 'json-parse-buffer)
(json-parse-buffer :array-type 'list)))))

(defconst projectile--cmake-command-preset-array-id-alist
'((:configure-command . "configurePresets")
(:compile-command . "buildPresets")
(:test-command . "testPresets")))

(defun projectile--cmake-command-preset-array-id (command-type)
"Map from COMMAND-TYPE to id of command preset array in CMake preset."
(cdr (assoc command-type projectile--cmake-command-preset-array-id-alist)))

(defun projectile--cmake-command-presets (filename command-type)
"Get CMake COMMAND-TYPE presets from FILENAME."
(when-let ((preset (projectile--cmake-read-preset (projectile-expand-root filename))))
(cl-remove-if
(lambda (preset) (equal (gethash "hidden" preset) t))
(gethash (projectile--cmake-command-preset-array-id command-type) preset))))

(defun projectile--cmake-all-command-presets (command-type)
"Get CMake user and system COMMAND-TYPE presets."
(projectile-flatten
(mapcar (lambda (filename) (projectile--cmake-command-presets filename command-type))
'("CMakeUserPresets.json" "CMakeSystemPresets.json"))))

(defun projectile--cmake-command-preset-names (command-type)
"Get names of CMake user and system COMMAND-TYPE presets."
(mapcar (lambda (preset)
(gethash "name" preset))
(projectile--cmake-all-command-presets command-type)))

(defcustom projectile-enable-cmake-presets nil
"Enables configuration with CMake presets.

When `projectile-enable-cmake-presets' is non-nil, CMake projects can
be configured, built and tested using presets."
:group 'projectile
:type 'boolean
:package-version '(projectile . "2.4.0"))

(defun projectile--cmake-use-command-presets (command-type)
"Test whether or not to use command presets for COMMAND-TYPE.

Presets are used if `projectile-enable-cmake-presets' is non-nil, and CMake
supports presets for COMMAND-TYPE, and `json-parse-buffer' is available."
(and projectile-enable-cmake-presets
(projectile--cmake-command-presets-supported command-type)
(functionp 'json-parse-buffer)))

(defun projectile--cmake-select-command (command-type)
"Select a CMake command preset or a manual CMake command.

The selection is done like this:

- If `projectile--cmake-use-commands-presets' for COMMAND-TYPE returns true, and
there is at least one preset available for COMMAND-TYPE, the user is prompted to
select a name of a command preset, or opt a manual command by selecting
`projectile--cmake-no-preset'.

- Else `projectile--cmake-no-preset' is used."
(if-let ((use-presets (projectile--cmake-use-command-presets command-type))
(preset-names (projectile--cmake-command-preset-names command-type)))
(projectile-completing-read
"Use preset: "
(append preset-names `(,projectile--cmake-no-preset)))
projectile--cmake-no-preset))

(defconst projectile--cmake-manual-command-alist
'((:configure-command . "cmake -S . -B build")
(:compile-command . "cmake --build build")
(:test-command . "cmake --build build --target test")))

(defun projectile--cmake-manual-command (command-type)
"Create maunual CMake COMMAND-TYPE command."
(cdr (assoc command-type projectile--cmake-manual-command-alist)))

(defconst projectile--cmake-preset-command-alist
'((:configure-command . "cmake . --preset %s")
(:compile-command . "cmake --build --preset %s")
(:test-command . "ctest --preset %s")))

(defun projectile--cmake-preset-command (command-type preset)
"Create CMake COMMAND-TYPE command using PRESET."
(format (cdr (assoc command-type projectile--cmake-preset-command-alist)) preset))

(defun projectile--cmake-command (command-type)
"Create a CMake COMMAND-TYPE command.

The command is created like this:

- If `projectile--cmake-select-command' returns `projectile--cmake-no-preset'
a manual COMMAND-TYPE command is created with
`projectile--cmake-manual-command'.

- Else a preset COMMAND-TYPE command using the selected preset is created with
`projectile--cmake-preset-command'."
(let ((maybe-preset (projectile--cmake-select-command command-type)))
(if (equal maybe-preset projectile--cmake-no-preset)
(projectile--cmake-manual-command command-type)
(projectile--cmake-preset-command command-type maybe-preset))))

(defun projectile--cmake-configure-command ()
"CMake configure command."
(projectile--cmake-command :configure-command))

(defun projectile--cmake-compile-command ()
"CMake compile command."
(projectile--cmake-command :compile-command))

(defun projectile--cmake-test-command ()
"CMake test command."
(projectile--cmake-command :test-command))

;;; Project type registration
;;
;; Project type detection happens in a reverse order with respect to
Expand Down Expand Up @@ -2836,12 +2980,11 @@ test/impl/other files as below:
:install "make install")
(projectile-register-project-type 'cmake '("CMakeLists.txt")
:project-file "CMakeLists.txt"
:compilation-dir "build"
:configure "cmake %s -B %s"
:compile "cmake --build ."
:test "ctest"
:install "cmake --build . --target install"
:package "cmake --build . --target package")
:configure #'projectile--cmake-configure-command
:compile #'projectile--cmake-compile-command
:test #'projectile--cmake-test-command
:install "cmake --build build --target install"
:package "cmake --build build --target package")
;; PHP
(projectile-register-project-type 'php-symfony '("composer.json" "app" "src" "vendor")
:project-file "composer.json"
Expand Down