mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 20:09:36 +00:00
feat: enhance build configuration with C++ standard support and variable substitution
- Added `cxx_standard` field to the build configuration for specifying C++ standards. - Updated documentation to reflect new `cxx_standard` and variable substitution capabilities in `cflags`, `ldflags`, and `include`. - Implemented logic to handle C++ standard flags in the build process. - Modified object file naming to include source file extensions. - Updated various builder modules to support new language parsing logic. - Introduced issue and feature request templates for better community engagement.
This commit is contained in:
@@ -230,11 +230,18 @@ fn get_list_with_profile_and_target(
|
||||
// Custom from target/profile
|
||||
if let Some(val) = get_config_value_raw(config, "build", field, profile, target) {
|
||||
if let Some(_arr) = val.as_array() {
|
||||
let custom = parse_string_array(&val, &format!("build.{field}"))?;
|
||||
if inherit {
|
||||
out.extend(custom);
|
||||
} else {
|
||||
out = custom;
|
||||
let is_base = inherit
|
||||
&& config
|
||||
.get(&format!("build.{field}"))
|
||||
.map(|v| v == &val)
|
||||
.unwrap_or(false);
|
||||
if !is_base {
|
||||
let custom = parse_string_array(&val, &format!("build.{field}"))?;
|
||||
if inherit {
|
||||
out.extend(custom);
|
||||
} else {
|
||||
out = custom;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if inherit {
|
||||
@@ -513,6 +520,8 @@ fn build_project_at(
|
||||
let build_language = get_language_with_profile(&config, profile)?;
|
||||
let build_standard =
|
||||
get_string_with_profile_and_target(&config, "standard", profile, build_target);
|
||||
let build_cxx_standard =
|
||||
get_string_with_profile_and_target(&config, "cxx_standard", profile, build_target);
|
||||
let build_kind = get_string_with_profile_and_target(&config, "kind", profile, build_target);
|
||||
let build_platform =
|
||||
get_string_with_profile_and_target(&config, "platform", profile, build_target);
|
||||
@@ -716,12 +725,29 @@ fn build_project_at(
|
||||
}
|
||||
|
||||
let target_dir_binding = normalize_target(build_target.unwrap_or(""), profile);
|
||||
let version_info = parse_version_info(&project_version);
|
||||
let substitute = |s: &str| -> String {
|
||||
s.replace("{profile}", profile)
|
||||
.replace("{name}", &project_name)
|
||||
.replace("{version}", &version_info.full)
|
||||
.replace("{version_major}", &version_info.major)
|
||||
.replace("{version_minor}", &version_info.minor)
|
||||
.replace("{version_patch}", &version_info.patch)
|
||||
.replace("{version_suffix}", &version_info.suffix)
|
||||
.replace("{version_suffix_dash}", &version_info.suffix_dash)
|
||||
};
|
||||
let resolved_cflags: Vec<String> = resolved_cflags.iter().map(|f| substitute(f)).collect();
|
||||
let modified_ldflags: Vec<String> =
|
||||
resolved_ldflags.iter().map(|f| substitute(f)).collect();
|
||||
let merged_include_dirs: Vec<String> =
|
||||
merged_include_dirs.iter().map(|f| substitute(f)).collect();
|
||||
let ctx = BuildContext {
|
||||
profile,
|
||||
project_name: &project_name,
|
||||
compiler: &resolved_compiler,
|
||||
language: &build_language,
|
||||
standard: &build_standard,
|
||||
cxx_standard: &build_cxx_standard,
|
||||
target: build_target,
|
||||
target_dir: target_dir_binding.as_deref(),
|
||||
kind: normalize_kind(&build_kind),
|
||||
@@ -740,7 +766,7 @@ fn build_project_at(
|
||||
lib_dirs: &resolved.lib_dirs,
|
||||
libs: &resolved.libs,
|
||||
cflags: &resolved_cflags,
|
||||
ldflags: &resolved_ldflags,
|
||||
ldflags: &modified_ldflags,
|
||||
output_filename: if output_filename.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -763,7 +789,6 @@ fn build_project_at(
|
||||
let tool_execs = resolve_toolchain_execs(&tc_uic, &tc_moc, &tc_rcc, &pkg_configs);
|
||||
let step_flags =
|
||||
build_step_flags(&resolved_cflags, &resolved.include_dirs, &resolved_compiler);
|
||||
let version_info = parse_version_info(&project_version);
|
||||
let step_vars = StepVars {
|
||||
profile,
|
||||
version: &version_info.full,
|
||||
|
||||
@@ -171,6 +171,7 @@ pub struct ProjectInfo {
|
||||
pub profile: String,
|
||||
pub language: String,
|
||||
pub standard: String,
|
||||
pub cxx_standard: String,
|
||||
pub compiler: String,
|
||||
pub kind: String,
|
||||
pub sources: Vec<String>,
|
||||
@@ -228,6 +229,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result<ProjectInfo,
|
||||
let version = get_config_str(&config, "package.version");
|
||||
let language = get_language_with_profile_or_default(&config, profile);
|
||||
let standard = get_string_with_profile(&config, "standard", profile);
|
||||
let cxx_standard = get_string_with_profile(&config, "cxx_standard", profile);
|
||||
let compiler_s = get_string_with_profile(&config, "compiler", profile);
|
||||
let kind = get_string_with_profile(&config, "kind", profile);
|
||||
let build_target = get_string_with_profile(&config, "target", profile);
|
||||
@@ -339,6 +341,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result<ProjectInfo,
|
||||
compiler: &resolved_compiler,
|
||||
language: &language,
|
||||
standard: &standard,
|
||||
cxx_standard: &cxx_standard,
|
||||
target: Some(build_target.as_str()),
|
||||
target_dir: target_dir_binding.as_deref(),
|
||||
kind: normalize_kind(&kind),
|
||||
@@ -381,6 +384,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result<ProjectInfo,
|
||||
profile: profile.to_string(),
|
||||
language,
|
||||
standard,
|
||||
cxx_standard,
|
||||
compiler: resolved_compiler,
|
||||
kind: normalize_kind(&kind).to_string(),
|
||||
sources: abs_sources,
|
||||
@@ -471,6 +475,10 @@ fn project_info_to_json(info: &ProjectInfo) -> String {
|
||||
" \"standard\": {},\n",
|
||||
json_str(&info.standard)
|
||||
));
|
||||
out.push_str(&format!(
|
||||
" \"cxx_standard\": {},\n",
|
||||
json_str(&info.cxx_standard)
|
||||
));
|
||||
out.push_str(&format!(
|
||||
" \"compiler\": {},\n",
|
||||
json_str(&info.compiler)
|
||||
@@ -575,6 +583,13 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
|
||||
};
|
||||
cmd.push(compiler.to_string());
|
||||
cmd.push("-c".to_string());
|
||||
|
||||
// ASM x flag — must be before source file
|
||||
if let Some(flag) = asm_lang_flag(source) {
|
||||
cmd.push("-x".to_string());
|
||||
cmd.push(flag.to_string());
|
||||
}
|
||||
|
||||
cmd.push(source.to_string());
|
||||
|
||||
// Object path (for -o, approximate — not critical for IntelliSense)
|
||||
@@ -595,15 +610,18 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
|
||||
cmd.push("-fPIC".to_string());
|
||||
}
|
||||
|
||||
// ASM x flag
|
||||
if let Some(flag) = asm_lang_flag(source) {
|
||||
cmd.push("-x".to_string());
|
||||
cmd.push(flag.to_string());
|
||||
}
|
||||
|
||||
// -std=
|
||||
if !info.standard.is_empty() && info.language.to_lowercase() != "asm" {
|
||||
cmd.push(format!("-std={}", info.standard));
|
||||
let ext = Path::new(source)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
|
||||
if is_cpp && !info.cxx_standard.is_empty() {
|
||||
cmd.push(format!("-std={}", info.cxx_standard));
|
||||
} else if !is_cpp {
|
||||
cmd.push(format!("-std={}", info.standard));
|
||||
}
|
||||
}
|
||||
|
||||
// Default profile flags (mirrors unix_cc.rs defaults)
|
||||
|
||||
@@ -262,7 +262,22 @@ pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||
stripped.to_path_buf()
|
||||
};
|
||||
let mut out = obj_dir.join(rel);
|
||||
out.set_extension(obj_ext.trim_start_matches('.'));
|
||||
let ext = out
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let obj_ext = obj_ext.trim_start_matches('.');
|
||||
if !ext.is_empty() {
|
||||
let stem = out
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
out.set_file_name(format!("{stem}.{ext}.{obj_ext}"));
|
||||
} else {
|
||||
out.set_extension(obj_ext);
|
||||
}
|
||||
out.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
@@ -379,28 +394,28 @@ mod tests {
|
||||
fn object_path_basic() {
|
||||
let obj_dir = Path::new("target/debug/obj");
|
||||
let result = object_path(obj_dir, "./src/main.c", "o");
|
||||
assert_eq!(result, "target/debug/obj/main.o");
|
||||
assert_eq!(result, "target/debug/obj/main.c.o");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_path_nested() {
|
||||
let obj_dir = Path::new("target/debug/obj");
|
||||
let result = object_path(obj_dir, "./src/core/utils.c", "o");
|
||||
assert_eq!(result, "target/debug/obj/core/utils.o");
|
||||
assert_eq!(result, "target/debug/obj/core/utils.c.o");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_path_no_prefix() {
|
||||
let obj_dir = Path::new("target/debug/obj");
|
||||
let result = object_path(obj_dir, "src/main.c", "o");
|
||||
assert_eq!(result, "target/debug/obj/main.o");
|
||||
assert_eq!(result, "target/debug/obj/main.c.o");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_path_msvc_ext() {
|
||||
let obj_dir = Path::new("target/debug/obj");
|
||||
let result = object_path(obj_dir, "./src/main.c", "obj");
|
||||
assert_eq!(result, "target/debug/obj/main.obj");
|
||||
assert_eq!(result, "target/debug/obj/main.c.obj");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::time::Instant;
|
||||
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
let lang = ctx.language.to_lowercase();
|
||||
if lang.split('+').any(|p| p.trim() != "asm") {
|
||||
if lang.split(',').any(|p| p.trim() != "asm") {
|
||||
return Err("GAS backend requires build.language = \"asm\"".to_string());
|
||||
}
|
||||
let assembler = if ctx.compiler.is_empty() {
|
||||
|
||||
@@ -27,7 +27,7 @@ pub struct BuildContext<'a> {
|
||||
pub compiler: &'a str,
|
||||
pub language: &'a str,
|
||||
pub standard: &'a str,
|
||||
#[allow(dead_code)]
|
||||
pub cxx_standard: &'a str,
|
||||
pub target: Option<&'a str>,
|
||||
pub target_dir: Option<&'a str>,
|
||||
pub kind: &'a str,
|
||||
|
||||
@@ -144,8 +144,8 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
|
||||
fn source_extensions(language: &str) -> Vec<&str> {
|
||||
let mut out = Vec::new();
|
||||
for part in language.split('+') {
|
||||
let lang = part.trim().to_lowercase();
|
||||
for part in language.split(',').map(|s| s.trim()) {
|
||||
let lang = part.to_lowercase();
|
||||
match lang.as_str() {
|
||||
"c" => out.extend(["c"]),
|
||||
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
|
||||
@@ -243,18 +243,26 @@ fn build_object(
|
||||
let mut cmd = Command::new(compiler);
|
||||
cmd.arg("/nologo");
|
||||
|
||||
match ctx.language.to_lowercase().as_str() {
|
||||
"c" => {
|
||||
cmd.arg("/TC");
|
||||
}
|
||||
"c++" | "cpp" | "cxx" => {
|
||||
cmd.arg("/TP");
|
||||
}
|
||||
_ => return Err("Unsupported language".to_string()),
|
||||
let ext = Path::new(source)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
|
||||
if is_cpp {
|
||||
cmd.arg("/TP");
|
||||
} else {
|
||||
cmd.arg("/TC");
|
||||
}
|
||||
|
||||
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
|
||||
let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?;
|
||||
let std_val = if is_cpp && !ctx.cxx_standard.is_empty() {
|
||||
ctx.cxx_standard
|
||||
} else if !is_cpp && !ctx.standard.is_empty() {
|
||||
ctx.standard
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if !std_val.is_empty() {
|
||||
let std_flag = msvc_standard_flag(if is_cpp { "c++" } else { "c" }, std_val)?;
|
||||
cmd.arg(std_flag);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::time::Instant;
|
||||
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
let lang = ctx.language.to_lowercase();
|
||||
if lang.split('+').any(|p| p.trim() != "asm") {
|
||||
if lang.split(',').any(|p| p.trim() != "asm") {
|
||||
return Err("NASM backend requires build.language = \"asm\"".to_string());
|
||||
}
|
||||
let assembler = if ctx.compiler.is_empty() {
|
||||
|
||||
@@ -133,8 +133,8 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
|
||||
fn source_extensions(language: &str) -> Vec<&str> {
|
||||
let mut out = Vec::new();
|
||||
for part in language.split('+') {
|
||||
let lang = part.trim().to_lowercase();
|
||||
for part in language.split(',').map(|s| s.trim()) {
|
||||
let lang = part.to_lowercase();
|
||||
match lang.as_str() {
|
||||
"c" => out.extend(["c"]),
|
||||
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
|
||||
@@ -210,16 +210,18 @@ fn build_object(
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(compiler);
|
||||
cmd.arg("-c").arg(source).arg("-o").arg(obj_path);
|
||||
|
||||
if ctx.kind == "sharedlib" {
|
||||
cmd.arg("-fPIC");
|
||||
}
|
||||
cmd.arg("-c");
|
||||
|
||||
if let Some(flag) = asm_lang_flag(source) {
|
||||
cmd.arg("-x").arg(flag);
|
||||
}
|
||||
|
||||
cmd.arg(source).arg("-o").arg(obj_path);
|
||||
|
||||
if ctx.kind == "sharedlib" {
|
||||
cmd.arg("-fPIC");
|
||||
}
|
||||
|
||||
if let Some(platform) = ctx.platform
|
||||
&& !platform.trim().is_empty()
|
||||
{
|
||||
@@ -227,7 +229,16 @@ fn build_object(
|
||||
}
|
||||
|
||||
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
|
||||
cmd.arg(format!("-std={}", ctx.standard));
|
||||
let ext = Path::new(source)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
|
||||
if is_cpp && !ctx.cxx_standard.is_empty() {
|
||||
cmd.arg(format!("-std={}", ctx.cxx_standard));
|
||||
} else if !is_cpp {
|
||||
cmd.arg(format!("-std={}", ctx.standard));
|
||||
}
|
||||
}
|
||||
|
||||
let use_dcr_defaults = ctx.cflags.is_empty() && !is_bare_metal_target(ctx.target);
|
||||
|
||||
@@ -100,6 +100,8 @@ pub struct PackageConfig {
|
||||
pub struct BuildConfig {
|
||||
pub language: LanguageConfig,
|
||||
pub standard: String,
|
||||
#[serde(default)]
|
||||
pub cxx_standard: Option<String>,
|
||||
pub compiler: String,
|
||||
#[serde(default)]
|
||||
pub kind: Option<String>,
|
||||
|
||||
@@ -190,7 +190,7 @@ pub fn parse_language_value(value: &toml::Value, key: &str) -> Result<String, St
|
||||
if parts.is_empty() {
|
||||
return Err(format!("{key} is empty"));
|
||||
}
|
||||
Ok(parts.join("+"))
|
||||
Ok(parts.join(","))
|
||||
}
|
||||
|
||||
pub fn resolve_compiler(
|
||||
@@ -258,7 +258,7 @@ fn toolchain_override_compiler(
|
||||
|
||||
pub fn primary_language(language: &str) -> String {
|
||||
let parts: Vec<String> = language
|
||||
.split('+')
|
||||
.split(',')
|
||||
.map(|p| p.trim().to_lowercase())
|
||||
.filter(|p| !p.is_empty())
|
||||
.collect();
|
||||
|
||||
Reference in New Issue
Block a user