From 2516139a311b7044a8cecd660e8d1ca5eee80fff Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Fri, 1 Jul 2022 04:36:02 +0800 Subject: [PATCH] fixed JPMS bug --- build.gradle | 3 +- common/build.gradle | 5 - launcher/build.gradle | 10 ++ .../net/woggioni/envelope/MainRunner.java | 9 +- launcher/src/main/java11/module-info.java | 1 - .../net/woggioni/envelope/MainRunner.java | 10 ++ loader/build.gradle | 4 + .../envelope/loader/AbstractJarFile.java | 4 +- .../net/woggioni/envelope/loader/JarFile.java | 3 +- .../envelope/loader/JarFileWrapper.java | 3 +- .../AbstractJarFile.java | 120 ++++++++++++++++++ .../JarFileModuleFinder.java | 6 +- settings.gradle | 1 + .../gradle/envelope/EnvelopeJarTask.java | 6 +- 14 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 loader/src/main/java11/net.woggioni.envelope.loader/AbstractJarFile.java diff --git a/build.gradle b/build.gradle index 4856d6e..232b158 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,6 @@ allprojects { dependencies { add("testImplementation", create(group: "org.junit.jupiter", name:"junit-jupiter-api", version: project["version.junitJupiter"])) add("testRuntimeOnly", create(group: "org.junit.jupiter", name: "junit-jupiter-engine", version: project["version.junitJupiter"])) - add("testImplementation", gradleTestKit()) } tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { @@ -63,6 +62,8 @@ configurations { dependencies { tar project(path: "launcher", configuration: 'tar') embedded project(path: "common", configuration: "archives") + + testImplementation gradleTestKit() } tasks.named('processResources', ProcessResources) { diff --git a/common/build.gradle b/common/build.gradle index 2e6880d..e69de29 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,5 +0,0 @@ -jar { - manifest { - attributes "Automatic-Module-Name" : "net.woggioni.envelope" - } -} \ No newline at end of file diff --git a/launcher/build.gradle b/launcher/build.gradle index 9174927..3caccf9 100644 --- a/launcher/build.gradle +++ b/launcher/build.gradle @@ -58,4 +58,14 @@ Provider tarTaskProvider = tasks.register("tar", Tar) { artifacts { tar tarTaskProvider +} + +compileJava11 { + options.javaModuleMainClass = 'net.woggioni.envelope.Launcher' + options.compilerArgs += ['--add-reads', 'net.woggioni.envelope=ALL-UNNAMED'] +} + +multiReleaseJar { + patchModule('net.woggioni.envelope', project(':common').jar.archiveFile + .map(RegularFile.&getAsFile).map(File.&toString)) } \ No newline at end of file diff --git a/launcher/src/main/java/net/woggioni/envelope/MainRunner.java b/launcher/src/main/java/net/woggioni/envelope/MainRunner.java index 3a7a786..54cf468 100644 --- a/launcher/src/main/java/net/woggioni/envelope/MainRunner.java +++ b/launcher/src/main/java/net/woggioni/envelope/MainRunner.java @@ -7,7 +7,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; class MainRunner { @SneakyThrows @@ -16,6 +15,14 @@ class MainRunner { String mainClassName, List classpath, Consumer> runner) { + if(mainClassName == null) { + throw new RuntimeException( + String.format( + "Missing main attribute '%s' from manifest", + Constants.ManifestAttributes.MAIN_CLASS + ) + ); + } URL[] urls = classpath.stream().map(Launcher::getURL).toArray(URL[]::new); try (URLClassLoader cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader().getParent())) { Thread.currentThread().setContextClassLoader(cl); diff --git a/launcher/src/main/java11/module-info.java b/launcher/src/main/java11/module-info.java index 1e8f4d2..ae344e5 100644 --- a/launcher/src/main/java11/module-info.java +++ b/launcher/src/main/java11/module-info.java @@ -1,6 +1,5 @@ module net.woggioni.envelope { requires java.logging; requires static lombok; - requires net.woggioni.envelope.loader; requires java.instrument; } \ No newline at end of file diff --git a/launcher/src/main/java11/net/woggioni/envelope/MainRunner.java b/launcher/src/main/java11/net/woggioni/envelope/MainRunner.java index b32f7d4..125b041 100644 --- a/launcher/src/main/java11/net/woggioni/envelope/MainRunner.java +++ b/launcher/src/main/java11/net/woggioni/envelope/MainRunner.java @@ -19,6 +19,7 @@ import java.net.URLClassLoader; import lombok.SneakyThrows; +import net.woggioni.envelope.Constants; import net.woggioni.envelope.loader.ModuleClassLoader; import net.woggioni.envelope.loader.JarFileModuleFinder; import net.woggioni.envelope.loader.JarFile; @@ -37,6 +38,14 @@ class MainRunner { List classpath, Consumer> runner) { if(mainModuleName == null) { + if(mainClassName == null) { + throw new RuntimeException( + String.format( + "Missing main attribute '%s' from manifest", + Constants.ManifestAttributes.MAIN_CLASS + ) + ); + } URL[] urls = classpath.stream().map(Launcher::getURL).toArray(URL[]::new); try (URLClassLoader cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader().getParent())) { Thread.currentThread().setContextClassLoader(cl); @@ -67,6 +76,7 @@ class MainRunner { ModuleLayer layer = controller.layer(); Module mainModule = layer.findModule(mainModuleName).orElseThrow( () -> new IllegalStateException(String.format("Main module '%s' not found", mainModuleName))); + Optional mainClassOpt = Optional.ofNullable(mainClassName); runner.accept(Optional.ofNullable(mainClassName) .or(() -> mainModule.getDescriptor().mainClass()) .map(className -> Class.forName(mainModule, className)) diff --git a/loader/build.gradle b/loader/build.gradle index 4e2f8bc..8376290 100644 --- a/loader/build.gradle +++ b/loader/build.gradle @@ -4,4 +4,8 @@ plugins { ext { setProperty('jpms.module.name', 'net.woggioni.envelope.loader') +} + +compileJava11 { + exclude('module-info.java') } \ No newline at end of file diff --git a/loader/src/main/java/net/woggioni/envelope/loader/AbstractJarFile.java b/loader/src/main/java/net/woggioni/envelope/loader/AbstractJarFile.java index 3bed58b..23d723b 100644 --- a/loader/src/main/java/net/woggioni/envelope/loader/AbstractJarFile.java +++ b/loader/src/main/java/net/woggioni/envelope/loader/AbstractJarFile.java @@ -35,8 +35,8 @@ abstract class AbstractJarFile extends java.util.jar.JarFile { * @param file the root jar file. * @throws IOException on IO error */ - AbstractJarFile(File file) throws IOException { - super(file); + AbstractJarFile(File file, boolean verify, int mode) throws IOException { + super(file, verify, mode); } /** diff --git a/loader/src/main/java/net/woggioni/envelope/loader/JarFile.java b/loader/src/main/java/net/woggioni/envelope/loader/JarFile.java index 8608f36..da1bb7b 100644 --- a/loader/src/main/java/net/woggioni/envelope/loader/JarFile.java +++ b/loader/src/main/java/net/woggioni/envelope/loader/JarFile.java @@ -38,6 +38,7 @@ import java.util.jar.Manifest; import java.util.stream.Stream; import java.util.stream.StreamSupport; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but @@ -129,7 +130,7 @@ public class JarFile extends AbstractJarFile implements Iterable manifestSupplier) throws IOException { - super(rootFile.getFile()); + super(rootFile.getFile(), true, ZipFile.OPEN_READ); this.rootFile = rootFile; this.pathFromRoot = pathFromRoot; CentralDirectoryParser parser = new CentralDirectoryParser(); diff --git a/loader/src/main/java/net/woggioni/envelope/loader/JarFileWrapper.java b/loader/src/main/java/net/woggioni/envelope/loader/JarFileWrapper.java index 775a33d..bc45f2e 100644 --- a/loader/src/main/java/net/woggioni/envelope/loader/JarFileWrapper.java +++ b/loader/src/main/java/net/woggioni/envelope/loader/JarFileWrapper.java @@ -26,6 +26,7 @@ import java.util.jar.JarEntry; import java.util.jar.Manifest; import java.util.stream.Stream; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * A wrapper used to create a copy of a {@link JarFile} so that it can be safely closed @@ -38,7 +39,7 @@ class JarFileWrapper extends AbstractJarFile { private final JarFile parent; JarFileWrapper(JarFile parent) throws IOException { - super(parent.getRootJarFile().getFile()); + super(parent.getRootJarFile().getFile(), true, ZipFile.OPEN_READ); this.parent = parent; } diff --git a/loader/src/main/java11/net.woggioni.envelope.loader/AbstractJarFile.java b/loader/src/main/java11/net.woggioni.envelope.loader/AbstractJarFile.java new file mode 100644 index 0000000..61a82eb --- /dev/null +++ b/loader/src/main/java11/net.woggioni.envelope.loader/AbstractJarFile.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.woggioni.envelope.loader; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Permission; +import java.util.Objects; +import java.util.zip.ZipFile; +import java.util.stream.Stream; +import java.util.jar.JarEntry; + +/** + * Base class for extended variants of {@link java.util.jar.JarFile}. + * + * @author Phillip Webb + */ +abstract class AbstractJarFile extends java.util.jar.JarFile { + + private static final String META_INF_VERSION_PREFIX = "META-INF/versions/"; + + private final Runtime.Version version; // current version + private final int versionFeature; // version.feature() + + private String getBasename(String name) { + if (name.startsWith(META_INF_VERSION_PREFIX)) { + int off = META_INF_VERSION_PREFIX.length(); + int index = name.indexOf('/', off); + try { + // filter out dir META-INF/versions/ and META-INF/versions/*/ + // and any entry with version > 'version' + if (index == -1 || index == (name.length() - 1) || + Integer.parseInt(name, off, index, 10) > versionFeature) { + return null; + } + } catch (NumberFormatException x) { + return null; // remove malformed entries silently + } + // map to its base name + return name.substring(index + 1); + } + return name; + } + + @Override + public Stream versionedStream() { + return stream() + .map(JarEntry::getName) + .map(this::getBasename) + .filter(Objects::nonNull) + .distinct() + .map(this::getJarEntry) + .filter(Objects::nonNull); + } + + /** + * Create a new {@link AbstractJarFile}. + * @param file the root jar file. + * @throws IOException on IO error + */ + AbstractJarFile(File file, boolean verify, int mode) throws IOException { + super(file, verify, mode); + this.version = Runtime.version(); + this.versionFeature = this.version.feature(); + } + + /** + * Return a URL that can be used to access this JAR file. NOTE: the specified URL + * cannot be serialized and or cloned. + * @return the URL + * @throws MalformedURLException if the URL is malformed + */ + abstract URL getUrl() throws MalformedURLException; + + /** + * Return the {@link JarFileType} of this instance. + * @return the jar file type + */ + abstract JarFileType getType(); + + /** + * Return the security permission for this JAR. + * @return the security permission. + */ + abstract Permission getPermission(); + + /** + * Return an {@link InputStream} for the entire jar contents. + * @return the contents input stream + * @throws IOException on IO error + */ + abstract InputStream getInputStream() throws IOException; + + /** + * The type of a {@link JarFile}. + */ + enum JarFileType { + + DIRECT, NESTED_DIRECTORY, NESTED_JAR + + } + +} diff --git a/loader/src/main/java11/net.woggioni.envelope.loader/JarFileModuleFinder.java b/loader/src/main/java11/net.woggioni.envelope.loader/JarFileModuleFinder.java index 5c4da81..8dcf19e 100644 --- a/loader/src/main/java11/net.woggioni.envelope.loader/JarFileModuleFinder.java +++ b/loader/src/main/java11/net.woggioni.envelope.loader/JarFileModuleFinder.java @@ -44,7 +44,6 @@ public class JarFileModuleFinder implements ModuleFinder { static final Pattern LEADING_DOTS = Pattern.compile("^\\."); static final Pattern TRAILING_DOTS = Pattern.compile("\\.$"); } - private final Map> modules; @SneakyThrows @@ -122,12 +121,9 @@ public class JarFileModuleFinder implements ModuleFinder { public JarFileModuleFinder(JarFile ...jarFiles) { this(Arrays.asList(jarFiles)); } - - - private static final String META_INF_VERSION_PREFIX = "META-INF/versions/"; private static Set collectPackageNames(JarFile jarFile) { Set result = jarFile - .stream() + .versionedStream() .filter(entry -> entry.getName().endsWith(".class")) .map(entry -> { String entryName = entry.getName(); diff --git a/settings.gradle b/settings.gradle index 8c95110..eb679ad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,3 +25,4 @@ rootProject.name = 'envelope' include 'common' include 'launcher' include 'loader' + diff --git a/src/main/java/net/woggioni/gradle/envelope/EnvelopeJarTask.java b/src/main/java/net/woggioni/gradle/envelope/EnvelopeJarTask.java index 7368a9a..c2d736c 100644 --- a/src/main/java/net/woggioni/gradle/envelope/EnvelopeJarTask.java +++ b/src/main/java/net/woggioni/gradle/envelope/EnvelopeJarTask.java @@ -58,7 +58,7 @@ public class EnvelopeJarTask extends AbstractArchiveTask { } } - @Getter(onMethod_ = {@Input}) + @Getter(onMethod_ = {@Input, @Optional}) private final Property mainClass; @Getter(onMethod_ = {@Input, @Optional}) @@ -235,7 +235,9 @@ public class EnvelopeJarTask extends AbstractArchiveTask { 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-Retransform-Classes"), "true"); - mainAttributes.putValue(Constants.ManifestAttributes.MAIN_CLASS, mainClass.get()); + if(mainClass.isPresent()) { + mainAttributes.putValue(Constants.ManifestAttributes.MAIN_CLASS, mainClass.get()); + } if(mainModule.isPresent()) { mainAttributes.putValue(Constants.ManifestAttributes.MAIN_MODULE, mainModule.get()); }