Giter Site home page Giter Site logo

layrry's Introduction

ModiTect - Tooling for the Java Module System

Version 1.0.0.Final - 2023-05-03

The ModiTect project aims at providing productivity tools for working with the Java module system ("Jigsaw").

Currently the following tasks are supported:

  • Generating module-info.java descriptors for given artifacts (Maven dependencies or local JAR files)
  • Adding module descriptors to your project's JAR as well as existing JAR files (dependencies)
  • Creating module runtime images

Compared to authoring module descriptors by hand, using ModiTect saves you work by defining dependence clauses based on your project's dependencies, describing exported and opened packages with patterns (instead of listing all packages separately), auto-detecting service usages and more. You also can use ModiTect to add a module descriptor to your project JAR while staying on Java 8 with your own build.

In future versions functionality may be added to work with other tools like jmod etc. under Maven and other dependency management tools in a comfortable manner.

Usage

ModiTect's functionality is currently exclusively exposed through a Maven plug-in. The core implementation is a separate module, though, so that plug-ins for other build systems such as Gradle could be written, too.

Generating module-info.java descriptors

To create a module-info.java descriptor for a given artifact, configure the generate-module-info goal as follows:

...
<plugin>
    <groupId>org.moditect</groupId>
    <artifactId>moditect-maven-plugin</artifactId>
    <version>1.0.0.Final</version>
    <executions>
        <execution>
            <id>generate-module-info</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate-module-info</goal>
            </goals>
            <configuration>
                <modules>
                    <module>
                        <artifact>
                            <groupId>com.example</groupId>
                            <artifactId>example-core</artifactId>
                            <version>1.0.0.Final</version>
                        </artifact>
                        <additionalDependencies>
                            <dependency>
                                <groupId>com.example</groupId>
                                <artifactId>example-extended</artifactId>
                                <version>1.0.0.Final</version>
                            </dependency>
                        </additionalDependencies>
                        <moduleInfo>
                            <name>com.example.core</name>
                            <exports>
                                !com.example.core.internal*;
                                *;
                            </exports>
                            <requires>
                                static com.some.optional.dependency;
                                !com.excluded.dependency;
                                *;
                            </requires>
                            <opens>
                                com.example.core.internal.controller to javafx.fxml;
                            </opens>
                            <opensResources>
                                com.example.resource;
                                com.example.resource.icon;
                                com.example.resource.sound;
                            </opensResources>
                            <uses>
                                 com.example.SomeService;
                            </uses>
                            <provides>
                                com.example.SomeService with com.example.SomeServiceImpl1,com.example.SomeServiceImpl2;
                            </provides>
                            <addServiceUses>true</addServiceUses>
                        </moduleInfo>
                    </module>
                    <module>
                        ...
                    </module>
                </modules>
                <jdepsExtraArgs>
                  <arg>--upgrade-module-path=...</arg>
                </jdepsExtraArgs>
            </configuration>
        </execution>
    </executions>
</plugin>
...

This will generate a module descriptor at target/generated-sources/com.example.core/module-info.java.

For each module to be processed, the following configuration options exist:

  • artifact: The GAV coordinates of the artifact for which a descriptor should be generated (required)
  • additionalDependencies: Additional artifacts to be processed; useful if the main artifact depends on code from another artifact but doesn't declare a dependency to that one (optional)
  • moduleInfo: Allows fine-grained configuration of the generated module descriptor (optional); has the following sub-elements:
    • name: Name to be used within the descriptor; if not given the name will be derived from the JAR name as per the naming rules for automatic modules (optional)
    • open: Whether the descriptor should be an open module or not (optional, defaults to false)
    • exports: List of name patterns for describing the exported packages of the module, separated by ";". Patterns can be inclusive or exclusive (starting with "!") and may contain the "*" as a wildcard. Inclusive patterns may be qualified exports ("to xyz"). For each package from the module, the given patterns are processed in the order they are given. As soon a package is matched by an inclusive pattern, the package will be added to the list of exported packages and no further patterns will be applied. As soon as a package is matched by an exclusive pattern, this package will not be added to the list of exported packages and no further patterns will be applied. (optional; the default value is "*;", i.e. all packages will be exported)
    • opens: List of name patterns for describing the open packages of the module, separated by ";". Patterns can be inclusive or exclusive (starting with "!") and may contain the "*" as a wildcard. Inclusive patterns may be qualified exports ("to xyz"). For each package from the module, the given patterns are processed in the order they are given. As soon a package is matched by an inclusive pattern, the package will be added to the list of open packages and no further patterns will be applied. As soon as a package is matched by an exclusive pattern, this package will not be added to the list of open packages and no further patterns will be applied. (optional; the default value is "!*;", i.e. no packages will be opened)
    • opensResources: List of package names only containing resources as png, css or mp3 files i.e. everything not being java files, separated by ";". For JavaFX 9+, it is required to force open resource-only packages. Please, refer to javafxports/openjdk-jfx#441 for more details. opensResources allows to do this. Contrary to opens, opensResources cannot accept patterns because Moditect manages patterns using a technical solution that can only works with compiled java classes. Hence, each resource-only packages must be declared one by one.
    • requires: List of name patterns for describing the dependences of the module, based on the automatically determined dependences. Patterns are inclusive or exclusive (starting with "!") and may contain the "*" character as a wildcard. Inclusive patterns may contain the static and transitive modifiers, in which case those modifiers will override the modifiers of the automatically determined dependence. For each of the automatically determined dependences of the module, the given patterns are processed in the order they are given. As soon as a dependence is matched by a pattern, the dependence will be added to the list of dependences (if the pattern is inclusive) or the dependence will be filtered out (for exclusive patterns) and no further patterns will be applied. Usually, only a few dependences will be given explicitly in order to override their modifiers, followed by a *; pattern to add all remaining automatically determined dependences.
    • addServiceUses: If true, the given artifact will be scanned for usages of ServiceLoader#load() and if usages passing a class literal are found (load( MyService.class )), an equivalent uses() clause will be added to the generated descriptor; usages of load() where a non-literal class object is passed, are ignored (optional, defaults to false)
    • uses: List of names of used services, separated by ";" only required if addServiceUses cannot be used due to dynamic invocations of ServiceLoader#load(), i.e. no class literal is passed (optional)
    • provides: List of services with their provided service implementations, separated by ";". A service and its implementation must be separated by the keyword "with" e.g. serviceX with implementationX; serviceY with implementationY;. If the module implements a particular service through several service implementations, those implementation classes must be separated by "," e.g. myService with implementation1, implementation2, implementation3;.
    • jdepsExtraArgs: A list of arguments passed to the jdeps invocation for creating a "candidate descriptor"

