Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add javac plugin #22

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ java {

tasks.jar {
from(sourceSets["main"].output, sourceSets["shared"].output)
from(project("javac-plugin").sourceSets["main"].output)

from(rootDir.resolve("LICENSE.md"))
from(rootDir.resolve("license")) {
into("license")
Expand All @@ -171,6 +173,7 @@ tasks.jar {

tasks.getByName<Jar>("sourcesJar") {
from(sourceSets["shared"].allSource)
from(project("javac-plugin").sourceSets["main"].allSource)
from(rootDir.resolve("LGPLv2.1.md"))

isPreserveFileTimestamps = false
Expand All @@ -185,6 +188,7 @@ project.evaluationDependsOnChildren()

val shadowJar by tasks.registering(ShadowJar::class) {
from(sourceSets["main"].output, sourceSets["shared"].output)
from(project("javac-plugin").sourceSets["main"].output)
from(rootDir.resolve("LGPLv2.1.md"))

isPreserveFileTimestamps = false
Expand Down
1 change: 1 addition & 0 deletions java-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ tasks.getByName<JavaCompile>("compileCoverageJava") {

val genCtSym by tasks.registering(GenerateCtSymTask::class) {
group = "jvmdg"
lowerVersion = fromVersion
upperVersion = toVersion - 1
}

Expand Down
37 changes: 37 additions & 0 deletions javac-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import org.gradle.internal.os.OperatingSystem

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
}

dependencies {
compileOnly(rootProject.sourceSets["main"].output)
compileOnly(rootProject.sourceSets["shared"].output)

// macos doesn't link tools.jar by default for some reason
val javaHome = javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(8)) }.get().metadata.installationPath
implementation(files("$javaHome/lib/tools.jar"))

testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.compileTestJava {
classpath += rootProject.tasks["shadowJar"].outputs.files
wagyourtail marked this conversation as resolved.
Show resolved Hide resolved

javaCompiler = javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(17))
}

options.compilerArgs.add("-Xplugin:jvmdg target=8 log=info")
}

tasks.test {
useJUnitPlatform()

javaLauncher = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(8))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package xyz.wagyourtail.jvmdg.javac;

import com.sun.source.util.Plugin;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.BasicJavacTask;
import com.sun.tools.javac.main.JavaCompiler;
import xyz.wagyourtail.jvmdg.ClassDowngrader;
import xyz.wagyourtail.jvmdg.cli.Flags;
import xyz.wagyourtail.jvmdg.compile.PathDowngrader;
import xyz.wagyourtail.jvmdg.logging.Logger;
import xyz.wagyourtail.jvmdg.util.Utils;

import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.Closeable;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;

@SuppressWarnings("UrlHashCode")
public class JvmdgJavacPlugin implements Plugin, Closeable {
private BasicJavacTask task;
private Flags flags;
private final Set<URL> classpathURLs = new HashSet<>();

@Override
public String getName() {
return "jvmdg";
}

static {
int vmVersion = Utils.classVersionToMajorVersion(Utils.getCurrentClassVersion());
if(vmVersion > 8) {
try {
Method getModule = Class.class.getDeclaredMethod("getModule");
openModule(getModule.invoke(JavacTask.class));
} catch (Throwable t) {
Utils.sneakyThrow(t);
}
}
}

@Override
public void init(JavacTask t, String... args) {
this.task = (BasicJavacTask) t;

JavaCompiler compiler = JavaCompiler.instance(task.getContext());
compiler.closeables = compiler.closeables.prepend(this);

this.flags = new Flags();
flags.api = flags.findJavaApi();

final String p = "[" + File.separator + "]";

for(String arg : args) {
if(arg.contains("=")) {
String[] split = arg.split("=", 2);
switch(split[0]) {
case "api":
if(flags.api == null) {
flags.api = new ArrayList<>();
}
for(String s : split[1].split(p)) {
flags.api.add(new File(s));
}
break;
case "logLevel":
case "log":
flags.logLevel = Logger.Level.valueOf(split[1].toUpperCase(Locale.ROOT));
break;
case "skipStubs":
for(String s : split[1].split(",")) {
flags.debugSkipStubs.add(Integer.parseInt(s));
}
break;
case "target":
flags.classVersion = Utils.majorVersionToClassVersion(Integer.parseInt(split[1]));
break;
case "classpath":
case "cp":
for(String s : split[1].split(p)) {
try {
classpathURLs.add(new File(s).toURI().toURL());
} catch (Throwable t1) {
Utils.sneakyThrow(t1);
}
}
break;
}
}
Copy link
Contributor

@wagyourtail wagyourtail Nov 24, 2024

Choose a reason for hiding this comment

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

does this have to have it's own seperate args. also the args should at least match the names of the ones in https://github.com/unimined/JvmDowngrader/blob/main/src/main/java/xyz/wagyourtail/jvmdg/cli/Main.java and maybe share some of the logic.

also, does it really make sense for the classpath for jvmdg to be different than the classpath provided to javac?

also, also, you can pass VM properties into javac somehow, which can also be used to set the jvmdg flags, so is this nececairy at all? https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-J

Copy link
Author

Choose a reason for hiding this comment

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

I could make it delegate to Main, that would just require a little moving of logic in Main around so I can get the parsed Flags. I'm not a particularly big fan of using -J though, I think having the arguments as the arguments to the plugin would work better.

}
}

@Override
public void close() {
try {
runDowngrade();
} catch (Throwable t) {
Utils.sneakyThrow(t);
}
}

private void runDowngrade() throws Throwable {
final JavaFileManager fileManager = task.getContext().get(JavaFileManager.class);

File root = new File(
fileManager.getJavaFileForOutput(
StandardLocation.CLASS_OUTPUT,
"", JavaFileObject.Kind.CLASS, null
).toUri()
).getParentFile();

// must be mutable, since GradleStandardJavaFileManager calls remove
Set<JavaFileObject.Kind> kindSet = new HashSet<>();
kindSet.add(JavaFileObject.Kind.CLASS);

for(JavaFileObject jfo : fileManager.list(StandardLocation.CLASS_PATH, "", kindSet, true)) {
classpathURLs.add(jfo.toUri().toURL());
}

Path tempOutput = Files.createTempDirectory("downgrade");
tempOutput.toFile().deleteOnExit();

try(ClassDowngrader cd = ClassDowngrader.downgradeTo(flags)) {
cd.logger.debug("classpath: " + classpathURLs);

PathDowngrader.downgradePaths(
cd,
Collections.singletonList(root.toPath()),
Collections.singletonList(tempOutput),
classpathURLs
);
}

Files.walk(tempOutput)
.filter(Files::isRegularFile)
.forEach(p -> {
try {
Path target = root.toPath().resolve(tempOutput.relativize(p));
Files.createDirectories(target.getParent());
Files.move(p, target, StandardCopyOption.REPLACE_EXISTING);
} catch (Throwable t) {
Utils.sneakyThrow(t);
}
});
}

public static void openModule(Object o) throws Throwable {
Class<?> moduleClass = o.getClass();
if(!moduleClass.getName().equals("java.lang.Module")) {
throw new IllegalArgumentException("Not a module: " + o);
}
MethodHandle implAddOpens = Utils.getImplLookup().findVirtual(moduleClass, "implAddOpens",
MethodType.methodType(void.class, String.class));

@SuppressWarnings("unchecked")
Set<String> packages = (Set<String>) moduleClass.getDeclaredMethod("getPackages").invoke(o);

for(String pn : packages) {
implAddOpens.invoke(o, pn);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xyz.wagyourtail.jvmdg.javac.JvmdgJavacPlugin
18 changes: 18 additions & 0 deletions javac-plugin/src/test/java/TheTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class TheTest {
@Test
public void test() {
// if this class loads, the plugin is working

// just make sure we're running on Java 8
assertTrue(System.getProperty("java.version").startsWith("1.8"));

String h = """
Hello World!
""";
assertEquals("Hello World!\n", h);
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ plugins {
include("gradle-plugin")
include("java-api")
include("site")
include("javac-plugin")

include("testing")
include("testing:downgrade")
Expand Down