mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 12:58:34 +00:00
Added:
- `build.kind = "efi"` — UEFI PE32+ executable support. Links with `-shared -nostdlib -Wl,-dll -Wl,--subsystem,10`, output via `efi_path()` with `.efi` extension, rejected by `dcr run`.
- `build.kind = "elf"` — bare-metal ELF executable support. Uses `elf_path()` output path, rejected by `dcr run`, no extra linker flags.
Fixed:
- `dcr run` with `--target` or `build.target` now correctly finds the binary in the arch-specific target directory.
- `dcr run` checks profile-specific `build.{profile}.kind` before rejecting library builds (previously only checked `build.kind`).
- `dcr clean` now reads `build.target` from `dcr.toml` when no `--target` flag is given.
- Linux: target directory is now consistent — `target/<arch>-unknown-linux-gnu/<profile>/` (no stale `target/<profile>/`).
- `dcr.lock` is now populated with all resolved dependencies (registry, path, git) instead of being always empty.
- Git dependencies are now properly recognized and recorded in `dcr.lock`.
- `is_registry_dep` correctly identifies version-based registry dependency tables (`{ version = "..." }`) and rejects tables with unknown keys.
- `dcr test` accepts `--debug`/`--release` flags (defaults to `debug` profile instead of always `release`).
- Build fingerprint cache (`build_cache_path`) now includes `target_dir` — different targets no longer share a single cache entry.
- `object_path` no longer hardcodes the `src/` prefix — works correctly with custom `build.roots` (e.g. `roots = ["lib"]`).
- `dcr update` now generates `.exe` candidates for all Windows targets, not just `x86_64-pc-windows-msvc`.
- Compiler existence is verified before starting the build (clear error if compiler is not found in PATH).
- Consolidated duplicate `OUTPUT_MUTEX` definitions — MSVC backend now uses the shared mutex from `common.rs`.
- Registry cache root directory is created if missing (prevents cryptic errors when `~/.dcr/` does not exist).
This commit is contained in:
@@ -5,6 +5,10 @@ use std::sync::{Mutex, OnceLock};
|
||||
|
||||
static OUTPUT_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
|
||||
pub fn get_output_lock() -> &'static Mutex<()> {
|
||||
OUTPUT_MUTEX.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
pub fn collect_sources(
|
||||
roots: &[PathBuf],
|
||||
extensions: &[&str],
|
||||
@@ -230,10 +234,16 @@ pub fn normalize_source_path(path: &Path) -> String {
|
||||
|
||||
pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||
let src_path = Path::new(source);
|
||||
let rel = src_path
|
||||
.strip_prefix("./src")
|
||||
.or_else(|_| src_path.strip_prefix("src"))
|
||||
.unwrap_or(src_path);
|
||||
let stripped = src_path.strip_prefix("./").unwrap_or(src_path);
|
||||
let rel = stripped
|
||||
.components()
|
||||
.skip(1)
|
||||
.collect::<std::path::PathBuf>();
|
||||
let rel = if rel.components().next().is_some() {
|
||||
rel
|
||||
} else {
|
||||
stripped.to_path_buf()
|
||||
};
|
||||
let mut out = obj_dir.join(rel);
|
||||
out.set_extension(obj_ext.trim_start_matches('.'));
|
||||
out.to_string_lossy().to_string()
|
||||
|
||||
@@ -18,7 +18,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
};
|
||||
let start_time = Instant::now();
|
||||
let sources = collect_sources(ctx)?;
|
||||
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||
let obj_dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
None => Path::new("./target").join(ctx.profile).join("obj"),
|
||||
};
|
||||
let objects = build_objects(assembler, &sources, &obj_dir, ctx, "o")?;
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
@@ -80,6 +83,8 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
let out_path = if ctx.kind == "sharedlib" {
|
||||
platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else if ctx.kind == "elf" {
|
||||
platform::elf_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
@@ -33,6 +33,12 @@ pub struct BuildContext<'a> {
|
||||
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
let compiler = ctx.compiler.to_lowercase();
|
||||
if !check_compiler_exists(ctx.compiler) {
|
||||
return Err(format!(
|
||||
"Compiler not found: {}. Make sure it is installed and available in PATH.",
|
||||
ctx.compiler
|
||||
));
|
||||
}
|
||||
if compiler.contains("clang-cl") {
|
||||
return msvc::build(ctx);
|
||||
}
|
||||
@@ -48,6 +54,14 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
unix_cc::build(ctx)
|
||||
}
|
||||
|
||||
fn check_compiler_exists(compiler: &str) -> bool {
|
||||
let name = if compiler.is_empty() { "cc" } else { compiler };
|
||||
std::process::Command::new(name)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
let compiler = ctx.compiler.to_lowercase();
|
||||
if compiler.contains("clang-cl") || compiler == "cl" || compiler.contains("msvc") {
|
||||
|
||||
@@ -18,7 +18,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
let start_time = Instant::now();
|
||||
let sources = collect_sources(ctx)?;
|
||||
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||
let obj_dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
None => Path::new("./target").join(ctx.profile).join("obj"),
|
||||
};
|
||||
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "obj")?;
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
@@ -92,6 +95,8 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
let out_path = if ctx.kind == "sharedlib" {
|
||||
platform::shared_lib_path(ctx.profile, ctx.project_name, ctx.target_dir)
|
||||
} else if ctx.kind == "elf" {
|
||||
platform::elf_path(ctx.profile, ctx.project_name, ctx.target_dir)
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir)
|
||||
};
|
||||
@@ -185,8 +190,6 @@ fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||
}
|
||||
}
|
||||
|
||||
static OUTPUT_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
fn build_objects(
|
||||
compiler: &str,
|
||||
sources: &[String],
|
||||
@@ -282,7 +285,7 @@ fn build_object(
|
||||
}
|
||||
}
|
||||
|
||||
let _lock = OUTPUT_MUTEX.lock().unwrap();
|
||||
let _lock = common::get_output_lock().lock().unwrap();
|
||||
|
||||
if !output.status.success() {
|
||||
eprint!("{}", clean_stdout);
|
||||
|
||||
@@ -18,7 +18,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
};
|
||||
let start_time = Instant::now();
|
||||
let sources = collect_sources(ctx)?;
|
||||
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||
let obj_dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
None => Path::new("./target").join(ctx.profile).join("obj"),
|
||||
};
|
||||
let objects = build_objects(assembler, &sources, &obj_dir, ctx, "o")?;
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
@@ -80,6 +83,8 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
let out_path = if ctx.kind == "sharedlib" {
|
||||
platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else if ctx.kind == "elf" {
|
||||
platform::elf_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
@@ -20,7 +20,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
ctx.exclude_dirs,
|
||||
ctx.include_paths,
|
||||
)?;
|
||||
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||
let obj_dir = match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).join("obj"),
|
||||
None => Path::new("./target").join(ctx.profile).join("obj"),
|
||||
};
|
||||
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o")?;
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
@@ -51,6 +54,12 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
cmd.arg("-shared");
|
||||
}
|
||||
}
|
||||
if ctx.kind == "efi" {
|
||||
cmd.arg("-shared");
|
||||
cmd.arg("-nostdlib");
|
||||
cmd.arg("-Wl,-dll");
|
||||
cmd.arg("-Wl,--subsystem,10");
|
||||
}
|
||||
for obj in &objects {
|
||||
cmd.arg(obj);
|
||||
}
|
||||
@@ -73,6 +82,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
let out_path = if ctx.kind == "sharedlib" {
|
||||
platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else if ctx.kind == "efi" {
|
||||
platform::efi_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else if ctx.kind == "elf" {
|
||||
platform::elf_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ const DEFAULT_LANGUAGE: &str = "c";
|
||||
const DEFAULT_STANDARD: &str = "c11";
|
||||
const DEFAULT_COMPILER: &str = "clang";
|
||||
const DEFAULT_KIND: &str = "bin";
|
||||
const VALID_KINDS: &[&str] = &["bin", "staticlib", "sharedlib", "efi", "elf"];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
@@ -242,7 +243,7 @@ impl Config {
|
||||
validate_toolchain(self.typed.toolchain.as_ref())?;
|
||||
if let Some(kind) = &self.typed.build.kind {
|
||||
let kind = kind.trim();
|
||||
if !kind.is_empty() && kind != "bin" && kind != "staticlib" && kind != "sharedlib" {
|
||||
if !kind.is_empty() && !VALID_KINDS.contains(&kind) {
|
||||
return Err(ConfigError::Invalid("build.kind is invalid".into()));
|
||||
}
|
||||
}
|
||||
@@ -410,7 +411,7 @@ impl Config {
|
||||
}
|
||||
if let Some(kind) = table.get("kind").and_then(|v| v.as_str()) {
|
||||
let kind = kind.trim();
|
||||
if !kind.is_empty() && kind != "bin" && kind != "staticlib" && kind != "sharedlib" {
|
||||
if !kind.is_empty() && !VALID_KINDS.contains(&kind) {
|
||||
return Err(ConfigError::Invalid(format!(
|
||||
"build.{profile}.kind is invalid"
|
||||
)));
|
||||
@@ -919,14 +920,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn validate_valid_kinds() {
|
||||
for kind in ["bin", "staticlib", "sharedlib"] {
|
||||
let dir = temp_dir(&format!("kind_{kind}"));
|
||||
let content = format!(
|
||||
for &kind in VALID_KINDS {
|
||||
let dir = temp_dir("valid_kind");
|
||||
let toml = format!(
|
||||
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n\n[build]\nlanguage = \"c\"\nstandard = \"c11\"\ncompiler = \"clang\"\nkind = \"{kind}\"\n\n[dependencies]\n"
|
||||
);
|
||||
let path = write_toml_file(&dir, &content);
|
||||
let result = Config::open(&path.to_string_lossy());
|
||||
assert!(result.is_ok(), "kind '{kind}' should be valid");
|
||||
let path = write_toml_file(&dir, &toml);
|
||||
assert!(
|
||||
Config::open(&path.to_string_lossy()).is_ok(),
|
||||
"kind '{kind}' should be valid"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,20 @@ pub mod register;
|
||||
|
||||
use crate::core::config::Config;
|
||||
use crate::core::deps::common::ResolvedDeps;
|
||||
use crate::core::deps::lock::{DepLock, write_lock};
|
||||
use std::path::Path;
|
||||
|
||||
fn dep_version(path: &Path) -> String {
|
||||
Config::open(&path.join("dcr.toml").to_string_lossy())
|
||||
.ok()
|
||||
.and_then(|c| {
|
||||
c.get("package.version")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn resolve_deps(
|
||||
config: &Config,
|
||||
_profile: &str,
|
||||
@@ -26,7 +38,7 @@ pub fn resolve_deps(
|
||||
.to_string();
|
||||
|
||||
let deps_table = config.get("dependencies").and_then(|v| v.as_table());
|
||||
let lock_packages = Vec::new();
|
||||
let mut lock_packages: Vec<DepLock> = Vec::new();
|
||||
|
||||
if let Some(deps) = deps_table {
|
||||
for (name, value) in deps {
|
||||
@@ -45,6 +57,23 @@ pub fn resolve_deps(
|
||||
.to_string(),
|
||||
);
|
||||
resolved.libs.push(name.clone());
|
||||
|
||||
lock_packages.push(DepLock {
|
||||
name: name.clone(),
|
||||
version: pkg_info
|
||||
.get("version")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
checksum: String::new(),
|
||||
source: format!(
|
||||
"registry+{}",
|
||||
pkg_info
|
||||
.get("registry_url")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("https://dcr-registry.pages.dev")
|
||||
),
|
||||
});
|
||||
} else if let Some(path) = path_dep_path(value) {
|
||||
let dep_root = project_root.join(path);
|
||||
if let Some(table) = value.as_table() {
|
||||
@@ -86,11 +115,25 @@ pub fn resolve_deps(
|
||||
push_default_lib_dirs(&mut resolved.lib_dirs, &dep_root);
|
||||
resolved.libs.push(name.clone());
|
||||
}
|
||||
|
||||
lock_packages.push(DepLock {
|
||||
name: name.clone(),
|
||||
version: dep_version(&dep_root),
|
||||
checksum: String::new(),
|
||||
source: format!("path+{}", dep_root.display()),
|
||||
});
|
||||
} else if let Some(git_info) = git_dep(value) {
|
||||
lock_packages.push(DepLock {
|
||||
name: name.clone(),
|
||||
version: git_info.version.unwrap_or_default(),
|
||||
checksum: String::new(),
|
||||
source: format!("git+{}", git_info.url),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::core::deps::lock::write_lock(
|
||||
write_lock(
|
||||
project_root,
|
||||
&project_name,
|
||||
&project_version,
|
||||
@@ -100,6 +143,24 @@ pub fn resolve_deps(
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
struct GitDep<'a> {
|
||||
url: &'a str,
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
fn git_dep(value: &toml::Value) -> Option<GitDep<'_>> {
|
||||
if let Some(table) = value.as_table()
|
||||
&& let Some(url) = table.get("git").and_then(|v| v.as_str())
|
||||
{
|
||||
let version = table
|
||||
.get("version")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
return Some(GitDep { url, version });
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn path_dep_path(value: &toml::Value) -> Option<&str> {
|
||||
if let Some(table) = value.as_table() {
|
||||
return table.get("path").and_then(|v| v.as_str());
|
||||
|
||||
@@ -51,10 +51,18 @@ pub fn get_index_path() -> PathBuf {
|
||||
}
|
||||
|
||||
pub fn get_registry_cache_root() -> PathBuf {
|
||||
get_index_path()
|
||||
.parent()
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
let index_path = get_index_path();
|
||||
if let Some(parent) = index_path.parent() {
|
||||
if parent.exists() {
|
||||
return parent.to_path_buf();
|
||||
}
|
||||
if let Err(e) = std::fs::create_dir_all(parent) {
|
||||
eprintln!("Warning: failed to create registry cache directory: {e}");
|
||||
}
|
||||
parent.to_path_buf()
|
||||
} else {
|
||||
PathBuf::from(".")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn package_root_from_registry_info(pkg_info: &JsonValue) -> Result<PathBuf, String> {
|
||||
@@ -124,12 +132,16 @@ pub fn is_registry_dep(value: &TomlValue) -> bool {
|
||||
return !is_path_like_string(raw) && !is_git_like_string(raw);
|
||||
}
|
||||
if let Some(table) = value.as_table() {
|
||||
return !table.contains_key("git")
|
||||
&& !table.contains_key("path")
|
||||
&& !table.contains_key("url")
|
||||
&& !table.contains_key("version");
|
||||
if table.contains_key("git") || table.contains_key("path") || table.contains_key("url") {
|
||||
return false;
|
||||
}
|
||||
table.contains_key("version")
|
||||
|| table.contains_key("features")
|
||||
|| table.contains_key("optional")
|
||||
|| table.contains_key("registry")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn path_from_string_dep(value: &TomlValue) -> Option<&str> {
|
||||
|
||||
Reference in New Issue
Block a user