Run imports in working directory of importer (#1817)
This commit is contained in:
parent
ea1b68644a
commit
fbd4a437a0
@ -20,7 +20,13 @@ impl Compiler {
|
|||||||
let (relative, src) = loader.load(root, ¤t.path)?;
|
let (relative, src) = loader.load(root, ¤t.path)?;
|
||||||
loaded.push(relative.into());
|
loaded.push(relative.into());
|
||||||
let tokens = Lexer::lex(relative, src)?;
|
let tokens = Lexer::lex(relative, src)?;
|
||||||
let mut ast = Parser::parse(¤t.path, ¤t.namepath, current.depth, &tokens)?;
|
let mut ast = Parser::parse(
|
||||||
|
¤t.path,
|
||||||
|
¤t.namepath,
|
||||||
|
current.depth,
|
||||||
|
&tokens,
|
||||||
|
¤t.working_directory,
|
||||||
|
)?;
|
||||||
|
|
||||||
paths.insert(current.path.clone(), relative.into());
|
paths.insert(current.path.clone(), relative.into());
|
||||||
srcs.insert(current.path.clone(), src);
|
srcs.insert(current.path.clone(), src);
|
||||||
@ -162,7 +168,13 @@ impl Compiler {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
|
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
|
||||||
let tokens = Lexer::test_lex(src)?;
|
let tokens = Lexer::test_lex(src)?;
|
||||||
let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)?;
|
let ast = Parser::parse(
|
||||||
|
&PathBuf::new(),
|
||||||
|
&Namepath::default(),
|
||||||
|
0,
|
||||||
|
&tokens,
|
||||||
|
&PathBuf::new(),
|
||||||
|
)?;
|
||||||
let root = PathBuf::from("justfile");
|
let root = PathBuf::from("justfile");
|
||||||
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
||||||
asts.insert(root.clone(), ast);
|
asts.insert(root.clone(), ast);
|
||||||
|
@ -31,6 +31,7 @@ pub(crate) struct Parser<'run, 'src> {
|
|||||||
recursion_depth: usize,
|
recursion_depth: usize,
|
||||||
submodule_depth: u32,
|
submodule_depth: u32,
|
||||||
tokens: &'run [Token<'src>],
|
tokens: &'run [Token<'src>],
|
||||||
|
working_directory: &'run Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'run, 'src> Parser<'run, 'src> {
|
impl<'run, 'src> Parser<'run, 'src> {
|
||||||
@ -40,6 +41,7 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||||||
module_namepath: &'run Namepath<'src>,
|
module_namepath: &'run Namepath<'src>,
|
||||||
submodule_depth: u32,
|
submodule_depth: u32,
|
||||||
tokens: &'run [Token<'src>],
|
tokens: &'run [Token<'src>],
|
||||||
|
working_directory: &'run Path,
|
||||||
) -> CompileResult<'src, Ast<'src>> {
|
) -> CompileResult<'src, Ast<'src>> {
|
||||||
Self {
|
Self {
|
||||||
expected_tokens: BTreeSet::new(),
|
expected_tokens: BTreeSet::new(),
|
||||||
@ -49,6 +51,7 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||||||
recursion_depth: 0,
|
recursion_depth: 0,
|
||||||
submodule_depth,
|
submodule_depth,
|
||||||
tokens,
|
tokens,
|
||||||
|
working_directory,
|
||||||
}
|
}
|
||||||
.parse_ast()
|
.parse_ast()
|
||||||
}
|
}
|
||||||
@ -734,15 +737,16 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||||||
attributes,
|
attributes,
|
||||||
body,
|
body,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
depth: self.submodule_depth,
|
||||||
doc,
|
doc,
|
||||||
name,
|
|
||||||
parameters: positional.into_iter().chain(variadic).collect(),
|
|
||||||
file_path: self.file_path.into(),
|
file_path: self.file_path.into(),
|
||||||
|
name,
|
||||||
|
namepath: self.module_namepath.join(name),
|
||||||
|
parameters: positional.into_iter().chain(variadic).collect(),
|
||||||
priors,
|
priors,
|
||||||
private: name.lexeme().starts_with('_'),
|
private: name.lexeme().starts_with('_'),
|
||||||
quiet,
|
quiet,
|
||||||
depth: self.submodule_depth,
|
working_directory: self.working_directory.into(),
|
||||||
namepath: self.module_namepath.join(name),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -960,8 +964,14 @@ mod tests {
|
|||||||
fn test(text: &str, want: Tree) {
|
fn test(text: &str, want: Tree) {
|
||||||
let unindented = unindent(text);
|
let unindented = unindent(text);
|
||||||
let tokens = Lexer::test_lex(&unindented).expect("lexing failed");
|
let tokens = Lexer::test_lex(&unindented).expect("lexing failed");
|
||||||
let justfile =
|
let justfile = Parser::parse(
|
||||||
Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens).expect("parsing failed");
|
&PathBuf::new(),
|
||||||
|
&Namepath::default(),
|
||||||
|
0,
|
||||||
|
&tokens,
|
||||||
|
&PathBuf::new(),
|
||||||
|
)
|
||||||
|
.expect("parsing failed");
|
||||||
let have = justfile.tree();
|
let have = justfile.tree();
|
||||||
if have != want {
|
if have != want {
|
||||||
println!("parsed text: {unindented}");
|
println!("parsed text: {unindented}");
|
||||||
@ -999,7 +1009,13 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
||||||
|
|
||||||
match Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens) {
|
match Parser::parse(
|
||||||
|
&PathBuf::new(),
|
||||||
|
&Namepath::default(),
|
||||||
|
0,
|
||||||
|
&tokens,
|
||||||
|
&PathBuf::new(),
|
||||||
|
) {
|
||||||
Ok(_) => panic!("Parsing unexpectedly succeeded"),
|
Ok(_) => panic!("Parsing unexpectedly succeeded"),
|
||||||
Err(have) => {
|
Err(have) => {
|
||||||
let want = CompileError {
|
let want = CompileError {
|
||||||
|
@ -37,6 +37,8 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
|
|||||||
pub(crate) private: bool,
|
pub(crate) private: bool,
|
||||||
pub(crate) quiet: bool,
|
pub(crate) quiet: bool,
|
||||||
pub(crate) shebang: bool,
|
pub(crate) shebang: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) working_directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src, D> Recipe<'src, D> {
|
impl<'src, D> Recipe<'src, D> {
|
||||||
@ -120,6 +122,18 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
!self.attributes.contains(&Attribute::NoExitMessage)
|
!self.attributes.contains(&Attribute::NoExitMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn working_directory<'a>(&'a self, search: &'a Search) -> Option<&Path> {
|
||||||
|
if self.change_directory() {
|
||||||
|
Some(if self.depth > 0 {
|
||||||
|
&self.working_directory
|
||||||
|
} else {
|
||||||
|
&search.working_directory
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn run<'run>(
|
pub(crate) fn run<'run>(
|
||||||
&self,
|
&self,
|
||||||
context: &RecipeContext<'src, 'run>,
|
context: &RecipeContext<'src, 'run>,
|
||||||
@ -222,12 +236,8 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
|
|
||||||
let mut cmd = context.settings.shell_command(config);
|
let mut cmd = context.settings.shell_command(config);
|
||||||
|
|
||||||
if self.change_directory() {
|
if let Some(working_directory) = self.working_directory(context.search) {
|
||||||
cmd.current_dir(if self.depth > 0 {
|
cmd.current_dir(working_directory);
|
||||||
self.file_path.parent().unwrap()
|
|
||||||
} else {
|
|
||||||
&context.search.working_directory
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(command);
|
cmd.arg(command);
|
||||||
@ -353,30 +363,19 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the script executable
|
// make script executable
|
||||||
Platform::set_execute_permission(&path).map_err(|error| Error::TmpdirIo {
|
Platform::set_execute_permission(&path).map_err(|error| Error::TmpdirIo {
|
||||||
recipe: self.name(),
|
recipe: self.name(),
|
||||||
io_error: error,
|
io_error: error,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// create a command to run the script
|
// create command to run script
|
||||||
let mut command = Platform::make_shebang_command(
|
let mut command =
|
||||||
&path,
|
Platform::make_shebang_command(&path, self.working_directory(context.search), shebang)
|
||||||
if self.change_directory() {
|
.map_err(|output_error| Error::Cygpath {
|
||||||
if self.depth > 0 {
|
recipe: self.name(),
|
||||||
Some(self.file_path.parent().unwrap())
|
output_error,
|
||||||
} else {
|
})?;
|
||||||
Some(&context.search.working_directory)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
shebang,
|
|
||||||
)
|
|
||||||
.map_err(|output_error| Error::Cygpath {
|
|
||||||
recipe: self.name(),
|
|
||||||
output_error,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if context.settings.positional_arguments {
|
if context.settings.positional_arguments {
|
||||||
command.args(positional);
|
command.args(positional);
|
||||||
|
@ -4,6 +4,7 @@ pub(crate) struct Source<'src> {
|
|||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
pub(crate) depth: u32,
|
pub(crate) depth: u32,
|
||||||
pub(crate) namepath: Namepath<'src>,
|
pub(crate) namepath: Namepath<'src>,
|
||||||
|
pub(crate) working_directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> Source<'src> {
|
impl<'src> Source<'src> {
|
||||||
@ -12,6 +13,7 @@ impl<'src> Source<'src> {
|
|||||||
path: path.into(),
|
path: path.into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
namepath: Namepath::default(),
|
namepath: Namepath::default(),
|
||||||
|
working_directory: path.parent().unwrap().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,11 +22,13 @@ impl<'src> Source<'src> {
|
|||||||
depth: self.depth + 1,
|
depth: self.depth + 1,
|
||||||
path,
|
path,
|
||||||
namepath: self.namepath.clone(),
|
namepath: self.namepath.clone(),
|
||||||
|
working_directory: self.working_directory.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn module(&self, name: Name<'src>, path: PathBuf) -> Self {
|
pub(crate) fn module(&self, name: Name<'src>, path: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
working_directory: path.parent().unwrap().into(),
|
||||||
path,
|
path,
|
||||||
depth: self.depth + 1,
|
depth: self.depth + 1,
|
||||||
namepath: self.namepath.join(name),
|
namepath: self.namepath.join(name),
|
||||||
|
@ -59,8 +59,14 @@ pub(crate) fn analysis_error(
|
|||||||
) {
|
) {
|
||||||
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
||||||
|
|
||||||
let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)
|
let ast = Parser::parse(
|
||||||
.expect("Parsing failed in analysis test...");
|
&PathBuf::new(),
|
||||||
|
&Namepath::default(),
|
||||||
|
0,
|
||||||
|
&tokens,
|
||||||
|
&PathBuf::new(),
|
||||||
|
)
|
||||||
|
.expect("Parsing failed in analysis test...");
|
||||||
|
|
||||||
let root = PathBuf::from("justfile");
|
let root = PathBuf::from("justfile");
|
||||||
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
||||||
|
@ -58,6 +58,7 @@ impl<'src> UnresolvedRecipe<'src> {
|
|||||||
private: self.private,
|
private: self.private,
|
||||||
quiet: self.quiet,
|
quiet: self.quiet,
|
||||||
shebang: self.shebang,
|
shebang: self.shebang,
|
||||||
|
working_directory: self.working_directory,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,3 +242,79 @@ fn optional_imports_dump_correctly() {
|
|||||||
.stdout("import? './import.justfile'\n")
|
.stdout("import? './import.justfile'\n")
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imports_in_root_run_in_justfile_directory() {
|
||||||
|
Test::new()
|
||||||
|
.write("foo/import.justfile", "bar:\n @cat baz")
|
||||||
|
.write("baz", "BAZ")
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
import 'foo/import.justfile'
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.test_round_trip(false)
|
||||||
|
.arg("bar")
|
||||||
|
.stdout("BAZ")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imports_in_submodules_run_in_submodule_directory() {
|
||||||
|
Test::new()
|
||||||
|
.justfile("mod foo")
|
||||||
|
.write("foo/mod.just", "import 'import.just'")
|
||||||
|
.write("foo/import.just", "bar:\n @cat baz")
|
||||||
|
.write("foo/baz", "BAZ")
|
||||||
|
.test_round_trip(false)
|
||||||
|
.arg("--unstable")
|
||||||
|
.arg("foo")
|
||||||
|
.arg("bar")
|
||||||
|
.stdout("BAZ")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_import_paths_are_relative_to_containing_submodule() {
|
||||||
|
Test::new()
|
||||||
|
.justfile("import 'foo/import.just'")
|
||||||
|
.write("foo/import.just", "import 'bar.just'")
|
||||||
|
.write("foo/bar.just", "bar:\n @echo BAR")
|
||||||
|
.test_round_trip(false)
|
||||||
|
.arg("bar")
|
||||||
|
.stdout("BAR\n")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recipes_in_nested_imports_run_in_parent_module() {
|
||||||
|
Test::new()
|
||||||
|
.justfile("import 'foo/import.just'")
|
||||||
|
.write("foo/import.just", "import 'bar/import.just'")
|
||||||
|
.write("foo/bar/import.just", "bar:\n @cat baz")
|
||||||
|
.write("baz", "BAZ")
|
||||||
|
.test_round_trip(false)
|
||||||
|
.arg("--unstable")
|
||||||
|
.arg("bar")
|
||||||
|
.stdout("BAZ")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shebang_recipes_in_imports_in_root_run_in_justfile_directory() {
|
||||||
|
Test::new()
|
||||||
|
.write(
|
||||||
|
"foo/import.justfile",
|
||||||
|
"bar:\n #!/usr/bin/env bash\n cat baz",
|
||||||
|
)
|
||||||
|
.write("baz", "BAZ")
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
import 'foo/import.justfile'
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.test_round_trip(false)
|
||||||
|
.arg("bar")
|
||||||
|
.stdout("BAZ")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user