mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Share mod-owned generation and move dispatch tests to its crate
Delegate launcher trait, event and helper generation to horizon-mod-api-gen. Keep the old-source compatibility path, move dispatch fixtures out of xtask into an unpublished workspace member, and select launcher checks when shared generator code changes.
This commit is contained in:
1 parent
8f6fe50c47
commit
616e855940
16 files changed
+245
-255
No files matched your search
Generated
+8
-1
@@ -1585,6 +1585,14 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-mod-api-gen-tests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"horizon-mod-api-gen",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-mods"
|
||||
version = "0.1.0"
|
||||
@@ -5165,7 +5173,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"horizon-mod-api-gen",
|
||||
"horizon-theme",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -32,6 +32,7 @@ members = [
|
||||
"crates/horizon-relay",
|
||||
"crates/horizon-mods",
|
||||
"crates/horizon-mod-api-gen",
|
||||
"crates/horizon-mod-api-gen/dispatch-tests",
|
||||
"crates/cargo-horizon-mod",
|
||||
"crates/horizon-port-pack",
|
||||
"crates/horizon-shaders",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "horizon-mod-api-gen-tests"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
description = "Generated mod API dispatch contract tests"
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
|
||||
[build-dependencies]
|
||||
horizon-mod-api-gen = { path = ".." }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,5 +1,6 @@
|
||||
fn main() {
|
||||
let root = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("..");
|
||||
let root =
|
||||
std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("../../..");
|
||||
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
|
||||
horizon_mod_api_gen::generate_test_hooks(&root, &out_dir)
|
||||
.expect("generating dispatch test fixture");
|
||||
@@ -1,3 +1,4 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Generation of a mod's own trait methods, events and dispatch helpers.
|
||||
use super::EventLifetimes;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::visit_mut::VisitMut;
|
||||
|
||||
/// A trait declaration supplied to the staged game's build script after horizon-mixins has run.
|
||||
pub struct TraitInjection {
|
||||
name: syn::Ident,
|
||||
/// The bindings' struct, as each catalog hook's (`InjectExampleTickEvent`).
|
||||
event: syn::Ident,
|
||||
parameters: Vec<(syn::Ident, syn::Type)>,
|
||||
contract: TraitContract,
|
||||
}
|
||||
|
||||
/// What a trait method may do with its event.
|
||||
pub enum TraitContract {
|
||||
/// HEAD: observe and change `&mut` bindings.
|
||||
Listen,
|
||||
/// Cancellable HEAD on a unit target, where `Flow::Cancel` returns early.
|
||||
Cancel,
|
||||
/// RETURN, MODIFY_ARG or MODIFY_EXPRESSION_VALUE: the ident names the `$value` binding,
|
||||
/// which is moved into the event and returned after the call.
|
||||
Value(syn::Ident),
|
||||
}
|
||||
|
||||
struct EventFields {
|
||||
lifetimes: EventLifetimes,
|
||||
types: Vec<syn::Type>,
|
||||
scalars: Vec<bool>,
|
||||
}
|
||||
|
||||
impl TraitInjection {
|
||||
/// Prepare a mod-owned trait method from parsed bindings and its return contract.
|
||||
pub fn new(
|
||||
name: syn::Ident,
|
||||
parameters: Vec<(syn::Ident, syn::Type)>,
|
||||
contract: TraitContract,
|
||||
) -> Self {
|
||||
let event = format_ident!(
|
||||
"{}Event",
|
||||
name.to_string()
|
||||
.split('_')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
chars.next().map_or(String::new(), |first| {
|
||||
first.to_uppercase().chain(chars).collect()
|
||||
})
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
Self {
|
||||
name,
|
||||
event,
|
||||
parameters,
|
||||
contract,
|
||||
}
|
||||
}
|
||||
|
||||
/// The declarations supplied to the staged game's build script.
|
||||
pub fn input(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"name": self.name.to_string(),
|
||||
"event": self.event.to_string(),
|
||||
"declaration": self.declaration(),
|
||||
"event_declaration": self.event(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Dispatch only to this injection’s owning mod, preserving its write-back contract.
|
||||
pub fn helper(&self, owner: usize) -> String {
|
||||
let name = &self.name;
|
||||
let event = &self.event;
|
||||
let params = self.parameters.iter().map(|(n, t)| quote!(#n: #t));
|
||||
let scalars = self.fields().scalars;
|
||||
let inits = self
|
||||
.parameters
|
||||
.iter()
|
||||
.zip(&scalars)
|
||||
.map(
|
||||
|((n, _), scalar)| {
|
||||
if *scalar { quote!(#n: *#n) } else { quote!(#n) }
|
||||
},
|
||||
);
|
||||
let write_back = self
|
||||
.parameters
|
||||
.iter()
|
||||
.zip(&scalars)
|
||||
.filter(|(_, scalar)| **scalar)
|
||||
.map(|((n, _), _)| quote!(*#n = event.#n;));
|
||||
let call = quote! {
|
||||
let mut event = crate::mod_api::#event { #(#inits),* };
|
||||
let _flow = owner.#name(&mut event);
|
||||
#(#write_back)*
|
||||
};
|
||||
// Before the mods are installed (an injection into code `main_with` runs first), or
|
||||
// with fewer mods than the build had, the injection does nothing or passes the value on.
|
||||
let (output, result, absent) = match &self.contract {
|
||||
TraitContract::Listen => (quote!(), call, quote!()),
|
||||
TraitContract::Cancel => (
|
||||
quote!(-> core::ops::ControlFlow<()>),
|
||||
quote! {
|
||||
#call
|
||||
if _flow == crate::mod_api::Flow::Cancel {
|
||||
return core::ops::ControlFlow::Break(());
|
||||
}
|
||||
core::ops::ControlFlow::Continue(())
|
||||
},
|
||||
quote!(core::ops::ControlFlow::Continue(())),
|
||||
),
|
||||
TraitContract::Value(value) => {
|
||||
let ty = self
|
||||
.parameters
|
||||
.iter()
|
||||
.find(|(n, _)| n == value)
|
||||
.map(|(_, t)| t);
|
||||
(quote!(-> #ty), quote!(#call event.#value), quote!(#value))
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
fn horizon_trait_dispatch(#(#params),*) #output {
|
||||
let Some(owner) = crate::mod_api::mods().get(#owner) else {
|
||||
return #absent;
|
||||
};
|
||||
#result
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn declaration(&self) -> String {
|
||||
let name = &self.name;
|
||||
let event = &self.event;
|
||||
let (output, default) = match self.contract {
|
||||
TraitContract::Cancel => (quote!(-> Flow), quote!(Flow::Continue)),
|
||||
TraitContract::Listen | TraitContract::Value(_) => (quote!(), quote!()),
|
||||
};
|
||||
let lifetimes = self.fields().lifetimes;
|
||||
let elided = match (lifetimes.a, lifetimes.b) {
|
||||
(_, true) => quote!(<'_, '_>),
|
||||
(true, false) => quote!(<'_>),
|
||||
(false, false) => quote!(),
|
||||
};
|
||||
quote!(fn #name(&self, _: &mut #event #elided) #output { #default }).to_string()
|
||||
}
|
||||
|
||||
/// Which lifetimes the event takes, its fields' types and which of them are scalars.
|
||||
fn fields(&self) -> EventFields {
|
||||
let mut lifetimes = EventLifetimes::default();
|
||||
let (types, scalars) = self
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|(name, ty)| match &self.contract {
|
||||
TraitContract::Value(value) if name == value => {
|
||||
let mut field = ty.clone();
|
||||
lifetimes.visit_type_mut(&mut field);
|
||||
(field, false)
|
||||
}
|
||||
_ => lifetimes.field(ty),
|
||||
})
|
||||
.unzip();
|
||||
EventFields {
|
||||
lifetimes,
|
||||
types,
|
||||
scalars,
|
||||
}
|
||||
}
|
||||
|
||||
fn event(&self) -> String {
|
||||
let event = &self.event;
|
||||
let name = self.name.to_string();
|
||||
let fields = self.fields();
|
||||
let lifetimes = fields.lifetimes;
|
||||
let types = fields.types;
|
||||
let generics = match (lifetimes.a, lifetimes.b) {
|
||||
(_, true) => quote!(<'a, 'b>),
|
||||
(true, false) => quote!(<'a>),
|
||||
(false, false) => quote!(),
|
||||
};
|
||||
let names = self.parameters.iter().map(|(n, _)| n);
|
||||
let doc = format!(
|
||||
" What [`Hooks::{name}`] is told. Fields may be added: the struct is `#[non_exhaustive]`."
|
||||
);
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[non_exhaustive]
|
||||
pub struct #event #generics { #(pub #names: #types),* }
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
@@ -1146,6 +1146,8 @@ enum FillMode {
|
||||
|
||||
mod build;
|
||||
pub use build::{generate, inject};
|
||||
mod injection;
|
||||
pub use injection::{TraitContract, TraitInjection};
|
||||
#[cfg(test)]
|
||||
mod injection_tests;
|
||||
|
||||
|
||||
@@ -2837,6 +2837,13 @@ ordinary mixin helpers in other crates continue to work as before. The generated
|
||||
ordinary helper of that mod: priority descending, mod id then injection id. It is not automatically
|
||||
ordered after other mixins. Catalog hooks retain the mixin-before-trait order below.
|
||||
|
||||
The launcher and catalog build scripts share `horizon-mod-api-gen` for trait, event and helper generation.
|
||||
For game sources with that build dependency, the launcher stages declarations in
|
||||
`.horizon-mod-api-injections.json`; Cargo consumes them through `HORIZON_MOD_API_INJECTIONS` without
|
||||
editing generated Rust in the source tree. The declarations stay with cached mixin output. Older released
|
||||
sources without that build dependency still receive the same declarations in their committed generated
|
||||
trait. Neither path requires rustfmt during a player build.
|
||||
|
||||
A linked trait mod implements `horizon_client::mod_api::Hooks`. Mixin injections run first.
|
||||
An injection that returns early skips trait mods. A claimed cancellation also skips trait mods.
|
||||
Otherwise, trait mods run in their supplied order. The first `Cancel` stops dispatch.
|
||||
|
||||
Generated
-1
@@ -2700,7 +2700,6 @@ dependencies = [
|
||||
"oo7",
|
||||
"proc-macro2",
|
||||
"pulldown-cmark",
|
||||
"quote",
|
||||
"raw-window-handle",
|
||||
"security-framework 3.7.0",
|
||||
"semver",
|
||||
|
||||
+2
-3
@@ -17,9 +17,8 @@ gpui_platform = { package = "gpui-fast-platform", version = "0.1.13", features =
|
||||
ureq = { version = "2", features = ["json", "tls"], default-features = false }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
# Validate and render injected trait signatures before they reach rustc.
|
||||
syn = { version = "2", features = ["full", "visit-mut"] }
|
||||
quote = "1"
|
||||
# Validate trait injection bindings before passing them to the shared generator.
|
||||
syn = { version = "2", features = ["full"] }
|
||||
# Checks the client jar and the version file found in other launchers' folders (sources.rs).
|
||||
sha1 = "0.10"
|
||||
# The mod browser checks Modrinth's SHA-512 before a pack reaches the game folder (browser/download.rs).
|
||||
|
||||
@@ -1,100 +1,11 @@
|
||||
//! Trait-backed injections, generated in the incoming copy and staged game.
|
||||
//! Trait-backed injections, prepared as build-script input alongside the staged game.
|
||||
use super::{api, Bundle, MANIFEST};
|
||||
use quote::{format_ident, quote};
|
||||
use horizon_mod_api_gen::{TraitContract as Contract, TraitInjection as Injection};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
path::Path,
|
||||
};
|
||||
use syn::visit_mut::VisitMut;
|
||||
|
||||
/// A trait declaration to add to the staged game after horizon-mixins has run.
|
||||
pub(super) struct Injection {
|
||||
name: syn::Ident,
|
||||
/// The bindings' struct, as each catalog hook's (`InjectExampleTickEvent`).
|
||||
event: syn::Ident,
|
||||
parameters: Vec<(syn::Ident, syn::Type)>,
|
||||
contract: Contract,
|
||||
}
|
||||
|
||||
/// What a trait method may do with its event.
|
||||
enum Contract {
|
||||
/// HEAD: observe and change `&mut` bindings.
|
||||
Listen,
|
||||
/// Cancellable HEAD on a unit target, where `Flow::Cancel` returns early.
|
||||
Cancel,
|
||||
/// RETURN, MODIFY_ARG or MODIFY_EXPRESSION_VALUE: the ident names the `$value` binding,
|
||||
/// which is moved into the event and returned after the call.
|
||||
Value(syn::Ident),
|
||||
}
|
||||
|
||||
/// Turns a binding's type into its event field's: a `&mut` primitive scalar becomes the value,
|
||||
/// written back after the call; every elided reference lifetime becomes `'a`, or `'b` inside a
|
||||
/// `&mut`, which can't shorten it. A copy of xtask's rule for catalog hooks
|
||||
/// (`horizon-mod-api-gen/src/lib.rs`, `EventLifetimes`); both test the same types.
|
||||
#[derive(Default)]
|
||||
struct EventLifetimes {
|
||||
a: bool,
|
||||
b: bool,
|
||||
in_mut: bool,
|
||||
}
|
||||
|
||||
impl EventLifetimes {
|
||||
/// The field's type, and whether it is a scalar copied in and written back.
|
||||
fn field(&mut self, ty: &syn::Type) -> (syn::Type, bool) {
|
||||
let scalar = match ty {
|
||||
syn::Type::Reference(reference) if reference.mutability.is_some() => {
|
||||
match &*reference.elem {
|
||||
syn::Type::Path(path)
|
||||
if path.qself.is_none()
|
||||
&& path.path.get_ident().is_some_and(|ident| {
|
||||
[
|
||||
"bool", "char", "i8", "i16", "i32", "i64", "i128", "isize",
|
||||
"u8", "u16", "u32", "u64", "u128", "usize", "f32", "f64",
|
||||
]
|
||||
.iter()
|
||||
.any(|scalar| ident == scalar)
|
||||
}) =>
|
||||
{
|
||||
Some(&*reference.elem)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(scalar) = scalar {
|
||||
return (scalar.clone(), true);
|
||||
}
|
||||
let mut field = ty.clone();
|
||||
self.visit_type_mut(&mut field);
|
||||
(field, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitMut for EventLifetimes {
|
||||
fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) {
|
||||
if reference.lifetime.is_none() {
|
||||
reference.lifetime = Some(syn::parse_quote!('_));
|
||||
}
|
||||
let outer = self.in_mut;
|
||||
self.visit_lifetime_mut(reference.lifetime.as_mut().unwrap());
|
||||
self.in_mut |= reference.mutability.is_some();
|
||||
self.visit_type_mut(&mut reference.elem);
|
||||
self.in_mut = outer;
|
||||
}
|
||||
fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) {
|
||||
if lifetime.ident == "_" {
|
||||
*lifetime = if self.in_mut {
|
||||
self.b = true;
|
||||
syn::parse_quote!('b)
|
||||
} else {
|
||||
self.a = true;
|
||||
syn::parse_quote!('a)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace trait injections with ordinary mixin helpers, leaving ordinary injections unchanged.
|
||||
pub(super) fn prepare(mods: &[Bundle], source: &Path) -> Result<Vec<Injection>, String> {
|
||||
@@ -223,23 +134,7 @@ pub(super) fn prepare(mods: &[Bundle], source: &Path) -> Result<Vec<Injection>,
|
||||
));
|
||||
}
|
||||
};
|
||||
let event = format_ident!(
|
||||
"{}Event",
|
||||
name.split('_')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
chars.next().map_or(String::new(), |first| {
|
||||
first.to_uppercase().chain(chars).collect()
|
||||
})
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
let injection = Injection {
|
||||
name: ident,
|
||||
event,
|
||||
parameters,
|
||||
contract,
|
||||
};
|
||||
let injection = Injection::new(ident, parameters, contract);
|
||||
let helper_path = format!("horizon-trait-injection-{index}.rs");
|
||||
let helper_file = bundle.dir.join(&helper_path);
|
||||
if helper_file.exists() {
|
||||
@@ -263,138 +158,6 @@ pub(super) fn prepare(mods: &[Bundle], source: &Path) -> Result<Vec<Injection>,
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
impl Injection {
|
||||
fn helper(&self, owner: usize) -> String {
|
||||
let name = &self.name;
|
||||
let event = &self.event;
|
||||
let params = self.parameters.iter().map(|(n, t)| quote!(#n: #t));
|
||||
let scalars = self.lifetimes().2;
|
||||
let inits = self
|
||||
.parameters
|
||||
.iter()
|
||||
.zip(&scalars)
|
||||
.map(
|
||||
|((n, _), scalar)| {
|
||||
if *scalar {
|
||||
quote!(#n: *#n)
|
||||
} else {
|
||||
quote!(#n)
|
||||
}
|
||||
},
|
||||
);
|
||||
let write_back = self
|
||||
.parameters
|
||||
.iter()
|
||||
.zip(&scalars)
|
||||
.filter(|(_, scalar)| **scalar)
|
||||
.map(|((n, _), _)| quote!(*#n = event.#n;));
|
||||
let call = quote! {
|
||||
let mut event = crate::mod_api::#event { #(#inits),* };
|
||||
let _flow = owner.#name(&mut event);
|
||||
#(#write_back)*
|
||||
};
|
||||
// Before the mods are installed (an injection into code `main_with` runs first), or
|
||||
// with fewer mods than the build had, the injection does nothing or passes the value on.
|
||||
let (output, result, absent) = match &self.contract {
|
||||
Contract::Listen => (quote!(), call, quote!()),
|
||||
Contract::Cancel => (
|
||||
quote!(-> core::ops::ControlFlow<()>),
|
||||
quote! {
|
||||
#call
|
||||
if _flow == crate::mod_api::Flow::Cancel {
|
||||
return core::ops::ControlFlow::Break(());
|
||||
}
|
||||
core::ops::ControlFlow::Continue(())
|
||||
},
|
||||
quote!(core::ops::ControlFlow::Continue(())),
|
||||
),
|
||||
Contract::Value(value) => {
|
||||
let ty = self
|
||||
.parameters
|
||||
.iter()
|
||||
.find(|(n, _)| n == value)
|
||||
.map(|(_, t)| t);
|
||||
(quote!(-> #ty), quote!(#call event.#value), quote!(#value))
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
fn horizon_trait_dispatch(#(#params),*) #output {
|
||||
let Some(owner) = crate::mod_api::mods().get(#owner) else {
|
||||
return #absent;
|
||||
};
|
||||
#result
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn declaration(&self) -> String {
|
||||
let name = &self.name;
|
||||
let event = &self.event;
|
||||
let (output, default) = match self.contract {
|
||||
Contract::Cancel => (quote!(-> Flow), quote!(Flow::Continue)),
|
||||
Contract::Listen | Contract::Value(_) => (quote!(), quote!()),
|
||||
};
|
||||
let elided = match self.lifetimes().0 {
|
||||
(_, true) => quote!(<'_, '_>),
|
||||
(true, false) => quote!(<'_>),
|
||||
(false, false) => quote!(),
|
||||
};
|
||||
quote!(fn #name(&self, _: &mut #event #elided) #output { #default }).to_string()
|
||||
}
|
||||
|
||||
/// Which lifetimes the event takes, its fields' types and which of them are scalars.
|
||||
fn lifetimes(&self) -> ((bool, bool), Vec<syn::Type>, Vec<bool>) {
|
||||
let mut lifetimes = EventLifetimes::default();
|
||||
let (types, scalars) = self
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|(name, ty)| match &self.contract {
|
||||
Contract::Value(value) if name == value => {
|
||||
let mut field = ty.clone();
|
||||
lifetimes.visit_type_mut(&mut field);
|
||||
(field, false)
|
||||
}
|
||||
_ => lifetimes.field(ty),
|
||||
})
|
||||
.unzip();
|
||||
((lifetimes.a, lifetimes.b), types, scalars)
|
||||
}
|
||||
|
||||
fn event(&self) -> String {
|
||||
let event = &self.event;
|
||||
let name = self.name.to_string();
|
||||
let (lifetimes, types, _) = self.lifetimes();
|
||||
let generics = match lifetimes {
|
||||
(_, true) => quote!(<'a, 'b>),
|
||||
(true, false) => quote!(<'a>),
|
||||
(false, false) => quote!(),
|
||||
};
|
||||
let names = self.parameters.iter().map(|(n, _)| n);
|
||||
let doc = format!(
|
||||
" What [`Hooks::{name}`] is told. Fields may be added: the struct is `#[non_exhaustive]`."
|
||||
);
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[non_exhaustive]
|
||||
pub struct #event #generics { #(pub #names: #types),* }
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Add declarations to the existing generated trait, not its dispatch implementations.
|
||||
impl Injection {
|
||||
fn input(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"name": self.name.to_string(),
|
||||
"event": self.event.to_string(),
|
||||
"declaration": self.declaration(),
|
||||
"event_declaration": self.event(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const INPUT: &str = ".horizon-mod-api-injections.json";
|
||||
pub(super) const ENV: &str = "HORIZON_MOD_API_INJECTIONS";
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class RustChangesTests(unittest.TestCase):
|
||||
with self.subTest(path=path):
|
||||
self.assertFalse(needs_launcher([path]))
|
||||
for path in ["launcher/src/main.rs", "launcher/Cargo.lock", "crates/horizon-port-pack/src/net.rs",
|
||||
"crates/horizon-mcp/src/lib.rs",
|
||||
"crates/horizon-mcp/src/lib.rs", "crates/horizon-mod-api-gen/src/injection.rs",
|
||||
"crates/horizon-theme/src/lib.rs", "Cargo.toml", "Cargo.lock",
|
||||
".github/workflows/ci.yml"]:
|
||||
with self.subTest(path=path):
|
||||
|
||||
@@ -63,6 +63,7 @@ class PrReadyTests(unittest.TestCase):
|
||||
self.assertTrue(pr_ready.launcher_rust("crates/horizon-prepare/src/lib.rs"))
|
||||
self.assertTrue(pr_ready.launcher_rust("crates/horizon-theme/src/lib.rs"))
|
||||
self.assertTrue(pr_ready.launcher_rust("crates/horizon-telemetry/src/lib.rs"))
|
||||
self.assertTrue(pr_ready.launcher_rust("crates/horizon-mod-api-gen/src/injection.rs"))
|
||||
self.assertFalse(pr_ready.launcher_rust("launcher/links.json"))
|
||||
self.assertFalse(pr_ready.launcher_rust("launcher/README.md"))
|
||||
self.assertFalse(pr_ready.launcher_rust("crates/horizon-client/src/game.rs"))
|
||||
@@ -122,6 +123,16 @@ class PrReadyTests(unittest.TestCase):
|
||||
self.assertEqual(tests.command, "cargo test --locked -p horizon-client -p horizon-client-render "
|
||||
"-p horizon-mod-api-gen --tests")
|
||||
|
||||
def test_shared_generator_edits_run_launcher_checks(self):
|
||||
with mock.patch.object(pr_ready, "affected_here", return_value=[]):
|
||||
steps = pr_ready.plan(args(quick=True), "origin/main",
|
||||
{"crates/horizon-mod-api-gen/src/injection.rs"},
|
||||
{**PACKAGES, "horizon-mod-api-gen": "crates/horizon-mod-api-gen"},
|
||||
LIBRARIES | {"horizon-mod-api-gen"}, DEPENDENCIES)
|
||||
by_name = {step.name: step for step in steps}
|
||||
for name in ("launcher rustfmt", "launcher clippy", "launcher tests"):
|
||||
self.assertIsNone(by_name[name].skip)
|
||||
|
||||
@unittest.skipUnless(POSIX_BASH, "needs a bash that is not WSL's")
|
||||
def test_the_launcher_fallback_asks_for_a_feature_the_launcher_has(self):
|
||||
"""The macOS fallback must name gpui_platform/runtime_shaders: gpui_platform is launcher/Cargo.toml's
|
||||
|
||||
@@ -71,6 +71,7 @@ LAUNCHER_PATHS = [
|
||||
"crates/horizon-bench/",
|
||||
"crates/horizon-friends/",
|
||||
"crates/horizon-mcp/",
|
||||
"crates/horizon-mod-api-gen/",
|
||||
"crates/horizon-mods/",
|
||||
"crates/horizon-port-pack/",
|
||||
"crates/horizon-prepare/",
|
||||
|
||||
@@ -8,11 +8,7 @@ description = "Repository tasks: cargo run -p xtask -- package BINARY OUTDIR | t
|
||||
|
||||
[dependencies]
|
||||
horizon-mod-api-gen = { path = "../crates/horizon-mod-api-gen" }
|
||||
serde_json = "1"
|
||||
horizon-theme = { path = "../crates/horizon-theme" }
|
||||
|
||||
[build-dependencies]
|
||||
horizon-mod-api-gen = { path = "../crates/horizon-mod-api-gen" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -7,7 +7,3 @@ pub fn run() -> Result<(), String> {
|
||||
.ok_or("run xtask from within the Horizon checkout")?;
|
||||
horizon_mod_api_gen::fill_hooks(root)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_api_tests.rs"]
|
||||
mod dispatch_tests;
|
||||
Reference in new issue
Block a user