From ddf070ece85b7817d4a502fd8799257064c983d5 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 5 Jan 2021 15:25:02 +0100 Subject: [PATCH] Use an agent to support processes launched with redirected io, fixes #241 --- .gitignore | 1 + agent/pom.xml | 81 +++++++++++++ .../java/org/mvndaemon/mvnd/agent/Agent.java | 64 ++++++++++ .../mvnd/client/DaemonConnector.java | 28 ++++- dist/pom.xml | 10 +- dist/src/main/provisio/maven-distro.xml | 4 +- helper/pom.xml | 33 ++++++ .../org/mvndaemon/mvnd/pump/AgentHelper.java | 29 +++++ .../mvndaemon/mvnd/it/JUnitPlatformTest.java | 53 +++++++++ .../projects/junit-platform/.mvn/maven.config | 3 + .../src/test/projects/junit-platform/pom.xml | 109 ++++++++++++++++++ .../org/mvndaemon/mvnd/test/GreetingTest.java | 27 +++++ pom.xml | 27 ++++- 13 files changed, 461 insertions(+), 8 deletions(-) create mode 100644 agent/pom.xml create mode 100644 agent/src/main/java/org/mvndaemon/mvnd/agent/Agent.java create mode 100644 helper/pom.xml create mode 100644 helper/src/main/java/org/mvndaemon/mvnd/pump/AgentHelper.java create mode 100644 integration-tests/src/test/java/org/mvndaemon/mvnd/it/JUnitPlatformTest.java create mode 100644 integration-tests/src/test/projects/junit-platform/.mvn/maven.config create mode 100644 integration-tests/src/test/projects/junit-platform/pom.xml create mode 100644 integration-tests/src/test/projects/junit-platform/src/test/java/org/mvndaemon/mvnd/test/GreetingTest.java diff --git a/.gitignore b/.gitignore index 1b79e74af..b25ebb84d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties +dependency-reduced-pom.xml # Eclipse .project diff --git a/agent/pom.xml b/agent/pom.xml new file mode 100644 index 000000000..bf45d610b --- /dev/null +++ b/agent/pom.xml @@ -0,0 +1,81 @@ + + + + 4.0.0 + + org.mvndaemon.mvnd + mvnd + 0.2.1-SNAPSHOT + + + mvnd-agent + + jar + Maven Daemon - Agent + + + + + org.javassist + javassist + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.mvndaemon.mvnd.agent.Agent + mvnd-helper-agent-${project.version}.jar + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + org.javassist:javassist + + META-INF/MANIFEST.MF + + + + + + + + + + + diff --git a/agent/src/main/java/org/mvndaemon/mvnd/agent/Agent.java b/agent/src/main/java/org/mvndaemon/mvnd/agent/Agent.java new file mode 100644 index 000000000..3c33984ad --- /dev/null +++ b/agent/src/main/java/org/mvndaemon/mvnd/agent/Agent.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mvndaemon.mvnd.agent; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; +import javassist.ClassPool; +import javassist.CtClass; + +public class Agent { + + public static final String START_WITH_PIPES = "if (redirects != null\n" + + " && redirects[1] == ProcessBuilder$Redirect.INHERIT\n" + + " && redirects[2] == ProcessBuilder$Redirect.INHERIT) {\n" + + " redirects[1] = redirects[2] = ProcessBuilder$Redirect.PIPE;" + + " Process p = start(redirects);\n" + + " AgentHelper.pump(p.getInputStream(), System.out);\n" + + " AgentHelper.pump(p.getErrorStream(), System.err);\n" + + " return p;\n" + + "}"; + + public static void premain(String args, Instrumentation instrumentation) throws Exception { + instrumentation.addTransformer(new ClassFileTransformer() { + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + if ("java/lang/ProcessBuilder".equals(className)) { + try { + ClassPool pool = ClassPool.getDefault(); + CtClass clazz = pool.get("java.lang.ProcessBuilder"); + pool.importPackage("org.mvndaemon.mvnd.pump"); + clazz.getDeclaredMethod("start", + new CtClass[] { clazz.getClassPool().get("java.lang.ProcessBuilder$Redirect[]") }) + .insertBefore(START_WITH_PIPES); + byte[] data = clazz.toBytecode(); + clazz.detach(); + return data; + } catch (Throwable e) { + System.err.println(e); + throw new IllegalClassFormatException(e.toString()); + } + } else { + return classfileBuffer; + } + } + }); + } + +} diff --git a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java index 74963bb14..d9db5f76f 100644 --- a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java +++ b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,12 @@ */ package org.mvndaemon.mvnd.client; +import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.SocketChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -32,7 +34,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import org.mvndaemon.mvnd.common.BuildProperties; import org.mvndaemon.mvnd.common.DaemonCompatibilitySpec; import org.mvndaemon.mvnd.common.DaemonCompatibilitySpec.Result; import org.mvndaemon.mvnd.common.DaemonConnection; @@ -300,10 +301,27 @@ private Process startDaemon(String uid) { final String java = Os.current().isUnixLike() ? "bin/java" : "bin\\java.exe"; args.add(parameters.javaHome().resolve(java).toString()); // classpath + String mvndCommonPath = null; + String mvndAgentPath = null; + for (Path jar : Files.newDirectoryStream(mvndHome.resolve("mvn/lib/ext"))) { + String s = jar.getFileName().toString(); + if (s.endsWith(".jar")) { + if (s.startsWith("mvnd-common-")) { + mvndCommonPath = jar.toString(); + } else if (s.startsWith("mvnd-agent-")) { + mvndAgentPath = jar.toString(); + } + } + } + if (mvndCommonPath == null) { + throw new IllegalStateException("Could not find mvnd-common jar in mvn/lib/ext/"); + } + if (mvndAgentPath == null) { + throw new IllegalStateException("Could not find mvnd-agent jar in mvn/lib/ext/"); + } args.add("-classpath"); - final String mvndCommonPath = "mvn/lib/ext/mvnd-common-" + BuildProperties.getInstance().getVersion() + ".jar"; - final String classpath = mvndHome.resolve(mvndCommonPath).toString(); - args.add(classpath); + args.add(mvndCommonPath + File.pathSeparator + mvndAgentPath); + args.add("-javaagent:" + mvndAgentPath); // debug options if (parameters.property(Environment.MVND_DEBUG).asBoolean()) { args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000"); diff --git a/dist/pom.xml b/dist/pom.xml index a68d64620..6c835adde 100644 --- a/dist/pom.xml +++ b/dist/pom.xml @@ -1,6 +1,6 @@ + + + 4.0.0 + + org.mvndaemon.mvnd + mvnd + 0.2.1-SNAPSHOT + + + mvnd-helper-agent + + jar + Maven Daemon - Helper Agent + + diff --git a/helper/src/main/java/org/mvndaemon/mvnd/pump/AgentHelper.java b/helper/src/main/java/org/mvndaemon/mvnd/pump/AgentHelper.java new file mode 100644 index 000000000..b1d916f67 --- /dev/null +++ b/helper/src/main/java/org/mvndaemon/mvnd/pump/AgentHelper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mvndaemon.mvnd.pump; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; + +public class AgentHelper { + + public static void pump(InputStream stream, PrintStream out) { + new Thread(() -> new BufferedReader(new InputStreamReader(stream)).lines().forEach(out::println)).start(); + } + +} diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/JUnitPlatformTest.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/JUnitPlatformTest.java new file mode 100644 index 000000000..bd3089931 --- /dev/null +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/JUnitPlatformTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mvndaemon.mvnd.it; + +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.mvndaemon.mvnd.assertj.TestClientOutput; +import org.mvndaemon.mvnd.client.Client; +import org.mvndaemon.mvnd.client.DaemonParameters; +import org.mvndaemon.mvnd.common.Message; +import org.mvndaemon.mvnd.junit.MvndTest; + +import static junit.framework.Assert.assertTrue; + +@MvndTest(projectDir = "src/test/projects/junit-platform") +public class JUnitPlatformTest { + + @Inject + Client client; + + @Inject + DaemonParameters parameters; + + @Test + void cleanTestInheritIO() throws InterruptedException { + + final TestClientOutput output = new TestClientOutput(); + client.execute(output, "clean", "test", "-e", "-Dmvnd.log.level=DEBUG").assertSuccess(); + assertHasTestMessage(output); + + } + + private void assertHasTestMessage(final TestClientOutput output) { + assertTrue(output.getMessages().stream() + .filter(Message.ProjectEvent.class::isInstance) + .map(Message.ProjectEvent.class::cast) + .anyMatch(it -> it.getMessage() != null && + "From test".equals(it.getMessage().replaceFirst(".*] ", "")))); + } +} diff --git a/integration-tests/src/test/projects/junit-platform/.mvn/maven.config b/integration-tests/src/test/projects/junit-platform/.mvn/maven.config new file mode 100644 index 000000000..4230c2417 --- /dev/null +++ b/integration-tests/src/test/projects/junit-platform/.mvn/maven.config @@ -0,0 +1,3 @@ +-Dmaven.wagon.httpconnectionManager.ttlSeconds=120 +-Dmaven.wagon.http.retryHandler.requestSentEnabled=true +-Dmaven.wagon.http.retryHandler.count=10 diff --git a/integration-tests/src/test/projects/junit-platform/pom.xml b/integration-tests/src/test/projects/junit-platform/pom.xml new file mode 100644 index 000000000..f54bac2b1 --- /dev/null +++ b/integration-tests/src/test/projects/junit-platform/pom.xml @@ -0,0 +1,109 @@ + + + + 4.0.0 + org.mvndaemon.mvnd.test.exec-output + jp-output + 0.0.1-SNAPSHOT + jar + + + UTF-8 + 1.8 + 1.8 + + 2.5 + 3.8.0 + 2.4 + 2.6 + 2.22.2 + 3.0.0 + + + + + org.junit.jupiter + junit-jupiter + 5.7.0 + test + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.codehaus.mojo + exec-maven-plugin + ${exec-maven-plugin.version} + + + + + + + de.sormuras.junit + junit-platform-maven-plugin + 1.1.0 + true + + NONE + JAVA + + true + + --disable-banner + + + + false +
flat
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/integration-tests/src/test/projects/junit-platform/src/test/java/org/mvndaemon/mvnd/test/GreetingTest.java b/integration-tests/src/test/projects/junit-platform/src/test/java/org/mvndaemon/mvnd/test/GreetingTest.java new file mode 100644 index 000000000..012b2c45f --- /dev/null +++ b/integration-tests/src/test/projects/junit-platform/src/test/java/org/mvndaemon/mvnd/test/GreetingTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mvndaemon.mvnd.test; + +import org.junit.jupiter.api.Test; + +class GreetingTest { + + @Test + void run() { + System.out.println("From test"); + } + +} diff --git a/pom.xml b/pom.xml index 0377ae19b..533d206fc 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ 1.3.1 1.13.9 1.0.15 + 3.27.0-GA build-plugin + agent + helper common client daemon @@ -169,6 +173,11 @@ ${assertj.version} + + org.mvndaemon.mvnd + mvnd-agent + 0.2.1-SNAPSHOT + org.mvndaemon.mvnd mvnd-client @@ -190,6 +199,11 @@ mvnd-daemon 0.2.1-SNAPSHOT + + org.mvndaemon.mvnd + mvnd-helper-agent + 0.2.1-SNAPSHOT + org.jline @@ -223,6 +237,12 @@ ${slf4j.version} + + org.javassist + javassist + ${javassist.version} + + @@ -341,6 +361,11 @@ limitations under the License. maven-install-plugin ${maven-install-plugin.version} + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + org.codehaus.gmaven groovy-maven-plugin