@@ -4,20 +4,17 @@ use anyhow::Context;
4
4
use clap:: parser:: ValueSource ;
5
5
use clap:: Arg ;
6
6
use clap:: ArgAction :: Set ;
7
- use core:: mem;
8
7
use fs_err as fs;
9
8
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 } ;
13
10
use spacetimedb_schema;
14
11
use spacetimedb_schema:: def:: { ModuleDef , ReducerDef , ScopedTypeName , TableDef , TypeDef } ;
15
12
use spacetimedb_schema:: identifier:: Identifier ;
16
13
use std:: path:: { Path , PathBuf } ;
17
- use wasmtime :: { Caller , StoreContextMut } ;
14
+ use std :: process :: { Command , Stdio } ;
18
15
19
16
use crate :: generate:: util:: iter_reducers;
20
- use crate :: util:: y_or_n;
17
+ use crate :: util:: { resolve_sibling_binary , y_or_n} ;
21
18
use crate :: Config ;
22
19
use crate :: { build, common_args} ;
23
20
use clap:: builder:: PossibleValue ;
@@ -98,6 +95,15 @@ pub fn cli() -> clap::Command {
98
95
}
99
96
100
97
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 < ( ) > {
101
107
let project_path = args. get_one :: < PathBuf > ( "project_path" ) . unwrap ( ) ;
102
108
let wasm_file = args. get_one :: < PathBuf > ( "wasm_file" ) . cloned ( ) ;
103
109
let json_module = args. get_many :: < PathBuf > ( "json_module" ) ;
@@ -111,13 +117,13 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
111
117
return Err ( anyhow:: anyhow!( "--namespace is only supported with --lang csharp" ) ) ;
112
118
}
113
119
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 ( ) {
116
122
serde_json:: from_slice ( & fs:: read ( path) ?) ?
117
123
} else {
118
124
serde_json:: from_reader ( std:: io:: stdin ( ) . lock ( ) ) ?
119
125
} ;
120
- module
126
+ module. try_into ( ) ?
121
127
} else {
122
128
let wasm_path = if let Some ( path) = wasm_file {
123
129
println ! ( "Skipping build. Instead we are inspecting {}" , path. display( ) ) ;
@@ -127,10 +133,8 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
127
133
} ;
128
134
let spinner = indicatif:: ProgressBar :: new_spinner ( ) ;
129
135
spinner. enable_steady_tick ( std:: time:: Duration :: from_millis ( 60 ) ) ;
130
- spinner. set_message ( "Compiling wasm..." ) ;
131
- let module = compile_wasm ( & wasm_path) ?;
132
136
spinner. set_message ( "Extracting schema from wasm..." ) ;
133
- extract_descriptions_from_module ( module ) ?
137
+ extract_descriptions ( & wasm_path ) . context ( "could not extract schema" ) ?
134
138
} ;
135
139
136
140
fs:: create_dir_all ( out_dir) ?;
@@ -147,7 +151,7 @@ pub async fn exec(config: Config, args: &clap::ArgMatches) -> anyhow::Result<()>
147
151
Language :: TypeScript => & typescript:: TypeScript ,
148
152
} ;
149
153
150
- for ( fname, code) in generate ( module, lang) ? {
154
+ for ( fname, code) in generate ( & module, lang) ? {
151
155
let fname = Path :: new ( & fname) ;
152
156
// If a generator asks for a file in a subdirectory, create the subdirectory first.
153
157
if let Some ( parent) = fname. parent ( ) . filter ( |p| !p. as_os_str ( ) . is_empty ( ) ) {
@@ -230,8 +234,7 @@ impl clap::ValueEnum for Language {
230
234
}
231
235
}
232
236
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 ) > > {
235
238
Ok ( itertools:: chain!(
236
239
module
237
240
. tables( )
@@ -266,113 +269,15 @@ pub trait Lang {
266
269
Self : Sized ;
267
270
}
268
271
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 ( ) ?)
378
283
}
0 commit comments