Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read config named as exe file #13

Merged
merged 10 commits into from
May 18, 2024
50 changes: 32 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,42 @@

A JVM starter in Rust

`roast` is a small executable that launches a JVM, similar to what using `jpackage` would output. It uses a config file to determine some options, following [packr](https://github.com/libgdx/packr/) json format:

```json
{
"classPath": [
"my-game.jar"
],
"mainClass": "io.github.fourlastor.gdx.lwjgl3.Lwjgl3Launcher",
"useZgcIfSupportedOs": true,
"vmArgs": [
"-Xmx1G"
]
}
```
`roast` is a small executable that launches a JVM, similar to what using `jpackage` would output. It uses a config file to determine some options, inspired by [packr](https://github.com/libgdx/packr/) json format.

In addition to launching the JVM, it hints Windows systems with hybrid GPU setups ([NVIDIA Optimus](https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm), [AMD PowerXpress](https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/)) to use the discrete GPU.


## API

`roast` will look for the following in its containing folder:
`roast` will look for the following in its containing folder

### Runtime

A JDK/JRE, or a minimized image from `jlink`, in the `jdk` folder.

### JSON config file

1. Config file `config.json` and the referenced jars.
1. A JDK/JRE (or a minimized image from `jlink`) in a folder called `jdk`
The config file must be in the `app` folder, and must have the same name as the executable, so for example, if your executable is `game` (or `game.exe`), the config file will be `app/game.json`.

It's possible to have multiple copies of roast named differently to support different launch options on the same runtime. For example, you could have `game` and `game-debug` executables corresponding to `app/game.json` and `app/game-debug.json`.

The config file supports the following options:

- `classPath`: path of the jars to include in the application classpath (usually, your application jar), this is mandatory
- `mainClass`: the package and name of the class from which to call the method `public static void main(String[] args)`, this is mandatory
- `useZgcIfSupportedOs`: uses ZGC when [supported](https://wiki.openjdk.org/display/zgc/Main#Main-SupportedPlatforms), defaults to false
- `useMainAsContextClassLoader`: sets the main class as the main thread's [context class loader](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getContextClassLoader--), defaults to false
- `vmArgs`: arguments to pass to the java runtime, defaults to an empty array
- `args`: arguments to the pass to main method, defaults to an empty array

For example:

```json
{
"classPath": ["app.jar"],
"mainClass": "io.github.fourlastor.Main",
"useZgcIfSupportedOs": true,
"useMainAsContextClassLoader": false,
"vmArgs": ["-Xmx1G"],
"args": ["cli", "args"]
}
```
42 changes: 29 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use std::{
struct Config {
classPath: Vec<String>,
mainClass: String,
vmArgs: Vec<String>,
useZgcIfSupportedOs: bool,
useMainAsContextClassLoader: bool,
vmArgs: Option<Vec<String>>,
args: Option<Vec<String>>,
useZgcIfSupportedOs: Option<bool>,
useMainAsContextClassLoader: Option<bool>,
}

// Picks discrete GPU on Windows, if possible
Expand All @@ -39,14 +40,16 @@ const JVM_LOCATION: [&str; 3] = ["jdk", "lib", "server"];
#[cfg(target_os = "linux")]
const JVM_LOCATION: [&str; 3] = ["jdk", "lib", "server"];

const APP_FOLDER: &str = "app";

fn start_jvm(
jvm_location: &Path,
class_path: Vec<String>,
main_class_name: &str,
vm_args: Vec<String>,
args: Vec<String>,
use_zgc_if_supported: bool,
use_main_as_context_class_loader: bool,
args: Vec<String>,
) {
let mut args_builder = InitArgsBuilder::new()
.version(JNIVersion::V8)
Expand Down Expand Up @@ -200,14 +203,21 @@ fn is_zgc_supported() -> bool {
return true;
}

fn read_config(path: PathBuf) -> Option<Config> {
return fs::read_to_string(path.clone())
.ok()
.and_then(|it| serde_json::from_str(&it).ok());
}

fn main() {
let args: Vec<String> = env::args().collect();
let cli_args: Vec<String> = env::args().skip(1).collect();
let current_exe = env::current_exe().expect("Failed to get current exe location");
let current_location = current_exe.parent().expect("Exe must be in a directory");
let jvm_location = current_location.join(JVM_LOCATION.iter().collect::<PathBuf>());
let config_file_path = current_location.join("config.json");
let data = fs::read_to_string(config_file_path).expect("Unable to read config file");
let config: Config = serde_json::from_str(&data).expect("Invalid config json");
let config_file_path = current_location
.join(APP_FOLDER)
.join(current_exe.with_extension("json").file_name().unwrap());
let config: Config = read_config(config_file_path).expect(&format!("Unable to read config file {}/{}/{}", current_location.to_string_lossy(), APP_FOLDER, current_exe.with_extension("json").to_string_lossy()));
let class_path: Vec<String> = config
.classPath
.into_iter()
Expand All @@ -219,13 +229,19 @@ fn main() {
.unwrap()
})
.collect();
let main_class = &config.mainClass.replace(".", "/");
let vm_args = config.vmArgs.unwrap_or_else(|| Vec::new());
let config_args = config.args.unwrap_or_else(|| Vec::new());
let use_zgc_if_supported = config.useZgcIfSupportedOs.unwrap_or(false);
let use_main_as_context_class_loader = config.useMainAsContextClassLoader.unwrap_or(false);

start_jvm(
&jvm_location,
class_path,
&config.mainClass.replace(".", "/"),
config.vmArgs,
config.useZgcIfSupportedOs,
config.useMainAsContextClassLoader,
args,
main_class,
vm_args,
[config_args, cli_args].concat(),
use_zgc_if_supported,
use_main_as_context_class_loader,
);
}