added support for Java 8
This commit is contained in:
18
build.gradle
18
build.gradle
@@ -13,12 +13,21 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url = woggioniMavenRepositoryUrl
|
url = woggioniMavenRepositoryUrl
|
||||||
|
content {
|
||||||
|
includeModule 'net.woggioni', 'xclassloader'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.woggioni.gradle"
|
group = "net.woggioni.gradle"
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lombok {
|
lombok {
|
||||||
version = getProperty('version.lombok')
|
version = getProperty('version.lombok')
|
||||||
}
|
}
|
||||||
@@ -33,6 +42,10 @@ allprojects {
|
|||||||
add("testImplementation", gradleTestKit())
|
add("testImplementation", gradleTestKit())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
||||||
|
options.release = 8
|
||||||
|
}
|
||||||
|
|
||||||
tasks.named("test", Test) {
|
tasks.named("test", Test) {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
@@ -58,11 +71,6 @@ dependencies {
|
|||||||
embedded project(path: "common", configuration: "archives")
|
embedded project(path: "common", configuration: "archives")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('processResources', ProcessResources) {
|
tasks.named('processResources', ProcessResources) {
|
||||||
from {
|
from {
|
||||||
configurations.named('embedded').map {
|
configurations.named('embedded').map {
|
||||||
|
@@ -1,8 +1,3 @@
|
|||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes "Automatic-Module-Name" : "net.woggioni.envelope"
|
attributes "Automatic-Module-Name" : "net.woggioni.envelope"
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
woggioniMavenRepositoryUrl=https://woggioni.net/mvn/
|
woggioniMavenRepositoryUrl=https://woggioni.net/mvn/
|
||||||
publishMavenRepositoryUrl=https://mvn.woggioni.net/
|
publishMavenRepositoryUrl=https://mvn.woggioni.net/
|
||||||
|
|
||||||
version.envelope=1.0-SNAPSHOT
|
lys-gradle-plugins.version = 2022.06
|
||||||
version.gradle=7.4
|
|
||||||
|
version.envelope=2022.06
|
||||||
|
version.gradle=7.4.2
|
||||||
version.lombok=1.18.22
|
version.lombok=1.18.22
|
||||||
version.xclassloader=1.0-SNAPSHOT
|
version.xclassloader=1.0-SNAPSHOT
|
||||||
version.junitJupiter=5.7.2
|
version.junitJupiter=5.7.2
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import java.util.jar.Attributes
|
import java.util.jar.Attributes
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "net.woggioni.gradle.multi-release-jar"
|
id 'net.woggioni.gradle.multi-release-jar'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.setProperty("jpms.module.name", "net.woggioni.envelope")
|
ext {
|
||||||
|
setProperty('jpms.module.name', 'net.woggioni.envelope')
|
||||||
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
embedded {
|
embedded {
|
||||||
@@ -28,13 +29,6 @@ java {
|
|||||||
modularity.inferModulePath = true
|
modularity.inferModulePath = true
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
|
||||||
javaCompiler = javaToolchains.compilerFor {
|
|
||||||
languageVersion = JavaLanguageVersion.of(16)
|
|
||||||
}
|
|
||||||
options.forkOptions.jvmArgs << "--illegal-access=permit"
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes([
|
attributes([
|
||||||
@@ -62,13 +56,6 @@ Provider<Tar> tarTaskProvider = tasks.register("tar", Tar) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
|
||||||
doFirst {
|
|
||||||
String path = project(":common").extensions.getByType(JavaPluginExtension).sourceSets.named("main").get().output.asPath
|
|
||||||
options.compilerArgs.addAll(["--patch-module", "net.woggioni.envelope=$path"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
tar tarTaskProvider
|
tar tarTaskProvider
|
||||||
}
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package net.woggioni.envelope;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.lang.module.Configuration;
|
||||||
|
import java.lang.module.ModuleFinder;
|
||||||
|
import java.lang.module.ResolvedModule;
|
||||||
|
import java.lang.module.ModuleReference;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import net.woggioni.xclassloader.ModuleClassLoader;
|
||||||
|
import net.woggioni.xclassloader.JarFileModuleFinder;
|
||||||
|
import net.woggioni.xclassloader.jar.JarFile;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
|
||||||
|
class MainRunner {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
static void run(JarFile currentJarFile,
|
||||||
|
String mainModuleName,
|
||||||
|
String mainClassName,
|
||||||
|
String librariesFolder,
|
||||||
|
Consumer<Class<?>> runner) {
|
||||||
|
if(mainModuleName == null) {
|
||||||
|
List<URL> jarList = new ArrayList<>();
|
||||||
|
Enumeration<JarEntry> entries = currentJarFile.entries();
|
||||||
|
while(entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
if(!entry.isDirectory() && name.startsWith(librariesFolder) && name.endsWith(".jar")) {
|
||||||
|
jarList.add(currentJarFile.getNestedJarFile(entry).getUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (URLClassLoader cl = new URLClassLoader(jarList.toArray(new URL[0]), ClassLoader.getSystemClassLoader().getParent())) {
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
runner.accept(cl.loadClass(mainClassName));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<JarFile> jarList = new ArrayList<>();
|
||||||
|
Enumeration<JarEntry> entries = currentJarFile.entries();
|
||||||
|
while(entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
if(!entry.isDirectory() && name.startsWith("LIB-INF") && name.endsWith(".jar")) {
|
||||||
|
jarList.add(currentJarFile.getNestedJarFile(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModuleLayer bootLayer = ModuleLayer.boot();
|
||||||
|
Configuration bootConfiguration = bootLayer.configuration();
|
||||||
|
Configuration cfg = bootConfiguration.resolve(new JarFileModuleFinder(jarList), ModuleFinder.of(), Collections.singletonList(mainModuleName));
|
||||||
|
Map<String, ClassLoader> packageMap = new TreeMap<>();
|
||||||
|
ModuleLayer.Controller controller =
|
||||||
|
ModuleLayer.defineModules(cfg, Collections.singletonList(ModuleLayer.boot()), moduleName -> {
|
||||||
|
ModuleReference modRef = cfg.findModule(moduleName)
|
||||||
|
.map(ResolvedModule::reference)
|
||||||
|
.orElseThrow();
|
||||||
|
ClassLoader cl = new ModuleClassLoader(
|
||||||
|
Collections.unmodifiableMap(packageMap),
|
||||||
|
modRef
|
||||||
|
);
|
||||||
|
for(String packageName : modRef.descriptor().packages()) {
|
||||||
|
packageMap.put(packageName, cl);
|
||||||
|
}
|
||||||
|
return cl;
|
||||||
|
});
|
||||||
|
ModuleLayer layer = controller.layer();
|
||||||
|
Module mainModule = layer.findModule(mainModuleName).orElseThrow(
|
||||||
|
() -> new IllegalStateException(String.format("Main module '%s' not found", mainModuleName)));
|
||||||
|
runner.accept(Optional.ofNullable(mainClassName)
|
||||||
|
.or(() -> mainModule.getDescriptor().mainClass())
|
||||||
|
.map(className -> Class.forName(mainModule, className))
|
||||||
|
.orElseThrow(() -> new IllegalStateException(
|
||||||
|
String.format("Unable to determine main class name for module '%s'", mainModule.getName()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,44 +1,49 @@
|
|||||||
package net.woggioni.envelope;
|
package net.woggioni.envelope;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.woggioni.xclassloader.PathURLStreamHandler;
|
import net.woggioni.xclassloader.jar.JarFile;
|
||||||
import net.woggioni.xclassloader.URLManager;
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.FileSystem;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.function.Function;
|
import java.util.function.Consumer;
|
||||||
import java.util.jar.Attributes;
|
import java.util.jar.Attributes;
|
||||||
import java.util.jar.JarFile;
|
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
import java.util.stream.Collector;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
|
import static java.util.jar.JarFile.MANIFEST_NAME;
|
||||||
|
|
||||||
public class Launcher {
|
public class Launcher {
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private static URI findCurrentJar() {
|
private static JarFile findCurrentJar() {
|
||||||
String launcherClassName = Launcher.class.getName();
|
String launcherClassName = Launcher.class.getName();
|
||||||
URL url = Launcher.class.getClassLoader().getResource(launcherClassName.replace('.', '/') + ".class");
|
URL url = Launcher.class.getClassLoader().getResource(launcherClassName.replace('.', '/') + ".class");
|
||||||
if (url == null || !"jar".equals(url.getProtocol()))
|
if (url == null || !"jar".equals(url.getProtocol()))
|
||||||
throw new IllegalStateException(String.format("The class %s must be used inside a JAR file", launcherClassName));
|
throw new IllegalStateException(String.format("The class %s must be used inside a JAR file", launcherClassName));
|
||||||
String path = url.getPath();
|
String path = Paths.get(new URI(url.getPath())).toString();
|
||||||
return new URI(path.substring(0, path.indexOf('!')));
|
return new JarFile(new File(path.substring(0, path.indexOf('!'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private static void run(Class<?> mainClass, String[] args) {
|
||||||
|
try {
|
||||||
|
Method mainMethod = mainClass.getMethod("main", String[].class);
|
||||||
|
Class<?> returnType = mainMethod.getReturnType();
|
||||||
|
if (mainMethod.getReturnType() != Void.TYPE) {
|
||||||
|
throw new IllegalArgumentException(String.format("Main method in class '%s' " +
|
||||||
|
"has wrong return type, expected '%s', found '%s' instead", mainClass, Void.class.getName(), returnType));
|
||||||
|
}
|
||||||
|
mainMethod.invoke(null, (Object) args);
|
||||||
|
} catch (NoSuchMethodException nsme) {
|
||||||
|
throw new IllegalArgumentException(String.format("No valid main method found in class '%s'", mainClass), nsme);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@@ -53,58 +58,39 @@ public class Launcher {
|
|||||||
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
|
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
|
||||||
String key = (String) entry.getKey();
|
String key = (String) entry.getKey();
|
||||||
String value = (String) entry.getValue();
|
String value = (String) entry.getValue();
|
||||||
if(System.getProperty(key) == null) System.setProperty(key, value);
|
if (System.getProperty(key) == null) System.setProperty(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
URI currentJar = findCurrentJar();
|
JarFile currentJar = findCurrentJar();
|
||||||
String currentJarPath = currentJar.getPath();
|
|
||||||
URL manifestResource = null;
|
URL manifestResource = null;
|
||||||
Enumeration<URL> enumeration = Launcher.class.getClassLoader().getResources(JarFile.MANIFEST_NAME);
|
Enumeration<URL> enumeration = Launcher.class.getClassLoader().getResources(MANIFEST_NAME);
|
||||||
while(enumeration.hasMoreElements()) {
|
while (enumeration.hasMoreElements()) {
|
||||||
URL candidate = enumeration.nextElement();
|
URL candidate = enumeration.nextElement();
|
||||||
URL subUrl = new URL(candidate.getFile());
|
URL subUrl = new URL(candidate.getFile());
|
||||||
String candidatePath = subUrl.getPath();
|
String candidatePath = subUrl.getPath();
|
||||||
int i = candidatePath.indexOf("!/");
|
int i = candidatePath.indexOf("!/");
|
||||||
candidatePath = candidatePath.substring(0, i);
|
candidatePath = candidatePath.substring(0, i);
|
||||||
if(Objects.equals(currentJarPath, candidatePath)) {
|
if (Objects.equals(currentJar.getName(), candidatePath)) {
|
||||||
manifestResource = candidate;
|
manifestResource = candidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(Objects.isNull(manifestResource)) {
|
if (Objects.isNull(manifestResource)) {
|
||||||
throw new RuntimeException("Launcher manifest not found");
|
throw new RuntimeException("Launcher manifest not found");
|
||||||
}
|
}
|
||||||
Manifest mf = new Manifest();
|
Manifest mf = new Manifest();
|
||||||
try(InputStream is = manifestResource.openStream()) {
|
try (InputStream is = manifestResource.openStream()) {
|
||||||
mf.read(is);
|
mf.read(is);
|
||||||
}
|
}
|
||||||
try(FileSystem fs = FileSystems.newFileSystem(Paths.get(currentJar), null)) {
|
|
||||||
Attributes mainAttributes = mf.getMainAttributes();
|
Attributes mainAttributes = mf.getMainAttributes();
|
||||||
|
|
||||||
Collector<Path, ArrayList<Path>, List<Path>> immutableListCollector = Collector.of(
|
|
||||||
ArrayList::new,
|
|
||||||
List::add,
|
|
||||||
(l1, l2) -> { l1.addAll(l2); return l1; },
|
|
||||||
Collections::unmodifiableList);
|
|
||||||
List<Path> jarList = StreamSupport.stream(fs.getRootDirectories().spliterator(), false).flatMap(new Function<Path, Stream<Path>>() {
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
public Stream<Path> apply(Path path) {
|
|
||||||
return Files.list(path.resolve(Constants.LIBRARIES_FOLDER))
|
|
||||||
.filter(Files::isRegularFile)
|
|
||||||
.filter(p -> p.getFileName().toString().endsWith(".jar"));
|
|
||||||
}
|
|
||||||
}).flatMap(new Function<Path, Stream<Path>>() {
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
public Stream<Path> apply(Path path) {
|
|
||||||
return StreamSupport.stream(FileSystems.newFileSystem(path, null).getRootDirectories().spliterator(), false);
|
|
||||||
}
|
|
||||||
}).collect(immutableListCollector);
|
|
||||||
|
|
||||||
String mainClassName = mainAttributes.getValue(Constants.ManifestAttributes.MAIN_CLASS);
|
String mainClassName = mainAttributes.getValue(Constants.ManifestAttributes.MAIN_CLASS);
|
||||||
String mainModuleName = mainAttributes.getValue(Constants.ManifestAttributes.MAIN_MODULE);
|
String mainModuleName = mainAttributes.getValue(Constants.ManifestAttributes.MAIN_MODULE);
|
||||||
Class<?> mainClass = MainClassLoader.loadMainClass(jarList, mainModuleName, mainClassName);
|
|
||||||
|
Consumer<Class<?>> runner = new Consumer<Class<?>>() {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public void accept(Class<?> mainClass) {
|
||||||
try {
|
try {
|
||||||
Method mainMethod = mainClass.getMethod("main", String[].class);
|
Method mainMethod = mainClass.getMethod("main", String[].class);
|
||||||
Class<?> returnType = mainMethod.getReturnType();
|
Class<?> returnType = mainMethod.getReturnType();
|
||||||
@@ -117,5 +103,13 @@ public class Launcher {
|
|||||||
throw new IllegalArgumentException(String.format("No valid main method found in class '%s'", mainClass), nsme);
|
throw new IllegalArgumentException(String.format("No valid main method found in class '%s'", mainClass), nsme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
MainRunner.run(
|
||||||
|
currentJar,
|
||||||
|
mainModuleName,
|
||||||
|
mainClassName,
|
||||||
|
Constants.LIBRARIES_FOLDER,
|
||||||
|
runner);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
package net.woggioni.envelope;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
class MainClassLoader {
|
|
||||||
@SneakyThrows
|
|
||||||
static Class<?> loadMainClass(Iterable<Path> roots, String mainModuleName, String mainClassName) {
|
|
||||||
ClassLoader pathClassLoader = new net.woggioni.xclassloader.PathClassLoader(roots, ClassLoader.getSystemClassLoader().getParent());
|
|
||||||
Thread.currentThread().setContextClassLoader(pathClassLoader);
|
|
||||||
return pathClassLoader.loadClass(mainClassName);
|
|
||||||
}
|
|
||||||
}
|
|
35
launcher/src/main/java/net/woggioni/envelope/MainRunner.java
Normal file
35
launcher/src/main/java/net/woggioni/envelope/MainRunner.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package net.woggioni.envelope;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.woggioni.xclassloader.jar.JarFile;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
|
||||||
|
class MainRunner {
|
||||||
|
@SneakyThrows
|
||||||
|
static void run(JarFile currentJarFile,
|
||||||
|
String mainModuleName,
|
||||||
|
String mainClassName,
|
||||||
|
String librariesFolder,
|
||||||
|
Consumer<Class<?>> runner) {
|
||||||
|
List<URL> jarList = new ArrayList<>();
|
||||||
|
Enumeration<JarEntry> entries = currentJarFile.entries();
|
||||||
|
while(entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
if(!entry.isDirectory() && name.startsWith(librariesFolder) && name.endsWith(".jar")) {
|
||||||
|
jarList.add(currentJarFile.getNestedJarFile(entry).getUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (URLClassLoader cl = new URLClassLoader(jarList.toArray(new URL[0]), ClassLoader.getSystemClassLoader().getParent())) {
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
runner.accept(cl.loadClass(mainClassName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,55 +0,0 @@
|
|||||||
package net.woggioni.envelope;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.lang.module.Configuration;
|
|
||||||
import java.lang.module.ModuleFinder;
|
|
||||||
import java.lang.module.ResolvedModule;
|
|
||||||
import java.lang.module.ModuleReference;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import net.woggioni.xclassloader.PathClassLoader;
|
|
||||||
import net.woggioni.xclassloader.ModuleClassLoader;
|
|
||||||
import net.woggioni.xclassloader.PathModuleFinder;
|
|
||||||
|
|
||||||
class MainClassLoader {
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
static Class<?> loadMainClass(Iterable<Path> roots, String mainModuleName, String mainClassName) {
|
|
||||||
if (mainModuleName == null) {
|
|
||||||
ClassLoader pathClassLoader = new net.woggioni.xclassloader.PathClassLoader(roots, ClassLoader.getSystemClassLoader().getParent());
|
|
||||||
Thread.currentThread().setContextClassLoader(pathClassLoader);
|
|
||||||
return pathClassLoader.loadClass(mainClassName);
|
|
||||||
} else {
|
|
||||||
ModuleLayer bootLayer = ModuleLayer.boot();
|
|
||||||
Configuration bootConfiguration = bootLayer.configuration();
|
|
||||||
Configuration cfg = bootConfiguration.resolve(new PathModuleFinder(roots), ModuleFinder.of(), Collections.singletonList(mainModuleName));
|
|
||||||
Map<String, ClassLoader> packageMap = new TreeMap<>();
|
|
||||||
ModuleLayer.Controller controller =
|
|
||||||
ModuleLayer.defineModules(cfg, Collections.singletonList(ModuleLayer.boot()), moduleName -> {
|
|
||||||
ModuleReference modRef = cfg.findModule(moduleName)
|
|
||||||
.map(ResolvedModule::reference)
|
|
||||||
.orElseThrow();
|
|
||||||
ClassLoader cl = new ModuleClassLoader(
|
|
||||||
Collections.unmodifiableMap(packageMap),
|
|
||||||
modRef
|
|
||||||
);
|
|
||||||
for(String packageName : modRef.descriptor().packages()) {
|
|
||||||
packageMap.put(packageName, cl);
|
|
||||||
}
|
|
||||||
return cl;
|
|
||||||
});
|
|
||||||
ModuleLayer layer = controller.layer();
|
|
||||||
Module mainModule = layer.findModule(mainModuleName).orElseThrow(
|
|
||||||
() -> new IllegalStateException(String.format("Main module '%s' not found", mainModuleName)));
|
|
||||||
return Optional.ofNullable(mainClassName)
|
|
||||||
.or(() -> mainModule.getDescriptor().mainClass())
|
|
||||||
.map(className -> Class.forName(mainModule, className))
|
|
||||||
.orElseThrow(() -> new IllegalStateException(String.format("Unable to determine main class name for module '%s'", mainModule.getName())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,86 @@
|
|||||||
|
package net.woggioni.envelope;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.lang.module.Configuration;
|
||||||
|
import java.lang.module.ModuleFinder;
|
||||||
|
import java.lang.module.ResolvedModule;
|
||||||
|
import java.lang.module.ModuleReference;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import net.woggioni.xclassloader.ModuleClassLoader;
|
||||||
|
import net.woggioni.xclassloader.JarFileModuleFinder;
|
||||||
|
import net.woggioni.xclassloader.jar.JarFile;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
|
||||||
|
class MainRunner {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
static void run(JarFile currentJarFile,
|
||||||
|
String mainModuleName,
|
||||||
|
String mainClassName,
|
||||||
|
String librariesFolder,
|
||||||
|
Consumer<Class<?>> runner) {
|
||||||
|
if(mainModuleName == null) {
|
||||||
|
List<URL> jarList = new ArrayList<>();
|
||||||
|
Enumeration<JarEntry> entries = currentJarFile.entries();
|
||||||
|
while(entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
if(!entry.isDirectory() && name.startsWith(librariesFolder) && name.endsWith(".jar")) {
|
||||||
|
jarList.add(currentJarFile.getNestedJarFile(entry).getUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (URLClassLoader cl = new URLClassLoader(jarList.toArray(new URL[0]), ClassLoader.getSystemClassLoader().getParent())) {
|
||||||
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
runner.accept(cl.loadClass(mainClassName));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<JarFile> jarList = new ArrayList<>();
|
||||||
|
Enumeration<JarEntry> entries = currentJarFile.entries();
|
||||||
|
while(entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
if(!entry.isDirectory() && name.startsWith("LIB-INF") && name.endsWith(".jar")) {
|
||||||
|
jarList.add(currentJarFile.getNestedJarFile(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModuleLayer bootLayer = ModuleLayer.boot();
|
||||||
|
Configuration bootConfiguration = bootLayer.configuration();
|
||||||
|
Configuration cfg = bootConfiguration.resolve(new JarFileModuleFinder(jarList), ModuleFinder.of(), Collections.singletonList(mainModuleName));
|
||||||
|
Map<String, ClassLoader> packageMap = new TreeMap<>();
|
||||||
|
ModuleLayer.Controller controller =
|
||||||
|
ModuleLayer.defineModules(cfg, Collections.singletonList(ModuleLayer.boot()), moduleName -> {
|
||||||
|
ModuleReference modRef = cfg.findModule(moduleName)
|
||||||
|
.map(ResolvedModule::reference)
|
||||||
|
.orElseThrow();
|
||||||
|
ClassLoader cl = new ModuleClassLoader(
|
||||||
|
Collections.unmodifiableMap(packageMap),
|
||||||
|
modRef
|
||||||
|
);
|
||||||
|
for(String packageName : modRef.descriptor().packages()) {
|
||||||
|
packageMap.put(packageName, cl);
|
||||||
|
}
|
||||||
|
return cl;
|
||||||
|
});
|
||||||
|
ModuleLayer layer = controller.layer();
|
||||||
|
Module mainModule = layer.findModule(mainModuleName).orElseThrow(
|
||||||
|
() -> new IllegalStateException(String.format("Main module '%s' not found", mainModuleName)));
|
||||||
|
runner.accept(Optional.ofNullable(mainClassName)
|
||||||
|
.or(() -> mainModule.getDescriptor().mainClass())
|
||||||
|
.map(className -> Class.forName(mainModule, className))
|
||||||
|
.orElseThrow(() -> new IllegalStateException(
|
||||||
|
String.format("Unable to determine main class name for module '%s'", mainModule.getName()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,8 @@ pluginManagement {
|
|||||||
content {
|
content {
|
||||||
includeModule 'net.woggioni.gradle', 'lombok'
|
includeModule 'net.woggioni.gradle', 'lombok'
|
||||||
includeModule 'net.woggioni.gradle.lombok', 'net.woggioni.gradle.lombok.gradle.plugin'
|
includeModule 'net.woggioni.gradle.lombok', 'net.woggioni.gradle.lombok.gradle.plugin'
|
||||||
|
includeModule 'net.woggioni.gradle', 'multi-version-jar'
|
||||||
|
includeModule 'net.woggioni.gradle.multi-version-jar', 'net.woggioni.gradle.multi-version-jar.gradle.plugin'
|
||||||
includeModule 'net.woggioni.gradle', 'multi-release-jar'
|
includeModule 'net.woggioni.gradle', 'multi-release-jar'
|
||||||
includeModule 'net.woggioni.gradle.multi-release-jar', 'net.woggioni.gradle.multi-release-jar.gradle.plugin'
|
includeModule 'net.woggioni.gradle.multi-release-jar', 'net.woggioni.gradle.multi-release-jar.gradle.plugin'
|
||||||
}
|
}
|
||||||
@@ -12,8 +14,9 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'net.woggioni.gradle.lombok' version "0.1"
|
id 'net.woggioni.gradle.lombok' version getProperty('lys-gradle-plugins.version')
|
||||||
id "net.woggioni.gradle.multi-release-jar" version "0.1"
|
id 'net.woggioni.gradle.multi-version-jar' version getProperty('lys-gradle-plugins.version')
|
||||||
|
id 'net.woggioni.gradle.multi-release-jar' version getProperty('lys-gradle-plugins.version')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -220,7 +220,7 @@ public class EnvelopeJarTask extends AbstractArchiveTask {
|
|||||||
Attributes mainAttributes = manifest.getMainAttributes();
|
Attributes mainAttributes = manifest.getMainAttributes();
|
||||||
mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||||
mainAttributes.put(Attributes.Name.MAIN_CLASS, Constants.DEFAULT_LAUNCHER);
|
mainAttributes.put(Attributes.Name.MAIN_CLASS, Constants.DEFAULT_LAUNCHER);
|
||||||
mainAttributes.put(Attributes.Name.MULTI_RELEASE, "true");
|
mainAttributes.putValue("Multi-Release", "true");
|
||||||
mainAttributes.put(new Attributes.Name("Launcher-Agent-Class"), Constants.AGENT_LAUNCHER);
|
mainAttributes.put(new Attributes.Name("Launcher-Agent-Class"), Constants.AGENT_LAUNCHER);
|
||||||
mainAttributes.put(new Attributes.Name("Can-Redefine-Classes"), "true");
|
mainAttributes.put(new Attributes.Name("Can-Redefine-Classes"), "true");
|
||||||
mainAttributes.put(new Attributes.Name("Can-Retransform-Classes"), "true");
|
mainAttributes.put(new Attributes.Name("Can-Retransform-Classes"), "true");
|
||||||
|
@@ -28,6 +28,7 @@ import java.util.zip.ZipEntry;
|
|||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
|
||||||
public class EnvelopePluginTest {
|
public class EnvelopePluginTest {
|
||||||
@@ -46,13 +47,13 @@ public class EnvelopePluginTest {
|
|||||||
|
|
||||||
private static URL getResource(String resourceName, Class<?> cls, ClassLoader cl) {
|
private static URL getResource(String resourceName, Class<?> cls, ClassLoader cl) {
|
||||||
URL result = null;
|
URL result = null;
|
||||||
if(cl != null) {
|
if (cl != null) {
|
||||||
result = cl.getResource(resourceName);
|
result = cl.getResource(resourceName);
|
||||||
}
|
}
|
||||||
if(result == null && cls != null) {
|
if (result == null && cls != null) {
|
||||||
result = cls.getClassLoader().getResource(resourceName);
|
result = cls.getClassLoader().getResource(resourceName);
|
||||||
}
|
}
|
||||||
if(result == null) {
|
if (result == null) {
|
||||||
result = EnvelopePluginTest.class.getClassLoader().getResource(resourceName);
|
result = EnvelopePluginTest.class.getClassLoader().getResource(resourceName);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -90,10 +91,11 @@ public class EnvelopePluginTest {
|
|||||||
GradleRunner runner = GradleRunner.create()
|
GradleRunner runner = GradleRunner.create()
|
||||||
.withDebug(true)
|
.withDebug(true)
|
||||||
.withProjectDir(rootProjectDir.toFile())
|
.withProjectDir(rootProjectDir.toFile())
|
||||||
.withArguments(Arrays.concat(new String[] {"-s", "--info", "-g", testGradleHomeDir.toString()}, taskName))
|
.withArguments(Arrays.concat(new String[]{"-s", "--info", "-g", testGradleHomeDir.toString()}, taskName))
|
||||||
.withPluginClasspath();
|
.withPluginClasspath();
|
||||||
System.out.println(runner.build().getOutput());
|
System.out.println(runner.build().getOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path testDir;
|
private Path testDir;
|
||||||
private static final Path testGradleHomeDir = Paths.get(System.getProperty("test.gradle.user.home"));
|
private static final Path testGradleHomeDir = Paths.get(System.getProperty("test.gradle.user.home"));
|
||||||
|
|
||||||
@@ -102,14 +104,14 @@ public class EnvelopePluginTest {
|
|||||||
public void setup(@TempDir Path testDir) {
|
public void setup(@TempDir Path testDir) {
|
||||||
this.testDir = testDir;
|
this.testDir = testDir;
|
||||||
String resourceFile = "test-resources.txt";
|
String resourceFile = "test-resources.txt";
|
||||||
try(BufferedReader reader =
|
try (BufferedReader reader =
|
||||||
Optional.ofNullable(getResource(resourceFile).openStream())
|
Optional.ofNullable(getResource(resourceFile).openStream())
|
||||||
.map(InputStreamReader::new)
|
.map(InputStreamReader::new)
|
||||||
.map(BufferedReader::new)
|
.map(BufferedReader::new)
|
||||||
.orElseThrow(() -> new FileNotFoundException(resourceFile))) {
|
.orElseThrow(() -> new FileNotFoundException(resourceFile))) {
|
||||||
while(true) {
|
while (true) {
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
if(line == null) break;
|
if (line == null) break;
|
||||||
Path destination = testDir.resolve(line);
|
Path destination = testDir.resolve(line);
|
||||||
Files.createDirectories(destination.getParent());
|
Files.createDirectories(destination.getParent());
|
||||||
Files.copy(getResource(line).openStream(), destination);
|
Files.copy(getResource(line).openStream(), destination);
|
||||||
|
Reference in New Issue
Block a user