Require set fallback := true to enable recipe fallback (#1368)

This commit is contained in:
Casey Rodarmor 2022-10-19 19:00:09 -07:00 committed by GitHub
parent ca614ad117
commit 28be873dfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 200 additions and 11 deletions

View File

@ -62,6 +62,7 @@ export : 'export' assignment
setting : 'set' 'dotenv-load' boolean? setting : 'set' 'dotenv-load' boolean?
| 'set' 'ignore-comments' boolean? | 'set' 'ignore-comments' boolean?
| 'set' 'export' boolean? | 'set' 'export' boolean?
| 'set' 'fallback' boolean?
| 'set' 'positional-arguments' boolean? | 'set' 'positional-arguments' boolean?
| 'set' 'allow-duplicate-recipes' boolean? | 'set' 'allow-duplicate-recipes' boolean?
| 'set' 'windows-powershell' boolean? | 'set' 'windows-powershell' boolean?

View File

@ -646,6 +646,7 @@ foo:
| `allow-duplicate-recipes` | boolean | False | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. | | `allow-duplicate-recipes` | boolean | False | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. |
| `dotenv-load` | boolean | False | Load a `.env` file, if present. | | `dotenv-load` | boolean | False | Load a `.env` file, if present. |
| `export` | boolean | False | Export all variables as environment variables. | | `export` | boolean | False | Export all variables as environment variables. |
| `fallback` | boolean | False | Search `justfile` in parent directory if the first recipe on the command line is not found. |
| `ignore-comments` | boolean | False | Ignore recipe lines beginning with `#`. | | `ignore-comments` | boolean | False | Ignore recipe lines beginning with `#`. |
| `positional-arguments` | boolean | False | Pass positional arguments. | | `positional-arguments` | boolean | False | Pass positional arguments. |
| `shell` | `[COMMAND, ARGS…]` | - | Set the command used to invoke recipes and evaluate backticks. | | `shell` | `[COMMAND, ARGS…]` | - | Set the command used to invoke recipes and evaluate backticks. |
@ -2063,8 +2064,10 @@ The `--dump` command can be used with `--dump-format json` to print a JSON repre
### Falling back to parent `justfile`s ### Falling back to parent `justfile`s
If a recipe is not found, `just` will look for `justfile`s in the parent If a recipe is not found in a `justfile` and the `fallback` setting is set,
directory and up, until it reaches the root directory. `just` will look for `justfile`s in the parent directory and up, until it
reaches the root directory. `just` will stop after it reaches a `justfile` in
which the `fallback` setting is `false` or unset.
This feature is currently unstable, and so must be enabled with the This feature is currently unstable, and so must be enabled with the
`--unstable` flag. `--unstable` flag.
@ -2072,6 +2075,7 @@ This feature is currently unstable, and so must be enabled with the
As an example, suppose the current directory contains this `justfile`: As an example, suppose the current directory contains this `justfile`:
```make ```make
set fallback
foo: foo:
echo foo echo foo
``` ```

View File

@ -53,6 +53,9 @@ impl<'src> Analyzer<'src> {
Setting::Export(export) => { Setting::Export(export) => {
settings.export = export; settings.export = export;
} }
Setting::Fallback(fallback) => {
settings.fallback = fallback;
}
Setting::IgnoreComments(ignore_comments) => { Setting::IgnoreComments(ignore_comments) => {
settings.ignore_comments = ignore_comments; settings.ignore_comments = ignore_comments;
} }

View File

@ -8,6 +8,7 @@ pub(crate) enum Keyword {
DotenvLoad, DotenvLoad,
Else, Else,
Export, Export,
Fallback,
False, False,
If, If,
IgnoreComments, IgnoreComments,

View File

@ -227,6 +227,7 @@ impl<'src> Node<'src> for Set<'src> {
Setting::AllowDuplicateRecipes(value) Setting::AllowDuplicateRecipes(value)
| Setting::DotenvLoad(value) | Setting::DotenvLoad(value)
| Setting::Export(value) | Setting::Export(value)
| Setting::Fallback(value)
| Setting::PositionalArguments(value) | Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) | Setting::WindowsPowerShell(value)
| Setting::IgnoreComments(value) => { | Setting::IgnoreComments(value) => {

View File

@ -761,8 +761,9 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
Some(Setting::AllowDuplicateRecipes(self.parse_set_bool()?)) Some(Setting::AllowDuplicateRecipes(self.parse_set_bool()?))
} }
Keyword::DotenvLoad => Some(Setting::DotenvLoad(self.parse_set_bool()?)), Keyword::DotenvLoad => Some(Setting::DotenvLoad(self.parse_set_bool()?)),
Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
Keyword::Export => Some(Setting::Export(self.parse_set_bool()?)), Keyword::Export => Some(Setting::Export(self.parse_set_bool()?)),
Keyword::Fallback => Some(Setting::Fallback(self.parse_set_bool()?)),
Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)), Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)),
Keyword::WindowsPowershell => Some(Setting::WindowsPowerShell(self.parse_set_bool()?)), Keyword::WindowsPowershell => Some(Setting::WindowsPowerShell(self.parse_set_bool()?)),
_ => None, _ => None,

