#![allow(dead_code)]
mod members;
mod resolver;
pub use members::{WorkspaceMember, WorkspaceMembers};
pub use resolver::WorkspaceResolver;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use crate::config::{CcgoConfig, WorkspaceConfig};
#[derive(Debug)]
pub struct Workspace {
pub root: PathBuf,
pub config: WorkspaceConfig,
pub root_config: CcgoConfig,
pub members: WorkspaceMembers,
}
impl Workspace {
pub fn load(root: &Path) -> Result<Self> {
let config_path = root.join("CCGO.toml");
if !config_path.exists() {
anyhow::bail!("Workspace root must contain CCGO.toml");
}
let root_config = CcgoConfig::load_from(&config_path)
.context("Failed to load workspace root CCGO.toml")?;
let workspace_config = root_config.workspace.clone()
.ok_or_else(|| anyhow::anyhow!("CCGO.toml does not define a workspace"))?;
let members = WorkspaceMembers::discover(root, &workspace_config)?;
Ok(Self {
root: root.to_path_buf(),
config: workspace_config,
root_config,
members,
})
}
pub fn is_workspace(path: &Path) -> bool {
let config_path = path.join("CCGO.toml");
if !config_path.exists() {
return false;
}
if let Ok(config) = CcgoConfig::load_from(&config_path) {
config.workspace.is_some()
} else {
false
}
}
pub fn get_member(&self, name: &str) -> Option<&WorkspaceMember> {
self.members.get(name)
}
pub fn default_members(&self) -> Vec<&WorkspaceMember> {
if self.config.default_members.is_empty() {
self.members.all()
} else {
self.config.default_members
.iter()
.filter_map(|name| self.members.get(name))
.collect()
}
}
pub fn resolve_member_dependencies(
&self,
member: &WorkspaceMember,
) -> Result<Vec<crate::config::DependencyConfig>> {
let resolver = WorkspaceResolver::new(self);
resolver.resolve_member_dependencies(member)
}
pub fn print_summary(&self) {
println!("\n📦 Workspace: {}", self.root.display());
println!(" Members: {}", self.members.len());
if !self.config.dependencies.is_empty() {
println!(" Shared dependencies: {}", self.config.dependencies.len());
}
println!("\n Packages:");
for member in self.members.all() {
println!(" - {} ({})", member.name, member.version);
}
}
}
pub fn find_workspace_root(start_path: &Path) -> Result<Option<PathBuf>> {
let mut current = start_path.to_path_buf();
loop {
if Workspace::is_workspace(¤t) {
return Ok(Some(current));
}
if let Some(parent) = current.parent() {
current = parent.to_path_buf();
} else {
return Ok(None);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn create_test_workspace() -> TempDir {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::write(
root.join("CCGO.toml"),
r#"
[workspace]
members = ["core", "utils"]
[[workspace.dependencies]]
name = "fmt"
version = "10.0.0"
"#,
)
.unwrap();
let core_dir = root.join("core");
fs::create_dir_all(&core_dir).unwrap();
fs::write(
core_dir.join("CCGO.toml"),
r#"
[package]
name = "core"
version = "1.0.0"
[[dependencies]]
name = "fmt"
workspace = true
"#,
)
.unwrap();
let utils_dir = root.join("utils");
fs::create_dir_all(&utils_dir).unwrap();
fs::write(
utils_dir.join("CCGO.toml"),
r#"
[package]
name = "utils"
version = "1.0.0"
"#,
)
.unwrap();
temp_dir
}
#[test]
fn test_is_workspace() {
let temp_dir = create_test_workspace();
assert!(Workspace::is_workspace(temp_dir.path()));
}
#[test]
fn test_load_workspace() {
let temp_dir = create_test_workspace();
let workspace = Workspace::load(temp_dir.path()).unwrap();
assert_eq!(workspace.members.len(), 2);
assert!(workspace.get_member("core").is_some());
assert!(workspace.get_member("utils").is_some());
}
#[test]
fn test_find_workspace_root() {
let temp_dir = create_test_workspace();
let core_dir = temp_dir.path().join("core");
let root = find_workspace_root(&core_dir).unwrap();
assert!(root.is_some());
assert_eq!(root.unwrap(), temp_dir.path());
}
}