--- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -4,19 +4,18 @@ any::Any, cell::RefCell, collections::HashMap, + env::current_dir, ffi::{c_void, CStr, CString}, - fs::File, - io::Read, os::raw::{c_char, c_int}, - path::{Path, PathBuf}, + path::PathBuf, ptr::null_mut, }; use jrsonnet_evaluator::{ error::{Error::*, Result}, - throw, ImportResolver, State, + throw, FileImportResolver, ImportResolver, State, }; -use jrsonnet_parser::SourcePath; +use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath}; pub type JsonnetImportCallback = unsafe extern "C" fn( ctx: *mut c_void, @@ -33,25 +32,31 @@ out: RefCell>>, } impl ImportResolver for CallbackImportResolver { - fn resolve_file_relative(&self, from: &Path, path: &str) -> Result { - let base = CString::new(from.to_str().unwrap()).unwrap().into_raw(); - let rel = CString::new(path).unwrap().into_raw(); + fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { + let base = if let Some(p) = from.downcast_ref::() { + let mut o = p.path().to_owned(); + o.pop(); + o + } else if let Some(d) = from.downcast_ref::() { + d.path().to_owned() + } else if from.is_default() { + current_dir().map_err(|e| ImportIo(e.to_string()))? + } else { + unreachable!("can't resolve this path"); + }; + let base = unsafe { crate::unparse_path(&base) }; + let rel = CString::new(path).unwrap(); let found_here: *mut c_char = null_mut(); let mut success: i32 = 0; let result_ptr = unsafe { (self.cb)( self.ctx, - base, - rel, + base.as_ptr(), + rel.as_ptr(), &mut (found_here as *const _), &mut success, ) }; - // Release memory occipied by arguments passed - unsafe { - let _ = CString::from_raw(base); - let _ = CString::from_raw(rel); - } let result_raw = unsafe { CStr::from_ptr(result_ptr) }; let result_str = result_raw.to_str().unwrap(); assert!(success == 0 || success == 1); @@ -62,7 +67,9 @@ } let found_here_raw = unsafe { CStr::from_ptr(found_here) }; - let found_here_buf = SourcePath::Path(PathBuf::from(found_here_raw.to_str().unwrap())); + let found_here_buf = SourcePath::new(SourceFile::new(PathBuf::from( + found_here_raw.to_str().unwrap(), + ))); unsafe { let _ = CString::from_raw(found_here); } @@ -79,12 +86,14 @@ Ok(self.out.borrow().get(resolved).unwrap().clone()) } - unsafe fn as_any(&self) -> &dyn Any { + fn as_any(&self) -> &dyn Any { self } } /// # Safety +/// +/// Caller should pass correct callback function #[no_mangle] pub unsafe extern "C" fn jsonnet_import_callback( vm: &State, @@ -96,54 +105,11 @@ ctx, out: RefCell::new(HashMap::new()), })) -} - -/// Standard FS import resolver -#[derive(Default)] -pub struct NativeImportResolver { - library_paths: RefCell>, -} -impl NativeImportResolver { - fn add_jpath(&self, path: PathBuf) { - self.library_paths.borrow_mut().push(path); - } } -impl ImportResolver for NativeImportResolver { - fn resolve_file_relative(&self, from: &Path, path: &str) -> Result { - let mut new_path = from.to_owned(); - new_path.push(path); - if new_path.exists() { - Ok(SourcePath::Path(new_path)) - } else { - for library_path in self.library_paths.borrow().iter() { - let mut cloned = library_path.clone(); - cloned.push(path); - if cloned.exists() { - return Ok(SourcePath::Path(cloned)); - } - } - throw!(ImportFileNotFound(from.to_owned(), path.to_owned())) - } - } - fn load_file_contents(&self, id: &SourcePath) -> Result> { - let path = match id { - SourcePath::Path(path) => path, - _ => unreachable!("NativeImportResolver::resolve_file may only return plain paths"), - }; - let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?; - let mut out = Vec::new(); - file.read_to_end(&mut out) - .map_err(|e| ImportIo(e.to_string()))?; - Ok(out) - } - unsafe fn as_any(&self) -> &dyn Any { - self - } -} /// # Safety /// -/// This function is safe, if received v is a pointer to normal C string +/// Caller should pass correct path: it should contain correct utf-8, and be \0-terminated #[no_mangle] pub unsafe extern "C" fn jsonnet_jpath_add(vm: &State, v: *const c_char) { let cstr = CStr::from_ptr(v); @@ -151,7 +117,7 @@ let any_resolver = vm.import_resolver(); let resolver = any_resolver .as_any() - .downcast_ref::() + .downcast_ref::() .expect("jpaths are not compatible with callback imports!"); resolver.add_jpath(path); } --- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -19,6 +19,8 @@ ] # Destructuring of locals exp-destruct = ["jrsonnet-evaluator/exp-destruct"] +# std.thisFile support +legacy-this-file = ["jrsonnet-cli/legacy-this-file"] [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" } --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -1,5 +1,4 @@ use std::{ - env::current_dir, fs::{create_dir_all, File}, io::{Read, Write}, }; @@ -140,7 +139,7 @@ let input_str = std::str::from_utf8(&input)?; s.evaluate_snippet("".to_owned(), input_str)? } else { - s.import(¤t_dir().expect("cwd"), &input)? + s.import(&input)? }; let val = s.with_tla(val)?; --- a/crates/jrsonnet-cli/Cargo.toml +++ b/crates/jrsonnet-cli/Cargo.toml @@ -15,6 +15,7 @@ "jrsonnet-evaluator/exp-serde-preserve-order", "jrsonnet-stdlib/exp-serde-preserve-order", ] +legacy-this-file = ["jrsonnet-stdlib/legacy-this-file"] [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2", features = [ --- a/crates/jrsonnet-cli/src/lib.rs +++ b/crates/jrsonnet-cli/src/lib.rs @@ -51,7 +51,7 @@ library_paths.extend(env::split_paths(path.as_os_str())); } - s.set_import_resolver(Box::new(FileImportResolver { library_paths })); + s.set_import_resolver(Box::new(FileImportResolver::new(library_paths))); s.set_max_stack(self.max_stack); Ok(()) --- a/crates/jrsonnet-cli/src/stdlib.rs +++ b/crates/jrsonnet-cli/src/stdlib.rs @@ -1,7 +1,7 @@ use std::{fs::read_to_string, str::FromStr}; use clap::Parser; -use jrsonnet_evaluator::{error::Result, State}; +use jrsonnet_evaluator::{error::Result, trace::PathResolver, State}; use crate::ConfigureState; @@ -110,7 +110,8 @@ if self.no_stdlib { return Ok(()); } - let ctx = jrsonnet_stdlib::ContextInitializer::new(s.clone()); + let ctx = + jrsonnet_stdlib::ContextInitializer::new(s.clone(), PathResolver::new_cwd_fallback()); for ext in self.ext_str.iter() { ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into()); } --- a/crates/jrsonnet-cli/src/trace.rs +++ b/crates/jrsonnet-cli/src/trace.rs @@ -42,11 +42,7 @@ } impl ConfigureState for TraceOpts { fn configure(&self, s: &State) -> Result<()> { - let resolver = if let Ok(dir) = std::env::current_dir() { - PathResolver::Relative(dir) - } else { - PathResolver::Absolute - }; + let resolver = PathResolver::new_cwd_fallback(); match self .trace_format .as_ref() --- a/crates/jrsonnet-interner/src/inner.rs +++ b/crates/jrsonnet-interner/src/inner.rs @@ -84,7 +84,9 @@ unsafe { Self::new_raw(str.as_bytes(), true) } } - pub const fn as_slice(&self) -> &[u8] { + // `slice::from_raw_parts` is not yet stabilized + #[allow(clippy::missing_const_for_fn)] + pub fn as_slice(&self) -> &[u8] { let header = Self::header(self); // SAFETY: data is not null, and it is correctly initialized let size = unsafe { (*header).size }; @@ -99,7 +101,7 @@ /// # Safety /// Data should be checked to be utf8 via [`check_utf8`] first - pub const unsafe fn as_str_unchecked(&self) -> &str { + pub unsafe fn as_str_unchecked(&self) -> &str { // SAFETY: data is checked unsafe { str::from_utf8_unchecked(self.as_slice()) } } --- a/crates/jrsonnet-interner/src/lib.rs +++ b/crates/jrsonnet-interner/src/lib.rs @@ -133,7 +133,7 @@ } #[must_use] - pub const fn as_slice(&self) -> &[u8] { + pub fn as_slice(&self) -> &[u8] { self.0.as_slice() } } --- a/crates/jrsonnet-stdlib/build.rs +++ b/crates/jrsonnet-stdlib/build.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, env, fs::File, io::Write, path::Path}; +use std::{env, fs::File, io::Write, path::Path}; use jrsonnet_parser::{parse, ParserSettings, Source}; use structdump::CodegenResult; @@ -8,7 +8,7 @@ include_str!("./src/std.jsonnet"), &ParserSettings { file_name: Source::new_virtual( - Cow::Borrowed(""), + "".into(), include_str!("./src/std.jsonnet").into(), ), }, --- a/crates/jrsonnet-stdlib/src/expr.rs +++ b/crates/jrsonnet-stdlib/src/expr.rs @@ -1,7 +1,7 @@ use jrsonnet_parser::LocExpr; mod structdump_import { - pub(super) use std::{borrow::Cow, option::Option, rc::Rc, vec}; + pub(super) use std::{option::Option, rc::Rc, vec}; pub(super) use jrsonnet_parser::*; } --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -1,5 +1,4 @@ use std::{ - borrow::Cow, cell::{Ref, RefCell, RefMut}, collections::HashMap, rc::Rc, @@ -10,6 +9,7 @@ function::{builtin::Builtin, ArgLike, CallLocation, FuncVal, TlaArg}, gc::{GcHashMap, TraceBox}, tb, throw_runtime, + trace::PathResolver, typed::{Any, Either, Either2, Either4, VecVal, M1}, val::{equals, ArrValue}, Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val, @@ -184,13 +184,27 @@ fn print_trace(&self, s: State, loc: CallLocation, value: IStr); } -pub struct StdTracePrinter; +pub struct StdTracePrinter { + resolver: PathResolver, +} +impl StdTracePrinter { + pub fn new(resolver: PathResolver) -> Self { + Self { resolver } + } +} impl TracePrinter for StdTracePrinter { fn print_trace(&self, _s: State, loc: CallLocation, value: IStr) { eprint!("TRACE:"); if let Some(loc) = loc.0 { let locs = loc.0.map_source_locations(&[loc.1]); - eprint!(" {}:{}", loc.0.short_display(), locs[0].line); + eprint!( + " {}:{}", + match loc.0.source_path().path() { + Some(p) => self.resolver.resolve(p), + None => loc.0.source_path().to_string(), + }, + locs[0].line + ); } eprintln!(" {}", value); } @@ -205,22 +219,13 @@ pub globals: GcHashMap>, /// Used for `std.trace` pub trace_printer: Box, -} - -impl Default for Settings { - fn default() -> Self { - Self { - ext_vars: Default::default(), - ext_natives: Default::default(), - globals: Default::default(), - trace_printer: Box::new(StdTracePrinter), - } - } + /// Used for `std.thisFile` + pub path_resolver: PathResolver, } pub fn extvar_source(name: &str, code: impl Into) -> Source { let source_name = format!("", name); - Source::new_virtual(Cow::Owned(source_name), code.into()) + Source::new_virtual(source_name.into(), code.into()) } pub struct ContextInitializer { @@ -233,8 +238,15 @@ settings: Rc>, } impl ContextInitializer { - pub fn new(s: State) -> Self { - let settings = Rc::new(RefCell::new(Settings::default())); + pub fn new(s: State, resolver: PathResolver) -> Self { + let settings = Settings { + ext_vars: Default::default(), + ext_natives: Default::default(), + globals: Default::default(), + trace_printer: Box::new(StdTracePrinter::new(resolver.clone())), + path_resolver: resolver, + }; + let settings = Rc::new(RefCell::new(settings)); Self { #[cfg(not(feature = "legacy-this-file"))] context: { @@ -313,13 +325,10 @@ .hide() .value( s, - Val::Str( - source - .path() - .map(|p| p.display().to_string()) - .unwrap_or_else(String::new) - .into(), - ), + Val::Str(match source.source_path().path() { + Some(p) => self.settings().path_resolver.resolve(p).into(), + None => source.source_path().to_string().into(), + }), ) .expect("this object builder is empty"); let stdlib_with_this_file = builder.build(); @@ -329,12 +338,12 @@ "std".into(), Thunk::evaluated(Val::Obj(stdlib_with_this_file)), ); - for (k, v) in &self.settings().globals { - context.bind(k.clone(), v.clone()) + for (k, v) in self.settings().globals.iter() { + context.bind(k.clone(), v.clone()); } context.build() } - unsafe fn as_any(&self) -> &dyn std::any::Any { + fn as_any(&self) -> &dyn std::any::Any { self } } @@ -540,12 +549,13 @@ impl StateExt for State { fn with_stdlib(&self) { - let initializer = ContextInitializer::new(self.clone()); + let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback()); self.settings_mut().context_initializer = Box::new(initializer) } fn add_global(&self, name: IStr, value: Thunk) { - // Safety: - unsafe { self.settings().context_initializer.as_any() } + self.settings() + .context_initializer + .as_any() .downcast_ref::() .expect("not standard context initializer") .settings_mut() --- a/crates/jrsonnet-stdlib/src/std.jsonnet +++ b/crates/jrsonnet-stdlib/src/std.jsonnet @@ -2,7 +2,7 @@ local std = self, local id = std.id, - thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support. This will slow down stdlib caching a bit, though', + thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support.\nThis will slow down stdlib caching a bit, though', toString(a):: '' + a, --- a/tests/tests/golden.rs +++ b/tests/tests/golden.rs @@ -21,7 +21,7 @@ common::with_test(&s); s.set_import_resolver(Box::new(FileImportResolver::default())); - let v = match s.import(root, &file.display().to_string()) { + let v = match s.import(file) { Ok(v) => v, Err(e) => return s.stringify_err(&e), }; --- a/tests/tests/suite.rs +++ b/tests/tests/suite.rs @@ -21,7 +21,7 @@ common::with_test(&s); s.set_import_resolver(Box::new(FileImportResolver::default())); - match s.import(root, &file.display().to_string()) { + match s.import(file) { Ok(Val::Bool(true)) => {} Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()), Ok(_) => panic!("test {} returned wrong type as result", file.display()),