SystemTray icon indicating copy to clipboard operation
SystemTray copied to clipboard

Question about dependencies needed for v4.0+

Open brett-smith opened this issue 3 years ago • 9 comments

Not a bug as such, more a question ...

There seems to be quite a few more dependencies that get pulled in with the new version of SystemTray, including Kotlin, an SSH client and more. Are you able to describe what is needed and why and what can be safely exclude?

I had a few problems with the Kotlin library with a modular Java 11 application, which i solved by putting kotlin-stdlib and kotlin-stdlib-common on the classpath and everything else on the module path. SystemTray seems to include kotlin-stdlib-jdk8which may have been part of the issue too.

Also logback-classic is included as an SLF4J implementation, meaning this needed to be excluded to prevent it conflicting with my own choice of logging impl.

It appears possible to exclude nearly all of these except Kotlin without affecting tray functionality on Linux Mint at least, but I don't know what affect this will have on other platforms.

These are the exclusions i'm using.

<dependency>
			<groupId>com.dorkbox</groupId>
			<artifactId>SystemTray</artifactId>
			<version>4.1</version>
			<exclusions>
				<exclusion>
					<groupId>ch.qos.logback</groupId>
					<artifactId>logback-classic</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-api</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.jetbrains.kotlin</groupId>
					<artifactId>kotlin-stdlib-jdk8</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.jetbrains.kotlinx</groupId>
					<artifactId>kotlinx-coroutines-core-jvm</artifactId>
				</exclusion>
				<exclusion>
					<groupId>io.github.microutils</groupId>
					<artifactId>kotlin-logging-jvm</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.hierynomus</groupId>
					<artifactId>sshj</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.jetbrains.kotlin</groupId>
			<artifactId>kotlin-stdlib</artifactId>
			<version>1.4.32</version>
		</dependency>

Thanks for all the hard work on v4 by the way, and keeping this incredibly useful library up to date!

brett-smith avatar Apr 11 '21 00:04 brett-smith

Short version: If you exclude anything, it will not work properly. Maybe it works for your specific OS/DE combination, but it definitely will crash on a different OS/DE combination. Specifically, executor is used extensively for detecting OS parameters and workarounds.

Long version: The SystemTray project depends on an Executor project (this is how many different OS specific settings are queried, especially on Linux), and this Executor project is written in Kotlin. This executor project also has the ability to remotely execute SSH commands, which is why you are seeing SSHJ. As an exception you could exclude the SSHJ dependency, since SSH isn't used. (Although I wouldn't recommend excluding anything in a build, but that's just me)

Notes: Regarding logback-classic, it is only supposed to be a dependency for test! Thank you for the report about it (and others), I will fix that asap. There were a lot of changes to the gradle build file for the 4.0 release and gradle is ..... very strange at times with how things work. Also, please don't exclude slf4j-api, as that is used for logging! There are pluggable bridges available for slf4j -> JDK and Log4J, which should be used if you have a different logging framework.

Additionally, you shouldn't have any issues with a JPMS build, as this was tested and runs without any issues "on my machine", so I'm interested what those errors/issues are. Kotlin 1.4+ also runs fine without any issues on JPMS, without resorting to classpath workarounds.

dorkbox avatar Apr 11 '21 09:04 dorkbox

Thanks for the explanation. This particular application is the first time I've tried to make a fully modular Java application, so I have (had to) been very careful with dependencies. SystemTray was one of the last pieces of this puzzle, so I'm thrilled to see JPMS support in this version. This journey with JPMS also made me appreciate libraries that have as few dependencies as possible ;)

I am also trying to keep the size of my application, both disk space and memory usage, as low as I can. Java desktop apps can be pretty hungry compared to comparable native apps (embarrassingly so), so wherever there is a dependency that can be squashed, I think it's worth it. It's quite likely to stay a Linux only app for the foreseeable future as well, so I only really care it works across Linux variants, not if it works on Mac, Windows etc.

I can live with Kotlin being there, its obviously now absolutely required, I am not so sure about an SSH client that will never be used if it can possibly be avoided.

This is the error I get if at least kotlin-stdlib and kotlin-stdlib-common are not on the classpath

Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: kotlin.jvm.internal.Intrinsics
	at Updates/dorkbox.updates.Updates.<clinit>(Updates.kt:29)
	at SystemTray/dorkbox.systemTray.SystemTray.<clinit>(SystemTray.java:152)
	at uk.co.bithatch.snake.ui/uk.co.bithatch.snake.ui.tray.Tray.adjustTray(Tray.java:308)
	at uk.co.bithatch.snake.ui/uk.co.bithatch.snake.ui.tray.Tray.lambda$new$0(Tray.java:99)
	at uk.co.bithatch.snake.ui/uk.co.bithatch.snake.ui.tray.Tray$$Lambda$344/0x000000002cafe038.run(Unknown Source)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:708)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:792)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:1108)
	... 18 more

