dcr 0.8.4

DCR is a utility for managing C/C++ projects in a Cargo-like style.
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

pub mod artifact;
pub mod cc_common;
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,
    pub compiler: &'a str,
    pub language: &'a str,
    pub standard: &'a str,
    pub cxx_standard: &'a str,
    pub target: Option<&'a str>,
    pub target_dir: Option<&'a str>,
    pub kind: &'a str,
    pub platform: Option<&'a str>,
    pub linker: Option<&'a str>,
    pub archiver: Option<&'a str>,
    pub moc: Option<&'a str>,
    pub uic: Option<&'a str>,
    pub rcc: Option<&'a str>,
    pub package_type: Option<&'a str>,
    pub freestanding: bool,
    pub panic_abort: bool,
    pub codegen_units: usize,
    pub source_roots: &'a [std::path::PathBuf],
    pub exclude_dirs: &'a [std::path::PathBuf],
    pub include_paths: &'a [String],
    pub include_dirs: &'a [String],
    pub lib_dirs: &'a [String],
    pub libs: &'a [String],
    pub cflags: &'a [String],
    pub ldflags: &'a [String],
    pub output_filename: Option<&'a str>,
    pub output_extension: Option<&'a str>,
    pub verbose: bool,
    pub qt: bool,
}

/// Trait defining the build interface for different compilers and languages.
pub trait Builder {
    #[allow(dead_code)]
    fn id(&self) -> &'static str;
    fn build(&self, ctx: &BuildContext) -> Result<f64, String>;
    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 {
    fn id(&self) -> &'static str {
        "cc"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        cc_common::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        cc_common::collect_sources(ctx)
    }
}

impl Builder for Msvc {
    fn id(&self) -> &'static str {
        "msvc"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        msvc::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        msvc::collect_sources(ctx)
    }
}

impl Builder for Nasm {
    fn id(&self) -> &'static str {
        "nasm"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        nasm::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        nasm::collect_sources(ctx)
    }
}

impl Builder for Gas {
    fn id(&self) -> &'static str {
        "gas"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        gas::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        gas::collect_sources(ctx)
    }
}

impl Builder for Masm {
    fn id(&self) -> &'static str {
        "masm"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        masm::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        masm::collect_sources(ctx)
    }
}

impl Builder for Fasm {
    fn id(&self) -> &'static str {
        "fasm"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        fasm::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        fasm::collect_sources(ctx)
    }
}

impl Builder for Llc {
    fn id(&self) -> &'static str {
        "llvm_ir"
    }
    fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
        llvm_ir::build(ctx)
    }
    fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
        llvm_ir::collect_sources(ctx)
    }
}

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;
    }
    if c == "as" || c.contains("gas") {
        return &Gas;
    }
    if c.contains("llc") {
        return &Llc;
    }
    if c.contains("fasm") {
        return &Fasm;
    }
    if c.contains("nasm") {
        return &Nasm;
    }
    if c == "ml" || c == "ml64" || c.contains("masm") {
        return &Masm;
    }
    if c == "cl" || c.contains("msvc") {
        return &Msvc;
    }
    &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!(
            "Compiler not found: {}. Make sure it is installed and available in PATH.",
            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"
    } else {
        match compiler.to_lowercase().as_str() {
            "gas" | "gnu-as" => "as",
            "nasm" => "nasm",
            "fasm" | "fasm64" => "fasm",
            "masm" | "ml" | "ml64" => "ml",
            _ => compiler,
        }
    };
    std::process::Command::new(name)
        .arg("--version")
        .output()
        .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::*;

    #[test]
    fn builder_resolves_by_compiler_name() {
        assert_eq!(builder_for("gcc").id(), "cc");
        assert_eq!(builder_for("clang").id(), "cc");
        assert_eq!(builder_for("clang++").id(), "cc");
        assert_eq!(builder_for("").id(), "cc");
        assert_eq!(builder_for("nasm").id(), "nasm");
        assert_eq!(builder_for("as").id(), "gas");
        assert_eq!(builder_for("llc-18").id(), "llvm_ir");
        assert_eq!(builder_for("fasm").id(), "fasm");
        assert_eq!(builder_for("ml64").id(), "masm");
        assert_eq!(builder_for("cl").id(), "msvc");
        assert_eq!(builder_for("clang-cl").id(), "msvc");
    }
}