use super::*;
pub(crate) fn dedupe_include_files(
resolved: &[(String, std::path::PathBuf, &'static str)],
) -> Result<Vec<(String, std::path::PathBuf)>> {
let mut seen: std::collections::BTreeMap<String, (std::path::PathBuf, &'static str)> =
std::collections::BTreeMap::new();
for (archive, host, origin) in resolved {
if let Some((existing, existing_origin)) = seen.get(archive) {
let existing_canon = existing.canonicalize().unwrap_or_else(|_| existing.clone());
let host_canon = host.canonicalize().unwrap_or_else(|_| host.clone());
if existing_canon != host_canon {
anyhow::bail!(
"include_files conflict for archive path '{archive}': sources disagree \
on host path ({} [origin: {existing_origin}] vs {} [origin: {origin}]). \
Remove the duplicate declaration or rename one of the archive entries.",
existing.display(),
host.display(),
);
}
} else {
seen.insert(archive.clone(), (host.clone(), origin));
}
}
Ok(seen
.into_iter()
.map(|(archive, (host, _origin))| (archive, host))
.collect())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolveSource {
Path,
EnvVar,
PathLookup,
SiblingDir,
TargetDebug,
TargetRelease,
AutoBuilt,
NotFound,
}
fn find_on_path(name: &str) -> Option<PathBuf> {
use std::os::unix::fs::PermissionsExt;
let path_var = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path_var) {
let candidate = dir.join(name);
if !candidate.is_file() {
continue;
}
let executable = candidate
.metadata()
.map(|m| m.permissions().mode() & 0o111 != 0)
.unwrap_or(false);
if executable {
return Some(candidate);
}
}
None
}
pub(crate) fn resolve_staged_schedulers_strict<F>(
entry: &KtstrTestEntry,
mut resolver: F,
) -> Result<Vec<(String, PathBuf, Vec<String>)>>
where
F: FnMut(&SchedulerSpec) -> Result<Option<PathBuf>>,
{
let mut out = Vec::with_capacity(entry.staged_schedulers.len());
for staged in entry.staged_schedulers {
let Some(host_path) = resolver(&staged.binary)? else {
continue;
};
out.push((
staged.name.to_string(),
host_path,
staged.sched_args.iter().map(|s| s.to_string()).collect(),
));
}
Ok(out)
}
pub fn resolve_scheduler(spec: &SchedulerSpec) -> Result<(Option<PathBuf>, ResolveSource)> {
match spec {
SchedulerSpec::Eevdf | SchedulerSpec::KernelBuiltin { .. } => {
Ok((None, ResolveSource::NotFound))
}
SchedulerSpec::Path(p) => {
let path = PathBuf::from(p);
anyhow::ensure!(
path.exists(),
"scheduler binary at '{p}' does not exist on disk. \
SchedulerSpec::Path treats its argument as an \
already-built binary — build the scheduler first \
(e.g. cargo build -p scx_<name>) and pass its \
target/debug/scx_<name> path, or correct the path if \
it has shifted."
);
Ok((Some(path), ResolveSource::Path))
}
SchedulerSpec::Discover(name) => {
if let Ok(p) = std::env::var(crate::per_name_scheduler_env(name)) {
let path = PathBuf::from(&p);
if path.exists() {
return Ok((Some(path), ResolveSource::EnvVar));
}
}
if let Ok(p) = std::env::var(crate::KTSTR_SCHEDULER_ENV) {
let path = PathBuf::from(&p);
if path.exists() {
return Ok((Some(path), ResolveSource::EnvVar));
}
}
if crate::cargo_test_mode::cargo_test_mode_active()
&& let Some(found) = find_on_path(name)
{
return Ok((Some(found), ResolveSource::PathLookup));
}
if !crate::cargo_test_mode::cargo_test_mode_active() {
match crate::build_and_find_binary(name) {
Ok(path) => return Ok((Some(path), ResolveSource::AutoBuilt)),
Err(e) => eprintln!(
"ktstr_test: workspace build of scheduler '{name}' failed \
({e:#}); falling back to a pre-built binary if present"
),
}
}
if let Ok(exe) = crate::resolve_current_exe()
&& let Some(dir) = exe.parent()
{
let candidate = dir.join(name);
if candidate.exists() {
return Ok((Some(candidate), ResolveSource::SiblingDir));
}
if dir.file_name().is_some_and(|d| d == "deps")
&& let Some(parent) = dir.parent()
{
let candidate = parent.join(name);
if candidate.exists() {
return Ok((Some(candidate), ResolveSource::SiblingDir));
}
}
}
let prefer_release =
std::env::var(crate::KTSTR_SCHEDULER_PROFILE_ENV).as_deref() == Ok("release");
for (dir, source) in target_dir_probe_order(prefer_release) {
let candidate = PathBuf::from(dir).join(name);
if candidate.exists() {
return Ok((Some(candidate), source));
}
}
match crate::build_and_find_binary(name) {
Ok(path) => return Ok((Some(path), ResolveSource::AutoBuilt)),
Err(e) => eprintln!("ktstr_test: auto-build scheduler '{name}' failed: {e:#}"),
}
anyhow::bail!(
"scheduler '{name}' not found. Set KTSTR_SCHEDULER or \
place it next to the test binary or in target/{{debug,release}}/"
)
}
}
}
pub(crate) fn target_dir_probe_order(prefer_release: bool) -> [(&'static str, ResolveSource); 2] {
if prefer_release {
[
("target/release", ResolveSource::TargetRelease),
("target/debug", ResolveSource::TargetDebug),
]
} else {
[
("target/debug", ResolveSource::TargetDebug),
("target/release", ResolveSource::TargetRelease),
]
}
}