mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 13:28:30 +00:00
Added:
- Toolchain overrides via `[toolchain]` and env (`DCR_*`) - ASM `.S` preprocessing via GCC/Clang (`-x assembler-with-cpp`)
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.7] - 2026-03-03
|
||||
Added:
|
||||
- Toolchain overrides via `[toolchain]` and env (`DCR_*`)
|
||||
- ASM `.S` preprocessing via GCC/Clang (`-x assembler-with-cpp`)
|
||||
|
||||
## [0.2.6] - 2026-03-01
|
||||
Added:
|
||||
- `sharedlib` build kind (shared library output)
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -90,7 +90,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dcr"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"self-replace",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dcr"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@@ -146,6 +146,14 @@ compiler = "clang"
|
||||
cflags = ["-Wall", "-Wextra"]
|
||||
ldflags = ["-lm"]
|
||||
|
||||
[toolchain]
|
||||
# Optional tool overrides:
|
||||
# cc = "clang"
|
||||
# cxx = "clang++"
|
||||
# as = "as"
|
||||
# ar = "ar"
|
||||
# ld = "ld"
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
|
||||
|
||||
4
TODO.md
4
TODO.md
@@ -3,7 +3,5 @@
|
||||
- Add header dependency tracking for incremental builds (GCC/Clang: -MMD, MSVC: /showIncludes)
|
||||
- Add external package managers (Conan, vcpkg)
|
||||
- Multi-target
|
||||
- ASM support
|
||||
- Linker script + direct linker control
|
||||
- Custom targets/steps
|
||||
- Tool overrides and env vars
|
||||
- Custom targets/steps
|
||||
@@ -26,6 +26,17 @@ kind = "bin"
|
||||
- `cflags` (string array, optional): extra compile flags.
|
||||
- `ldflags` (string array, optional): extra link flags.
|
||||
|
||||
## Toolchain section
|
||||
|
||||
```toml
|
||||
[toolchain]
|
||||
cc = "clang"
|
||||
cxx = "clang++"
|
||||
as = "as"
|
||||
ar = "ar"
|
||||
ld = "ld"
|
||||
```
|
||||
|
||||
## Behavior notes
|
||||
|
||||
- Compiler backend is selected by compiler string (`gcc`, `clang`, `cl`, `clang-cl`).
|
||||
|
||||
@@ -39,6 +39,18 @@ If `build.platform` is set, DCR passes it as an architecture hint:
|
||||
- GCC/Clang: `-march=<platform>`
|
||||
- MSVC: maps to `/arch:*` for known values (`x86`, `i386`, `i486`, `i586`, `i686`, `sse2`, `avx`, `avx2`)
|
||||
|
||||
## Toolchain overrides
|
||||
|
||||
If `[toolchain]` is set, DCR uses its values to override compiler/linker tools:
|
||||
|
||||
- `cc`, `cxx`, `as` override compilers for C/C++/ASM
|
||||
- `ar` overrides the archiver (used for `staticlib`)
|
||||
- `ld` overrides the linker (used by GCC/Clang/NASM/GAS backends)
|
||||
|
||||
Environment overrides (highest priority):
|
||||
- `DCR_COMPILER` (all languages)
|
||||
- `DCR_CC`, `DCR_CXX`, `DCR_AS`, `DCR_AR`, `DCR_LD`
|
||||
|
||||
## Linking
|
||||
|
||||
For `kind = "bin"`:
|
||||
|
||||
@@ -17,6 +17,13 @@ platform = "string" # optional
|
||||
cflags = ["string", "..."] # optional
|
||||
ldflags = ["string", "..."] # optional
|
||||
|
||||
[toolchain]
|
||||
cc = "string" # optional
|
||||
cxx = "string" # optional
|
||||
as = "string" # optional
|
||||
ar = "string" # optional
|
||||
ld = "string" # optional
|
||||
|
||||
[dependencies]
|
||||
name = {
|
||||
path = "string", # required for each dependency
|
||||
|
||||
104
src/cli/build.rs
104
src/cli/build.rs
@@ -33,6 +33,11 @@ pub fn build(args: &[String]) -> i32 {
|
||||
let build_target = get_config_str(&config, "build.target");
|
||||
let build_kind = get_config_str(&config, "build.kind");
|
||||
let build_platform = get_config_str(&config, "build.platform");
|
||||
let tc_cc = get_config_opt(&config, "toolchain.cc");
|
||||
let tc_cxx = get_config_opt(&config, "toolchain.cxx");
|
||||
let tc_as = get_config_opt(&config, "toolchain.as");
|
||||
let tc_ar = get_config_opt(&config, "toolchain.ar");
|
||||
let tc_ld = get_config_opt(&config, "toolchain.ld");
|
||||
let build_cflags = match get_config_list(&config, "build.cflags") {
|
||||
Ok(v) => v,
|
||||
Err(msg) => {
|
||||
@@ -48,11 +53,21 @@ pub fn build(args: &[String]) -> i32 {
|
||||
}
|
||||
};
|
||||
|
||||
let resolved_compiler = resolve_compiler(
|
||||
&build_language,
|
||||
&project_compiler,
|
||||
tc_cc.as_deref(),
|
||||
tc_cxx.as_deref(),
|
||||
tc_as.as_deref(),
|
||||
);
|
||||
let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref());
|
||||
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
|
||||
|
||||
println!(
|
||||
" Building project `{}`\n Profile: {}\n Compiler: {}\n",
|
||||
colored(&project_name, BOLD_GREEN),
|
||||
colored(&active_profile, BOLD_GREEN),
|
||||
colored(&project_compiler, BOLD_GREEN)
|
||||
colored(&resolved_compiler, BOLD_GREEN)
|
||||
);
|
||||
|
||||
ensure_target_dirs(&items, &active_profile, normalize_target(&build_target));
|
||||
@@ -74,12 +89,14 @@ pub fn build(args: &[String]) -> i32 {
|
||||
let ctx = BuildContext {
|
||||
profile: &active_profile,
|
||||
project_name: &project_name,
|
||||
compiler: &project_compiler,
|
||||
compiler: &resolved_compiler,
|
||||
language: &build_language,
|
||||
standard: &build_standard,
|
||||
target_dir: normalize_target(&build_target),
|
||||
kind: normalize_kind(&build_kind),
|
||||
platform: normalize_platform(&build_platform),
|
||||
linker: resolved_linker.as_deref(),
|
||||
archiver: resolved_archiver.as_deref(),
|
||||
include_dirs: &resolved.include_dirs,
|
||||
lib_dirs: &resolved.lib_dirs,
|
||||
libs: &resolved.libs,
|
||||
@@ -126,6 +143,16 @@ fn get_config_str(config: &Config, key: &str) -> String {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn get_config_opt(config: &Config, key: &str) -> Option<String> {
|
||||
let value = config.get(key)?.as_str()?;
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_list(config: &Config, key: &str) -> Result<Vec<String>, String> {
|
||||
let value = match config.get(key) {
|
||||
Some(v) => v,
|
||||
@@ -195,3 +222,76 @@ fn normalize_platform(platform: &str) -> Option<&str> {
|
||||
Some(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_compiler(
|
||||
language: &str,
|
||||
compiler: &str,
|
||||
tc_cc: Option<&str>,
|
||||
tc_cxx: Option<&str>,
|
||||
tc_as: Option<&str>,
|
||||
) -> String {
|
||||
let lang = language.to_lowercase();
|
||||
env_override_compiler(&lang)
|
||||
.or_else(|| toolchain_override_compiler(&lang, tc_cc, tc_cxx, tc_as))
|
||||
.unwrap_or_else(|| compiler.to_string())
|
||||
}
|
||||
|
||||
fn env_override_compiler(lang: &str) -> Option<String> {
|
||||
if let Ok(value) = std::env::var("DCR_COMPILER") {
|
||||
let trimmed = value.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
if lang == "asm" {
|
||||
if let Ok(value) = std::env::var("DCR_AS") {
|
||||
let trimmed = value.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
if (lang == "c++" || lang == "cpp" || lang == "cxx")
|
||||
&& let Ok(value) = std::env::var("DCR_CXX")
|
||||
{
|
||||
let trimmed = value.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
if let Ok(value) = std::env::var("DCR_CC") {
|
||||
let trimmed = value.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn toolchain_override_compiler(
|
||||
lang: &str,
|
||||
tc_cc: Option<&str>,
|
||||
tc_cxx: Option<&str>,
|
||||
tc_as: Option<&str>,
|
||||
) -> Option<String> {
|
||||
if lang == "asm" {
|
||||
return tc_as.map(|v| v.to_string());
|
||||
}
|
||||
if (lang == "c++" || lang == "cpp" || lang == "cxx")
|
||||
&& let Some(v) = tc_cxx
|
||||
{
|
||||
return Some(v.to_string());
|
||||
}
|
||||
tc_cc.map(|v| v.to_string())
|
||||
}
|
||||
|
||||
fn resolve_tool(env_key: &str, fallback: Option<&str>) -> Option<String> {
|
||||
if let Ok(value) = std::env::var(env_key) {
|
||||
let trimmed = value.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
fallback.map(|v| v.to_string())
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
let mut cmd = Command::new("ar");
|
||||
let mut cmd = Command::new(ctx.archiver.unwrap_or("ar"));
|
||||
cmd.arg("rcs").arg(&lib_path);
|
||||
for obj in &objects {
|
||||
cmd.arg(obj);
|
||||
@@ -33,7 +33,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(compiler);
|
||||
let mut cmd = Command::new(ctx.linker.unwrap_or(compiler));
|
||||
if ctx.kind == "sharedlib" {
|
||||
if cfg!(target_os = "macos") {
|
||||
cmd.arg("-dynamiclib");
|
||||
@@ -150,6 +150,9 @@ fn build_objects(
|
||||
if ctx.kind == "sharedlib" {
|
||||
cmd.arg("-fPIC");
|
||||
}
|
||||
if let Some(flag) = asm_lang_flag(source) {
|
||||
cmd.arg("-x").arg(flag);
|
||||
}
|
||||
if let Some(platform) = ctx.platform
|
||||
&& !platform.trim().is_empty()
|
||||
{
|
||||
@@ -178,6 +181,15 @@ fn build_objects(
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
fn asm_lang_flag(source: &str) -> Option<&'static str> {
|
||||
let ext = Path::new(source).extension().and_then(|v| v.to_str())?;
|
||||
match ext {
|
||||
"S" => Some("assembler-with-cpp"),
|
||||
"s" | "asm" => Some("assembler"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||
let src_path = Path::new(source);
|
||||
let rel = src_path
|
||||
|
||||
@@ -21,12 +21,13 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
let mut cmd = Command::new(if cfg!(target_os = "windows") {
|
||||
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
|
||||
"lib"
|
||||
} else {
|
||||
"ar"
|
||||
});
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut cmd = Command::new(archiver);
|
||||
if cfg!(target_os = "windows") && archiver == "lib" {
|
||||
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||
} else {
|
||||
cmd.arg("rcs").arg(&lib_path);
|
||||
@@ -44,7 +45,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("cc");
|
||||
let mut cmd = Command::new(ctx.linker.unwrap_or("cc"));
|
||||
if ctx.kind == "sharedlib" {
|
||||
if cfg!(target_os = "macos") {
|
||||
cmd.arg("-dynamiclib");
|
||||
|
||||
@@ -18,7 +18,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
let mut cmd = Command::new("ar");
|
||||
let mut cmd = Command::new(ctx.archiver.unwrap_or("ar"));
|
||||
cmd.arg("rcs").arg(&lib_path);
|
||||
for obj in &objects {
|
||||
cmd.arg(obj);
|
||||
@@ -33,7 +33,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(compiler);
|
||||
let mut cmd = Command::new(ctx.linker.unwrap_or(compiler));
|
||||
if ctx.kind == "sharedlib" {
|
||||
if cfg!(target_os = "macos") {
|
||||
cmd.arg("-dynamiclib");
|
||||
@@ -150,6 +150,9 @@ fn build_objects(
|
||||
if ctx.kind == "sharedlib" {
|
||||
cmd.arg("-fPIC");
|
||||
}
|
||||
if let Some(flag) = asm_lang_flag(source) {
|
||||
cmd.arg("-x").arg(flag);
|
||||
}
|
||||
if let Some(platform) = ctx.platform
|
||||
&& !platform.trim().is_empty()
|
||||
{
|
||||
@@ -178,6 +181,15 @@ fn build_objects(
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
fn asm_lang_flag(source: &str) -> Option<&'static str> {
|
||||
let ext = Path::new(source).extension().and_then(|v| v.to_str())?;
|
||||
match ext {
|
||||
"S" => Some("assembler-with-cpp"),
|
||||
"s" | "asm" => Some("assembler"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||
let src_path = Path::new(source);
|
||||
let rel = src_path
|
||||
|
||||
@@ -13,6 +13,8 @@ pub struct BuildContext<'a> {
|
||||
pub target_dir: Option<&'a str>,
|
||||
pub kind: &'a str,
|
||||
pub platform: Option<&'a str>,
|
||||
pub linker: Option<&'a str>,
|
||||
pub archiver: Option<&'a str>,
|
||||
pub include_dirs: &'a [String],
|
||||
pub lib_dirs: &'a [String],
|
||||
pub libs: &'a [String],
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
let mut cmd = Command::new("lib");
|
||||
let mut cmd = Command::new(ctx.archiver.unwrap_or("lib"));
|
||||
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||
for obj in &objects {
|
||||
cmd.arg(obj);
|
||||
|
||||
@@ -21,12 +21,13 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
let mut cmd = Command::new(if cfg!(target_os = "windows") {
|
||||
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
|
||||
"lib"
|
||||
} else {
|
||||
"ar"
|
||||
});
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut cmd = Command::new(archiver);
|
||||
if cfg!(target_os = "windows") && archiver == "lib" {
|
||||
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||
} else {
|
||||
cmd.arg("rcs").arg(&lib_path);
|
||||
@@ -44,8 +45,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
}
|
||||
|
||||
let linker = "cc";
|
||||
let mut cmd = Command::new(linker);
|
||||
let mut cmd = Command::new(ctx.linker.unwrap_or("cc"));
|
||||
if ctx.kind == "sharedlib" {
|
||||
if cfg!(target_os = "macos") {
|
||||
cmd.arg("-dynamiclib");
|
||||
|
||||
@@ -129,6 +129,15 @@ impl Config {
|
||||
{
|
||||
return Err(ConfigError::Invalid("build.platform is empty".into()));
|
||||
}
|
||||
if let Some(toolchain) = self.get("toolchain").and_then(|v| v.as_table()) {
|
||||
for key in ["cc", "cxx", "as", "ar", "ld"] {
|
||||
if let Some(v) = toolchain.get(key)
|
||||
&& !v.as_str().map(|s| !s.trim().is_empty()).unwrap_or(false)
|
||||
{
|
||||
return Err(ConfigError::Invalid(format!("toolchain.{key} is invalid")));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(kind) = build.get("kind").and_then(|v| v.as_str()) {
|
||||
let kind = kind.trim();
|
||||
if kind != "bin" && kind != "staticlib" && kind != "sharedlib" {
|
||||
@@ -213,6 +222,7 @@ fn format_toml(value: &Value) -> Result<String, ConfigError> {
|
||||
.get("dependencies")
|
||||
.and_then(|v| v.as_table())
|
||||
.ok_or_else(|| ConfigError::Invalid("missing [dependencies]".into()))?;
|
||||
let toolchain = root.get("toolchain").and_then(|v| v.as_table());
|
||||
|
||||
let name = package.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let version = package
|
||||
@@ -251,6 +261,24 @@ fn format_toml(value: &Value) -> Result<String, ConfigError> {
|
||||
}
|
||||
out.push('\n');
|
||||
|
||||
if let Some(toolchain) = toolchain {
|
||||
let mut lines = Vec::new();
|
||||
for key in ["cc", "cxx", "as", "ar", "ld"] {
|
||||
if let Some(value) = toolchain.get(key).and_then(|v| v.as_str())
|
||||
&& !value.trim().is_empty()
|
||||
{
|
||||
lines.push(format!("{key} = \"{value}\""));
|
||||
}
|
||||
}
|
||||
if !lines.is_empty() {
|
||||
out.push_str("[toolchain]\n");
|
||||
for line in lines {
|
||||
out.push_str(&format!("{line}\n"));
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
out.push_str("[dependencies]\n");
|
||||
if !deps.is_empty() {
|
||||
let mut keys: Vec<&String> = deps.keys().collect();
|
||||
|
||||
Reference in New Issue
Block a user