Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [#1964](https://github.com/bbatsov/projectile/issues/1964): Implement `project-name` and `project-buffers` methods for the `project.el` integration, so that code using `project.el` APIs returns correct results for Projectile-managed projects.
* [#1837](https://github.com/bbatsov/projectile/issues/1837): Add `eat` project terminal commands with keybindings `x x` and `x 4 x`.
* Add keybinding `A` (in the projectile command map) and a menu entry for `projectile-add-known-project`.
* [#1653](https://github.com/bbatsov/projectile/issues/1653): Add `projectile-compile-subproject` and `projectile-test-subproject` commands for building/testing individual modules in multi-module projects (e.g. Maven, Gradle). Bound to `c m c` and `c m t`.

### Bugs fixed

Expand Down
77 changes: 77 additions & 0 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -5372,6 +5372,33 @@ project of that type"
'(compile-history . 1)
'compile-history))))

(defun projectile-subproject-root ()
"Find the root of the nearest subproject containing the current file.
Walk up from `default-directory' looking for the project type's
`:project-file' marker, stopping at the project root. Returns the
directory containing the nearest marker, or signals an error if no
subproject is found between the current directory and the project root."
(let* ((project-root (projectile-acquire-root))
(type (projectile-project-type project-root))
(project-file (projectile-project-type-attribute type 'project-file))
(markers (if (listp project-file) project-file (list project-file)))
(dir (file-name-directory (or (buffer-file-name) default-directory)))
(result nil))
(unless markers
(user-error "Project type `%s' has no project-file defined" type))
;; Walk up from current directory, stop at (but include) project root.
(while (and (not result)
dir
(string-prefix-p project-root dir))
(when (seq-some (lambda (m)
(file-exists-p (expand-file-name m dir)))
markers)
(setq result dir))
(let ((parent (file-name-directory (directory-file-name dir))))
(setq dir (unless (string= parent dir) parent))))
(or result
(user-error "No subproject found between current directory and project root"))))

(defun projectile-compilation-dir ()
"Retrieve the compilation directory for this project."
(let* ((project-root (projectile-acquire-root))
Expand Down Expand Up @@ -5541,6 +5568,54 @@ with a prefix ARG."
:save-buffers t
:use-comint-mode projectile-test-use-comint-mode)))

;;;###autoload
(defun projectile-compile-subproject (arg)
"Run compilation in the nearest subproject.
Find the closest build file (e.g. pom.xml, build.gradle) between the
current directory and the project root, then run the project's compile
command there. This is useful for multi-module projects where building
a single module is faster than building the entire project.

Normally you'll be prompted for a compilation command, unless
variable `compilation-read-command'. You can force the prompt
with a prefix ARG."
(interactive "P")
(let* ((subproject-root (projectile-subproject-root))
(project-root (projectile-acquire-root))
(projectile-project-compilation-dir
(file-relative-name subproject-root project-root))
(command (projectile-compilation-command (projectile-compilation-dir)))
(command-map (if (projectile--cache-project-commands-p) projectile-compilation-cmd-map)))
(projectile--run-project-cmd command command-map
:show-prompt arg
:prompt-prefix "Compile subproject command: "
:save-buffers t
:use-comint-mode projectile-compile-use-comint-mode)))

;;;###autoload
(defun projectile-test-subproject (arg)
"Run tests in the nearest subproject.
Find the closest build file (e.g. pom.xml, build.gradle) between the
current directory and the project root, then run the project's test
command there. This is useful for multi-module projects where testing
a single module is faster than testing the entire project.

Normally you'll be prompted for a compilation command, unless
variable `compilation-read-command'. You can force the prompt
with a prefix ARG."
(interactive "P")
(let* ((subproject-root (projectile-subproject-root))
(project-root (projectile-acquire-root))
(projectile-project-compilation-dir
(file-relative-name subproject-root project-root))
(command (projectile-test-command (projectile-compilation-dir)))
(command-map (if (projectile--cache-project-commands-p) projectile-test-cmd-map)))
(projectile--run-project-cmd command command-map
:show-prompt arg
:prompt-prefix "Test subproject command: "
:save-buffers t
:use-comint-mode projectile-test-use-comint-mode)))

;;;###autoload
(defun projectile-install-project (arg)
"Run project install command.
Expand Down Expand Up @@ -6317,6 +6392,8 @@ Magit that don't trigger `find-file-hook'."
(define-key map (kbd "c i") #'projectile-install-project)
(define-key map (kbd "c t") #'projectile-test-project)
(define-key map (kbd "c r") #'projectile-run-project)
(define-key map (kbd "c m c") #'projectile-compile-subproject)
(define-key map (kbd "c m t") #'projectile-test-subproject)
;; TODO: Legacy keybindings that will be removed in Projectile 3
(define-key map (kbd "C") #'projectile-configure-project)
(define-key map (kbd "K") #'projectile-package-project)
Expand Down