From f3b0d37c548d11c4ed3b3f4a376c7d76d2ca531c Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 11 Oct 2022 15:07:43 +0200 Subject: [PATCH] replace and customize javafx plugin for headless environments --- build.gradle | 2 +- buildSrc/build.gradle | 9 + .../java/org/openjfx/gradle/JavaFXModule.java | 114 ++++++++++++ .../org/openjfx/gradle/JavaFXOptions.java | 164 ++++++++++++++++++ .../org/openjfx/gradle/JavaFXPlatform.java | 91 ++++++++++ .../java/org/openjfx/gradle/JavaFXPlugin.java | 49 ++++++ .../org/openjfx/gradle/tasks/ExecTask.java | 124 +++++++++++++ 7 files changed, 552 insertions(+), 1 deletion(-) create mode 100644 buildSrc/src/main/java/org/openjfx/gradle/JavaFXModule.java create mode 100644 buildSrc/src/main/java/org/openjfx/gradle/JavaFXOptions.java create mode 100644 buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlatform.java create mode 100644 buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlugin.java create mode 100644 buildSrc/src/main/java/org/openjfx/gradle/tasks/ExecTask.java diff --git a/build.gradle b/build.gradle index 21b1a4b6..d901e952 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'application' - id 'org.openjfx.javafxplugin' version '0.0.13' + id 'org-openjfx-javafxplugin' id 'extra-java-module-info' id 'org.beryx.jlink' version '2.25.0' } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index ebb5470f..64398347 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -4,10 +4,15 @@ plugins { dependencies { implementation 'org.ow2.asm:asm:8.0.1' + implementation 'com.google.gradle:osdetector-gradle-plugin:1.7.0' + implementation 'org.javamodularity:moduleplugin:1.8.12' } repositories { mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } } gradlePlugin { @@ -17,5 +22,9 @@ gradlePlugin { id = "extra-java-module-info" implementationClass = "org.gradle.sample.transform.javamodules.ExtraModuleInfoPlugin" } + register("org-openjfx-javafxplugin") { + id = "org-openjfx-javafxplugin" + implementationClass = "org.openjfx.gradle.JavaFXPlugin" + } } } diff --git a/buildSrc/src/main/java/org/openjfx/gradle/JavaFXModule.java b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXModule.java new file mode 100644 index 00000000..982d844f --- /dev/null +++ b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXModule.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018, 2020, Gluon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjfx.gradle; + +import org.gradle.api.GradleException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum JavaFXModule { + + BASE, + GRAPHICS(BASE), + CONTROLS(BASE, GRAPHICS), + FXML(BASE, GRAPHICS), + MEDIA(BASE, GRAPHICS), + SWING(BASE, GRAPHICS), + WEB(BASE, CONTROLS, GRAPHICS, MEDIA); + + static final String PREFIX_MODULE = "javafx."; + private static final String PREFIX_ARTIFACT = "javafx-"; + + private List dependentModules; + + JavaFXModule(JavaFXModule...dependentModules) { + this.dependentModules = List.of(dependentModules); + } + + public static Optional fromModuleName(String moduleName) { + return Stream.of(JavaFXModule.values()) + .filter(javaFXModule -> moduleName.equals(javaFXModule.getModuleName())) + .findFirst(); + } + + public String getModuleName() { + return PREFIX_MODULE + name().toLowerCase(Locale.ROOT); + } + + public String getModuleJarFileName() { + return getModuleName() + ".jar"; + } + + public String getArtifactName() { + return PREFIX_ARTIFACT + name().toLowerCase(Locale.ROOT); + } + + public boolean compareJarFileName(JavaFXPlatform platform, String jarFileName) { + Pattern p = Pattern.compile(getArtifactName() + "-.+-" + platform.getClassifier() + "\\.jar"); + return p.matcher(jarFileName).matches(); + } + + public static Set getJavaFXModules(List moduleNames) { + validateModules(moduleNames); + + return moduleNames.stream() + .map(JavaFXModule::fromModuleName) + .flatMap(Optional::stream) + .flatMap(javaFXModule -> javaFXModule.getMavenDependencies().stream()) + .collect(Collectors.toSet()); + } + + public static void validateModules(List moduleNames) { + var invalidModules = moduleNames.stream() + .filter(module -> JavaFXModule.fromModuleName(module).isEmpty()) + .collect(Collectors.toList()); + + if (! invalidModules.isEmpty()) { + throw new GradleException("Found one or more invalid JavaFX module names: " + invalidModules); + } + } + + public List getDependentModules() { + return dependentModules; + } + + public List getMavenDependencies() { + List dependencies = new ArrayList<>(dependentModules); + dependencies.add(0, this); + return dependencies; + } +} diff --git a/buildSrc/src/main/java/org/openjfx/gradle/JavaFXOptions.java b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXOptions.java new file mode 100644 index 00000000..70cdf941 --- /dev/null +++ b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXOptions.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2018, Gluon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjfx.gradle; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.openjfx.gradle.JavaFXModule.PREFIX_MODULE; + +public class JavaFXOptions { + + private static final String MAVEN_JAVAFX_ARTIFACT_GROUP_ID = "org.openjfx"; + private static final String JAVAFX_SDK_LIB_FOLDER = "lib"; + + private final Project project; + private final JavaFXPlatform platform; + + private String version = "16"; + private String sdk; + private String configuration = "implementation"; + private String lastUpdatedConfiguration; + private List modules = new ArrayList<>(); + private FlatDirectoryArtifactRepository customSDKArtifactRepository; + + public JavaFXOptions(Project project) { + this.project = project; + this.platform = JavaFXPlatform.detect(project); + } + + public JavaFXPlatform getPlatform() { + return platform; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + updateJavaFXDependencies(); + } + + /** + * If set, the JavaFX modules will be taken from this local + * repository, and not from Maven Central + * @param sdk, the path to the local JavaFX SDK folder + */ + public void setSdk(String sdk) { + this.sdk = sdk; + updateJavaFXDependencies(); + } + + public String getSdk() { + return sdk; + } + + /** Set the configuration name for dependencies, e.g. + * 'implementation', 'compileOnly' etc. + * @param configuration The configuration name for dependencies + */ + public void setConfiguration(String configuration) { + this.configuration = configuration; + updateJavaFXDependencies(); + } + + public String getConfiguration() { + return configuration; + } + + public List getModules() { + return modules; + } + + public void setModules(List modules) { + this.modules = modules; + updateJavaFXDependencies(); + } + + public void modules(String...moduleNames) { + setModules(List.of(moduleNames)); + } + + private void updateJavaFXDependencies() { + clearJavaFXDependencies(); + + String configuration = getConfiguration(); + JavaFXModule.getJavaFXModules(this.modules).stream() + .sorted() + .forEach(javaFXModule -> { + if (customSDKArtifactRepository != null) { + project.getDependencies().add(configuration, Map.of("name", javaFXModule.getModuleName())); + } else { + project.getDependencies().add(configuration, + String.format("%s:%s:%s:%s", MAVEN_JAVAFX_ARTIFACT_GROUP_ID, javaFXModule.getArtifactName(), + getVersion(), getPlatform().getClassifier())); + } + }); + lastUpdatedConfiguration = configuration; + } + + private void clearJavaFXDependencies() { + if (customSDKArtifactRepository != null) { + project.getRepositories().remove(customSDKArtifactRepository); + customSDKArtifactRepository = null; + } + + if (sdk != null && ! sdk.isEmpty()) { + Map dirs = new HashMap<>(); + dirs.put("name", "customSDKArtifactRepository"); + if (sdk.endsWith(File.separator)) { + dirs.put("dirs", sdk + JAVAFX_SDK_LIB_FOLDER); + } else { + dirs.put("dirs", sdk + File.separator + JAVAFX_SDK_LIB_FOLDER); + } + customSDKArtifactRepository = project.getRepositories().flatDir(dirs); + } + + if (lastUpdatedConfiguration == null) { + return; + } + var configuration = project.getConfigurations().findByName(lastUpdatedConfiguration); + if (configuration != null) { + if (customSDKArtifactRepository != null) { + configuration.getDependencies() + .removeIf(dependency -> dependency.getName().startsWith(PREFIX_MODULE)); + } + configuration.getDependencies() + .removeIf(dependency -> MAVEN_JAVAFX_ARTIFACT_GROUP_ID.equals(dependency.getGroup())); + } + } +} diff --git a/buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlatform.java b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlatform.java new file mode 100644 index 00000000..00d9dae7 --- /dev/null +++ b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlatform.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018, Gluon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjfx.gradle; + +import com.google.gradle.osdetector.OsDetector; +import org.gradle.api.GradleException; +import org.gradle.api.Project; + +import java.awt.*; +import java.util.Arrays; +import java.util.stream.Collectors; + +public enum JavaFXPlatform { + + LINUX("linux", "linux-x86_64"), + LINUX_MONOCLE("linux-monocle", "linux-x86_64-monocle"), + LINUX_AARCH64("linux-aarch64", "linux-aarch_64"), + LINUX_AARCH64_MONOCLE("linux-aarch64-monocle", "linux-aarch_64-monocle"), + WINDOWS("win", "windows-x86_64"), + WINDOWS_MONOCLE("win-monocle", "windows-x86_64-monocle"), + OSX("mac", "osx-x86_64"), + OSX_MONOCLE("mac-monocle", "osx-x86_64-monocle"), + OSX_AARCH64("mac-aarch64", "osx-aarch_64"), + OSX_AARCH64_MONOCLE("mac-aarch64-monocle", "osx-aarch_64-monocle"); + + private final String classifier; + private final String osDetectorClassifier; + + JavaFXPlatform( String classifier, String osDetectorClassifier ) { + this.classifier = classifier; + this.osDetectorClassifier = osDetectorClassifier; + } + + public String getClassifier() { + return classifier; + } + + public static JavaFXPlatform detect(Project project) { + + String osClassifier = project.getExtensions().getByType(OsDetector.class).getClassifier(); + + if(GraphicsEnvironment.isHeadless()) { + osClassifier += "-monocle"; + } + + for ( JavaFXPlatform platform: values()) { + if ( platform.osDetectorClassifier.equals(osClassifier)) { + return platform; + } + } + + String supportedPlatforms = Arrays.stream(values()) + .map(p->p.osDetectorClassifier) + .collect(Collectors.joining("', '", "'", "'")); + + throw new GradleException( + String.format( + "Unsupported JavaFX platform found: '%s'! " + + "This plugin is designed to work on supported platforms only." + + "Current supported platforms are %s.", osClassifier, supportedPlatforms ) + ); + + } +} diff --git a/buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlugin.java b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlugin.java new file mode 100644 index 00000000..2b5e59dd --- /dev/null +++ b/buildSrc/src/main/java/org/openjfx/gradle/JavaFXPlugin.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, Gluon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjfx.gradle; + +import com.google.gradle.osdetector.OsDetectorPlugin; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.javamodularity.moduleplugin.ModuleSystemPlugin; +import org.openjfx.gradle.tasks.ExecTask; + +public class JavaFXPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().apply(OsDetectorPlugin.class); + project.getPlugins().apply(ModuleSystemPlugin.class); + + project.getExtensions().create("javafx", JavaFXOptions.class, project); + + project.getTasks().create("configJavafxRun", ExecTask.class, project); + } +} diff --git a/buildSrc/src/main/java/org/openjfx/gradle/tasks/ExecTask.java b/buildSrc/src/main/java/org/openjfx/gradle/tasks/ExecTask.java new file mode 100644 index 00000000..6b31f844 --- /dev/null +++ b/buildSrc/src/main/java/org/openjfx/gradle/tasks/ExecTask.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019, 2021, Gluon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjfx.gradle.tasks; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.plugins.ApplicationPlugin; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.TaskAction; +import org.javamodularity.moduleplugin.extensions.RunModuleOptions; +import org.openjfx.gradle.JavaFXModule; +import org.openjfx.gradle.JavaFXOptions; +import org.openjfx.gradle.JavaFXPlatform; + +import javax.inject.Inject; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TreeSet; + +public class ExecTask extends DefaultTask { + + private static final Logger LOGGER = Logging.getLogger(ExecTask.class); + + private final Project project; + private JavaExec execTask; + + @Inject + public ExecTask(Project project) { + this.project = project; + project.getPluginManager().withPlugin(ApplicationPlugin.APPLICATION_PLUGIN_NAME, e -> { + execTask = (JavaExec) project.getTasks().findByName(ApplicationPlugin.TASK_RUN_NAME); + if (execTask != null) { + execTask.dependsOn(this); + } else { + throw new GradleException("Run task not found."); + } + }); + } + + @TaskAction + public void action() { + if (execTask != null) { + JavaFXOptions javaFXOptions = project.getExtensions().getByType(JavaFXOptions.class); + JavaFXModule.validateModules(javaFXOptions.getModules()); + + var definedJavaFXModuleNames = new TreeSet<>(javaFXOptions.getModules()); + if (!definedJavaFXModuleNames.isEmpty()) { + RunModuleOptions moduleOptions = execTask.getExtensions().findByType(RunModuleOptions.class); + + final FileCollection classpathWithoutJavaFXJars = execTask.getClasspath().filter( + jar -> Arrays.stream(JavaFXModule.values()).noneMatch(javaFXModule -> jar.getName().contains(javaFXModule.getArtifactName())) + ); + final FileCollection javaFXPlatformJars = execTask.getClasspath().filter(jar -> isJavaFXJar(jar, javaFXOptions.getPlatform())); + + if (moduleOptions != null) { + LOGGER.info("Modular JavaFX application found"); + // Remove empty JavaFX jars from classpath + execTask.setClasspath(classpathWithoutJavaFXJars.plus(javaFXPlatformJars)); + definedJavaFXModuleNames.forEach(javaFXModule -> moduleOptions.getAddModules().add(javaFXModule)); + } else { + LOGGER.info("Non-modular JavaFX application found"); + // Remove all JavaFX jars from classpath + execTask.setClasspath(classpathWithoutJavaFXJars); + + var javaFXModuleJvmArgs = List.of("--module-path", javaFXPlatformJars.getAsPath()); + + var jvmArgs = new ArrayList(); + jvmArgs.add("--add-modules"); + jvmArgs.add(String.join(",", definedJavaFXModuleNames)); + + List execJvmArgs = execTask.getJvmArgs(); + if (execJvmArgs != null) { + jvmArgs.addAll(execJvmArgs); + } + jvmArgs.addAll(javaFXModuleJvmArgs); + + execTask.setJvmArgs(jvmArgs); + } + } + } else { + throw new GradleException("Run task not found. Please, make sure the Application plugin is applied"); + } + } + + private static boolean isJavaFXJar(File jar, JavaFXPlatform platform) { + return jar.isFile() && + Arrays.stream(JavaFXModule.values()).anyMatch(javaFXModule -> + javaFXModule.compareJarFileName(platform, jar.getName()) || + javaFXModule.getModuleJarFileName().equals(jar.getName())); + } +}