Skip to content

Commit cafb0d8

Browse files
committed
Use spacetimedb-standalone to get schema for spacetime generate
1 parent ae85f64 commit cafb0d8

File tree

19 files changed

+207
-170
lines changed

19 files changed

+207
-170
lines changed

Cargo.lock

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ toml_edit.workspace = true
7171
tracing = { workspace = true, features = ["release_max_level_off"] }
7272
walkdir.workspace = true
7373
wasmbin.workspace = true
74-
wasmtime.workspace = true
7574
webbrowser.workspace = true
7675
clap-markdown.workspace = true
7776

crates/cli/examples/regen-csharp-moduledef.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use fs_err as fs;
55
use regex::Regex;
66
use spacetimedb_cli::generate::{csharp, generate};
77
use spacetimedb_lib::{RawModuleDef, RawModuleDefV8};
8+
use spacetimedb_schema::def::ModuleDef;
89
use std::path::Path;
910
use std::sync::OnceLock;
1011

@@ -30,8 +31,9 @@ fn main() -> anyhow::Result<()> {
3031
fs::remove_dir_all(dir)?;
3132
fs::create_dir(dir)?;
3233

34+
let module: ModuleDef = module.try_into()?;
3335
generate(
34-
RawModuleDef::V8BackCompat(module),
36+
&module,
3537
&csharp::Csharp {
3638
namespace: "SpacetimeDB.Internal",
3739
},

crates/cli/src/subcommands/generate/mod.rs

+29-124
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,17 @@ use anyhow::Context;
44
use clap::parser::ValueSource;
55
use clap::Arg;
66
use clap::ArgAction::Set;
7-
use core::mem;
87
use fs_err as fs;
98
use spacetimedb_lib::de::serde::DeserializeWrapper;
10-
use spacetimedb_lib::{bsatn, RawModuleDefV8};
11-
use spacetimedb_lib::{RawModuleDef, MODULE_ABI_MAJOR_VERSION};
12-
use spacetimedb_primitives::errno;
9+
use spacetimedb_lib::{sats, RawModuleDef};
1310
use spacetimedb_schema;
1411
use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef};
1512
use spacetimedb_schema::identifier::Identifier;
1613
use std::path::{Path, PathBuf};
17-
use wasmtime::{Caller, StoreContextMut};
14+
use std::process::{Command, Stdio};
1815

1916
use crate::generate::util::iter_reducers;
20-
use crate::util::y_or_n;
17+
use crate::util::{resolve_sibling_binary, y_or_n};
2118
use crate::Config;
2219
use crate::{build, common_args};
2320
use clap::builder::PossibleValue;
@@ -98,6 +95,15 @@ pub fn cli() -> clap::Command {
9895
}
9996

10097
pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()> {
98+
exec_ex(config, args, extract_descriptions).await
99+
}
100+
101+
/// Like `exec`, but lets you specify a custom a function to extract a schema from a file.
102+
pub async fn exec_ex(
103+
config: Config,
104+
args: &clap::ArgMatches,
105+
extract_descriptions: ExtractDescriptions,
106+
) -> anyhow::Result<()> {
101107
let project_path = args.get_one::<PathBuf>("project_path").unwrap();
102108
let wasm_file = args.get_one::<PathBuf>("wasm_file").cloned();
103109
let json_module = args.get_many::<PathBuf>("json_module");
@@ -111,13 +117,13 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
111117
return Err(anyhow::anyhow!("--namespace is only supported with --lang csharp"));
112118
}
113119

114-
let module = if let Some(mut json_module) = json_module {
115-
let DeserializeWrapper(module) = if let Some(path) = json_module.next() {
120+
let module: ModuleDef = if let Some(mut json_module) = json_module {
121+
let DeserializeWrapper::<RawModuleDef>(module) = if let Some(path) = json_module.next() {
116122
serde_json::from_slice(&fs::read(path)?)?
117123
} else {
118124
serde_json::from_reader(std::io::stdin().lock())?
119125
};
120-
module
126+
module.try_into()?
121127
} else {
122128
let wasm_path = if let Some(path) = wasm_file {
123129
println!("Skipping build. Instead we are inspecting {}", path.display());
@@ -127,10 +133,8 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
127133
};
128134
let spinner = indicatif::ProgressBar::new_spinner();
129135
spinner.enable_steady_tick(std::time::Duration::from_millis(60));
130-
spinner.set_message("Compiling wasm...");
131-
let module = compile_wasm(&wasm_path)?;
132136
spinner.set_message("Extracting schema from wasm...");
133-
extract_descriptions_from_module(module)?
137+
extract_descriptions(&wasm_path).context("could not extract schema")?
134138
};
135139

136140
fs::create_dir_all(out_dir)?;
@@ -147,7 +151,7 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
147151
Language::TypeScript => &typescript::TypeScript,
148152
};
149153

150-
for (fname, code) in generate(module, lang)? {
154+
for (fname, code) in generate(&module, lang)? {
151155
let fname = Path::new(&fname);
152156
// If a generator asks for a file in a subdirectory, create the subdirectory first.
153157
if let Some(parent) = fname.parent().filter(|p| !p.as_os_str().is_empty()) {
@@ -230,8 +234,7 @@ impl clap::ValueEnum for Language {
230234
}
231235
}
232236

233-
pub fn generate(module: RawModuleDef, lang: &dyn Lang) -> anyhow::Result<Vec<(String, String)>> {
234-
let module = &ModuleDef::try_from(module)?;
237+
pub fn generate(module: &ModuleDef, lang: &dyn Lang) -> anyhow::Result<Vec<(String, String)>> {
235238
Ok(itertools::chain!(
236239
module
237240
.tables()
@@ -266,113 +269,15 @@ pub trait Lang {
266269
Self: Sized;
267270
}
268271

269-
pub fn extract_descriptions(wasm_file: &Path) -> anyhow::Result<RawModuleDef> {
270-
let module = compile_wasm(wasm_file)?;
271-
extract_descriptions_from_module(module)
272-
}
273-
274-
fn compile_wasm(wasm_file: &Path) -> anyhow::Result<wasmtime::Module> {
275-
wasmtime::Module::from_file(&wasmtime::Engine::default(), wasm_file)
276-
}
277-
278-
fn extract_descriptions_from_module(module: wasmtime::Module) -> anyhow::Result<RawModuleDef> {
279-
let engine = module.engine();
280-
let ctx = WasmCtx {
281-
mem: None,
282-
sink: Vec::new(),
283-
};
284-
let mut store = wasmtime::Store::new(engine, ctx);
285-
let mut linker = wasmtime::Linker::new(engine);
286-
linker.allow_shadowing(true).define_unknown_imports_as_traps(&module)?;
287-
let module_name = &*format!("spacetime_{MODULE_ABI_MAJOR_VERSION}.0");
288-
linker.func_wrap(
289-
module_name,
290-
"console_log",
291-
|mut caller: Caller<'_, WasmCtx>,
292-
_level: u32,
293-
_target_ptr: u32,
294-
_target_len: u32,
295-
_filename_ptr: u32,
296-
_filename_len: u32,
297-
_line_number: u32,
298-
message_ptr: u32,
299-
message_len: u32| {
300-
let (mem, _) = WasmCtx::mem_env(&mut caller);
301-
let slice = deref_slice(mem, message_ptr, message_len).unwrap();
302-
println!("from wasm: {}", String::from_utf8_lossy(slice));
303-
},
304-
)?;
305-
linker.func_wrap(module_name, "bytes_sink_write", WasmCtx::bytes_sink_write)?;
306-
let instance = linker.instantiate(&mut store, &module)?;
307-
let memory = instance.get_memory(&mut store, "memory").context("no memory export")?;
308-
store.data_mut().mem = Some(memory);
309-
310-
let mut preinits = instance
311-
.exports(&mut store)
312-
.filter_map(|exp| Some((exp.name().strip_prefix("__preinit__")?.to_owned(), exp.into_func()?)))
313-
.collect::<Vec<_>>();
314-
preinits.sort_by(|(a, _), (b, _)| a.cmp(b));
315-
for (_, func) in preinits {
316-
func.typed(&store)?.call(&mut store, ())?
317-
}
318-
let module: RawModuleDef = match instance.get_func(&mut store, "__describe_module__") {
319-
Some(f) => {
320-
store.data_mut().sink = Vec::new();
321-
f.typed::<u32, ()>(&store)?.call(&mut store, 1)?;
322-
let buf = mem::take(&mut store.data_mut().sink);
323-
bsatn::from_slice(&buf)?
324-
}
325-
// TODO: shouldn't we return an error here?
326-
None => RawModuleDef::V8BackCompat(RawModuleDefV8::default()),
327-
};
328-
Ok(module)
329-
}
330-
331-
struct WasmCtx {
332-
mem: Option<wasmtime::Memory>,
333-
sink: Vec<u8>,
334-
}
335-
336-
fn deref_slice(mem: &[u8], offset: u32, len: u32) -> anyhow::Result<&[u8]> {
337-
anyhow::ensure!(offset != 0, "ptr is null");
338-
mem.get(offset as usize..)
339-
.and_then(|s| s.get(..len as usize))
340-
.context("pointer out of bounds")
341-
}
342-
343-
fn read_u32(mem: &[u8], offset: u32) -> anyhow::Result<u32> {
344-
Ok(u32::from_le_bytes(deref_slice(mem, offset, 4)?.try_into().unwrap()))
345-
}
346-
347-
impl WasmCtx {
348-
pub fn get_mem(&self) -> wasmtime::Memory {
349-
self.mem.expect("Initialized memory")
350-
}
351-
352-
fn mem_env<'a>(ctx: impl Into<StoreContextMut<'a, Self>>) -> (&'a mut [u8], &'a mut Self) {
353-
let ctx = ctx.into();
354-
let mem = ctx.data().get_mem();
355-
mem.data_and_store_mut(ctx)
356-
}
357-
358-
pub fn bytes_sink_write(
359-
mut caller: Caller<'_, Self>,
360-
sink_handle: u32,
361-
buffer_ptr: u32,
362-
buffer_len_ptr: u32,
363-
) -> anyhow::Result<u32> {
364-
if sink_handle != 1 {
365-
return Ok(errno::NO_SUCH_BYTES.get().into());
366-
}
367-
368-
let (mem, env) = Self::mem_env(&mut caller);
369-
370-
// Read `buffer_len`, i.e., the capacity of `buffer` pointed to by `buffer_ptr`.
371-
let buffer_len = read_u32(mem, buffer_len_ptr)?;
372-
// Write `buffer` to `sink`.
373-
let buffer = deref_slice(mem, buffer_ptr, buffer_len)?;
374-
env.sink.extend(buffer);
375-
376-
Ok(0)
377-
}
272+
pub type ExtractDescriptions = fn(&Path) -> anyhow::Result<ModuleDef>;
273+
fn extract_descriptions(wasm_file: &Path) -> anyhow::Result<ModuleDef> {
274+
let bin_path = resolve_sibling_binary("spacetimedb-standalone")?;
275+
let child = Command::new(&bin_path)
276+
.arg("extract-schema")
277+
.arg(wasm_file)
278+
.stdout(Stdio::piped())
279+
.spawn()
280+
.with_context(|| format!("failed to spawn {}", bin_path.display()))?;
281+
let sats::serde::SerdeWrapper::<RawModuleDef>(module) = serde_json::from_reader(child.stdout.unwrap())?;
282+
Ok(module.try_into()?)
378283
}

crates/cli/src/subcommands/start.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use anyhow::Context;
66
use clap::{Arg, ArgMatches};
77
use spacetimedb_paths::SpacetimePaths;
88

9+
use crate::util::resolve_sibling_binary;
10+
911
pub fn cli() -> clap::Command {
1012
clap::Command::new("start")
1113
.about("Start a local SpacetimeDB instance")
@@ -45,12 +47,7 @@ pub async fn exec(paths: &SpacetimePaths, args: &ArgMatches) -> anyhow::Result<E
4547
Edition::Standalone => "spacetimedb-standalone",
4648
Edition::Cloud => "spacetimedb-cloud",
4749
};
48-
let resolved_exe = std::env::current_exe().context("could not retrieve current exe")?;
49-
let bin_path = resolved_exe
50-
.parent()
51-
.unwrap()
52-
.join(bin_name)
53-
.with_extension(std::env::consts::EXE_EXTENSION);
50+
let bin_path = resolve_sibling_binary(bin_name)?;
5451
let mut cmd = Command::new(&bin_path);
5552
cmd.arg("start")
5653
.arg("--data-dir")

crates/cli/src/util.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use spacetimedb_auth::identity::{IncomingClaims, SpacetimeIdentityClaims};
55
use spacetimedb_client_api_messages::name::GetNamesResponse;
66
use spacetimedb_lib::Identity;
77
use std::io::Write;
8-
use std::path::Path;
8+
use std::path::{Path, PathBuf};
99

1010
use crate::config::Config;
1111
use crate::login::{spacetimedb_login_force, DEFAULT_AUTH_HOST};
@@ -320,3 +320,13 @@ pub async fn get_login_token_or_log_in(
320320
spacetimedb_login_force(config, &host, true).await
321321
}
322322
}
323+
324+
pub fn resolve_sibling_binary(bin_name: &str) -> anyhow::Result<PathBuf> {
325+
let resolved_exe = std::env::current_exe().context("could not retrieve current exe")?;
326+
let bin_path = resolved_exe
327+
.parent()
328+
.unwrap()
329+
.join(bin_name)
330+
.with_extension(std::env::consts::EXE_EXTENSION);
331+
Ok(bin_path)
332+
}

crates/cli/tests/codegen.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
use spacetimedb_cli::generate::{csharp::Csharp, extract_descriptions, generate, rust::Rust, typescript::TypeScript};
1+
use spacetimedb_cli::generate::{csharp::Csharp, generate, rust::Rust, typescript::TypeScript};
22
use spacetimedb_data_structures::map::HashMap;
3+
use spacetimedb_schema::def::ModuleDef;
34
use spacetimedb_testing::modules::{CompilationMode, CompiledModule};
4-
use std::path::Path;
55
use std::sync::OnceLock;
66

7-
fn compiled_module() -> &'static Path {
8-
static COMPILED_MODULE: OnceLock<CompiledModule> = OnceLock::new();
7+
fn compiled_module() -> &'static ModuleDef {
8+
static COMPILED_MODULE: OnceLock<ModuleDef> = OnceLock::new();
99
COMPILED_MODULE
10-
.get_or_init(|| CompiledModule::compile("module-test", CompilationMode::Debug))
11-
.path()
10+
.get_or_init(|| CompiledModule::compile("module-test", CompilationMode::Debug).extract_schema_blocking())
1211
}
1312

1413
macro_rules! declare_tests {
1514
($($name:ident => $lang:expr,)*) => ($(
1615
#[test]
1716
fn $name() {
18-
let module = extract_descriptions(compiled_module()).unwrap();
17+
let module = compiled_module();
1918
let outfiles: HashMap<_, _> = generate(module, &$lang)
2019
.unwrap()
2120
.into_iter()

0 commit comments

Comments
 (0)