Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1682,12 +1682,16 @@ Available recipes:
This is useful for helper recipes which are only meant to be used as
dependencies of other recipes.

### Enabling and Disabling Recipes
### Enabling and Disabling Items

The `[linux]`, `[macos]`, `[unix]`, and `[windows]` attributes<sup>1.8.0</sup>
are configuration attributes. By default, recipes are always enabled. A recipe
with one or more configuration attributes will only be enabled when one or more
of those configurations is active.
The `[android]`, `[dragonfly]`, `[freebsd]`, `[linux]`, `[macos]`, `[netbsd]`,
`[openbsd]`, `[unix]`, and `[windows]` attributes are conditional attributes.
By default, items are always enabled. An item with one or more conditional
attributes will only be enabled when one or more of those conditional
attributes is active.

The conditional attributes originally applied only to recipes, but may now be
applied to all top-level items<sup>master</sup>.

This can be used to write `justfile`s that behave differently depending on
which operating system they run on. The `run` recipe in this `justfile` will
Expand All @@ -1706,6 +1710,16 @@ run:
main.exe
```

Similarly, a setting can be made conditional on the current operating system:

```just
[unix]
set shell := ['sh', '-cu']

[windows]
set shell := ['cmd', '/c']
```

### Allow Duplicate Recipes

If `allow-duplicate-recipes` is set to `true`, defining multiple recipes with
Expand Down Expand Up @@ -4666,7 +4680,7 @@ change their behavior.
| Name | Type | Description |
|------|------|-------------|
| `[arg(ARG, help="HELP")]`<sup>1.46.0</sup> | recipe | Print help string `HELP` for `ARG` in usage messages. May be a const expression<sup>1.55.0</sup>. |
| `[arg(ARG, long="LONG")]`<sup>1.46.0</sup> | recipe | Require values of argument `ARG` to be passed as `--LONG` option. If the parameter is variadic, the option is repeatable<sup1.55.0master</sup>. |
| `[arg(ARG, long="LONG")]`<sup>1.46.0</sup> | recipe | Require values of argument `ARG` to be passed as `--LONG` option. If the parameter is variadic, the option is repeatable<sup>1.55.0master</sup>. |
| `[arg(ARG, pattern="PATTERN")]`<sup>1.45.0</sup> | recipe | Require values of argument `ARG` to match regular expression `PATTERN`. May be a const expression<sup>1.55.0</sup>. |
| `[arg(ARG, short="S")]`<sup>1.46.0</sup> | recipe | Require values of argument `ARG` to be passed as short `-S` option. If the parameter is variadic, the option is repeatable<sup>1.55.0</sup>. |
| `[arg(ARG, value=VALUE)]`<sup>1.46.0</sup> | recipe | Makes option `ARG` a flag which does not take a value. |
Expand All @@ -4676,29 +4690,29 @@ change their behavior.
| `[continue(SIGNALS)]`<sup>1.54.0</sup> | recipe | Continue execution normally if a command is interrupted by any of `SIGNALS` and exits successfully. Defaults to `SIGINT`. |
| `[default]`<sup>1.43.0</sup> | recipe | Use recipe as module's default recipe. |
| `[doc(DOC)]`<sup>1.27.0</sup> | module, recipe | Set recipe or module's [documentation comment](#documentation-comments) to `DOC`. |
| `[dragonfly]`<sup>1.47.0</sup> | recipe | Enable recipe on DragonFly BSD. |
| `[dragonfly]`<sup>1.47.0</sup> | any<sup>master</sup> | Enable item on DragonFly BSD. |
| `[env(NAME, VALUE)]` <sup>1.47.0</sup> | recipe | Set environment variable `NAME` to `VALUE` for recipe. `NAME` and `VALUE` may be expressions<sup>1.51.0</sup>. |
| `[extension(EXT)]`<sup>1.32.0</sup> | recipe | Set shebang recipe script's file extension to `EXT`. `EXT` should include a period if one is desired. |
| `[exit-message]`<sup>1.39.0</sup> | recipe | Print error message if recipe fails regardless of `set no-exit-message`. |
| `[freebsd]`<sup>1.47.0</sup> | recipe | Enable recipe on FreeBSD. |
| `[freebsd]`<sup>1.47.0</sup> | any<sup>master</sup> | Enable item on FreeBSD. |
| `[group(NAME)]`<sup>1.27.0</sup> | module, recipe | Put recipe or module in [group](#groups) `NAME`. |
| `[android]`<sup>1.50.0</sup> | recipe | Enable recipe on Android. |
| `[linux]`<sup>1.8.0</sup> | recipe | Enable recipe on Linux. |
| `[macos]`<sup>1.8.0</sup> | recipe | Enable recipe on macOS. |
| `[android]`<sup>1.50.0</sup> | any<sup>master</sup> | Enable item on Android. |
| `[linux]`<sup>1.8.0</sup> | any<sup>master</sup> | Enable item on Linux. |
| `[macos]`<sup>1.8.0</sup> | any<sup>master</sup> | Enable item on macOS. |
| `[metadata(METADATA)]`<sup>1.42.0</sup> | recipe | Attach `METADATA` to recipe. |
| `[netbsd]`<sup>1.47.0</sup> | recipe | Enable recipe on NetBSD. |
| `[netbsd]`<sup>1.47.0</sup> | any<sup>master</sup> | Enable item on NetBSD. |
| `[no-cd]`<sup>1.9.0</sup> | recipe | Don't change directory before executing recipe. |
| `[no-exit-message]`<sup>1.7.0</sup> | recipe | Don't print an error message if recipe fails. |
| `[no-quiet]`<sup>1.23.0</sup> | recipe | Override globally quiet recipes and always echo out the recipe. |
| `[openbsd]`<sup>1.38.0</sup> | recipe | Enable recipe on OpenBSD. |
| `[openbsd]`<sup>1.38.0</sup> | any<sup>master</sup> | Enable item on OpenBSD. |
| `[parallel]`<sup>1.42.0</sup> | recipe | Run this recipe's dependencies in parallel. |
| `[positional-arguments]`<sup>1.29.0</sup> | recipe | Turn on [positional arguments](#positional-arguments) for this recipe. |
| `[private]`<sup>1.10.0</sup> | alias, recipe | Make recipe, alias, or variable private. See [Private Recipes](#private-recipes). |
| `[script(COMMAND)]`<sup>1.32.0</sup> | recipe | Execute recipe as a script interpreted by `COMMAND`. See [script recipes](#script-recipes) for more details. |
| `[script]`<sup>1.33.0</sup> | recipe | Execute recipe as script. See [script recipes](#script-recipes) for more details. |
| `[shell]`<sup>1.52.0</sup> | recipe | Execute recipe as a shell recipe, overriding `set default-script`. |
| `[unix]`<sup>1.8.0</sup> | recipe | Enable recipe on unixes. (Includes macOS). |
| `[windows]`<sup>1.8.0</sup> | recipe | Enable recipe on Windows. |
| `[unix]`<sup>1.8.0</sup> | any<sup>master</sup> | Enable item on unixes. (Includes macOS). |
| `[windows]`<sup>1.8.0</sup> | any<sup>master</sup> | Enable item on Windows. |
| `[working-directory(PATH)]`<sup>1.38.0</sup> | recipe | Set recipe working directory. `PATH` may be an expression<sup>1.51.0</sup> whose value is relative or absolute. If relative, it is interpreted relative to the default working directory. |

A recipe can have multiple attributes, either on multiple lines:
Expand Down
45 changes: 26 additions & 19 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@ impl<'run, 'src> Analyzer<'run, 'src> {
list_features.extend(&ast.list_features);

for item in &ast.items {
if !item.is_enabled() {
continue;
}

match item {
Item::Alias(alias) => {
Self::define(&mut definitions, alias.name, "alias", false)?;
Self::define(&mut definitions, alias.name, ItemKind::Alias, false)?;
self.aliases.insert(alias.clone());
}
Item::Assignment(assignment) => {
Expand All @@ -87,7 +91,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
..
} => {
if let Some(absolute) = absolute {
Self::define(&mut definitions, *name, "module", false)?;
Self::define(&mut definitions, *name, ItemKind::Module, false)?;
self.modules.insert(Self::analyze(
asts,
config,
Expand All @@ -106,12 +110,10 @@ impl<'run, 'src> Analyzer<'run, 'src> {
}
Item::Newline => {}
Item::Recipe(recipe) => {
if recipe.enabled() {
Self::analyze_recipe(recipe)?;
self.recipes.push(recipe);
}
Self::analyze_recipe(recipe)?;
self.recipes.push(recipe);
}
Item::Set(set) => {
Item::Setting(set) => {
self.analyze_set(set)?;
self.sets.insert(set.clone());
}
Expand Down Expand Up @@ -161,8 +163,8 @@ impl<'run, 'src> Analyzer<'run, 'src> {
let name = function.name.lexeme();
if let Some(first) = functions.get(name) {
return Err(function.name.error(Redefinition {
first_type: "function",
second_type: "function",
first_type: ItemKind::Function,
second_type: ItemKind::Function,
name,
first: first.name.line,
}));
Expand Down Expand Up @@ -269,7 +271,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
Self::define(
&mut definitions,
recipe.name,
"recipe",
ItemKind::Recipe,
settings.allow_duplicate_recipes,
)?;

Expand Down Expand Up @@ -430,9 +432,9 @@ impl<'run, 'src> Analyzer<'run, 'src> {
}

fn define(
definitions: &mut HashMap<&'src str, (&'static str, Name<'src>)>,
definitions: &mut HashMap<&'src str, (ItemKind, Name<'src>)>,
name: Name<'src>,
second_type: &'static str,
second_type: ItemKind,
duplicates_allowed: bool,
) -> CompileResult<'src> {
if let Some((first_type, original)) = definitions.get(name.lexeme())
Expand Down Expand Up @@ -550,8 +552,8 @@ mod tests {
column: 6,
width: 3,
kind: Redefinition {
first_type: "alias",
second_type: "alias",
first_type: ItemKind::Alias,
second_type: ItemKind::Alias,
name: "foo",
first: 0,
},
Expand Down Expand Up @@ -588,8 +590,8 @@ mod tests {
column: 0,
width: 3,
kind: Redefinition {
first_type: "alias",
second_type: "recipe",
first_type: ItemKind::Alias,
second_type: ItemKind::Recipe,
name: "foo",
first: 2,
},
Expand All @@ -603,8 +605,8 @@ mod tests {
column: 6,
width: 3,
kind: Redefinition {
first_type: "recipe",
second_type: "alias",
first_type: ItemKind::Recipe,
second_type: ItemKind::Alias,
name: "foo",
first: 0,
},
Expand Down Expand Up @@ -647,7 +649,12 @@ mod tests {
line: 2,
column: 0,
width: 1,
kind: Redefinition { first_type: "recipe", second_type: "recipe", name: "a", first: 0 },
kind: Redefinition {
first: 0,
first_type: ItemKind::Recipe,
name: "a",
second_type: ItemKind::Recipe,
},
}

analysis_error! {
Expand Down
15 changes: 15 additions & 0 deletions src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ impl AttributeKind {
matches!(self, Self::Arg | Self::Cache)
}

pub(crate) fn is_enabler(self) -> bool {
matches!(
self,
Self::Android
| Self::Dragonfly
| Self::Freebsd
| Self::Linux
| Self::Macos
| Self::Netbsd
| Self::Openbsd
| Self::Unix
| Self::Windows
)
}

fn argument_range(self) -> RangeInclusive<usize> {
match self {
Self::Android
Expand Down
49 changes: 46 additions & 3 deletions src/attribute_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@ impl<'src> AttributeSet<'src> {
self.0.keys().any(|attribute| attribute.kind() == kind)
}

pub(crate) fn is_enabled(&self) -> bool {
let android = self.contains(AttributeKind::Android);
let dragonfly = self.contains(AttributeKind::Dragonfly);
let freebsd = self.contains(AttributeKind::Freebsd);
let linux = self.contains(AttributeKind::Linux);
let macos = self.contains(AttributeKind::Macos);
let netbsd = self.contains(AttributeKind::Netbsd);
let openbsd = self.contains(AttributeKind::Openbsd);
let unix = self.contains(AttributeKind::Unix);
let windows = self.contains(AttributeKind::Windows);

(!windows
&& !linux
&& !macos
&& !openbsd
&& !freebsd
&& !dragonfly
&& !netbsd
&& !unix
&& !android)
|| (cfg!(target_os = "android") && android)
|| (cfg!(target_os = "dragonfly") && dragonfly)
|| (cfg!(target_os = "freebsd") && freebsd)
|| (cfg!(target_os = "linux") && linux)
|| (cfg!(target_os = "macos") && macos)
|| (cfg!(target_os = "netbsd") && netbsd)
|| (cfg!(target_os = "openbsd") && openbsd)
|| (cfg!(unix) && unix)
|| (cfg!(windows) && windows)
}

pub(crate) fn get(&self, kind: AttributeKind) -> Option<&Attribute<'src>> {
self.0.keys().find(|attribute| attribute.kind() == kind)
}
Expand Down Expand Up @@ -52,12 +83,24 @@ impl<'src> AttributeSet<'src> {

pub(crate) fn ensure_valid_attributes(
&self,
item_kind: &'static str,
item_kind: ItemKind,
item_token: Token<'src>,
valid: &[AttributeKind],
) -> Result<(), CompileError<'src>> {
let valid = match item_kind {
ItemKind::Alias | ItemKind::Assignment => [AttributeKind::Private].as_slice(),
ItemKind::Comment | ItemKind::Newline => unreachable!(),
ItemKind::Function | ItemKind::Import | ItemKind::Setting | ItemKind::Unexport => &[],
ItemKind::Module => &[
AttributeKind::Doc,
AttributeKind::Group,
AttributeKind::Private,
],
ItemKind::Recipe => return Ok(()),
};

for attribute in self.0.keys() {
if !valid.contains(&attribute.kind()) {
let kind = attribute.kind();
if !kind.is_enabler() && !valid.contains(&kind) {
return Err(item_token.error(CompileErrorKind::InvalidAttribute {
item_kind,
item_name: item_token.lexeme(),
Expand Down
2 changes: 1 addition & 1 deletion src/compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ impl Display for CompileError<'_> {
f,
"{first_type} `{name}` defined on line {} is redefined as {} {second_type} on line {}",
first.ordinal(),
if *second_type == "alias" { "an" } else { "a" },
second_type.article(),
self.token.line.ordinal(),
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/compile_error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub(crate) enum CompileErrorKind<'src> {
message: String,
},
InvalidAttribute {
item_kind: &'static str,
item_kind: ItemKind,
item_name: &'src str,
attribute: Box<Attribute<'src>>,
},
Expand Down Expand Up @@ -166,9 +166,9 @@ pub(crate) enum CompileErrorKind<'src> {
ParsingRecursionDepthExceeded,
Redefinition {
first: usize,
first_type: &'static str,
first_type: ItemKind,
name: &'src str,
second_type: &'static str,
second_type: ItemKind,
},
RequiredParameterFollowsDefaultParameter {
parameter: &'src str,
Expand Down
4 changes: 4 additions & 0 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ impl Compiler {
paths.insert(current.path.clone(), relative.into());

for item in &mut ast.items {
if !item.is_enabled() {
continue;
}

match item {
Item::Module {
absolute,
Expand Down
Loading
Loading