It is also possible to run this goal directly, specifying the different options as JVM parameters like this:

mvn moditect:generate-module-info \
    -Dmoditect.artifact=com.example:example-core:1.0.0.Final \
    -Dmoditect.moduleName=com.example.core \
    -Dmoditect.additionalDependencies=com.example:example-extended:1.0.0.Final \
    -Dmoditect.exportExcludes=com\.example\.core\.internal\..* \
    -Dmoditect.addServiceUses=true

Adding a module descriptor to the project JAR

To add a module descriptor to the JAR produced by the current Maven project, configure the add-module-info goal as follows:

...
<plugin>
    <groupId>org.moditect</groupId>
    <artifactId>moditect-maven-plugin</artifactId>
    <version>1.0.0.Final</version>
    <executions>
        <execution>
            <id>add-module-infos</id>
            <phase>package</phase>
            <goals>
                <goal>add-module-info</goal>
            </goals>
            <configuration>
                <jvmVersion>11</jvmVersion>
                <failOnWarning>false</failOnWarning>
                <outputTimestamp>1980-01-01T00:00:02Z</outputTimestamp>
                <module>
                    <moduleInfo>
                        <name>com.example</name>
                        <exports>
                            !com.example.internal.*;
                            *;
                        </exports>
                    </moduleInfo>
                </module>
              <exclusions>
                <exclusion>
                  <groupId>com.acme</groupId>
                  <artifactId>thing</artifactId>
                </exclusion>
              </exclusions>
            </configuration>
        </execution>
    </executions>
</plugin>
...

The optional jvmVersion element allows to define which JVM version the module descriptor should target (leveraging the concept of multi-release JARs). When defined, the module descriptor will be put into META-INF/versions/${jvmVersion}. The value must be 9 or greater. The special value base (the default) can be used to add the descriptor to the root of the final JAR. Putting the descriptor under META-INF/versions can help to increase compatibility with older libraries scanning class files that may fail when encountering the module-info.class file (as chances are lower that such tool will look for class files under META-INF/versions/...).

The optional outputTimestamp element may be used to create reproducible output archive entries, either formatted as ISO 8601 extended offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch. As an alternative you may set ${project.build.outputTimestamp} which also matches the user property used by other Maven plugins such as maven-jar-plugin.

The optional failOnWarning option prevents the build from failing when set to false. The default is to fail.

The optional exclusions option may be used to filter out any compile or runtime dependencies that should not be used, as it might be the case when shading internal dependencies.

The jdepsExtraArgs option can be used to specify a list of arguments passed to the jdeps invocation for creating a "candidate descriptor".

The following configuration options exist for the <module> configuration element:

  • moduleInfoSource: Inline representation of a module-info.java descriptor (optional; either this or moduleInfoFile or moduleInfo must be given)
  • moduleInfoFile: Path to a module-info.java descriptor (optional; either this or moduleInfoSource or moduleInfo must be given)
  • moduleInfo: A moduleInfo configuration as used with the generate-module-info goal (optional; either this or moduleInfoSource or moduleInfoFile must be given)
  • mainClass: The fully-qualified name of the main class to be added to the module descriptor (optional)

Note that moduleInfoSource and moduleInfoFile can be used on Java 8, allowing to add a Java 9 module descriptor to your JAR also if you did not move to Java 9 for your own build yet. moduleInfo can only be used on Java 9 or later.

Adding module descriptors to existing JAR files

To add a module descriptor for a given dependency, configure the add-module-info goal as follows:

...
<plugin>
    <groupId>org.moditect</groupId>
    <artifactId>moditect-maven-plugin</artifactId>
    <version>1.0.0.Final</version>
    <executions>
        <execution>
            <id>add-module-infos</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>add-module-info</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/modules</outputDirectory>
                <modules>
                    <module>
                        <artifact>
                            <groupId>com.example</groupId>
                            <artifactId>example-core</artifactId>
                            <version>1.0.0.Final</version>
                        </artifact>
                        <moduleInfoSource>
                            module com.example.core {
                                requires java.logging;
                                exports com.example.api;
                                provides com.example.api.SomeService
                                    with com.example.internal.SomeServiceImpl;
                            }
                        </moduleInfoSource>
                    </module>
                    <module>
                        ...
                    </module>
                </modules>
            </configuration>
        </execution>
    </executions>
</plugin>
...

For each module to be processed, the following configuration options exist:

  • artifact: The GAV coordinates of the artifact for which a descriptor should be generated (either this or file must be given)
  • file: Path to the file for which a descriptor should be generated (either this or artifact must be given)
  • moduleInfoSource: Inline representation of a module-info.java descriptor (optional; either this or moduleInfoFile or moduleInfo must be given)
  • moduleInfoFile: Path to a module-info.java descriptor (optional; either this or moduleInfoSource or moduleInfo must be given)
  • moduleInfo: A moduleInfo configuration as used with the generate-module-info goal (optional; either this or moduleInfoSource or moduleInfoFile must be given)
  • mainClass: The fully-qualified name of the main class to be added to the module descriptor (optional)
  • version: The version to be added to the module descriptor; if not given and artifact is given, the artifact's version will be used; otherwise no version will be added (optional)

The modularized JARs can be found in the folder given via outputDirectory. The jdepsExtraArgs option can be used to specify a list of arguments passed to the jdeps invocation for creating a "candidate descriptor".

Creating modular runtime images

To create a modular runtime image (see JEP 220), configure the create-runtime-image goal as follows:

