Skip to content

Commit

Permalink
Implement jvm probing, refactor code accordingly
Browse files Browse the repository at this point in the history
  • Loading branch information
Srdjan-V committed Apr 25, 2024
1 parent fb2fad9 commit 0ee8828
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 76 deletions.
6 changes: 6 additions & 0 deletions dcevm-detect/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ plugins {

//Source code
//https://github.com/dcevm/dcevm/tree/93890b30437f7476824df90f8e9646c391440c31/installer


dependencies {
implementation "org.ow2.asm:asm:9.7"
implementation 'com.google.guava:guava:31.1-jre'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.srdjanv.hotswapgradle.dcevmdetection;

import com.github.bsideup.jabel.Desugar;

@Desugar
public record VMMeta(boolean isDcevmPresent, String dcevmVersion) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.github.srdjanv.hotswapgradle.dcevmdetection;

import com.github.bsideup.jabel.Desugar;
import java.util.Optional;

@Desugar
public record VMReport(Optional<VMMeta> vmMeta, Optional<Exception> exception) {
public static VMReport none() {
return new VMReport(Optional.empty(), Optional.empty());
}

public static VMReport none(String message) {
return new VMReport(Optional.empty(), Optional.of(new Exception(message)));
}

public static VMReport exception(Exception exception) {
return new VMReport(Optional.empty(), Optional.of(exception));
}

public static VMReport of(VMMeta meta) {
return new VMReport(Optional.of(meta), Optional.empty());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.github.srdjanv.hotswapgradle.dcevmdetection.probe;

import java.nio.file.Files;
import java.nio.file.Path;

public enum PlatformJavaPaths {
MAC_OS("bin/java"),
LINUX("bin/java"),
WINDOWS("bin/java.exe");

public static Path resolveExecutable(Path javaHome) {
if (Files.isRegularFile(javaHome)) throw new IllegalArgumentException("Java home is a file");
if (Files.isDirectory(javaHome.resolve("jre"))) javaHome = javaHome.resolve("jre");
return javaHome.resolve(get().path);
}

public static PlatformJavaPaths get() {
String os = System.getProperty("os.name").toLowerCase();

if (os.contains("windows")) {
return WINDOWS;
} else if (os.contains("mac") || os.contains("darwin")) {
return MAC_OS;
} else if (os.contains("unix") || os.contains("linux")) {
return LINUX;
}
throw new IllegalStateException("OS is unsupported: " + os);
}

private final String path;

PlatformJavaPaths(String s) {
path = s;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.github.srdjanv.hotswapgradle.dcevmdetection.probe;

import static org.objectweb.asm.Opcodes.*;

import com.google.common.base.Suppliers;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Supplier;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

// This is taken from Gradle internals
public class ProbeBuilder {
public static final String MARKER_PREFIX = "DCEVM_DETECT_PROBE_VALUE:";

public static void writeClass(Path probeFile) throws IOException {
Files.createFile(probeFile);
try (var stream = new FileOutputStream(probeFile.toFile())) {
stream.write(probeClass.get());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static final Supplier<byte[]> probeClass = Suppliers.memoize(ProbeBuilder::createProbeClass);

private static byte[] createProbeClass() {
ClassWriter cw = new ClassWriter(0);
createClassHeader(cw);
createConstructor(cw);
createMainMethod(cw);
cw.visitEnd();
return cw.toByteArray();
}

private static void createClassHeader(ClassWriter cw) {
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, "JavaProbe", null, "java/lang/Object", null);
}

private static void createMainMethod(ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
for (Props type : Props.values()) {
dumpProperty(mv, type.getValue());
}
mv.visitInsn(RETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l3, 0);
mv.visitMaxs(3, 1);
mv.visitEnd();
}

private static void dumpProperty(MethodVisitor mv, String property) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(MARKER_PREFIX);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);

mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(property);
mv.visitLdcInsn("unknown");
mv.visitMethodInsn(
INVOKESTATIC,
"java/lang/System",
"getProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}

private static void createConstructor(ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "LJavaProbe;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.github.srdjanv.hotswapgradle.dcevmdetection.probe;

import static io.github.srdjanv.hotswapgradle.dcevmdetection.probe.PlatformJavaPaths.resolveExecutable;
import static io.github.srdjanv.hotswapgradle.dcevmdetection.probe.ProbeBuilder.MARKER_PREFIX;

import io.github.srdjanv.hotswapgradle.dcevmdetection.VMMeta;
import io.github.srdjanv.hotswapgradle.dcevmdetection.VMReport;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ProbeDcevmDetection {
public static VMReport buildReport(Path javaHome) {
try {
Path tempDir = Files.createTempDirectory("HotswapProbe");
tempDir.toFile().deleteOnExit();
Path tempFile = tempDir.resolve("JavaProbe.class");
ProbeBuilder.writeClass(tempFile);

Process process = new ProcessBuilder(
resolveExecutable(javaHome).toAbsolutePath().toString(),
"-Xmx32m",
"-Xms32m",
"-cp",
tempDir.toRealPath().toAbsolutePath().toString(),
com.google.common.io.Files.getNameWithoutExtension(tempFile.toString()))
.start();

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
if (process.waitFor(3, TimeUnit.MINUTES)) {
int exitValue = process.exitValue();
if (exitValue == 0) return parseExecOutput(reader.lines());

throw new Exception("Command returned unexpected result code: "
+ exitValue + "\nError output:\n"
+ reader.lines().collect(Collectors.joining(System.lineSeparator())));
} else throw new Exception("VM Timeout reached");
} catch (Exception exception) {
return VMReport.exception(exception);
}
}

public static VMReport parseExecOutput(Stream<String> out) {
String[] split = out.filter(line -> line.startsWith(MARKER_PREFIX))
.map(line -> line.substring(MARKER_PREFIX.length()))
.toArray(String[]::new);
if (split.length != 2)
return VMReport.exception(new Exception("Invalid output format: " + Arrays.toString(split)));

boolean isDcevm = split[Props.VM_NAME.ordinal()].contains("Dynamic Code Evolution");
String dcevmVersion = split[Props.VM_VERSION.ordinal()];
return VMReport.of(new VMMeta(isDcevm, dcevmVersion));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.srdjanv.hotswapgradle.dcevmdetection.probe;

public enum Props {
VM_NAME("java.vm.name"),
VM_VERSION("java.vm.version");

private final String value;

Props(String internal) {
value = internal;
}

public String getValue() {
return value;
}
}
3 changes: 2 additions & 1 deletion plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ tasks.withType(Javadoc).configureEach {
dependencies {
implementation gradleTestKit()
implementation(project(":dcevm-detect").sourceSets.main.output)
implementation(project(":dcevm-detect").configurations.implementation.incoming.dependencies)

implementation 'org.jetbrains:annotations:24.0.1'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.guava:guava:31.1-jre'
//implementation 'com.google.guava:guava:31.1-jre'

implementation "org.apache.httpcomponents:httpclient:4.5.14"
implementation 'org.apache.commons:commons-lang3:3.14.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.github.srdjanv.hotswapgradle.base;

import java.io.File;
import java.util.Collections;
import java.util.function.Consumer;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
Expand Down Expand Up @@ -40,6 +39,6 @@ public static void defaultConfig(GradleRunner config) {
config.forwardOutput();
config.withPluginClasspath();
config.withDebug(true);
//config.withEnvironment(Collections.singletonMap(""))
// config.withEnvironment(Collections.singletonMap(""))
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package io.github.srdjanv.hotswapgradle;

import io.github.srdjanv.hotswapgradle.agent.HotswapAgentProvider;
import io.github.srdjanv.hotswapgradle.dcevmdetection.included.IncludedDcevmDetection;
import io.github.srdjanv.hotswapgradle.dcevmdetection.legacy.LegacyDcevmDetection;
import io.github.srdjanv.hotswapgradle.registry.ICashedJVMRegistry;
import io.github.srdjanv.hotswapgradle.registry.ICachedJVMRegistry;
import io.github.srdjanv.hotswapgradle.registry.IKnownDcevmRegistry;
import io.github.srdjanv.hotswapgradle.registry.ILocalJVMRegistry;
import io.github.srdjanv.hotswapgradle.registry.internal.CashedJVMRegistry;
import io.github.srdjanv.hotswapgradle.registry.internal.CachedJVMRegistry;
import io.github.srdjanv.hotswapgradle.registry.internal.KnownDcevmRegistry;
import io.github.srdjanv.hotswapgradle.registry.internal.LocalJVMRegistry;
import io.github.srdjanv.hotswapgradle.validator.DcevmValidator;
import java.util.Objects;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildService;
Expand All @@ -19,8 +18,6 @@
import org.gradle.tooling.events.task.TaskFinishEvent;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

public abstract class HotswapGradleService
implements BuildService<HotswapGradleService.HotSwapParameters>, OperationCompletionListener {
public interface HotSwapParameters extends BuildServiceParameters {
Expand All @@ -38,14 +35,14 @@ public interface HotSwapParameters extends BuildServiceParameters {
private DcevmValidator dcevmValidator;
private final HotswapAgentProvider downloader;
private final IKnownDcevmRegistry knownDCEVMRegistry;
private final ICashedJVMRegistry cashedJVMRegistry;
private final ICachedJVMRegistry cashedJVMRegistry;
private final ILocalJVMRegistry localJVMRegistry;

public HotswapGradleService() {
dcevmValidator = DcevmValidator.defaultValidator();
downloader = new HotswapAgentProvider(this);
knownDCEVMRegistry = new KnownDcevmRegistry(this);
cashedJVMRegistry = new CashedJVMRegistry(this);
cashedJVMRegistry = new CachedJVMRegistry(this);
localJVMRegistry = new LocalJVMRegistry(this);
}

Expand All @@ -65,7 +62,7 @@ public IKnownDcevmRegistry getKnownDCEVMRegistry() {
return knownDCEVMRegistry;
}

public ICashedJVMRegistry getCashedJVMRegistry() {
public ICachedJVMRegistry getCashedJVMRegistry() {
return cashedJVMRegistry;
}

Expand Down
Loading

0 comments on commit 0ee8828

Please sign in to comment.