Skip to content

Commit

Permalink
Use an agent to support processes launched with redirected io, fixes a…
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Jan 5, 2021
1 parent 3fa4eb3 commit f33250e
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 7 deletions.
59 changes: 59 additions & 0 deletions agent/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!--
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-pump-${version}.jar</Boot-Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>

</project>
63 changes: 63 additions & 0 deletions agent/src/main/java/org/mvndaemon/mvnd/agent/Agent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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"
+ " PumpThread.start(p.getInputStream(), System.out);\n"
+ " PumpThread.start(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) {
throw new IllegalClassFormatException(e.toString());
}
} else {
return classfileBuffer;
}
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -300,10 +301,33 @@ 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;
String javassistPath = 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();
} else if (s.startsWith("javassist-")) {
javassistPath = 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/");
}
if (javassistPath == null) {
throw new IllegalStateException("Could not find javassist 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 + File.pathSeparator + javassistPath);
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");
Expand Down
10 changes: 9 additions & 1 deletion dist/pom.xml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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-pump</artifactId>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-client</artifactId>
Expand Down
4 changes: 3 additions & 1 deletion dist/src/main/provisio/maven-distro.xml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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-pump:${project.version}"/>
</artifactSet>

<fileSet to="/">
Expand Down
21 changes: 20 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -64,10 +64,13 @@
<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>pump</module>
<module>common</module>
<module>client</module>
<module>daemon</module>
Expand Down Expand Up @@ -169,6 +172,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>
Expand All @@ -190,6 +198,11 @@
<artifactId>mvnd-daemon</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mvndaemon.mvnd</groupId>
<artifactId>mvnd-pump</artifactId>
<version>0.2.1-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.jline</groupId>
Expand Down Expand Up @@ -223,6 +236,12 @@
<version>${slf4j.version}</version>
</dependency>

<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>

</dependencies>
</dependencyManagement>

Expand Down
33 changes: 33 additions & 0 deletions pump/pom.xml
Original file line number Diff line number Diff line change
@@ -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-pump</artifactId>

<packaging>jar</packaging>
<name>Maven Daemon - Pump</name>

</project>
56 changes: 56 additions & 0 deletions pump/src/main/java/org/mvndaemon/mvnd/pump/PumpThread.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.UncheckedIOException;

public class PumpThread extends Thread {

private final InputStream stream;
private final PrintStream out;

public static void start(InputStream stream, PrintStream out) {
new PumpThread(stream, out).start();
}

public PumpThread(InputStream stream, PrintStream out) {
this.stream = stream;
this.out = out;
}

@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
try {
String line;
while ((line = reader.readLine()) != null) {
out.println(line);
}
} finally {
reader.close();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

}

0 comments on commit f33250e

Please sign in to comment.