...
<plugin>
    <groupId>org.moditect</groupId>
    <artifactId>moditect-maven-plugin</artifactId>
    <version>1.0.0.Final</version>
    <executions>
        <execution>
            <id>create-runtime-image</id>
            <phase>package</phase>
            <goals>
                <goal>create-runtime-image</goal>
            </goals>
            <configuration>
                <modulePath>
                    <path>${project.build.directory}/modules</path>
                </modulePath>
                <modules>
                    <module>com.example.module1</module>
                    <module>com.example.module2</module>
                </modules>
                <excludedResources>
                    <pattern>glob:/com.example/**</pattern>
                </excludedResources>
                <baseJdk>version=9,vendor=openjdk,platform=linux-x64</baseJdk>
                <launcher>
                    <name>helloWorld</name>
                    <module>com.example.module1</module>
                </launcher>
                <outputDirectory>
                    ${project.build.directory}/jlink-image
                </outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>
...

The following configuration options exist:

  • modulePath: One or more directories with modules to be considered for creating the image (required); the jmods directory of the current JVM will be added implicitly, so it doesn't have to be given here
  • modules: The module(s) to be used as the root for resolving the modules to be added to the image (required)
  • outputDirectory: Directory in which the runtime image should be created (required)
  • launcher: file name and main module for creating a launcher file (optional)
  • stripDebug whether to strip debug symbols or not (optional, defaults to false)
  • excludedResources list of patterns for excluding matching resources from the created runtime image
  • baseJdk: requirements for identifying a JDK in ~/.m2/toolchains.xml whose jmod files will be used when creating the runtime image (optional; if not given the JDK running the current build will be used). Must unambiguously identify one toolchain entry of type jdk that matches all given requirements in its <provides> configuration. This can be used for creating runtime images on one platform (e.g. OS X) while targeting another (e.g. Linux).
  • ignoreSigningInformation: Suppresses a fatal error when signed modular JARs are linked in the runtime image. The signature-related files of the signed modular JARs aren’t copied to the runtime image.
  • noManPages: No man pages will be added
  • noHeaderFiles: No native header files will be added
  • bindServices: Link service provider modules and their dependencies
  • jarInclusionPolicy: Whether to add the application JAR and optionally its dependencies to the runtime image, under the jars directory, allowing to run a classpath-based application on a modular runtime image; allowed values are NONE, APP, and APP_WITH_DEPENDENCIES.

In order to identify the JDK images which should go into a custom runtime image for a classpath-based application, you can run the following goal:

list-application-image-modules

This will run the jdeps command under the hood, listing all JDK modules required by the application and its dependencies.

Once the image has been created, it can be executed by running:

./<outputDirectory>/bin/java --module com.example

Or, if a launcher has been configured:

./<outputDirectory>/bin/<launcherName>

Examples

Undertow

The POM file in integrationtest/undertow shows a more complete example. It adds module descriptors for Undertow Core and its dependencies, i.e. it allows to run the Undertow web server based on Java 9 modules.

Run

cd integrationtest/undertow
mvn clean install

to build the example. You then can start Undertow by executing

java --module-path target/modules --module com.example

Alternatively, you can run the modular runtime image created by the example:

./target/jlink-image/bin/helloWorld

Then visit http://localhost:8080/?name=YourName in your browser for the canonical "Hello World" example.

Vert.x

The POM file in integrationtest/vertx shows a more complete example. It adds module descriptors for Vert.x and its dependencies (Netty, Jackson) and creates a modular runtime image with a "hello world" verticle.

Execute

cd integrationtest/vert.x
mvn clean install -Pjlink

to build the example.

You can then run the modular runtime image like so:

./target/jlink-image/bin/helloWorld

Then visit http://localhost:8080/?name=YourName in your browser for the canonical "Hello World" example.

The runtime image has a size of 45 MB, which could be further improved by a few adjustments to the involved libraries. E.g. jackson-databind pulls in java.sql unconditionally which could be avoided by making data converters related to java.sql types an optional feature.

Using Docker

The Vert.x example can also be run on Docker. To do so, run the build with the "docker-base" profile:

mvn clean install -Pdocker-base

This will create an image named moditect/vertx-helloworld-base which contains the jlink image. To run that image execute

docker run --rm -t -i -p 8080:8080 moditect/vertx-helloworld-base

Changes to the application will require to rebuild the entire jlink image which actually isn't needed if just the app itself changed but not its dependencies (used JDK modules or 3rd-party modules). Therefore another image can be build using the "docker" profile:

mvn clean install -Pdocker

This will create an image named moditect/vertx-helloworld which extends the base image and just adds the application module (com.example) on the upgrade module path. Hence that image is very quick to be built (and distributed) once the base image is in place. To run that image execute

docker run --rm -t -i -p 8080:8080 moditect/vertx-helloworld

Status

ModiTect is at an early stage of development and it still has some rough edges. Use it at your own risk.

Further Planned Features

Adding module descriptors to existing JARs is the first functionality implemented in ModiTect. Potential future developments include:

  • Update existing module descriptors (e.g. to remove/replace a requires clause)
  • Better support for generating and adding a module descriptor to the JAR produced by a build itself (i.e. not a JAR it depends on)
  • Install/Deploy updated (modularized) JARs with a new name/classifier etc.
  • Adding transitive modifier to dependences based on whether their types are exposed in a module's exported API or not
  • YOUR ideas :)

Related Work

ModuleTools by Remi Forax shows how to assemble module descriptors using ASM.

License

ModiTect is licensed under the Apache License version 2.0. ASM (which is contained within the ModiTect JAR in the org/moditect/internal/shaded/asm/ package) is licensed under the 3 clause BSD license (see etc/LICENSE_ASM.txt).

layrry's People

Contributors

aalmiray avatar augustnagro avatar dependabot[bot] avatar github-actions[bot] avatar gunnarmorling avatar joschi avatar sullis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

layrry's Issues

Expose Layers config to running application

Working with the modular-tiles example I wanted to add an option (via menu) to add/remove plugins. This requires copying files to the watched plugins directory, and removing files from said directory.

The problem is, the application has no clue where this directory is located! The application can be launched from any directory within the file system, yet the plugins directory is set relative to the basedir of the given layers config file. For this use case I could get away by having access to the value of the computed basedir (calculated by org.moditect.layrry.launcher.LayrryLauncher). This value could be exposed as a System property and would have to blindly assume that the plugins directory remains as plugins.

A better approach would be to let the running application have access to parsed the Layers configuration or even the Layers instance. If the later then it should not be possible for the app to run the Layers once again.

PluginLifecycleListeners will never be initialized if the starting directory is empty

A layer defined for dynamically loaded plugins cannot be initially empty.

Steps to Reproduce

  1. build the project. Note that the vert.x plugin example runs correctly.
  2. remove the membership directory from vertx-example/layrry-links-runner/target/route-plugins/
  3. launch the vert.x example using java -jar.
  4. copy the tournaments plugin to vertx-example/layrry-links-runner/target/route-plugins/
  5. Note that not additional routes are loaded

Stuck at execution

I have a simple maven project that creates layers. Using NetBeans 12.1. I install the launcher-all.jar and platform.jar locally. Yet the execution of the project fails with:

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< net.clementlevallois:layrry-test >------------------
[INFO] Building layrry-test 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ layrry-test ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\levallois\Google Drive\open\layrry-test\src\main\resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ layrry-test ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ layrry-test ---
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/levallois/.m2/repository/org/moditect/layrry/layrry-launcher/1.0-SNAPSHOT/layrry-launcher-1.0-SNAPSHOT.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/levallois/.m2/repository/org/slf4j/slf4j-simple/1.7.29/slf4j-simple-1.7.29.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
nov. 28, 2020 4:13:04 PM org.jboss.shrinkwrap.resolver.impl.maven.logging.LogTransferListener transferFailed
WARNING: Failed downloading org/moditect/layrry/layrry-aggregator/1.0-SNAPSHOT/maven-metadata.xml from https://repo1.maven.org/maven2/. Reason: 
org.eclipse.aether.transfer.MetadataNotFoundException: Could not find metadata org.moditect.layrry:layrry-aggregator:1.0-SNAPSHOT/maven-metadata.xml in central (https://repo1.maven.org/maven2)
Exception in thread "main" java.lang.module.FindException: Unable to derive module descriptor for C:\Users\LEVALL~1\AppData\Local\Temp\layrry-plugins2751936294030724511\0-null-plugins\layrry-launcher-1.0-SNAPSHOT-all.jar
	at java.base/jdk.internal.module.ModulePath.readJar(ModulePath.java:648)
	at java.base/jdk.internal.module.ModulePath.readModule(ModulePath.java:331)
	at java.base/jdk.internal.module.ModulePath.scanDirectory(ModulePath.java:284)
	at java.base/jdk.internal.module.ModulePath.scan(ModulePath.java:232)
	at java.base/jdk.internal.module.ModulePath.scanNextEntry(ModulePath.java:190)
	at java.base/jdk.internal.module.ModulePath.findAll(ModulePath.java:166)
	at org.moditect.layrry.internal.LayersImpl.createModuleLayer(LayersImpl.java:168)
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:119)
	at net.clementlevallois.layrry.test.LayrryLauncher.main(LayrryLauncher.java:28)
Caused by: java.lang.module.InvalidModuleDescriptorException: Provider class org.jboss.shrinkwrap.resolver.api.loadable.SpiServiceLoader not in module
	at java.base/jdk.internal.module.ModulePath.deriveModuleDescriptor(ModulePath.java:555)
	at java.base/jdk.internal.module.ModulePath.readJar(ModulePath.java:644)
	... 8 more

Why would layrry-aggregator be needed?

Error launching a JavaFX application

I noticed a problem when when trying to launch https://github.com/carldea/worldclock/ (from @carldea) using an external Layers config file. The problem stems form the different options there are to launch a JavaFX application. I've replicated the problem with the following code

App.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.net.URL;

public class App extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        URL location = getClass().getResource("app.fxml");
        FXMLLoader fxmlLoader = new FXMLLoader(location);
        HBox hbox = fxmlLoader.load();

        Scene scene = new Scene(hbox);
        stage.setScene(scene);
        stage.setTitle("App");
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

module-info.java

module app {
    exports app;
    requires javafx.base;
    requires javafx.graphics;
    requires javafx.controls;
    requires javafx.fxml;
}

layers.toml

[layers.javafx]
    modules = [
        "org.openjfx:javafx-base:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-controls:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-graphics:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-fxml:jar:{{os.detected.jfxname}}:{{javafx_version}}"]
[layers.core]
    modules = [
        "org.kordamp.ikonli:app:{{project_version}}"]
    parents = ["javafx"]
[main]
  module = "app"
  class = "app.App"

versions.properties

project_version = 12.0.1-SNAPSHOT
javafx_version = 11

The error I get when running is

Exception in thread "main" java.lang.RuntimeException: Couldn't run module main class
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:139)
	at org.moditect.layrry.Layrry.launch(Layrry.java:56)
	at org.moditect.layrry.Layrry.run(Layrry.java:50)
	at org.moditect.layrry.launcher.LayrryLauncher.launch(LayrryLauncher.java:53)
	at org.moditect.layrry.launcher.LayrryLauncher.main(LayrryLauncher.java:35)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:136)
	... 4 more
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: app.App
	at javafx.graphics/javafx.application.Application.launch(Application.java:304)
	at [email protected]/app.App.main(App.java:42)
	... 9 more
Caused by: java.lang.ClassNotFoundException: app.App
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:468)
	at javafx.graphics/javafx.application.Application.launch(Application.java:292)

This is caused by invoking launch() in the main() method, as shown before

    public static void main(String[] args) {
        launch();
    }

However if the launch procedure is changed to

    public static void main(String[] args) {
        Application.launch(App.class, args);
    }

Then the application class is found and launched, however it fails due to another problem related to FXML (#68). The original problem appears to be caused by different strategies involving classloaders:

  • launch() relies on the context classloader to find the target class to load, which may be different from the classloader that loaded the class with the main method to begin with.
  • launch(Class, String...) relies on same classloader as the class with the main method.

I wonder if Layrry would have to setup the context classloader as well. I think it does not make any changes as far as I can tell.

LarryLinkTest is a probable flaky test

I've seen the following error when running on CI appearing a couple of times however it does not reproduce on a local build

[INFO] Running com.example.layrry.links.integrationtest.LayrryLinksTest
16:50:52.913 [main] INFO  c.e.l.l.c.i.LayrryLinksVerticle - Adding plug-in: PluginDescriptor [name=plugins-layrry-links-membership-1.0.0, moduleLayer=com.example.layrry.links.membership]
16:50:53.151 [vert.x-eventloop-thread-0] INFO  c.e.l.l.c.i.LayrryLinksVerticle - Adding router for path: /routes
16:50:53.179 [vert.x-eventloop-thread-0] INFO  c.e.l.l.c.i.LayrryLinksVerticle - Adding router for path: /members
16:50:53.399 [vert.x-eventloop-thread-0] INFO  c.e.l.l.c.i.LayrryLinksVerticle - Server ready! Browse to http://localhost:8080/routes
16:50:55.447 [pool-1-thread-1] INFO  c.e.l.l.c.i.LayrryLinksVerticle - Adding plug-in: PluginDescriptor [name=plugins-layrry-links-tournament-1.0.0, moduleLayer=]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 9.462 s <<< FAILURE! - in com.example.layrry.links.integrationtest.LayrryLinksTest
[ERROR] com.example.layrry.links.integrationtest.LayrryLinksTest.runLayers  Time elapsed: 9.397 s  <<< ERROR!
org.awaitility.core.ConditionTimeoutException: Condition with com.example.layrry.links.integrationtest.LayrryLinksTest was not fulfilled within 5 seconds.
	at com.example.layrry.links.integrationtest.LayrryLinksTest.runLayers(LayrryLinksTest.java:71)

Launcher scripts define explicit classpath

This will make it harder to update dependencies on the fly (such as adding a new config parser) by dropping a JAR into $LAYRRY_HOME/lib. One must edit the launcher script to update the classpath

Linux/OSX

 100   │ CLASSPATH="$BASEDIR"/etc:"$REPO"/layrry-config-yaml-1.0.0.Alpha1.jar:"$REPO"/layrry-config-1.0.0.Alpha1.jar:"$REPO"/os-maven-plugin-1.6.2.jar:"$REPO"/jsr305-3.0.2.jar:"$REPO"/compiler-0.9.7.jar:"$REPO"/snakeyaml-1.27.jar:"$REPO"/
       │ layrry-config-toml-1.0.0.Alpha1.jar:"$REPO"/toml-1.2.jar:"$REPO"/layrry-core-1.0.0.Alpha1.jar:"$REPO"/slf4j-simple-1.7.29.jar:"$REPO"/slf4j-api-1.7.30.jar:"$REPO"/shrinkwrap-resolver-impl-maven-3.1.4.jar:"$REPO"/shrinkwrap-resolv
       │ er-spi-maven-3.1.4.jar:"$REPO"/shrinkwrap-resolver-spi-3.1.4.jar:"$REPO"/maven-resolver-provider-3.6.3.jar:"$REPO"/javax.inject-1.jar:"$REPO"/maven-model-3.6.3.jar:"$REPO"/maven-model-builder-3.6.3.jar:"$REPO"/maven-artifact-3.6.
       │ 3.jar:"$REPO"/commons-lang3-3.11.jar:"$REPO"/maven-builder-support-3.6.3.jar:"$REPO"/org.eclipse.sisu.inject-0.3.4.jar:"$REPO"/maven-repository-metadata-3.6.3.jar:"$REPO"/maven-settings-3.6.3.jar:"$REPO"/maven-settings-builder-3.
       │ 6.3.jar:"$REPO"/maven-resolver-api-1.4.1.jar:"$REPO"/maven-resolver-impl-1.4.1.jar:"$REPO"/maven-resolver-spi-1.4.1.jar:"$REPO"/maven-resolver-util-1.4.1.jar:"$REPO"/maven-resolver-connector-basic-1.4.1.jar:"$REPO"/maven-resolver
       │ -transport-wagon-1.4.1.jar:"$REPO"/plexus-interpolation-1.25.jar:"$REPO"/plexus-utils-3.2.1.jar:"$REPO"/plexus-sec-dispatcher-1.4.jar:"$REPO"/plexus-cipher-1.7.jar:"$REPO"/wagon-provider-api-3.3.4.jar:"$REPO"/wagon-file-3.3.4.jar
       │ :"$REPO"/wagon-http-lightweight-2.12.jar:"$REPO"/wagon-http-shared-2.12.jar:"$REPO"/jsoup-1.12.1.jar:"$REPO"/commons-io-2.5.jar:"$REPO"/shrinkwrap-resolver-api-maven-3.1.4.jar:"$REPO"/shrinkwrap-resolver-api-3.1.4.jar:"$REPO"/dir
       │ ectory-watcher-0.13.0.jar:"$REPO"/jna-5.6.0.jar:"$REPO"/commons-compress-1.20.jar:"$REPO"/jcommander-1.78.jar:"$REPO"/layrry-launcher-1.0.0.Alpha1.jar

Windows

  75   │ set CLASSPATH="%BASEDIR%"\etc;"%REPO%"\layrry-config-yaml-1.0.0.Alpha1.jar;"%REPO%"\layrry-config-1.0.0.Alpha1.jar;"%REPO%"\os-maven-plugin-1.6.2.jar;"%REPO%"\jsr305-3.0.2.jar;"%REPO%"\compiler-0.9.7.jar;"%REPO%"\snakeyaml-1.27.j
       │ ar;"%REPO%"\layrry-config-toml-1.0.0.Alpha1.jar;"%REPO%"\toml-1.2.jar;"%REPO%"\layrry-core-1.0.0.Alpha1.jar;"%REPO%"\slf4j-simple-1.7.29.jar;"%REPO%"\slf4j-api-1.7.30.jar;"%REPO%"\shrinkwrap-resolver-impl-maven-3.1.4.jar;"%REPO%"
       │ \shrinkwrap-resolver-spi-maven-3.1.4.jar;"%REPO%"\shrinkwrap-resolver-spi-3.1.4.jar;"%REPO%"\maven-resolver-provider-3.6.3.jar;"%REPO%"\javax.inject-1.jar;"%REPO%"\maven-model-3.6.3.jar;"%REPO%"\maven-model-builder-3.6.3.jar;"%RE
       │ PO%"\maven-artifact-3.6.3.jar;"%REPO%"\commons-lang3-3.11.jar;"%REPO%"\maven-builder-support-3.6.3.jar;"%REPO%"\org.eclipse.sisu.inject-0.3.4.jar;"%REPO%"\maven-repository-metadata-3.6.3.jar;"%REPO%"\maven-settings-3.6.3.jar;"%RE
       │ PO%"\maven-settings-builder-3.6.3.jar;"%REPO%"\maven-resolver-api-1.4.1.jar;"%REPO%"\maven-resolver-impl-1.4.1.jar;"%REPO%"\maven-resolver-spi-1.4.1.jar;"%REPO%"\maven-resolver-util-1.4.1.jar;"%REPO%"\maven-resolver-connector-bas
       │ ic-1.4.1.jar;"%REPO%"\maven-resolver-transport-wagon-1.4.1.jar;"%REPO%"\plexus-interpolation-1.25.jar;"%REPO%"\plexus-utils-3.2.1.jar;"%REPO%"\plexus-sec-dispatcher-1.4.jar;"%REPO%"\plexus-cipher-1.7.jar;"%REPO%"\wagon-provider-a
       │ pi-3.3.4.jar;"%REPO%"\wagon-file-3.3.4.jar;"%REPO%"\wagon-http-lightweight-2.12.jar;"%REPO%"\wagon-http-shared-2.12.jar;"%REPO%"\jsoup-1.12.1.jar;"%REPO%"\commons-io-2.5.jar;"%REPO%"\shrinkwrap-resolver-api-maven-3.1.4.jar;"%REPO
       │ %"\shrinkwrap-resolver-api-3.1.4.jar;"%REPO%"\directory-watcher-0.13.0.jar;"%REPO%"\jna-5.6.0.jar;"%REPO%"\commons-compress-1.20.jar;"%REPO%"\jcommander-1.78.jar;"%REPO%"\layrry-launcher-1.0.0.Alpha1.jar

We could switch to the * notation supported since JDK 7.

Split layrry-launcher module into embedded and cli

The current layrry-launcher module allows 2 modes of operation:

  • direct API usage for creating layers, suitable for embedding in a custom app launcher.
  • configuration based (yaml, toml) launch with CLI options.

Error resolving FXML file

As a follow up t #67, there seems to be a problem when resolving types consumed in an FXML file. The following code fails

App.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.net.URL;

public class App extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        URL location = getClass().getResource("app.fxml");
        FXMLLoader fxmlLoader = new FXMLLoader(location);
        HBox hbox = fxmlLoader.load();

        Scene scene = new Scene(hbox);
        stage.setScene(scene);
        stage.setTitle("App");
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

module-info.java

module app {
    exports app;
    requires javafx.base;
    requires javafx.graphics;
    requires javafx.controls;
    requires javafx.fxml;
}

layers.toml

[layers.javafx]
    modules = [
        "org.openjfx:javafx-base:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-controls:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-graphics:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-fxml:jar:{{os.detected.jfxname}}:{{javafx_version}}"]
[layers.core]
    modules = [
        "org.kordamp.ikonli:app:{{project_version}}"]
    parents = ["javafx"]
[main]
  module = "app"
  class = "app.App"

versions.properties

project_version = 12.0.1-SNAPSHOT
javafx_version = 11

The error I get is

Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Couldn't run module main class
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:139)
	at org.moditect.layrry.Layrry.launch(Layrry.java:56)
	at org.moditect.layrry.Layrry.run(Layrry.java:50)
	at org.moditect.layrry.launcher.LayrryLauncher.launch(LayrryLauncher.java:53)
	at org.moditect.layrry.launcher.LayrryLauncher.main(LayrryLauncher.java:35)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:136)
	... 4 more
Caused by: java.lang.RuntimeException: Exception in Application start method
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
	at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: javafx.fxml.LoadException: 
file:///Users/aalmiray/.m2/repository/org/kordamp/ikonli/app/12.0.1-SNAPSHOT/app-12.0.1-SNAPSHOT.jar!/app/app.fxml

	at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
	at javafx.fxml/javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2863)
	at javafx.fxml/javafx.fxml.FXMLLoader.processImport(FXMLLoader.java:2707)
	at javafx.fxml/javafx.fxml.FXMLLoader.processProcessingInstruction(FXMLLoader.java:2676)
	at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2542)
	at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
	at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
	at [email protected]/app.App.start(App.java:33)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
	at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
Caused by: java.lang.ClassNotFoundException: javafx.geometry.Insets
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	at javafx.fxml/javafx.fxml.FXMLLoader.loadTypeForPackage(FXMLLoader.java:2931)
	at javafx.fxml/javafx.fxml.FXMLLoader.loadType(FXMLLoader.java:2920)
	at javafx.fxml/javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2861)

The javafx.geometry.Insets class is provided by the javafx.graphics module which is set as a requirement on the app module.

Add logging support

It would be good to turn on verbose output when Layrry is assembling the layers for example.

Consider adding a jbang-catalog

this would allow running layrry in combination with jbang as

jbang layrry@moditect --layer-config config.yml

Requires layrry-cli being published to a remote Maven repository (Maven Central most likely)

Refactor configuration with SPI

Now that YAML and TOML are available as config formats, it may be a good idea to split config model & parsing, by providing a config SPI.

Proposal:

  • move package org.moditect.layrry.internal.descriptor from layrry-launcher into a new module named layrry-config.
  • rename existing LayersConfigParser to LayersConfigLoader and use ServiceLoader to load instances of LayersConfigParser (new interface defined in following item) and consume them.
  • add an interface LayersConfigParser with two methods:
    • boolean supports(Path configFile)
    • LayersConfig parse(InputStream in)
  • move YAML support to a new module layrry-config-yaml and implement the new interface.
  • move TOML support to a new module layrry-config-toml and implement the new interface.
  • add these two new modules as dependencies on layrry-launcher and have the launcher invoke LayersConfigLoader.

Build requires JDK14

The readme at a307c3a states that JDK 11 or greater is required to build Layrry however the vertex-example projects require JDK 14 as a minimum.

Cleanup example and integration settings

These projects have a combination of org.moditect.layrry, com.example.it, com.example.greeter, and com.example.layrry.links in their <groupId>.

It'd be better if the groups were consistent, such as org.moditect.layrry.example.*

README clarification

README says

The Java Module System doesn't define any means of mapping between modules (e.g. com.acme.crm) and JARs providing ... Instead, it's the responsibility of the user to obtain all required JARs of a modularized application and provide them via --module-path.

Not entirely accurate as those jars/jmods can be bundled/distributed into a custom runtime image with jlink.

Rename layrry-launcher FAT JAR

The current name is set to ${project.artifactId}-${project.version}-jar-with-dependencies.jar.
It could be instead something like layrry-cli-${project.version}.jar or layrry-runner-${project.version}.jar.

Java 15 as a baseline

While we still might solely require Jdk 11 for the Layrry runtime, the build currently requires 14. Let's move this 15 now that 14 is EOL.

Support additional property sources during configuration

During a brainstorm for removing the explicit runner found in https://github.com/moditect/layrry-examples/tree/master/modular-tiles I thought it might be a good idea to provide the Layrry launcher with an addition source of properties that can be used for placeholder substitution, something akin to

`java -jar layrry-launcher.jar --layers-config layers.yml --properties layrry.properties`

The launcher would then read the properties file and place all properties into the Mustache scope, allowing placeholder substitutions. This could turn a somewhat static Layers config file into a "self updatable" config source, as [groupId, artifactId, version] could be provided by external properties. No need to provide a similar layers.yml file to update a version (say from groovy-json:3.0.5 to groovy-json:3.0.6) but also may handle artifact relocation.

Example:
Groovy 3 is currently published with groupId = org.codehaus.groovy.
Groovy 4 will change to groupId = org.apache.groovy.

A layers file that requires Groovy could declare a module definition as

"{{groovy.groupId}}:groovy-json:{{groovy.version}}"

Explicit definition of an additional properties source makes sense for locally launched applications but may pose a security risk with remotely launched applications (see #37) where you'd want the property source to come from the same trusted host (and adjacent location perhaps) as the layers config file. An implicit mode could be implemented

  1. Given the path of the config layers file replace the filename extension with .properties. If a match is found, use it.
  2. Given the path of the config layers file, locate a file named layrry.properties. If a match is found, use it.
  3. If there are no matches in 1) and 2) then no implicit property sources will be used.

Of course, explicit declaration such as --properties my-props.properties win over implicit, such that if the explicit file is not found then no implicit search will be performed.

Support for layer tags

Layers are a way to define module sets for applications.
Those module-sets are composed of a hierarchy defined by a layers-configuration file. This model fits most application types with fixed a set of dependency- and various application-modules.

However, some applications would benefit from a more flexible layer composition at startup time or runtime. One example of this might be an application that provides some plugin system, where the plugins could be simple jar modules or complete layers on their own, e.g. if they ship with their dependencies. Such an application might want to support the execution with a different set of enabled plugins or layers, depending on the configuration or an application "profile".

For use-cases like above, it would be helpful to have a tagging mechanism for layers. A tag aware layer composition facility would enable applications to compose layers based on a set of given tags dynamically. Frameworks like spring-boot support profiles, which provide a way to segregate parts of application configuration and make it available only in specific environments. The same could be applied to layers.

Tags could also help to qualify the handling of layers. Layers could be tagged as hot-deployable, read-only, or updatable to advise a layer composition system to handle some layers differently.

Another use-case for tagged layers would be environment-specific tags, e.g. windows or linux to denote that a particular layer is only available on a specific OS.

Are artifact classifiers supported?

Launching a JavaFX application may require platform specific artifacts. OpenJFX ships artifacts with a classifier, whose short notation would be in the form g:a:v:c, such as org.openjfx:javafx-base:11:mac.

Currently configuring a module like that results in

WARNING: Failed downloading org/openjfx/javafx-base/mac/javafx-base-mac.11 from https://repo1.maven.org/maven2/

error: module layrry.launcher reads package x from both y and z

Hi,

I created a simple project you can be found here to get to know and understand Layrry.

I created 4 modules in the repository, two of them have packages of the same name. I'm trying to use the class of the two modules in the third module.

And finally, I'm trying to run the third module's main class in the fourth module with larry.

I'm gett getting the following error when I compile the modules:

./third/src/main/java/module-info.java:5: error: module third reads package com.kodcu from both first and second
module third
^
error: the unnamed module reads package com.kodcu from both first and second
error: module layrry.launcher reads package com.kodcu from both second and first

module descriptor file from first modue

module first
{
    exports com.kodcu;
}

module descriptor file from second modue

module second
{
    exports com.kodcu;
}

module descriptor file from third modue

module third
{
    requires first;
    requires second;
}

module descriptor file from fourth modue

module runner
{
    requires layrry.launcher;
}

The myrunner class

public class MyRunner
{
    public static void main(String[] args){
        Layers layers = Layers.builder()
                .layer("first")
                .withModule("com.kodcu:first:1.0.0")
                .layer("second")
                .withModule("com.kodcu:second:1.0.0")
                .layer("third")
                .withModule("layrry.message:third:1.0.0")
                .layer("runner")
                .withModule("layrry.runner:runner:1.0.0")
                .build();

        layers.run("runner/layrry.runner.Run");
    }
}

compile.sh

#!/usr/bin/env bash

javac -d mods \
--module-path mylib \
--module-source-path "./*/src/main/java/" \
$(find ./*/src/main/java/ -name "*.java")

