From b29d2c89022e676247a3be5638cd43f0210ef3a7 Mon Sep 17 00:00:00 2001 From: Serban Iordache Date: Mon, 6 Jul 2020 02:28:22 +0200 Subject: [PATCH] add method jdkDownload --- build.gradle | 2 +- doc/user_guide.adoc | 43 +++++-- .../jlink/data/JlinkPluginExtension.groovy | 7 +- .../beryx/jlink/data/TargetPlatform.groovy | 50 +++++++- .../impl/CreateMergedModuleTaskImpl.groovy | 13 +- .../jlink/impl/JPackageImageTaskImpl.groovy | 1 - .../org/beryx/jlink/util/JdkUtil.groovy | 117 ++++++++++++++++++ 7 files changed, 208 insertions(+), 25 deletions(-) create mode 100644 src/main/groovy/org/beryx/jlink/util/JdkUtil.groovy diff --git a/build.gradle b/build.gradle index 84e46713..4b063356 100644 --- a/build.gradle +++ b/build.gradle @@ -103,7 +103,7 @@ dependencies { shadowJar { configurations = [project.configurations.plugin] - classifier = null + archiveClassifier = null dependencies { include(dependency("org.ow2.asm:asm:$asmVersion")) include(dependency("org.ow2.asm:asm-commons:$asmVersion")) diff --git a/doc/user_guide.adoc b/doc/user_guide.adoc index ddc58bb7..fbcffb03 100644 --- a/doc/user_guide.adoc +++ b/doc/user_guide.adoc @@ -27,10 +27,8 @@ consisting only of a module descriptor. The module descriptor specifies that the _depends on_: `prepareMergedJarsDir` <>:: Uses the https://jdk.java.net/jpackage/[jpackage] tool to create a platform-specific application image. + _depends on_: `prepareModulesDir` + - _This task is experimental._ <>:: Uses the https://jdk.java.net/jpackage/[jpackage] tool to create a platform-specific application installer. + _depends on_: `jpackageImage` + - _This task is experimental._ A detailed description of these tasks is given in <> @@ -141,7 +139,20 @@ determine the location of the image directory and of the image archive. +      _Methods:_ +          [maroon]##addOptions##(String... [purple]##options##): an alternative way of setting the `options` property. +          [maroon]##addExtraModulePath##(String [purple]##path##): pass the specified path to the `--module-path` option of jlink. + -         This method can be used to specify the location of the platform-specific OpenJFX modules. +             This method can be used to specify the location of the platform-specific OpenJFX modules. + +         [maroon]##jdkDownload##(String [purple]##downloadUrl##, Closure [purple]##downloadConfig##=null): helper method for setting [purple]##jdkHome##. + +             It downloads and unpacks a JDK distribution from the given URL. + +             The optional closure allows configuring the following parameters: + +               - [purple]##downloadDir##: the directory in which the distribution is downloaded and unpacked. + +                   _defaultValue_: `_buildDir_/jdks/_targetPlatform-name_` + +               - [purple]##archiveName##: the name under which the archived distribution should be saved. + +                   _defaultValue_: `jdk` + +               - [purple]##archiveExtension##: accepted values: `tar.gz` and `zip`. + +                   _defaultValue_: `null` (inferred from the URL) + +               - [purple]##pathToHome##: the relative path to the JDK home in the unpacked distribution. + +                   _defaultValue_: `null` (inferred by scanning the unpacked distribution) + +               - [purple]##overwrite##: if `true`, the plugin overwrites an already existing distribution. + +                   _defaultValue_: `false` [cols="1,100", frame=none, grid=none] |=== @@ -151,23 +162,33 @@ a| a| .Usage example jlink { ... targetPlatform("linux-s390x") { - jdkHome = "/usr/lib/jvm/linux-s390x/jdk-11.0.2+9" + jdkHome = "/usr/lib/jvm/linux-s390x/jdk-14.0.1_7" addOptions("--endian", "big") addExtraModulePath("/usr/lib/openjfx/linux-s390x/jmods") } - targetPlatform("mac") { - jdkHome = "/usr/lib/jvm/mac/jdk-11.0.2+9" - addExtraModulePath("/usr/lib/openjfx/mac/jmods") - } + targetPlatform("win") { - jdkHome = "/usr/lib/jvm/win/jdk-11.0.2+9" + jdkHome = jdkDownload("https://github.com/AdoptOpenJDK/openjdk14-binaries/releases/download/jdk-14.0.1%2B7.1/OpenJDK14U-jdk_x64_windows_hotspot_14.0.1_7.zip") addExtraModulePath("/usr/lib/openjfx/win/jmods") } + + targetPlatform("mac") { + jdkHome = jdkDownload("https://github.com/AdoptOpenJDK/openjdk14-binaries/releases/download/jdk-14.0.1%2B7/OpenJDK14U-jdk_x64_mac_hotspot_14.0.1_7.tar.gz") { + downloadDir = "$buildDir/myMac" + archiveName = "my-mac-jdk" + archiveExtension = "tar.gz" + pathToHome = "jdk-14.0.1+7/Contents/Home" + overwrite = true + } + addExtraModulePath("/usr/lib/openjfx/mac/jmods") + } ... } ---- |=== + + [[scriptBlocks]] == Script blocks @@ -416,7 +437,7 @@ jlink { === jpackage -This experimental script block allows you to customize the https://jdk.java.net/jpackage/[jpackage]-based generation of platform-specific application images and installers. +This script block allows you to customize the https://jdk.java.net/jpackage/[jpackage]-based generation of platform-specific application images and installers. jpackageHome:: The path to the JDK providing the jpackage tool. + _defaultValue_: the first non-empty value from: + @@ -482,7 +503,7 @@ targetPlatformName:: This property is required only when using the `targetPlatfo It specifies which of the images produced by jlink should be used as runtime image by jpackage. Its value must match the name provided in one of the calls to the `targetPlatform` method. + _defaultValue_: null + - _usage example_: `targetPlatform = "linux"` + _usage example_: `targetPlatformName = "linux"` _Usage example_ diff --git a/src/main/groovy/org/beryx/jlink/data/JlinkPluginExtension.groovy b/src/main/groovy/org/beryx/jlink/data/JlinkPluginExtension.groovy index ecdbc776..546a7c7d 100644 --- a/src/main/groovy/org/beryx/jlink/data/JlinkPluginExtension.groovy +++ b/src/main/groovy/org/beryx/jlink/data/JlinkPluginExtension.groovy @@ -16,7 +16,6 @@ package org.beryx.jlink.data import groovy.transform.CompileStatic -import groovy.transform.ToString import org.beryx.jlink.util.Util import org.gradle.api.Action import org.gradle.api.Project @@ -30,6 +29,7 @@ import static org.beryx.jlink.util.Util.EXEC_EXTENSION @CompileStatic class JlinkPluginExtension { + private final Project project final Property jlinkBasePath final Property imageName final DirectoryProperty imageDir @@ -57,6 +57,7 @@ class JlinkPluginExtension { final Property jpackageData JlinkPluginExtension(Project project) { + this.project = project jlinkBasePath = project.objects.property(String) jlinkBasePath.set(project.provider{"$project.buildDir/jlinkbase" as String}) @@ -147,11 +148,11 @@ class JlinkPluginExtension { } void targetPlatform(String name, String jdkHome, List options = []) { - Util.putToMapProvider(targetPlatforms, name, new TargetPlatform(name, jdkHome, options)) + Util.putToMapProvider(targetPlatforms, name, new TargetPlatform(project, name, jdkHome, options)) } void targetPlatform(String name, Action action) { - def targetPlatform = new TargetPlatform(name) + def targetPlatform = new TargetPlatform(project, name) action.execute(targetPlatform) Util.putToMapProvider(targetPlatforms, name, targetPlatform) } diff --git a/src/main/groovy/org/beryx/jlink/data/TargetPlatform.groovy b/src/main/groovy/org/beryx/jlink/data/TargetPlatform.groovy index 2729a6b5..462f4988 100644 --- a/src/main/groovy/org/beryx/jlink/data/TargetPlatform.groovy +++ b/src/main/groovy/org/beryx/jlink/data/TargetPlatform.groovy @@ -16,20 +16,36 @@ package org.beryx.jlink.data import groovy.transform.CompileStatic +import org.beryx.jlink.util.JdkUtil +import org.gradle.api.Project +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging @CompileStatic class TargetPlatform implements Serializable { + private static final Logger LOGGER = Logging.getLogger(TargetPlatform.class) + + transient private final Project project final String name - String jdkHome + private Serializable jdkHome List options = [] List extraModulePaths = [] - TargetPlatform(String name, String jdkHome = '', List options = []) { + TargetPlatform(Project project, String name, String jdkHome = '', List options = []) { + this.project = project this.name = name - this.jdkHome = jdkHome + this.@jdkHome = jdkHome this.options.addAll(options) } + String getJdkHome() { + (this.@jdkHome == null) ? null : this.@jdkHome.toString() + } + + void setJdkHome(Serializable jdkHome) { + this.@jdkHome = jdkHome + } + void addOptions(String... opts) { opts.each { String opt -> options.add(opt) } } @@ -37,4 +53,32 @@ class TargetPlatform implements Serializable { void addExtraModulePath(String path) { extraModulePaths << path } + + private static class LazyString implements Serializable { + final Closure closure + LazyString(Closure closure) { + this.closure = closure + } + + @Lazy String string = closure.call() + + @Override + String toString() { + string + } + } + + LazyString jdkDownload(String downloadUrl, Closure downloadConfig = null) { + def options = new JdkUtil.JdkDownloadOptions(project, name, downloadUrl) + if(downloadConfig) { + downloadConfig.delegate = options + downloadConfig(options) + } + return new LazyString({ + def relativePathToHome = JdkUtil.downloadFrom(downloadUrl, options) + def pathToHome = "$options.downloadDir/$relativePathToHome" + LOGGER.info("Home of downloaded JDK distribution: $pathToHome") + return pathToHome as String + }) + } } diff --git a/src/main/groovy/org/beryx/jlink/impl/CreateMergedModuleTaskImpl.groovy b/src/main/groovy/org/beryx/jlink/impl/CreateMergedModuleTaskImpl.groovy index 57ae037a..86a0c31f 100644 --- a/src/main/groovy/org/beryx/jlink/impl/CreateMergedModuleTaskImpl.groovy +++ b/src/main/groovy/org/beryx/jlink/impl/CreateMergedModuleTaskImpl.groovy @@ -24,6 +24,7 @@ import org.beryx.jlink.util.Util import org.beryx.jlink.data.CreateMergedModuleTaskData import org.gradle.api.GradleException import org.gradle.api.Project +import org.gradle.api.file.CopySpec import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging @@ -41,16 +42,16 @@ class CreateMergedModuleTaskImpl extends BaseTaskImpl + spec.from td.mergedJarsDir + spec.into td.tmpModuleInfoDirPath + spec.exclude "**/module-info.class" } Util.createJar(project, td.mergedModuleJar, project.file(td.tmpModuleInfoDirPath)) } @@ -60,7 +61,7 @@ class CreateMergedModuleTaskImpl extends BaseTaskImpl { @CompileDynamic void execute() { - LOGGER.warn("The jpackage task is experimental. Use it at your own risk.") def result = project.exec { ignoreExitValue = true standardOutput = new ByteArrayOutputStream() diff --git a/src/main/groovy/org/beryx/jlink/util/JdkUtil.groovy b/src/main/groovy/org/beryx/jlink/util/JdkUtil.groovy new file mode 100644 index 00000000..f45dc4ee --- /dev/null +++ b/src/main/groovy/org/beryx/jlink/util/JdkUtil.groovy @@ -0,0 +1,117 @@ +/* + * Copyright 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 + * + * http://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 org.beryx.jlink.util + +import groovy.transform.CompileStatic +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.file.CopySpec +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging + +@CompileStatic +class JdkUtil { + private static final Logger LOGGER = Logging.getLogger(JdkUtil.class) + + static class JdkDownloadOptions implements Serializable { + transient final Project project + String downloadDir + String archiveName + String archiveExtension + String pathToHome + boolean overwrite + + JdkDownloadOptions(Project project, String targetPlatform, String downloadUrl) { + this.project = project + this.downloadDir = "$project.buildDir/jdks/$targetPlatform" + this.archiveName = "jdk" + def urlPath = new URL(downloadUrl).path + if(urlPath.endsWith(".tar.gz")) archiveExtension = "tar.gz" + if(urlPath.endsWith(".zip")) archiveExtension = "zip" + } + + void validate() { + if(!project) throw new GradleException("Internal error: project not set in JdkDownloadOptions") + if(!downloadDir) throw new GradleException("Please provide a value for 'downloadDir' when calling the 'jdkDownload' method") + if(!archiveName) throw new GradleException("Please provide a value for 'archiveName' when calling the 'jdkDownload' method") + if(!archiveExtension) throw new GradleException("Cannot infer the archive type. Please provide a value for 'archiveExtension' when calling the 'jdkDownload' method. Accepted values: 'tar.gz', 'zip'") + } + } + + static String downloadFrom(String url, JdkDownloadOptions options) { + options.validate() + def archiveFile = new File("${options.downloadDir}/${options.archiveName}.${options.archiveExtension}") + def archiveDir = archiveFile.parentFile + maybeCreateDir(archiveDir) + if(options.overwrite) { + archiveFile.delete() + if(archiveFile.exists()) throw new GradleException("Cannot delete $archiveFile") + } else { + def pathToHome = options.pathToHome ?: detectPathToHome(archiveDir) + if(pathToHome) { + LOGGER.info("JDK found at $archiveDir/$pathToHome; skipping download from $url") + return pathToHome + } + } + if(!archiveFile.file) { + LOGGER.info("Downloading from $url into $archiveFile") + new URL(url).withInputStream{ is -> archiveFile.withOutputStream{ it << is }} + } + return unpackJdk(archiveDir, archiveFile, options) + } + + private static String unpackJdk(File archiveDir, File archiveFile, JdkDownloadOptions options) { + if(!archiveFile.file) throw new GradleException("Archive file $archiveFile does not exist.") + LOGGER.info("Unpacking $archiveFile") + options.project.copy { CopySpec spec -> + spec.from ((options.archiveExtension == 'tar.gz') + ? options.project.tarTree(options.project.resources.gzip(archiveFile)) + : options.project.zipTree(archiveFile)) + spec.into archiveDir + } + if(options.pathToHome) { + if(!isJdkHome(new File("$archiveDir/$options.pathToHome"))) { + LOGGER.warn("JDK home not found at $archiveDir/$options.pathToHome! Please check the the unpacked archive.") + } + return options.pathToHome + } else { + def pathToHome = detectPathToHome(archiveDir) + if(!pathToHome) throw new GradleException("Cannot detect JDK home in $archiveDir. Check the unpacked archive and/or try setting `pathToHome` when calling the 'jdkDownload' method") + return pathToHome + } + } + + private static String detectPathToHome(File dir) { + def dirs = [dir] + dir.eachDirRecurse { dirs << it } + def homeDir = dirs.find { isJdkHome(it) } + if(!homeDir) return null + def relPath = dir.toPath().relativize(homeDir.toPath()) + return relPath ?: '.' + } + + private static boolean isJdkHome(File dir) { + new File("$dir/bin/java").file || new File("$dir/bin/java.exe").file + } + + private static void maybeCreateDir(File dir) { + if (!dir.directory) { + if (dir.exists()) throw new GradleException("$dir exists but it's not a directory.") + dir.mkdirs() + if (!dir.directory) throw new GradleException("Cannot create directory $dir.") + } + } +}