Feat(Launcher): route --run and --play to the runner

This commit is contained in:
devLucasEmiliano committed 2026-10-22 18:38:12 -03:00
1 parent 2e64e4dd81
commit 5d030c3269
6 files changed
+94 -20

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
#![recursion_limit = "256"]
pub mod core;
pub mod runner;
pub mod startup;
pub mod ui;
+13
View File
@@ -5,8 +5,10 @@
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use spartanport_launcher::core::env::environment_of;
use spartanport_launcher::core::paths::{default_port_dir, Paths};
use spartanport_launcher::core::platform;
use spartanport_launcher::runner::{self, RunContext};
use spartanport_launcher::startup::{self, Route};
use spartanport_launcher::{ui, APP_NAME, VERSION};
@@ -22,6 +24,17 @@ fn main() {
let port_dir = default_port_dir(&exe_dir);
match startup::route(&args) {
Route::Version => println!("{APP_NAME} {VERSION}"),
// `Route::Play` routes like `Run` until the play role exists.
Route::Run | Route::Play => {
let paths = Paths::from_env(&port_dir, &|key| std::env::var(key).ok());
let context = RunContext {
port_dir: &paths.port_dir,
env: environment_of(std::env::vars_os()),
args: args.iter().skip(1).cloned().collect(),
out: &mut std::io::stdout(),
};
std::process::exit(runner::run(context))
}
Route::Python => {
// The Python launcher of the port folder (`SPARTAN_PORT_DIR` included).
let paths = Paths::from_env(&port_dir, &|key| std::env::var(key).ok());
+30
View File
@@ -0,0 +1,30 @@
//! The runner (Python `run.py`): starts the game under the probe and reports what it saw.
use std::ffi::OsString;
use std::io::Write;
use std::path::Path;
use indexmap::IndexMap;
/// What one run needs from the program around it.
pub struct RunContext<'a> {
pub port_dir: &'a Path,
pub env: IndexMap<String, String>,
pub args: Vec<OsString>,
pub out: &'a mut dyn Write,
}
/// Runs the runner and returns its exit code.
pub fn run(context: RunContext<'_>) -> i32 {
let probe = if cfg!(windows) {
"spartanport-probe.exe"
} else {
"spartanport-probe"
};
// A closed stdout is nothing to report on: the code says it.
let _ = writeln!(
context.out,
"{probe} not found: build it with build.sh or use a packaged build."
);
1
}
+12 -9
View File
@@ -21,8 +21,11 @@ use crate::APP_NAME;
pub enum Route {
/// `--version`: the name and the version.
Version,
/// `--run`, `--script`, `--install-update` or `--play`: the Python launcher does these until
/// the Rust runner exists.
/// `--run`: the Rust runner.
Run,
/// `--play`: the Rust runner's play role.
Play,
/// `--script` or `--install-update`: the Python launcher does these.
Python,
/// Anything else: the window.
Launcher,
@@ -32,18 +35,18 @@ pub enum Route {
/// counts anywhere, as in the Python `main`.
pub fn route<S: AsRef<OsStr>>(args: &[S]) -> Route {
let is = |arg: &S, flag: &str| arg.as_ref() == OsStr::new(flag);
let leading = ["--run", "--script", "--install-update"];
if args
.first()
.is_some_and(|first| leading.iter().any(|flag| is(first, flag)))
{
return Route::Python;
match args.first() {
Some(first) if is(first, "--run") => return Route::Run,
Some(first) if is(first, "--script") || is(first, "--install-update") => {
return Route::Python;
}
_ => {}
}
if args.iter().any(|arg| is(arg, "--version")) {
return Route::Version;
}
if args.iter().any(|arg| is(arg, "--play")) {
return Route::Python;
return Route::Play;
}
Route::Launcher
}
+9 -5
View File
@@ -16,11 +16,15 @@ fn walk(root: impl AsRef<Path>) -> Vec<PathBuf> {
}
#[test]
fn core_never_mentions_iced() {
let root = concat!(env!("CARGO_MANIFEST_DIR"), "/src/core");
for entry in walk(root) {
let text = std::fs::read_to_string(&entry).unwrap();
assert!(!text.contains("iced"), "{entry:?} imports iced");
fn core_and_runner_never_mention_iced() {
for root in [
concat!(env!("CARGO_MANIFEST_DIR"), "/src/core"),
concat!(env!("CARGO_MANIFEST_DIR"), "/src/runner"),
] {
for entry in walk(root) {
let text = std::fs::read_to_string(&entry).unwrap();
assert!(!text.contains("iced"), "{entry:?} imports iced");
}
}
}
+29 -6
View File
@@ -18,9 +18,7 @@ fn arguments_route_to_python() {
let none: [&str; 0] = [];
assert_eq!(route(&none), Route::Launcher);
assert_eq!(route(&["--version"]), Route::Version);
// The Python roles: what follows `--run`, `--script` and `--install-update` is theirs.
assert_eq!(route(&["--run"]), Route::Python);
assert_eq!(route(&["--run", "--fps", "60"]), Route::Python);
// The Python roles: what follows `--script` and `--install-update` is theirs.
assert_eq!(
route(&["--script", "prepare.py", "--version"]),
Route::Python
@@ -29,14 +27,39 @@ fn arguments_route_to_python() {
route(&["--install-update", "a.zip", "C:\\Port"]),
Route::Python
);
// `--play` counts anywhere, as in the Python `main`.
assert_eq!(route(&["--play"]), Route::Python);
assert_eq!(route(&["--fullscreen", "--play"]), Route::Python);
// Anything else opens the window; the Python ignores unknown arguments too.
assert_eq!(route(&["--fullscreen"]), Route::Launcher);
assert_eq!(route(&["play"]), Route::Launcher);
}
#[test]
fn run_and_play_are_their_own_routes() {
assert_eq!(route(&["--run"]), Route::Run);
assert_eq!(route(&["--run", "--timeout", "5"]), Route::Run);
// `--play` counts anywhere, as in the Python `main`.
assert_eq!(route(&["--play"]), Route::Play);
assert_eq!(route(&["--fullscreen", "--play"]), Route::Play);
assert_eq!(route(&["--script", "x.py"]), Route::Python);
assert_eq!(route(&["--install-update", "--run"]), Route::Python);
}
#[test]
fn run_without_a_probe_fails_with_the_python_message() {
let dir = tempfile::tempdir().unwrap();
let out = std::process::Command::new(LAUNCHER)
.arg("--run")
.env("SPARTAN_PORT_DIR", dir.path())
.output()
.unwrap();
assert_eq!(out.status.code(), Some(1));
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("spartanport-probe"), "{stdout}");
assert!(
stdout.contains("not found: build it with build.sh"),
"{stdout}"
);
}
/// The launcher program cargo built for these tests.
const LAUNCHER: &str = env!("CARGO_BIN_EXE_spartanport-launcher");