Initially, I perceived Layrry as a solution for package conflicts from unnamed modules.

Did I get it wrong or did I make the wrong use?

Allow multiple directories in plugin layers

Currently a single directory may be used per plugin layer. Spotted this at the vert.x example

plugins1:
  parents:
    - "platform"
    - "log"
  directory: ../../../target/route-plugins1
plugins2:
  parents:
    - "platform"
    - "log"
  directory: ../../../target/route-plugins2

These two layers could be combined in a single layer.

Of course there are uses cases where you'd want to keep plugin layers separate.

Raise warning in case CL of removed plug-in does not get GC'ed

The idea would be to keep a WeakReference for the class loader of each plug-in. If a plug-in gets removed, and this weak reference does not get enqeued after some time, that's an indicator that there's still strong reference to that class loader and a warning should be logged.

org.moditect.layrry.example.links.test.LayrryLinksTest.canAddPluginToEmptyPluginsDirectory fails with ClassNotFoundException

[INFO] --- maven-surefire-plugin:3.0.0-M4:test (default-test) @ layrry-links-runner ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.moditect.layrry.example.links.test.LayrryLinksTest
[ERROR] Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 2.349 s <<< FAILURE! - in org.moditect.layrry.example.links.test.LayrryLinksTest
[ERROR] org.moditect.layrry.example.links.test.LayrryLinksTest.canAddPluginToEmptyPluginsDirectory Time elapsed: 2.041 s <<< ERROR!
java.lang.IllegalArgumentException: java.lang.reflect.InvocationTargetException
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddPluginToEmptyPluginsDirectory(LayrryLinksTest.java:113)
Caused by: java.lang.reflect.InvocationTargetException
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddPluginToEmptyPluginsDirectory(LayrryLinksTest.java:113)
Caused by: java.lang.NoClassDefFoundError: io/vertx/core/AbstractVerticle
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddPluginToEmptyPluginsDirectory(LayrryLinksTest.java:113)
Caused by: java.lang.ClassNotFoundException: io.vertx.core.AbstractVerticle
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddPluginToEmptyPluginsDirectory(LayrryLinksTest.java:113)

