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:
2026-05-27 20:04:01 +08:00
parent b5bbdb929b
commit acdd1cc175
18 changed files with 602 additions and 328 deletions

View File

@@ -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]

View File

@@ -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() {

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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>,