View File

@ -4,8 +4,9 @@ use super::*;
pub(crate) enum Setting<'src> { pub(crate) enum Setting<'src> {
AllowDuplicateRecipes(bool), AllowDuplicateRecipes(bool),
DotenvLoad(bool), DotenvLoad(bool),
IgnoreComments(bool),
Export(bool), Export(bool),
Fallback(bool),
IgnoreComments(bool),
PositionalArguments(bool), PositionalArguments(bool),
Shell(Shell<'src>), Shell(Shell<'src>),
WindowsPowerShell(bool), WindowsPowerShell(bool),
@ -17,8 +18,9 @@ impl<'src> Display for Setting<'src> {
match self { match self {
Setting::AllowDuplicateRecipes(value) Setting::AllowDuplicateRecipes(value)
| Setting::DotenvLoad(value) | Setting::DotenvLoad(value)
| Setting::IgnoreComments(value)
| Setting::Export(value) | Setting::Export(value)
| Setting::Fallback(value)
| Setting::IgnoreComments(value)
| Setting::PositionalArguments(value) | Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => write!(f, "{}", value), | Setting::WindowsPowerShell(value) => write!(f, "{}", value),
Setting::Shell(shell) | Setting::WindowsShell(shell) => write!(f, "{}", shell), Setting::Shell(shell) | Setting::WindowsShell(shell) => write!(f, "{}", shell),

View File

@ -10,6 +10,7 @@ pub(crate) struct Settings<'src> {
pub(crate) allow_duplicate_recipes: bool, pub(crate) allow_duplicate_recipes: bool,
pub(crate) dotenv_load: Option<bool>, pub(crate) dotenv_load: Option<bool>,
pub(crate) export: bool, pub(crate) export: bool,
pub(crate) fallback: bool,
pub(crate) ignore_comments: bool, pub(crate) ignore_comments: bool,
pub(crate) positional_arguments: bool, pub(crate) positional_arguments: bool,
pub(crate) shell: Option<Shell<'src>>, pub(crate) shell: Option<Shell<'src>>,

View File

@ -135,7 +135,7 @@ impl Subcommand {
}; };
match Self::run_inner(config, loader, arguments, overrides, &search) { match Self::run_inner(config, loader, arguments, overrides, &search) {
Err(err @ Error::UnknownRecipes { .. }) => { Err((err @ Error::UnknownRecipes { .. }, true)) => {
match search.justfile.parent().unwrap().parent() { match search.justfile.parent().unwrap().parent() {
Some(parent) => { Some(parent) => {
unknown_recipes_errors.get_or_insert(err); unknown_recipes_errors.get_or_insert(err);
@ -144,7 +144,7 @@ impl Subcommand {
None => return Err(err), None => return Err(err),
} }
} }
result => return result, result => return result.map_err(|(err, _fallback)| err),
} }
} }
} else { } else {
@ -155,6 +155,7 @@ impl Subcommand {
overrides, overrides,
&Search::find(&config.search_config, &config.invocation_directory)?, &Search::find(&config.search_config, &config.invocation_directory)?,
) )
.map_err(|(err, _fallback)| err)
} }
} }
@ -164,9 +165,12 @@ impl Subcommand {
arguments: &[String], arguments: &[String],
overrides: &BTreeMap<String, String>, overrides: &BTreeMap<String, String>,
search: &Search, search: &Search,
) -> Result<(), Error<'src>> { ) -> Result<(), (Error<'src>, bool)> {
let (_src, _ast, justfile) = Self::compile(config, loader, search)?; let (_src, _ast, justfile) =
justfile.run(config, search, overrides, arguments) Self::compile(config, loader, search).map_err(|err| (err, false))?;
justfile
.run(config, search, overrides, arguments)
.map_err(|err| (err, justfile.settings.fallback))
} }
fn compile<'src>( fn compile<'src>(

View File

@ -6,6 +6,40 @@ fn runs_recipe_in_parent_if_not_found_in_current() {
.tree(tree! { .tree(tree! {
bar: { bar: {
justfile: " justfile: "
set fallback
baz:
echo subdir
"
}
})
.justfile(
"
foo:
echo root
",
)
.args(&["--unstable", "foo"])
.current_dir("bar")
.stderr(format!(
"
Trying ..{}justfile
echo root
",
MAIN_SEPARATOR
))
.stdout("root\n")
.run();
}
#[test]
fn setting_accepts_value() {
Test::new()
.tree(tree! {
bar: {
justfile: "
set fallback := true
baz: baz:
echo subdir echo subdir
" "
@ -36,6 +70,8 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
.tree(tree! { .tree(tree! {
bar: { bar: {
justfile: " justfile: "
set fallback
baz: baz:
echo subdir echo subdir
" "
@ -64,6 +100,8 @@ fn requires_unstable() {
.tree(tree! { .tree(tree! {
bar: { bar: {
justfile: " justfile: "
set fallback
baz: baz:
echo subdir echo subdir
" "
@ -83,7 +121,7 @@ fn requires_unstable() {
} }
#[test] #[test]
fn works_with_provided_search_directory() { fn requires_setting() {
Test::new() Test::new()
.tree(tree! { .tree(tree! {
bar: { bar: {
@ -99,6 +137,34 @@ fn works_with_provided_search_directory() {
echo root echo root
", ",
) )
.args(&["--unstable", "foo"])
.current_dir("bar")
.status(EXIT_FAILURE)
.stderr("error: Justfile does not contain recipe `foo`.\n")
.run();
}
#[test]
fn works_with_provided_search_directory() {
Test::new()
.tree(tree! {
bar: {
justfile: "
set fallback
baz:
echo subdir
"
}
})
.justfile(
"
set fallback
foo:
echo root
",
)
.args(&["--unstable", "./foo"]) .args(&["--unstable", "./foo"])
.stdout("root\n") .stdout("root\n")
.stderr(format!( .stderr(format!(
@ -118,6 +184,8 @@ fn doesnt_work_with_justfile() {
.tree(tree! { .tree(tree! {
bar: { bar: {
justfile: " justfile: "
set fallback
baz: baz:
echo subdir echo subdir
" "
@ -125,6 +193,8 @@ fn doesnt_work_with_justfile() {
}) })
.justfile( .justfile(
" "
set fallback
foo: foo:
echo root echo root
", ",
@ -142,6 +212,8 @@ fn doesnt_work_with_justfile_and_working_directory() {
.tree(tree! { .tree(tree! {
bar: { bar: {
justfile: " justfile: "
set fallback
baz: baz:
echo subdir echo subdir
" "
@ -149,6 +221,8 @@ fn doesnt_work_with_justfile_and_working_directory() {
}) })
.justfile( .justfile(
" "
set fallback
foo: foo:
echo root echo root
", ",
@ -173,6 +247,8 @@ fn prints_correct_error_message_when_recipe_not_found() {
.tree(tree! { .tree(tree! {
bar: { bar: {
justfile: " justfile: "
set fallback
bar: bar:
echo subdir echo subdir
" "
@ -196,3 +272,82 @@ fn prints_correct_error_message_when_recipe_not_found() {
)) ))
.run(); .run();
} }
#[test]
fn multiple_levels_of_fallback_work() {
Test::new()
.tree(tree! {
a: {
b: {
justfile: "
set fallback
foo:
echo subdir
"
},
justfile: "
set fallback
bar:
echo subdir
"
}
})
.justfile(
"
baz:
echo root
",
)
.args(&["--unstable", "baz"])
.current_dir("a/b")
.stdout("root\n")
.stderr(format!(
"
Trying ..{}justfile
Trying ..{}..{}justfile
echo root
",
MAIN_SEPARATOR, MAIN_SEPARATOR, MAIN_SEPARATOR
))
.run();
}
#[test]
fn stop_fallback_when_fallback_is_false() {
Test::new()
.tree(tree! {
a: {
b: {
justfile: "
set fallback
foo:
echo subdir
"
},
justfile: "
bar:
echo subdir
"
}
})
.justfile(
"
baz:
echo root
",
)
.args(&["--unstable", "baz"])
.current_dir("a/b")
.stderr(format!(
"
Trying ..{}justfile
error: Justfile does not contain recipe `baz`.
Did you mean `bar`?
",
MAIN_SEPARATOR
))
.status(EXIT_FAILURE)
.run();
}

View File

@ -42,6 +42,7 @@ fn alias() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"ignore_comments": false, "ignore_comments": false,
@ -72,6 +73,7 @@ fn assignment() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -115,6 +117,7 @@ fn body() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -168,6 +171,7 @@ fn dependencies() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -258,6 +262,7 @@ fn dependency_argument() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -312,6 +317,7 @@ fn duplicate_recipes() {
"allow_duplicate_recipes": true, "allow_duplicate_recipes": true,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -348,6 +354,7 @@ fn doc_comment() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -372,6 +379,7 @@ fn empty_justfile() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -505,6 +513,7 @@ fn parameters() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -577,6 +586,7 @@ fn priors() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -613,6 +623,7 @@ fn private() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -649,6 +660,7 @@ fn quiet() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -676,6 +688,7 @@ fn settings() {
" "
set dotenv-load set dotenv-load
set export set export
set fallback
set positional-arguments set positional-arguments
set ignore-comments set ignore-comments
set shell := ['a', 'b', 'c'] set shell := ['a', 'b', 'c']
@ -704,6 +717,7 @@ fn settings() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": true, "dotenv_load": true,
"export": true, "export": true,
"fallback": true,
"ignore_comments": true, "ignore_comments": true,
"positional_arguments": true, "positional_arguments": true,
"shell": { "shell": {
@ -746,6 +760,7 @@ fn shebang() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"ignore_comments": false, "ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
@ -782,6 +797,7 @@ fn simple() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"fallback": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"ignore_comments": false, "ignore_comments": false,