I have tried various combinations, the only one that works is :-.

...
-classpath
classpath/jffi.jar:classpath/asm.jar:classpath/jnr-a64asm.jar:classpath/jnr-x86asm.jar:classpath/kotlinx-coroutines-core-jvm.jar:classpath/javassist.jar:classpath/kotlin-stdlib.jar:classpath/kotlin-stdlib-common.jar:classpath/annotations.jar
-p
modulepath/snake-ui.jar:modulepath/javafx-controls.jar:modulepath/javafx-controls-linux.jar:modulepath/javafx-graphics.jar:modulepath/javafx-graphics-linux.jar:modulepath/javafx-base.jar:modulepath/javafx-base-linux.jar:modulepath/javafx-web.jar:modulepath/javafx-web-linux.jar:modulepath/javafx-media.jar:modulepath/javafx-media-linux.jar:modulepath/javafx-fxml.jar:modulepath/javafx-fxml-linux.jar:modulepath/snake-lib.jar:modulepath/commons-lang3.jar:modulepath/linuxio4j.jar:modulepath/gson.jar:modulepath/snake-widgets.jar:modulepath/icon-generator-javafx.jar:modulepath/icon-generator-common.jar:modulepath/macrolib.jar:modulepath/dbus-java.jar:modulepath/jnr-unixsocket.jar:modulepath/jnr-ffi.jar:modulepath/jffi-native.jar:modulepath/asm-commons.jar:modulepath/asm-analysis.jar:modulepath/asm-tree.jar:modulepath/asm-util.jar:modulepath/jnr-constants.jar:modulepath/jnr-enxio.jar:modulepath/jnr-posix.jar:modulepath/jna.jar:modulepath/jna-platform.jar:modulepath/jfreedesktop-core.jar:modulepath/FX-BorderlessScene.jar:modulepath/SystemTray.jar:modulepath/Executor.jar:modulepath/SwtJavaFx.jar:modulepath/Utilities.jar:modulepath/xz.jar:modulepath/Updates.jar:modulepath/PropertyLoader.jar:modulepath/slf4j-api.jar:modulepath/forker-wrapped.jar:modulepath/two-slices.jar:modulepath/commonmark.jar:modulepath/groovy.jar:modulepath/controlsfx.jar:modulepath/jimpulse.jar:modulepath/jdraw.jar:modulepath/jfreedesktop-javafx.jar:modulepath/javafx-swing.jar:modulepath/javafx-swing-linux.jar:modulepath/svgSalamander.jar:modulepath/ikonli-javafx.jar:modulepath/ikonli-core.jar:modulepath/ikonli-swing.jar:modulepath/ikonli-fontawesome-pack.jar:modulepath/forker-updater.jar:modulepath/forker-client.jar:modulepath/forker-common.jar:modulepath/picocli.jar:modulepath/snake-app-linux.jar:modulepath/snake-backend-openrazer.jar:modulepath/snake-ui-linux.jar:modulepath/snake-theme-awhitelight.jar:modulepath/snake-theme-agreenfuture.jar:modulepath/snake-theme-anorangetwist.jar:modulepath/snake-theme-ayellowdwarf.jar:modulepath/snake-theme-apinkburst.jar:modulepath/snake-theme-apurplehaze.jar:modulepath/snake-theme-aredsky.jar:modulepath/snake-theme-abluelife.jar:modulepath/slf4j-simple.jar
...

Maybe this is some interaction with one of the other libraries or configuration I'm using but just can't find what!

Regarding SLF4J, yeah I am aware of that, and I didn't really need to exclude it, but I am loading elsewhere via another dependency (DBus Java), so it didn't matter.

I do want to ask about another one though. com.dorkbox:Updates. I missed this one to begin with, and only noticed when I found a new thread called update-system that I didn't recognise and dug deeper to find out what it was.

Discovering this was some kind of telemetry, I have reviewed the code and it appears to be mostly harmless, but without a way to easily turn it off. I would like to ask though if it would be possible to turn this off. My own application is updatable, although I allow this to be turned off for people who don't like apps 'phoning home' (not that I collect these stats). It's also pretty unusual for an open source library. I'd expect at least the possibility to have a checkbox "Yes I want to allow anonymous statistics to help the developers of this application" or something.

