///|
priv suberror ExpansionFailure {
ExpansionFailure(String)
}
///|
priv struct PreparedRecipe {
command : String
silent : Bool
ignore_error : Bool
recursive : Bool
}
///|
priv struct PreparedRecipes {
commands : Array[PreparedRecipe]
automatic : Map[String, String]
mut environment : Map[String, String]?
}
///|
async fn prepare_recipes(
rule : Rule,
database : Database,
settings : Settings,
newer_dependencies : Array[String],
) -> PreparedRecipes {
settings.expansion_lock.acquire()
defer settings.expansion_lock.release()
let automatic = automatic_variables(
rule.target,
rule.stem,
rule.dependencies,
newer_dependencies~,
)
let commands : Array[PreparedRecipe] = []
for recipe in rule.recipes {
let mut command = expand(
recipe,
database.variables,
automatic,
0,
cwd=settings.cwd,
exported=database.exported,
state=database.expansion(),
) catch {
MakeError(message) => raise ExpansionFailure(message)
error => raise error
}
let mut silent = settings.silent
let mut ignore_error = false
let mut recursive = recipe.contains("$(MAKE)") || recipe.contains("${MAKE}")
let mut scanning = true
while scanning && command != "" {
match command[0] {
'@' => {
silent = true
command = command[1:].to_owned()
}
'-' => {
ignore_error = true
command = command[1:].to_owned()
}
'+' => {
recursive = true
command = command[1:].to_owned()
}
' ' | '\t' => command = command[1:].to_owned()
_ => scanning = false
}
}
commands.push({ command, silent, ignore_error, recursive, })
}
{ commands, automatic, environment: None, }
}
///|
async fn run_recipe(
recipe : PreparedRecipe,
prepared : PreparedRecipes,
database : Database,
settings : Settings,
) -> Int32 {
settings.expansion_lock.acquire()
let mut expanding = true
defer (if expanding { settings.expansion_lock.release() })
if recipe.command.trim().is_empty() {
return 0
}
if !recipe.silent || settings.dry_run {
@stdio.stdout.write(recipe.command + "\n")
}
// GNU avoids constructing a target environment for a pure colon no-op,
// empty recipes, and dry-run lines that do not actually execute.
if recipe.command == ":" || (settings.dry_run && !recipe.recursive) {
return 0
}
let environment = match prepared.environment {
Some(environment) => environment
None => {
let environment = command_environment(
database,
automatic=prepared.automatic,
) catch {
MakeError(message) => raise ExpansionFailure(message)
error => raise error
}
prepared.environment = Some(environment)
environment
}
}
expanding = false
settings.expansion_lock.release()
let status = execute_shell(
@shell.Session::new(name="make", env=environment, cwd=settings.cwd),
recipe.command,
)
if status != 0 && !recipe.ignore_error {
status
} else {
0
}
}