8000 fix: remove maven-invoker to eliminate javax.inject leak (#144) by kawasima · Pull Request #145 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content

fix: remove maven-invoker to eliminate javax.inject leak (#144) - #145

Merged
kawasima merged 2 commits into
developfrom
feature/issue-144-remove-maven-invoker
Apr 3, 2026
Merged

kawasima merged 2 commits into
developfrom
feature/issue-144-remove-maven-invoker

Conversation

@kawasima
@kawasima kawasima commented Apr 3, 2026
Copy link
Copy Markdown
Collaborator

Summary

  • Remove maven-invoker:3.3.0 and maven-shared-utils:3.4.2 from enkan-devel
  • Rewrite MavenCompiler to use ProcessBuilder instead of maven-invoker API
  • This eliminates the transitive javax.inject:javax.inject:1 dependency that caused silent @Inject failures (fields left null at runtime when @javax.inject.Inject was used instead of @jakarta.inject.Inject)

Why not just exclude?

maven-invoker wraps ProcessBuilder internally — there's no value in keeping the dependency when we can call mvn compile directly. Removing it entirely is cleaner than adding an <exclusion>.

Test plan

  • mvn dependency:tree -pl enkan-devel | grep javax.inject — empty
  • mvn dependency:tree -pl kotowari-example | grep javax.inject — empty
  • mvn test -pl enkan-devel -am — all tests pass (MavenCompilerTest skipped per assumeTrue as before)

closes #144

🤖 Generated with Claude Code

…144)

Replace maven-invoker API with ProcessBuilder in MavenCompiler.
This removes maven-invoker:3.3.0 and maven-shared-utils:3.4.2,
which transitively brought javax.inject:javax.inject:1 onto the
classpath — causing silent injection failures when developers
accidentally used @javax.inject.Inject instead of @jakarta.inject.Inject.

closes #144

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR removes maven-invoker from enkan-devel to eliminate the transitive javax.inject dependency that could cause silent DI failures, and rewrites MavenCompiler to invoke mvn compile directly via ProcessBuilder.

Changes:

  • Removed maven-invoker and maven-shared-utils dependencies from enkan-devel.
  • Reimplemented MavenCompiler to execute Maven using ProcessBuilder and stream stdout/stderr back to the REPL transport.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
enkan-devel/src/main/java/enkan/system/devel/compiler/MavenCompiler.java Switches from Maven Invoker API to ProcessBuilder and streams process output via virtual threads.
enkan-devel/pom.xml Drops Maven Invoker-related dependencies to remove the javax.inject leak.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +39
String mvnCommand = new File(mavenHome, "bin/mvn").getAbsolutePath();
ProcessBuilder pb = new ProcessBuilder(mvnCommand, "compile");
pb.directory(new File(projectDirectory));
pb.redirectErrorStream(false);
Copilot AI Apr 3, 2026
Copy link

Choose a reason for hiding this comment

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

mvnCommand is hard-coded to ${MAVEN_HOME}/bin/mvn, which won’t exist on Windows (typically mvn.cmd/mvn.bat). The previous Maven Invoker implementation handled platform-specific launchers; consider selecting the correct executable based on OS (or falling back to mvn on PATH) to avoid breaking /compile on Windows environments.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +66
Thread stdoutReader = Thread.ofVirtual().start(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
t.send(ReplResponse.withOut(line));
}
} catch (IOException e) {
// Process ended — stop reading
}
});

Thread stderrReader = Thread.ofVirtual().start(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
t.send(ReplResponse.withErr(line));
}
} catch (IOException e) {
// Process ended — stop reading
}
Copilot AI Apr 3, 2026
Copy link

Choose a reason for hiding this comment

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

The stdout/stderr reader threads swallow all IOExceptions. That can mask real read failures (e.g., stream closed unexpectedly while the process is still running) and will silently drop Maven output. Consider capturing the exception (e.g., to an AtomicReference) and reporting it via CompileResult.failure(...) or at least forwarding an error to the Transport so failures aren’t hidden.

Copilot uses AI. Check for mistakes.
Comment on lines 69 to 82
int exitCode = process.waitFor();
stdoutReader.join();
stderrReader.join();

if (exitCode != 0) {
return CompileResult.failure(new IllegalStateException(
"Maven compile failed with exit code " + invocationResult.getExitCode()));
"Maven compile failed with exit code " + exitCode));
}
} catch (MavenInvocationException ex) {
return CompileResult.failure(ex);
} catch (IOException e) {
return CompileResult.failure(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompileResult.failure(e);
}
Copilot AI Apr 3, 2026
Copy link

Choose a reason for hiding this comment

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

If execute is interrupted while waiting, the method returns a failure but the spawned Maven process (and the reader threads) may continue running in the background. Consider ensuring the process is terminated on interruption (e.g., process.destroyForcibly() in the InterruptedException path or a finally block) to avoid orphaned builds and unexpected output after returning.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +37
String mvnCommand = new File(mavenHome, "bin/mvn").getAbsolutePath();
ProcessBuilder pb = new ProcessBuilder(mvnCommand, "compile");
Copilot AI Apr 3, 2026
Copy link

Choose a reason for hiding this comment

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

MAVEN_HOME existence is validated, but ${MAVEN_HOME}/bin/mvn itself isn’t. When it’s missing/non-executable, this will fail later with a generic IOException, which is less actionable than a targeted misconfiguration error. Consider checking mvnCommand exists (and is executable) and throwing MisconfigurationException("devel.MAVEN_HOME_NOT_SET") (or a more specific key) before starting the process.

Suggested change
String mvnCommand = new File(mavenHome, "bin/mvn").getAbsolutePath();
ProcessBuilder pb = new ProcessBuilder(mvnCommand, "compile");
File mvnCommand = new File(mavenHome, "bin/mvn");
if (!mvnCommand.exists() || !mvnCommand.isFile() || !mvnCommand.canExecute()) {
throw new MisconfigurationException("devel.MAVEN_HOME_NOT_SET");
}
ProcessBuilder pb = new ProcessBuilder(mvnCommand.getAbsolutePath(), "compile");

Copilot uses AI. Check for mistakes.
- Fall back to PATH-resolved mvn when MAVEN_HOME/M2_HOME is not set
- Select mvn.cmd on Windows for both MAVEN_HOME and PATH cases
- Validate mvn executable with canExecute() before starting process
- Report IOException from reader threads via Transport instead of swallowing
- Add pom.xml existence check with clear error message
- Destroy process and join reader threads in finally block to prevent leaks
- Use waitFor(10, MINUTES) timeout; join reader threads with 5s limit
- Fix stale assumeTrue in tests to detect mvn via PATH as well

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kawasima
kawasima merged commit 57eb459 into develop Apr 3, 2026
1 check passed
@kawasima
kawasima deleted the feature/issue-144-remove-maven-invoker branch April 3, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0