diff --git a/CHANGELOG.md b/CHANGELOG.md index c68e55a..31c19a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.2.5] - 2026-02-26 +Added: +- Incremental builds (object caching by mtime) +- `build.kind` with `staticlib` support +- Custom output directory via `build.target` + ## [0.2.4] - 2026-02-22 Added: - Custom build flags: `build.cflags` and `build.ldflags` diff --git a/README.md b/README.md index d70cfab..9f04939 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,8 @@ Two profiles are supported: Custom flags can be added in `dcr.toml` via `build.cflags` and `build.ldflags`. You can also set `build.target` to override the output directory (profile-independent). +Use `build.kind = "staticlib"` to build a static library instead of a binary. +`dcr run` is only for `build.kind = "bin"` and will fail for `staticlib`. ## Configuration The main project file is `dcr.toml`. @@ -143,6 +145,8 @@ ldflags = ["-lm"] Path dependencies are supported. DCR will resolve them on build and generate `dcr.lock`. +Incremental build note: object files are rebuilt when source `.c/.cpp` files change. Header dependency tracking is not implemented yet. + ## Requirements - Rust toolchain (`rustc`, `cargo`) - for building DCR from source - C compiler (`clang`, `gcc`, or 'cl'(msvc)) diff --git a/TODO.md b/TODO.md index 903f7af..2ea051c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # TODO - Add incremental builds +- Add header dependency tracking for incremental builds (GCC/Clang: -MMD, MSVC: /showIncludes) - Add external package managers (Conan, vcpkg) -- Support building static libraries (are .a) +- Support building static libraries (ar .a) diff --git a/docs/dcr-toml.md b/docs/dcr-toml.md index 47bd088..1592abc 100644 --- a/docs/dcr-toml.md +++ b/docs/dcr-toml.md @@ -13,6 +13,8 @@ version = "0.1.0" language = "c" standard = "c11" compiler = "gcc" +# Build kind: "bin" (default) or "staticlib" +kind = "bin" # Output directory for binary (optional) target = "./dist" # Custom compile/link flags (optional) @@ -33,10 +35,15 @@ frecli = { path = "./lib/frecli", include = ["."], lib = ["."], libs = ["frecli" - `language` (string, required): `c`, `c++`, `cpp`, or `cxx` - `standard` (string, required): e.g. `c99`, `c11`, `c++17` - `compiler` (string, required): `gcc`, `clang`, `cl`(msvc), or `clang-cl` +- `kind` (string, optional): `bin` (default) or `staticlib` - `target` (string, optional): output directory for the binary (profile-independent) - `cflags` (string[], optional): additional compile flags - `ldflags` (string[], optional): additional link flags +Notes: +- `dcr run` is supported only for `kind = "bin"`. +- Incremental rebuild currently tracks source file changes (`.c/.cpp`) only. + ### [dependencies] Only `path` dependencies are supported right now. diff --git a/docs/index.md b/docs/index.md index 7cc66e8..4839e4e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,6 +23,7 @@ You can add custom compile and link flags in `[build]`: [build] compiler = "gcc" standard = "c99" +kind = "bin" target = "./dist" cflags = ["-D_POSIX_C_SOURCE=200809L", "-Wall", "-Wextra", "-O2", "-Ilib/frecli"] ldflags = ["-Llib/frecli", "-lfrecli", "-lm"] @@ -40,3 +41,8 @@ frecli = { path = "./lib/frecli", include = ["."], lib = ["."], libs = ["frecli" ## Source Discovery DCR compiles all `.c`/`.cpp` files under `src/` recursively. The file containing `main()` can be in any subdirectory. + +## Notes + +- `dcr run` works only for `kind = "bin"`. +- Incremental rebuild currently tracks source file changes only (`.c/.cpp`). diff --git a/docs/ru/README_ru.md b/docs/ru/README_ru.md index 425b71e..707b830 100644 --- a/docs/ru/README_ru.md +++ b/docs/ru/README_ru.md @@ -119,6 +119,8 @@ hello/ Кастомные флаги можно добавить через `build.cflags` и `build.ldflags` в `dcr.toml`. Можно указать `build.target`, чтобы задать директорию выхода бинарника (без привязки к профилю). +Используйте `build.kind = "staticlib"`, чтобы собрать статическую библиотеку вместо бинарника. +`dcr run` работает только для `build.kind = "bin"` и завершится ошибкой для `staticlib`. ## Конфигурация Основной файл проекта — `dcr.toml`. @@ -143,6 +145,8 @@ ldflags = ["-lm"] Поддерживаются path-зависимости. DCR резолвит их при сборке и создаёт `dcr.lock`. +Примечание по инкрементальной сборке: пересборка объектников идёт по изменениям `.c/.cpp`. Учет зависимостей заголовков пока не реализован. + ## Требования - Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR - C-компилятор (`clang`, `gcc` или 'cl'(msvc)) diff --git a/src/cli/build.rs b/src/cli/build.rs index 11b43ce..f5e852d 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -31,6 +31,7 @@ pub fn build(args: &[String]) -> i32 { let build_language = get_config_str(&config, "build.language"); let build_standard = get_config_str(&config, "build.standard"); let build_target = get_config_str(&config, "build.target"); + let build_kind = get_config_str(&config, "build.kind"); let build_cflags = match get_config_list(&config, "build.cflags") { Ok(v) => v, Err(msg) => { @@ -76,6 +77,7 @@ pub fn build(args: &[String]) -> i32 { language: &build_language, standard: &build_standard, target_dir: normalize_target(&build_target), + kind: normalize_kind(&build_kind), include_dirs: &resolved.include_dirs, lib_dirs: &resolved.lib_dirs, libs: &resolved.libs, @@ -177,3 +179,8 @@ fn normalize_target(target: &str) -> Option<&str> { Some(trimmed) } } + +fn normalize_kind(kind: &str) -> &str { + let trimmed = kind.trim(); + if trimmed.is_empty() { "bin" } else { trimmed } +} diff --git a/src/cli/run.rs b/src/cli/run.rs index 70f7a1d..739c899 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -1,12 +1,10 @@ use crate::cli::build::build; use crate::config::{PROFILE, flags}; use crate::core::config::Config; -use crate::platform; +use crate::core::runner::run_binary; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_GREEN, colored}; -use std::path::Path; -use std::process::Command; pub fn run(args: &[String]) -> i32 { let mut active_profile = PROFILE.to_string(); @@ -28,6 +26,10 @@ pub fn run(args: &[String]) -> i32 { .get("package.name") .and_then(|v| v.as_str()) .unwrap_or(""); + let build_kind = config + .get("build.kind") + .and_then(|v| v.as_str()) + .unwrap_or(""); let target_dir = config .get("build.target") .and_then(|v| v.as_str()) @@ -49,40 +51,23 @@ pub fn run(args: &[String]) -> i32 { } } + if build_kind.trim() == "staticlib" { + error("Cannot run static library"); + return 1; + } let build_status = build(args); - let bin_path = platform::bin_path(&active_profile, project_name, target_dir); + let bin_path = crate::platform::bin_path(&active_profile, project_name, target_dir); if build_status == 0 { println!("\n {} {}", colored("Running", BOLD_GREEN), bin_path); println!("--------------------------------"); - return Command::new(bin_path) - .status() - .map(|status| status.code().unwrap_or(1)) - .unwrap_or(1); + return run_binary(project_name, &active_profile, target_dir); } - if target_dir.is_none() - && check_dir(Some(&format!("target/{active_profile}"))) - .unwrap_or_default() - .contains(&active_profile) - { - warn("Launch of the latest release"); - return Command::new(platform::bin_path( - &active_profile, - project_name, - target_dir, - )) - .status() - .map(|status| status.code().unwrap_or(1)) - .unwrap_or(1); - } - if target_dir.is_some() && Path::new(&bin_path).exists() { - warn("Launch of the latest release"); - return Command::new(bin_path) - .status() - .map(|status| status.code().unwrap_or(1)) - .unwrap_or(1); + let fallback_code = run_binary(project_name, &active_profile, target_dir); + if fallback_code != 1 { + return fallback_code; } error("Fix errors in the code to run the project"); - 0 + 1 } diff --git a/src/core/builder/clang.rs b/src/core/builder/clang.rs index e38dfb3..30e23b2 100644 --- a/src/core/builder/clang.rs +++ b/src/core/builder/clang.rs @@ -1,6 +1,7 @@ use crate::core::builder::BuildContext; use crate::platform; use std::fs; +use std::path::Path; use std::process::Command; use std::time::Instant; @@ -11,13 +12,30 @@ pub fn build(ctx: &BuildContext) -> Result { ctx.compiler }; let start_time = Instant::now(); - let mut cmd = Command::new(compiler); let sources = collect_sources(ctx.language)?; - for source in &sources { - cmd.arg(source); + let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o")?; + + if ctx.kind == "staticlib" { + let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + let mut cmd = Command::new("ar"); + cmd.arg("rcs").arg(&lib_path); + for obj in &objects { + cmd.arg(obj); + } + match cmd.status() { + Ok(status) if status.success() => { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } + Ok(_) => return Err("Build failed".to_string()), + Err(err) => return Err(format!("Build failed: {err}")), + } } - if !ctx.standard.is_empty() { - cmd.arg(format!("-std={}", ctx.standard)); + + let mut cmd = Command::new(compiler); + for obj in &objects { + cmd.arg(obj); } for flag in default_flags(ctx.profile) { cmd.arg(flag); @@ -25,9 +43,6 @@ pub fn build(ctx: &BuildContext) -> Result { for flag in ctx.cflags { cmd.arg(flag); } - for dir in ctx.include_dirs { - cmd.arg(format!("-I{dir}")); - } for dir in ctx.lib_dirs { cmd.arg(format!("-L{dir}")); } @@ -106,3 +121,63 @@ fn default_flags(profile: &str) -> &'static [&'static str] { _ => &[], } } + +fn build_objects( + compiler: &str, + sources: &[String], + obj_dir: &Path, + ctx: &BuildContext, + obj_ext: &str, +) -> Result, String> { + let mut objects = Vec::new(); + for source in sources { + let obj_path = object_path(obj_dir, source, obj_ext); + if let Some(parent) = Path::new(&obj_path).parent() { + fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?; + } + if needs_rebuild(source, &obj_path) { + let mut cmd = Command::new(compiler); + cmd.arg("-c").arg(source).arg("-o").arg(&obj_path); + if !ctx.standard.is_empty() { + cmd.arg(format!("-std={}", ctx.standard)); + } + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } + for flag in ctx.cflags { + cmd.arg(flag); + } + for dir in ctx.include_dirs { + cmd.arg(format!("-I{dir}")); + } + match cmd.status() { + Ok(status) if status.success() => {} + Ok(_) => return Err("Build failed".to_string()), + Err(err) => return Err(format!("Build failed: {err}")), + } + } + objects.push(obj_path); + } + Ok(objects) +} + +fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String { + let src_path = Path::new(source); + let rel = src_path + .strip_prefix("./src") + .or_else(|_| src_path.strip_prefix("src")) + .unwrap_or(src_path); + let mut out = obj_dir.join(rel); + out.set_extension(obj_ext.trim_start_matches('.')); + out.to_string_lossy().to_string() +} + +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()); + match (src_time, obj_time) { + (Ok(s), Ok(o)) => s > o, + (Ok(_), Err(_)) => true, + _ => true, + } +} diff --git a/src/core/builder/gcc.rs b/src/core/builder/gcc.rs index e38dfb3..30e23b2 100644 --- a/src/core/builder/gcc.rs +++ b/src/core/builder/gcc.rs @@ -1,6 +1,7 @@ use crate::core::builder::BuildContext; use crate::platform; use std::fs; +use std::path::Path; use std::process::Command; use std::time::Instant; @@ -11,13 +12,30 @@ pub fn build(ctx: &BuildContext) -> Result { ctx.compiler }; let start_time = Instant::now(); - let mut cmd = Command::new(compiler); let sources = collect_sources(ctx.language)?; - for source in &sources { - cmd.arg(source); + let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o")?; + + if ctx.kind == "staticlib" { + let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + let mut cmd = Command::new("ar"); + cmd.arg("rcs").arg(&lib_path); + for obj in &objects { + cmd.arg(obj); + } + match cmd.status() { + Ok(status) if status.success() => { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } + Ok(_) => return Err("Build failed".to_string()), + Err(err) => return Err(format!("Build failed: {err}")), + } } - if !ctx.standard.is_empty() { - cmd.arg(format!("-std={}", ctx.standard)); + + let mut cmd = Command::new(compiler); + for obj in &objects { + cmd.arg(obj); } for flag in default_flags(ctx.profile) { cmd.arg(flag); @@ -25,9 +43,6 @@ pub fn build(ctx: &BuildContext) -> Result { for flag in ctx.cflags { cmd.arg(flag); } - for dir in ctx.include_dirs { - cmd.arg(format!("-I{dir}")); - } for dir in ctx.lib_dirs { cmd.arg(format!("-L{dir}")); } @@ -106,3 +121,63 @@ fn default_flags(profile: &str) -> &'static [&'static str] { _ => &[], } } + +fn build_objects( + compiler: &str, + sources: &[String], + obj_dir: &Path, + ctx: &BuildContext, + obj_ext: &str, +) -> Result, String> { + let mut objects = Vec::new(); + for source in sources { + let obj_path = object_path(obj_dir, source, obj_ext); + if let Some(parent) = Path::new(&obj_path).parent() { + fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?; + } + if needs_rebuild(source, &obj_path) { + let mut cmd = Command::new(compiler); + cmd.arg("-c").arg(source).arg("-o").arg(&obj_path); + if !ctx.standard.is_empty() { + cmd.arg(format!("-std={}", ctx.standard)); + } + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } + for flag in ctx.cflags { + cmd.arg(flag); + } + for dir in ctx.include_dirs { + cmd.arg(format!("-I{dir}")); + } + match cmd.status() { + Ok(status) if status.success() => {} + Ok(_) => return Err("Build failed".to_string()), + Err(err) => return Err(format!("Build failed: {err}")), + } + } + objects.push(obj_path); + } + Ok(objects) +} + +fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String { + let src_path = Path::new(source); + let rel = src_path + .strip_prefix("./src") + .or_else(|_| src_path.strip_prefix("src")) + .unwrap_or(src_path); + let mut out = obj_dir.join(rel); + out.set_extension(obj_ext.trim_start_matches('.')); + out.to_string_lossy().to_string() +} + +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()); + match (src_time, obj_time) { + (Ok(s), Ok(o)) => s > o, + (Ok(_), Err(_)) => true, + _ => true, + } +} diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs index 1b03dba..c7ebdf7 100644 --- a/src/core/builder/mod.rs +++ b/src/core/builder/mod.rs @@ -9,6 +9,7 @@ pub struct BuildContext<'a> { pub language: &'a str, pub standard: &'a str, pub target_dir: Option<&'a str>, + pub kind: &'a str, pub include_dirs: &'a [String], pub lib_dirs: &'a [String], pub libs: &'a [String], diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index f6c18ff..0aa1117 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -1,6 +1,7 @@ use crate::core::builder::BuildContext; use crate::platform; use std::fs; +use std::path::Path; use std::process::Command; use std::time::Instant; @@ -11,9 +12,28 @@ pub fn build(ctx: &BuildContext) -> Result { ctx.compiler }; let start_time = Instant::now(); - let mut cmd = Command::new(compiler); let sources = collect_sources(ctx.language)?; + let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let objects = build_objects(compiler, &sources, &obj_dir, ctx, "obj")?; + if ctx.kind == "staticlib" { + let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + let mut cmd = Command::new("lib"); + cmd.arg("/nologo").arg(format!("/OUT:{lib_path}")); + for obj in &objects { + cmd.arg(obj); + } + match cmd.status() { + Ok(status) if status.success() => { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } + Ok(_) => return Err("Build failed".to_string()), + Err(err) => return Err(format!("Build failed: {err}")), + } + } + + let mut cmd = Command::new(compiler); cmd.arg("/nologo"); match ctx.language.to_lowercase().as_str() { "c" => { @@ -32,15 +52,15 @@ pub fn build(ctx: &BuildContext) -> Result { cmd.arg(std_flag); } + for obj in &objects { + cmd.arg(obj); + } for flag in default_flags(ctx.profile) { cmd.arg(flag); } for flag in ctx.cflags { cmd.arg(flag); } - for dir in ctx.include_dirs { - cmd.arg(format!("/I{dir}")); - } for dir in ctx.lib_dirs { cmd.arg(format!("/LIBPATH:{dir}")); } @@ -57,10 +77,6 @@ pub fn build(ctx: &BuildContext) -> Result { cmd.arg(flag); } } - - for source in &sources { - cmd.arg(source); - } cmd.arg(format!( "/Fe:{}", platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) @@ -145,3 +161,76 @@ fn default_flags(profile: &str) -> &'static [&'static str] { _ => &[], } } + +fn build_objects( + compiler: &str, + sources: &[String], + obj_dir: &Path, + ctx: &BuildContext, + obj_ext: &str, +) -> Result, String> { + let mut objects = Vec::new(); + for source in sources { + let obj_path = object_path(obj_dir, source, obj_ext); + if let Some(parent) = Path::new(&obj_path).parent() { + fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?; + } + if needs_rebuild(source, &obj_path) { + let mut cmd = Command::new(compiler); + cmd.arg("/nologo"); + match ctx.language.to_lowercase().as_str() { + "c" => { + cmd.arg("/TC"); + } + "c++" | "cpp" | "cxx" => { + cmd.arg("/TP"); + } + _ => { + return Err("Unsupported language".to_string()); + } + } + if !ctx.standard.is_empty() { + let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?; + cmd.arg(std_flag); + } + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } + for flag in ctx.cflags { + cmd.arg(flag); + } + for dir in ctx.include_dirs { + cmd.arg(format!("/I{dir}")); + } + cmd.arg("/c").arg(source).arg(format!("/Fo:{}", obj_path)); + match cmd.status() { + Ok(status) if status.success() => {} + Ok(_) => return Err("Build failed".to_string()), + Err(err) => return Err(format!("Build failed: {err}")), + } + } + objects.push(obj_path); + } + Ok(objects) +} + +fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String { + let src_path = Path::new(source); + let rel = src_path + .strip_prefix("./src") + .or_else(|_| src_path.strip_prefix("src")) + .unwrap_or(src_path); + let mut out = obj_dir.join(rel); + out.set_extension(obj_ext.trim_start_matches('.')); + out.to_string_lossy().to_string() +} + +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()); + match (src_time, obj_time) { + (Ok(s), Ok(o)) => s > o, + (Ok(_), Err(_)) => true, + _ => true, + } +} diff --git a/src/core/config.rs b/src/core/config.rs index f117b8a..95ded8e 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -7,6 +7,7 @@ const DEFAULT_VERSION: &str = "0.1.0"; const DEFAULT_LANGUAGE: &str = "c"; const DEFAULT_STANDARD: &str = "c11"; const DEFAULT_COMPILER: &str = "clang"; +const DEFAULT_KIND: &str = "bin"; #[derive(Debug)] pub enum ConfigError { @@ -114,12 +115,18 @@ impl Config { .and_then(|v| v.as_table()) .ok_or_else(|| ConfigError::Invalid("missing [build]".into()))?; - for key in ["language", "standard", "compiler"] { + for key in ["language", "standard", "compiler", "kind"] { let value = build.get(key).and_then(|v| v.as_str()).unwrap_or(""); if value.trim().is_empty() { return Err(ConfigError::Invalid(format!("build.{key} is empty"))); } } + if let Some(kind) = build.get("kind").and_then(|v| v.as_str()) { + let kind = kind.trim(); + if kind != "bin" && kind != "staticlib" { + return Err(ConfigError::Invalid("build.kind is invalid".into())); + } + } Ok(()) } @@ -171,6 +178,7 @@ fn default_toml() -> Result { "compiler".to_string(), Value::String(DEFAULT_COMPILER.to_string()), ); + build.insert("kind".to_string(), Value::String(DEFAULT_KIND.to_string())); let mut root = Map::new(); root.insert("package".to_string(), Value::Table(package)); @@ -217,6 +225,11 @@ fn format_toml(value: &Value) -> Result { out.push_str(&format!("language = \"{language}\"\n")); out.push_str(&format!("standard = \"{standard}\"\n")); out.push_str(&format!("compiler = \"{compiler}\"\n")); + let kind = build + .get("kind") + .and_then(|v| v.as_str()) + .unwrap_or(DEFAULT_KIND); + out.push_str(&format!("kind = \"{kind}\"\n")); if let Some(target) = build.get("target").and_then(|v| v.as_str()) && !target.trim().is_empty() { diff --git a/src/core/mod.rs b/src/core/mod.rs index 8c5794b..78f3a00 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,4 @@ pub mod builder; pub mod config; pub mod deps; +pub mod runner; diff --git a/src/core/runner.rs b/src/core/runner.rs new file mode 100644 index 0000000..7a4c253 --- /dev/null +++ b/src/core/runner.rs @@ -0,0 +1,37 @@ +use crate::platform; +use crate::utils::fs::check_dir; +use crate::utils::log::warn; +use std::path::Path; +use std::process::Command; + +pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 { + let bin_path = platform::bin_path(profile, project_name, target_dir); + if Path::new(&bin_path).exists() { + return Command::new(bin_path) + .status() + .map(|status| status.code().unwrap_or(1)) + .unwrap_or(1); + } + + if target_dir.is_none() + && check_dir(Some(&format!("target/{profile}"))) + .unwrap_or_default() + .contains(&profile.to_string()) + { + warn("Launch of the latest release"); + return Command::new(platform::bin_path(profile, project_name, target_dir)) + .status() + .map(|status| status.code().unwrap_or(1)) + .unwrap_or(1); + } + + if target_dir.is_some() && Path::new(&bin_path).exists() { + warn("Launch of the latest release"); + return Command::new(bin_path) + .status() + .map(|status| status.code().unwrap_or(1)) + .unwrap_or(1); + } + + 1 +} diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 439a5ad..250339b 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -4,3 +4,10 @@ pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { None => format!("./target/{profile}/{name}"), } } + +pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/lib{name}.a"), + } +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 439a5ad..250339b 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -4,3 +4,10 @@ pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { None => format!("./target/{profile}/{name}"), } } + +pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/lib{name}.a"), + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index e4b929b..25a1b8a 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -26,3 +26,25 @@ pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { } } } + +pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + #[cfg(target_os = "linux")] + { + linux::lib_path(profile, name, target_dir) + } + #[cfg(target_os = "macos")] + { + return macos::lib_path(profile, name, target_dir); + } + #[cfg(target_os = "windows")] + { + return windows::lib_path(profile, name, target_dir); + } + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] + { + match target_dir { + Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/lib{name}.a"), + } + } +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 5baf780..c7f0945 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -4,3 +4,10 @@ pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { None => format!("./target/{profile}/{name}.exe"), } } + +pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}.lib", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}.lib"), + } +}