@@ -9,7 +9,7 @@ use starlark::eval::{Evaluator, FileLoader};
99use starlark:: syntax:: AstModule ;
1010
1111use crate :: eval:: { AxlScriptEvaluator , LOAD_STACK } ;
12- use crate :: helpers:: sanitize_load_path_lexically;
12+ use crate :: helpers:: { sanitize_load_path_lexically, LoadPath } ;
1313
1414thread_local ! {
1515 static LOADED_MODULES : RefCell <HashMap <String , FrozenModule >> = RefCell :: new( HashMap :: new( ) ) ;
@@ -24,6 +24,9 @@ pub struct AxlLoader<'a> {
2424 // The script dir which relative loads are relative to
2525 pub ( super ) script_dir : PathBuf ,
2626
27+ // The current module name that the load statement is in
28+ pub ( super ) module_name : String ,
29+
2730 // The module root directory that relative loads cannot escape
2831 pub ( super ) module_root : PathBuf ,
2932
@@ -38,29 +41,57 @@ impl<'a> AxlLoader<'a> {
3841 module_subpath : & Path ,
3942 ) -> starlark:: Result < ( PathBuf , PathBuf ) > {
4043 let module_root = self . axl_deps_root . join ( module_name) ;
41- let resolved_script_path = module_root. join ( module_subpath) ;
44+
45+ let mut resolved_script_path = module_root. clone ( ) ;
46+ for component in module_subpath. components ( ) {
47+ match component {
48+ Component :: CurDir => { }
49+ Component :: ParentDir => {
50+ resolved_script_path. pop ( ) ;
51+ }
52+ Component :: Normal ( s) => {
53+ resolved_script_path. push ( s) ;
54+ }
55+ _ => {
56+ return Err ( starlark:: Error :: new_other ( anyhow ! (
57+ "invalid path component in load path: {}" ,
58+ module_subpath. display( )
59+ ) ) ) ;
60+ }
61+ }
62+ }
63+
64+ if !resolved_script_path. starts_with ( & module_root) {
65+ return Err ( starlark:: Error :: new_other ( anyhow ! (
66+ "resolved path {} for load path {} escapes the module root directory {}" ,
67+ resolved_script_path. display( ) ,
68+ module_subpath. display( ) ,
69+ module_root. display( )
70+ ) ) ) ;
71+ }
72+
4273 if resolved_script_path. is_file ( ) {
4374 return Ok ( ( resolved_script_path, module_root) ) ;
4475 }
4576 if !module_root. exists ( ) {
4677 return Err ( starlark:: Error :: new_other ( anyhow ! (
47- "failed to resolve load(\" @{}/{}\" , ...): module '{}' not found (expected module directory at '{}')" ,
78+ "failed to resolve load(\" @{}// {}\" , ...): module '{}' not found (expected module directory at '{}')" ,
4879 module_name,
4980 module_subpath. display( ) ,
5081 module_name,
5182 module_root. display( )
5283 ) ) ) ;
5384 } else if !module_root. is_dir ( ) {
5485 return Err ( starlark:: Error :: new_other ( anyhow ! (
55- "failed to resolve load(\" @{}/{}\" , ...): module '{}' root at '{}' exists but is not a directory" ,
86+ "failed to resolve load(\" @{}// {}\" , ...): module '{}' root at '{}' exists but is not a directory" ,
5687 module_name,
5788 module_subpath. display( ) ,
5889 module_name,
5990 module_root. display( )
6091 ) ) ) ;
6192 } else {
6293 return Err ( starlark:: Error :: new_other ( anyhow ! (
63- "failed to resolve load(\" @{}/{}\" , ...): script file not found in module '{}' (expected at '{}')" ,
94+ "failed to resolve load(\" @{}// {}\" , ...): script file not found in module '{}' (expected at '{}')" ,
6495 module_name,
6596 module_subpath. display( ) ,
6697 module_name,
@@ -69,20 +100,7 @@ impl<'a> AxlLoader<'a> {
69100 }
70101 }
71102
72- fn resolve_axl_script ( & self , script_path : & Path ) -> starlark:: Result < PathBuf > {
73- let script_path_str = script_path. to_str ( ) . ok_or_else ( || {
74- starlark:: Error :: new_other ( anyhow ! (
75- "path is not valid UTF-8: {}" ,
76- script_path. display( )
77- ) )
78- } ) ?;
79- let base: & PathBuf =
80- if script_path_str. starts_with ( "./" ) || script_path_str. starts_with ( "../" ) {
81- & self . script_dir
82- } else {
83- & self . module_root
84- } ;
85-
103+ fn resolve_axl_script ( & self , script_path : & Path , base : & PathBuf ) -> starlark:: Result < PathBuf > {
86104 let mut resolved_script_path = base. clone ( ) ;
87105 for component in script_path. components ( ) {
88106 match component {
@@ -125,38 +143,53 @@ impl<'a> FileLoader for AxlLoader<'a> {
125143 ) ) ) ;
126144 }
127145
128- let ( module_name, module_subpath_or_script_path) = sanitize_load_path_lexically ( load_path) ?;
129-
130- // Check if the @module/path/to/file.axl has been loaded before; if it has use the cached version.
131- // NB: it is by design that a `root/sub/.aspect/modules/foo/foo.axl` can override
132- // the load of a `root/.aspect/modules/foo/foo.axl` and of `modules_cache/foo/foo.axl` since
133- // we want to allow overriding of individual files in modules as needed.
134- if module_name. is_some ( ) {
135- let module_specifier = format ! (
136- "@{}/{}" ,
137- module_name. as_ref( ) . unwrap( ) ,
138- module_subpath_or_script_path. display( )
139- ) ;
140- if let Some ( module) =
141- LOADED_MODULES . with ( |modules| modules. borrow ( ) . get ( & module_specifier) . cloned ( ) )
142- {
143- return Ok ( module) ;
146+ let load_path_enum = sanitize_load_path_lexically ( load_path) ?;
147+
148+ let ( resolved_script_path, module_root) = match & load_path_enum {
149+ LoadPath :: ModuleSpecifier { module, subpath } => {
150+ self . resolve_axl_module_script ( module, subpath) ?
144151 }
152+ LoadPath :: ModuleSubpath ( subpath) => (
153+ self . resolve_axl_script ( subpath, & self . module_root ) ?,
154+ self . module_root . clone ( ) ,
155+ ) ,
156+ LoadPath :: RelativePath ( relpath) => (
157+ self . resolve_axl_script ( relpath, & self . script_dir ) ?,
158+ self . module_root . clone ( ) ,
159+ ) ,
160+ } ;
161+
162+ let target_module_name = match & load_path_enum {
163+ LoadPath :: ModuleSpecifier { module, .. } => module. clone ( ) ,
164+ _ => self . module_name . clone ( ) ,
165+ } ;
166+
167+ if target_module_name. is_empty ( ) {
168+ return Err ( starlark:: Error :: new_other ( anyhow ! (
169+ "failed to resolve a target module name from load path {}" ,
170+ load_path
171+ ) ) ) ;
145172 }
146173
147- // Resolve the load path to a file on disk
148- let ( resolved_script_path , module_root ) = if module_name . is_some ( ) {
149- self . resolve_axl_module_script (
150- module_name . as_ref ( ) . unwrap ( ) . as_str ( ) ,
151- & module_subpath_or_script_path ,
152- ) ?
153- } else {
154- (
155- self . resolve_axl_script ( & module_subpath_or_script_path ) ? ,
156- self . module_root . clone ( ) ,
157- )
174+ let module_specifier = {
175+ let rel = resolved_script_path
176+ . strip_prefix ( & module_root )
177+ . map_err ( |e| {
178+ starlark :: Error :: new_other ( anyhow ! ( "failed to strip prefix: {}" , e ) )
179+ } ) ? ;
180+ let subpath_str = rel
181+ . to_str ( )
182+ . ok_or_else ( || starlark :: Error :: new_other ( anyhow ! ( "non-UTF8 path" ) ) ) ?
183+ . replace ( '\\' , "/" ) ;
184+ format ! ( "@{}//{}" , target_module_name , subpath_str )
158185 } ;
159186
187+ if let Some ( cached_module) =
188+ LOADED_MODULES . with ( |modules| modules. borrow ( ) . get ( & module_specifier) . cloned ( ) )
189+ {
190+ return Ok ( cached_module) ;
191+ }
192+
160193 // Cycle detection: Prevent loading the same file recursively.
161194 let mut cycle_error = None ;
162195 LOAD_STACK . with ( |stack| {
@@ -197,6 +230,7 @@ impl<'a> FileLoader for AxlLoader<'a> {
197230 . parent ( )
198231 . expect ( "file path has parent" )
199232 . to_path_buf ( ) ,
233+ module_name : target_module_name,
200234 module_root,
201235 axl_deps_root : self . axl_deps_root . clone ( ) ,
202236 } ;
@@ -207,19 +241,12 @@ impl<'a> FileLoader for AxlLoader<'a> {
207241 drop ( eval) ;
208242 let frozen_module = module. freeze ( ) ?;
209243
210- // Cache the load @module/path/to/file.axl so it can be re-used on subsequent loads
211- if module_name. is_some ( ) {
212- let module_specifier = format ! (
213- "@{}/{}" ,
214- module_name. as_ref( ) . unwrap( ) ,
215- module_subpath_or_script_path. display( )
216- ) ;
217- LOADED_MODULES . with ( |modules| {
218- modules
219- . borrow_mut ( )
220- . insert ( module_specifier, frozen_module. clone ( ) ) ;
221- } ) ;
222- }
244+ // Cache the load @module//path/to/file.axl so it can be re-used on subsequent loads
245+ LOADED_MODULES . with ( |modules| {
246+ modules
247+ . borrow_mut ( )
248+ . insert ( module_specifier, frozen_module. clone ( ) ) ;
249+ } ) ;
223250
224251 // Pop the load stack after successful load
225252 LOAD_STACK . with ( |stack| {
0 commit comments