Repository: casey/just (Rust)

Issue:
Dependency executed twice when order changes
Perhaps a bug, or I am the bug, but I cannot get `just` to handle dependency execution like it does in the docs. The docs state "Dependencies will always run first, even if they are passed after a recipe that depends on them" and then shows: 
```$ just test build
cc main.c foo.c bar.c -o main
./test
testing… all tests passed!
```
In this case, `build` is only ran once.

If I replicate this, but with some simple `echo` commands, I get the dependency being executed twice:
```
build:
    echo "building something"

test: build
    echo "testing something"
```
```
$ just test build
echo "building something"
building something
echo "testing something"
testing something
echo "building something"
building something
```
To get the intended behavior, I have to call `build` before `test`:
```
$just build test
echo "building something"
building something
echo "testing something"
testing something
```

The end goal is to have multiple recipes have the same dependency but only execute said dependency once, irregardless of order. Since the docs seem to show it being possible, I am wondering if I am the bug?


Discussion:
Nice catch! I'm surprised also, I would have expected the behavior documented in the readme. I tested old revisions, and it looks like it was never the case that it worked that way.

I created this justfile:

```
foo: bar
  @echo foo

bar:
  @echo bar
```

And ran `just foo foo`. All versions of just that I could find printed out:

```
bar
foo
foo
```

So `foo` always runs twice, but `bar` only runs once. This seems like a bug to me. Either top level arguments should be treated as complete invocations and run their dependencies every time they're called. Or they should only run if they haven't been run before.

I'm leaning towards making it so it behaves as it's documented in the readme. `just foo foo` would run `bar`, then `foo`, then terminate.
Make only runs foo once:

```
$ make foo foo
bar
foo
make: `foo' is up to date.
```
Yep, the Make behavior was a check of ours before migrating to just. I'd vote for keeping the functionality the same, for whatever that's worth :). At least in our case we may invoke several recipes with the same dependency which only needs to run once.

In any case, we will use continue to migrate to just. It's just too slick not to ;)
Fixed! I'm in the process of publishing 1.1.3 with the change.
> Fixed! I'm in the process of publishing 1.1.3 with the change.

Legend! I'll give it a test sometime today.
And it works! 
Nice!



Patch:
diff --git a/src/justfile.rs b/src/justfile.rs
index ce0ce9fd27..718f2fd069 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -284,6 +284,15 @@ impl<'src> Justfile<'src> {
     search: &Search,
     ran: &mut BTreeSet<Vec<String>>,
   ) -> RunResult<'src, ()> {
+    let mut invocation = vec![recipe.name().to_owned()];
+    for argument in arguments {
+      invocation.push((*argument).to_string());
+    }
+
+    if ran.contains(&invocation) {
+      return Ok(());
+    }
+
     let (outer, positional) = Evaluator::evaluate_parameters(
       context.config,
       dotenv,
@@ -300,20 +309,19 @@ impl<'src> Justfile<'src> {
       Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
 
     for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
-      let mut invocation = vec![recipe.name().to_owned()];
-
-      for argument in arguments {
-        invocation.push(evaluator.evaluate_expression(argument)?);
-      }
-
-      if !ran.contains(&invocation) {
-        let arguments = invocation
-          .iter()
-          .skip(1)
-          .map(String::as_ref)
-          .collect::<Vec<&str>>();
-        self.run_recipe(context, recipe, &arguments, dotenv, search, ran)?;
-      }
+      let arguments = arguments
+        .iter()
+        .map(|argument| evaluator.evaluate_expression(argument))
+        .collect::<RunResult<Vec<String>>>()?;
+
+      self.run_recipe(
+        context,
+        recipe,
+        &arguments.iter().map(String::as_ref).collect::<Vec<&str>>(),
+        dotenv,
+        search,
+        ran,
+      )?;
     }
 
     recipe.run(context, dotenv, scope.child(), search, &positional)?;
@@ -339,11 +347,6 @@ impl<'src> Justfile<'src> {
       }
     }
 
-    let mut invocation = vec![recipe.name().to_owned()];
-    for argument in arguments {
-      invocation.push((*argument).to_string());
-    }
-
     ran.insert(invocation);
     Ok(())
   }


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 798b045517..04ca439820 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -33,6 +33,7 @@ mod quiet;
 mod quote;
 mod readme;
 mod regexes;
+mod run;
 mod search;
 mod shebang;
 mod shell;
diff --git a/tests/run.rs b/tests/run.rs
new file mode 100644
index 0000000000..2d7d7d3208
--- /dev/null
+++ b/tests/run.rs
@@ -0,0 +1,19 @@
+use crate::common::*;
+
+#[test]
+fn dont_run_duplicate_recipes() {
+  Test::new()
+    .justfile(
+      "
+      foo:
+        # foo
+    ",
+    )
+    .args(&["foo", "foo"])
+    .stderr(
+      "
+      # foo
+    ",
+    )
+    .run();
+}