[ERROR] org.moditect.layrry.example.links.test.LayrryLinksTest.canAddAndRemovePlugin Time elapsed: 0.25 s <<< ERROR!
java.lang.IllegalArgumentException: java.lang.reflect.InvocationTargetException
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddAndRemovePlugin(LayrryLinksTest.java:72)
Caused by: java.lang.reflect.InvocationTargetException
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddAndRemovePlugin(LayrryLinksTest.java:72)
Caused by: java.lang.NoClassDefFoundError: io/vertx/core/AbstractVerticle
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddAndRemovePlugin(LayrryLinksTest.java:72)
Caused by: java.lang.ClassNotFoundException: io.vertx.core.AbstractVerticle
at org.moditect.layrry.example.links.test.LayrryLinksTest.canAddAndRemovePlugin(LayrryLinksTest.java:72)

Support for loading only signed jars and custom JVM

Hi

first of all, thanks for the effort in brining improvements in the java module system.
May I ask you if you spot a room for adding some enforcing rules on modules themselves, such as loading codesigned modules only, especially with modern cryptographic algorithms.

It looks like SunEC provider will be removed from future Java versions (starting from 15) and if the code is signed with strong algorithms such as SHA512withECDSA, everything added in a policy file is just not accepted, since the provider itself cannot be instantiated.

