mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 19:39:31 +00:00
Added:
- `build.steps` for pre-build generators (e.g., `moc`/`uic`/`rcc`)
- `build.pkg_config` for `pkg-config`-driven cflags/ldflags resolution
- `[toolchain]` support for `uic`, `moc`, `rcc`
- Step command variables (`{uic}`, `{moc}`, `{rcc}`, `{cflags}`, `{stem}`, `{in}`, `{out}`)
- `{profile}` placeholder support for dependency paths
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.2.10] - 2026-03-16
|
||||||
|
Added:
|
||||||
|
- `build.steps` for pre-build generators (e.g., `moc`/`uic`/`rcc`)
|
||||||
|
- `build.pkg_config` for `pkg-config`-driven cflags/ldflags resolution
|
||||||
|
- `[toolchain]` support for `uic`, `moc`, `rcc`
|
||||||
|
- Step command variables (`{uic}`, `{moc}`, `{rcc}`, `{cflags}`, `{stem}`, `{in}`, `{out}`)
|
||||||
|
- `{profile}` placeholder support for dependency paths
|
||||||
|
|
||||||
## [0.2.9] - 2026-03-14
|
## [0.2.9] - 2026-03-14
|
||||||
Added:
|
Added:
|
||||||
- Header dependency tracking for fine-grained incremental builds (rebuilds when `#include` files change)
|
- Header dependency tracking for fine-grained incremental builds (rebuilds when `#include` files change)
|
||||||
|
|||||||
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -90,8 +90,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"glob",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"self-replace",
|
"self-replace",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -262,6 +263,12 @@ dependencies = [
|
|||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
glob = "0.3"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
self-replace = "1"
|
self-replace = "1"
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ dcr clean --debug
|
|||||||
|
|
||||||
### GCC/Clang-like toolchains
|
### GCC/Clang-like toolchains
|
||||||
|
|
||||||
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
|
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`
|
||||||
- `release`: `-O3 -DNDEBUG -march=native`
|
- `release`: `-O3 -DNDEBUG -march=native`
|
||||||
|
|
||||||
### MSVC toolchain
|
### MSVC toolchain
|
||||||
|
|
||||||
- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-`
|
- `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-`
|
||||||
- `release`: `/O2 /DNDEBUG`
|
- `release`: `/O2 /DNDEBUG`
|
||||||
|
|
||||||
## Artifacts
|
## Artifacts
|
||||||
@@ -37,12 +37,12 @@ Default compiler flags:
|
|||||||
|
|
||||||
GCC/Clang:
|
GCC/Clang:
|
||||||
|
|
||||||
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
|
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`
|
||||||
- `release`: `-O3 -DNDEBUG -march=native`
|
- `release`: `-O3 -DNDEBUG -march=native`
|
||||||
|
|
||||||
MSVC:
|
MSVC:
|
||||||
|
|
||||||
- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-`
|
- `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-`
|
||||||
- `release`: `/O2 /DNDEBUG`
|
- `release`: `/O2 /DNDEBUG`
|
||||||
|
|
||||||
Static library mode (`kind = "staticlib"`):
|
Static library mode (`kind = "staticlib"`):
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ These flags are applied automatically based on profile and compiler backend.
|
|||||||
|
|
||||||
GCC/Clang:
|
GCC/Clang:
|
||||||
|
|
||||||
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
|
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`
|
||||||
- `release`: `-O3 -DNDEBUG -march=native`
|
- `release`: `-O3 -DNDEBUG -march=native`
|
||||||
|
|
||||||
MSVC:
|
MSVC:
|
||||||
|
|
||||||
- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-`
|
- `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-`
|
||||||
- `release`: `/O2 /DNDEBUG`
|
- `release`: `/O2 /DNDEBUG`
|
||||||
|
|
||||||
## Platform hint
|
## Platform hint
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ For each dependency in `[dependencies]`, DCR:
|
|||||||
5. Synchronizes dependency files to `target/<profile>/deps/<dep-name>/`.
|
5. Synchronizes dependency files to `target/<profile>/deps/<dep-name>/`.
|
||||||
6. Adds include/lib/link options to compile/link commands.
|
6. Adds include/lib/link options to compile/link commands.
|
||||||
|
|
||||||
|
### Profile placeholders
|
||||||
|
|
||||||
|
`path`, `include`, and `lib` entries may use `{profile}` which expands to the active profile
|
||||||
|
(`debug` or `release`).
|
||||||
|
|
||||||
## Failure cases
|
## Failure cases
|
||||||
|
|
||||||
- Dependency path is missing or not a directory.
|
- Dependency path is missing or not a directory.
|
||||||
|
|||||||
480
src/cli/build.rs
480
src/cli/build.rs
@@ -6,9 +6,10 @@ use crate::core::workspace::parse_workspace;
|
|||||||
use crate::utils::fs::{check_dir, find_project_root};
|
use crate::utils::fs::{check_dir, find_project_root};
|
||||||
use crate::utils::log::{error, warn};
|
use crate::utils::log::{error, warn};
|
||||||
use crate::utils::text::{BOLD_GREEN, colored};
|
use crate::utils::text::{BOLD_GREEN, colored};
|
||||||
|
use glob::glob;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
pub fn build(args: &[String]) -> i32 {
|
pub fn build(args: &[String]) -> i32 {
|
||||||
@@ -190,8 +191,14 @@ fn build_project_at(
|
|||||||
let tc_as = get_config_opt(&config, "toolchain.as");
|
let tc_as = get_config_opt(&config, "toolchain.as");
|
||||||
let tc_ar = get_config_opt(&config, "toolchain.ar");
|
let tc_ar = get_config_opt(&config, "toolchain.ar");
|
||||||
let tc_ld = get_config_opt(&config, "toolchain.ld");
|
let tc_ld = get_config_opt(&config, "toolchain.ld");
|
||||||
|
let tc_uic = get_config_opt(&config, "toolchain.uic");
|
||||||
|
let tc_moc = get_config_opt(&config, "toolchain.moc");
|
||||||
|
let tc_rcc = get_config_opt(&config, "toolchain.rcc");
|
||||||
let build_cflags = get_config_list(&config, "build.cflags")?;
|
let build_cflags = get_config_list(&config, "build.cflags")?;
|
||||||
let build_ldflags = get_config_list(&config, "build.ldflags")?;
|
let build_ldflags = get_config_list(&config, "build.ldflags")?;
|
||||||
|
let pkg_configs = get_config_list(&config, "build.pkg_config")?;
|
||||||
|
let build_generated = get_config_list(&config, "build.generated")?;
|
||||||
|
let build_steps = get_build_steps(&config)?;
|
||||||
|
|
||||||
let resolved_compiler = resolve_compiler(
|
let resolved_compiler = resolve_compiler(
|
||||||
&build_language,
|
&build_language,
|
||||||
@@ -206,6 +213,8 @@ fn build_project_at(
|
|||||||
ensure_target_dirs(&items, profile, normalize_target(&build_target));
|
ensure_target_dirs(&items, profile, normalize_target(&build_target));
|
||||||
|
|
||||||
let resolved = resolve_deps(&config, profile, project_root)?;
|
let resolved = resolve_deps(&config, profile, project_root)?;
|
||||||
|
let (resolved_cflags, resolved_ldflags) =
|
||||||
|
resolve_pkg_config_flags(&pkg_configs, &build_cflags, &build_ldflags)?;
|
||||||
let ctx = BuildContext {
|
let ctx = BuildContext {
|
||||||
profile,
|
profile,
|
||||||
project_name: &project_name,
|
project_name: &project_name,
|
||||||
@@ -221,14 +230,34 @@ fn build_project_at(
|
|||||||
include_dirs: &resolved.include_dirs,
|
include_dirs: &resolved.include_dirs,
|
||||||
lib_dirs: &resolved.lib_dirs,
|
lib_dirs: &resolved.lib_dirs,
|
||||||
libs: &resolved.libs,
|
libs: &resolved.libs,
|
||||||
cflags: &build_cflags,
|
cflags: &resolved_cflags,
|
||||||
ldflags: &build_ldflags,
|
ldflags: &resolved_ldflags,
|
||||||
};
|
};
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] debug: compiler={}", ctx.compiler);
|
||||||
|
eprintln!("[dcr] debug: cflags={:?}", ctx.cflags);
|
||||||
|
eprintln!("[dcr] debug: ldflags={:?}", ctx.ldflags);
|
||||||
|
eprintln!("[dcr] debug: lib_dirs={:?}", ctx.lib_dirs);
|
||||||
|
eprintln!("[dcr] debug: libs={:?}", ctx.libs);
|
||||||
|
}
|
||||||
|
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 steps_dirty = build_steps_need_run(&build_steps)?;
|
||||||
|
if steps_dirty {
|
||||||
|
clean_generated_files(&build_generated)?;
|
||||||
|
run_build_steps(&build_steps, &tool_execs, &step_flags)?;
|
||||||
|
}
|
||||||
let sources = collect_sources(&ctx)?;
|
let sources = collect_sources(&ctx)?;
|
||||||
let headers = collect_header_files(&ctx, project_root)?;
|
let headers = collect_header_files(&ctx, project_root)?;
|
||||||
let lib_files = collect_lib_files(&ctx);
|
let lib_files = collect_lib_files(&ctx);
|
||||||
let fingerprint = compute_build_fingerprint(&ctx, &sources, &headers, &lib_files)?;
|
let fingerprint = compute_build_fingerprint(&ctx, &sources, &headers, &lib_files)?;
|
||||||
if should_skip_build(&ctx, &fingerprint) {
|
let mut skip = should_skip_build(&ctx, &fingerprint);
|
||||||
|
if steps_dirty {
|
||||||
|
skip = false;
|
||||||
|
}
|
||||||
|
let debug_enabled = std::env::var("DCR_DEBUG").is_ok();
|
||||||
|
if skip && !debug_enabled {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
println!(
|
println!(
|
||||||
@@ -237,8 +266,10 @@ fn build_project_at(
|
|||||||
project_name,
|
project_name,
|
||||||
project_version
|
project_version
|
||||||
);
|
);
|
||||||
run_build(&ctx)?;
|
if !skip {
|
||||||
write_build_fingerprint(&ctx, &fingerprint)?;
|
run_build(&ctx)?;
|
||||||
|
write_build_fingerprint(&ctx, &fingerprint)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -350,6 +381,443 @@ fn resolve_tool(env_key: &str, fallback: Option<&str>) -> Option<String> {
|
|||||||
fallback.map(|v| v.to_string())
|
fallback.map(|v| v.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_pkg_config_flags(
|
||||||
|
pkgs: &[String],
|
||||||
|
base_cflags: &[String],
|
||||||
|
base_ldflags: &[String],
|
||||||
|
) -> Result<(Vec<String>, Vec<String>), String> {
|
||||||
|
let mut cflags = base_cflags.to_vec();
|
||||||
|
let mut ldflags = base_ldflags.to_vec();
|
||||||
|
if pkgs.is_empty() {
|
||||||
|
return Ok((cflags, ldflags));
|
||||||
|
}
|
||||||
|
for pkg in pkgs {
|
||||||
|
let c_out = run_pkg_config(pkg, "--cflags")?;
|
||||||
|
let l_out = run_pkg_config(pkg, "--libs")?;
|
||||||
|
cflags.extend(split_flags(&c_out));
|
||||||
|
ldflags.extend(split_flags(&l_out));
|
||||||
|
}
|
||||||
|
Ok((cflags, ldflags))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pkg_config(pkg: &str, arg: &str) -> Result<String, String> {
|
||||||
|
let output = std::process::Command::new("pkg-config")
|
||||||
|
.arg(arg)
|
||||||
|
.arg(pkg)
|
||||||
|
.output()
|
||||||
|
.map_err(|err| format!("Failed to run pkg-config: {err}"))?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("pkg-config failed for {pkg}: {stderr}"));
|
||||||
|
}
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_flags(value: &str) -> Vec<String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
let mut chars = value.chars().peekable();
|
||||||
|
let mut quote: Option<char> = None;
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
if let Some(q) = quote {
|
||||||
|
if ch == q {
|
||||||
|
quote = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == '\\' {
|
||||||
|
if let Some(next) = chars.next() {
|
||||||
|
current.push(next);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current.push(ch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == '"' || ch == '\'' {
|
||||||
|
quote = Some(ch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch.is_whitespace() {
|
||||||
|
if !current.is_empty() {
|
||||||
|
out.push(current.clone());
|
||||||
|
current.clear();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == '\\' {
|
||||||
|
if let Some(next) = chars.next() {
|
||||||
|
current.push(next);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current.push(ch);
|
||||||
|
}
|
||||||
|
if !current.is_empty() {
|
||||||
|
out.push(current);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ToolchainExecs {
|
||||||
|
uic: String,
|
||||||
|
moc: String,
|
||||||
|
rcc: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_toolchain_execs(
|
||||||
|
uic: &Option<String>,
|
||||||
|
moc: &Option<String>,
|
||||||
|
rcc: &Option<String>,
|
||||||
|
pkg_configs: &[String],
|
||||||
|
) -> ToolchainExecs {
|
||||||
|
let qt_bins = resolve_qt_host_bins(pkg_configs);
|
||||||
|
ToolchainExecs {
|
||||||
|
uic: resolve_qt_tool(uic, qt_bins.as_deref(), "uic"),
|
||||||
|
moc: resolve_qt_tool(moc, qt_bins.as_deref(), "moc"),
|
||||||
|
rcc: resolve_qt_tool(rcc, qt_bins.as_deref(), "rcc"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_qt_tool(configured: &Option<String>, qt_bins: Option<&Path>, tool: &str) -> String {
|
||||||
|
if let Some(value) = configured {
|
||||||
|
return value.clone();
|
||||||
|
}
|
||||||
|
if let Some(dir) = qt_bins {
|
||||||
|
let candidate = dir.join(tool);
|
||||||
|
if candidate.is_file() {
|
||||||
|
return candidate.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
let candidate = dir.join(format!("{tool}.exe"));
|
||||||
|
if candidate.is_file() {
|
||||||
|
return candidate.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(candidate) = detect_qt6_tool_variant(tool) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
tool.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_qt_host_bins(pkgs: &[String]) -> Option<PathBuf> {
|
||||||
|
let qt_pkgs: Vec<&String> = pkgs.iter().filter(|p| p.starts_with("Qt6")).collect();
|
||||||
|
if qt_pkgs.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let preferred = ["Qt6Core", "Qt6Widgets", "Qt6Gui"];
|
||||||
|
for name in preferred {
|
||||||
|
if let Some(dir) = query_pkg_config_var(name, "host_bins") {
|
||||||
|
return Some(dir);
|
||||||
|
}
|
||||||
|
if let Some(dir) = query_pkg_config_var(name, "libexecdir")
|
||||||
|
&& let Some(bin) = qt_bins_from_libexec(&dir)
|
||||||
|
{
|
||||||
|
return Some(bin);
|
||||||
|
}
|
||||||
|
if let Some(dir) = query_pkg_config_var(name, "bindir") {
|
||||||
|
return Some(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pkg in qt_pkgs {
|
||||||
|
if let Some(dir) = query_pkg_config_var(pkg, "host_bins") {
|
||||||
|
return Some(dir);
|
||||||
|
}
|
||||||
|
if let Some(dir) = query_pkg_config_var(pkg, "libexecdir")
|
||||||
|
&& let Some(bin) = qt_bins_from_libexec(&dir)
|
||||||
|
{
|
||||||
|
return Some(bin);
|
||||||
|
}
|
||||||
|
if let Some(dir) = query_pkg_config_var(pkg, "bindir") {
|
||||||
|
return Some(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_pkg_config_var(pkg: &str, var: &str) -> Option<PathBuf> {
|
||||||
|
let output = std::process::Command::new("pkg-config")
|
||||||
|
.arg(format!("--variable={var}"))
|
||||||
|
.arg(pkg)
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
|
if value.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let path = PathBuf::from(value);
|
||||||
|
if path.is_dir() { Some(path) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qt_bins_from_libexec(libexec: &Path) -> Option<PathBuf> {
|
||||||
|
for tool in ["moc", "uic", "rcc"] {
|
||||||
|
if libexec.join(tool).is_file() {
|
||||||
|
return Some(libexec.to_path_buf());
|
||||||
|
}
|
||||||
|
if cfg!(target_os = "windows") && libexec.join(format!("{tool}.exe")).is_file() {
|
||||||
|
return Some(libexec.to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let bin = libexec.join("bin");
|
||||||
|
if bin.is_dir() { Some(bin) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_qt6_tool_variant(tool: &str) -> Option<String> {
|
||||||
|
[format!("{tool}6"), format!("{tool}-qt6")]
|
||||||
|
.into_iter()
|
||||||
|
.find(|candidate| is_on_path(candidate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_on_path(cmd: &str) -> bool {
|
||||||
|
std::process::Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(format!("command -v {cmd}"))
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct BuildStep {
|
||||||
|
name: String,
|
||||||
|
input: String,
|
||||||
|
output: String,
|
||||||
|
cmd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_build_steps(config: &Config) -> Result<Vec<BuildStep>, String> {
|
||||||
|
let value = match config.get("build.steps") {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
let arr = value
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| "build.steps must be an array".to_string())?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for item in arr {
|
||||||
|
let tbl = item
|
||||||
|
.as_table()
|
||||||
|
.ok_or_else(|| "build.steps entries must be tables".to_string())?;
|
||||||
|
let name = tbl
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
let input = tbl
|
||||||
|
.get("in")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
let output = tbl
|
||||||
|
.get("out")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
let cmd = tbl
|
||||||
|
.get("cmd")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
if name.is_empty() || input.is_empty() || output.is_empty() || cmd.is_empty() {
|
||||||
|
return Err("build.steps entries must include name, in, out, cmd".to_string());
|
||||||
|
}
|
||||||
|
out.push(BuildStep {
|
||||||
|
name,
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
cmd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_build_steps(
|
||||||
|
steps: &[BuildStep],
|
||||||
|
tools: &ToolchainExecs,
|
||||||
|
step_flags: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
for step in steps {
|
||||||
|
run_build_step(step, tools, step_flags)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_build_step(
|
||||||
|
step: &BuildStep,
|
||||||
|
tools: &ToolchainExecs,
|
||||||
|
step_flags: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let inputs = expand_glob(&step.input)?;
|
||||||
|
if inputs.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let needs_stem = step.output.contains("{stem}");
|
||||||
|
if inputs.len() > 1 && !needs_stem {
|
||||||
|
return Err(format!(
|
||||||
|
"build.steps '{}' output must include {{stem}} for multiple inputs",
|
||||||
|
step.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for input in inputs {
|
||||||
|
let stem = input.file_stem().and_then(|v| v.to_str()).unwrap_or("");
|
||||||
|
let out_path = PathBuf::from(step.output.replace("{stem}", stem));
|
||||||
|
if !should_run_step(&input, &out_path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(parent) = out_path.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.map_err(|err| format!("Failed to create step output dir: {err}"))?;
|
||||||
|
}
|
||||||
|
let cmd = substitute_step_cmd(&step.cmd, &input, &out_path, tools, step_flags, stem);
|
||||||
|
let status = run_shell_command(&cmd)
|
||||||
|
.map_err(|err| format!("Failed to run step '{}': {err}", step.name))?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(format!("Step '{}' failed", step.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean_generated_files(patterns: &[String]) -> Result<(), String> {
|
||||||
|
for pattern in patterns {
|
||||||
|
for path in expand_glob(pattern)? {
|
||||||
|
if path.is_file() {
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_glob(pattern: &str) -> Result<Vec<PathBuf>, String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let entries = glob(pattern).map_err(|err| format!("glob error: {err}"))?;
|
||||||
|
for entry in entries {
|
||||||
|
let path = entry.map_err(|err| format!("glob error: {err}"))?;
|
||||||
|
out.push(path);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_run_step(input: &Path, output: &Path) -> bool {
|
||||||
|
let in_time = fs::metadata(input).and_then(|m| m.modified());
|
||||||
|
let out_time = fs::metadata(output).and_then(|m| m.modified());
|
||||||
|
match (in_time, out_time) {
|
||||||
|
(Ok(i), Ok(o)) => i > o,
|
||||||
|
(Ok(_), Err(_)) => true,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substitute_step_cmd(
|
||||||
|
template: &str,
|
||||||
|
input: &Path,
|
||||||
|
output: &Path,
|
||||||
|
tools: &ToolchainExecs,
|
||||||
|
step_flags: &str,
|
||||||
|
stem: &str,
|
||||||
|
) -> String {
|
||||||
|
template
|
||||||
|
.replace("{in}", &input.to_string_lossy())
|
||||||
|
.replace("{out}", &output.to_string_lossy())
|
||||||
|
.replace("{uic}", &tools.uic)
|
||||||
|
.replace("{moc}", &tools.moc)
|
||||||
|
.replace("{rcc}", &tools.rcc)
|
||||||
|
.replace("{cflags}", step_flags)
|
||||||
|
.replace("{stem}", stem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_step_flags(cflags: &[String], include_dirs: &[String], compiler: &str) -> String {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let msvc_style = is_msvc_compiler(compiler) || cflags.iter().any(|f| f.starts_with('/'));
|
||||||
|
for flag in cflags {
|
||||||
|
if flag.starts_with("-I") || flag.starts_with("-D") {
|
||||||
|
out.push(flag.clone());
|
||||||
|
}
|
||||||
|
if flag.starts_with("/I") || flag.starts_with("/D") {
|
||||||
|
out.push(flag.clone());
|
||||||
|
}
|
||||||
|
if msvc_style && flag.starts_with("-D") {
|
||||||
|
out.push(format!("/D{}", flag.trim_start_matches("-D")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dir in include_dirs {
|
||||||
|
out.push(format!("-I{dir}"));
|
||||||
|
if msvc_style {
|
||||||
|
out.push(format!("/I{dir}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut dedup = Vec::new();
|
||||||
|
for item in out {
|
||||||
|
if !dedup.contains(&item) {
|
||||||
|
dedup.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dedup
|
||||||
|
.into_iter()
|
||||||
|
.map(quote_step_arg)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quote_step_arg(arg: String) -> String {
|
||||||
|
if !arg.chars().any(|c| c.is_whitespace() || c == '"') {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
let escaped = arg.replace('"', "\\\"");
|
||||||
|
format!("\"{escaped}\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_msvc_compiler(compiler: &str) -> bool {
|
||||||
|
let lower = compiler.to_lowercase();
|
||||||
|
lower.contains("cl.exe")
|
||||||
|
|| lower == "cl"
|
||||||
|
|| lower.contains("clang-cl")
|
||||||
|
|| lower.contains("msvc")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_shell_command(cmd: &str) -> Result<std::process::ExitStatus, std::io::Error> {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
std::process::Command::new("cmd")
|
||||||
|
.arg("/C")
|
||||||
|
.arg(cmd)
|
||||||
|
.status()
|
||||||
|
} else {
|
||||||
|
std::process::Command::new("sh").arg("-c").arg(cmd).status()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_steps_need_run(steps: &[BuildStep]) -> Result<bool, String> {
|
||||||
|
for step in steps {
|
||||||
|
let inputs = expand_glob(&step.input)?;
|
||||||
|
if inputs.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let needs_stem = step.output.contains("{stem}");
|
||||||
|
if inputs.len() > 1 && !needs_stem {
|
||||||
|
return Err(format!(
|
||||||
|
"build.steps '{}' output must include {{stem}} for multiple inputs",
|
||||||
|
step.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for input in inputs {
|
||||||
|
let stem = input.file_stem().and_then(|v| v.to_str()).unwrap_or("");
|
||||||
|
let out_path = PathBuf::from(step.output.replace("{stem}", stem));
|
||||||
|
if should_run_step(&input, &out_path) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_build_fingerprint(
|
fn compute_build_fingerprint(
|
||||||
ctx: &BuildContext,
|
ctx: &BuildContext,
|
||||||
sources: &[String],
|
sources: &[String],
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub fn flags(profile: &str) -> Option<&'static [&'static str]> {
|
|||||||
"-Wall",
|
"-Wall",
|
||||||
"-Wextra",
|
"-Wextra",
|
||||||
"-fno-omit-frame-pointer",
|
"-fno-omit-frame-pointer",
|
||||||
"-DDEBUG",
|
"-DDCR_DEBUG",
|
||||||
]),
|
]),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,9 +175,11 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
static CWD_LOCK: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
fn temp_dir(prefix: &str) -> PathBuf {
|
fn temp_dir(prefix: &str) -> PathBuf {
|
||||||
let n = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
|
let n = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
@@ -282,6 +284,7 @@ mod tests {
|
|||||||
fs::write(src.join("utils.c"), "").unwrap();
|
fs::write(src.join("utils.c"), "").unwrap();
|
||||||
fs::write(src.join("README.md"), "").unwrap(); // should be ignored
|
fs::write(src.join("README.md"), "").unwrap(); // should be ignored
|
||||||
|
|
||||||
|
let _guard = CWD_LOCK.lock().unwrap();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let result = collect_sources(&["c"], &[]);
|
let result = collect_sources(&["c"], &[]);
|
||||||
@@ -303,6 +306,7 @@ mod tests {
|
|||||||
fs::write(src.join("other.cc"), "").unwrap();
|
fs::write(src.join("other.cc"), "").unwrap();
|
||||||
fs::write(src.join("skip.c"), "").unwrap(); // should be ignored
|
fs::write(src.join("skip.c"), "").unwrap(); // should be ignored
|
||||||
|
|
||||||
|
let _guard = CWD_LOCK.lock().unwrap();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let result = collect_sources(&["cpp", "cxx", "cc"], &[]);
|
let result = collect_sources(&["cpp", "cxx", "cc"], &[]);
|
||||||
@@ -318,6 +322,7 @@ mod tests {
|
|||||||
let src = dir.join("src");
|
let src = dir.join("src");
|
||||||
fs::create_dir_all(&src).unwrap();
|
fs::create_dir_all(&src).unwrap();
|
||||||
|
|
||||||
|
let _guard = CWD_LOCK.lock().unwrap();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let result = collect_sources(&["c"], &[]);
|
let result = collect_sources(&["c"], &[]);
|
||||||
@@ -336,6 +341,7 @@ mod tests {
|
|||||||
fs::write(vendor.join("lib.c"), "").unwrap(); // should be excluded
|
fs::write(vendor.join("lib.c"), "").unwrap(); // should be excluded
|
||||||
|
|
||||||
let exclude = vec![vendor.clone()];
|
let exclude = vec![vendor.clone()];
|
||||||
|
let _guard = CWD_LOCK.lock().unwrap();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let result = collect_sources(&["c"], &exclude);
|
let result = collect_sources(&["c"], &exclude);
|
||||||
@@ -355,6 +361,7 @@ mod tests {
|
|||||||
fs::write(src.join("main.c"), "").unwrap();
|
fs::write(src.join("main.c"), "").unwrap();
|
||||||
fs::write(sub.join("nested.c"), "").unwrap();
|
fs::write(sub.join("nested.c"), "").unwrap();
|
||||||
|
|
||||||
|
let _guard = CWD_LOCK.lock().unwrap();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let result = collect_sources(&["c"], &[]);
|
let result = collect_sources(&["c"], &[]);
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
for obj in &objects {
|
for obj in &objects {
|
||||||
cmd.arg(obj);
|
cmd.arg(obj);
|
||||||
}
|
}
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -73,6 +76,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
};
|
};
|
||||||
cmd.arg("-o").arg(out_path);
|
cmd.arg("-o").arg(out_path);
|
||||||
|
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
for obj in &objects {
|
for obj in &objects {
|
||||||
cmd.arg(obj);
|
cmd.arg(obj);
|
||||||
}
|
}
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -91,6 +94,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
};
|
};
|
||||||
cmd.arg(format!("/Fe:{out_path}"));
|
cmd.arg(format!("/Fe:{out_path}"));
|
||||||
|
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -158,7 +164,7 @@ fn msvc_arch_flag(platform: Option<&str>) -> Option<&'static str> {
|
|||||||
fn default_flags(profile: &str) -> &'static [&'static str] {
|
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||||
match profile {
|
match profile {
|
||||||
"release" => &["/O2", "/DNDEBUG"],
|
"release" => &["/O2", "/DNDEBUG"],
|
||||||
"debug" => &["/Od", "/Zi", "/W4", "/DDEBUG", "/Oy-"],
|
"debug" => &["/Od", "/Zi", "/W4", "/DDCR_DEBUG", "/Oy-"],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,6 +215,9 @@ fn build_objects(
|
|||||||
cmd.arg("/c").arg(source).arg(format!("/Fo:{}", obj_path));
|
cmd.arg("/c").arg(source).arg(format!("/Fo:{}", obj_path));
|
||||||
cmd.arg("/showIncludes");
|
cmd.arg("/showIncludes");
|
||||||
|
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
for obj in &objects {
|
for obj in &objects {
|
||||||
cmd.arg(obj);
|
cmd.arg(obj);
|
||||||
}
|
}
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -73,6 +76,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
};
|
};
|
||||||
cmd.arg("-o").arg(out_path);
|
cmd.arg("-o").arg(out_path);
|
||||||
|
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -112,6 +118,9 @@ fn build_objects(
|
|||||||
for flag in ctx.cflags {
|
for flag in ctx.cflags {
|
||||||
cmd.arg(flag);
|
cmd.arg(flag);
|
||||||
}
|
}
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {}
|
Ok(status) if status.success() => {}
|
||||||
Ok(_) => return Err("Build failed".to_string()),
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
for obj in &objects {
|
for obj in &objects {
|
||||||
cmd.arg(obj);
|
cmd.arg(obj);
|
||||||
}
|
}
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -62,6 +65,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
};
|
};
|
||||||
cmd.arg("-o").arg(out_path);
|
cmd.arg("-o").arg(out_path);
|
||||||
|
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {
|
Ok(status) if status.success() => {
|
||||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
@@ -96,7 +102,7 @@ fn default_flags(profile: &str) -> &'static [&'static str] {
|
|||||||
"-Wall",
|
"-Wall",
|
||||||
"-Wextra",
|
"-Wextra",
|
||||||
"-fno-omit-frame-pointer",
|
"-fno-omit-frame-pointer",
|
||||||
"-DDEBUG",
|
"-DDCR_DEBUG",
|
||||||
],
|
],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
@@ -143,6 +149,9 @@ fn build_objects(
|
|||||||
}
|
}
|
||||||
let d_path = Path::new(&obj_path).with_extension("d");
|
let d_path = Path::new(&obj_path).with_extension("d");
|
||||||
cmd.arg("-MMD").arg("-MF").arg(&d_path);
|
cmd.arg("-MMD").arg("-MF").arg(&d_path);
|
||||||
|
if std::env::var("DCR_DEBUG").is_ok() {
|
||||||
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
|
}
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(status) if status.success() => {}
|
Ok(status) if status.success() => {}
|
||||||
Ok(_) => return Err("Build failed".to_string()),
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ pub fn resolve_deps(
|
|||||||
let mut lock_packages = Vec::new();
|
let mut lock_packages = Vec::new();
|
||||||
|
|
||||||
for dep in deps {
|
for dep in deps {
|
||||||
let dep_path = resolve_path(project_root, &dep.path_raw)?;
|
let dep_path_raw = expand_profile(&dep.path_raw, profile);
|
||||||
|
let dep_path = resolve_path(project_root, &dep_path_raw)?;
|
||||||
if !dep_path.is_dir() {
|
if !dep_path.is_dir() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Dependency '{}' path is not a directory: {}",
|
"Dependency '{}' path is not a directory: {}",
|
||||||
@@ -59,8 +60,13 @@ pub fn resolve_deps(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let include = resolve_paths(&dep_path, dep.include_raw.as_deref(), &["include"])?;
|
let include = resolve_paths(&dep_path, dep.include_raw.as_deref(), &["include"], profile)?;
|
||||||
let lib = resolve_paths(&dep_path, dep.lib_raw.as_deref(), &["lib", "lib64"])?;
|
let lib = resolve_paths(
|
||||||
|
&dep_path,
|
||||||
|
dep.lib_raw.as_deref(),
|
||||||
|
&["lib", "lib64"],
|
||||||
|
profile,
|
||||||
|
)?;
|
||||||
let libs_list = dep
|
let libs_list = dep
|
||||||
.libs_raw
|
.libs_raw
|
||||||
.clone()
|
.clone()
|
||||||
@@ -200,11 +206,13 @@ fn resolve_paths(
|
|||||||
base: &Path,
|
base: &Path,
|
||||||
raw: Option<&[String]>,
|
raw: Option<&[String]>,
|
||||||
defaults: &[&str],
|
defaults: &[&str],
|
||||||
|
profile: &str,
|
||||||
) -> Result<Vec<PathBuf>, String> {
|
) -> Result<Vec<PathBuf>, String> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
if let Some(raw) = raw {
|
if let Some(raw) = raw {
|
||||||
for r in raw {
|
for r in raw {
|
||||||
let p = Path::new(r);
|
let expanded = expand_profile(r, profile);
|
||||||
|
let p = Path::new(&expanded);
|
||||||
let full = if p.is_absolute() {
|
let full = if p.is_absolute() {
|
||||||
p.to_path_buf()
|
p.to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
@@ -227,6 +235,10 @@ fn resolve_paths(
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expand_profile(raw: &str, profile: &str) -> String {
|
||||||
|
raw.replace("{profile}", profile)
|
||||||
|
}
|
||||||
|
|
||||||
fn sync_dep_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
|
fn sync_dep_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||||
if dst.exists() {
|
if dst.exists() {
|
||||||
fs::remove_dir_all(dst)?;
|
fs::remove_dir_all(dst)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user