mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 22:59:35 +00:00
feat: add native Qt support and dcr lint command; refactor architecture and integration tests
This commit is contained in:
@@ -15,7 +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/>.
|
||||
|
||||
use crate::core::config::Config;
|
||||
use crate::core::build_config::Config;
|
||||
use crate::utils::log::warn;
|
||||
|
||||
pub struct VersionInfo {
|
||||
@@ -32,17 +32,16 @@ pub fn parse_version_info(version: &str) -> VersionInfo {
|
||||
if full.is_empty() {
|
||||
full = "0.0.0".to_string();
|
||||
}
|
||||
let full_clone = full.clone();
|
||||
let (base, suffix) = match full_clone.split_once('-') {
|
||||
Some((head, tail)) => (head, tail),
|
||||
None => (full_clone.as_str(), ""),
|
||||
let (base, suffix) = match full.split_once('-') {
|
||||
Some((head, tail)) => (head.to_string(), tail.to_string()),
|
||||
None => (full.clone(), String::new()),
|
||||
};
|
||||
let mut parts = base.split('.');
|
||||
let major = parts.next().unwrap_or("0").to_string();
|
||||
let minor = parts.next().unwrap_or("0").to_string();
|
||||
let patch = parts.next().unwrap_or("0").to_string();
|
||||
let suffix_dash = if suffix.is_empty() {
|
||||
"".to_string()
|
||||
String::new()
|
||||
} else {
|
||||
format!("-{suffix}")
|
||||
};
|
||||
@@ -51,11 +50,51 @@ pub fn parse_version_info(version: &str) -> VersionInfo {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
suffix: suffix.to_string(),
|
||||
suffix,
|
||||
suffix_dash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substitute_vars(template: &str, info: &VersionInfo, profile: &str, name: &str) -> String {
|
||||
let s = template
|
||||
.replace("{profile}", profile)
|
||||
.replace("{name}", name);
|
||||
substitute_version_vars(&s, info)
|
||||
}
|
||||
|
||||
pub fn substitute_version_vars(template: &str, info: &VersionInfo) -> String {
|
||||
template
|
||||
.replace("{version}", &info.full)
|
||||
.replace("{version_major}", &info.major)
|
||||
.replace("{version_minor}", &info.minor)
|
||||
.replace("{version_patch}", &info.patch)
|
||||
.replace("{version_suffix}", &info.suffix)
|
||||
.replace("{version_suffix_dash}", &info.suffix_dash)
|
||||
}
|
||||
|
||||
pub fn normalize_target(target: &str, profile: &str) -> Option<String> {
|
||||
let trimmed = normalize_target_os(target.trim());
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("target/{trimmed}/{profile}"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_kind(kind: &str) -> &str {
|
||||
let trimmed = kind.trim();
|
||||
if trimmed.is_empty() { "bin" } else { trimmed }
|
||||
}
|
||||
|
||||
pub fn normalize_platform(platform: &str) -> Option<&str> {
|
||||
let trimmed = platform.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_target_os(target: &str) -> &str {
|
||||
match target {
|
||||
"linux" => "x86_64-unknown-linux-gnu",
|
||||
@@ -187,16 +226,40 @@ pub fn get_list_with_profile(config: &Config, field: &str, profile: &str) -> Vec
|
||||
out
|
||||
}
|
||||
|
||||
fn contains_ignore_case(text: &str, pattern: &str) -> bool {
|
||||
if pattern.len() > text.len() {
|
||||
return false;
|
||||
}
|
||||
text.as_bytes()
|
||||
.windows(pattern.len())
|
||||
.any(|w| w.eq_ignore_ascii_case(pattern.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn is_bare_metal_target(target: Option<&str>) -> bool {
|
||||
if let Some(t) = target {
|
||||
let lower = t.to_lowercase();
|
||||
lower.contains("none")
|
||||
|| lower.contains("-elf")
|
||||
|| lower.contains("eabi")
|
||||
|| lower.contains("baremetal")
|
||||
|| lower.contains("bare-metal")
|
||||
} else {
|
||||
false
|
||||
match target {
|
||||
Some(t) => {
|
||||
contains_ignore_case(t, "none")
|
||||
|| contains_ignore_case(t, "-elf")
|
||||
|| contains_ignore_case(t, "eabi")
|
||||
|| contains_ignore_case(t, "baremetal")
|
||||
|| contains_ignore_case(t, "bare-metal")
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_profile_flags(profile: &str) -> &'static [&'static str] {
|
||||
match profile {
|
||||
"release" => &["-O3", "-DNDEBUG"],
|
||||
"debug" => &[
|
||||
"-O0",
|
||||
"-g",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-fno-omit-frame-pointer",
|
||||
"-DDCR_DEBUG",
|
||||
],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +439,7 @@ pub fn resolve_pkg_config_flags_lossy(
|
||||
}
|
||||
}
|
||||
|
||||
fn run_pkg_config(pkg: &str, arg: &str) -> Result<String, String> {
|
||||
pub fn run_pkg_config(pkg: &str, arg: &str) -> Result<String, String> {
|
||||
let output = std::process::Command::new("pkg-config")
|
||||
.arg(arg)
|
||||
.arg(pkg)
|
||||
@@ -505,4 +568,52 @@ mod tests {
|
||||
assert_eq!(info.suffix, "beta");
|
||||
assert_eq!(info.suffix_dash, "-beta");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_target_with_profile() {
|
||||
assert_eq!(
|
||||
normalize_target("linux", "debug"),
|
||||
Some("target/x86_64-unknown-linux-gnu/debug".into())
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_target("x86_64-unknown-linux-gnu", "release"),
|
||||
Some("target/x86_64-unknown-linux-gnu/release".into())
|
||||
);
|
||||
assert_eq!(normalize_target("", "debug"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_kind_empty_defaults_to_bin() {
|
||||
assert_eq!(normalize_kind(""), "bin");
|
||||
assert_eq!(normalize_kind(" "), "bin");
|
||||
assert_eq!(normalize_kind("staticlib"), "staticlib");
|
||||
assert_eq!(normalize_kind(" elf "), "elf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_platform_empty_is_none() {
|
||||
assert_eq!(normalize_platform(""), None);
|
||||
assert_eq!(normalize_platform(" "), None);
|
||||
assert_eq!(normalize_platform("native"), Some("native"));
|
||||
assert_eq!(normalize_platform(" efi "), Some("efi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_profile_flags_by_profile() {
|
||||
assert!(default_profile_flags("debug").contains(&"-O0"));
|
||||
assert!(default_profile_flags("debug").contains(&"-g"));
|
||||
assert!(default_profile_flags("release").contains(&"-O3"));
|
||||
assert!(default_profile_flags("release").contains(&"-DNDEBUG"));
|
||||
assert!(default_profile_flags("unknown").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_bare_metal_detects_embedded_targets() {
|
||||
assert!(is_bare_metal_target(Some("aarch64-none-elf")));
|
||||
assert!(is_bare_metal_target(Some("i686-elf")));
|
||||
assert!(is_bare_metal_target(Some("armv7e-m-none-eabi")));
|
||||
assert!(is_bare_metal_target(Some("riscv32-unknown-none-elf")));
|
||||
assert!(!is_bare_metal_target(Some("x86_64-unknown-linux-gnu")));
|
||||
assert!(!is_bare_metal_target(None));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,24 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn home_dir() -> Option<PathBuf> {
|
||||
if let Ok(home) = std::env::var("HOME") {
|
||||
return Some(PathBuf::from(home));
|
||||
}
|
||||
if let Ok(profile) = std::env::var("USERPROFILE") {
|
||||
return Some(PathBuf::from(profile));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn to_hex(bytes: &[u8]) -> String {
|
||||
let mut out = String::with_capacity(bytes.len() * 2);
|
||||
for b in bytes {
|
||||
out.push_str(&format!("{:02x}", b));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn check_dir(dir: Option<&str>) -> io::Result<Vec<String>> {
|
||||
let path: PathBuf = match dir {
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
// 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/>.
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
// checking installed git on system
|
||||
pub fn is_git_available() -> bool {
|
||||
Command::new("git")
|
||||
.arg("--version")
|
||||
@@ -27,17 +9,6 @@ pub fn is_git_available() -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
// warning of git not in system
|
||||
#[allow(dead_code)]
|
||||
pub fn warn_if_git_missing() {
|
||||
if !is_git_available() {
|
||||
crate::utils::log::warn(
|
||||
"Git is not installed or not found in PATH. Some features (like dependency cloning and VCS initialization) may fail.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// init repo in folder
|
||||
pub fn git_init(path: &Path) -> Result<(), String> {
|
||||
if !is_git_available() {
|
||||
return Err("git executable not found in PATH".to_string());
|
||||
@@ -55,58 +26,3 @@ pub fn git_init(path: &Path) -> Result<(), String> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Clone the dependency and switch to the branch/tag/commit
|
||||
#[allow(dead_code)]
|
||||
pub fn git_clone_and_checkout(
|
||||
url: &str,
|
||||
target_dir: &Path,
|
||||
branch: Option<&str>,
|
||||
tag: Option<&str>,
|
||||
rev: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
if !is_git_available() {
|
||||
return Err("git executable not found in PATH".to_string());
|
||||
}
|
||||
|
||||
let mut args = vec!["clone"];
|
||||
|
||||
if let Some(b) = branch {
|
||||
args.push("--branch");
|
||||
args.push(b);
|
||||
args.push("--single-branch");
|
||||
} else if let Some(t) = tag {
|
||||
args.push("--branch");
|
||||
args.push(t);
|
||||
args.push("--single-branch");
|
||||
}
|
||||
|
||||
args.push(url);
|
||||
|
||||
let target_dir_str = target_dir.to_string_lossy().to_string();
|
||||
args.push(&target_dir_str);
|
||||
|
||||
let status = Command::new("git")
|
||||
.args(&args)
|
||||
.status()
|
||||
.map_err(|e| format!("failed to execute git clone: {}", e))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(format!("git clone of {} failed", url));
|
||||
}
|
||||
|
||||
if let Some(r) = rev {
|
||||
let checkout_status = Command::new("git")
|
||||
.arg("checkout")
|
||||
.arg(r)
|
||||
.current_dir(target_dir)
|
||||
.status()
|
||||
.map_err(|e| format!("failed to execute git checkout: {}", e))?;
|
||||
|
||||
if !checkout_status.success() {
|
||||
return Err(format!("git checkout to revision {} failed", r));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user