Skip to content
This repository has been archived by the owner on May 12, 2024. It is now read-only.

Commit

Permalink
Implemented gradle plugin and cleaned up code
Browse files Browse the repository at this point in the history
Release 1.0.0
  • Loading branch information
RaphiMC committed Sep 4, 2023
1 parent ebd3c27 commit aa66b2f
Show file tree
Hide file tree
Showing 22 changed files with 532 additions and 163 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,47 @@ Here is an example command to convert the jar input.jar to Java 8 and output it
## Usage (As a library)
To transform a ``ClassNode`` you can use the ``JavaDowngrader`` class.
As a low level class modification framework in your application [ClassTransform](https://github.com/Lenni0451/ClassTransform) is recommended.
JavaDowngrader provides the ``impl-classtransform`` submodule which contains various utility classes for ClassTransform.

## Usage (In Gradle)
To use JavaDowngrader in gradle (To downgrade a whole jar or one of your source sets) you have to add the following to the top of your build.gradle:
```groovy
buildscript {
repositories {
maven {
name = "Lenni0451 Releases"
url "https://maven.lenni0451.net/releases"
}
}
dependencies {
classpath "net.raphimc.javadowngrader:gradle-plugin:1.0.0"
}
}
```

### Downgrade the main source set
```groovy
tasks.register("java8Main", DowngradeSourceSetTask) {
sourceSet = sourceSets.main
}.get().dependsOn("classes")
classes.finalizedBy("java8Main")
```

### Downgrade the built jar (If you use Java 8+ libraries)
```groovy
tasks.register("java8Jar", DowngradeJarTask) {
input = tasks.jar.archiveFile.get().asFile
outputSuffix = "+java8"
compileClassPath = sourceSets.main.compileClasspath
}.get().dependsOn("build")
build.finalizedBy("java8Jar")
```

Some of the optional properties include:
- ``targetVersion``: The target classfile version (Default: 8)
- ``outputSuffix``: The suffix to append to the output jar file (Default: "-downgraded")
- ``copyRuntimeClasses``: Whether to copy the JavaDowngrader runtime classes to the output jar (Default: true). Should be set to false if your jar already contains JavaDowngrader itself

## Contact
If you encounter any issues, please report them on the
Expand Down
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ allprojects {

repositories {
mavenCentral()
maven {
name = "Lenni0451 Releases"
url "https://maven.lenni0451.net/releases"
}
maven {
name = "Lenni0451 Snapshots"
url "https://maven.lenni0451.net/snapshots"
}
}

java {
Expand Down
5 changes: 5 additions & 0 deletions gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dependencies {
api project(":impl-classtransform")

implementation gradleApi()
}
1 change: 1 addition & 0 deletions gradle-plugin/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
maven_name=gradle-plugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
* Copyright (C) 2023 RK_01/RaphiMC and contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.javadowngrader.gradle.task;

import net.lenni0451.classtransform.TransformerManager;
import net.lenni0451.classtransform.utils.tree.BasicClassProvider;
import net.raphimc.javadowngrader.impl.classtransform.JavaDowngraderTransformer;
import net.raphimc.javadowngrader.impl.classtransform.classprovider.LazyFileClassProvider;
import net.raphimc.javadowngrader.impl.classtransform.classprovider.PathClassProvider;
import net.raphimc.javadowngrader.impl.classtransform.util.ClassNameUtil;
import net.raphimc.javadowngrader.impl.classtransform.util.FileSystemUtil;
import net.raphimc.javadowngrader.runtime.RuntimeRoot;
import net.raphimc.javadowngrader.util.Constants;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.TaskAction;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Stream;

public class DowngradeJarTask extends DefaultTask {

@Internal
private File input;

@Internal
private String outputSuffix = "-downgraded";

@Internal
private FileCollection compileClassPath;

@Internal
private int targetVersion = Opcodes.V1_8;

@Internal
private boolean copyRuntimeClasses = true;

@TaskAction
public void run() throws IOException, URISyntaxException {
Objects.requireNonNull(this.input, "input must be set");
Objects.requireNonNull(this.outputSuffix, "outputSuffix must be set");
Objects.requireNonNull(this.compileClassPath, "compileClassPath must be set");
if (!this.input.exists()) throw new IllegalArgumentException("input does not exist");
if (!this.input.isFile() || !this.input.getName().endsWith(".jar")) throw new IllegalArgumentException("input is not a jar file");

System.out.println("Downgrading jar: " + this.input.getName());
try (FileSystem inFs = FileSystems.newFileSystem(this.input.toPath(), null)) {
final Path inRoot = inFs.getRootDirectories().iterator().next();

final TransformerManager transformerManager = new TransformerManager(
new PathClassProvider(inRoot, new LazyFileClassProvider(this.compileClassPath.getFiles(), new BasicClassProvider()))
);
transformerManager.addBytecodeTransformer(new JavaDowngraderTransformer(
transformerManager, this.targetVersion, c -> Files.isRegularFile(inRoot.resolve(ClassNameUtil.toClassFilename(c)))
));

final String outputName = this.input.getName().substring(0, this.input.getName().length() - 4) + this.outputSuffix;
final File outputFile = new File(this.input.getParentFile(), outputName + ".jar");

try (FileSystem outFs = FileSystems.newFileSystem(new URI("jar:" + outputFile.toURI()), Collections.singletonMap("create", "true"))) {
final Path outRoot = outFs.getRootDirectories().iterator().next();

// Downgrade classes
try (Stream<Path> stream = Files.walk(inRoot)) {
stream.forEach(path -> {
try {
final String relative = ClassNameUtil.slashName(inRoot.relativize(path));
final Path dest = outRoot.resolve(relative);
if (Files.isDirectory(path)) {
Files.createDirectories(dest);
return;
}
final Path parent = dest.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
if (!relative.endsWith(".class") || relative.contains("META-INF/versions/")) {
Files.copy(path, dest);
return;
}
final String className = ClassNameUtil.toClassName(relative);
final byte[] bytecode = Files.readAllBytes(path);
final byte[] result;
try {
result = transformerManager.transform(className, bytecode);
} catch (Throwable e) {
throw new RuntimeException("Failed to transform " + className, e);
}
Files.write(dest, result != null ? result : bytecode);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}

// Copy runtime classes
if (this.copyRuntimeClasses) {
try (FileSystem runtimeRootFs = FileSystemUtil.getOrCreateFileSystem(RuntimeRoot.class.getResource("").toURI())) {
final Path runtimeRoot = runtimeRootFs.getPath(Constants.JAVADOWNGRADER_RUNTIME_PACKAGE);
try (Stream<Path> stream = Files.walk(runtimeRoot)) {
stream.filter(Files::isRegularFile)
.filter(p -> !p.getFileName().toString().equals(Constants.JAVADOWNGRADER_RUNTIME_ROOT))
.forEach(path -> {
final String relative = ClassNameUtil.slashName(runtimeRoot.relativize(path));
final Path dest = outRoot.resolve(Constants.JAVADOWNGRADER_RUNTIME_PACKAGE + relative);
try {
Files.createDirectories(dest.getParent());
Files.copy(path, dest);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
}
}
}
}

public File getInput() {
return this.input;
}

public String getOutputSuffix() {
return this.outputSuffix;
}

public FileCollection getCompileClassPath() {
return this.compileClassPath;
}

public int getTargetVersion() {
return this.targetVersion;
}

public boolean getCopyRuntimeClasses() {
return this.copyRuntimeClasses;
}

public void setInput(final File input) {
this.input = input;
}

public void setOutputSuffix(final String outputSuffix) {
this.outputSuffix = outputSuffix;
}

public void setCompileClassPath(final FileCollection compileClassPath) {
this.compileClassPath = compileClassPath;
}

public void setTargetVersion(final int targetVersion) {
this.targetVersion = targetVersion;
}

public void setCopyRuntimeClasses(final boolean copyRuntimeClasses) {
this.copyRuntimeClasses = copyRuntimeClasses;
}

}
Loading

0 comments on commit aa66b2f

Please sign in to comment.