mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 19:39:31 +00:00
- Path dependencies now work correctly: `dcr add` stores them as `{ path =
"./..." }` tables, `is_registry_dep` properly distinguishes registry strings
from path/git strings, and `deps/mod.rs` resolves both table-form and
legacy string-form path dependencies
- Registry dependency paths no longer hardcoded to `project_root/dcr-index`;
now use `package_root_from_registry_info()` which resolves relative to the
registry cache root (`~/.dcr/`)
- Registry dependencies are now actually built: `build_project_at()` is
called when `include_dir` or `lib_dir` is missing
Added:
- `SIGINT`/`Ctrl+C` handler via `ctrlc` crate — `dcr build` now checks
`BUILD_INTERRUPTED` flag at key points and aborts cleanly
- `utils/build.rs` — extracted shared utilities: `parse_version_info`,
`normalize_target_os`, `resolve_compiler`, `primary_language`,
`resolve_pkg_config_flags` and config helpers. Eliminates code duplication
between `cli/build.rs`, `cli/run.rs`, `cli/clean.rs`, `cli/gen.rs`
- `utils/fs.rs::with_dir` — extracted common directory-scoped execution
- `run_command_sync_output` in `builder/common.rs` with global `OUTPUT_MUTEX`
— synchronized compiler output across all backends (unix_cc, gas, nasm, msvc)
- New helper functions in `deps/register.rs`: `get_registry_cache_root`,
`package_root_from_registry_info`, `registry_include_dir`, `registry_lib_dir`,
`path_from_string_dep`
- Unit tests for `register.rs` (`is_registry_dep`, `package_root_from_registry_info`),
`deps/mod.rs` (`path_dep_path`, `push_default_lib_dirs`),
`utils/build.rs` (`normalize_target_os`, `parse_version_info`)
Removed:
- Removed unused dependencies `indicatif` and `console` from `Cargo.toml`
151 lines
4.3 KiB
Rust
151 lines
4.3 KiB
Rust
use crate::core::config::Config;
|
|
use crate::utils::build::normalize_target_os;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::path::{Path, PathBuf};
|
|
use toml::Value;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct WorkspaceMember {
|
|
pub name: String,
|
|
pub path: PathBuf,
|
|
pub deps: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Workspace {
|
|
pub members: Vec<WorkspaceMember>,
|
|
}
|
|
|
|
pub fn parse_workspace(
|
|
config: &Config,
|
|
profile: &str,
|
|
target: Option<&str>,
|
|
root: &Path,
|
|
) -> Result<Option<Workspace>, String> {
|
|
let mut table = None;
|
|
// Order: workspace.target.profile, workspace.profile.target, workspace.target, workspace.profile, workspace
|
|
let combinations = if let Some(t) = target {
|
|
let normalized_t = normalize_target_os(t);
|
|
vec![
|
|
format!("workspace.{}.{}", normalized_t, profile),
|
|
format!("workspace.{}.{}", profile, normalized_t),
|
|
format!("workspace.{}", normalized_t),
|
|
format!("workspace.{}", profile),
|
|
"workspace".to_string(),
|
|
]
|
|
} else {
|
|
vec![format!("workspace.{}", profile), "workspace".to_string()]
|
|
};
|
|
for key in combinations {
|
|
if let Some(val) = config.get(&key).and_then(|v| v.as_table()) {
|
|
table = Some(val);
|
|
break;
|
|
}
|
|
}
|
|
let table = match table {
|
|
Some(t) => t,
|
|
None => return Ok(None),
|
|
};
|
|
|
|
let mut members = Vec::new();
|
|
for (name, value) in table {
|
|
let tbl = value
|
|
.as_table()
|
|
.ok_or_else(|| format!("workspace.{name} must be a table with path and deps"))?;
|
|
let path_raw = tbl
|
|
.get("path")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or_else(|| format!("workspace.{name}.path is required"))?;
|
|
let path = resolve_path(root, path_raw)?;
|
|
if !path.join("dcr.toml").is_file() {
|
|
return Err(format!(
|
|
"workspace.{name}.path does not contain dcr.toml: {}",
|
|
path.display()
|
|
));
|
|
}
|
|
let deps = parse_deps(tbl.get("deps"))?;
|
|
members.push(WorkspaceMember {
|
|
name: name.to_string(),
|
|
path,
|
|
deps,
|
|
});
|
|
}
|
|
|
|
let mut ws = Workspace { members };
|
|
ws.members = topo_sort(&ws.members)?;
|
|
Ok(Some(ws))
|
|
}
|
|
|
|
fn resolve_path(root: &Path, raw: &str) -> Result<PathBuf, String> {
|
|
let p = Path::new(raw);
|
|
let full = if p.is_absolute() {
|
|
p.to_path_buf()
|
|
} else {
|
|
root.join(p)
|
|
};
|
|
Ok(full)
|
|
}
|
|
|
|
fn parse_deps(value: Option<&Value>) -> Result<Vec<String>, String> {
|
|
let Some(value) = value else {
|
|
return Ok(Vec::new());
|
|
};
|
|
let arr = value
|
|
.as_array()
|
|
.ok_or_else(|| "workspace deps must be an array of strings".to_string())?;
|
|
let mut out = Vec::new();
|
|
for item in arr {
|
|
let s = item
|
|
.as_str()
|
|
.ok_or_else(|| "workspace deps must be an array of strings".to_string())?;
|
|
out.push(s.to_string());
|
|
}
|
|
Ok(out)
|
|
}
|
|
|
|
fn topo_sort(members: &[WorkspaceMember]) -> Result<Vec<WorkspaceMember>, String> {
|
|
let mut map = HashMap::new();
|
|
for m in members {
|
|
map.insert(m.name.clone(), m.clone());
|
|
}
|
|
let mut state: HashMap<String, u8> = HashMap::new();
|
|
let mut order: Vec<WorkspaceMember> = Vec::new();
|
|
|
|
for name in map.keys() {
|
|
if *state.get(name).unwrap_or(&0) == 0 {
|
|
visit(name, &map, &mut state, &mut order)?;
|
|
}
|
|
}
|
|
Ok(order)
|
|
}
|
|
|
|
fn visit(
|
|
name: &str,
|
|
map: &HashMap<String, WorkspaceMember>,
|
|
state: &mut HashMap<String, u8>,
|
|
order: &mut Vec<WorkspaceMember>,
|
|
) -> Result<(), String> {
|
|
match state.get(name).copied().unwrap_or(0) {
|
|
1 => return Err(format!("workspace dependency cycle at {name}")),
|
|
2 => return Ok(()),
|
|
_ => {}
|
|
}
|
|
state.insert(name.to_string(), 1);
|
|
let member = map
|
|
.get(name)
|
|
.ok_or_else(|| format!("unknown workspace member {name}"))?;
|
|
let mut seen = HashSet::new();
|
|
for dep in &member.deps {
|
|
if !seen.insert(dep) {
|
|
continue;
|
|
}
|
|
if !map.contains_key(dep) {
|
|
return Err(format!("workspace dependency '{dep}' not found"));
|
|
}
|
|
visit(dep, map, state, order)?;
|
|
}
|
|
state.insert(name.to_string(), 2);
|
|
order.push(member.clone());
|
|
Ok(())
|
|
}
|