Another great addition would be the support for customized JVM created with jlink and jpackage.

Layer with directory cannot be set as parent of another Layer

Given the following configuration

layers:
  javafx:
    modules:
      - "org.openjfx:javafx-base:jar:mac:11.0.2"
      - "org.openjfx:javafx-controls:jar:mac:11.0.2"
      - "org.openjfx:javafx-graphics:jar:mac:11.0.2"
      - "org.openjfx:javafx-web:jar:mac:11.0.2"
      - "org.openjfx:javafx-media:jar:mac:11.0.2"
  core:
    modules:
      - "org.kordamp.tiles:modular-tiles-model:0.0.0"
      - "org.kordamp.tiles:modular-tiles-core:0.0.0"
      - "org.moditect.layrry:layrry-platform:1.0-SNAPSHOT"
      - "eu.hansolo:tilesfx:11.44"
    parents:
      - "javafx"
  plugins:
    parents:
      - "core"
    directory: plugins
  app:
    parents:
      - "plugins"
    modules:
      - "org.kordamp.tiles:modular-tiles-app:0.0.0"
main:
  module: "org.kordamp.tiles.app"
  class: "org.kordamp.tiles.app.Main"

Where the plugin layer is a parent of app.

When the plugins directory is empty and the application starts

Exception in thread "main" java.lang.IllegalArgumentException: Layer 'app': parent layer 'plugins' not configured yet
    at org.moditect.layrry.internal.LayersImpl.getParentLayers(LayersImpl.java:195)
    at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:103)
    at org.moditect.layrry.Layrry.run(Layrry.java:41)
    at org.moditect.layrry.launcher.LayrryLauncher.launch(LayrryLauncher.java:48)
    at org.moditect.layrry.launcher.LayrryLauncher.main(LayrryLauncher.java:34)

