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?
| 'set' 'ignore-comments' boolean?
| 'set' 'export' boolean?
| 'set' 'fallback' boolean?
| 'set' 'positional-arguments' boolean?
| 'set' 'allow-duplicate-recipes' 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. |
| `dotenv-load` | boolean | False | Load a `.env` file, if present. |
| `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 `#`. |
| `positional-arguments` | boolean | False | Pass positional arguments. |
| `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
If a recipe is not found, `just` will look for `justfile`s in the parent
directory and up, until it reaches the root directory.
If a recipe is not found in a `justfile` and the `fallback` setting is set,
`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
`--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`:
```make
set fallback
foo:
echo foo
```

View File

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

View File

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

View File

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

View File

@ -761,8 +761,9 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
Some(Setting::AllowDuplicateRecipes(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::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::WindowsPowershell => Some(Setting::WindowsPowerShell(self.parse_set_bool()?)),
_ => None,

View File

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

View File

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

View File

@ -6,6 +6,40 @@ fn runs_recipe_in_parent_if_not_found_in_current() {
.tree(tree! {
bar: {
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:
echo subdir
"
@ -36,6 +70,8 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
.tree(tree! {
bar: {
justfile: "
set fallback
baz:
echo subdir
"
@ -64,6 +100,8 @@ fn requires_unstable() {
.tree(tree! {
bar: {
justfile: "
set fallback
baz:
echo subdir
"
@ -83,7 +121,7 @@ fn requires_unstable() {
}
#[test]
fn works_with_provided_search_directory() {
fn requires_setting() {
Test::new()
.tree(tree! {
bar: {
@ -99,6 +137,34 @@ fn works_with_provided_search_directory() {
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"])
.stdout("root\n")
.stderr(format!(
@ -118,6 +184,8 @@ fn doesnt_work_with_justfile() {
.tree(tree! {
bar: {
justfile: "
set fallback
baz:
echo subdir
"
@ -125,6 +193,8 @@ fn doesnt_work_with_justfile() {
})
.justfile(
"
set fallback
foo:
echo root
",
@ -142,6 +212,8 @@ fn doesnt_work_with_justfile_and_working_directory() {
.tree(tree! {
bar: {
justfile: "
set fallback
baz:
echo subdir
"
@ -149,6 +221,8 @@ fn doesnt_work_with_justfile_and_working_directory() {
})
.justfile(
"
set fallback
foo:
echo root
",
@ -173,6 +247,8 @@ fn prints_correct_error_message_when_recipe_not_found() {
.tree(tree! {
bar: {
justfile: "
set fallback
bar:
echo subdir
"
@ -196,3 +272,82 @@ fn prints_correct_error_message_when_recipe_not_found() {
))
.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,
"dotenv_load": null,
"export": false,
"fallback": false,
"positional_arguments": false,
"shell": null,
"ignore_comments": false,
@ -72,6 +73,7 @@ fn assignment() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -115,6 +117,7 @@ fn body() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -168,6 +171,7 @@ fn dependencies() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -258,6 +262,7 @@ fn dependency_argument() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -312,6 +317,7 @@ fn duplicate_recipes() {
"allow_duplicate_recipes": true,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -348,6 +354,7 @@ fn doc_comment() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -372,6 +379,7 @@ fn empty_justfile() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -505,6 +513,7 @@ fn parameters() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -577,6 +586,7 @@ fn priors() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -613,6 +623,7 @@ fn private() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -649,6 +660,7 @@ fn quiet() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -676,6 +688,7 @@ fn settings() {
"
set dotenv-load
set export
set fallback
set positional-arguments
set ignore-comments
set shell := ['a', 'b', 'c']
@ -704,6 +717,7 @@ fn settings() {
"allow_duplicate_recipes": false,
"dotenv_load": true,
"export": true,
"fallback": true,
"ignore_comments": true,
"positional_arguments": true,
"shell": {
@ -746,6 +760,7 @@ fn shebang() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
@ -782,6 +797,7 @@ fn simple() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"fallback": false,
"positional_arguments": false,
"shell": null,
"ignore_comments": false,