badass-jlink-plugin
badass-jlink-plugin copied to clipboard
DLLs in root folder
Hello,
When building a project with jpackage on windows, the main directory where the executable is created also contains a long list of libraries like api-ms-win*.dll, applauncher.dll
and other.
Is it possible to put all these libraries in a separate subfolder named lib
or something like that and keep the root folder clean?
There is no jpackage option for doing this, so you need to configure Gradle to take care of moving the libraries:
tasks.jpackageImage.doLast {
def appName = jpackageData.imageName
def dir = "$jpackageData.imageOutputDir/$appName"
file("$dir/lib").mkdirs()
ant.move(todir: "$dir/lib") {
fileset(dir: dir) {
include name: "*.dll"
}
}
}
However, this is problematic. Your application will no longer start, because the required libraries are not found in the directories specified by the PATH environment variable. My proposed workaround is to write a custom launcher that adds the lib directory to the PATH and then starts the original launcher.
In your Gradle script, you need to rename the original launcher, copy the custom launcher in its place, and also duplicate the .ico and .cfg files.
You can see a complete example here, along with the resulting MSI installer.
I have integrated the new gradle task into my build.gradle file and now it looks like this:
plugins {
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
// id "org.javamodularity.moduleplugin" version "1.6.0"
id 'org.openjfx.javafxplugin' version '0.0.8'
id "org.beryx.jlink" version "2.17.7"
id "com.jfrog.bintray" version "1.7"
}
group 'design.alecsa'
version '1.1.0'
sourceCompatibility = '14'
targetCompatibility = '14'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
maven {
url "https://dl.bintray.com/jerady/maven"
}
}
defaultTasks 'clean', 'jlinkZip'
configurations {
springFactoriesHolder { transitive = false }
}
javafx {
version = "14"
modules = ['javafx.controls', 'javafx.fxml', 'javafx.swing']
}
dependencies {
springFactoriesHolder 'org.springframework.boot:spring-boot-actuator-autoconfigure'
springFactoriesHolder 'org.springframework.boot:spring-boot-autoconfigure'
springFactoriesHolder 'org.springframework.boot:spring-boot'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'de.jensd:fontawesomefx-commons:11.0', {
exclude group: 'org.openjfx'
}
implementation 'de.jensd:fontawesomefx-controls:11.0', {
exclude group: 'org.openjfx'
}
implementation 'de.jensd:fontawesomefx-fontawesome:4.7.0-11', {
exclude group: 'org.openjfx'
}
// implementation 'de.jensd:fontawesomefx-emojione:2.2.7-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'de.jensd:fontawesomefx-icons525:3.0.0-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'de.jensd:fontawesomefx-materialicons:2.2.0-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'de.jensd:fontawesomefx-materialstackicons:2.1-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'de.jensd:fontawesomefx-octicons:4.3.0-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'de.jensd:fontawesomefx-weathericons:2.0.10-11', {
// exclude group: 'org.openjfx'
// }
// implementation 'org.springframework.boot:spring-boot-starter-actuator'
// implementation 'org.springframework.boot:spring-boot-starter-cache'
// implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'javax.cache:cache-api'
implementation 'org.ehcache:ehcache'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
compile group: 'org.yaml', name: 'snakeyaml', version: '1.26'
// https://mvnrepository.com/artifact/commons-io/commons-io
compile group: 'commons-io', name: 'commons-io', version: '2.6'
// https://mvnrepository.com/artifact/org.ehcache/ehcache
compile group: 'org.ehcache', name: 'ehcache', version: '3.8.1'
// https://mvnrepository.com/artifact/javax.cache/cache-api
compile group: 'javax.cache', name: 'cache-api', version: '1.1.1'
}
jar {
enabled = true
}
prepareMergedJarsDir.doLast {
// extract and merge META-INF/spring.factories from springFactoriesHolder
def factories = configurations.springFactoriesHolder.files.collect {
def props = new Properties()
props.load(zipTree(it).matching { include 'META-INF/spring.factories' }.singleFile.newInputStream())
props
}
def mergedProps = new Properties()
factories.each { props ->
props.each { key, value ->
def oldVal = mergedProps[key]
mergedProps[key] = oldVal ? "$oldVal,$value" : value
}
}
def content = mergedProps.collect { key, value ->
def v = (value as String).replace(',', ',\\\n')
"$key=$v"
}.join('\n\n')
mkdir("$jlinkBasePath/META-INF")
new File("$jlinkBasePath/META-INF/spring.factories").text = content
// insert META-INF/spring.factories into the main jar
ant.zip(update: "true", destfile: jar.archivePath, keepcompression: true) {
fileset(dir: "$jlinkBasePath", includes: 'META-INF/**')
}
}
jlink {
imageZip = file("$buildDir/image-zip/krop-image.zip")
forceMerge 'log4j-api', 'javafx.base'
mergedModule {
additive = true
uses 'ch.qos.logback.classic.spi.Configurator'
uses 'org.ehcache.core.spi.service.ServiceFactory'
uses 'org.ehcache.xml.CacheManagerServiceConfigurationParser'
uses 'org.ehcache.xml.CacheServiceConfigurationParser'
excludeProvides implementation: 'com.sun.xml.bind.v2.ContextFactory'
excludeRequires 'com.fasterxml.jackson.module.paramnames'
excludeProvides implementation: 'com.sun.xml.bind.v2.ContextFactory'
excludeProvides servicePattern: 'javax.enterprise.inject.*'
excludeProvides service: 'org.apache.logging.log4j.spi.Provider'
}
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
launcher {
name = 'krop'
customImage {
appModules = ['design.alecsa.merged.module']
}
jvmArgs = [
'--add-reads', 'design.alecsa.merged.module=design.alecsa.krop',
"--add-opens", "javafx.graphics/javafx.css=de.jensd.fx.fontawesomefx.commons",
'-cp', 'config/'
]
}
jpackage {
imageName = 'Krop'
skipInstaller = true
installerName = 'Krop'
installerType = 'exe'
if (org.gradle.internal.os.OperatingSystem.current().windows) {
installerOptions += ['--win-per-user-install', '--win-dir-chooser', '--win-menu']
}
}
forceMerge('log4j-api')
}
tasks.jlink.doLast {
// Spring performs its magic by scanning the classpath, but in a modular application the classpath is replaced by the module-path.
// To circumvent this problem, we copy all resources into the 'config' directory and set this directory as classpath.
copy {
from "src/main/resources"
into "$imageDir.asFile/bin/config"
}
copy {
from 'resources/css'
into "$imageDir.asFile/bin/config/static/resources/css"
}
}
tasks.jpackageImage.doLast {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
def appName = jpackageData.imageName
def dir = "$jpackageData.imageOutputDir/$appName"
file("$dir/lib").mkdirs()
ant.move(todir: "$dir/lib") {
fileset(dir: dir) {
include name: "*.dll"
}
}
file("$dir/${appName}.exe").renameTo "$dir/__app.exe"
copy {
from dir
into dir
include "${appName}.ico"
rename("${appName}.ico", "__app.ico")
}
copy {
from "$dir/app"
into "$dir/app"
include "${appName}.cfg"
rename("${appName}.cfg", "__app.cfg")
}
copy {
from "src/main/resources"
into dir
include "launcher.exe"
rename("launcher.exe", "${appName}.exe")
}
}
}
mainClassName = 'design.alecsa.krop.JavaFxSpringApp'
Unfortunately when I run the launcher executable, I get an error window with the title __app.exe
that says: failed to launch JVM
.
If I manually add the lib
folder to the system path setx path "%path%;lib"
I can start the application with the __app.exe
executable in the root folder. The launcher doesn't seem to work though. I even tried rebuilding the launcher.exe
file from the launcher.go
.
Is there anything specific to my gradle file that might be causing the launcher to error out? I'm thinking that maybe the jvmArgs
could interfere, but since __app.exe
works if it has the correct path, then launcher.exe should also work..
I don't see anything wrong in your build.gradle. If you can upload to GitHub an example project that allows reproducing this issue, I will investigate further.