mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 21:49:36 +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`
230 lines
7.2 KiB
Rust
230 lines
7.2 KiB
Rust
use crate::config::flags;
|
|
use crate::core::config::Config;
|
|
use crate::core::workspace::parse_workspace;
|
|
use crate::utils::build::parse_version_info;
|
|
use crate::utils::fs::{check_dir, find_project_root, with_dir};
|
|
use crate::utils::log::{error, warn};
|
|
use crate::utils::text::{BOLD_GREEN, colored};
|
|
use glob::glob;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
pub fn clean(args: &[String]) -> i32 {
|
|
let start_dir = match std::env::current_dir() {
|
|
Ok(dir) => dir,
|
|
Err(_) => {
|
|
error("Failed to determine current directory");
|
|
return 1;
|
|
}
|
|
};
|
|
let root = match find_project_root(&start_dir) {
|
|
Ok(Some(dir)) => dir,
|
|
Ok(None) => {
|
|
error("dcr.toml file not found");
|
|
return 1;
|
|
}
|
|
Err(_) => {
|
|
error("Failed to find project root");
|
|
return 1;
|
|
}
|
|
};
|
|
let flags = match parse_clean_flags(args) {
|
|
Ok(v) => v,
|
|
Err(msg) => {
|
|
error(&msg);
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
match with_dir(&root, || clean_from_root(&root, &flags)) {
|
|
Ok(()) => 0,
|
|
Err(msg) => {
|
|
error(&msg);
|
|
1
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CleanFlags {
|
|
profile: Option<String>,
|
|
target: Option<String>,
|
|
all: bool,
|
|
}
|
|
|
|
fn parse_clean_flags(args: &[String]) -> Result<CleanFlags, String> {
|
|
let mut profile: Option<String> = None;
|
|
let mut target: Option<String> = None;
|
|
let mut all = false;
|
|
let mut iter = args.iter();
|
|
while let Some(arg) = iter.next() {
|
|
if arg == "--all" {
|
|
all = true;
|
|
continue;
|
|
}
|
|
if arg == "--target" {
|
|
if let Some(t) = iter.next() {
|
|
target = Some(t.clone());
|
|
} else {
|
|
return Err("--target requires a value".to_string());
|
|
}
|
|
continue;
|
|
}
|
|
if arg.starts_with("--") {
|
|
let candidate = arg.trim_start_matches("--").to_string();
|
|
if flags(&candidate).is_some() {
|
|
if profile.is_some() {
|
|
return Err("Duplicate profile flag".to_string());
|
|
}
|
|
profile = Some(candidate);
|
|
continue;
|
|
}
|
|
}
|
|
return Err("Unknown argument".to_string());
|
|
}
|
|
Ok(CleanFlags {
|
|
profile,
|
|
target,
|
|
all,
|
|
})
|
|
}
|
|
|
|
fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
|
|
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
|
|
|
|
let target = flags.target.clone().or_else(|| {
|
|
Some(if cfg!(target_os = "linux") {
|
|
"x86_64-unknown-linux-gnu".to_string()
|
|
} else if cfg!(target_os = "macos") {
|
|
"x86_64-apple-darwin".to_string()
|
|
} else if cfg!(target_os = "windows") {
|
|
"x86_64-pc-windows-msvc".to_string()
|
|
} else {
|
|
"unknown".to_string()
|
|
})
|
|
});
|
|
|
|
if flags.all
|
|
&& let Some(workspace) = parse_workspace(
|
|
&config,
|
|
flags.profile.as_deref().unwrap_or("debug"),
|
|
target.as_deref(),
|
|
root,
|
|
)?
|
|
{
|
|
for member in &workspace.members {
|
|
clean_project_at(&member.path, flags.profile.as_deref(), target.as_deref())?;
|
|
}
|
|
}
|
|
clean_project_at(root, flags.profile.as_deref(), target.as_deref())
|
|
}
|
|
|
|
fn clean_project_at(
|
|
project_root: &Path,
|
|
profile: Option<&str>,
|
|
target: Option<&str>,
|
|
) -> Result<(), String> {
|
|
with_dir(project_root, || {
|
|
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
|
|
let project_name = std::env::current_dir()
|
|
.ok()
|
|
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
|
|
.unwrap_or_else(|| "project".to_string());
|
|
let items = check_dir(None).map_err(|_| "Failed to read project directory".to_string())?;
|
|
if !items.contains(&"dcr.toml".to_string()) {
|
|
return Err("dcr.toml file not found".to_string());
|
|
}
|
|
println!(
|
|
" Cleaning project `{}`",
|
|
colored(&project_name, BOLD_GREEN)
|
|
);
|
|
if let Some(profile) = profile {
|
|
let target_dir = if let Some(t) = target {
|
|
format!("target/{t}/{profile}")
|
|
} else {
|
|
format!("target/{profile}")
|
|
};
|
|
let target_items = check_dir(Some("target")).unwrap_or_default();
|
|
let parent_dir = target.unwrap_or("");
|
|
let dir_exists = if target.is_some() {
|
|
target_items.contains(&parent_dir.to_string())
|
|
} else {
|
|
target_items.contains(&profile.to_string())
|
|
};
|
|
if !dir_exists {
|
|
warn(&format!("Directory target/{} not found", target_dir));
|
|
} else {
|
|
println!(" Profile: {}", colored(profile, BOLD_GREEN));
|
|
if let Some(t) = target {
|
|
println!(" Target: {}", colored(t, BOLD_GREEN));
|
|
}
|
|
let _ = fs::remove_dir_all(&target_dir);
|
|
println!(
|
|
"{} Removed directory {}",
|
|
colored("\n ✔", BOLD_GREEN),
|
|
target_dir
|
|
);
|
|
}
|
|
clean_custom_paths(&config, profile)?;
|
|
return Ok(());
|
|
}
|
|
|
|
if items.contains(&"target".to_string()) {
|
|
let _ = fs::remove_dir_all("target");
|
|
println!(
|
|
"{} Removed directory target",
|
|
colored("\n ✔", BOLD_GREEN)
|
|
);
|
|
} else {
|
|
warn("Directory target not found");
|
|
}
|
|
clean_custom_paths(&config, "debug")?;
|
|
clean_custom_paths(&config, "release")?;
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn clean_custom_paths(config: &Config, profile: &str) -> Result<(), String> {
|
|
let patterns = match config.get("build.clean") {
|
|
Some(v) => v
|
|
.as_array()
|
|
.ok_or_else(|| "build.clean must be an array of strings".to_string())?
|
|
.iter()
|
|
.filter_map(|item| item.as_str())
|
|
.map(|s| {
|
|
let value = s.replace("{profile}", profile);
|
|
substitute_version_vars(&value, config)
|
|
})
|
|
.collect::<Vec<String>>(),
|
|
None => Vec::new(),
|
|
};
|
|
if patterns.is_empty() {
|
|
return Ok(());
|
|
}
|
|
for pattern in patterns {
|
|
for entry in glob(&pattern).map_err(|err| format!("glob error: {err}"))? {
|
|
let path = entry.map_err(|err| format!("glob error: {err}"))?;
|
|
if path.is_dir() {
|
|
let _ = fs::remove_dir_all(&path);
|
|
} else if path.is_file() {
|
|
let _ = fs::remove_file(&path);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn substitute_version_vars(value: &str, config: &Config) -> String {
|
|
let version = config
|
|
.get("package.version")
|
|
.and_then(|v| v.as_str())
|
|
.unwrap_or("");
|
|
let info = parse_version_info(version);
|
|
value
|
|
.replace("{version}", &info.full)
|
|
.replace("{version_major}", &info.major)
|
|
.replace("{version_minor}", &info.minor)
|
|
.replace("{version_patch}", &info.patch)
|
|
.replace("{version_suffix}", &info.suffix)
|
|
.replace("{version_suffix_dash}", &info.suffix_dash)
|
|
}
|