Repository: casey/just
Pull request #2139 (2024-06-08 07:36:13)

is_dependency function
https://github.com/casey/just/issues/1930


Patch:
diff --git a/README.md b/README.md
index 9efa80a6..040639f0 100644
--- a/README.md
+++ b/README.md
@@ -1426,6 +1426,12 @@ $ just
 - `env(key)`<sup>1.15.0</sup> — Alias for `env_var(key)`.
 - `env(key, default)`<sup>1.15.0</sup> — Alias for `env_var_or_default(key, default)`.
 
+#### Invocation Information
+
+- `is_dependency()` - Returns the string `true` if the current recipe is being
+  run as a dependency of another recipe, rather than being run directly,
+  otherwise returns the string `false`.
+
 #### Invocation Directory
 
 - `invocation_directory()` - Retrieves the absolute path to the current
diff --git a/src/evaluator.rs b/src/evaluator.rs
index ba336770..82260b84 100644
--- a/src/evaluator.rs
+++ b/src/evaluator.rs
@@ -3,6 +3,7 @@ use super::*;
 pub(crate) struct Evaluator<'src: 'run, 'run> {
   pub(crate) assignments: Option<&'run Table<'src, Assignment<'src>>>,
   pub(crate) context: ExecutionContext<'src, 'run>,
+  pub(crate) is_dependency: bool,
   pub(crate) scope: Scope<'src, 'run>,
 }
 
@@ -49,6 +50,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
       context,
       assignments: Some(&module.assignments),
       scope,
+      is_dependency: false,
     };
 
     for assignment in module.assignments.values() {
@@ -280,10 +282,11 @@ impl<'src, 'run> Evaluator<'src, 'run> {
 
   pub(crate) fn evaluate_parameters(
     context: &ExecutionContext<'src, 'run>,
+    is_dependency: bool,
     arguments: &[String],
     parameters: &[Parameter<'src>],
   ) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
-    let mut evaluator = Self::new(context, context.scope);
+    let mut evaluator = Self::new(context, is_dependency, context.scope);
 
     let mut positional = Vec::new();
 
@@ -324,11 +327,13 @@ impl<'src, 'run> Evaluator<'src, 'run> {
 
   pub(crate) fn new(
     context: &ExecutionContext<'src, 'run>,
+    is_dependency: bool,
     scope: &'run Scope<'src, 'run>,
   ) -> Self {
     Self {
-      context: *context,
       assignments: None,
+      context: *context,
+      is_dependency,
       scope: scope.child(),
     }
   }
diff --git a/src/function.rs b/src/function.rs
index d44bbb5e..206faf8c 100644
--- a/src/function.rs
+++ b/src/function.rs
@@ -59,6 +59,7 @@ pub(crate) fn get(name: &str) -> Option<Function> {
     "home_directory" => Nullary(|_| dir("home", dirs::home_dir)),
     "invocation_directory" => Nullary(invocation_directory),
     "invocation_directory_native" => Nullary(invocation_directory_native),
+    "is_dependency" => Nullary(is_dependency),
     "join" => BinaryPlus(join),
     "just_executable" => Nullary(just_executable),
     "just_pid" => Nullary(just_pid),
@@ -341,6 +342,10 @@ fn invocation_directory_native(context: Context) -> Result<String, String> {
     })
 }
 
+fn is_dependency(context: Context) -> Result<String, String> {
+  Ok(context.evaluator.is_dependency.to_string())
+}
+
 fn prepend(_context: Context, prefix: &str, s: &str) -> Result<String, String> {
   Ok(
     s.split_whitespace()
diff --git a/src/justfile.rs b/src/justfile.rs
index be5d5f8a..5b3f0bf7 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -257,6 +257,7 @@ impl<'src> Justfile<'src> {
         &context,
         &mut ran,
         invocation.recipe,
+        false,
       )?;
     }
 
@@ -385,6 +386,7 @@ impl<'src> Justfile<'src> {
     context: &ExecutionContext<'src, '_>,
     ran: &mut Ran<'src>,
     recipe: &Recipe<'src>,
+    is_dependency: bool,
   ) -> RunResult<'src> {
     if ran.has_run(&recipe.namepath, arguments) {
       return Ok(());
@@ -397,11 +399,11 @@ impl<'src> Justfile<'src> {
     }
 
     let (outer, positional) =
-      Evaluator::evaluate_parameters(context, arguments, &recipe.parameters)?;
+      Evaluator::evaluate_parameters(context, is_dependency, arguments, &recipe.parameters)?;
 
     let scope = outer.child();
 
-    let mut evaluator = Evaluator::new(context, &scope);
+    let mut evaluator = Evaluator::new(context, true, &scope);
 
     if !context.config.no_dependencies {
       for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
@@ -410,11 +412,11 @@ impl<'src> Justfile<'src> {
           .map(|argument| evaluator.evaluate_expression(argument))
           .collect::<RunResult<Vec<String>>>()?;
 
-        Self::run_recipe(&arguments, context, ran, recipe)?;
+        Self::run_recipe(&arguments, context, ran, recipe, true)?;
       }
     }
 
-    recipe.run(context, &scope, &positional)?;
+    recipe.run(context, &scope, &positional, is_dependency)?;
 
     if !context.config.no_dependencies {
       let mut ran = Ran::default();
@@ -426,7 +428,7 @@ impl<'src> Justfile<'src> {
           evaluated.push(evaluator.evaluate_expression(argument)?);
         }
 
-        Self::run_recipe(&evaluated, context, &mut ran, recipe)?;
+        Self::run_recipe(&evaluated, context, &mut ran, recipe, true)?;
       }
     }
 
diff --git a/src/recipe.rs b/src/recipe.rs
index ac068394..13b3bb99 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -149,6 +149,7 @@ impl<'src, D> Recipe<'src, D> {
     context: &ExecutionContext<'src, 'run>,
     scope: &Scope<'src, 'run>,
     positional: &[String],
+    is_dependency: bool,
   ) -> RunResult<'src, ()> {
     let config = &context.config;
 
@@ -162,7 +163,7 @@ impl<'src, D> Recipe<'src, D> {
       );
     }
 
-    let evaluator = Evaluator::new(context, scope);
+    let evaluator = Evaluator::new(context, is_dependency, scope);
 
     if self.shebang {
       self.run_shebang(context, scope, positional, config, evaluator)


Test patch:
diff --git a/tests/functions.rs b/tests/functions.rs
index caf772c5..bfb05f25 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -1027,3 +1027,26 @@ import
     )
     .run();
 }
+
+#[test]
+fn is_dependency() {
+  let justfile = "
+    alpha: beta
+      @echo 'alpha {{is_dependency()}}'
+    beta: && gamma
+      @echo 'beta {{is_dependency()}}'
+    gamma:
+      @echo 'gamma {{is_dependency()}}'
+  ";
+  Test::new()
+    .args(["alpha"])
+    .justfile(justfile)
+    .stdout("beta true\ngamma true\nalpha false\n")
+    .run();
+
+  Test::new()
+    .args(["beta"])
+    .justfile(justfile)
+    .stdout("beta false\ngamma true\n")
+    .run();
+}