Skip to content

Commit 01d902c

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

File tree

18 files changed

+173
-166
lines changed

18 files changed

+173
-166
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

+17-121
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;
@@ -127,11 +124,10 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
127124
};
128125
let spinner = indicatif::ProgressBar::new_spinner();
129126
spinner.enable_steady_tick(std::time::Duration::from_millis(60));
130-
spinner.set_message("Compiling wasm...");
131-
let module = compile_wasm(&wasm_path)?;
132127
spinner.set_message("Extracting schema from wasm...");
133-
extract_descriptions_from_module(module)?
128+
extract_descriptions(&wasm_path).context("could not extract schema")?
134129
};
130+
let module: ModuleDef = module.try_into()?;
135131

136132
fs::create_dir_all(out_dir)?;
137133

@@ -147,7 +143,7 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
147143
Language::TypeScript => &typescript::TypeScript,
148144
};
149145

150-
for (fname, code) in generate(module, lang)? {
146+
for (fname, code) in generate(&module, lang)? {
151147
let fname = Path::new(&fname);
152148
// If a generator asks for a file in a subdirectory, create the subdirectory first.
153149
if let Some(parent) = fname.parent().filter(|p| !p.as_os_str().is_empty()) {
@@ -230,8 +226,7 @@ impl clap::ValueEnum for Language {
230226
}
231227
}
232228

233-
pub fn generate(module: RawModuleDef, lang: &dyn Lang) -> anyhow::Result<Vec<(String, String)>> {
234-
let module = &ModuleDef::try_from(module)?;
229+
pub fn generate(module: &ModuleDef, lang: &dyn Lang) -> anyhow::Result<Vec<(String, String)>> {
235230
Ok(itertools::chain!(
236231
module
237232
.tables()
@@ -266,113 +261,14 @@ pub trait Lang {
266261
Self: Sized;
267262
}
268263

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-
}
264+
fn extract_descriptions(wasm_file: &Path) -> anyhow::Result<RawModuleDef> {
265+
let bin_path = resolve_sibling_binary("spacetimedb-standalone")?;
266+
let child = Command::new(&bin_path)
267+
.arg("extract-schema")
268+
.arg(wasm_file)
269+
.stdout(Stdio::piped())
270+
.spawn()
271+
.with_context(|| format!("failed to spawn {}", bin_path.display()))?;
272+
let sats::serde::SerdeWrapper(def) = serde_json::from_reader(child.stdout.unwrap())?;
273+
Ok(def)
378274
}

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()

crates/core/src/host/host_controller.rs

+32-10
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ use log::{info, trace, warn};
2121
use parking_lot::{Mutex, RwLock};
2222
use spacetimedb_data_structures::map::IntMap;
2323
use spacetimedb_durability::{self as durability, TxOffset};
24-
use spacetimedb_lib::hash_bytes;
24+
use spacetimedb_lib::{hash_bytes, Identity};
2525
use spacetimedb_paths::server::{ReplicaDir, ServerDataDir};
2626
use spacetimedb_paths::FromPathUnchecked;
2727
use spacetimedb_sats::hash::Hash;
28+
use spacetimedb_schema::def::ModuleDef;
2829
use std::future::Future;
2930
use std::ops::Deref;
3031
use std::sync::Arc;
@@ -96,7 +97,7 @@ struct HostRuntimes {
9697
}
9798

9899
impl HostRuntimes {
99-
fn new(data_dir: &ServerDataDir) -> Arc<Self> {
100+
fn new(data_dir: Option<&ServerDataDir>) -> Arc<Self> {
100101
let wasmtime = WasmtimeRuntime::new(data_dir);
101102
Arc::new(Self { wasmtime })
102103
}
@@ -170,7 +171,7 @@ impl HostController {
170171
program_storage,
171172
energy_monitor,
172173
durability,
173-
runtimes: HostRuntimes::new(&data_dir),
174+
runtimes: HostRuntimes::new(Some(&data_dir)),
174175
data_dir,
175176
}
176177
}
@@ -259,8 +260,8 @@ impl HostController {
259260
///
260261
/// This is not necessary during hotswap publishes,
261262
/// as the automigration planner and executor accomplish the same validity checks.
262-
pub async fn check_module_validity(&self, database: Database, program: Program) -> anyhow::Result<()> {
263-
Host::try_init_in_memory_to_check(self, database, program).await
263+
pub async fn check_module_validity(&self, database: Database, program: Program) -> anyhow::Result<Arc<ModuleInfo>> {
264+
Host::try_init_in_memory_to_check(&self.runtimes, database, program).await
264265
}
265266

266267
/// Run a computation on the [`RelationalDB`] of a [`ModuleHost`] managed by
@@ -815,12 +816,10 @@ impl Host {
815816
/// This is not necessary during hotswap publishes,
816817
/// as the automigration planner and executor accomplish the same validity checks.
817818
async fn try_init_in_memory_to_check(
818-
host_controller: &HostController,
819+
runtimes: &Arc<HostRuntimes>,
819820
database: Database,
820821
program: Program,
821-
) -> anyhow::Result<()> {
822-
let HostController { runtimes, .. } = host_controller;
823-
822+
) -> anyhow::Result<Arc<ModuleInfo>> {
824823
// Even in-memory databases acquire a lockfile.
825824
// Grab a tempdir to put that lockfile in.
826825
let phony_replica_dir = TempDir::with_prefix("spacetimedb-publish-in-memory-check")
@@ -858,7 +857,7 @@ impl Host {
858857
Result::from(call_result)?;
859858
}
860859

861-
Ok(())
860+
Ok(launched.module_host.info)
862861
}
863862

864863
/// Attempt to replace this [`Host`]'s [`ModuleHost`] with a new one running
@@ -946,3 +945,26 @@ async fn metric_reporter(replica_ctx: Arc<ReplicaContext>) {
946945
tokio::time::sleep(STORAGE_METERING_INTERVAL).await;
947946
}
948947
}
948+
949+
/// Extracts the schema from a given module.
950+
///
951+
/// Spins up a dummy host and returns the `ModuleDef` that it extracts.
952+
pub async fn extract_schema(program_bytes: Box<[u8]>, host_type: HostType) -> anyhow::Result<ModuleDef> {
953+
let owner_identity = Identity::from_u256(0xdcba_u32.into());
954+
let database_identity = Identity::from_u256(0xabcd_u32.into());
955+
let program = Program::from_bytes(program_bytes);
956+
957+
let database = Database {
958+
id: 0,
959+
database_identity,
960+
owner_identity,
961+
host_type,
962+
initial_program: program.hash,
963+
};
964+
965+
let runtimes = HostRuntimes::new(None);
966+
let module_info = Host::try_init_in_memory_to_check(&runtimes, database, program).await?;
967+
let module_info = Arc::into_inner(module_info).unwrap();
968+
969+
Ok(module_info.module_def)
970+
}

0 commit comments

Comments
 (0)