Use an agent to support processes launched with redirected io, fixes #241

This commit is contained in:
Guillaume Nodet
2021-01-05 15:25:02 +01:00
parent f84f1e0c4b
commit d6464ed790
13 changed files with 460 additions and 8 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
# Eclipse
.project

81
agent/pom.xml Normal file
View File

@@ -0,0 +1,81 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd</artifactId>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>mvnd-agent</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon - Agent</name>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>org.mvndaemon.mvnd.agent.Agent</Premain-Class>
<Boot-Class-Path>mvnd-helper-agent-${project.version}.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>org.javassist:javassist</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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;
}
}
});
}
}

View File

@@ -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.UUID;
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 @@ public class DaemonConnector {
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");

10
dist/pom.xml vendored
View File

@@ -1,6 +1,6 @@
<!--
Copyright 2019 the original author or authors.
Copyright 2019-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.
@@ -31,6 +31,14 @@
<name>Maven Daemon - Distribution</name>
<dependencies>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-agent</artifactId>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-helper-agent</artifactId>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-client</artifactId>

View File

@@ -1,6 +1,6 @@
<!--
Copyright 2019 the original author or authors.
Copyright 2019-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.
@@ -63,6 +63,8 @@
<artifact id="org.mvndaemon.mvnd:mvnd-client:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.mvndaemon.mvnd:mvnd-agent:${project.version}"/>
<artifact id="org.mvndaemon.mvnd:mvnd-helper-agent:${project.version}"/>
</artifactSet>
<fileSet to="/">

33
helper/pom.xml Normal file
View File

@@ -0,0 +1,33 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd</artifactId>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>mvnd-helper-agent</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon - Helper Agent</name>
</project>

View File

@@ -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();
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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().contains("[stdout] From test")));
}
}

View File

@@ -0,0 +1,3 @@
-Dmaven.wagon.httpconnectionManager.ttlSeconds=120
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-Dmaven.wagon.http.retryHandler.count=10

View File

@@ -0,0 +1,109 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mvndaemon.mvnd.test.exec-output</groupId>
<artifactId>jp-output</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven-clean-plugin.version>2.5</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-install-plugin.version>2.4</maven-install-plugin.version>
<maven-resources-plugin.version>2.6</maven-resources-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<exec-maven-plugin.version>3.0.0</exec-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>de.sormuras.junit</groupId>
<artifactId>junit-platform-maven-plugin</artifactId>
<version>1.1.0</version>
<extensions>true</extensions>
<configuration>
<isolation>NONE</isolation>
<executor>JAVA</executor>
<javaOptions>
<inheritIO>true</inheritIO>
<additionalLauncherOptions>
<additionalLauncherOption>--disable-banner</additionalLauncherOption>
</additionalLauncherOptions>
</javaOptions>
<tweaks>
<failIfNoTests>false</failIfNoTests>
<details>flat</details>
</tweaks>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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");
}
}

27
pom.xml
View File

@@ -1,6 +1,6 @@
<!--
Copyright 2019 the original author or authors.
Copyright 2019-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.
@@ -58,16 +58,20 @@
<license-maven-plugin.version>3.0</license-maven-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-install-plugin.version>2.4</maven-install-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<mrm.version>1.2.0</mrm.version>
<surefire.version>2.22.2</surefire.version><!-- keep in sync with junit-platform-launcher.version -->
<junit-platform-launcher.version>1.3.1</junit-platform-launcher.version><!-- keep in sync with surefire.version -->
<takari-lifecycle.version>1.13.9</takari-lifecycle.version>
<takari-provisio.version>1.0.15</takari-provisio.version>
<javassist.version>3.27.0-GA</javassist.version>
</properties>
<modules>
<module>build-plugin</module>
<module>agent</module>
<module>helper</module>
<module>common</module>
<module>client</module>
<module>daemon</module>
@@ -169,6 +173,11 @@
<version>${assertj.version}</version>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-agent</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-client</artifactId>
@@ -190,6 +199,11 @@
<artifactId>mvnd-daemon</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-helper-agent</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
@@ -223,6 +237,12 @@
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -341,6 +361,11 @@ limitations under the License.</inlineHeader>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>