Skip to content

Commit

Permalink
Search for missing recipes in parent directory justfiles (#1149)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Mar 31, 2022
1 parent 1c92974 commit 52f73db
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 56 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,35 @@ default:

The `--dump` command can be used with `--dump-format json` to print a JSON representation of a `justfile`. The JSON format is currently unstable, so the `--unstable` flag is required.

### 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.

This feature is currently unstable, and so must be enabled with the
`--unstable` flag.

As an example, suppose the current directory contains this `justfile`:

```make
foo:
echo foo
```

And the parent directory contains this `justfile`:

```make
bar:
echo bar
```

```sh
$ just --unstable bar
Trying ../justfile
echo bar
bar
```

Changelog
---------

Expand Down
2 changes: 1 addition & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) use std::{
iter::{self, FromIterator},
mem,
ops::{Index, Range, RangeInclusive},
path::{Path, PathBuf},
path::{self, Path, PathBuf},
process::{self, Command, ExitStatus, Stdio},
rc::Rc,
str::{self, Chars},
Expand Down
6 changes: 5 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,11 @@ impl Config {
}

pub(crate) fn run<'src>(self, loader: &'src Loader) -> Result<(), Error<'src>> {
self.subcommand.run(&self, loader)
if let Err(error) = InterruptHandler::install(self.verbosity) {
warn!("Failed to set CTRL-C handler: {}", error);
}

self.subcommand.execute(&self, loader)
}
}

Expand Down
8 changes: 2 additions & 6 deletions src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ impl<'src> Justfile<'src> {
overrides: &BTreeMap<String, String>,
arguments: &[String],
) -> RunResult<'src, ()> {
if let Err(error) = InterruptHandler::install(config.verbosity) {
warn!("Failed to set CTRL-C handler: {}", error);
}

let unknown_overrides = overrides
.keys()
.filter(|name| !self.assignments.contains_key(name.as_str()))
Expand Down Expand Up @@ -344,8 +340,8 @@ impl<'src> Justfile<'src> {
}

let mut invocation = vec![recipe.name().to_owned()];
for argument in arguments.iter().copied() {
invocation.push(argument.to_owned());
for argument in arguments {
invocation.push((*argument).to_string());
}

ran.insert(invocation);
Expand Down
25 changes: 12 additions & 13 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,7 @@ impl Search {
invocation_directory: &Path,
) -> SearchResult<Self> {
match search_config {
SearchConfig::FromInvocationDirectory => {
let justfile = Self::justfile(invocation_directory)?;

let working_directory = Self::working_directory_from_justfile(&justfile)?;

Ok(Self {
justfile,
working_directory,
})
}

SearchConfig::FromInvocationDirectory => Self::find_next(invocation_directory),
SearchConfig::FromSearchDirectory { search_directory } => {
let search_directory = Self::clean(invocation_directory, search_directory);

Expand All @@ -40,7 +30,6 @@ impl Search {
working_directory,
})
}

SearchConfig::WithJustfile { justfile } => {
let justfile = Self::clean(invocation_directory, justfile);

Expand All @@ -51,7 +40,6 @@ impl Search {
working_directory,
})
}

SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,
Expand All @@ -62,6 +50,17 @@ impl Search {
}
}

pub(crate) fn find_next(starting_dir: &Path) -> SearchResult<Self> {
let justfile = Self::justfile(starting_dir)?;

let working_directory = Self::working_directory_from_justfile(&justfile)?;

Ok(Self {
justfile,
working_directory,
})
}