When the plugins directory has a layer such as

plugins
└── tile-clock-0.0.0
    └── tile-clock-0.0.0.jar

Exception in thread "main" java.lang.IllegalArgumentException: Layer 'app': parent layer 'tile-clock-0.0.0' not configured yet
    at org.moditect.layrry.internal.LayersImpl.getParentLayers(LayersImpl.java:195)
    at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:103)
    at org.moditect.layrry.Layrry.run(Layrry.java:41)
    at org.moditect.layrry.launcher.LayrryLauncher.launch(LayrryLauncher.java:48)
    at org.moditect.layrry.launcher.LayrryLauncher.main(LayrryLauncher.java:34)

Streamline POM files

Some POM files have empty declarations such as <dependencies></dependencies> or <dependencyManagement></dependencyManagement> which are not really needed.

Invoking LayrryLauncher with no args should print usage and quit

Currently it prints an exception and quits

Exception in thread "main" com.beust.jcommander.ParameterException: The following option is required: [--layers-config]
	at com.beust.jcommander.JCommander.validateOptions(JCommander.java:388)
	at com.beust.jcommander.JCommander.parse(JCommander.java:357)
	at com.beust.jcommander.JCommander.parse(JCommander.java:335)
	at org.moditect.layrry.launcher.LayrryLauncher.launch(LayrryLauncher.java:50)
	at org.moditect.layrry.launcher.LayrryLauncher.main(LayrryLauncher.java:41)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.