use {
crate::*,
anyhow::{
Result,
anyhow,
bail,
},
cargo_metadata::MetadataCommand,
std::{
env,
fmt,
path::{
Path,
PathBuf,
},
},
};
static DEFAULT_WATCHES: &[&str] = &[
"Cargo.toml",
"src",
"tests",
"benches",
"examples",
"build.rs",
];
#[derive(Debug)]
pub struct Context {
pub name: String,
pub nature: ContextNature,
pub package_directory: PathBuf,
pub workspace_root: Option<PathBuf>,
cargo_mission_location: Option<CargoContext>,
pub path_to_watch: Option<PathBuf>,
}
struct CargoContext {
pub cargo_toml_file: PathBuf,
pub packages: Vec<cargo_metadata::Package>,
}
impl fmt::Debug for CargoContext {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.debug_struct("CargoContext")
.field("cargo_toml_file", &self.cargo_toml_file)
.finish_non_exhaustive()
}
}
impl Context {
pub fn new(args: &Args) -> Result<Self> {
let package_directory = find_package_directory(args)?;
let name = package_directory
.file_name()
.unwrap_or(package_directory.as_os_str())
.to_string_lossy()
.to_string();
let path_to_watch = args.watch.as_ref().map(PathBuf::from);
let mut cargo_toml_file = package_directory.join("Cargo.toml");
let nature = if cargo_toml_file.exists() && cargo_toml_file.is_file() {
ContextNature::Cargo
} else {
ContextNature::Other
};
let mut workspace_root = None;
let mut cargo_mission_location = None;
if nature == ContextNature::Cargo {
let metadata = if args.offline {
MetadataCommand::new()
.current_dir(&package_directory)
.no_deps()
.other_options(["--frozen".to_string(), "--offline".to_string()])
.exec()
} else {
MetadataCommand::new()
.current_dir(&package_directory)
.exec()
};
let metadata = metadata?;
if let Some(resolved_root) = metadata.resolve.and_then(|resolve| resolve.root) {
cargo_toml_file = metadata
.packages
.iter()
.find(|p| p.id == resolved_root)
.map(|p| p.manifest_path.as_std_path().to_path_buf())
.ok_or_else(|| {
anyhow!("the resolved root package was not in the package list")
})?;
if metadata.workspace_root.as_std_path() != package_directory {
workspace_root = Some(metadata.workspace_root.as_std_path().to_path_buf());
}
}
cargo_mission_location = Some(CargoContext {
cargo_toml_file,
packages: metadata.packages,
});
}
Ok(Self {
name,
nature,
package_directory,
workspace_root,
cargo_mission_location,
path_to_watch,
})
}
pub fn mission<'s>(
&self,
concrete_job_ref: ConcreteJobRef,
leaf_job: &Job, settings: &'s Settings,
) -> Result<Mission<'s>> {
let mut job = settings.all_jobs.clone();
job.apply(leaf_job);
let location_name = self.name.clone();
let mut paths_to_watch: Vec<PathBuf> = Vec::new();
if let Some(path_to_watch) = &self.path_to_watch {
paths_to_watch.push(path_to_watch.clone());
} else {
let mut watches = Vec::new();
if let Some(v) = &job.watch {
for watch in v {
watches.push(watch.as_str());
}
}
let add_default = job.default_watch.unwrap_or(true);
if add_default {
for watch in DEFAULT_WATCHES {
if !watches.contains(watch) {
watches.push(watch);
}
}
}
add_to_paths_to_watch(&watches, &self.package_directory, &mut paths_to_watch);
if let Some(workspace_root) = &self.workspace_root {
add_to_paths_to_watch(&watches, workspace_root, &mut paths_to_watch);
}
if let Some(location) = &self.cargo_mission_location {
for item in &location.packages {
if item.source.is_none() {
let Some(item_path) = item.manifest_path.parent() else {
continue;
};
add_to_paths_to_watch(
&watches,
item_path.as_std_path(),
&mut paths_to_watch,
);
if item.manifest_path.exists() {
paths_to_watch.push(item.manifest_path.clone().into());
} else {
warn!("missing manifest file: {:?}", item.manifest_path);
}
}
}
}
}
let mut conf_execution_directory = job.workdir.as_ref();
if let Some(path) = conf_execution_directory {
if !path.exists() {
error!("Ignoring configured non existing workdir: {path:?}");
conf_execution_directory = None;
}
}
let execution_directory = conf_execution_directory
.unwrap_or(&self.package_directory)
.clone();
Ok(Mission {
location_name,
concrete_job_ref,
execution_directory,
package_directory: self.package_directory.clone(),
workspace_directory: self.workspace_root.clone(),
job,
paths_to_watch,
settings,
})
}
pub fn workspace_cargo_path(&self) -> Option<PathBuf> {
self.workspace_root.as_ref().map(|p| p.join("Cargo.toml"))
}
pub fn workspace_config_path(&self) -> Option<PathBuf> {
self.workspace_root.as_ref().map(|p| p.join("bacon.toml"))
}
pub fn workspace_dot_config_path(&self) -> Option<PathBuf> {
self.workspace_root
.as_ref()
.map(|p| p.join(".config/bacon.toml"))
}
pub fn package_cargo_path(&self) -> PathBuf {
self.package_directory.join("Cargo.toml")
}
pub fn package_config_path(&self) -> PathBuf {
self.package_directory.join("bacon.toml")
}
pub fn package_dot_config_path(&self) -> PathBuf {
self.package_directory.join(".config/bacon.toml")
}
pub fn unix_socket_path(&self) -> PathBuf {
self.package_directory.join(".bacon.socket")
}
}
fn add_to_paths_to_watch(
watches: &[&str],
base_path: &Path,
paths_to_watch: &mut Vec<PathBuf>,
) {
for watch in watches {
let full_path = base_path.join(watch);
if !paths_to_watch.contains(&full_path) && full_path.exists() {
paths_to_watch.push(full_path);
}
}
}
fn find_package_directory(args: &Args) -> Result<PathBuf> {
if let Some(dir) = args.project.as_ref() {
let path = PathBuf::from(dir);
if !path.exists() || !path.is_dir() {
bail!("The project path must be a directory");
}
return Ok(path);
}
let base_dir = env::current_dir().unwrap();
let package_directory = closest_bacon_or_cargo_dir(&base_dir).unwrap_or(base_dir);
Ok(package_directory)
}
fn closest_bacon_or_cargo_dir(start_path: &Path) -> Option<PathBuf> {
let mut current_path = start_path;
loop {
let bacon_toml = current_path.join("bacon.toml");
if bacon_toml.exists() && bacon_toml.is_file() {
return Some(current_path.to_path_buf());
}
let cargo_toml = current_path.join("Cargo.toml");
if cargo_toml.exists() && cargo_toml.is_file() {
return Some(current_path.to_path_buf());
}
if let Some(parent) = current_path.parent() {
current_path = parent;
} else {
break;
}
}
None
}