Skip to content

Commit

Permalink
Merge pull request #22 from rhysdh540/main
Browse files Browse the repository at this point in the history
add javac plugin
  • Loading branch information
wagyourtail authored Dec 19, 2024
2 parents c0dc658 + 7995c2f commit fbf6963
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 21 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,21 +180,22 @@ I do not automatically make the configuration shadowed into your output.

#### Including newer dependencies on lower java version building

This is not recommended due to the following but is still possible.
It is recommended to just shade dependencies into your project and downgrade the combined output.
It is **strongly** not recommended due to the following but is still possible.
instead, you *should* just shade dependencies into your project and downgrade the combined output.
The dependencies may not be correctly represented in the pom with this method.

There may be issues with gradle metadata breaking because of how early gradle checks the java version,
you can disable this by setting `mavenPom` and `artifact` in the `metadataSources` function on repositories
to explicitly disable gradle metadata.
There may be issues with metadata breaking because of how early gradle checks the java version,
you can disable this by setting `artifact` in the `metadataSources` function on repositories
to explicitly disable metadata, if you are lucky, you may be able to include `mavenPom` as well, so trasitive dependencies can resolve, otherwise
**transitive dependencies will not be included**, and must be included manually.

for example:

```gradle
repositories {
mavenCentral {
metadataSources {
mavenPom()
mavenPom() // may still break with this line on some dependencies
artifact()
}
}
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public static int deflate(Deflater def, ByteBuffer buf, int flush) {

@Stub
public static void setInput(Deflater def, ByteBuffer buf) {
throw new UnsupportedOperationException(
"JVMDowngrader, setInput(ByteBuffer) is not supported because it's impure.");
byte[] remain = new byte[buf.remaining()];
buf.get(remain);
def.setInput(remain);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ public static void setDictionary(Inflater inf, ByteBuffer buf) {
}

@Stub
public static void setInput(Deflater def, ByteBuffer buf) {
throw new UnsupportedOperationException(
"JVMDowngrader, setInput(ByteBuffer) is not supported because it's impure.");
public static void setInput(Inflater inf, ByteBuffer buf) {
byte[] remain = new byte[buf.remaining()];
buf.get(remain);
inf.setInput(remain);
}

}
33 changes: 33 additions & 0 deletions javac-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
}

val java8 = javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(8)) }.get()

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

implementation(files("${java8.metadata.installationPath}/lib/tools.jar"))

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

testAnnotationProcessor(sourceSets["main"].output)
testAnnotationProcessor(rootProject.sourceSets["main"].runtimeClasspath)
}

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

val apiJar = project(":java-api").tasks.named("testJar").get().outputs.files.singleFile
options.compilerArgs.add("-Xplugin:jvmdg downgrade shade --prefix test --classVersion 52 --logLevel info --api ${apiJar.absolutePath}")
}

tasks.test {
useJUnitPlatform()
}
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.Constants;
import xyz.wagyourtail.jvmdg.cli.Arguments;
import xyz.wagyourtail.jvmdg.cli.Main;
import xyz.wagyourtail.jvmdg.compile.PathDowngrader;
import xyz.wagyourtail.jvmdg.util.Lazy;
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.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Stream;

public class JvmdgJavacPlugin extends Main implements Plugin, Closeable {
private BasicJavacTask task;
private String[] args;

private final Lazy<JavaFileManager> fileManager = new Lazy<JavaFileManager>() {
@Override
protected JavaFileManager init() {
return task.getContext().get(JavaFileManager.class);
}
};

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

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

@Override
protected Arguments buildArgumentList() {
Arguments args = super.buildArgumentList();
Arguments downgrade = args.getChild("downgrade");
Arguments target = downgrade.getChild("--target");
// remove --target
downgrade.removeChild(target);
downgrade.removeChild(downgrade.getChild("--classpath"));
args.getChild("shade").removeChild(target);
args.removeChild(args.getChild("bootstrap"));
return args;
}

@Override
public void getTargets(Map<String, List<String[]>> args, Map<Path, Path> targets, List<FileSystem> fileSystems) throws IOException {
if (!Constants.DIR.exists() && !Constants.DIR.mkdirs()) {
throw new IOException("Failed to create directory: " + Constants.DIR);
}
Path files = Files.createTempDirectory(Constants.DIR.toPath(), "downgrade").toAbsolutePath();
System.out.println(files);
files.toFile().deleteOnExit();

targets.put(tempFiles.poll().toPath(), files);
tempFiles.push(files.toFile());
}

@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.args = args;
}

@Override
public void close() throws IOException {
execute();
}

@Override
@SuppressWarnings("UrlHashCode")
public Set<URL> getClasspath(Map<String, List<String[]>> args) throws MalformedURLException {
Set<URL> classpathURLs = new HashSet<>();

try {
// the set argument must be mutable, since GradleStandardJavaFileManager calls remove
for (JavaFileObject jfo : fileManager.get().list(StandardLocation.CLASS_PATH, "",
new HashSet<>(Collections.singletonList(JavaFileObject.Kind.CLASS)), true)) {
classpathURLs.add(jfo.toUri().toURL());
}
} catch (IOException e) {
Utils.sneakyThrow(e);
}

return classpathURLs;
}

private void execute() throws IOException {
File root = new File(
fileManager.get().getJavaFileForOutput(
StandardLocation.CLASS_OUTPUT,
"", JavaFileObject.Kind.CLASS, null
).toUri()
).getParentFile();

tempFiles.add(root);

try {
parseArgs(args);
} catch (Exception e) {
Utils.sneakyThrow(e);
}

File output = tempFiles.pollFirst();

assert output != null;
if (output.equals(root)) return;

Stream<Path> walk = Files.walk(output.toPath());
walk.filter(Files::isRegularFile)
.forEach(p -> {
try {
Path target = root.toPath().resolve(output.toPath().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
20 changes: 20 additions & 0 deletions javac-plugin/src/test/java/TheTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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"));

"test %s".formatted("test2");

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
13 changes: 13 additions & 0 deletions src/main/java/xyz/wagyourtail/jvmdg/cli/Arguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ public Arguments addChildren(Arguments... children) {
return this;
}

public Arguments getChild(String arg) {
for (Set<String> strings : this.children.keySet()) {
if (strings.contains(arg.toLowerCase(Locale.ROOT))) {
return children.get(strings);
}
}
return null;
}

public Arguments removeChild(Arguments child) {
return this.children.remove(child.altNames);
}

public String help() {
StringBuilder ret = new StringBuilder();
for (Arguments child : children.values()) {
Expand Down
Loading

0 comments on commit fbf6963

Please sign in to comment.