pub(crate) fn init(
search_config: &SearchConfig,
invocation_directory: &Path,
Expand Down
122 changes: 104 additions & 18 deletions src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub(crate) enum Subcommand {
Init,
List,
Run {
overrides: BTreeMap<String, String>,
arguments: Vec<String>,
overrides: BTreeMap<String, String>,
},
Show {
name: String,
Expand All @@ -38,7 +38,11 @@ pub(crate) enum Subcommand {
}

impl Subcommand {
pub(crate) fn run<'src>(&self, config: &Config, loader: &'src Loader) -> Result<(), Error<'src>> {
pub(crate) fn execute<'src>(
&self,
config: &Config,
loader: &'src Loader,
) -> Result<(), Error<'src>> {
use Subcommand::*;

match self {
Expand All @@ -48,6 +52,10 @@ impl Subcommand {
}
Completions { shell } => return Self::completions(shell),
Init => return Self::init(config),
Run {
arguments,
overrides,
} => return Self::run(config, loader, arguments, overrides),
_ => {}
}

Expand All @@ -57,17 +65,7 @@ impl Subcommand {
return Self::edit(&search);
}

let src = loader.load(&search.justfile)?;

let tokens = Lexer::lex(src)?;
let ast = Parser::parse(&tokens)?;
let justfile = Analyzer::analyze(ast.clone())?;

if config.verbosity.loud() {
for warning in &justfile.warnings {
eprintln!("{}", warning.color_display(config.color.stderr()));
}
}
let (src, ast, justfile) = Self::compile(config, loader, &search)?;

match self {
Choose { overrides, chooser } => {
Expand All @@ -79,19 +77,107 @@ impl Subcommand {
Dump => Self::dump(config, ast, justfile)?,
Format => Self::format(config, &search, src, ast)?,
List => Self::list(config, justfile),
Run {
arguments,
overrides,
} => justfile.run(config, &search, overrides, arguments)?,
Show { ref name } => Self::show(config, name, justfile)?,
Summary => Self::summary(config, justfile),
Variables => Self::variables(justfile),
Changelog | Completions { .. } | Edit | Init => unreachable!(),
Changelog | Completions { .. } | Edit | Init | Run { .. } => unreachable!(),
}

Ok(())
}

pub(crate) fn run<'src>(
config: &Config,
loader: &'src Loader,
arguments: &[String],
overrides: &BTreeMap<String, String>,
) -> Result<(), Error<'src>> {
if config.unstable && config.search_config == SearchConfig::FromInvocationDirectory {
let mut path = config.invocation_directory.clone();

let mut unknown_recipes_errors = None;

loop {
let search = match Search::find_next(&path) {
Err(SearchError::NotFound) => match unknown_recipes_errors {
Some(err) => return Err(err),
None => return Err(SearchError::NotFound.into()),
},
Err(err) => return Err(err.into()),
Ok(search) => {
if config.verbosity.loud() && path != config.invocation_directory {
eprintln!(
"Trying {}",
config
.invocation_directory
.strip_prefix(path)
.unwrap()
.components()
.map(|_| path::Component::ParentDir)
.collect::<PathBuf>()
.join(search.justfile.file_name().unwrap())
.display()
);
}
search
}
};

match Self::run_inner(config, loader, arguments, overrides, &search) {
Err(err @ Error::UnknownRecipes { .. }) => {
match search.justfile.parent().unwrap().parent() {
Some(parent) => {
unknown_recipes_errors.get_or_insert(err);
path = parent.into();
}
None => return Err(err),
}
}
result => return result,
}
}
} else {
Self::run_inner(
config,
loader,
arguments,
overrides,
&Search::find(&config.search_config, &config.invocation_directory)?,
)
}
}

fn run_inner<'src>(
config: &Config,
loader: &'src Loader,
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)
}

fn compile<'src>(
config: &Config,
loader: &'src Loader,
search: &Search,
) -> Result<(&'src str, Ast<'src>, Justfile<'src>), Error<'src>> {
let src = loader.load(&search.justfile)?;

let tokens = Lexer::lex(src)?;
let ast = Parser::parse(&tokens)?;
let justfile = Analyzer::analyze(ast.clone())?;

if config.verbosity.loud() {
for warning in &justfile.warnings {
eprintln!("{}", warning.color_display(config.color.stderr()));
}
}

Ok((src, ast, justfile))
}

fn changelog() {
print!("{}", include_str!("../CHANGELOG.md"));
}
Expand Down
2 changes: 1 addition & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub(crate) use std::{
fs,
io::Write,
iter,
path::{Path, PathBuf},
path::{Path, PathBuf, MAIN_SEPARATOR},
process::{Command, Output, Stdio},
str,
};
Expand Down
1 change: 0 additions & 1 deletion tests/dotenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ fn no_warning() {
)
.stdout("unset\n")
.stderr("echo ${DOTENV_KEY:-unset}\n")
.suppress_dotenv_load_warning(false)
.run();
}

Expand Down
Loading

0 comments on commit 52f73db

Please sign in to comment.