Skip to content

feat: Support multi-module Java projects in tasks#244

Open
drmaniac wants to merge 1 commit intozed-extensions:mainfrom
drmaniac:multi-module-support
Open

feat: Support multi-module Java projects in tasks#244
drmaniac wants to merge 1 commit intozed-extensions:mainfrom
drmaniac:multi-module-support

Conversation

@drmaniac
Copy link
Copy Markdown

@drmaniac drmaniac commented May 8, 2026

Previously, Zed's Java tasks for running and testing applications did not correctly support multi-module Maven or Gradle projects. When executing a task from a file within a submodule, the commands would attempt to run at the root level, potentially leading to incorrect execution or failures.

This change introduces logic to dynamically determine the nearest Maven pom.xml or Gradle build.gradle/settings.gradle file relative to the currently active file. It then modifies the Maven and Gradle commands to target the specific module.

For Maven, this involves adding the -pl <module> and -am flags. For Gradle, it prepends the module path (e.g., :module-name:) to the run or test command. This ensures that tasks are executed within the context of the correct module, improving the developer experience for multi-module Java projects.

@cla-bot cla-bot Bot added the cla-signed label May 8, 2026
@tartarughina
Copy link
Copy Markdown
Collaborator

tartarughina commented May 8, 2026

Thanks for the PR, could you provide steps to test these changes or add how you tested them?

@drmaniac drmaniac force-pushed the multi-module-support branch from 5b53433 to 0e45c2c Compare May 8, 2026 20:57
Previously, Zed's Java tasks for running and testing applications
did not correctly support multi-module Maven or Gradle projects.
When executing a task from a file within a submodule, the commands
would attempt to run at the root level, potentially leading to
incorrect execution or failures.

This change introduces logic to dynamically determine the nearest
Maven `pom.xml` or Gradle `build.gradle`/`settings.gradle` file
relative to the currently active file. It then modifies the Maven
and Gradle commands to target the specific module.

For Maven, this involves adding the `-pl <module>` and `-am` flags.
For Gradle, it prepends the module path (e.g., `:module-name:`) to
the `run` or `test` command. This ensures that tasks are executed
within the context of the correct module, improving the developer
experience for multi-module Java projects.
@drmaniac drmaniac force-pushed the multi-module-support branch from 0e45c2c to bb03226 Compare May 8, 2026 21:06
@drmaniac
Copy link
Copy Markdown
Author

drmaniac commented May 8, 2026

Hi, I have added integration tests to this project (I wasn't sure if these are allowed/expected for extensions).

In addition, I apologize that the original PR had an issue with the Maven part; I have fixed it.

I also tested the changes in tasks.json by adding them as custom tasks in my local Zed config. I tried it out on a complex Gradle project with composite and multiple modules, and a Maven project with multiple modules.

Copy link
Copy Markdown
Collaborator

@tartarughina tartarughina May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never thought of creating a temp project like you are doing here. This is a really nice idea to make testing more robust. I would although expand the temp projects to cover all instances covered by our tasks, for example nesting and single module project


let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json");
let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json");
let mut test_command = tasks[1]["command"].as_str().expect("Command is not a string").to_string();
Copy link
Copy Markdown
Collaborator

@tartarughina tartarughina May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are testing only tasks at indices 0 and 1 on multi module projects, missing the remaining tasks and not checking normal projects. Also let's consider the possibility of tasks being reordered, tests would start to fail. Maybe we could retrieve the command by tag to make them more robust.

Comment thread languages/java/tasks.json
{
"label": "Run $ZED_CUSTOM_java_class_name",
"command": "pkg=\"${ZED_CUSTOM_java_package_name:}\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean compile exec:java -Dexec.mainClass=\"$c\"; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;",
"command": "pkg=\"$ZED_CUSTOM_java_package_name\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test-compile exec:java -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; else $CMD clean test-compile -pl \"$m\" -am && $CMD exec:java -pl \"$m\" -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using test-compile for running the main class alongside providing the tests' scope?

Comment thread languages/java/tasks.json
{
"label": "Run tests",
"command": "if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean test; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD test; else >&2 echo 'No build tool found'; exit 1; fi;",
"command": "f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test; else $CMD clean test-compile -pl \"$m\" -am && $CMD test -pl \"$m\"; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:test; else >&2 echo 'No build tool found'; exit 1; fi;",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we first compiling the test to the run it for maven? Isn't that redundant?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants