Skip to content

Commit a3f59eb

Browse files
committed
Use spacetimedb-standalone to get schema for spacetime generate
1 parent 020d64c commit a3f59eb

File tree

19 files changed

+210
-177
lines changed

19 files changed

+210
-177
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/src/subcommands/generate.rs

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
#![warn(clippy::uninlined_format_args)]
22

3+
use anyhow::Context;
34
use clap::parser::ValueSource;
45
use clap::Arg;
56
use clap::ArgAction::Set;
67
use fs_err as fs;
7-
use spacetimedb_codegen::{
8-
compile_wasm, extract_descriptions_from_module, generate, Csharp, Lang, Rust, TypeScript, AUTO_GENERATED_PREFIX,
9-
};
8+
use spacetimedb_codegen::{generate, Csharp, Lang, Rust, TypeScript, AUTO_GENERATED_PREFIX};
109
use spacetimedb_lib::de::serde::DeserializeWrapper;
10+
use spacetimedb_lib::{sats, RawModuleDef};
1111
use spacetimedb_schema;
1212
use spacetimedb_schema::def::ModuleDef;
1313
use std::path::{Path, PathBuf};
14+
use std::process::{Command, Stdio};
1415

1516
use crate::tasks::csharp::dotnet_format;
1617
use crate::tasks::rust::rustfmt;
17-
use crate::util::y_or_n;
18+
use crate::util::{resolve_sibling_binary, y_or_n};
1819
use crate::Config;
1920
use crate::{build, common_args};
2021
use clap::builder::PossibleValue;
@@ -88,6 +89,15 @@ pub fn cli() -> clap::Command {
8889
}
8990

9091
pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()> {
92+
exec_ex(config, args, extract_descriptions).await
93+
}
94+
95+
/// Like `exec`, but lets you specify a custom a function to extract a schema from a file.
96+
pub async fn exec_ex(
97+
config: Config,
98+
args: &clap::ArgMatches,
99+
extract_descriptions: ExtractDescriptions,
100+
) -> anyhow::Result<()> {
91101
let project_path = args.get_one::<PathBuf>("project_path").unwrap();
92102
let wasm_file = args.get_one::<PathBuf>("wasm_file").cloned();
93103
let json_module = args.get_many::<PathBuf>("json_module");
@@ -101,13 +111,13 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
101111
return Err(anyhow::anyhow!("--namespace is only supported with --lang csharp"));
102112
}
103113

104-
let module = if let Some(mut json_module) = json_module {
105-
let DeserializeWrapper(module) = if let Some(path) = json_module.next() {
114+
let module: ModuleDef = if let Some(mut json_module) = json_module {
115+
let DeserializeWrapper::<RawModuleDef>(module) = if let Some(path) = json_module.next() {
106116
serde_json::from_slice(&fs::read(path)?)?
107117
} else {
108118
serde_json::from_reader(std::io::stdin().lock())?
109119
};
110-
module
120+
module.try_into()?
111121
} else {
112122
let wasm_path = if let Some(path) = wasm_file {
113123
println!("Skipping build. Instead we are inspecting {}", path.display());
@@ -117,12 +127,9 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
117127
};
118128
let spinner = indicatif::ProgressBar::new_spinner();
119129
spinner.enable_steady_tick(std::time::Duration::from_millis(60));
120-
spinner.set_message("Compiling wasm...");
121-
let module = compile_wasm(&wasm_path)?;
122130
spinner.set_message("Extracting schema from wasm...");
123-
extract_descriptions_from_module(module)?
131+
extract_descriptions(&wasm_path).context("could not extract schema")?
124132
};
125-
let module: ModuleDef = module.try_into()?;
126133

127134
fs::create_dir_all(out_dir)?;
128135

@@ -234,3 +241,16 @@ impl Language {
234241
Ok(())
235242
}
236243
}
244+
245+
pub type ExtractDescriptions = fn(&Path) -> anyhow::Result<ModuleDef>;
246+
fn extract_descriptions(wasm_file: &Path) -> anyhow::Result<ModuleDef> {
247+
let bin_path = resolve_sibling_binary("spacetimedb-standalone")?;
248+
let child = Command::new(&bin_path)
249+
.arg("extract-schema")
250+
.arg(wasm_file)
251+
.stdout(Stdio::piped())
252+
.spawn()
253+
.with_context(|| format!("failed to spawn {}", bin_path.display()))?;
254+
let sats::serde::SerdeWrapper::<RawModuleDef>(module) = serde_json::from_reader(child.stdout.unwrap())?;
255+
Ok(module.try_into()?)
256+
}

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/codegen/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ spacetimedb-schema.workspace = true
1515
anyhow.workspace = true
1616
convert_case.workspace = true
1717
itertools.workspace = true
18-
wasmtime.workspace = true
1918

2019
[dev-dependencies]
2120
fs-err.workspace = true

crates/codegen/src/lib.rs

-120
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
use std::mem;
2-
use std::path::Path;
3-
4-
use anyhow::Context;
5-
use spacetimedb_lib::{bsatn, RawModuleDefV8};
6-
use spacetimedb_lib::{RawModuleDef, MODULE_ABI_MAJOR_VERSION};
7-
use spacetimedb_primitives::errno;
81
use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef};
92
use spacetimedb_schema::identifier::Identifier;
10-
use wasmtime::{Caller, StoreContextMut};
113

