mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-22 03:29:36 +00:00
docs: add and clean up AI-generated rustdoc comments (grok-4.5)
This commit is contained in:
@@ -22,6 +22,7 @@ use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
||||
/// File wrapper that shifts all seek offsets (used to skip the boot sector on a FAT image).
|
||||
struct OffsetFile<T> {
|
||||
inner: T,
|
||||
offset: u64,
|
||||
@@ -73,6 +74,7 @@ impl<T: Seek> Seek for OffsetFile<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a size string like `1.5m` or `500k` into bytes (1024-based; empty → 0).
|
||||
fn parse_size(s: &str) -> Result<u64, String> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
@@ -101,6 +103,7 @@ fn parse_size(s: &str) -> Result<u64, String> {
|
||||
Ok((val * multiplier) as u64)
|
||||
}
|
||||
|
||||
/// Creates a FAT image from layout config, substituting `{profile}` and optional boot sector.
|
||||
pub fn pack_archive(
|
||||
project_root: &Path,
|
||||
config: &ArchiveConfig,
|
||||
@@ -142,6 +145,7 @@ pub fn pack_archive(
|
||||
if offset == 0
|
||||
&& let Some(boot_rel) = &config.bootsector
|
||||
{
|
||||
// Load and write bootsector if present
|
||||
let boot_rel_substituted = boot_rel.replace("{profile}", profile);
|
||||
let boot_path = project_root.join(&boot_rel_substituted);
|
||||
if boot_path.is_file() {
|
||||
|
||||
@@ -23,6 +23,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Returns the artifact directory path based on the build context.
|
||||
pub(crate) fn artifact_dir(ctx: &BuildContext) -> PathBuf {
|
||||
match ctx.target_dir {
|
||||
Some(dir) => Path::new(dir).to_path_buf(),
|
||||
@@ -30,6 +31,17 @@ pub(crate) fn artifact_dir(ctx: &BuildContext) -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the artifact path for a given kind/profile/name.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `kind`: Artifact kind (`bin`, `staticlib`, `sharedlib`, `flat-bin`, `efi`, `elf`, …).
|
||||
/// - `profile`: Build profile (`debug` / `release` / custom).
|
||||
/// - `project_name`: Default basename when `output_filename` is unset.
|
||||
/// - `target_dir`: Optional artifact directory override.
|
||||
/// - `output_filename` / `output_extension`: Optional name/ext overrides.
|
||||
///
|
||||
/// # Returns
|
||||
/// Relative/absolute path string, or `None` for `none` / `custom` kinds.
|
||||
pub fn resolve_artifact_path(
|
||||
kind: &str,
|
||||
profile: &str,
|
||||
@@ -75,6 +87,7 @@ pub fn resolve_artifact_path(
|
||||
Some(path)
|
||||
}
|
||||
|
||||
/// Converts a relative or absolute path to an absolute path by joining with project root if needed.
|
||||
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() {
|
||||
@@ -88,6 +101,7 @@ pub fn absolute_artifact_path(project_root: &Path, relative_or_abs: &str) -> Pat
|
||||
crate::utils::fs::canonicalize_path(&joined)
|
||||
}
|
||||
|
||||
/// Returns the output path for a flat binary artifact.
|
||||
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");
|
||||
@@ -99,6 +113,7 @@ pub(crate) fn flat_output_path(ctx: &BuildContext) -> String {
|
||||
artifact_dir(ctx).join(file).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
/// Returns the output path for a flat binary artifact from a source file.
|
||||
pub(crate) fn flat_source_output_path(ctx: &BuildContext, source: &str) -> String {
|
||||
let stem = Path::new(source)
|
||||
.file_stem()
|
||||
@@ -113,6 +128,7 @@ pub(crate) fn flat_source_output_path(ctx: &BuildContext, source: &str) -> Strin
|
||||
artifact_dir(ctx).join(file).to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
// Helper to find a suitable objcopy tool in PATH
|
||||
fn resolve_objcopy() -> Result<String, String> {
|
||||
for candidate in ["llvm-objcopy", "objcopy", "gobjcopy"] {
|
||||
if Command::new(candidate)
|
||||
@@ -131,6 +147,7 @@ fn resolve_objcopy() -> Result<String, String> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts an object/ELF to a raw binary via `objcopy -O binary` (skips if up to date).
|
||||
pub(crate) fn objcopy_binary(ctx: &BuildContext, input: &str, output: &str) -> Result<(), String> {
|
||||
if let Some(parent) = Path::new(output).parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| format!("output dir error: {e}"))?;
|
||||
@@ -147,6 +164,7 @@ pub(crate) fn objcopy_binary(ctx: &BuildContext, input: &str, output: &str) -> R
|
||||
common::run_command_sync_output(&mut cmd)
|
||||
}
|
||||
|
||||
/// Links objects into an intermediate ELF, then converts it to a flat binary via objcopy.
|
||||
pub(crate) fn link_flat_binary(
|
||||
ctx: &BuildContext,
|
||||
objects: &[String],
|
||||
@@ -208,6 +226,7 @@ pub(crate) fn link_flat_binary(
|
||||
Ok(common::elapsed_secs(start_time))
|
||||
}
|
||||
|
||||
/// Archives objects into a static library.
|
||||
pub(crate) fn archive_static(
|
||||
ctx: &BuildContext,
|
||||
objects: &[String],
|
||||
@@ -234,6 +253,7 @@ pub(crate) fn archive_static(
|
||||
run(ctx, cmd, start_time)
|
||||
}
|
||||
|
||||
/// Links objects into a binary or library based on artifact kind.
|
||||
pub(crate) fn link_binary(
|
||||
ctx: &BuildContext,
|
||||
objects: &[String],
|
||||
@@ -299,6 +319,7 @@ pub(crate) fn link_binary(
|
||||
run(ctx, cmd, start_time)
|
||||
}
|
||||
|
||||
/// Returns the default archiver name based on compiler and platform.
|
||||
fn default_archiver(compiler: &str) -> &'static str {
|
||||
let c = compiler.to_lowercase();
|
||||
if cfg!(target_os = "windows") {
|
||||
@@ -314,11 +335,13 @@ fn default_archiver(compiler: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the archiver is an MSVC lib tool.
|
||||
fn is_msvc_lib(archiver: &str) -> bool {
|
||||
let a = archiver.to_lowercase();
|
||||
a == "lib" || a == "lib.exe" || a.ends_with("\\lib.exe") || a.ends_with("/lib.exe")
|
||||
}
|
||||
|
||||
/// Runs a command and handles success/failure with timing.
|
||||
fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64, String> {
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
eprintln!("[dcr] {:?}", cmd);
|
||||
@@ -345,12 +368,14 @@ fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64,
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Default `bin` kind resolves via platform bin path.
|
||||
#[test]
|
||||
fn resolve_bin_default() {
|
||||
let p = resolve_artifact_path("bin", "debug", "hello", None, None, None).unwrap();
|
||||
assert_eq!(p, platform::bin_path("debug", "hello", None));
|
||||
}
|
||||
|
||||
/// Custom `target_dir` places the binary under that directory.
|
||||
#[test]
|
||||
fn resolve_bin_custom_target_dir() {
|
||||
let p = resolve_artifact_path("bin", "debug", "hello", Some("/out"), None, None).unwrap();
|
||||
@@ -359,6 +384,7 @@ mod tests {
|
||||
assert_eq!(p, expected.to_string_lossy());
|
||||
}
|
||||
|
||||
/// `staticlib` kind resolves to a platform static library path.
|
||||
#[test]
|
||||
fn resolve_staticlib() {
|
||||
let p = resolve_artifact_path("staticlib", "release", "mylib", Some("dist"), None, None)
|
||||
@@ -372,6 +398,7 @@ mod tests {
|
||||
assert_eq!(p, expected.to_string_lossy());
|
||||
}
|
||||
|
||||
/// Custom output filename and extension override the default name.
|
||||
#[test]
|
||||
fn resolve_with_filename_ext() {
|
||||
let p = resolve_artifact_path(
|
||||
@@ -391,6 +418,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// `none` kind has no artifact path.
|
||||
#[test]
|
||||
fn resolve_none_kind() {
|
||||
assert!(resolve_artifact_path("none", "debug", "x", None, None, None).is_none());
|
||||
|
||||
@@ -24,12 +24,16 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Compiles C/C++ sources and links/archives by kind (`bin`, libs, `flat-bin`, or compile-only).
|
||||
///
|
||||
/// Handles Qt preprocessing when enabled. Returns elapsed seconds.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
let compiler = if ctx.compiler.is_empty() {
|
||||
"cc"
|
||||
} else {
|
||||
ctx.compiler
|
||||
};
|
||||
// Reject unsupported flat-bin with Qt
|
||||
if is_flat_bin(ctx.kind) && ctx.qt {
|
||||
return Err("flat-bin is not supported with build.qt = true".to_string());
|
||||
}
|
||||
@@ -69,6 +73,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
artifact::link_binary(ctx, &objects, compiler, start_time)
|
||||
}
|
||||
|
||||
/// Collects source files from the provided source roots for the build context.
|
||||
///
|
||||
/// Delegates to common source collection after resolving language-specific extensions.
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
let extensions = source_extensions(ctx.language);
|
||||
common::collect_sources(
|
||||
@@ -79,14 +86,19 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Source file extensions for `language` (owned `Vec` of static slices).
|
||||
fn source_extensions(language: &str) -> Vec<&'static str> {
|
||||
crate::core::build::common::source_extensions(language)
|
||||
}
|
||||
|
||||
/// Retrieves default compiler flags for the specified build profile.
|
||||
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||
crate::utils::build::default_profile_flags(profile)
|
||||
}
|
||||
|
||||
/// Compiles all provided sources into object files in parallel.
|
||||
///
|
||||
/// Prepares object file paths and spawns parallel tasks for each source compilation.
|
||||
fn build_objects(
|
||||
compiler: &str,
|
||||
sources: &[String],
|
||||
@@ -100,6 +112,7 @@ fn build_objects(
|
||||
.map(|s| common::object_path(obj_dir, s, obj_ext))
|
||||
.collect();
|
||||
|
||||
// Compile objects in parallel
|
||||
common::parallel_build(
|
||||
sources.len(),
|
||||
|i| {
|
||||
@@ -117,6 +130,9 @@ fn build_objects(
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
/// Compiles a single source file to an object file.
|
||||
///
|
||||
/// Creates object directory if needed, checks for rebuild necessity, constructs and runs the compiler command with appropriate flags.
|
||||
fn build_object(
|
||||
compiler: &str,
|
||||
source: &str,
|
||||
@@ -128,6 +144,7 @@ fn build_object(
|
||||
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||
}
|
||||
|
||||
// Skip if object is already up to date
|
||||
if !common::needs_rebuild(source, obj_path) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -183,6 +200,8 @@ fn build_object(
|
||||
|
||||
let use_dcr_defaults = ctx.cflags.is_empty()
|
||||
&& !(ctx.freestanding || is_bare_metal_target(ctx.target) || is_flat_bin(ctx.kind));
|
||||
|
||||
// Use default flags only if no custom flags and not special target
|
||||
if use_dcr_defaults {
|
||||
for flag in default_flags(ctx.profile) {
|
||||
cmd.arg(flag);
|
||||
@@ -211,6 +230,7 @@ fn build_object(
|
||||
common::run_command_sync_output(&mut cmd)
|
||||
}
|
||||
|
||||
/// Returns the assembly language flag for the compiler based on source extension.
|
||||
fn asm_lang_flag(source: &str) -> Option<&'static str> {
|
||||
crate::core::build::common::asm_lang_flag(source)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub mod msvc;
|
||||
use crate::core::build::language::asm::{fasm, gas, masm, nasm};
|
||||
use crate::core::build::language::llvm_ir;
|
||||
|
||||
/// Configuration context for the build process, including compiler, target, and output settings.
|
||||
pub struct BuildContext<'a> {
|
||||
pub profile: &'a str,
|
||||
pub project_name: &'a str,
|
||||
@@ -56,6 +57,7 @@ pub struct BuildContext<'a> {
|
||||
pub qt: bool,
|
||||
}
|
||||
|
||||
/// Trait defining the build interface for different compilers and languages.
|
||||
pub trait Builder {
|
||||
#[allow(dead_code)]
|
||||
fn id(&self) -> &'static str;
|
||||
@@ -63,12 +65,19 @@ pub trait Builder {
|
||||
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String>;
|
||||
}
|
||||
|
||||
/// Concrete builder for C/C++ languages using gcc/clang.
|
||||
struct Cc;
|
||||
/// Concrete builder for MSVC and clang-cl.
|
||||
struct Msvc;
|
||||
/// Concrete builder for NASM assembly.
|
||||
struct Nasm;
|
||||
/// Concrete builder for GNU Assembler (gas).
|
||||
struct Gas;
|
||||
/// Concrete builder for MASM assembler.
|
||||
struct Masm;
|
||||
/// Concrete builder for FASM assembler.
|
||||
struct Fasm;
|
||||
/// Concrete builder for LLVM IR.
|
||||
struct Llc;
|
||||
|
||||
impl Builder for Cc {
|
||||
@@ -156,6 +165,7 @@ impl Builder for Llc {
|
||||
}
|
||||
|
||||
pub fn builder_for(compiler: &str) -> &'static dyn Builder {
|
||||
// Normalize compiler name to lowercase for matching.
|
||||
let c = compiler.to_lowercase();
|
||||
if c.contains("clang-cl") {
|
||||
return &Msvc;
|
||||
@@ -181,6 +191,7 @@ pub fn builder_for(compiler: &str) -> &'static dyn Builder {
|
||||
&Cc
|
||||
}
|
||||
|
||||
/// Main build entrypoint that validates the compiler and delegates to the selected builder.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
if !check_compiler_exists(ctx.compiler) {
|
||||
return Err(format!(
|
||||
@@ -188,9 +199,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
ctx.compiler
|
||||
));
|
||||
}
|
||||
// Delegate to the concrete builder's build method.
|
||||
builder_for(ctx.compiler).build(ctx)
|
||||
}
|
||||
|
||||
/// True if `compiler --version` can be spawned (exit status is ignored).
|
||||
fn check_compiler_exists(compiler: &str) -> bool {
|
||||
let name = if compiler.is_empty() {
|
||||
"cc"
|
||||
@@ -209,10 +222,12 @@ fn check_compiler_exists(compiler: &str) -> bool {
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Collects source code files by delegating to the builder selected for the compiler.
|
||||
pub fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
builder_for(ctx.compiler).collect_sources(ctx)
|
||||
}
|
||||
|
||||
/// Unit test verifying that builder_for correctly identifies the builder for various compiler names.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// MSVC build backend for DCR.
|
||||
///
|
||||
/// This module provides the MSVC-specific build implementation for C and C++
|
||||
/// projects, including object compilation and linking for executables, static
|
||||
/// libraries, shared libraries, and flat binaries.
|
||||
use crate::core::build::builder::BuildContext;
|
||||
use crate::core::build::builder::artifact;
|
||||
use crate::core::build::common;
|
||||
@@ -25,6 +30,7 @@ use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Builds the project target using the MSVC compiler.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
let compiler = if ctx.compiler.is_empty() {
|
||||
"cl"
|
||||
@@ -159,6 +165,7 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Links the flat binary output for MSVC build targets.
|
||||
fn link_flat_msvc(
|
||||
ctx: &BuildContext,
|
||||
compiler: &str,
|
||||
@@ -214,6 +221,7 @@ fn link_flat_msvc(
|
||||
Ok(common::elapsed_secs(start_time))
|
||||
}
|
||||
|
||||
/// Collects all source files for the build based on language extensions.
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
let extensions = source_extensions(ctx.language);
|
||||
common::collect_sources(
|
||||
@@ -224,10 +232,12 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Source file extensions for `language` (owned `Vec` of static slices).
|
||||
fn source_extensions(language: &str) -> Vec<&'static str> {
|
||||
crate::core::build::common::source_extensions(language)
|
||||
}
|
||||
|
||||
/// Maps the language and standard version to the corresponding MSVC flag string.
|
||||
fn msvc_standard_flag(language: &str, standard: &str) -> Result<String, String> {
|
||||
let lang = language.to_lowercase();
|
||||
let std = standard.to_lowercase();
|
||||
@@ -251,6 +261,7 @@ fn msvc_standard_flag(language: &str, standard: &str) -> Result<String, String>
|
||||
Err("Unsupported language".to_string())
|
||||
}
|
||||
|
||||
/// Returns the MSVC architecture flag based on the target platform.
|
||||
fn msvc_arch_flag(platform: Option<&str>) -> Option<&'static str> {
|
||||
let raw = platform?.trim();
|
||||
if raw.is_empty() {
|
||||
@@ -268,6 +279,7 @@ fn msvc_arch_flag(platform: Option<&str>) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns default compiler flags for the specified build profile.
|
||||
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||
match profile {
|
||||
"release" => &["/O2", "/DNDEBUG"],
|
||||
@@ -276,6 +288,7 @@ fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles all sources into object files using parallel processing.
|
||||
fn build_objects(
|
||||
compiler: &str,
|
||||
sources: &[String],
|
||||
@@ -297,6 +310,7 @@ fn build_objects(
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
/// Compiles a single source file to an object file.
|
||||
fn build_object(
|
||||
compiler: &str,
|
||||
source: &str,
|
||||
@@ -369,6 +383,7 @@ fn build_object(
|
||||
let mut headers = Vec::new();
|
||||
let mut clean_stdout = String::new();
|
||||
|
||||
// Parse compiler output to extract include file notes for dependency generation
|
||||
for line in stdout.lines() {
|
||||
if let Some(stripped) = line.strip_prefix("Note: including file:") {
|
||||
headers.push(stripped.trim().to_string());
|
||||
|
||||
@@ -21,6 +21,17 @@ use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Computes a build fingerprint by hashing context fields and file metadata (SHA-256).
|
||||
///
|
||||
/// Includes `dcr.toml` (required) and `dcr.lock` when present, plus path/size/mtime
|
||||
/// for each source, header, and library file.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `ctx`: Build context (profile, tools, flags, dirs, …).
|
||||
/// - `sources` / `headers` / `lib_files`: Inputs whose metadata enters the hash.
|
||||
///
|
||||
/// # Returns
|
||||
/// Hex-encoded fingerprint, or an error if `dcr.toml` or a listed file cannot be read.
|
||||
pub(crate) fn compute_build_fingerprint(
|
||||
ctx: &BuildContext,
|
||||
sources: &[String],
|
||||
@@ -61,12 +72,14 @@ pub(crate) fn compute_build_fingerprint(
|
||||
for value in ctx.ldflags {
|
||||
hasher.update(value.as_bytes());
|
||||
}
|
||||
// Include project metadata from dcr.toml in the fingerprint
|
||||
let toml =
|
||||
fs::read_to_string("dcr.toml").map_err(|err| format!("Failed to read dcr.toml: {err}"))?;
|
||||
hasher.update(toml.as_bytes());
|
||||
if let Ok(lock) = fs::read_to_string("dcr.lock") {
|
||||
hasher.update(lock.as_bytes());
|
||||
}
|
||||
// Include source, header, and library files in the fingerprint
|
||||
for source in sources {
|
||||
let path = Path::new(source);
|
||||
update_hasher_with_file(&mut hasher, path)?;
|
||||
@@ -80,6 +93,14 @@ pub(crate) fn compute_build_fingerprint(
|
||||
Ok(to_hex(&hasher.finalize()))
|
||||
}
|
||||
|
||||
/// Whether the build can be skipped: main artifact exists and cache hash matches.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `ctx`: Used to locate the output artifact and `.dcr-build.hash`.
|
||||
/// - `fingerprint`: Fresh hash from [`compute_build_fingerprint`].
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` only when both the artifact and matching cache file are present.
|
||||
pub(crate) fn should_skip_build(ctx: &BuildContext, fingerprint: &str) -> bool {
|
||||
let output = build_output_path(ctx);
|
||||
if !Path::new(&output).is_file() {
|
||||
@@ -90,6 +111,7 @@ pub(crate) fn should_skip_build(ctx: &BuildContext, fingerprint: &str) -> bool {
|
||||
cached.trim() == fingerprint
|
||||
}
|
||||
|
||||
/// Persists the build fingerprint to the cache file on disk.
|
||||
pub(crate) fn write_build_fingerprint(ctx: &BuildContext, fingerprint: &str) -> Result<(), String> {
|
||||
let cache_path = build_cache_path(ctx.profile, ctx.target_dir);
|
||||
if let Some(parent) = cache_path.parent() {
|
||||
@@ -99,6 +121,7 @@ pub(crate) fn write_build_fingerprint(ctx: &BuildContext, fingerprint: &str) ->
|
||||
.map_err(|err| format!("Failed to write cache: {err}"))
|
||||
}
|
||||
|
||||
/// Returns the path to the build cache hash file based on profile and target directory.
|
||||
fn build_cache_path(profile: &str, target_dir: Option<&str>) -> PathBuf {
|
||||
match target_dir {
|
||||
Some(dir) => Path::new(dir).join(".dcr-build.hash"),
|
||||
@@ -106,6 +129,7 @@ fn build_cache_path(profile: &str, target_dir: Option<&str>) -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the output path for the build artifact using the provided context.
|
||||
pub fn build_output_path(ctx: &BuildContext) -> String {
|
||||
crate::core::build::builder::artifact::resolve_artifact_path(
|
||||
ctx.kind,
|
||||
@@ -124,6 +148,7 @@ pub fn build_output_path(ctx: &BuildContext) -> String {
|
||||
})
|
||||
}
|
||||
|
||||
/// Collects header files from source roots and include directories, filtering out excluded paths.
|
||||
pub(crate) fn collect_header_files(
|
||||
ctx: &BuildContext,
|
||||
project_root: &Path,
|
||||
@@ -165,6 +190,7 @@ pub(crate) fn collect_header_files(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Recursively scans a directory to collect header files, respecting exclusion rules.
|
||||
fn collect_header_files_rec(
|
||||
dir: &Path,
|
||||
out: &mut Vec<PathBuf>,
|
||||
@@ -197,11 +223,13 @@ fn collect_header_files_rec(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if the given path has a header file extension.
|
||||
fn is_header_file(path: &Path) -> bool {
|
||||
let ext = path.extension().and_then(|v| v.to_str()).unwrap_or("");
|
||||
matches!(ext, "h" | "hpp" | "hh" | "hxx" | "inc")
|
||||
}
|
||||
|
||||
/// Collects library files from lib directories using platform-specific candidates.
|
||||
pub(crate) fn collect_lib_files(ctx: &BuildContext) -> Vec<PathBuf> {
|
||||
let mut out = Vec::new();
|
||||
for dir in ctx.lib_dirs {
|
||||
@@ -218,6 +246,7 @@ pub(crate) fn collect_lib_files(ctx: &BuildContext) -> Vec<PathBuf> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Candidate library filenames for the **host** OS (`cfg!(target_os = …)`).
|
||||
fn lib_candidates(name: &str) -> Vec<String> {
|
||||
if cfg!(target_os = "windows") {
|
||||
return vec![format!("{name}.lib")];
|
||||
@@ -236,6 +265,7 @@ fn lib_candidates(name: &str) -> Vec<String> {
|
||||
]
|
||||
}
|
||||
|
||||
/// Updates the hasher with the file's path, size, and last modification time.
|
||||
fn update_hasher_with_file(hasher: &mut Sha256, path: &Path) -> Result<(), String> {
|
||||
hasher.update(path.to_string_lossy().as_bytes());
|
||||
let meta = fs::metadata(path).map_err(|err| format!("source read error: {err}"))?;
|
||||
@@ -248,6 +278,7 @@ fn update_hasher_with_file(hasher: &mut Sha256, path: &Path) -> Result<(), Strin
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts a byte slice containing the SHA256 hash to a hexadecimal string.
|
||||
fn to_hex(bytes: &[u8]) -> String {
|
||||
crate::utils::fs::to_hex(bytes)
|
||||
}
|
||||
|
||||
@@ -27,18 +27,25 @@ static OUTPUT_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
static PROGRESS_LABEL: OnceLock<Mutex<Option<String>>> = OnceLock::new();
|
||||
static PROGRESS_DIRTY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Returns a static mutex used to synchronize access to stderr during progress reporting.
|
||||
/// This ensures thread-safe output when multiple build threads write to the terminal.
|
||||
pub fn get_output_lock() -> &'static Mutex<()> {
|
||||
OUTPUT_MUTEX.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
/// Initializes the shared progress label storage.
|
||||
fn progress_label_lock() -> &'static Mutex<Option<String>> {
|
||||
PROGRESS_LABEL.get_or_init(|| Mutex::new(None))
|
||||
}
|
||||
|
||||
/// Sets the label text that will be shown next to the compile progress counter.
|
||||
/// The label is cleared after the progress line is finished.
|
||||
pub fn set_progress_label(label: Option<String>) {
|
||||
*progress_label_lock().lock().unwrap() = label;
|
||||
}
|
||||
|
||||
/// Finishes the current progress line by clearing the label and flushing the line if it was dirty.
|
||||
/// This is called when a build step completes normally.
|
||||
pub fn finish_progress_line() {
|
||||
let was_dirty = PROGRESS_DIRTY.swap(false, Ordering::SeqCst);
|
||||
*progress_label_lock().lock().unwrap() = None;
|
||||
@@ -52,6 +59,8 @@ pub fn finish_progress_line() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupts the current progress line (if any) and flushes it to stderr.
|
||||
/// Used when a build step produces output that should interrupt the progress display.
|
||||
pub fn interrupt_progress_for_output() {
|
||||
if !PROGRESS_DIRTY.swap(false, Ordering::SeqCst) {
|
||||
return;
|
||||
@@ -63,6 +72,9 @@ pub fn interrupt_progress_for_output() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports the current compile progress to stderr in the format "label [done/total]".
|
||||
/// Only prints if the terminal is attached and the label is set.
|
||||
/// Updates the dirty flag so the line can be overwritten on the next call.
|
||||
fn report_compile_progress(done: usize, total: usize) {
|
||||
let label = progress_label_lock().lock().unwrap().clone();
|
||||
let Some(label) = label else {
|
||||
@@ -82,6 +94,9 @@ fn report_compile_progress(done: usize, total: usize) {
|
||||
PROGRESS_DIRTY.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Recursively collects all source files under the given roots that match the provided extensions.
|
||||
/// Directories listed in exclude_dirs are skipped unless overridden by include_paths.
|
||||
/// Returns a sorted list of normalized source paths or an error string.
|
||||
pub fn collect_sources(
|
||||
roots: &[PathBuf],
|
||||
extensions: &[&str],
|
||||
@@ -104,6 +119,8 @@ pub fn collect_sources(
|
||||
Ok(sources)
|
||||
}
|
||||
|
||||
/// Recursively walks a directory tree to collect source files.
|
||||
/// Skips excluded directories unless they are explicitly allowed by include_paths.
|
||||
fn collect_sources_rec(
|
||||
dir: &Path,
|
||||
extensions: &[&str],
|
||||
@@ -151,6 +168,8 @@ fn collect_sources_rec(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determines whether a path should be excluded from source collection.
|
||||
/// Returns false if the path is explicitly allowed by include_paths (even if it matches an exclude).
|
||||
pub fn is_excluded(path: &Path, exclude_dirs: &[PathBuf], include_paths: &[String]) -> bool {
|
||||
if matches_patterns(path, include_paths, true) {
|
||||
return false;
|
||||
@@ -161,6 +180,8 @@ pub fn is_excluded(path: &Path, exclude_dirs: &[PathBuf], include_paths: &[Strin
|
||||
exclude_dirs.iter().any(|dir| path_is_under(path, dir))
|
||||
}
|
||||
|
||||
/// Checks if a path is under a given prefix, handling both absolute and relative paths
|
||||
/// and normalizing Windows-style separators for cross-platform comparison.
|
||||
fn path_is_under(path: &Path, prefix: &Path) -> bool {
|
||||
if path.starts_with(prefix) {
|
||||
return true;
|
||||
@@ -180,6 +201,9 @@ fn path_is_under(path: &Path, prefix: &Path) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches a path against include/exclude glob and path patterns.
|
||||
/// Positive patterns allow files; negative patterns (starting with !) exclude them.
|
||||
/// Returns true if the path matches any rule.
|
||||
fn matches_patterns(path: &Path, patterns: &[String], positive: bool) -> bool {
|
||||
if patterns.is_empty() {
|
||||
return false;
|
||||
@@ -244,10 +268,20 @@ fn matches_patterns(path: &Path, patterns: &[String], positive: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the string contains any glob magic characters (*, ?, or [).
|
||||
pub fn has_glob_magic(value: &str) -> bool {
|
||||
value.chars().any(|c| matches!(c, '*' | '?' | '['))
|
||||
}
|
||||
|
||||
/// Returns a GCC/Clang `-x` language for assembly sources.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `source`: Source file path (extension decides the dialect).
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Some("assembler-with-cpp")` for `.S`
|
||||
/// - `Some("assembler")` for `.s` / `.asm`
|
||||
/// - `None` for other extensions
|
||||
pub fn asm_lang_flag(source: &str) -> Option<&'static str> {
|
||||
let ext = Path::new(source).extension().and_then(|v| v.to_str())?;
|
||||
match ext {
|
||||
@@ -257,6 +291,8 @@ pub fn asm_lang_flag(source: &str) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of file extensions associated with the given language token(s).
|
||||
/// Splits comma-separated tokens and falls back to .c if no language is recognized.
|
||||
pub fn source_extensions(language: &str) -> Vec<&'static str> {
|
||||
let mut out = Vec::new();
|
||||
for part in language.split(',').map(|s| s.trim()) {
|
||||
@@ -270,10 +306,13 @@ pub fn source_extensions(language: &str) -> Vec<&'static str> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Returns elapsed time in seconds with two decimal places for display purposes.
|
||||
pub fn elapsed_secs(start: Instant) -> f64 {
|
||||
((start.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0
|
||||
}
|
||||
|
||||
/// Spawns a fixed number of worker threads to parallelize a build task.
|
||||
/// Reports progress after each task completes.
|
||||
pub fn parallel_build<F>(total_tasks: usize, task_fn: F, max_jobs: usize) -> Result<(), String>
|
||||
where
|
||||
F: Fn(usize) -> Result<(), String> + Sync + Send,
|
||||
@@ -330,6 +369,8 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs a command, prints stdout/stderr to the terminal (interrupting progress),
|
||||
/// and returns `Ok(())` or `Err` based on exit status.
|
||||
pub fn run_command_sync_output(cmd: &mut Command) -> Result<(), String> {
|
||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
||||
let output = cmd.output().map_err(|err| {
|
||||
@@ -357,6 +398,8 @@ pub fn run_command_sync_output(cmd: &mut Command) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes a source path to be relative to the current working directory when possible.
|
||||
/// Returns the original path unchanged if it cannot be made relative.
|
||||
pub fn normalize_source_path(path: &Path) -> String {
|
||||
if !path.is_absolute() {
|
||||
return path.to_string_lossy().to_string();
|
||||
@@ -369,6 +412,8 @@ pub fn normalize_source_path(path: &Path) -> String {
|
||||
path.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
/// Computes the output object file path by stripping the leading ./ and adjusting the extension.
|
||||
/// Places the object in the specified obj_dir while preserving directory structure.
|
||||
pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||
let src_path = Path::new(source);
|
||||
let stripped = src_path.strip_prefix("./").unwrap_or(src_path);
|
||||
@@ -401,6 +446,16 @@ pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||
out.to_string_lossy().replace('\\', "/")
|
||||
}
|
||||
|
||||
/// Checks whether a source file needs to be rebuilt.
|
||||
///
|
||||
/// Compares mtimes of the source, object, and deps listed in the sibling `.d` file.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `source`: Source path.
|
||||
/// - `object`: Object path (`.d` is derived by changing the extension).
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if the object is missing, older than source/deps, or a dep is missing.
|
||||
pub fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||
let src_time = fs::metadata(source).and_then(|m| m.modified());
|
||||
let obj_time = fs::metadata(object).and_then(|m| m.modified());
|
||||
@@ -436,6 +491,14 @@ pub fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks whether the output executable needs to be relinked.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `objects`: Object file paths that feed the link step.
|
||||
/// - `output_path`: Linked artifact path.
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if the output is missing or any object is newer than it.
|
||||
pub fn needs_link(objects: &[String], output_path: &str) -> bool {
|
||||
let out_meta = match fs::metadata(output_path) {
|
||||
Ok(m) => m,
|
||||
@@ -461,6 +524,8 @@ pub fn needs_link(objects: &[String], output_path: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Parses a dependency file (.d) produced by GCC or MSVC compilers.
|
||||
/// Extracts all dependency paths, handling escaped newlines and Windows-style paths.
|
||||
fn parse_d_file(content: &str) -> Vec<String> {
|
||||
let mut deps = Vec::new();
|
||||
let mut target_end = 0;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// Core build engine module for DCR.
|
||||
/// This module provides the main build logic for projects and workspaces.
|
||||
use crate::core::build::builder::{BuildContext, build as build_project, collect_sources};
|
||||
use crate::core::build::cache::{
|
||||
collect_header_files, collect_lib_files, compute_build_fingerprint, should_skip_build,
|
||||
@@ -49,6 +51,7 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Instant;
|
||||
|
||||
/// Ensures that target directories exist for the build output.
|
||||
fn ensure_target_dirs(
|
||||
project_root: &Path,
|
||||
items: &[String],
|
||||
@@ -68,6 +71,7 @@ fn ensure_target_dirs(
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the project build and returns the elapsed time in seconds or an error.
|
||||
fn run_compile(
|
||||
ctx: &BuildContext,
|
||||
_cancel: &Arc<AtomicBool>,
|
||||
@@ -89,8 +93,16 @@ fn run_compile(
|
||||
}
|
||||
}
|
||||
|
||||
// Library entry point: run a build described by `req`, streaming progress to
|
||||
// `rep`. The CLI wraps this with a CliReporter; TUI/GUI/MCP plug in their own.
|
||||
/// Library entry point: run a build described by `req`, streaming progress to
|
||||
/// `rep`. The CLI wraps this with a CliReporter; TUI/GUI/MCP plug in their own.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `root`: Project or workspace root containing `dcr.toml`.
|
||||
/// - `req`: Profile, target, force/verbose, optional member, cancel flag.
|
||||
/// - `rep`: Progress sink for build events.
|
||||
///
|
||||
/// # Returns
|
||||
/// `BuildOutcome` with elapsed seconds, or `BuildError` on failure/cancel.
|
||||
pub fn run_build(
|
||||
root: &Path,
|
||||
req: &BuildRequest,
|
||||
@@ -110,6 +122,7 @@ pub fn run_build(
|
||||
Ok(BuildOutcome { secs })
|
||||
}
|
||||
|
||||
/// Orchestrates the build for the specified profile and target, handling workspaces and projects.
|
||||
#[allow(unused_variables, clippy::too_many_arguments)]
|
||||
fn build_all(
|
||||
root: &Path,
|
||||
@@ -279,6 +292,7 @@ fn build_all(
|
||||
Ok(elapsed)
|
||||
}
|
||||
|
||||
/// Builds each workspace member in order (flat loop, not recursive into nested workspaces).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_workspace(
|
||||
workspace: &crate::core::workspace::Workspace,
|
||||
@@ -308,6 +322,7 @@ fn build_workspace(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a single project at the given root, handling configuration and steps.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_project_at(
|
||||
project_root: &Path,
|
||||
@@ -904,6 +919,7 @@ fn build_project_at(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts relative paths in flags to absolute paths based on project root for include and link flags.
|
||||
fn absolutize_flag_path(flag: &str, project_root: &Path, prefixes: &[&str]) -> String {
|
||||
for prefix in prefixes {
|
||||
if let Some(rest) = flag.strip_prefix(prefix) {
|
||||
@@ -924,6 +940,7 @@ fn absolutize_flag_path(flag: &str, project_root: &Path, prefixes: &[&str]) -> S
|
||||
flag.to_string()
|
||||
}
|
||||
|
||||
/// Finds the nearest ancestor directory named "target" starting from the given dir.
|
||||
fn find_target_root(dir: &str, fallback: &Path) -> PathBuf {
|
||||
let p = Path::new(dir);
|
||||
for ancestor in p.ancestors() {
|
||||
@@ -934,6 +951,7 @@ fn find_target_root(dir: &str, fallback: &Path) -> PathBuf {
|
||||
fallback.to_path_buf()
|
||||
}
|
||||
|
||||
/// Packages the built library by copying headers and libs to the target include and lib directories.
|
||||
fn package_library(
|
||||
ctx: &BuildContext,
|
||||
headers: &[PathBuf],
|
||||
@@ -988,12 +1006,14 @@ fn package_library(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Holds the resolved executable paths for Qt tools.
|
||||
pub(crate) struct ToolchainExecs {
|
||||
pub(crate) uic: String,
|
||||
pub(crate) moc: String,
|
||||
pub(crate) rcc: String,
|
||||
}
|
||||
|
||||
/// Resolves the paths for uic, moc, and rcc tools, preferring configured values or pkg-config/Qt6 variants.
|
||||
fn resolve_toolchain_execs(
|
||||
uic: &Option<String>,
|
||||
moc: &Option<String>,
|
||||
@@ -1008,6 +1028,7 @@ fn resolve_toolchain_execs(
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a Qt tool path, falling back to Qt6 variants if not found in standard locations.
|
||||
fn resolve_qt_tool(configured: &Option<String>, qt_bins: Option<&Path>, tool: &str) -> String {
|
||||
if let Some(value) = configured {
|
||||
return value.clone();
|
||||
@@ -1030,6 +1051,7 @@ fn resolve_qt_tool(configured: &Option<String>, qt_bins: Option<&Path>, tool: &s
|
||||
tool.to_string()
|
||||
}
|
||||
|
||||
/// Queries pkg-config for Qt6 host bin directories for the given packages.
|
||||
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() {
|
||||
@@ -1065,6 +1087,7 @@ fn resolve_qt_host_bins(pkgs: &[String]) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Executes pkg-config to get a variable value for a package and returns the path if valid.
|
||||
fn query_pkg_config_var(pkg: &str, var: &str) -> Option<PathBuf> {
|
||||
let output = std::process::Command::new("pkg-config")
|
||||
.arg(format!("--variable={var}"))
|
||||
@@ -1082,6 +1105,7 @@ fn query_pkg_config_var(pkg: &str, var: &str) -> Option<PathBuf> {
|
||||
if path.is_dir() { Some(path) } else { None }
|
||||
}
|
||||
|
||||
/// Extracts tool binaries from a libexec directory or its bin subdirectory.
|
||||
fn qt_bins_from_libexec(libexec: &Path) -> Option<PathBuf> {
|
||||
for tool in ["moc", "uic", "rcc"] {
|
||||
if libexec.join(tool).is_file() {
|
||||
@@ -1095,12 +1119,14 @@ fn qt_bins_from_libexec(libexec: &Path) -> Option<PathBuf> {
|
||||
if bin.is_dir() { Some(bin) } else { None }
|
||||
}
|
||||
|
||||
/// Detects if a Qt6 tool variant (like uic6 or uic-qt6) is available on the PATH.
|
||||
fn detect_qt6_tool_variant(tool: &str) -> Option<String> {
|
||||
[format!("{tool}6"), format!("{tool}-qt6")]
|
||||
.into_iter()
|
||||
.find(|candidate| is_on_path(candidate))
|
||||
}
|
||||
|
||||
/// Checks if a command is available either as a file or in the PATH environment variable.
|
||||
fn is_on_path(cmd: &str) -> bool {
|
||||
if Path::new(cmd).is_file() {
|
||||
return true;
|
||||
|
||||
@@ -5,6 +5,13 @@ use crate::utils::build::{is_compile_only, is_flat_bin};
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Keeps only assembler-relevant flags from a cflags list.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `flags`: Full compiler flag list (often shared with C/C++).
|
||||
///
|
||||
/// # Returns
|
||||
/// Cloned flags that start with `-I`/`-i` or equal `-g`.
|
||||
pub fn filter_asm_flags(flags: &[String]) -> Vec<String> {
|
||||
flags
|
||||
.iter()
|
||||
@@ -16,6 +23,19 @@ pub fn filter_asm_flags(flags: &[String]) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Builds assembly sources with direct flat emission when `kind` is `flat-bin`.
|
||||
///
|
||||
/// For non-flat kinds, assembles to objects and then links/archives as usual.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `ctx`: Build context (kind, sources, flags, …).
|
||||
/// - `backend_name`: Label used in error messages (e.g. `"NASM"`).
|
||||
/// - `default_assembler`: Assembler binary when `ctx.compiler` is empty.
|
||||
/// - `extensions`: Source extensions to collect.
|
||||
/// - `build_object`: Per-source assemble callback `(assembler, src, out, ctx)`.
|
||||
///
|
||||
/// # Returns
|
||||
/// Elapsed seconds, or an error string.
|
||||
pub fn build_assembly<F>(
|
||||
ctx: &BuildContext,
|
||||
backend_name: &str,
|
||||
@@ -36,6 +56,15 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds assembly sources; for `flat-bin`, assembles to objects then runs objcopy.
|
||||
///
|
||||
/// For non-flat kinds, assembles to objects and then links/archives as usual.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - Same as [`build_assembly`]; only the flat emission strategy differs.
|
||||
///
|
||||
/// # Returns
|
||||
/// Elapsed seconds, or an error string.
|
||||
pub fn build_assembly_via_objcopy<F>(
|
||||
ctx: &BuildContext,
|
||||
backend_name: &str,
|
||||
@@ -56,12 +85,14 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
/// Flat-bin emission strategy: direct assembler output, or object then objcopy.
|
||||
#[derive(Clone, Copy)]
|
||||
enum FlatEmit {
|
||||
Direct,
|
||||
ViaObjcopy,
|
||||
}
|
||||
|
||||
/// Core assembly build with a chosen flat emission mode; `build_object` runs per source in parallel.
|
||||
fn build_assembly_with_mode<F>(
|
||||
ctx: &BuildContext,
|
||||
backend_name: &str,
|
||||
@@ -74,6 +105,7 @@ where
|
||||
F: Fn(&str, &str, &str, &BuildContext) -> Result<(), String> + Sync,
|
||||
{
|
||||
let lang = ctx.language.to_lowercase();
|
||||
// Validate language
|
||||
if lang.split(',').any(|p| {
|
||||
let t = p.trim();
|
||||
t != "asm" && t != "llvm_ir" && t != "llvm-ir" && t != "ll"
|
||||
@@ -104,6 +136,7 @@ where
|
||||
};
|
||||
|
||||
let objects = match (flat, flat_mode) {
|
||||
// Dispatch based on flat binary kind and emission mode
|
||||
(true, FlatEmit::Direct) => {
|
||||
let outs: Vec<String> = sources
|
||||
.iter()
|
||||
@@ -148,6 +181,7 @@ where
|
||||
artifact::link_binary(ctx, &objects, "cc", start_time)
|
||||
}
|
||||
|
||||
/// Compiles all sources to objects in parallel (non-flat path) and returns object paths.
|
||||
fn build_objects_common<F>(
|
||||
assembler: &str,
|
||||
sources: &[String],
|
||||
|
||||
@@ -5,10 +5,14 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Builds an assembly file using the FASM assembler.
|
||||
///
|
||||
/// Delegates to the shared assembly build logic with FASM configuration.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly(ctx, "FASM", "fasm", &["asm", "fasm"], build_object)
|
||||
}
|
||||
|
||||
/// Collects all source files for assembly files using FASM.
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
common::collect_sources(
|
||||
ctx.source_roots,
|
||||
@@ -18,6 +22,7 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Assembles one FASM source into an object file.
|
||||
fn build_object(
|
||||
assembler: &str,
|
||||
source: &str,
|
||||
@@ -28,6 +33,7 @@ fn build_object(
|
||||
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||
}
|
||||
|
||||
// Skip rebuilding if the object is up to date
|
||||
if !common::needs_rebuild(source, obj_path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Builds GAS (`.s`) sources via the GNU Assembler.
|
||||
///
|
||||
/// Resolves `as` on PATH and delegates to the shared assembly builder. For
|
||||
/// `flat-bin`, intermediates are converted with objcopy after assembly.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
// Use full path to as
|
||||
let as_path = std::process::Command::new("which")
|
||||
@@ -35,6 +39,9 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly_via_objcopy(ctx, "GAS", &as_path, &["s"], build_object)
|
||||
}
|
||||
|
||||
/// Collects assembly source files with .s extension from the build context's source roots.
|
||||
///
|
||||
/// Excludes specified directories and respects include paths for filtering.
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
common::collect_sources(
|
||||
ctx.source_roots,
|
||||
@@ -44,6 +51,10 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Assembles a single .s source file into an object file using the provided assembler.
|
||||
///
|
||||
/// Creates parent directories for the output object, skips if up-to-date,
|
||||
/// runs the assembler with flags from the build context, and handles verbose output.
|
||||
fn build_object(
|
||||
assembler: &str,
|
||||
source: &str,
|
||||
@@ -54,10 +65,12 @@ fn build_object(
|
||||
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||
}
|
||||
|
||||
// Check if the object file is already up to date to avoid unnecessary rebuilds.
|
||||
if !common::needs_rebuild(source, obj_path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Prepare the assembler command with source file and output path.
|
||||
let mut cmd = Command::new(assembler);
|
||||
cmd.arg(source).arg("-o").arg(obj_path);
|
||||
|
||||
|
||||
@@ -5,10 +5,15 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Builds MASM (`.asm`) sources via `ml`.
|
||||
///
|
||||
/// For `flat-bin`, intermediates are converted with objcopy after assembly.
|
||||
/// Returns elapsed seconds or an error.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly_via_objcopy(ctx, "MASM", "ml", &["asm"], build_object)
|
||||
}
|
||||
|
||||
/// Collects MASM assembly source files from the given source roots.
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
common::collect_sources(
|
||||
ctx.source_roots,
|
||||
@@ -18,6 +23,12 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds a single MASM object file from the given source.
|
||||
///
|
||||
/// Creates the parent directory for the output object if it doesn't exist,
|
||||
/// skips the build if the object is already up to date, constructs the ml
|
||||
/// command line with flags, prints the command in debug mode if enabled,
|
||||
/// and executes it.
|
||||
fn build_object(
|
||||
assembler: &str,
|
||||
source: &str,
|
||||
@@ -25,10 +36,12 @@ fn build_object(
|
||||
ctx: &BuildContext,
|
||||
) -> Result<(), String> {
|
||||
if let Some(parent) = Path::new(obj_path).parent() {
|
||||
// Ensure the parent directory exists for the object file
|
||||
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||
}
|
||||
|
||||
if !common::needs_rebuild(source, obj_path) {
|
||||
// Skip rebuild if the object file is already current
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -40,6 +53,7 @@ fn build_object(
|
||||
.arg(source);
|
||||
|
||||
for flag in crate::core::build::language::asm::common::filter_asm_flags(ctx.cflags) {
|
||||
// Add filtered compiler flags to the command
|
||||
cmd.arg(flag);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// Assembly language module for the DCR build system.
|
||||
pub mod common;
|
||||
pub mod fasm;
|
||||
pub mod gas;
|
||||
@@ -23,15 +24,21 @@ pub mod nasm;
|
||||
|
||||
use crate::core::build::language::Language;
|
||||
|
||||
/// Represents the assembly language support in the build system.
|
||||
pub struct Asm;
|
||||
|
||||
impl Language for Asm {
|
||||
/// Returns the identifier string for this language.
|
||||
fn id(&self) -> &'static str {
|
||||
"asm"
|
||||
}
|
||||
|
||||
/// Returns the list of supported file extensions for assembly files.
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["s", "S", "asm"]
|
||||
}
|
||||
|
||||
/// Checks whether the given token matches the assembly language, using case-insensitive comparison.
|
||||
fn matches_token(&self, token: &str) -> bool {
|
||||
token.eq_ignore_ascii_case("asm")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// Support for NASM assembly language in the DCR build system.
|
||||
use crate::core::build::builder::BuildContext;
|
||||
use crate::core::build::common;
|
||||
use crate::core::build::language::asm::common as asm;
|
||||
@@ -22,10 +23,16 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Builds assembly sources using the NASM assembler.
|
||||
///
|
||||
/// Delegates to the shared assembly builder with NASM-specific configuration.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly(ctx, "NASM", "nasm", &["asm"], build_object)
|
||||
}
|
||||
|
||||
/// Collects source files for NASM assembly.
|
||||
///
|
||||
/// Filters for .asm files only, as NASM uses that extension (distinct from GAS .s).
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
// NASM handles .asm only (not .s which is GAS syntax, not .S which is GCC preprocessed)
|
||||
common::collect_sources(
|
||||
@@ -36,6 +43,9 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds a single object file from an assembly source using NASM.
|
||||
///
|
||||
/// Handles directory creation, rebuild checks, format selection, and command execution.
|
||||
fn build_object(
|
||||
assembler: &str,
|
||||
source: &str,
|
||||
@@ -43,13 +53,16 @@ fn build_object(
|
||||
ctx: &BuildContext,
|
||||
) -> Result<(), String> {
|
||||
if let Some(parent) = Path::new(obj_path).parent() {
|
||||
// Ensure output directory exists before building.
|
||||
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||
}
|
||||
|
||||
// Skip building if the object is already up to date.
|
||||
if !common::needs_rebuild(source, obj_path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Select appropriate object format for the target platform and binary type.
|
||||
let format = if crate::utils::build::is_flat_bin(ctx.kind) {
|
||||
"bin"
|
||||
} else {
|
||||
@@ -63,6 +76,7 @@ fn build_object(
|
||||
.arg("-o")
|
||||
.arg(obj_path);
|
||||
|
||||
// Append any additional assembly flags from build context.
|
||||
for flag in crate::core::build::language::asm::common::filter_asm_flags(ctx.cflags) {
|
||||
cmd.arg(flag);
|
||||
}
|
||||
@@ -74,6 +88,9 @@ fn build_object(
|
||||
common::run_command_sync_output(&mut cmd)
|
||||
}
|
||||
|
||||
/// Selects the NASM object file format string based on the target platform.
|
||||
///
|
||||
/// Supports different formats for x86 on various OSes and architectures.
|
||||
fn nasm_format(platform: Option<&str>) -> &'static str {
|
||||
if let Some(p) = platform {
|
||||
let p = p.to_lowercase().replace('-', "_");
|
||||
|
||||
@@ -17,15 +17,21 @@
|
||||
|
||||
use crate::core::build::language::Language;
|
||||
|
||||
/// Represents the C programming language support within the DCR build system.
|
||||
pub struct C;
|
||||
|
||||
impl Language for C {
|
||||
/// Returns the unique identifier for the C language.
|
||||
fn id(&self) -> &'static str {
|
||||
"c"
|
||||
}
|
||||
|
||||
/// Returns an array containing the file extension associated with C source code.
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["c"]
|
||||
}
|
||||
|
||||
/// Checks whether the given token matches the C language identifier in a case-insensitive manner.
|
||||
fn matches_token(&self, token: &str) -> bool {
|
||||
token.eq_ignore_ascii_case("c")
|
||||
}
|
||||
|
||||
@@ -15,20 +15,26 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// C++ language support for the DCR build system.
|
||||
pub mod qt;
|
||||
|
||||
use crate::core::build::language::Language;
|
||||
|
||||
pub struct Cxx;
|
||||
|
||||
/// C++ language implementation.
|
||||
impl Language for Cxx {
|
||||
/// Returns the language ID "cxx".
|
||||
fn id(&self) -> &'static str {
|
||||
"cxx"
|
||||
}
|
||||
/// Returns the extensions for C++ files.
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["cpp", "cxx", "cc"]
|
||||
}
|
||||
/// Checks if the token matches C++ language names, ignoring case.
|
||||
fn matches_token(&self, token: &str) -> bool {
|
||||
// Case-insensitive comparison using to_lowercase
|
||||
matches!(token.to_lowercase().as_str(), "c++" | "cpp" | "cxx")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@ use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
/// Runs uic/rcc/moc on Qt UI, resource, and header sources under `build_dir/qt`.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `ctx`: Sources, excludes, and toolchain tool paths.
|
||||
/// - `build_dir`: Directory that will contain the generated `qt/` tree.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Some(qt_dir))` if any generated files were produced.
|
||||
/// - `Ok(None)` if there was nothing to process.
|
||||
/// - `Err` if a Qt tool fails.
|
||||
pub fn process_qt(ctx: &BuildContext, build_dir: &Path) -> Result<Option<PathBuf>, String> {
|
||||
let qt_dir = build_dir.join("qt");
|
||||
fs::create_dir_all(&qt_dir).map_err(|e| format!("Failed to create qt dir: {e}"))?;
|
||||
@@ -37,6 +47,7 @@ pub fn process_qt(ctx: &BuildContext, build_dir: &Path) -> Result<Option<PathBuf
|
||||
let mut qt_moc_args = Vec::new();
|
||||
let qt_modules = vec!["Qt6Core", "Qt6Widgets", "Qt6Gui", "Qt6Svg"];
|
||||
|
||||
// Collect -I and -D flags from pkg-config for Qt modules to use in moc invocations
|
||||
for module in qt_modules {
|
||||
if let Ok(cflags) = run_pkg_config(module, "--cflags") {
|
||||
for flag in cflags.split_whitespace() {
|
||||
@@ -123,6 +134,7 @@ pub fn process_qt(ctx: &BuildContext, build_dir: &Path) -> Result<Option<PathBuf
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the specified command with the given arguments and returns success or an error message.
|
||||
fn run_command(cmd_path: &str, args: &[&str], verbose: bool) -> Result<(), String> {
|
||||
let mut cmd = Command::new(cmd_path);
|
||||
cmd.args(args);
|
||||
|
||||
@@ -6,24 +6,32 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Represents the LLVM IR language implementation for the build system.
|
||||
pub struct LlvmIr;
|
||||
|
||||
impl Language for LlvmIr {
|
||||
/// Returns the unique identifier for the LLVM IR language.
|
||||
fn id(&self) -> &'static str {
|
||||
"llvm_ir"
|
||||
}
|
||||
|
||||
/// Returns the list of file extensions recognized for this language.
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["ll"]
|
||||
}
|
||||
|
||||
/// Determines whether the provided token matches the LLVM IR language.
|
||||
fn matches_token(&self, token: &str) -> bool {
|
||||
matches!(token.to_lowercase().as_str(), "llvm_ir" | "llvm-ir" | "ll")
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds LLVM IR source files into object files using llc.
|
||||
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
asm::build_assembly_via_objcopy(ctx, "LLVM-IR", "llc", &["ll"], build_object)
|
||||
}
|
||||
|
||||
/// Collects source files for LLVM IR compilation from the given context.
|
||||
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
|
||||
common::collect_sources(
|
||||
ctx.source_roots,
|
||||
@@ -33,6 +41,7 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String>
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds a single LLVM IR source file into an object file.
|
||||
fn build_object(
|
||||
assembler: &str,
|
||||
source: &str,
|
||||
@@ -40,6 +49,7 @@ fn build_object(
|
||||
ctx: &BuildContext,
|
||||
) -> Result<(), String> {
|
||||
if let Some(parent) = Path::new(obj_path).parent() {
|
||||
// Create parent directory if it does not exist
|
||||
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,21 +15,29 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// Core module for language support in the DCR build system.
|
||||
/// Defines the Language trait and helper functions to register and lookup languages.
|
||||
pub mod asm;
|
||||
pub mod c;
|
||||
pub mod cxx;
|
||||
pub mod llvm_ir;
|
||||
|
||||
/// Trait representing a supported programming language.
|
||||
/// Each implementation must specify its unique identifier, list of supported file extensions,
|
||||
/// and a method to determine if a given token matches the language.
|
||||
pub trait Language {
|
||||
fn id(&self) -> &'static str;
|
||||
fn extensions(&self) -> &'static [&'static str];
|
||||
fn matches_token(&self, token: &str) -> bool;
|
||||
}
|
||||
|
||||
/// Returns a fixed-size array containing static references to all supported language types.
|
||||
pub fn languages() -> [&'static dyn Language; 4] {
|
||||
[&c::C, &cxx::Cxx, &asm::Asm, &llvm_ir::LlvmIr]
|
||||
}
|
||||
|
||||
/// Looks up the language implementation that matches the given token by checking each language's
|
||||
/// matches_token method. Returns the first match or None if no language matches.
|
||||
pub fn language_for_token(token: &str) -> Option<&'static dyn Language> {
|
||||
languages().into_iter().find(|l| l.matches_token(token))
|
||||
}
|
||||
@@ -38,6 +46,7 @@ pub fn language_for_token(token: &str) -> Option<&'static dyn Language> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Ensures that input tokens are resolved to the correct language IDs.
|
||||
#[test]
|
||||
fn tokens_resolve_to_languages() {
|
||||
assert_eq!(language_for_token("c").unwrap().id(), "c");
|
||||
@@ -48,6 +57,7 @@ mod tests {
|
||||
assert!(language_for_token("rust").is_none());
|
||||
}
|
||||
|
||||
/// Ensures that each language reports the expected set of file extensions.
|
||||
#[test]
|
||||
fn languages_expose_expected_extensions() {
|
||||
assert_eq!(c::C.extensions(), &["c"]);
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/// Core build module for DCR — a Cargo-like C/C++ project manager.
|
||||
/// This module defines the build engine and re-exports key components for
|
||||
/// building C/C++ projects.
|
||||
#[cfg(feature = "archive")]
|
||||
pub mod archive;
|
||||
pub mod builder;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
|
||||
/// Represents a build request with options for the build process, including profile selection, target specification, force flag, verbosity, workspace, and a cancellation signal.
|
||||
pub struct BuildRequest {
|
||||
pub profile: String,
|
||||
pub target: Option<String>,
|
||||
@@ -30,21 +31,25 @@ pub struct BuildRequest {
|
||||
pub cancel: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
/// Contains the result of a build operation, specifically the duration in seconds.
|
||||
#[allow(dead_code)]
|
||||
pub struct BuildOutcome {
|
||||
pub secs: f64,
|
||||
}
|
||||
|
||||
/// Wraps a string message as a build error.
|
||||
pub struct BuildError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// Creates a BuildError from a String message.
|
||||
impl From<String> for BuildError {
|
||||
fn from(message: String) -> Self {
|
||||
BuildError { message }
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents events that the build engine can emit during compilation and packaging.
|
||||
pub enum BuildEvent<'a> {
|
||||
TargetStart {
|
||||
index: usize,
|
||||
@@ -81,6 +86,8 @@ pub enum BuildEvent<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
/// Trait for handling build events in different reporting backends.
|
||||
pub trait BuildReporter {
|
||||
/// Callback invoked when a build event is generated.
|
||||
fn on_event(&mut self, event: BuildEvent<'_>);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
use crate::core::build_config::Config;
|
||||
use crate::utils::build::{get_config_str, normalize_target_os, profile_table};
|
||||
|
||||
/// Retrieves a raw TOML value from the config using a prioritized lookup order based on target, profile, and base sections.
|
||||
///
|
||||
/// The keys are checked in this order:
|
||||
/// {section}.{target}.{profile}.{field}, {section}.{profile}.{target}.{field}, {section}.{target}.{field}, {section}.{profile}.{field}, {section}.{field}
|
||||
///
|
||||
/// Returns the value if found, otherwise None.
|
||||
pub(crate) fn get_config_value_raw(
|
||||
config: &Config,
|
||||
section: &str,
|
||||
@@ -57,6 +63,9 @@ pub(crate) fn get_config_value_raw(
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks if the 'inherit' flag is enabled for the given section, profile, and target.
|
||||
///
|
||||
/// Returns true by default if the flag is not set.
|
||||
pub(crate) fn get_inherit(
|
||||
config: &Config,
|
||||
section: &str,
|
||||
@@ -68,6 +77,9 @@ pub(crate) fn get_inherit(
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Extracts a trimmed non-empty string value from the config.
|
||||
///
|
||||
/// Returns Some(value) if found, None otherwise.
|
||||
pub(crate) fn get_config_value(
|
||||
config: &Config,
|
||||
section: &str,
|
||||
@@ -80,6 +92,9 @@ pub(crate) fn get_config_value(
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Fetches a string value from the build section, inheriting from base if enabled.
|
||||
///
|
||||
/// Returns the value or default empty string if not inheriting.
|
||||
pub(crate) fn get_string_with_profile_and_target(
|
||||
config: &Config,
|
||||
field: &str,
|
||||
@@ -94,10 +109,12 @@ pub(crate) fn get_string_with_profile_and_target(
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a build string value for the given profile without a target.
|
||||
pub fn get_build_string_with_profile(config: &Config, field: &str, profile: &str) -> String {
|
||||
get_string_with_profile_and_target(config, field, profile, None)
|
||||
}
|
||||
|
||||
/// Gets a string value from the language-specific build.{lang} section.
|
||||
pub(crate) fn get_lang_string(
|
||||
config: &Config,
|
||||
lang: &str,
|
||||
@@ -108,6 +125,7 @@ pub(crate) fn get_lang_string(
|
||||
get_config_value(config, &format!("build.{lang}"), field, profile, target)
|
||||
}
|
||||
|
||||
/// Gets a list of strings from the language-specific build.{lang} section.
|
||||
pub(crate) fn get_lang_list(
|
||||
config: &Config,
|
||||
lang: &str,
|
||||
@@ -121,6 +139,7 @@ pub(crate) fn get_lang_list(
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a TOML array into a Vec<String>, erroring if not an array of strings.
|
||||
pub(crate) fn parse_string_array(value: &toml::Value, key: &str) -> Result<Vec<String>, String> {
|
||||
let arr = value
|
||||
.as_array()
|
||||
@@ -135,6 +154,9 @@ pub(crate) fn parse_string_array(value: &toml::Value, key: &str) -> Result<Vec<S
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Collects a list of strings from config, supporting inheritance from base and profile/target overrides.
|
||||
///
|
||||
/// If inherit is true, starts with the base list and appends custom entries if defined.
|
||||
pub(crate) fn get_list_with_profile_and_target(
|
||||
config: &Config,
|
||||
field: &str,
|
||||
@@ -154,6 +176,7 @@ pub(crate) fn get_list_with_profile_and_target(
|
||||
.get(&format!("build.{field}"))
|
||||
.map(|v| v == &val)
|
||||
.unwrap_or(false);
|
||||
// Skip re-adding the base list when this is the base definition to prevent duplication
|
||||
if !is_base {
|
||||
let custom = parse_string_array(&val, &format!("build.{field}"))?;
|
||||
if inherit {
|
||||
@@ -184,6 +207,7 @@ pub(crate) fn get_list_with_profile_and_target(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Gets the list of strings for the given profile without a target.
|
||||
pub(crate) fn get_list_with_profile(
|
||||
config: &Config,
|
||||
field: &str,
|
||||
@@ -192,10 +216,21 @@ pub(crate) fn get_list_with_profile(
|
||||
get_list_with_profile_and_target(config, field, profile, None)
|
||||
}
|
||||
|
||||
/// Returns the list of targets for the given profile.
|
||||
pub(crate) fn get_targets(config: &Config, profile: &str) -> Result<Vec<String>, String> {
|
||||
get_list_with_profile(config, "targets", profile)
|
||||
}
|
||||
|
||||
/// Parses the config value as a list of strings.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `config`: Project config.
|
||||
/// - `key`: Dot-path key (e.g. `build.sources`).
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok([])` if the key is missing.
|
||||
/// - `Ok(vec)` for an array of strings.
|
||||
/// - `Err` if present but not an array of strings.
|
||||
pub(crate) fn get_config_list(config: &Config, key: &str) -> Result<Vec<String>, String> {
|
||||
let value = match config.get(key) {
|
||||
Some(v) => v,
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(crate) struct BuildStep {
|
||||
cmd: String,
|
||||
}
|
||||
|
||||
/// Holds interpolated version and profile strings for build step substitution.
|
||||
pub(crate) struct StepVars<'a> {
|
||||
pub(crate) profile: &'a str,
|
||||
pub(crate) version: &'a str,
|
||||
@@ -40,6 +41,7 @@ pub(crate) struct StepVars<'a> {
|
||||
pub(crate) version_suffix_dash: &'a str,
|
||||
}
|
||||
|
||||
/// Parses TOML array of build step tables into Vec<BuildStep>.
|
||||
fn get_build_steps_from_value(value: &toml::Value, key: &str) -> Result<Vec<BuildStep>, String> {
|
||||
let arr = value
|
||||
.as_array()
|
||||
@@ -86,6 +88,7 @@ fn get_build_steps_from_value(value: &toml::Value, key: &str) -> Result<Vec<Buil
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Retrieves build steps for a specific profile from config, falling back to default if not present.
|
||||
pub(crate) fn get_build_steps_with_profile(
|
||||
config: &Config,
|
||||
field: &str,
|
||||
@@ -99,6 +102,7 @@ pub(crate) fn get_build_steps_with_profile(
|
||||
get_build_steps(config, &format!("build.{field}"))
|
||||
}
|
||||
|
||||
/// Gets build steps from config for the given key.
|
||||
fn get_build_steps(config: &Config, key: &str) -> Result<Vec<BuildStep>, String> {
|
||||
let value = match config.get(key) {
|
||||
Some(v) => v,
|
||||
@@ -110,6 +114,7 @@ fn get_build_steps(config: &Config, key: &str) -> Result<Vec<BuildStep>, String>
|
||||
use crate::core::build::report::{BuildEvent, BuildReporter};
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
|
||||
/// Runs all build steps in sequence, checking for cancellation and reporting progress.
|
||||
pub(crate) fn run_build_steps(
|
||||
steps: &[BuildStep],
|
||||
tools: &ToolchainExecs,
|
||||
@@ -127,6 +132,7 @@ pub(crate) fn run_build_steps(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes a single build step, handling glob expansion, output paths, and command substitution.
|
||||
fn run_build_step(
|
||||
step: &BuildStep,
|
||||
tools: &ToolchainExecs,
|
||||
@@ -137,6 +143,7 @@ fn run_build_step(
|
||||
) -> Result<(), String> {
|
||||
let input_pattern = expand_step_value(&step.input, "", vars);
|
||||
let inputs = expand_glob(&input_pattern)?;
|
||||
// Skip step if no matching input files found
|
||||
if inputs.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -170,6 +177,7 @@ fn run_build_step(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes files (not directories) matching the given glob patterns.
|
||||
pub(crate) fn clean_generated_files(patterns: &[String]) -> Result<(), String> {
|
||||
for pattern in patterns {
|
||||
for path in expand_glob(pattern)? {
|
||||
@@ -181,6 +189,7 @@ pub(crate) fn clean_generated_files(patterns: &[String]) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Expands glob patterns to find matching files.
|
||||
pub(crate) 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}"))?;
|
||||
@@ -191,6 +200,7 @@ pub(crate) fn expand_glob(pattern: &str) -> Result<Vec<PathBuf>, String> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Verifies that expected artifacts are present by expanding patterns.
|
||||
pub(crate) fn verify_expectations(patterns: &[String], vars: &StepVars) -> Result<(), String> {
|
||||
for pattern in patterns {
|
||||
let expanded = expand_step_value(pattern, "", vars);
|
||||
@@ -202,6 +212,7 @@ pub(crate) fn verify_expectations(patterns: &[String], vars: &StepVars) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// True if the step should run: missing/unreadable output, unreadable input, or input newer than output.
|
||||
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());
|
||||
@@ -212,6 +223,7 @@ fn should_run_step(input: &Path, output: &Path) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Substitutes variables into step command template and replaces placeholders.
|
||||
fn substitute_step_cmd(
|
||||
template: &str,
|
||||
input: &Path,
|
||||
@@ -232,6 +244,7 @@ fn substitute_step_cmd(
|
||||
.replace("{stem}", stem)
|
||||
}
|
||||
|
||||
/// Builds a list of compiler flags from cflags, include dirs, and compiler type.
|
||||
pub(crate) fn build_step_flags(
|
||||
cflags: &[String],
|
||||
include_dirs: &[String],
|
||||
@@ -264,6 +277,7 @@ pub(crate) fn build_step_flags(
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
/// Quotes argument if it contains whitespace or double quotes.
|
||||
fn quote_step_arg(arg: String) -> String {
|
||||
if !arg.chars().any(|c| c.is_whitespace() || c == '"') {
|
||||
return arg;
|
||||
@@ -272,6 +286,7 @@ fn quote_step_arg(arg: String) -> String {
|
||||
format!("\"{escaped}\"")
|
||||
}
|
||||
|
||||
/// Checks if compiler is MSVC based on name.
|
||||
fn is_msvc_compiler(compiler: &str) -> bool {
|
||||
let lower = compiler.to_lowercase();
|
||||
lower.contains("cl.exe")
|
||||
@@ -280,6 +295,7 @@ fn is_msvc_compiler(compiler: &str) -> bool {
|
||||
|| lower.contains("msvc")
|
||||
}
|
||||
|
||||
/// Runs shell command with output capture and cancellation support.
|
||||
fn run_shell_command(
|
||||
cmd: &str,
|
||||
cancel: &Arc<AtomicBool>,
|
||||
@@ -352,6 +368,7 @@ fn run_shell_command(
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Checks if any build step needs to be run by comparing timestamps.
|
||||
pub(crate) fn build_steps_need_run(steps: &[BuildStep], vars: &StepVars) -> Result<bool, String> {
|
||||
for step in steps {
|
||||
let input_pattern = expand_step_value(&step.input, "", vars);
|
||||
@@ -380,6 +397,7 @@ pub(crate) fn build_steps_need_run(steps: &[BuildStep], vars: &StepVars) -> Resu
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Creates VersionInfo from StepVars.
|
||||
fn make_version_info(vars: &StepVars) -> VersionInfo {
|
||||
VersionInfo {
|
||||
full: vars.version.to_string(),
|
||||
@@ -391,6 +409,7 @@ fn make_version_info(vars: &StepVars) -> VersionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands step value by substituting vars and stem.
|
||||
fn expand_step_value(template: &str, stem: &str, vars: &StepVars) -> String {
|
||||
let info = make_version_info(vars);
|
||||
let s = substitute_vars(template, &info, vars.profile, "");
|
||||
|
||||
Reference in New Issue
Block a user