I have successfully ported my desktop application from Dagger/Spring to Micronaut/Spring (there's just way too much Spring code in there for me to be able strip it all out in a timely manner). Using Micronaut and PicoCLI everything is working beautifully in Eclipse/Gradle. It's launching, tests are passing, so now I want to do a build and install it to make sure that everything will work for our customer.
Our build (via Gradle) uses Shadow (com.github.johnrengelman.shadow) to combine our application with all of its dependencies into a single Jar file. I've checked and all expected dependencies appear to be present, and from the look of it the generated Micronaut classes are also present in the shadow jar. We then use Launch4J to make this shadow jar executable. From everything that I can tell, the output from Launch4J looks to be correct, however when I try to run the installed application, the splash screen appears for a fraction of a second and that's it. When I run the Launch4J generated executable as a jar file, I get the following:
Error: An unexpected error occurred while trying to open file My App.exe
We're running with OpenJDK11 (11.0.2) and generating a JRE as part of the gradle build
runtime {
modules = [
'java.base',
'java.compiler',
'java.datatransfer',
'java.desktop',
'java.instrument',
'java.logging',
'java.management',
'java.naming',
'java.prefs',
'java.rmi',
'java.scripting',
'java.security.sasl',
'java.sql',
'java.transaction.xa',
'java.xml',
'jdk.compiler',
'jdk.httpserver',
'jdk.javadoc',
'jdk.naming.rmi',
'jdk.unsupported'
]
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}
The Launch4J debug information I've been able to collect all looks correct (other than an Exit Code of 1), and all of my attempts at digging into this have proven to be fruitless thus far. I gather that the most common causes of this type of error are invalid MANIFEST.MD in the jar (I've checked and it's identical to the original pre-Micronaut manifest), or issues with the classpath (the shadow jar should contain all of the required dependencies, and no other classpath references are used as far as I can tell). The relevant portions of the build.gradle are as follows:
task createExe(type: Launch4jLibraryTask) {
jarTask = shadowJar
icon = "${projectDir}/src/main/installer/nsis/Icon.ico"
outfile = "${appName}.exe"
copyConfigurable = files {}
stayAlive = 'true'
bundledJrePath = 'jre'
bundledJre64Bit = true
jvmOptions = [
'-Xmx1024m',
'-Dsun.java2d.d3d=false'
]
fileDescription = appName + ' Application'
productName = appName
internalName = appName
copyright = 'My Company'
companyName = 'My Company'
}
jar {
manifest {
attributes(
'Main-Class': mainClassName,
'SplashScreen-Image': 'images/Z006BASSPS.jpg'
)
}
}
shadowJar {
dependsOn replaceTokens
zip64 true
mergeServiceFiles() {
exclude 'META-INF/services/org.xmlpull.v1.XmlPullParserFactory'
}
append 'META-INF/spring.factories'
append 'META-INF/spring.handlers'
append 'META-INF/spring.schemas'
append 'META-INF/spring.tooling'
}
None of which has been changed since pre-Micronaut when everything was working.
I've tried launching shadow jar produced by the build with the JRE it produces, and that looks like it's working (or at least, it's being executed and not dying instantly). So I'm fairly confident that the issue is on the Launch4J portion of the build.
Is Micronaut compatible with Launch4j? Is there something that is missing, or something else that can be tried to see what could be going on? I'm genuinely stumped, everything looks like it should work, but it's not...
Update:
After doing a bunch of digging, I've come to realize that the issue is with Launch4j. The shadow jar produced by this build is working perfectly. And yet running the working Jar through Launch4j results in an executable that only produces
Error: An unexpected error occurred while trying to open file launch4j\MyApp.exe
I created a dummy Micronaut application and producing a build of it with Launch4j also seems to work exactly as expected, so I don't think that it's a Launch4j - Micronaut incompatibility per-say.
Is there any way to get something more from the executable? Something that is actually actionable? I've tried changing the headerType from gui to console (per other Launch4j questions I've seen on here), but that had no impact. I've tried running with --l4j-debug(-all), and compared it with the output from before the upgrade, and there is no meaningful change in the produced log file.
Update:
I believe that I've narrowed down what the problem is: launch4j doesn't seem to work if there are too many files in the jar. I presume that the limit is 65535, as that's the limit for when Shadow requires the zip64 flag. In my tiny dummy application I added 70,000 text files into the resources directory and with those files in place it would no longer run (as per what was described above). Once I dropped the number of text files to 50,000 everything started running again. I don't know for sure whether the limit is 65535 per say (but that is my expectation), however everything seems to point to it.
I haven't been able to find any information for using launch4j with more than 65535 files (i.e.: something like the shadowJar zip64 flag), nor really much of any information in this regard in general for that matter. However one solution that works for me, is to set dontWrapJar
to true
. This creates a tiny launcher for running the created jar file with the bundled JRE, rather than converting the entire jar, keeping all (or at least the majority of) the files out of the launch4j generated executable. The updated gradle task is as follows
task createExe(type: Launch4jLibraryTask) {
jarTask = shadowJar
dontWrapJar true // <<<< THIS IS THE ADDED LINE
icon = "${projectDir}/src/main/installer/nsis/Icon.ico"
outfile = "${appName}.exe"
copyConfigurable = files {}
stayAlive = 'true'
bundledJrePath = 'jre'
bundledJre64Bit = true
jvmOptions = [
'-Xmx1024m',
'-Dsun.java2d.d3d=false'
]
fileDescription = appName + ' Application'
productName = appName
internalName = appName
copyright = 'My Company'
companyName = 'My Company'
}
Other than that, the only other change was updating the packaging process to incorporate the generated shadow jar in a lib
directory in the produced installer.