Hierarchical namespace support for JBang directives
A small game program written using LWJGL requires the following dependencies to work on various OS/CPU architectures. See PR https://github.com/jbangdev/jbang-examples/pull/15
//DEPS org.lwjgl:lwjgl:3.3.6
//DEPS org.lwjgl:lwjgl-glfw:3.3.6
//DEPS org.lwjgl:lwjgl-opengl:3.3.6
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
Specifically on macOS (x64 and arm64) a Mac-specific runtime option is also required. This option is not supported by Java VMs on non-Mac operating systems and causes them to display an error message and stop.
//RUNTIME_OPTIONS -XstartOnFirstThread
JBang does not allow platform specific runtime options or dependencies to be configured that only take affect when a pre-determined OS or OS/CPU combination is detected.
This problem could be solved by allowing a small subset of JBang directives to be tagged with a namespace (package name in Java terminology).
//RUNTIME_OPTIONS[namespace]-XstartOnFirstThread//DEPS[namespace]org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
How is this envisioned to work in practice?
//DEPS org.lwjgl:lwjgl:3.3.6 org.lwjgl:lwjgl-glfw:3.3.6 org.lwjgl:lwjgl-opengl:3.3.6
//DEPS[os.windows.cpu.amd64] org.lwjgl:lwjgl:3.3.6:natives-windows org.lwjgl:lwjgl-glfw:3.3.6:natives-windows org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//DEPS[os.windows.cpu.arm64] org.lwjgl:lwjgl:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//DEPS[os.linux.cpu.amd64] org.lwjgl:lwjgl:3.3.6:natives-linux org.lwjgl:lwjgl-glfw:3.3.6:natives-linux org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//DEPS[os.linux.cpu.arm64] org.lwjgl:lwjgl:3.3.6:natives-linux-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//DEPS[os.macos.cpu.amd64] org.lwjgl:lwjgl:3.3.6:natives-macos org.lwjgl:lwjgl-glfw:3.3.6:natives-macos org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//DEPS[os.macos.cpu.arm64] org.lwjgl:lwjgl:3.3.6:natives-macos-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
//RUNTIME_OPTIONS[os.macos] -XstartOnFirstThread
When a script is executed JBang would filter the configured directives based on OS and CPU architecture detected.
When JBang exports to Maven or Gradle, the namespace values will be used to ensure dependencies and runtime options only apply to specific OS/CPU combinations.
Fatjar support will also have to be revisited.
A non-hierarchical representation of the same information:
//DEPS org.lwjgl:lwjgl:3.3.6 org.lwjgl:lwjgl-glfw:3.3.6 org.lwjgl:lwjgl-opengl:3.3.6
//DEPS[os=windows,cpu=amd64] org.lwjgl:lwjgl:3.3.6:natives-windows org.lwjgl:lwjgl-glfw:3.3.6:natives-windows org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//DEPS[os=windows,cpu=arm64] org.lwjgl:lwjgl:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//DEPS[os=linux,cpu=amd64] org.lwjgl:lwjgl:3.3.6:natives-linux org.lwjgl:lwjgl-glfw:3.3.6:natives-linux org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//DEPS[os=linux,cpu=arm64] org.lwjgl:lwjgl:3.3.6:natives-linux-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//DEPS[os=macos,cpu=amd64] org.lwjgl:lwjgl:3.3.6:natives-macos org.lwjgl:lwjgl-glfw:3.3.6:natives-macos org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//DEPS[os=macos,cpu=arm64] org.lwjgl:lwjgl:3.3.6:natives-macos-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
//RUNTIME_OPTIONS[os=macos] -XstartOnFirstThread
How could namespaces be supported on the JBang command-line? Simply add a --namespace option.
$ jbang --namespace "[os.macos]" --runtime-option -XstartOnFirstThread -namespace "[]" --deps org.lwjgl:lwjgl:3.3.6 ....
But then one can argue that JBang needs a //NAMESPACE directive.
//NAMESPACE [os.macos]
//RUNTIME_OPTIONS -XstartOnFirstThread
//NAMESPACE []
//DEPS org.lwjgl:lwjgl:3.3.6
//DEPS org.lwjgl:lwjgl-glfw:3.3.6
//DEPS org.lwjgl:lwjgl-opengl:3.3.6
//NAMESPACE [os.windows.cpu.amd64]
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//NAMESPACE [os.windows.cpu.arm64]
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//NAMESPACE [os.linux.cpu.amd64]
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux org.lwjgl:lwjgl-glfw:3.3.6:natives-linux org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//NAMESPACE [os.linux.cpu.arm64]
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//NAMESPACE [os.macos.cpu.amd64]
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos org.lwjgl:lwjgl-glfw:3.3.6:natives-macos org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//NAMESPACE [os.macos.cpu.arm64]
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
in #2133 the original idea was to have attribute list on //DEPS directive similar to how asciidoc does attributees, i.e. //DEPS{"run"} g:a:v, g:a2:v woulde be same as //DEPS g:a:v{scope="run"}, g:a2:v{scope="run"}
I didn't implement that as we didn't have a good usecase(s) enough to break the format of //DEPS for IDE's but also so that the attributes are part of the dependency request rather than to the directive.
But definitely something we could consider if we find enough usecases for it.
I find the idea of being able to say this directive only apply when certain criterias makes sense - and having a name for that (you call it namespace, maven would call it profile) actually could make the syntax quite compact.
We'll need a way to define which "profile"/"namespace" to run with and there would need to be some defaults defined as can't expect users caling app to define what namespace/profile to run with.
One way this could look like is:
//profile with name only active/true if fullfilling all properties
//PROFILE osx-arm64{os=mac,arch=aarch64}
//PROFILE osx{os=mac}
//PROFILE windows{os=windows}
//PROFILE windows-aarm64{os=windows,arch=aarch64}
//PROFILE linux={os=linux}
//runtime_options only include if profile=osx active
//RUNTIME_OPTIONS{profile=osx} -XstartOnFirstThread
//always included
//DEPS org.lwjgl:lwjgl:3.3.6
//DEPS org.lwjgl:lwjgl-glfw:3.3.6
//DEPS org.lwjgl:lwjgl-opengl:3.3.6
//only included when os and arch mathces
//DEPS{profile=windows} org.lwjgl:lwjgl:3.3.6:natives-windows,org.lwjgl:lwjgl-glfw:3.3.6:natives-windows,org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//DEPS{profile=windows-aarm64} org.lwjgl:lwjgl:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64 org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//DEPS{profile=linux-arm64} org.lwjgl:lwjgl:3.3.6:natives-linux org.lwjgl:lwjgl-glfw:3.3.6:natives-linux org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//DEPS{profile=osx} org.lwjgl:lwjgl:3.3.6:natives-macos org.lwjgl:lwjgl-glfw:3.3.6:natives-macos org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
issue with above is that windows and windows-aarm64 could both be active at the same time....
@maxandersen , I'm working on a reponse but got something planned for the evening. Will reply more fully over the weekend. In the meantime I'll leave this hierarchical representation of a JBang configuration here. I think the PROFILE concept and NAMESPACE concepts are different. NAMESPACE is similar to PACKAGE (in Java). PROFILE would be created at runtime by os.name and os.arch which would then at runtime activate certain parts of the configuration tree.
yes, but that isn't (really) different from {os=macos,cpu=x64} except namespace is locked to only hardware notions where generic key/value is open ended.
I'm simply suggesting being able to name some of these combinations.
I'm simply suggesting being able to name some of these combinations.
OK.
The main reason why I've been advocating for support for a largely pre-defined hierarchical configuration structure is that it will allow JBang to externalize configuration data into a JSON, TOML, or YAML configuration file like config.yaml similar to how Quarkus supports application.yaml
jbang:
deps:
- org.lwjgl:lwjgl:3.3.6
- org.lwjgl:lwjgl-glfw:3.3.6
- org.lwjgl:lwjgl-opengl:3.3.6
runtimeOptions: -Xmx1g
platforms:
macos:
runtimeOptions: -XstartOnFirstThread
arch:
- x86_64:
deps:
- org.lwjgl:lwjgl:3.3.6:natives-macos
- org.lwjgl:lwjgl-glfw:3.3.6:natives-macos
- org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
- aarch64:
deps:
- org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
- org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64
- org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
In the main JBang script file a new directive //JBANG_CONFIG_FILE could then be used to retrieve additional JBang configuration data from a configuration file.
//JAVA 21+
//SOURCES File1.java File2.java
//JBANG_CONFIG_FILE myAppConfig.yaml
public static void main(String[] args) {
}
And if we have three different scripts (3 games implemented using LWJGL) that use the same configuration we do not have to duplicate the configuration directives in three programs; the shared config can simply be referenced using //JBANG_CONFIG_FILE.
The //NAMESPACE directive that was proposed earlier is really just a path to the location in the configuration data hierarchy where the data should be stored so it could be called JBANG_CONFIG_PATH.
//JBANG_CONFIG_PATH /platforms/macos/cpu/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos
...
The argument for being able to create toml isn't winning me over (yet :) but the argument that if we do a predefined structure based on nested arch parts could be used to define consistent profiles in maven and Gradle configs.
I like that.
The argument for being able to create toml isn't winning me over (yet :)
It could be yaml as well. :-)
define consistent profiles in maven and Gradle configs
Nice.
I'm not trying to advance the idea of including toml data in comments in a Java or other JBang supported language file anymore. But I do think that allowing configuration data (directives) to be externalized to a file (YAML might be best for this) has merit. It could be a nice evolutionary direction for JBang while retaining full support for all //JBANG-DIRECTIVES.
Just realized that it is possible to have a dedicated Java source file containing only JBang directives which is then referenced using the //SOURCES directive from the main program. This approach works reasonably well to share config options between scripts or to externalize config options. This is new to me, but might already be a well-established JBang trick that several programs depend on.
Example config updated to use //CONFIG_PATH.
//DEPS org.lwjgl:lwjgl:3.3.6
//DEPS org.lwjgl:lwjgl-glfw:3.3.6
//DEPS org.lwjgl:lwjgl-opengl:3.3.6
//RUNTIME_OPTIONS -Xmx1g
//CONFIG_PATH jbang/platforms/macos
//RUNTIME_OPTIONS -XstartOnFirstThread
//CONFIG_PATH jbang/platforms/windows/arch/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//CONFIG_PATH jbang/platforms/windows/arch/aarch64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//CONFIG_PATH jbang/platforms/linux/arch/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//CONFIG_PATH jbang/platforms/linux/arch/aarch64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//CONFIG_PATH jbang/platforms/macos/arch/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//CONFIG_PATH jbang/platforms/macos/arch/aarch64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
//CONFIG_PATH jbang
What is config_path?
What is config_path?
Thought it would be a better name to use than namespace:
//NAMESPACE [jbang.platforms.macos.arch.arm64]//CONFIG_PATH jbang/platforms/macos/arch/arm64
//CONFIG_PATH could support relative paths as well. See ../aarc64 below.
//DEPS org.lwjgl:lwjgl:3.3.6
//DEPS org.lwjgl:lwjgl-glfw:3.3.6
//DEPS org.lwjgl:lwjgl-opengl:3.3.6
//RUNTIME_OPTIONS -Xmx1g
//CONFIG_PATH jbang/platforms/macos
//RUNTIME_OPTIONS -XstartOnFirstThread
//CONFIG_PATH jbang/platforms/windows/arch/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//CONFIG_PATH ../aarch64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//CONFIG_PATH jbang/platforms/linux/arch/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-linux
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//CONFIG_PATH ../aarch64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//CONFIG_PATH jbang/platforms/macos/arch/x86_64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-macos
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//CONFIG_PATH ../aarch64
//DEPS org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
//DEPS org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64
//DEPS org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
//CONFIG_PATH jbang
Ok - but that is:
A) overlapping with jbang config variables B) now means previous lines "mutates" the meaning of lines coming after it...which means all existing simple parsing won't work C) IDEs needs more complex updating too
Alternatively, and I'm thinking about Toml again, if a config file could be added to the //SOURCES list, then the platform/os/cpu libraries and runtime options could be specified in a TOML file and referenced as //SOURCES config.toml
[jbang.platform.macos]
runtimeOptions = ["-XstartOnFirstThread"]
[jbang.platform.macos.aarch64]
deps = ["org.lwjgl:lwjgl:3.3.6:natives-macos-arm64", "org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64", "org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64"]
[jbang.platform.macos.x86_64]
deps = ["org.lwjgl:lwjgl:3.3.6:natives-macos", "org.lwjgl:lwjgl-glfw:3.3.6:natives-macos", "org.lwjgl:lwjgl-opengl:3.3.6:natives-macos"]
Toml file content is super easy to parse using TomlJ (Java). Gradle version catalogs are also specified using Toml (https://docs.gradle.org/current/userguide/version_catalogs.html) so it is not that far fetched an idea.
The additional Toml platform information will be used during Maven or Gradle build file creation.
A separate file, just to define some "profiles"? Please people, remember that all this is supposed to be really simple. Extra config files, of whatever format, just to add extra info to directives is not keeping things simple.
I think having the written-out version as explained in https://github.com/jbangdev/jbang/issues/2188#issuecomment-3213340934 would already be incredibly useful ... and used by 0.1% of users. So I'd not even think about profiles and namespaces unless we get a bunch of users clamoring for them because it's "so much work to write out all options all the time".
I do agree that being able to set options depending os/arch would be useful, but we already discussed that the syntax //XXX{opts} is going to be harder on our current parser and it also affect all our current IDE plugins (meaning you must upgrade because there would be no backward compatibility).
We did discuss that we could probably do //XXX {opts}. Some things might still fail when using older plugins, but syntax highlighting would work for example.
We did discuss that we could probably do //XXX {opts}
Do you have this format in mind?
//RUNTIME_OPTONS {osFamily=macos} -XstartOnFirstThread
//DEPS {os.family="macos", os.arch="amd64"} org.lwjgl:lwjgl:3.3.6:natives-macos
//DEPS {os.family="macos", os.arch="amd64"} org.lwjgl:lwjgl-glfw:3.3.6:natives-macos
//DEPS {os.family="macos", os.arch="amd64"} org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//DEPS {os.family="macos", os.arch="arm64"} org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
//DEPS {os.family="macos", os.arch="arm64"} org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64
//DEPS {os.family="macos", os.arch="arm64"} org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
which would align with Maven profiles
<profile>
<id>macos-amd64</id>
<activation>
<os>
<family>Mac</family>
<arch>amd64</arch>
</os>
</activation>
<dependencies>
<dependency>
....
</dependency>
</dependencies>
</profile>
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.commons:commons-lang3:3.18.0
import org.apache.commons.lang3.SystemUtils;
public class detectOS {
public static void main(String... args) {
System.out.println("Linux: " + SystemUtils.IS_OS_LINUX);
System.out.println("macOS: " + SystemUtils.IS_OS_MAC);
System.out.println("Windows: " + SystemUtils.IS_OS_WINDOWS);
System.out.println("Arch: " + SystemUtils.OS_ARCH);
}
}
Do you have this format in mind?
Yes, although I would go for shorter keys, simply:
//DEPS {os=macos, arch=amd64} org.lwjgl:lwjgl:3.3.6:natives-macos
No need to be overly technical with the naming, just keep it short and easy to type.
And given the fact that there is no overlap in values between os, arch and scope we should perhaps just allow key-less values for those important cases and infer the key, eg:
//DEPS {macos, amd64} org.lwjgl:lwjgl:3.3.6:natives-macos
The problem with this is that it is NOT possible to represent in maven/Gradle afaics.
I'm not (yet) saying we shouldn't enable this but maybe if we could make it be mappable to a "profile" then it would enable use of native variants much more portably than the current open ended approach i proposed.
//DEPS {macos, amd64} org.lwjgl:lwjgl:3.3.6:natives-macos
Nice and concise.
Please keep in mind we need this for //RUNTIME_OPTIONS as well primarily for the operating system family only to be able to specify platform specific runtime options. LWJGL applications do not start on macOS if the startOnFirstThread option is not specified. If it is specified on other platforms, then it results in a JVM start-up failure because the option is platform specific (macOS only).
//RUNTIME_OPTIONS {macos} -XstartOnFirstThread
An initial implementation could target //RUNTIME_OPTIONS only, which will add immediate value. The game Pong.java (https://github.com/jbangdev/jbang-examples/pull/15) must be run on macOS as follows:
- jbang run -R=-XstartOnFirstThread Pong.java
and on Windows and Linux as:
- jbang run Pong.java
If //RUNTIME_OPTIONS {macos} -XstartOnFirstThread could be added to Pong.java then the game can be run on all platforms without specifying any additional runtime options on the command-line.
Support for Linux or Windows specific JVM or other runtime options are also required:
//RUNTIME_OPTIONS {windows} ....//RUNTIME_OPTIONS {linux} ....
Support for //DEPS {os, arch} .... could follow later.
Please keep in mind we need this ...
Sure, this is a generic idea that could be applied to any relevant directives. (Perhaps just bite the bullet and add it generically to all?)
Edit: NB @maxandersen I realize this is contrary to what I fought for before :-) It would make the current suggested implementation unnecessary. So perhaps we should go all out to this alternative solution, it being more generally useful, and scratch the idea of having props for each dependency.
Meaning we'd not be able to do:
//DEPS org.lwjgl:lwjgl:3.3.6 org.lwjgl:lwjgl:3.3.6:natives-macos{os.family="macos", os.arch="amd64"}
but only
//DEPS org.lwjgl:lwjgl:3.3.6
//DEPS {os="macos", arch="amd64"} org.lwjgl:lwjgl:3.3.6:natives-macos
It also means that filtering would not be the responsibility of the //DEPS handling code but it would be pushed higher up the chain in the part that generically handles directives, and the //DEPS handling code would receive the filtered result.
It also means that filtering would not be the responsibility of the //DEPS handling code but it would be pushed higher up the chain in the part that generically handles directives, and the //DEPS handling code would receive the filtered result.
This means that the maven and gradle export functionality would only be able to see the filtered dependencies and would then only support one os/arch combination. But this might be fine for a first implementation, otherwise fairly extensive code changes are required followed by a lot of additional testing work as well.
This only an experiment.
I created a java-cli command (based on previous work on jython-cli)
- https://github.com/wfouche/jbang-catalog/blob/main/scripts/JavaCli.java
Pong.java was modified to become Pong2.java
- https://github.com/wfouche/jbang-catalog/blob/main/scripts/Pong2.java
which can be run on various platforms using:
$ jbang run java-cli@wfouche Pong2.java
Deps and RuntimeOptions detected per OS / ARCH combination.
Yes that experiment (run separate process to calculate arguments for jbang) is already possible and something used with for example jdbc@maxandersen.
Not solving the issue of havi g a way to resolve consistently/deterministically in jbang, IDEs and maven/Gradle.
Not solving the issue of havi g a way to resolve consistently/deterministically in jbang, IDEs and maven/Gradle.
Edited: 2025-08-27T10:17:25 (GMT+2)
The key aim of the experiment was to show the benefits of using a Concrete Syntax Tree (CST) which the TomlParseResult is. Parsing becomes a non-issue and navigating the globally available CST can be done by all parts of the code even the Maven/Gradle export code.
It would be possible to continue to parse the JBang directives (as it is done today) but also incrementally and internally construct a multi-line Toml document (Java String) and then create a CST from the String document. This would be completely invisible to users of JBang and those writing scripts.
- {//DIRECTIVES across all Sources} -> multi-line Toml String -> TomlParseResult (CST) -> .....
Here's a Java code fragment showing how a CST can be used to extract runtime options that are specific to the current OS.
// platform.<linux,macos,windows>.runtimeOptions
keyName = "platform." + osFamily + ".runtimeOptions";
for (Object e : cst.getArrayOrEmpty(keyName).toList()) {
String ropt = (String) e;
ropts.add(ropt);
}
Example:
//DEPS info.picocli:picocli:4.6.3
//DESC This script launches sqlline or h2 console by just specifying jdbc url.
//DESC `jdbc` will use jbang to download the required driver and launch
//DESC sqlline (cli) or h2 console (web) or jdbcnav (desktop).
//JAVA 11+
//RUNTIME_OPTIONS {os="macos"} -XstartOnFirstThread
//DEPS {os="macos", arch="amd64"} org.lwjgl:lwjgl:3.3.6:natives-macos
translated to
[jbang]
deps = ["info.picocli:picocli:4.6.3"]
desc = '''
This script launches sqlline or h2 console by just specifying jdbc url.
`jdbc` will use jbang to download the required driver and launch
sqlline (cli) or h2 console (web) or jdbcnav (desktop).
'''
java = "11+"
[platform.macos]
runtimeOptions = [" -XstartOnFirstThread"]
[platform.macos.amd64]
deps = ["org.lwjgl:lwjgl:3.3.6:natives-macos"]
@wfouche you can keep hammering the Toml drum, but there's no interest from our side to go anywhere in that direction.
I also see no advantage to using Toml as an internal model. What is more likely to happen is that the current ad-hoc parser will "grow up" and return an actual Java model. (Of course that model can then be serialized to whatever format we'd want, still, toml is not currently on our wishlist of formats to support)
To wrap up this discussion, I had a look at how to represent the LWJGL version information in a Gradle version catalog.
Gradle version catalog - libs.versions.toml
[versions]
lwjgl = "3.3.6"
[libraries]
# Core LWJGL Libraries
lwjgl = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl" }
lwjgl-glfw = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl" }
lwjgl-opengl = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl" }
# Native Dependencies (with classifiers)
lwjgl-natives-windows-amd64 = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl", classifier = "natives-windows" }
lwjgl-glfw-natives-windows-amd64 = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl", classifier = "natives-windows" }
lwjgl-opengl-natives-windows-amd64 = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl", classifier = "natives-windows" }
lwjgl-natives-windows-arm64 = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl", classifier = "natives-windows-arm64" }
lwjgl-glfw-natives-windows-arm64 = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl", classifier = "natives-windows-arm64" }
lwjgl-opengl-natives-windows-arm64 = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl", classifier = "natives-windows-arm64" }
lwjgl-natives-macos-amd64 = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl", classifier = "natives-macos" }
lwjgl-glfw-natives-macos-amd64 = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl", classifier = "natives-macos" }
lwjgl-opengl-natives-macos-amd64 = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl", classifier = "natives-macos" }
lwjgl-natives-macos-arm64 = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl", classifier = "natives-macos-arm64" }
lwjgl-glfw-natives-macos-arm64 = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl", classifier = "natives-macos-arm64" }
lwjgl-opengl-natives-macos-arm64 = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl", classifier = "natives-macos-arm64" }
lwjgl-natives-linux-amd64 = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl", classifier = "natives-linux" }
lwjgl-glfw-natives-linux-amd64 = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl", classifier = "natives-linux" }
lwjgl-opengl-natives-linux-amd64 = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl", classifier = "natives-linux" }
lwjgl-natives-linux-arm64 = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl", classifier = "natives-linux-arm64" }
lwjgl-glfw-natives-linux-arm64 = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl", classifier = "natives-linux-arm64" }
lwjgl-opengl-natives-linux-arm64 = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl", classifier = "natives-linux-arm64" }
[bundles]
# Bundles for each platform
lwjgl-windows-amd64 = ["lwjgl", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-natives-windows", "lwjgl-glfw-natives-windows", "lwjgl-opengl-natives-windows"]
lwjgl-windows-arm64 = ["lwjgl", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-natives-windows-arm64", "lwjgl-glfw-natives-windows-arm64", "lwjgl-opengl-natives-windows-arm64"]
lwjgl-macos-amd64 = ["lwjgl", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-natives-macos", "lwjgl-glfw-natives-macos", "lwjgl-opengl-natives-macos"]
lwjgl-macos-arm64 = ["lwjgl", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-natives-macos-arm64", "lwjgl-glfw-natives-macos-arm64", "lwjgl-opengl-natives-macos-arm64"]
lwjgl-linux-amd64 = ["lwjgl", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-natives-linux", "lwjgl-glfw-natives-linux", "lwjgl-opengl-natives-linux"]
lwjgl-linux-arm64 = ["lwjgl", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-natives-linux-arm64", "lwjgl-glfw-natives-linux-arm64", "lwjgl-opengl-natives-linux-arm64"]
In the build.gradle file at runtime the correct bundle can be acivated with code like this:
dependencies {
def osArch = System.getProperty("os.arch")
// Add the appropriate LWJGL bundle based on the operating system
if (org.gradle.internal.os.OperatingSystem.current().isWindows() && osArch == "x86_64") {
implementation libs.bundles.lwjgl.windows.amd64
}
if (org.gradle.internal.os.OperatingSystem.current().isWindows() && osArch == "aarch64") {
implementation libs.bundles.lwjgl.windows.arm64
}
if (org.gradle.internal.os.OperatingSystem.current().isMacOs() && osArch == "x86_64") {
implementation libs.bundles.lwjgl.macos.amd64
}
if (org.gradle.internal.os.OperatingSystem.current().isMacOs() && osArch == "aarch64") {
implementation libs.bundles.lwjgl.macos.arm64
}
if (org.gradle.internal.os.OperatingSystem.current().isLinux() && osArch == "x86_64") {
implementation libs.bundles.lwjgl.linux.amd64
}
if (org.gradle.internal.os.OperatingSystem.current().isLinux() && osArch == "aarch64") {
implementation libs.bundles.lwjgl.linux.arm64
}
}
Maybe the bundle concept from Gradle version catalogs could be supported in JBang.
A bundle tag could consist of three parts: name, osFamily (optional), osArch (optional). If osFamily is not specified then osArch is absent. Examples lib1, lib1-macos, lib1-macos-arm64.
//DEPS {lwjgl-common} org.lwjgl:lwjgl:3.3.6
//DEPS {lwjgl-common} org.lwjgl:lwjgl-glfw:3.3.6
//DEPS {lwjgl-common} org.lwjgl:lwjgl-opengl:3.3.6
//DEPS {lwjgl-windows-amd64} org.lwjgl:lwjgl:3.3.6:natives-windows
//DEPS {lwjgl-windows-amd64} org.lwjgl:lwjgl-glfw:3.3.6:natives-windows
//DEPS {lwjgl-windows-amd64} org.lwjgl:lwjgl-opengl:3.3.6:natives-windows
//DEPS {lwjgl-windows-arm64} org.lwjgl:lwjgl:3.3.6:natives-windows-arm64
//DEPS {lwjgl-windows-arm64} org.lwjgl:lwjgl-glfw:3.3.6:natives-windows-arm64
//DEPS {lwjgl-windows-arm64} org.lwjgl:lwjgl-opengl:3.3.6:natives-windows-arm64
//DEPS {lwjgl-linux-amd64} org.lwjgl:lwjgl:3.3.6:natives-linux
//DEPS {lwjgl-linux-amd64} org.lwjgl:lwjgl-glfw:3.3.6:natives-linux
//DEPS {lwjgl-linux-amd64} org.lwjgl:lwjgl-opengl:3.3.6:natives-linux
//DEPS {lwjgl-linux-arm64} org.lwjgl:lwjgl:3.3.6:natives-linux-arm64
//DEPS {lwjgl-linux-arm64} org.lwjgl:lwjgl-glfw:3.3.6:natives-linux-arm64
//DEPS {lwjgl-linux-arm64} org.lwjgl:lwjgl-opengl:3.3.6:natives-linux-arm64
//DEPS {lwjgl-macos-amd64} org.lwjgl:lwjgl:3.3.6:natives-macos
//DEPS {lwjgl-macos-amd64} org.lwjgl:lwjgl-glfw:3.3.6:natives-macos
//DEPS {lwjgl-macos-amd64} org.lwjgl:lwjgl-opengl:3.3.6:natives-macos
//DEPS {lwjgl-macos-arm64} org.lwjgl:lwjgl:3.3.6:natives-macos-arm64
//DEPS {lwjgl-macos-arm64} org.lwjgl:lwjgl-glfw:3.3.6:natives-macos-arm64
//DEPS {lwjgl-macos-arm64} org.lwjgl:lwjgl-opengl:3.3.6:natives-macos-arm64
//BUNDLE_DEPS lwjgl-windows-amd64 = [lwjgl-common, lwjgl-windows-amd64]
//BUNDLE_DEPS lwjgl-windows-arm64 = [lwjgl-common, lwjgl-windows-arm64]
//BUNDLE_DEPS lwjgl-macos-amd64 = [lwjgl-common, lwjgl-macos-amd64]
//BUNDLE_DEPS lwjgl-macos-arm64 = [lwjgl-common, lwjgl-macos-arm64]
//BUNDLE_DEPS lwjgl-linux-amd64 = [lwjgl-common, lwjgl-linux-amd64]
//BUNDLE_DEPS lwjgl-linux-arm64 = [lwjgl-common, lwjgl-linux-arm64]
//RUNTIME_OPTIONS {rto} -Xmx512m
//RUNTIME_OPTIONS {rto-macos} -XstartOnFirstThread
//BUNDLE_RUNTIME_OPTIONS lwjgl = [rto]
//BUNDLE_RUNTIME_OPTIONS lwjgl-macos = [rto-macos]
How would it look like for things outside dependencies - ie. The runtime flags ?