Excluding com.dorkbox:Updates from the project results in an ugly exception in the console ..

Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: dorkbox.updates.Updates
	at SystemTray/dorkbox.systemTray.SystemTray.<clinit>(SystemTray.java:152)
	at uk.co.bithatch.snake.ui/uk.co.bithatch.snake.ui.tray.Tray.adjustTray(Tray.java:308)
	at uk.co.bithatch.snake.ui/uk.co.bithatch.snake.ui.tray.Tray.lambda$new$0(Tray.java:99)

.. but the application continues to run fine. So perhaps this error could be hidden or toned down a bit as one possible measure.

brett-smith avatar Apr 11 '21 13:04 brett-smith

That's a lot all at once, but to answer your immediate concern, Updates.ENABLE = false will turn it off.

If you look at the source, you'll see more specific details - most notably that only the class + UUID + version are used, primarily for my own applications, so I can be aware of code differences. Also of note, it follows the GDPR, and absolutely nothing, aside from that information is used. It's telemetry, but not exactly as there is no IP address/etc usage.

Regarding the addition of the SSH client... (SSHJ), i think it makes sense to have that as an optional dependency, so release 4.2 will do that. (This way bouncycastle is also not included as a transitive dependency)

Regarding kotlin and JPMS.... This is what I used when testing JPMS. This is normally a single line, but I will break it out to make it easier to read. Ignore version info, as this was from last week.

D:\Code\extras\jdk-11.0.8+10\bin\java.exe -Dvisualvm.id=28910047055900

"-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=56280:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin" 

-Dfile.encoding=UTF-8 

-p 
D:\Code\dorkbox\public_projects_libraries\SystemTray_Modules\build\classes-intellij;
C:\Users\user\.m2\repository\com\dorkbox\SystemTray\3.17\SystemTray-3.17.jar;
C:\Users\user\.m2\repository\com\dorkbox\Utilities\1.9\Utilities-1.9.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-jdk8\1.4.31\e613be5465ef1e6fd0468707690b7ebf625ea2fe\kotlin-stdlib-jdk8-1.4.31.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.slf4j\slf4j-api\1.7.30\b5a4b6d16ab13e34a88fae84c35cd5d68cac922c\slf4j-api-1.7.30.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-jdk7\1.4.31\84ce8e85f6e84270b2b501d44e9f0ba6ff64fa71\kotlin-stdlib-jdk7-1.4.31.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib\1.4.31\a58e0fb9812a6a93ca24b5da75e4b5a0cb89c957\kotlin-stdlib-1.4.31.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-common\1.4.31\6dd50665802f54ba9bc3f70ecb20227d1bc81323\kotlin-stdlib-common-1.4.31.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.jetbrains\annotations\13.0\919f0dfe192fb4e063e7dacadee7f8bb9a2672a9\annotations-13.0.jar;
C:\Users\user\.m2\repository\com\dorkbox\Executor\3.0\Executor-3.0.jar;
C:\Users\user\.m2\repository\com\dorkbox\SwtJavaFx\1.1\SwtJavaFx-1.1.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\ch.qos.logback\logback-classic\1.2.3\7c4f3c474fb2c041d8028740440937705ebb473a\logback-classic-1.2.3.jar;
C:\Users\user\.m2\repository\com\dorkbox\PropertyLoader\1.0\PropertyLoader-1.0.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.javassist\javassist\3.27.0-GA\f63e6aa899e15eca8fdaa402a79af4c417252213\javassist-3.27.0-GA.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\net.java.dev.jna\jna-platform\5.7.0\7b3683e4fceb28433c1365250dbc1f58e4c1df2c\jna-platform-5.7.0.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\net.java.dev.jna\jna\5.7.0\a04e3db7cf0011d1b6a4bcfaee30ab20f077014b\jna-5.7.0.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.tukaani\xz\1.9\1ea4bec1a921180164852c65006d928617bd2caf\xz-1.9.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\com.hierynomus\sshj\0.30.0\b373a758e184f321baa6be64f91674d0552da67b\sshj-0.30.0.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\ch.qos.logback\logback-core\1.2.3\864344400c3d4d92dfeb0a305dc87d953677c03c\logback-core-1.2.3.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlinx\kotlinx-coroutines-core-jvm\1.4.2\4b9c6b2de7cabfb2c9ad7a5c709b1ddb7bbfd2ad\kotlinx-coroutines-core-jvm-1.4.2.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\com.hierynomus\asn-one\0.4.0\8b84eb87d5a09bce5a01afb903bbe64ee7c874b9\asn-one-0.4.0.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.bouncycastle\bcpkix-jdk15on\1.66\86cca2c1a32775abe92316d9628b7ee50b6f19ad\bcpkix-jdk15on-1.66.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\org.bouncycastle\bcprov-jdk15on\1.66\ed564ade61defca27e26fb1378a70b22831fc5c1\bcprov-jdk15on-1.66.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\com.jcraft\jzlib\1.1.3\c01428efa717624f7aabf4df319939dda9646b2d\jzlib-1.1.3.jar;C:\Users\user\.gradle\caches\modules-2\files-2.1\net.i2p.crypto\eddsa\0.3.0\1901c8d4d8bffb7d79027686cfb91e704217c3e1\eddsa-0.3.0.jar;
C:\Users\user\.gradle\caches\modules-2\files-2.1\io.github.microutils\kotlin-logging-jvm\2.0.6\539f788429038f058f9c0da3cac14f7b653f0db8\kotlin-logging-jvm-2.0.6.jar 

