mirror of
https://github.com/Zenithsiz/zbuild.git
synced 2026-02-03 14:10:02 +00:00
Dependency file parser now supports backslashes at the end of the line to continue the line, as well as multiple dependencies in the same file
This commit is contained in:
parent
aefeb9db4c
commit
0058abb8c0
112
src/build.rs
112
src/build.rs
@ -24,7 +24,12 @@ use {
|
||||
futures::{stream::FuturesUnordered, StreamExt},
|
||||
indexmap::IndexMap,
|
||||
itertools::Itertools,
|
||||
std::{collections::BTreeMap, future::Future, sync::Arc, time::SystemTime},
|
||||
std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
future::Future,
|
||||
sync::Arc,
|
||||
time::SystemTime,
|
||||
},
|
||||
tokio::{fs, process, sync::Semaphore, task},
|
||||
};
|
||||
|
||||
@ -635,39 +640,68 @@ impl Builder {
|
||||
reason: &BuildReason,
|
||||
) -> Result<Vec<(Target<ArcStr>, BuildResult, Option<BuildLockDepGuard>)>, AppError> {
|
||||
tracing::trace!(target=?parent_target, ?rule.name, ?deps_file, "Building dependencies of target rule dependency-file");
|
||||
let (output, deps) = self::parse_deps_file(deps_file).await?;
|
||||
let mut deps = self::parse_deps_file(deps_file).await?;
|
||||
tracing::trace!(target=?parent_target, ?rule.name, ?deps_file, ?deps, "Found dependencies of target rule dependency-file");
|
||||
|
||||
match rule.output.is_empty() {
|
||||
// Ensure that at least one dependency output matches the rule
|
||||
let matches_rule = |output: &str| match rule.output.is_empty() {
|
||||
// If there were no outputs, make sure it matches the rule name
|
||||
// TODO: Seems kinda weird for it to match the rule name, but not sure how else to check this here
|
||||
true =>
|
||||
if output != *rule.name {
|
||||
return Err(AppError::DepFileMissingRuleName {
|
||||
deps_file_path: deps_file.into(),
|
||||
rule_name: rule.name.to_string(),
|
||||
dep_output: output,
|
||||
});
|
||||
},
|
||||
true => (output == &*rule.name)
|
||||
.then_some(())
|
||||
.ok_or_else(|| AppError::DepFileMissingRuleName {
|
||||
deps_file_path: deps_file.into(),
|
||||
rule_name: rule.name.to_string(),
|
||||
dep_output: output.to_owned(),
|
||||
}),
|
||||
|
||||
// If there were any output, make sure the dependency file applies to one of them
|
||||
false => {
|
||||
let any_matches = rule.output.iter().any(|out| match out {
|
||||
OutItem::File { file, .. } => **file == output,
|
||||
false => rule
|
||||
.output
|
||||
.iter()
|
||||
.any(|out| match out {
|
||||
OutItem::File { file, .. } => &**file == output,
|
||||
})
|
||||
.then_some(())
|
||||
.ok_or_else(|| AppError::DepFileMissingOutputs {
|
||||
deps_file_path: deps_file.into(),
|
||||
rule_outputs: rule.output.iter().map(OutItem::to_string).collect(),
|
||||
dep_output: output.to_owned(),
|
||||
}),
|
||||
};
|
||||
|
||||
// Find all outputs that match and those that don't match
|
||||
let (oks, errs) = deps
|
||||
.keys()
|
||||
.map(|output| matches_rule(output).map_err(|err| (output.clone(), err)))
|
||||
.partition_result::<Vec<_>, Vec<_>, _, _>();
|
||||
match oks.is_empty() {
|
||||
true => {
|
||||
// If we had no matching outputs, try to return all errors
|
||||
errs.into_iter()
|
||||
.map(|(_, err)| Err(err))
|
||||
.collect::<ResultMultiple<()>>()?;
|
||||
|
||||
// If no errors existed, return an error for that
|
||||
return Err(AppError::DepFileEmpty {
|
||||
deps_file_path: deps_file.into(),
|
||||
});
|
||||
if !any_matches {
|
||||
return Err(AppError::DepFileMissingOutputs {
|
||||
deps_file_path: deps_file.into(),
|
||||
rule_outputs: rule.output.iter().map(OutItem::to_string).collect(),
|
||||
dep_output: output,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Otherwise, just log and remove all errors
|
||||
false =>
|
||||
for (output, err) in errs {
|
||||
let _: Vec<_> = deps.remove(&output).expect("Dependency should exist");
|
||||
|
||||
tracing::warn!(target=%parent_target, ?rule.name, err=%err.pretty(), "Ignoring unknown output in dependency file");
|
||||
},
|
||||
}
|
||||
|
||||
// Build all dependencies
|
||||
// Note: We don't want to fail-early, see the note on `deps` in `build_unchecked`
|
||||
let deps_res = deps
|
||||
.into_iter()
|
||||
.flat_map(|(_, deps)| deps)
|
||||
.map(|dep| {
|
||||
let dep = ArcStr::from(util::normalize_path(&dep));
|
||||
tracing::trace!(?rule.name, ?dep, "Found rule dependency");
|
||||
@ -757,19 +791,35 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// Parses a dependencies file
|
||||
// TODO: Support multiple dependencies in each file
|
||||
async fn parse_deps_file(file: &str) -> Result<(String, Vec<String>), AppError> {
|
||||
async fn parse_deps_file(file: &str) -> Result<HashMap<ArcStr, Vec<ArcStr>>, AppError> {
|
||||
// Read it
|
||||
let contents = fs::read_to_string(file).await.map_err(AppError::read_file(file))?;
|
||||
let mut contents = fs::read_to_string(file).await.map_err(AppError::read_file(file))?;
|
||||
|
||||
// Parse it
|
||||
let (output, deps) = contents.split_once(':').ok_or_else(|| AppError::DepFileMissingColon {
|
||||
deps_file_path: file.into(),
|
||||
})?;
|
||||
let output = output.trim().to_owned();
|
||||
let deps = deps.split_whitespace().map(str::to_owned).collect();
|
||||
// Replace all backslashes at the end of a line with spaces
|
||||
// Note: Although it'd be fine to replace it with a single space, by replacing it
|
||||
// with two, we avoid having to move the string contents
|
||||
util::string_replace_in_place_with(&mut contents, "\\\n", " ");
|
||||
|
||||
Ok((output, deps))
|
||||
// Now go through all non-empty lines and parse them
|
||||
let deps = contents
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let line = line.trim();
|
||||
(!line.is_empty()).then_some(line)
|
||||
})
|
||||
.map(|line| {
|
||||
// Parse it
|
||||
let (output, deps) = line.split_once(':').ok_or_else(|| AppError::DepFileMissingColon {
|
||||
deps_file_path: file.into(),
|
||||
})?;
|
||||
let output = ArcStr::from(output.trim());
|
||||
let deps = deps.split_whitespace().map(ArcStr::from).collect();
|
||||
|
||||
Ok((output, deps))
|
||||
})
|
||||
.collect::<ResultMultiple<_>>()?;
|
||||
|
||||
Ok(deps)
|
||||
}
|
||||
|
||||
/// Returns the last build time of a rule.
|
||||
|
||||
12
src/error.rs
12
src/error.rs
@ -528,7 +528,7 @@ decl_error! {
|
||||
|
||||
/// Dependencies file missing rule name
|
||||
#[source(None)]
|
||||
#[fmt("Dependencies file {deps_file_path:?} is missing the rule name {rule_name}, found {dep_output}")]
|
||||
#[fmt("Dependencies file {deps_file_path:?} is missing the rule name {rule_name:?}, found {dep_output:?}")]
|
||||
DepFileMissingRuleName {
|
||||
/// Dep file path
|
||||
deps_file_path: PathBuf,
|
||||
@ -542,7 +542,7 @@ decl_error! {
|
||||
|
||||
/// Dependencies file missing rule name
|
||||
#[source(None)]
|
||||
#[fmt("Dependencies file {deps_file_path:?} is missing any output of {rule_outputs:?}, found {dep_output}")]
|
||||
#[fmt("Dependencies file {deps_file_path:?} is missing any output of {rule_outputs:?}, found {dep_output:?}")]
|
||||
DepFileMissingOutputs {
|
||||
/// Dep file path
|
||||
deps_file_path: PathBuf,
|
||||
@ -554,6 +554,14 @@ decl_error! {
|
||||
dep_output: String,
|
||||
},
|
||||
|
||||
/// Dependencies file empty
|
||||
#[source(None)]
|
||||
#[fmt("Dependencies file {deps_file_path:?} had no dependencies")]
|
||||
DepFileEmpty {
|
||||
/// Dep file path
|
||||
deps_file_path: PathBuf,
|
||||
},
|
||||
|
||||
/// Rule executable was empty
|
||||
#[source(None)]
|
||||
#[fmt("Rule {rule_name} executable as empty")]
|
||||
|
||||
33
src/util.rs
33
src/util.rs
@ -12,8 +12,10 @@ use {
|
||||
pin_project::pin_project,
|
||||
std::{
|
||||
io,
|
||||
mem,
|
||||
path::{self, Path, PathBuf},
|
||||
pin::Pin,
|
||||
str::pattern::Pattern,
|
||||
task,
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
@ -116,6 +118,37 @@ pub fn normalize_path(path: &str) -> String {
|
||||
path
|
||||
}
|
||||
|
||||
/// In-place replaces matching parts of a string.
|
||||
#[expect(
|
||||
clippy::needless_pass_by_value,
|
||||
reason = "It's more ergonomic to pass patterns by value"
|
||||
)]
|
||||
pub fn string_replace_in_place_with<P>(s: &mut String, pat: P, replace_with: &str)
|
||||
where
|
||||
P: Pattern + Clone,
|
||||
{
|
||||
let mut cur_idx = 0;
|
||||
let mut matches = s.match_indices(pat.clone());
|
||||
|
||||
// Find all matches, replacing the range as we go.
|
||||
#[expect(
|
||||
clippy::string_slice,
|
||||
reason = "The index will always be valid, as it's the end of the string returned by `match_indices`, which \
|
||||
must return substrings of the string"
|
||||
)]
|
||||
while let Some((pos, part)) = matches.next() {
|
||||
// Replace the range
|
||||
mem::drop(matches);
|
||||
s.replace_range(cur_idx + pos..cur_idx + pos + part.len(), replace_with);
|
||||
|
||||
// After replacing `...ABC` with `...DEF`, put ourselves as the end of
|
||||
// the string we just replaced, to avoid recursively replacing the same
|
||||
// pattern over and over.
|
||||
cur_idx += pos + replace_with.len();
|
||||
matches = s[cur_idx..].match_indices(pat.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user