124
mod code_indenter;
135
pub mod csharp;
@@ -49,115 +41,3 @@ pub trait Lang {
4941
fn generate_reducer(&self, module: &ModuleDef, reducer: &ReducerDef) -> String;
5042
fn generate_globals(&self, module: &ModuleDef) -> Vec<(String, String)>;
5143
}
52-
53-
pub fn extract_descriptions(wasm_file: &Path) -> anyhow::Result<RawModuleDef> {
54-
let module = compile_wasm(wasm_file)?;
55-
extract_descriptions_from_module(module)
56-
}
57-
58-
pub fn compile_wasm(wasm_file: &Path) -> anyhow::Result<wasmtime::Module> {
59-
wasmtime::Module::from_file(&wasmtime::Engine::default(), wasm_file)
60-
}
61-
62-
#[allow(clippy::disallowed_macros)]
63-
pub fn extract_descriptions_from_module(module: wasmtime::Module) -> anyhow::Result<RawModuleDef> {
64-
let engine = module.engine();
65-
let ctx = WasmCtx {
66-
mem: None,
67-
sink: Vec::new(),
68-
};
69-
let mut store = wasmtime::Store::new(engine, ctx);
70-
let mut linker = wasmtime::Linker::new(engine);
71-
linker.allow_shadowing(true).define_unknown_imports_as_traps(&module)?;
72-
let module_name = &*format!("spacetime_{MODULE_ABI_MAJOR_VERSION}.0");
73-
linker.func_wrap(
74-
module_name,
75-
"console_log",
76-
|mut caller: Caller<'_, WasmCtx>,
77-
_level: u32,
78-
_target_ptr: u32,
79-
_target_len: u32,
80-
_filename_ptr: u32,
81-
_filename_len: u32,
82-
_line_number: u32,
83-
message_ptr: u32,
84-
message_len: u32| {
85-
let (mem, _) = WasmCtx::mem_env(&mut caller);
86-
let slice = deref_slice(mem, message_ptr, message_len).unwrap();
87-
println!("from wasm: {}", String::from_utf8_lossy(slice));
88-
},
89-
)?;
90-
linker.func_wrap(module_name, "bytes_sink_write", WasmCtx::bytes_sink_write)?;
91-
let instance = linker.instantiate(&mut store, &module)?;
92-
let memory = instance.get_memory(&mut store, "memory").context("no memory export")?;
93-
store.data_mut().mem = Some(memory);
94-
95-
let mut preinits = instance
96-
.exports(&mut store)
97-
.filter_map(|exp| Some((exp.name().strip_prefix("__preinit__")?.to_owned(), exp.into_func()?)))
98-
.collect::<Vec<_>>();
99-
preinits.sort_by(|(a, _), (b, _)| a.cmp(b));
100-
for (_, func) in preinits {
101-
func.typed(&store)?.call(&mut store, ())?
102-
}
103-
let module: RawModuleDef = match instance.get_func(&mut store, "__describe_module__") {
104-
Some(f) => {
105-
store.data_mut().sink = Vec::new();
106-
f.typed::<u32, ()>(&store)?.call(&mut store, 1)?;
107-
let buf = mem::take(&mut store.data_mut().sink);
108-
bsatn::from_slice(&buf)?
109-
}
110-
// TODO: shouldn't we return an error here?
111-
None => RawModuleDef::V8BackCompat(RawModuleDefV8::default()),
112-
};
113-
Ok(module)
114-
}
115-
116-
struct WasmCtx {
117-
mem: Option<wasmtime::Memory>,
118-
sink: Vec<u8>,
119-
}
120-
121-
fn deref_slice(mem: &[u8], offset: u32, len: u32) -> anyhow::Result<&[u8]> {
122-
anyhow::ensure!(offset != 0, "ptr is null");
123-
mem.get(offset as usize..)
124-
.and_then(|s| s.get(..len as usize))
125-
.context("pointer out of bounds")
126-
}
127-
128-
fn read_u32(mem: &[u8], offset: u32) -> anyhow::Result<u32> {
129-
Ok(u32::from_le_bytes(deref_slice(mem, offset, 4)?.try_into().unwrap()))
130-
}
131-
132-
impl WasmCtx {
133-
pub fn get_mem(&self) -> wasmtime::Memory {
134-
self.mem.expect("Initialized memory")
135-
}
136-
137-
fn mem_env<'a>(ctx: impl Into<StoreContextMut<'a, Self>>) -> (&'a mut [u8], &'a mut Self) {
138-
let ctx = ctx.into();
139-
let mem = ctx.data().get_mem();
140-
mem.data_and_store_mut(ctx)
141-
}
142-
143-
pub fn bytes_sink_write(
144-
mut caller: Caller<'_, Self>,
145-
sink_handle: u32,
146-
buffer_ptr: u32,
147-
buffer_len_ptr: u32,
148-
) -> anyhow::Result<u32> {
149-
if sink_handle != 1 {
150-
return Ok(errno::NO_SUCH_BYTES.get().into());
151-
}
152-
153-
let (mem, env) = Self::mem_env(&mut caller);
154-
155-
// Read `buffer_len`, i.e., the capacity of `buffer` pointed to by `buffer_ptr`.
156-
let buffer_len = read_u32(mem, buffer_len_ptr)?;
157-
// Write `buffer` to `sink`.
158-
let buffer = deref_slice(mem, buffer_ptr, buffer_len)?;
159-
env.sink.extend(buffer);
160-
161-
Ok(0)
162-
}
163-
}

crates/codegen/tests/codegen.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
use spacetimedb_codegen::{extract_descriptions, generate, Csharp, Rust, TypeScript};
1+
use spacetimedb_codegen::{generate, Csharp, Rust, 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().try_into().unwrap();
17+
let module = compiled_module();
1918
let outfiles = HashMap::<_, _>::from_iter(generate(&module, &$lang));
2019
insta::with_settings!({ sort_maps => true }, {
2120
insta::assert_toml_snapshot!(outfiles);

0 commit comments

Comments
 (0)