-m SystemTray.Modules.main/dorkbox.TestTray

This is an absolute beast of a command line (IntelliJ does it automatically for me), but the part to notice is that there is nothing on the classpath, and Kotlin is on the module path, specified with -p.

dorkbox avatar Apr 11 '21 17:04 dorkbox

Just remembered, Updates.ENABLE is the kotlin notation, for java it's Updates.INSTANCE.ENABLE

dorkbox avatar Apr 11 '21 17:04 dorkbox

That's a lot all at once,

Apologies, will try to be keep it a bit more concise.

Just remembered, Updates.ENABLE is the kotlin notation, for java it's Updates.INSTANCE.ENABLE

Ah I missed that. To make this work, I had to 1) Explicitly add Updates to my pom.xml and also add requires Updates to my module-info.java to be able to access the Updates class.

Regarding the addition of the SSH client... (SSHJ), i think it makes sense to have that as an optional dependency, so release 4.2 will do that. (This way bouncycastle is also not included as a transitive dependency)

Perfect.

Regarding kotlin and JPMS.... This is what I used when testing JPMS. This is normally a single line, but I will break it out to make it easier to read. Ignore version info, as this was from last week.

Got it! I found this - https://stackoverflow.com/questions/47958627/do-kotlin-1-2-10-and-java-9-have-opposite-rules-regarding-automatic-modules - and that lead me to add this to my module-info.

module uk.co.bithatch.snake.ui {
        // Require SystemTray itself
	requires SystemTray;
       
        // Need this to get access to Updates class. Requiring transitive on SystemTray is not enough
	requires Updates;

        // This fixes "ClassNotFoundException: kotlin.jvm.internal.Intrinsics"
	requires kotlin.stdlib;

       // .. other requirements
}

This leads me to my final question (honest). Do you intend to either fully implement a module-info in all DorkBox modules, or at least add an Automatic-Module-Name to MANIFEST.MF? It would be nice if the DorkBox modules did not rely on automatic module name, and had a fully namespaced id. E.g. dorkbox.SystemTray.

brett-smith avatar Apr 12 '21 00:04 brett-smith

I like the detailed questions! It was just a lot to answer all at once.

I'm working on module-info right now for the Executor. By the way, all the jars (for the past year or so) already have an Automatic-Module-Name - I'm not sure how it works for transitive dependencies, and obviously having a proper JPMS release is important!

The only reason it's not done right now, is because multi-Release jars + JPMS is a massive headache even oracle support doesn't know the best way to solve.

dorkbox avatar Apr 12 '21 07:04 dorkbox

I'm working on module-info right now for the Executor. By the way, all the jars (for the past year or so) already have an Automatic-Module-Name - I'm not sure how it works for transitive dependencies, and obviously having a proper JPMS release is important!

Oh ok, yeh I see these now. I still think the names should be fully qualified though. (http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2017-May/000687.html - "Strongly recommend that all modules be named according to the reverse Internet domain-name convention").

The only reason it's not done right now, is because multi-Release jars + JPMS is a massive headache even oracle support doesn't know the best way to solve.

Understandable. I have used MRJAR + JPMS successfully in a few projects, and could tell you how to do it with Maven. With Gradle though I wouldn't have a clue (this was my PR to the dbus-java project adding JPMS+MRJAR - https://github.com/hypfvieh/dbus-java/commit/ed1e6ec133b14c2681693b24635a63782afa98e7)

brett-smith avatar Apr 12 '21 08:04 brett-smith

They will be strongly named. It won't be a FQDN, mostly since I own all dorkbox.(primary) and some .international domains -- it will reflect the package name, so it will be be dorkbox.executor and dorkbox.systemtray

I'll close this once I have JPMS done. I might have it done, but the trick is to verify it (the jar at least looks correct, but who knows!) :P

dorkbox avatar Apr 13 '21 17:04 dorkbox

Sounds good! Looking forward to the update.

brett-smith avatar Apr 13 '21 22:04 brett-smith

Wow, JPMS support was a massive headache, but it's just about done.

dorkbox avatar Jan 11 '23 00:01 dorkbox