aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBozhidar Batsov <bozhidar@batsov.dev>2026-02-16 10:11:10 +0200
committerBozhidar Batsov <bozhidar@batsov.dev>2026-02-16 10:11:10 +0200
commit8b53767bacc98e393a2d6790b0505e5acc1e0c71 (patch)
tree1a46a1921452a0fff8c4b2258f9ca259824089aa
parentf12fdae30a36e3614fa6944969bf37dec9998301 (diff)
Add subproject compile and test commands (#1653)feature/1653-compile-subproject
Add `projectile-compile-subproject` and `projectile-test-subproject` for building/testing individual modules in multi-module projects (e.g. Maven, Gradle). These commands find the nearest build file (project-file marker) between the current directory and the project root, then run the compile/test command in that directory. Keybindings: `c m c` (compile subproject), `c m t` (test subproject).
-rw-r--r--CHANGELOG.md1
-rw-r--r--projectile.el77
2 files changed, 78 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f608122..bdabbc2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/projectile.el b/projectile.el
index f6b7e63..73df351 100644
--- a/projectile.el
+++ b/projectile.el
@@ -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))
@@ -5542,6 +5569,54 @@ with a prefix ARG."
: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.
@@ -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)