diff --git a/README.md b/README.md
index a3d78ef89b..1f2f5409eb 100644
--- a/README.md
+++ b/README.md
@@ -3319,7 +3319,33 @@ Imports may be made optional by putting a `?` after the `import` keyword:
import? 'foo/bar.just'
```
-Missing source files for optional imports do not produce an error.
+Importing the same source file multiple times is not an errormaster.
+This allows importing multiple justfiles, for example `foo.just` and
+`bar.just`, which both import a third justfile containing shared recipes, for
+example `baz.just`, without the duplicate import of `baz.just` being an error:
+
+```mf
+# justfile
+import 'foo.just'
+import 'bar.just'
+```
+
+```mf
+# foo.just
+import 'baz.just'
+foo: baz
+```
+
+```mf
+# bar.just
+import 'baz.just'
+bar: baz
+```
+
+```just
+# baz
+baz:
+```
### Modules1.19.0
diff --git a/src/analyzer.rs b/src/analyzer.rs
index ef6bcb2293..9a26ef0455 100644
--- a/src/analyzer.rs
+++ b/src/analyzer.rs
@@ -35,6 +35,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
root: &Path,
) -> CompileResult<'src, Justfile<'src>> {
let mut definitions = HashMap::new();
+ let mut imports = HashSet::new();
let mut stack = Vec::new();
let ast = asts.get(root).unwrap();
@@ -54,7 +55,9 @@ impl<'run, 'src> Analyzer<'run, 'src> {
Item::Comment(_) => (),
Item::Import { absolute, .. } => {
if let Some(absolute) = absolute {
- stack.push(asts.get(absolute).unwrap());
+ if imports.insert(absolute) {
+ stack.push(asts.get(absolute).unwrap());
+ }
}
}
Item::Module {
diff --git a/src/compiler.rs b/src/compiler.rs
index 1a555ebabb..e8d96f2edb 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -21,7 +21,6 @@ impl Compiler {
let tokens = Lexer::lex(relative, src)?;
let mut ast = Parser::parse(
current.file_depth,
- ¤t.path,
¤t.import_offsets,
¤t.namepath,
&tokens,
@@ -214,14 +213,7 @@ impl Compiler {
#[cfg(test)]
pub(crate) fn test_compile(src: &str) -> CompileResult {
let tokens = Lexer::test_lex(src)?;
- let ast = Parser::parse(
- 0,
- &PathBuf::new(),
- &[],
- &Namepath::default(),
- &tokens,
- &PathBuf::new(),
- )?;
+ let ast = Parser::parse(0, &[], &Namepath::default(), &tokens, &PathBuf::new())?;
let root = PathBuf::from("justfile");
let mut asts: HashMap = HashMap::new();
asts.insert(root.clone(), ast);
diff --git a/src/parser.rs b/src/parser.rs
index ea727204e5..99544acd13 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -26,7 +26,6 @@ use {super::*, TokenKind::*};
pub(crate) struct Parser<'run, 'src> {
expected_tokens: BTreeSet,
file_depth: u32,
- file_path: &'run Path,
import_offsets: Vec,
module_namepath: &'run Namepath<'src>,
next_token: usize,
@@ -39,7 +38,6 @@ impl<'run, 'src> Parser<'run, 'src> {
/// Parse `tokens` into an `Ast`
pub(crate) fn parse(
file_depth: u32,
- file_path: &'run Path,
import_offsets: &[usize],
module_namepath: &'run Namepath<'src>,
tokens: &'run [Token<'src>],
@@ -48,7 +46,6 @@ impl<'run, 'src> Parser<'run, 'src> {
Self {
expected_tokens: BTreeSet::new(),
file_depth,
- file_path,
import_offsets: import_offsets.to_vec(),
module_namepath,
next_token: 0,
@@ -910,7 +907,6 @@ impl<'run, 'src> Parser<'run, 'src> {
dependencies,
doc,
file_depth: self.file_depth,
- file_path: self.file_path.into(),
import_offsets: self.import_offsets.clone(),
name,
namepath: self.module_namepath.join(name),
@@ -1162,15 +1158,8 @@ mod tests {
fn test(text: &str, want: Tree) {
let unindented = unindent(text);
let tokens = Lexer::test_lex(&unindented).expect("lexing failed");
- let justfile = Parser::parse(
- 0,
- &PathBuf::new(),
- &[],
- &Namepath::default(),
- &tokens,
- &PathBuf::new(),
- )
- .expect("parsing failed");
+ let justfile = Parser::parse(0, &[], &Namepath::default(), &tokens, &PathBuf::new())
+ .expect("parsing failed");
let have = justfile.tree();
if have != want {
println!("parsed text: {unindented}");
@@ -1208,14 +1197,7 @@ mod tests {
) {
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
- match Parser::parse(
- 0,
- &PathBuf::new(),
- &[],
- &Namepath::default(),
- &tokens,
- &PathBuf::new(),
- ) {
+ match Parser::parse(0, &[], &Namepath::default(), &tokens, &PathBuf::new()) {
Ok(_) => panic!("Parsing unexpectedly succeeded"),
Err(have) => {
let want = CompileError {
diff --git a/src/recipe.rs b/src/recipe.rs
index bb9fe14e33..05516225a6 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -26,8 +26,6 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
#[serde(skip)]
pub(crate) file_depth: u32,
#[serde(skip)]
- pub(crate) file_path: PathBuf,
- #[serde(skip)]
pub(crate) import_offsets: Vec,
pub(crate) name: Name<'src>,
pub(crate) namepath: Namepath<'src>,
diff --git a/src/testing.rs b/src/testing.rs
index a10c398834..12069feede 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -59,15 +59,8 @@ pub(crate) fn analysis_error(
) {
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
- let ast = Parser::parse(
- 0,
- &PathBuf::new(),
- &[],
- &Namepath::default(),
- &tokens,
- &PathBuf::new(),
- )
- .expect("Parsing failed in analysis test...");
+ let ast = Parser::parse(0, &[], &Namepath::default(), &tokens, &PathBuf::new())
+ .expect("Parsing failed in analysis test...");
let root = PathBuf::from("justfile");
let mut asts: HashMap = HashMap::new();
diff --git a/src/unresolved_recipe.rs b/src/unresolved_recipe.rs
index 6cbc95da91..661d75649b 100644
--- a/src/unresolved_recipe.rs
+++ b/src/unresolved_recipe.rs
@@ -50,7 +50,6 @@ impl<'src> UnresolvedRecipe<'src> {
dependencies,
doc: self.doc,
file_depth: self.file_depth,
- file_path: self.file_path,
import_offsets: self.import_offsets,
name: self.name,
namepath: self.namepath,
diff --git a/tests/imports.rs b/tests/imports.rs
index 693a67cd3b..93b3fc75c4 100644
--- a/tests/imports.rs
+++ b/tests/imports.rs
@@ -360,3 +360,51 @@ fn reused_import_are_allowed() {
})
.run();
}
+
+#[test]
+fn multiply_imported_items_do_not_conflict() {
+ Test::new()
+ .justfile(
+ "
+ import 'a.just'
+ import 'a.just'
+ foo: bar
+ ",
+ )
+ .write(
+ "a.just",
+ "
+x := 'y'
+
+@bar:
+ echo hello
+",
+ )
+ .stdout("hello\n")
+ .run();
+}
+
+#[test]
+fn nested_multiply_imported_items_do_not_conflict() {
+ Test::new()
+ .justfile(
+ "
+ import 'a.just'
+ import 'b.just'
+ foo: bar
+ ",
+ )
+ .write("a.just", "import 'c.just'")
+ .write("b.just", "import 'c.just'")
+ .write(
+ "c.just",
+ "
+x := 'y'
+
+@bar:
+ echo hello
+",
+ )
+ .stdout("hello\n")
+ .run();
+}