feat: add .dcr/ build facts and canonical artifact paths for IDE integrations

This commit is contained in:
2026-08-05 00:37:00 +08:00
parent 794d571d58
commit b9cbef43e9
27 changed files with 1590 additions and 326 deletions

View File

@@ -30,6 +30,64 @@ pub(crate) fn artifact_dir(ctx: &BuildContext) -> PathBuf {
}
}
pub fn resolve_artifact_path(
kind: &str,
profile: &str,
project_name: &str,
target_dir: Option<&str>,
output_filename: Option<&str>,
output_extension: Option<&str>,
) -> Option<String> {
if crate::utils::build::is_flat_bin(kind) {
let name = output_filename.unwrap_or(project_name);
let ext = output_extension.unwrap_or("bin");
let file = if ext.is_empty() {
name.to_string()
} else {
format!("{name}.{ext}")
};
let dir = match target_dir {
Some(dir) => Path::new(dir).to_path_buf(),
None => Path::new("./target").join(profile),
};
return Some(dir.join(file).to_string_lossy().to_string());
}
if matches!(kind, "none" | "custom") {
return None;
}
let name = output_filename.unwrap_or(project_name);
let ext = output_extension.unwrap_or("");
let final_name = if ext.is_empty() {
name.to_string()
} else {
format!("{}.{}", name, ext)
};
let path = match kind {
"staticlib" => platform::lib_path(profile, &final_name, target_dir),
"sharedlib" => platform::shared_lib_path(profile, &final_name, target_dir),
"efi" => platform::efi_path(profile, &final_name, target_dir),
"elf" => platform::elf_path(profile, &final_name, target_dir),
_ => platform::bin_path(profile, &final_name, target_dir),
};
Some(path)
}
pub fn absolute_artifact_path(project_root: &Path, relative_or_abs: &str) -> PathBuf {
let p = Path::new(relative_or_abs);
let joined = if p.is_absolute() {
p.to_path_buf()
} else {
let stripped = relative_or_abs
.strip_prefix("./")
.unwrap_or(relative_or_abs);
project_root.join(stripped)
};
crate::utils::fs::canonicalize_path(&joined)
}
pub(crate) fn flat_output_path(ctx: &BuildContext) -> String {
let name = ctx.output_filename.unwrap_or(ctx.project_name);
let ext = ctx.output_extension.unwrap_or("bin");
@@ -282,3 +340,66 @@ fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64,
Err(format!("Build failed:\nstdout: {stdout}\nstderr: {stderr}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_bin_default() {
let p = resolve_artifact_path("bin", "debug", "hello", None, None, None).unwrap();
if cfg!(target_os = "linux") {
let arch = std::env::consts::ARCH;
assert_eq!(p, format!("./target/{arch}-unknown-linux-gnu/debug/hello"));
} else if cfg!(windows) {
assert_eq!(p, "./target/debug/hello.exe");
} else {
assert_eq!(p, "./target/debug/hello");
}
}
#[test]
fn resolve_bin_custom_target_dir() {
let p = resolve_artifact_path("bin", "debug", "hello", Some("/out"), None, None).unwrap();
if cfg!(windows) {
assert_eq!(p, "/out/hello.exe");
} else {
assert_eq!(p, "/out/hello");
}
}
#[test]
fn resolve_staticlib() {
let p = resolve_artifact_path("staticlib", "release", "mylib", Some("dist"), None, None)
.unwrap();
if cfg!(windows) {
assert_eq!(p, "dist/mylib.lib");
} else {
assert_eq!(p, "dist/libmylib.a");
}
}
#[test]
fn resolve_with_filename_ext() {
let p = resolve_artifact_path(
"bin",
"debug",
"pkg",
Some("/out"),
Some("KERNEL"),
Some("ELF"),
)
.unwrap();
assert_eq!(
p,
std::path::Path::new("/out")
.join("KERNEL.ELF")
.to_string_lossy()
);
}
#[test]
fn resolve_none_kind() {
assert!(resolve_artifact_path("none", "debug", "x", None, None, None).is_none());
}
}

View File

@@ -106,33 +106,22 @@ fn build_cache_path(profile: &str, target_dir: Option<&str>) -> PathBuf {
}
}
fn build_output_path(ctx: &BuildContext) -> String {
if crate::utils::build::is_flat_bin(ctx.kind) {
return crate::core::build::builder::artifact::flat_output_path(ctx);
}
let name = ctx.output_filename.unwrap_or(ctx.project_name);
let ext = ctx.output_extension.unwrap_or("");
let final_name = if ext.is_empty() {
name.to_string()
} else {
format!("{}.{}", name, ext)
};
if ctx.kind == "staticlib" {
return crate::platform::lib_path(ctx.profile, &final_name, ctx.target_dir);
}
if ctx.kind == "sharedlib" {
return crate::platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir);
}
if ctx.kind == "efi" {
return crate::platform::efi_path(ctx.profile, &final_name, ctx.target_dir);
}
if ctx.kind == "elf" {
return crate::platform::elf_path(ctx.profile, &final_name, ctx.target_dir);
}
crate::platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
pub fn build_output_path(ctx: &BuildContext) -> String {
crate::core::build::builder::artifact::resolve_artifact_path(
ctx.kind,
ctx.profile,
ctx.project_name,
ctx.target_dir,
ctx.output_filename,
ctx.output_extension,
)
.unwrap_or_else(|| {
crate::platform::bin_path(
ctx.profile,
ctx.output_filename.unwrap_or(ctx.project_name),
ctx.target_dir,
)
})
}
pub(crate) fn collect_header_files(

View File

@@ -38,8 +38,9 @@ use crate::core::workspace::parse_workspace;
use crate::utils::build::{
default_target_triple, get_bool_with_profile, get_config_opt, get_config_str,
get_language_with_profile, is_bare_metal_target, normalize_kind, normalize_platform,
normalize_target, normalize_target_os, parse_version_info, prepend_clang_target_flag,
primary_language, resolve_compiler, resolve_pkg_config_flags, resolve_tool, substitute_vars,
normalize_target_os, parse_version_info, prepend_clang_target_flag, primary_language,
resolve_artifact_target_dir, resolve_compiler, resolve_pkg_config_flags, resolve_tool,
substitute_vars,
};
use crate::utils::fs::check_dir;
use std::fs;
@@ -544,70 +545,14 @@ fn build_project_at(
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
let out_dir_config = get_build_string_with_profile(&config, "out_dir", profile);
let target_dir_binding = if !out_dir_config.is_empty() {
let p = Path::new(&out_dir_config);
if p.is_absolute() {
out_dir_config
} else {
project_root.join(p).to_string_lossy().to_string()
}
} else {
match workspace_root {
Some(root) => {
let target_str = build_target
.map(normalize_target_os)
.filter(|t| !t.is_empty());
let rel: PathBuf = match target_str {
Some(ref t) => Path::new("target").join(t).join(profile),
None => {
let default_dir = if cfg!(target_os = "linux") {
let arch = std::env::consts::ARCH;
format!("{arch}-unknown-linux-gnu/{profile}")
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
let arch = std::env::consts::ARCH;
let os = std::env::consts::OS;
format!("{arch}-unknown-{os}/{profile}")
} else {
profile.to_string()
};
Path::new("target").join(&default_dir)
}
};
root.join(&rel).to_string_lossy().to_string()
}
None => {
let base_dir = if has_explicit_target {
PathBuf::from(
normalize_target(&build_target_owned, profile)
.expect("explicit build target must be non-empty"),
)
} else {
let default_dir = if cfg!(target_os = "linux") {
let arch = std::env::consts::ARCH;
format!("{arch}-unknown-linux-gnu/{profile}")
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
let arch = std::env::consts::ARCH;
let os = std::env::consts::OS;
format!("{arch}-unknown-{os}/{profile}")
} else {
profile.to_string()
};
Path::new("target").join(&default_dir)
};
project_root.join(base_dir).to_string_lossy().to_string()
}
}
};
let target_dir_binding = resolve_artifact_target_dir(
project_root,
workspace_root,
profile,
&build_target_owned,
&out_dir_config,
has_explicit_target,
);
let target_dir = if target_dir_binding.is_empty() {
None
} else {