mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 09:48:31 +00:00
268 lines
8.6 KiB
Rust
268 lines
8.6 KiB
Rust
// DCR — Cargo-like C/C++ project manager.
|
|
//
|
|
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
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(|| {
|
|
config
|
|
.get("build.target")
|
|
.and_then(|v| v.as_str())
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty())
|
|
})
|
|
.or_else(|| {
|
|
Some(if cfg!(target_os = "linux") {
|
|
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
|
|
} 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 if cfg!(any(
|
|
target_os = "freebsd",
|
|
target_os = "openbsd",
|
|
target_os = "netbsd",
|
|
target_os = "dragonfly"
|
|
)) {
|
|
format!(
|
|
"{}-unknown-{}",
|
|
std::env::consts::ARCH,
|
|
std::env::consts::OS
|
|
)
|
|
} 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)
|
|
}
|