From b2854db91b505de384260b23e4124088358c880c Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 18 May 2026 16:31:29 +0800 Subject: [PATCH 01/12] refactor: simplify Linux cross-compilation setup and install cargo-zigbuild --- .github/workflows/release.yml | 37 ++++++++--------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a36ebdf..a060ddf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,27 +112,11 @@ jobs: name: release-notes path: .github - - name: Install apt packages - if: runner.os == 'Linux' && matrix.apt != '' + - name: Install cargo-zigbuild (Linux cross-compilation) + if: runner.os == 'Linux' run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu musl-tools - case "${{ matrix.target }}" in - aarch64-unknown-linux-gnu) - echo "CC_aarch64-unknown-linux-gnu=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - echo "AR_aarch64-unknown-linux-gnu=aarch64-linux-gnu-ar" >> $GITHUB_ENV - ;; - x86_64-unknown-linux-musl) - echo "CC_x86_64-unknown-linux-musl=x86_64-linux-musl-gcc" >> $GITHUB_ENV - ;; - aarch64-unknown-linux-musl) - curl -L https://musl.cc/aarch64-linux-musl-cross.tgz -o /tmp/musl-aarch64.tgz - sudo tar xzf /tmp/musl-aarch64.tgz -C /opt - echo "/opt/aarch64-linux-musl-cross/bin" >> $GITHUB_PATH - echo "CC_aarch64-unknown-linux-musl=aarch64-linux-musl-gcc" >> $GITHUB_ENV - echo "AR_aarch64-unknown-linux-musl=aarch64-linux-musl-ar" >> $GITHUB_ENV - ;; - esac + pip install cargo-zigbuild + echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Setup LLVM + MinGW (gnullvm) if: contains(matrix.target, 'gnullvm') @@ -148,17 +132,12 @@ jobs: - name: Build shell: bash - env: - MATRIX_ENV_CC: ${{ matrix.env_cc }} - MATRIX_ENV_LINKER: ${{ matrix.env_linker }} run: | - if [[ -n "${MATRIX_ENV_CC:-}" ]]; then - export "${MATRIX_ENV_CC?}" + if [ "${{ runner.os }}" = "Linux" ] && [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then + cargo zigbuild --release --target ${{ matrix.target }} + else + cargo build --release --target ${{ matrix.target }} fi - if [[ -n "${MATRIX_ENV_LINKER:-}" ]]; then - export "${MATRIX_ENV_LINKER?}" - fi - cargo build --release --target ${{ matrix.target }} - name: Derive version string shell: bash From 6b9e731c7b0bbcd34a6bf9e6479132e2040cfe1f Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 18 May 2026 16:41:01 +0800 Subject: [PATCH 02/12] fix: update git2 dependency to disable default features and enable https --- Cargo.lock | 15 --------------- Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf48b1c..a10a6e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -624,26 +624,11 @@ checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" dependencies = [ "cc", "libc", - "libssh2-sys", "libz-sys", "openssl-sys", "pkg-config", ] -[[package]] -name = "libssh2-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - [[package]] name = "libz-sys" version = "1.1.28" diff --git a/Cargo.toml b/Cargo.toml index 4aeb799..acf8560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,6 @@ serde = { version = "1", features = ["derive"] } self-replace = "1" sha2 = "0.10" toml = "1.0.3" -git2 = "0.20.4" +git2 = { version = "0.20.4", default-features = false, features = ["https"] } serde_json = "1" ctrlc = "3" From 527a7894105b869727965bbe7b268210c1eea7b2 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 18 May 2026 16:46:11 +0800 Subject: [PATCH 03/12] fix: update git2 dependency to use vendored-openssl --- Cargo.lock | 25 +++++++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a10a6e4..d52b41c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -624,11 +624,26 @@ checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" dependencies = [ "cc", "libc", + "libssh2-sys", "libz-sys", "openssl-sys", "pkg-config", ] +[[package]] +name = "libssh2-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.28" @@ -721,6 +736,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-src" +version = "300.5.4+3.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.115" @@ -729,6 +753,7 @@ checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/Cargo.toml b/Cargo.toml index acf8560..817267a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,6 @@ serde = { version = "1", features = ["derive"] } self-replace = "1" sha2 = "0.10" toml = "1.0.3" -git2 = { version = "0.20.4", default-features = false, features = ["https"] } +git2 = { version = "0.20.4", features = ["vendored-openssl"] } serde_json = "1" ctrlc = "3" From c4a49cb345499965c277678fbaf82eb7557a7ce6 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 18 May 2026 16:58:44 +0800 Subject: [PATCH 04/12] feat: add openssl dependency and update git2 configuration --- Cargo.lock | 41 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d52b41c..0366d4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,7 @@ dependencies = [ "ctrlc", "git2", "glob", + "openssl", "reqwest", "self-replace", "serde", @@ -192,6 +193,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -730,6 +746,31 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl" +version = "0.10.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 817267a..2e113bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ serde = { version = "1", features = ["derive"] } self-replace = "1" sha2 = "0.10" toml = "1.0.3" -git2 = { version = "0.20.4", features = ["vendored-openssl"] } +git2 = "0.20.4" serde_json = "1" ctrlc = "3" +openssl = { version = "0.10", features = ["vendored"] } \ No newline at end of file From 9ad968374e8cced1b032d13bd1e0e20906b0fb7d Mon Sep 17 00:00:00 2001 From: Dexoron Date: Thu, 21 May 2026 00:02:24 +0800 Subject: [PATCH 05/12] fix: target system add: new field in dcr.toml - filename, extension --- src/cli/build.rs | 43 ++++++++++++++++++++++++++++++++----- src/cli/gen.rs | 2 ++ src/core/builder/gas.rs | 8 +++++-- src/core/builder/mod.rs | 2 ++ src/core/builder/nasm.rs | 8 +++++-- src/core/builder/unix_cc.rs | 8 +++++-- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/cli/build.rs b/src/cli/build.rs index 4b47552..6039c99 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -50,7 +50,15 @@ pub fn build(args: &[String]) -> i32 { Err(code) => return code, }; - // If no target specified, use default host target for target-specific config + if flags.target.is_none() { + let config_path = root.join("dcr.toml"); + if let Ok(config) = Config::open(config_path.to_str().unwrap()) { + let bt = get_build_string_with_profile(&config, "target", "debug"); + if !bt.is_empty() { + flags.target = Some(bt); + } + } + } if flags.target.is_none() { let default_target = if cfg!(target_os = "linux") { "x86_64-unknown-linux-gnu" @@ -490,10 +498,23 @@ fn build_project_at( .or_else(|| get_config_opt(&config, "toolchain.moc")); let tc_rcc = get_config_value(&config, "toolchain", "rcc", profile, build_target) .or_else(|| get_config_opt(&config, "toolchain.rcc")); - let build_cflags = + let mut build_cflags = get_list_with_profile_and_target(&config, "cflags", profile, build_target)?; + // Автоматически добавляем --target=..., если задан build.target и флаг ещё не указан + if let Some(t) = build_target { + if !t.trim().is_empty() { + let target_flag = format!("--target={}", t.trim()); + if !build_cflags.iter().any(|f| f == &target_flag || f.starts_with("--target=")) { + build_cflags.insert(0, target_flag); + } + } + } let build_ldflags = get_list_with_profile_and_target(&config, "ldflags", profile, build_target)?; + + // Новые поля: filename и extension + let output_filename = get_string_with_profile_and_target(&config, "filename", profile, build_target); + let output_extension = get_string_with_profile_and_target(&config, "extension", profile, build_target); let build_excludes = get_list_with_profile_and_target(&config, "exclude", profile, build_target)?; let build_includes = @@ -672,6 +693,8 @@ fn build_project_at( libs: &resolved.libs, cflags: &resolved_cflags, ldflags: &resolved_ldflags, + output_filename: if output_filename.is_empty() { None } else { Some(output_filename.as_str()) }, + output_extension: if output_extension.is_empty() { None } else { Some(output_extension.as_str()) }, }; if std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] debug: compiler={}", ctx.compiler); @@ -1265,13 +1288,23 @@ fn build_cache_path(profile: &str, target_dir: Option<&str>) -> std::path::PathB } fn build_output_path(ctx: &BuildContext) -> String { + let name = ctx.output_filename.unwrap_or(ctx.project_name); + let ext = ctx.output_extension.unwrap_or(""); + + let final_name = if ext.is_empty() { + name.to_string() + } else { + format!("{}.{}", name, ext) + }; + if ctx.kind == "staticlib" { - return crate::platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + // Для библиотек добавляем lib префикс если нужно, но упростим + return crate::platform::lib_path(ctx.profile, &final_name, ctx.target_dir); } if ctx.kind == "sharedlib" { - return crate::platform::shared_lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + return crate::platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir); } - crate::platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) + crate::platform::bin_path(ctx.profile, &final_name, ctx.target_dir) } fn collect_header_files( diff --git a/src/cli/gen.rs b/src/cli/gen.rs index 2f6bc77..e1c70b7 100644 --- a/src/cli/gen.rs +++ b/src/cli/gen.rs @@ -337,6 +337,8 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result Result { for flag in ctx.ldflags { cmd.arg(flag); } + let name = ctx.output_filename.unwrap_or(ctx.project_name); + let ext = ctx.output_extension.unwrap_or(""); + let final_name = if ext.is_empty() { name.to_string() } else { format!("{}.{}", name, ext) }; + let out_path = if ctx.kind == "sharedlib" { - platform::shared_lib_path(ctx.profile, ctx.project_name, ctx.target_dir) + platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) } else { - platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) + platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; cmd.arg("-o").arg(out_path); diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs index d4c9f2e..26d015b 100644 --- a/src/core/builder/mod.rs +++ b/src/core/builder/mod.rs @@ -26,6 +26,8 @@ pub struct BuildContext<'a> { 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 fn build(ctx: &BuildContext) -> Result { diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs index 96460de..0a4a43c 100644 --- a/src/core/builder/nasm.rs +++ b/src/core/builder/nasm.rs @@ -70,10 +70,14 @@ pub fn build(ctx: &BuildContext) -> Result { for flag in ctx.ldflags { cmd.arg(flag); } + let name = ctx.output_filename.unwrap_or(ctx.project_name); + let ext = ctx.output_extension.unwrap_or(""); + let final_name = if ext.is_empty() { name.to_string() } else { format!("{}.{}", name, ext) }; + let out_path = if ctx.kind == "sharedlib" { - platform::shared_lib_path(ctx.profile, ctx.project_name, ctx.target_dir) + platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) } else { - platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) + platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; cmd.arg("-o").arg(out_path); diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index e7f48fa..a3cdb6f 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -63,10 +63,14 @@ pub fn build(ctx: &BuildContext) -> Result { for flag in ctx.ldflags { cmd.arg(flag); } + let name = ctx.output_filename.unwrap_or(ctx.project_name); + let ext = ctx.output_extension.unwrap_or(""); + let final_name = if ext.is_empty() { name.to_string() } else { format!("{}.{}", name, ext) }; + let out_path = if ctx.kind == "sharedlib" { - platform::shared_lib_path(ctx.profile, ctx.project_name, ctx.target_dir) + platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) } else { - platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) + platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; cmd.arg("-o").arg(out_path); From ef2fb1e8e402701b6d265d33a2f37d286f311d4b Mon Sep 17 00:00:00 2001 From: Dexoron Date: Fri, 22 May 2026 14:37:45 +0800 Subject: [PATCH 06/12] feat: update version to 0.6.7, enhance build configuration, and add dcr tree command --- .gitignore | 4 +++- CHANGELOG.md | 24 +++++++++++++++++-- Cargo.lock | 2 +- Cargo.toml | 2 +- docs/SUMMARY.md | 1 + docs/commands/dcr-build.md | 4 ++++ docs/commands/dcr-tree.md | 22 +++++++++++++++++ docs/configuration/build-section.md | 5 ++++ .../cross-compilation.md | 2 ++ docs/reference-and-help/cli-reference.md | 6 ++++- docs/reference-and-help/config-reference.md | 2 ++ src/core/builder/unix_cc.rs | 16 ++++++++++++- 12 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 docs/commands/dcr-tree.md diff --git a/.gitignore b/.gitignore index be6d93f..cefd271 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ target/ sandbox/** .idea/ .vscode/ -.env \ No newline at end of file +.env + +personal/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a0072..8eebbea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.6.7] - 2026-05-21 + +Added: + +- `build.filename` and `build.extension` in `dcr.toml` — complete control over the final artifact name without relying on `package.name`. + Example: + ```toml + [build] + filename = "KERNEL" + extension = "EFI" + ``` + Produces `KERNEL.EFI` (works for `bin`, `staticlib`, and `sharedlib`). +- Automatic injection of `--target=` into compiler flags when `build.target` is set in `dcr.toml`. This greatly simplifies clang-based cross-compilation (especially for bare-metal targets like `aarch64-none-elf`). +- Bare-metal targets (containing `none`, `-elf`, `eabi`, `baremetal`) no longer receive DCR's internal default flags (`-g`, `-Wall`, `-Wextra`, `-fno-omit-frame-pointer`, `-DDCR_DEBUG`, etc.). Prevents unwanted sections (`.comment`, debug info, etc.) that break custom linker scripts when `inherit = true`. + +Fixed: + +- `build.target` declared in `dcr.toml` is now correctly used as the default target even when `--target` is not passed on the command line (previously the host target was always forced for config resolution). + ## [0.6.6] - 2026-05-18 Fixed: @@ -29,8 +48,9 @@ between `cli/build.rs`, `cli/run.rs`, `cli/clean.rs`, `cli/gen.rs` `package_root_from_registry_info`, `registry_include_dir`, `registry_lib_dir`, `path_from_string_dep` - Unit tests for `register.rs` (`is_registry_dep`, `package_root_from_registry_info`), -`deps/mod.rs` (`path_dep_path`, `push_default_lib_dirs`), -`utils/build.rs` (`normalize_target_os`, `parse_version_info`) + `deps/mod.rs` (`path_dep_path`, `push_default_lib_dirs`), + `utils/build.rs` (`normalize_target_os`, `parse_version_info`) +- `dcr tree` command — visual dependency tree viewer (similar to `cargo tree`) Removed: diff --git a/Cargo.lock b/Cargo.lock index 0366d4f..dceb3b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,7 +112,7 @@ dependencies = [ [[package]] name = "dcr" -version = "0.6.6" +version = "0.6.7" dependencies = [ "ctrlc", "git2", diff --git a/Cargo.toml b/Cargo.toml index 2e113bf..4f705cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.6.6" +version = "0.6.7" edition = "2024" build = "build.rs" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index b5fdc8e..91abe29 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -27,6 +27,7 @@ * [dcr clean](commands/dcr-clean.md) * [dcr gen](commands/dcr-gen.md) * [dcr test](commands/dcr-test.md) +* [dcr tree](commands/dcr-tree.md) ## Dependencies & Build diff --git a/docs/commands/dcr-build.md b/docs/commands/dcr-build.md index 01e64a9..dd08931 100644 --- a/docs/commands/dcr-build.md +++ b/docs/commands/dcr-build.md @@ -25,6 +25,8 @@ dcr build --debug --clean ## Config values used - `package.name` +- `build.filename` +- `build.extension` - `build.compiler` - `build.language` - `build.standard` @@ -60,7 +62,9 @@ dcr build --debug --clean - Incremental rebuild checks source/object mtime and tracked header dependencies. - `--force` skips build cache checks and recompiles. - `--clean` removes `target/` and `build.clean` paths before building. +- `build.filename` + `build.extension` allow full control over the final artifact name (e.g. `KERNEL.BIN` instead of using `package.name`). - Default GCC/Clang profile flags: `debug` -> `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`, `release` -> `-O3 -DNDEBUG`. + These defaults are **not applied** for bare-metal targets (e.g. `aarch64-none-elf`, targets containing `none` / `-elf` / `eabi`). - For `language = "asm"` with `compiler = "as"`/`"gas"`, use `.s` files (no preprocessing). For `.S`, use `gcc` or `clang`. - In workspace root, `dcr build` builds all members in dependency order. - `build.exclude` removes paths from source/header collection; `build.include` re-allows matching paths and has priority over `exclude`. diff --git a/docs/commands/dcr-tree.md b/docs/commands/dcr-tree.md new file mode 100644 index 0000000..066e3b6 --- /dev/null +++ b/docs/commands/dcr-tree.md @@ -0,0 +1,22 @@ +# dcr tree + +Displays a visual dependency tree for the current project (similar to `cargo tree`). + +## Usage + +```sh +dcr tree +dcr tree --invert +dcr tree -p some-package +``` + +## What it shows + +- All direct and transitive dependencies +- Version of each dependency +- Whether a dependency is a registry, git, or path dependency + +## Notes + +- Useful for debugging dependency issues and understanding the build graph. +- Output format is inspired by `cargo tree`. diff --git a/docs/configuration/build-section.md b/docs/configuration/build-section.md index bcedd6d..ac59089 100644 --- a/docs/configuration/build-section.md +++ b/docs/configuration/build-section.md @@ -8,6 +8,8 @@ language = "c" standard = "c11" compiler = "clang" kind = "bin" +# filename = "KERNEL" +# extension = "BIN" # optional: # inherit = true # Inherit from base (default true) # targets = ["linux", "macos"] # Multiple targets to build @@ -32,9 +34,12 @@ kind = "bin" - `standard` (string, required): language standard passed to compiler. - `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`). - `kind` (string, required): `bin`, `staticlib`, or `sharedlib`. +- `filename` (string, optional): custom output filename (without extension). Overrides `package.name` for the final artifact. +- `extension` (string, optional): custom file extension without the leading dot (e.g. `BIN`, `efi`, `EFI`). Combined with `filename`. - `inherit` (bool, optional): inherit settings from base `[build]` (default `true`). If `false`, only use target/profile specific settings. - `targets` (string array, optional): list of targets to build simultaneously. Supports short names (`linux`, `macos`, `windows`). - `target` (string, optional): custom output directory for final artifact. + When set, DCR automatically injects `--target=` into `cflags` (useful for clang cross-compilation). - `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`). - `cflags` (string array, optional): extra compile flags. - `ldflags` (string array, optional): extra link flags. diff --git a/docs/dependencies-and-build/cross-compilation.md b/docs/dependencies-and-build/cross-compilation.md index c4c4898..a3cf9f0 100644 --- a/docs/dependencies-and-build/cross-compilation.md +++ b/docs/dependencies-and-build/cross-compilation.md @@ -178,6 +178,8 @@ compiler = "aarch64-linux-gnu-gcc"' >> dcr.toml dcr build --target aarch64-linux-gnu --release ``` +**Note:** If you set `target = "aarch64-linux-gnu"` (or any value) directly under `[build]`, DCR will automatically add `--target=...` to cflags when using clang. You usually don't need to repeat it manually. + ### Windows Build on Linux ```bash diff --git a/docs/reference-and-help/cli-reference.md b/docs/reference-and-help/cli-reference.md index 231e420..5aab80e 100644 --- a/docs/reference-and-help/cli-reference.md +++ b/docs/reference-and-help/cli-reference.md @@ -7,9 +7,11 @@ dcr new dcr init dcr setup dcr add (optional) -dcr build [--debug|--release] +dcr build [--debug|--release] [--target ] dcr run [--debug|--release] dcr clean [--debug|--release] [--all] +dcr tree +dcr test dcr gen dcr --help dcr --version @@ -23,6 +25,8 @@ dcr --update - `init` and `--update` do not accept extra arguments. - `clean` accepts `--debug|--release` and optional `--all`. - `gen` supports `project-info`, `compile-commands`, `vscode`, `clion`. +- `tree` shows a dependency tree (similar to `cargo tree`). +- `test` / `tests` runs the project's tests. ## Exit behavior overview diff --git a/docs/reference-and-help/config-reference.md b/docs/reference-and-help/config-reference.md index 9b03619..b62110e 100644 --- a/docs/reference-and-help/config-reference.md +++ b/docs/reference-and-help/config-reference.md @@ -12,6 +12,8 @@ language = "c|c++|cpp|cxx|asm" # required (string or array) standard = "string" # required compiler = "string" # required kind = "bin|staticlib|sharedlib" # required +filename = "string" # optional (custom output name without extension) +extension = "string" # optional (custom extension without dot, e.g. "BIN") target = "string" # optional platform = "string" # optional cflags = ["string", "..."] # optional diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index a3cdb6f..ddd022c 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -129,6 +129,19 @@ fn default_flags(profile: &str) -> &'static [&'static str] { } } +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 + } +} + fn build_objects( compiler: &str, sources: &[String], @@ -183,7 +196,8 @@ fn build_object( cmd.arg(format!("-std={}", ctx.standard)); } - if ctx.cflags.is_empty() { + let use_dcr_defaults = ctx.cflags.is_empty() && !is_bare_metal_target(ctx.target); + if use_dcr_defaults { for flag in default_flags(ctx.profile) { cmd.arg(flag); } From adfa24f8f4a1e528a4ac27d4a364f5b01b0fbb3c Mon Sep 17 00:00:00 2001 From: Dexoron Date: Fri, 22 May 2026 15:14:56 +0800 Subject: [PATCH 07/12] feat: add AGENTS.md for quick start and CI guidelines; update documentation for dcr add and dcr build commands --- AGENT.md | 77 --------------------- AGENTS.md | 56 +++++++++++++++ CHANGELOG.md | 3 + docs/commands/dcr-add.md | 7 +- docs/commands/dcr-build.md | 2 + docs/configuration/build-section.md | 1 + docs/reference-and-help/cli-reference.md | 2 +- docs/reference-and-help/config-reference.md | 1 + src/cli/add.rs | 39 ++++++++++- src/cli/build.rs | 70 ++++++++++++++----- src/cli/flags.rs | 7 ++ src/cli/gen.rs | 1 + src/core/builder/gas.rs | 10 ++- src/core/builder/mod.rs | 1 + src/core/builder/msvc.rs | 4 +- src/core/builder/nasm.rs | 12 ++-- src/core/builder/unix_cc.rs | 12 ++-- src/core/deps/register.rs | 23 ++++-- src/core/registry.rs | 15 +++- 19 files changed, 225 insertions(+), 118 deletions(-) delete mode 100644 AGENT.md create mode 100644 AGENTS.md diff --git a/AGENT.md b/AGENT.md deleted file mode 100644 index fbec080..0000000 --- a/AGENT.md +++ /dev/null @@ -1,77 +0,0 @@ -# AGENT.md - -## Project Overview - -**DCR (Dexoron Cargo Realization)** — CLI tool for managing C/C++/ASM projects in a Cargo-style. - -- **Implementation Language**: Rust (edition 2024) -- **License**: MIT -- **Repository**: [github.com/dexoron/dcr](https://github.com/dexoron/dcr) - -## Architecture - -### Source Tree (`src/`) - -``` -src/ -├── main.rs # Entry point, CLI command routing -├── config.rs # Global constants -├── cli/ # CLI command implementation -│ ├── new.rs # dcr new -│ ├── init.rs # dcr init -│ ├── setup.rs # dcr setup (registry initialization) -│ ├── add.rs # dcr add -│ ├── build.rs # dcr build -│ ├── run.rs # dcr run -│ ├── clean.rs # dcr clean -│ ├── help.rs # dcr --help -│ └── flag_update.rs # dcr --update -├── core/ # Business logic -│ ├── config.rs # dcr.toml parsing -│ ├── registry.rs # Package index management (RegistryManager) -│ ├── deps/ # Dependency resolution (path, git) -│ ├── runner.rs # Binary execution -│ ├── workspace.rs # [workspace] support -│ └── builder/ # Compiler backends (gcc, clang, msvc, nasm, gas) -├── platform/ # Platform-specific logic -└── utils/ # Helper utilities -``` - -### Key Concepts - -- **`~/.dcr/`**: Centralized storage for registries and configurations. -- **`config.toml`**: File in `~/.dcr/` defining available package indices and their priorities. -- **`dcr.toml`**: Local project configuration. -- **Registry System**: `RegistryManager` handles package discovery across multiple indices based on priority. - -## Supported Platforms - -| Platform | Target triple | -|---|---| -| Linux | `x86_64-unknown-linux-gnu` | -| macOS Intel | `x86_64-apple-darwin` | -| macOS ARM | `aarch64-apple-darwin` | -| Windows | `x86_64-pc-windows-msvc` | - -## Dependencies (Cargo) - -| Crate | Purpose | -|---|---| -| `reqwest` | HTTP requests | -| `serde` | Serialization | -| `toml` | Configuration parsing | -| `git2` | Git dependency handling | - -## Development - -### Build & Run -```bash -cargo build --release -./target/release/dcr setup -``` - -## Code Conventions -- Rust stable edition 2024. -- `cargo fmt` and `cargo clippy -- -D warnings`. -- CLI output in English. -- Use `utils::log` for messaging. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..63afa31 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md + +## Quick start + +```bash +cargo build --release && ./target/release/dcr setup +``` + +## Pre-PR (CI) gate + +```bash +cargo fmt --all --check +cargo clippy --all-targets --all-features -- -D warnings +cargo check --all-targets --all-features +cargo test +``` + +CI (`.github/workflows/ci.yml`) runs fmt, clippy, check — **not** tests. + +## Tests + +- Integration tests in `tests/` build the dcr binary and run it in temp dirs under `sandbox/cli-tests/` (gitignored). +- Some tests **skip** if no C compiler (`gcc`/`clang`/`cc`) is found at runtime. +- Compiler can be overridden via `DCR_COMPILER` or `DCR_CC` env vars. +- Tests use `$CARGO_BIN_EXE_dcr` when available, else `target/debug/dcr`. + +## Architecture + +- **Single Rust package** (edition 2024), not a workspace. Only one binary: `dcr`. +- **Entrypoint**: `src/main.rs` — matches `args[1]` against command names. +- **Compiler backends** in `src/core/builder/`: `unix_cc` (gcc/clang), `msvc` (cl/clang-cl), `gas`, `nasm`. Dispatched by matching compiler name string. +- **Config**: `dcr.toml` (per-project), `~/.dcr/config.toml` (registry), `~/.dcr/index.json` (index). Defaults: `language=c`, `standard=c11`, `compiler=clang`, `kind=bin`. +- **`build.rs`** embeds `TARGET` as `DCR_TARGET` env var (shown in `--version`). +- **CLI output** must be in English. Use `utils::log` for messaging. + +## Key commands + +| Command | Notes | +|---|---| +| `dcr new ` | Creates project dir | +| `dcr init` | Init from CWD (must be empty) | +| `dcr build [--debug\|--release] [--target ]` | Default profile: `--debug` | +| `dcr run [--debug\|--release]` | Only for `kind = "bin"` | +| `dcr clean [--debug\|--release] [--all]` | `--all` for workspace roots | +| `dcr tree` | Dependency tree viewer | +| `dcr test [--init]` | Runs/test project tests | +| `dcr gen ` | IDE integration | +| `dcr --update` | Self-update from GitHub Releases | + +## Cross-compilation + +`--target` accepts short names (`linux`, `macos`, `windows`) or full triples. In CI, Linux cross-builds use `cargo-zigbuild`. When `build.target` is set in `dcr.toml`, DCR auto-injects `--target=` into cflags (clang). Bare-metal targets (containing `none`/`-elf`/`eabi`/`baremetal`) skip DCR's default compiler flags. + +## Dependencies (Cargo) + +reqwest (blocking+json+rustls-tls), serde, toml, git2, openssl (vendored), self-replace, sha2, ctrlc. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eebbea..f34f3e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Added: Produces `KERNEL.EFI` (works for `bin`, `staticlib`, and `sharedlib`). - Automatic injection of `--target=` into compiler flags when `build.target` is set in `dcr.toml`. This greatly simplifies clang-based cross-compilation (especially for bare-metal targets like `aarch64-none-elf`). - Bare-metal targets (containing `none`, `-elf`, `eabi`, `baremetal`) no longer receive DCR's internal default flags (`-g`, `-Wall`, `-Wextra`, `-fno-omit-frame-pointer`, `-DDCR_DEBUG`, etc.). Prevents unwanted sections (`.comment`, debug info, etc.) that break custom linker scripts when `inherit = true`. +- `build.ldscript` in `dcr.toml` — linker script path passed as `-T ` to the linker. Essential for bare-metal/embedded/freestanding targets. +- `dcr build --verbose` — prints compiler/linker command lines (also works with `DCR_DEBUG` env var). +- `dcr add ` without a source argument — if a registry is configured, DCR looks up the package by name and adds it as a version dependency. Fixed: diff --git a/docs/commands/dcr-add.md b/docs/commands/dcr-add.md index 356ac71..701184f 100644 --- a/docs/commands/dcr-add.md +++ b/docs/commands/dcr-add.md @@ -5,11 +5,15 @@ Add a dependency to `dcr.toml`. ## Usage ```bash -dcr add [options] +dcr add [source] [options] ``` +If `` is omitted and a registry is configured, DCR will look up the +package by name in the registry and add it as a version dependency. + ## Sources +- *omitted*: Look up in the configured registry. - `path:./path/to/lib`: Local directory. - `github:user/repo`: GitHub repository. - `gitlab:user/repo`: GitLab repository. @@ -29,4 +33,5 @@ dcr add my_lib path:./libs/my_lib dcr add cool_lib github:user/cool_lib dcr add shared_lib gitlab:org/shared --branch develop dcr add custom_lib git:git.internal.com/r/lib --tag v1.0.0 +dcr add mylib # Looks up in registry ``` diff --git a/docs/commands/dcr-build.md b/docs/commands/dcr-build.md index dd08931..d712c26 100644 --- a/docs/commands/dcr-build.md +++ b/docs/commands/dcr-build.md @@ -35,6 +35,7 @@ dcr build --debug --clean - `build.platform` - `build.cflags` - `build.ldflags` +- `build.ldscript` - `build.debug` / `build.release` - `build.exclude` - `build.include` @@ -58,6 +59,7 @@ dcr build --debug --clean - Profile flag (`--debug` / `--release`) can appear in any argument position (duplicates are rejected). - Unknown profile flags return an error. - `--target ` builds for specified target (short names: `linux`, `macos`, `windows`). +- `--verbose` prints the compiler/linker command lines to stderr (also works via `DCR_DEBUG` env var). - If no `--target`, builds for `build.targets` or native. - Incremental rebuild checks source/object mtime and tracked header dependencies. - `--force` skips build cache checks and recompiles. diff --git a/docs/configuration/build-section.md b/docs/configuration/build-section.md index ac59089..a556406 100644 --- a/docs/configuration/build-section.md +++ b/docs/configuration/build-section.md @@ -43,6 +43,7 @@ kind = "bin" - `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`). - `cflags` (string array, optional): extra compile flags. - `ldflags` (string array, optional): extra link flags. +- `ldscript` (string, optional): linker script path. Passed as `-T ` to the linker. Useful for bare-metal/embedded/freestanding targets. - `exclude` (string array, optional): paths or glob patterns to skip during source/header collection. - `include` (string array, optional): allowlist paths or globs that override `exclude`. - `include` directory entries (non-glob) are also passed to the compiler as `-I` include paths. diff --git a/docs/reference-and-help/cli-reference.md b/docs/reference-and-help/cli-reference.md index 5aab80e..43d0449 100644 --- a/docs/reference-and-help/cli-reference.md +++ b/docs/reference-and-help/cli-reference.md @@ -20,7 +20,7 @@ dcr --update ## Notes on argument parsing -- `build`/`run` parse flags in any order (`--debug|--release`, `--force`, `--clean`) and reject duplicates. +- `build`/`run` parse flags in any order (`--debug|--release`, `--force`, `--clean`, `--verbose`) and reject duplicates. - `new` requires exactly one argument. - `init` and `--update` do not accept extra arguments. - `clean` accepts `--debug|--release` and optional `--all`. diff --git a/docs/reference-and-help/config-reference.md b/docs/reference-and-help/config-reference.md index b62110e..231a50e 100644 --- a/docs/reference-and-help/config-reference.md +++ b/docs/reference-and-help/config-reference.md @@ -18,6 +18,7 @@ target = "string" # optional platform = "string" # optional cflags = ["string", "..."] # optional ldflags = ["string", "..."] # optional +ldscript = "string" # optional (linker script, passed as -T) exclude = ["string", "..."] # optional include = ["string", "..."] # optional roots = ["string", "..."] # optional diff --git a/src/cli/add.rs b/src/cli/add.rs index afcdcce..ad499a1 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -1,4 +1,5 @@ use crate::core::config::Config; +use crate::core::deps::register; use crate::utils::fs::find_project_root; use crate::utils::log::error; use crate::utils::text::{BOLD_GREEN, colored}; @@ -12,6 +13,7 @@ pub struct AddArgs { pub branch: Option, pub tag: Option, pub rev: Option, + pub version_from_registry: Option, } pub fn add(args: &[String]) -> i32 { @@ -71,6 +73,8 @@ pub fn add(args: &[String]) -> i32 { let mut table = Map::new(); table.insert("path".to_string(), Value::String(path)); Value::Table(table) + } else if let Some(version) = add_args.version_from_registry { + Value::String(version) } else { error("Dependency source (path or git) must be provided"); return 1; @@ -124,8 +128,38 @@ fn parse_add_args(args: &[String]) -> Result { } source_spec = s.clone(); } else { - error("Dependency source is required"); - return Err(1); + // No source provided — try registry auto-lookup + match register::resolve_package_from_registry(&name) { + Ok(info) => { + let version = info + .get("latest_version") + .or_else(|| info.get("version")) + .and_then(|v| v.as_str()) + .ok_or_else(|| { + ( + "Registry entry for `".to_string() + &name + "` has no version field", + 1, + ) + }) + .map_err(|(msg, code)| { + error(&msg); + code + })?; + return Ok(AddArgs { + name, + path: None, + git: None, + branch: None, + tag: None, + rev: None, + version_from_registry: Some(version.to_string()), + }); + } + Err(e) => { + error(&format!("Cannot add `{}` without source: {}", name, e)); + return Err(1); + } + } } while let Some(arg) = iter.next() { @@ -197,5 +231,6 @@ fn parse_add_args(args: &[String]) -> Result { branch, tag, rev, + version_from_registry: None, }) } diff --git a/src/cli/build.rs b/src/cli/build.rs index 6039c99..3a1f23c 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -77,7 +77,13 @@ pub fn build(args: &[String]) -> i32 { let _ = clean(&clean_args); } match with_dir(&root, || { - build_from_root(&root, &flags.profile, flags.target.as_deref(), flags.force) + build_from_root( + &root, + &flags.profile, + flags.target.as_deref(), + flags.force, + flags.verbose, + ) }) { Ok(()) => 0, Err(msg) => { @@ -377,6 +383,7 @@ fn build_from_root( profile: &str, target: Option<&str>, force: bool, + verbose: bool, ) -> Result<(), String> { let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; let project_name = get_config_str(&config, "package.name"); @@ -418,12 +425,19 @@ fn build_from_root( ); } if let Some(workspace) = parse_workspace(&config, profile, build_target.as_deref(), root)? { - build_workspace(&workspace, profile, build_target.as_deref(), force)?; + build_workspace(&workspace, profile, build_target.as_deref(), force, verbose)?; let excludes: Vec = workspace.members.iter().map(|m| m.path.clone()).collect(); - build_project_at(root, profile, build_target.as_deref(), &excludes, force)?; + build_project_at( + root, + profile, + build_target.as_deref(), + &excludes, + force, + verbose, + )?; } else { - build_project_at(root, profile, build_target.as_deref(), &[], force)?; + build_project_at(root, profile, build_target.as_deref(), &[], force, verbose)?; } } let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -440,9 +454,10 @@ fn build_workspace( profile: &str, target: Option<&str>, force: bool, + verbose: bool, ) -> Result<(), String> { for member in &workspace.members { - build_project_at(&member.path, profile, target, &[], force)?; + build_project_at(&member.path, profile, target, &[], force, verbose)?; } Ok(()) } @@ -453,6 +468,7 @@ fn build_project_at( target: Option<&str>, exclude_dirs: &[std::path::PathBuf], force: bool, + verbose: bool, ) -> Result<(), String> { with_dir(project_root, || { check_interrupted()?; @@ -500,21 +516,30 @@ fn build_project_at( .or_else(|| get_config_opt(&config, "toolchain.rcc")); let mut build_cflags = get_list_with_profile_and_target(&config, "cflags", profile, build_target)?; - // Автоматически добавляем --target=..., если задан build.target и флаг ещё не указан - if let Some(t) = build_target { - if !t.trim().is_empty() { - let target_flag = format!("--target={}", t.trim()); - if !build_cflags.iter().any(|f| f == &target_flag || f.starts_with("--target=")) { - build_cflags.insert(0, target_flag); - } + if let Some(t) = build_target + && !t.trim().is_empty() + { + let target_flag = format!("--target={}", t.trim()); + if !build_cflags + .iter() + .any(|f| f == &target_flag || f.starts_with("--target=")) + { + build_cflags.insert(0, target_flag); } } - let build_ldflags = + let mut build_ldflags = get_list_with_profile_and_target(&config, "ldflags", profile, build_target)?; + let build_ldscript = + get_string_with_profile_and_target(&config, "ldscript", profile, build_target); + if !build_ldscript.is_empty() { + build_ldflags.push(format!("-T{}", build_ldscript)); + } // Новые поля: filename и extension - let output_filename = get_string_with_profile_and_target(&config, "filename", profile, build_target); - let output_extension = get_string_with_profile_and_target(&config, "extension", profile, build_target); + let output_filename = + get_string_with_profile_and_target(&config, "filename", profile, build_target); + let output_extension = + get_string_with_profile_and_target(&config, "extension", profile, build_target); let build_excludes = get_list_with_profile_and_target(&config, "exclude", profile, build_target)?; let build_includes = @@ -578,7 +603,7 @@ fn build_project_at( dep_root.display() )); } - build_project_at(&dep_root, profile, build_target, &[], force)?; + build_project_at(&dep_root, profile, build_target, &[], force, verbose)?; print!( "\r{:100}\r {} {} v{}", "", @@ -693,8 +718,17 @@ fn build_project_at( libs: &resolved.libs, cflags: &resolved_cflags, ldflags: &resolved_ldflags, - output_filename: if output_filename.is_empty() { None } else { Some(output_filename.as_str()) }, - output_extension: if output_extension.is_empty() { None } else { Some(output_extension.as_str()) }, + output_filename: if output_filename.is_empty() { + None + } else { + Some(output_filename.as_str()) + }, + output_extension: if output_extension.is_empty() { + None + } else { + Some(output_extension.as_str()) + }, + verbose, }; if std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] debug: compiler={}", ctx.compiler); diff --git a/src/cli/flags.rs b/src/cli/flags.rs index 23ab6e8..59f959e 100644 --- a/src/cli/flags.rs +++ b/src/cli/flags.rs @@ -6,6 +6,7 @@ pub struct BuildRunFlags { pub target: Option, pub force: bool, pub clean: bool, + pub verbose: bool, } pub fn parse_build_run_flags(args: &[String]) -> Result { @@ -13,6 +14,7 @@ pub fn parse_build_run_flags(args: &[String]) -> Result { let mut target = None; let mut force = false; let mut clean = false; + let mut verbose = false; let mut iter = args.iter(); while let Some(arg) = iter.next() { @@ -29,6 +31,10 @@ pub fn parse_build_run_flags(args: &[String]) -> Result { clean = true; continue; } + if candidate == "verbose" { + verbose = true; + continue; + } if candidate == "target" { if let Some(t) = iter.next() { target = Some(t.clone()); @@ -55,5 +61,6 @@ pub fn parse_build_run_flags(args: &[String]) -> Result { target, force, clean, + verbose, }) } diff --git a/src/cli/gen.rs b/src/cli/gen.rs index e1c70b7..1f27cf8 100644 --- a/src/cli/gen.rs +++ b/src/cli/gen.rs @@ -339,6 +339,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result Result { for obj in &objects { cmd.arg(obj); } - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { @@ -72,7 +72,11 @@ pub fn build(ctx: &BuildContext) -> Result { } let name = ctx.output_filename.unwrap_or(ctx.project_name); let ext = ctx.output_extension.unwrap_or(""); - let final_name = if ext.is_empty() { name.to_string() } else { format!("{}.{}", name, ext) }; + let final_name = if ext.is_empty() { + name.to_string() + } else { + format!("{}.{}", name, ext) + }; let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) @@ -81,7 +85,7 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg("-o").arg(out_path); - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs index 26d015b..fa7e446 100644 --- a/src/core/builder/mod.rs +++ b/src/core/builder/mod.rs @@ -28,6 +28,7 @@ pub struct BuildContext<'a> { pub ldflags: &'a [String], pub output_filename: Option<&'a str>, pub output_extension: Option<&'a str>, + pub verbose: bool, } pub fn build(ctx: &BuildContext) -> Result { diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index a073f09..49b3c96 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -28,7 +28,7 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { @@ -97,7 +97,7 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg(format!("/Fe:{out_path}")); - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs index 0a4a43c..a44f8c9 100644 --- a/src/core/builder/nasm.rs +++ b/src/core/builder/nasm.rs @@ -37,7 +37,7 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { @@ -72,7 +72,11 @@ pub fn build(ctx: &BuildContext) -> Result { } let name = ctx.output_filename.unwrap_or(ctx.project_name); let ext = ctx.output_extension.unwrap_or(""); - let final_name = if ext.is_empty() { name.to_string() } else { format!("{}.{}", name, ext) }; + let final_name = if ext.is_empty() { + name.to_string() + } else { + format!("{}.{}", name, ext) + }; let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) @@ -81,7 +85,7 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg("-o").arg(out_path); - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { @@ -151,7 +155,7 @@ fn build_object( cmd.arg(flag); } - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index ddd022c..25c04d7 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -30,7 +30,7 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { @@ -65,7 +65,11 @@ pub fn build(ctx: &BuildContext) -> Result { } let name = ctx.output_filename.unwrap_or(ctx.project_name); let ext = ctx.output_extension.unwrap_or(""); - let final_name = if ext.is_empty() { name.to_string() } else { format!("{}.{}", name, ext) }; + let final_name = if ext.is_empty() { + name.to_string() + } else { + format!("{}.{}", name, ext) + }; let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) @@ -74,7 +78,7 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg("-o").arg(out_path); - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } match cmd.status() { @@ -214,7 +218,7 @@ fn build_object( let d_path = Path::new(obj_path).with_extension("d"); cmd.arg("-MMD").arg("-MF").arg(&d_path); - if std::env::var("DCR_DEBUG").is_ok() { + if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { eprintln!("[dcr] {:?}", cmd); } diff --git a/src/core/deps/register.rs b/src/core/deps/register.rs index f9d01f2..a3d1d4a 100644 --- a/src/core/deps/register.rs +++ b/src/core/deps/register.rs @@ -15,9 +15,23 @@ pub struct DcrConfig { pub registry: std::collections::HashMap, } +fn home_dir() -> Option { + 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 +} + +fn dcr_config_dir() -> Option { + home_dir().map(|h| h.join(".dcr")) +} + pub fn get_registry_config() -> Option { - let home = std::env::var("HOME").ok()?; - let config_path = Path::new(&home).join(".dcr/config.toml"); + let home = home_dir()?; + let config_path = home.join(".dcr/config.toml"); if !config_path.exists() { return None; } @@ -30,8 +44,9 @@ pub fn get_index_path() -> PathBuf { std::env::var("DCR_INDEX_PATH") .map(PathBuf::from) .unwrap_or_else(|_| { - let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); - Path::new(&home).join(".dcr").join("index.json") + dcr_config_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("index.json") }) } diff --git a/src/core/registry.rs b/src/core/registry.rs index 3625855..ed36c15 100644 --- a/src/core/registry.rs +++ b/src/core/registry.rs @@ -20,10 +20,21 @@ pub struct RegistryManager { pub path: PathBuf, } +fn home_dir() -> Option { + 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 +} + impl RegistryManager { pub fn load() -> Result> { - let home = std::env::var("HOME")?; - let dcr_dir = PathBuf::from(home).join(".dcr"); + let dcr_dir = home_dir() + .ok_or("Cannot determine home directory")? + .join(".dcr"); let config_path = dcr_dir.join("config.toml"); if !config_path.exists() { From 7969209eae50eb435577f0ee86e7a1ae941dc997 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Tue, 26 May 2026 16:38:23 +0800 Subject: [PATCH 08/12] Added: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `build.kind = "efi"` — UEFI PE32+ executable support. Links with `-shared -nostdlib -Wl,-dll -Wl,--subsystem,10`, output via `efi_path()` with `.efi` extension, rejected by `dcr run`. - `build.kind = "elf"` — bare-metal ELF executable support. Uses `elf_path()` output path, rejected by `dcr run`, no extra linker flags. Fixed: - `dcr run` with `--target` or `build.target` now correctly finds the binary in the arch-specific target directory. - `dcr run` checks profile-specific `build.{profile}.kind` before rejecting library builds (previously only checked `build.kind`). - `dcr clean` now reads `build.target` from `dcr.toml` when no `--target` flag is given. - Linux: target directory is now consistent — `target/-unknown-linux-gnu//` (no stale `target//`). - `dcr.lock` is now populated with all resolved dependencies (registry, path, git) instead of being always empty. - Git dependencies are now properly recognized and recorded in `dcr.lock`. - `is_registry_dep` correctly identifies version-based registry dependency tables (`{ version = "..." }`) and rejects tables with unknown keys. - `dcr test` accepts `--debug`/`--release` flags (defaults to `debug` profile instead of always `release`). - Build fingerprint cache (`build_cache_path`) now includes `target_dir` — different targets no longer share a single cache entry. - `object_path` no longer hardcodes the `src/` prefix — works correctly with custom `build.roots` (e.g. `roots = ["lib"]`). - `dcr update` now generates `.exe` candidates for all Windows targets, not just `x86_64-pc-windows-msvc`. - Compiler existence is verified before starting the build (clear error if compiler is not found in PATH). - Consolidated duplicate `OUTPUT_MUTEX` definitions — MSVC backend now uses the shared mutex from `common.rs`. - Registry cache root directory is created if missing (prevents cryptic errors when `~/.dcr/` does not exist). --- CHANGELOG.md | 18 +++++++- README.md | 8 ++-- docs/commands/dcr-build.md | 4 +- docs/commands/dcr-clean.md | 2 +- docs/commands/dcr-run.md | 2 +- docs/commands/dcr-test.md | 3 +- docs/configuration/build-section.md | 4 +- docs/getting-started/build-and-run.md | 2 +- src/cli/build.rs | 23 +++++++--- src/cli/clean.rs | 30 ++++++++----- src/cli/flag_update.rs | 4 +- src/cli/run.rs | 31 ++++++++----- src/cli/test.rs | 16 ++++--- src/core/builder/common.rs | 18 ++++++-- src/core/builder/gas.rs | 7 ++- src/core/builder/mod.rs | 14 ++++++ src/core/builder/msvc.rs | 11 +++-- src/core/builder/nasm.rs | 7 ++- src/core/builder/unix_cc.rs | 15 ++++++- src/core/config.rs | 19 ++++---- src/core/deps/mod.rs | 65 ++++++++++++++++++++++++++- src/core/deps/register.rs | 30 +++++++++---- src/platform/linux.rs | 48 +++++++++++++++----- src/platform/macos.rs | 14 ++++++ src/platform/mod.rs | 44 ++++++++++++++++++ src/platform/windows.rs | 14 ++++++ 26 files changed, 368 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f34f3e2..455c7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Changelog -## [0.6.7] - 2026-05-21 +## [0.6.7] - 2026-05-26 Added: +- `build.kind = "efi"` — UEFI PE32+ executable support. Links with `-shared -nostdlib -Wl,-dll -Wl,--subsystem,10`, output via `efi_path()` with `.efi` extension, rejected by `dcr run`. +- `build.kind = "elf"` — bare-metal ELF executable support. Uses `elf_path()` output path, rejected by `dcr run`, no extra linker flags. - `build.filename` and `build.extension` in `dcr.toml` — complete control over the final artifact name without relying on `package.name`. Example: ```toml @@ -21,6 +23,20 @@ Added: Fixed: - `build.target` declared in `dcr.toml` is now correctly used as the default target even when `--target` is not passed on the command line (previously the host target was always forced for config resolution). +- `dcr run` with `--target` or `build.target` now correctly finds the binary in the arch-specific target directory. +- `dcr run` checks profile-specific `build.{profile}.kind` before rejecting library builds (previously only checked `build.kind`). +- `dcr clean` now reads `build.target` from `dcr.toml` when no `--target` flag is given. +- Linux: target directory is now consistent — `target/-unknown-linux-gnu//` (no stale `target//`). +- `dcr.lock` is now populated with all resolved dependencies (registry, path, git) instead of being always empty. +- Git dependencies are now properly recognized and recorded in `dcr.lock`. +- `is_registry_dep` correctly identifies version-based registry dependency tables (`{ version = "..." }`) and rejects tables with unknown keys. +- `dcr test` accepts `--debug`/`--release` flags (defaults to `debug` profile instead of always `release`). +- Build fingerprint cache (`build_cache_path`) now includes `target_dir` — different targets no longer share a single cache entry. +- `object_path` no longer hardcodes the `src/` prefix — works correctly with custom `build.roots` (e.g. `roots = ["lib"]`). +- `dcr update` now generates `.exe` candidates for all Windows targets, not just `x86_64-pc-windows-msvc`. +- Compiler existence is verified before starting the build (clear error if compiler is not found in PATH). +- Consolidated duplicate `OUTPUT_MUTEX` definitions — MSVC backend now uses the shared mutex from `common.rs`. +- Registry cache root directory is created if missing (prevents cryptic errors when `~/.dcr/` does not exist). ## [0.6.6] - 2026-05-18 diff --git a/README.md b/README.md index 58f14e9..ca201c9 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The current implementation is written in Rust. - Clean build artifacts - Generate IDE integration files (VS Code, CLion) and compilation databases - Generate a minimal C project template -- Build static and shared libraries +- Build static, shared libraries, UEFI PE32+ executables, and bare-metal ELF executables - Dependency management: - Git and Path dependencies with `dcr.lock` - Registry-based dependency resolution @@ -154,7 +154,9 @@ You can also set `build.target` to override the output directory (profile-indepe You can set `build.platform` to pass `-march=` (GCC/Clang) or `/arch:*` (MSVC) where supported. Use `build.kind = "staticlib"` to build a static library instead of a binary. Use `build.kind = "sharedlib"` to build a shared library (`.so`/`.dylib`/`.dll`). -`dcr run` is only for `build.kind = "bin"` and will fail for libraries. +Use `build.kind = "efi"` to build a UEFI PE32+ executable (`.efi`). +Use `build.kind = "elf"` to build a bare-metal ELF executable. +`dcr run` is only for `build.kind = "bin"` and will fail for libraries, EFI, and ELF. Use `build.exclude`/`build.include` to control source/header collection. Use `build.roots` and `build.src_disable` to replace the default `src/` root. Use `build.steps` and `build.post_steps` to run custom commands. @@ -203,7 +205,7 @@ type = "app" # or "lib", "none" language = "c" standard = "c11" compiler = "clang" -kind = "bin" # "bin", "staticlib", "sharedlib" +kind = "bin" # "bin", "staticlib", "sharedlib", "efi", "elf" # Optional platform hint # platform = "x86_64" # Optional custom flags diff --git a/docs/commands/dcr-build.md b/docs/commands/dcr-build.md index d712c26..d812bd8 100644 --- a/docs/commands/dcr-build.md +++ b/docs/commands/dcr-build.md @@ -64,7 +64,9 @@ dcr build --debug --clean - Incremental rebuild checks source/object mtime and tracked header dependencies. - `--force` skips build cache checks and recompiles. - `--clean` removes `target/` and `build.clean` paths before building. -- `build.filename` + `build.extension` allow full control over the final artifact name (e.g. `KERNEL.BIN` instead of using `package.name`). +- `build.filename` + `build.extension` allow full control over the final artifact name (e.g. `KERNEL.BIN` instead of using `package.name`). When `build.kind = "efi"`, the output is a PE32+ executable with `.efi` extension. +- `build.kind = "efi"` links with `-shared -nostdlib -Wl,-dll -Wl,--subsystem,10` for UEFI PE32+ format. Use `build.filename` and `build.extension` for full control over the artifact name. +- `build.kind = "elf"` produces a bare-metal ELF executable. Linking flags are user-controlled via `build.ldflags` (no PIE/CRT defaults are added). - Default GCC/Clang profile flags: `debug` -> `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`, `release` -> `-O3 -DNDEBUG`. These defaults are **not applied** for bare-metal targets (e.g. `aarch64-none-elf`, targets containing `none` / `-elf` / `eabi`). - For `language = "asm"` with `compiler = "as"`/`"gas"`, use `.s` files (no preprocessing). For `.S`, use `gcc` or `clang`. diff --git a/docs/commands/dcr-clean.md b/docs/commands/dcr-clean.md index dce8d8f..81e8254 100644 --- a/docs/commands/dcr-clean.md +++ b/docs/commands/dcr-clean.md @@ -15,7 +15,7 @@ dcr clean --release --all ## Behavior - Without arguments: removes entire `target/`. -- With profile: removes only `target//`. +- With profile: removes only the profile-specific directory. Respects `build.target` from config: if `build.target = "aarch64-unknown-linux-gnu"`, cleans `target/aarch64-unknown-linux-gnu//`. - With `--all`: also cleans all workspace members. - Additionally removes paths from `build.clean` if configured. - `build.clean` supports `{profile}` and version placeholders. diff --git a/docs/commands/dcr-run.md b/docs/commands/dcr-run.md index b5f09f7..f5902db 100644 --- a/docs/commands/dcr-run.md +++ b/docs/commands/dcr-run.md @@ -23,7 +23,7 @@ dcr run --debug --clean ## Restrictions -- If `build.kind = "staticlib"` or `build.kind = "sharedlib"`, `run` exits with error. +- If `build.kind` (or `build.{profile}.kind`) is `"staticlib"` or `"sharedlib"`, `run` exits with error. - The profile is selected from the first argument. - `--target ` runs the binary for specified target (short names: `linux`, `macos`, `windows`). - `--force` skips build cache checks and recompiles. diff --git a/docs/commands/dcr-test.md b/docs/commands/dcr-test.md index 01d9011..19766c1 100644 --- a/docs/commands/dcr-test.md +++ b/docs/commands/dcr-test.md @@ -6,6 +6,7 @@ Runs the project's test suite. ```sh dcr test +dcr test --debug dcr test --release dcr test --target linux dcr test --init @@ -39,6 +40,6 @@ DCR uses a simple test framework: - Test binary is linked with the main project (for lib projects) or standalone. - `--init` creates `tests/dcr_test.h` and `tests/test.c` if missing. -- Tests run in release mode by default for performance. +- Tests run in debug mode by default. Use `--release` for release mode. - Custom test logic can be added by editing `tests/test.c`. - Fails if no `tests/test.c` exists (run `dcr test --init` first). \ No newline at end of file diff --git a/docs/configuration/build-section.md b/docs/configuration/build-section.md index a556406..dd5f03e 100644 --- a/docs/configuration/build-section.md +++ b/docs/configuration/build-section.md @@ -33,7 +33,7 @@ kind = "bin" Mixed languages can be specified as an array, for example `["c", "c++", "asm"]`. - `standard` (string, required): language standard passed to compiler. - `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`). -- `kind` (string, required): `bin`, `staticlib`, or `sharedlib`. +- `kind` (string, required): `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`. - `filename` (string, optional): custom output filename (without extension). Overrides `package.name` for the final artifact. - `extension` (string, optional): custom file extension without the leading dot (e.g. `BIN`, `efi`, `EFI`). Combined with `filename`. - `inherit` (bool, optional): inherit settings from base `[build]` (default `true`). If `false`, only use target/profile specific settings. @@ -151,4 +151,4 @@ rcc = "rcc" - Compiler backend is selected by compiler string (`gcc`, `clang`, `cl`, `clang-cl`). - Empty or unknown compiler value falls back to clang-like build path. -- `run` is not allowed for library kinds (`staticlib`, `sharedlib`). +- `run` is not allowed for library kinds (`staticlib`, `sharedlib`), `efi`, or `elf`. diff --git a/docs/getting-started/build-and-run.md b/docs/getting-started/build-and-run.md index 470a255..731cf68 100644 --- a/docs/getting-started/build-and-run.md +++ b/docs/getting-started/build-and-run.md @@ -49,7 +49,7 @@ dcr clean --release --all ## Notes -- `run` is unavailable for `build.kind = "staticlib"` and `build.kind = "sharedlib"`. +- `run` is unavailable for `build.kind = "staticlib"`, `build.kind = "sharedlib"`, `build.kind = "efi"`, and `build.kind = "elf"`. - Build profile flag (`--debug` / `--release`) can be passed in any argument position. - Unknown profile flags produce an error. - In workspace root, `clean --all` cleans all member projects. diff --git a/src/cli/build.rs b/src/cli/build.rs index 3a1f23c..c190b6d 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -355,9 +355,15 @@ fn ensure_target_dirs(items: &[String], profile: &str, target_dir: Option Result<(), } fn build_cache_path(profile: &str, target_dir: Option<&str>) -> std::path::PathBuf { - let _ = target_dir; - Path::new("./target").join(profile).join(".dcr-build.hash") + match target_dir { + Some(dir) => Path::new(dir).join(".dcr-build.hash"), + None => Path::new("./target").join(profile).join(".dcr-build.hash"), + } } fn build_output_path(ctx: &BuildContext) -> String { @@ -1332,12 +1340,17 @@ fn build_output_path(ctx: &BuildContext) -> String { }; if ctx.kind == "staticlib" { - // Для библиотек добавляем lib префикс если нужно, но упростим return crate::platform::lib_path(ctx.profile, &final_name, ctx.target_dir); } if ctx.kind == "sharedlib" { return crate::platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir); } + if ctx.kind == "efi" { + return crate::platform::efi_path(ctx.profile, &final_name, ctx.target_dir); + } + if ctx.kind == "elf" { + return crate::platform::elf_path(ctx.profile, &final_name, ctx.target_dir); + } crate::platform::bin_path(ctx.profile, &final_name, ctx.target_dir) } diff --git a/src/cli/clean.rs b/src/cli/clean.rs index b86f589..43e772e 100644 --- a/src/cli/clean.rs +++ b/src/cli/clean.rs @@ -91,17 +91,27 @@ fn parse_clean_flags(args: &[String]) -> Result { fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> { let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; - let target = flags.target.clone().or_else(|| { - Some(if cfg!(target_os = "linux") { - "x86_64-unknown-linux-gnu".to_string() - } else if cfg!(target_os = "macos") { - "x86_64-apple-darwin".to_string() - } else if cfg!(target_os = "windows") { - "x86_64-pc-windows-msvc".to_string() - } else { - "unknown".to_string() + let target = flags + .target + .clone() + .or_else(|| { + config + .get("build.target") + .and_then(|v| v.as_str()) + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) }) - }); + .or_else(|| { + Some(if cfg!(target_os = "linux") { + "x86_64-unknown-linux-gnu".to_string() + } else if cfg!(target_os = "macos") { + "x86_64-apple-darwin".to_string() + } else if cfg!(target_os = "windows") { + "x86_64-pc-windows-msvc".to_string() + } else { + "unknown".to_string() + }) + }); if flags.all && let Some(workspace) = parse_workspace( diff --git a/src/cli/flag_update.rs b/src/cli/flag_update.rs index 1111f4b..8c2e986 100644 --- a/src/cli/flag_update.rs +++ b/src/cli/flag_update.rs @@ -138,8 +138,8 @@ fn download_asset(client: &Client, url: &str) -> Result, String> { fn asset_candidates(target: &str) -> Vec { let mut names = vec![format!("dcr-{target}")]; - if target == "x86_64-pc-windows-msvc" { - names.push("dcr-x86_64-pc-windows-msvc.exe".to_string()); + if target.contains("-windows-") || target.ends_with("-windows") { + names.push(format!("dcr-{target}.exe")); } names diff --git a/src/cli/run.rs b/src/cli/run.rs index 20a23bc..3a23970 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -69,15 +69,6 @@ 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()) - .map(|s| s.trim()) - .filter(|s| !s.is_empty()); let mut flags = match parse_build_run_flags(args) { Ok(v) => v, @@ -98,6 +89,12 @@ pub fn run(args: &[String]) -> i32 { flags.target = Some(default_target.to_string()); } + let build_kind = config + .get(&format!("build.{}.kind", flags.profile)) + .and_then(|v| v.as_str()) + .or_else(|| config.get("build.kind").and_then(|v| v.as_str())) + .unwrap_or(""); + let normalized_target_dir = flags .target .as_ref() @@ -110,7 +107,9 @@ pub fn run(args: &[String]) -> i32 { let run_cmd = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), version); let kind = build_kind.trim(); - if run_cmd.is_none() && (kind == "staticlib" || kind == "sharedlib") { + if run_cmd.is_none() + && (kind == "staticlib" || kind == "sharedlib" || kind == "efi" || kind == "elf") + { error("Cannot run library build"); return 1; } @@ -128,13 +127,21 @@ pub fn run(args: &[String]) -> i32 { } println!("\n {} {}", colored("Running", BOLD_GREEN), bin_path); println!("--------------------------------"); - return run_binary(project_name, &flags.profile, target_dir); + return run_binary( + project_name, + &flags.profile, + normalized_target_dir.as_deref(), + ); } let fallback_code = if let Some(cmd) = run_cmd { run_shell(&cmd) } else { - run_binary(project_name, &flags.profile, target_dir) + run_binary( + project_name, + &flags.profile, + normalized_target_dir.as_deref(), + ) }; if fallback_code != 1 { return fallback_code; diff --git a/src/cli/test.rs b/src/cli/test.rs index 127424e..83855fa 100644 --- a/src/cli/test.rs +++ b/src/cli/test.rs @@ -16,10 +16,11 @@ const BOLD_BLUE: &str = "\x1b[1m\x1b[94m"; pub fn test(args: &[String]) -> i32 { let mut init_header = false; + let mut profile = "debug"; for arg in args { if arg == "--help" { println!("USAGE:"); - println!(" dcr test [--init]"); + println!(" dcr test [--init] [--debug | --release]"); println!(); println!("ALIASES:"); println!(" dcr tests"); @@ -29,12 +30,18 @@ pub fn test(args: &[String]) -> i32 { println!(); println!("OPTIONS:"); println!(" --init Create tests/dcr_test.h in current project"); + println!(" --debug Build and run tests with debug profile (default)"); + println!(" --release Build and run tests with release profile"); return 0; } if arg == "--init" { init_header = true; continue; } + if arg == "--debug" || arg == "--release" { + profile = arg.trim_start_matches("--"); + continue; + } error("Unknown argument"); return 1; } @@ -68,7 +75,7 @@ pub fn test(args: &[String]) -> i32 { } } - match with_dir(&root, run_testsuite) { + match with_dir(&root, || run_testsuite(profile)) { Ok(code) => code, Err(msg) => { error(&msg); @@ -77,13 +84,12 @@ pub fn test(args: &[String]) -> i32 { } } -fn run_testsuite() -> Result { - if build::build(&[String::from("--release")]) != 0 { +fn run_testsuite(profile: &str) -> Result { + if build::build(&[format!("--{profile}")]) != 0 { return Ok(1); } let config = Config::open("./dcr.toml").map_err(|_| "Failed to read dcr.toml".to_string())?; - let profile = "release"; let name = get_config_str(&config, "package.name"); let language = get_language_with_profile(&config, profile)?; let compiler_cfg = get_string_with_profile(&config, "compiler", profile); diff --git a/src/core/builder/common.rs b/src/core/builder/common.rs index 0575e0d..b0dd391 100644 --- a/src/core/builder/common.rs +++ b/src/core/builder/common.rs @@ -5,6 +5,10 @@ use std::sync::{Mutex, OnceLock}; static OUTPUT_MUTEX: OnceLock> = OnceLock::new(); +pub fn get_output_lock() -> &'static Mutex<()> { + OUTPUT_MUTEX.get_or_init(|| Mutex::new(())) +} + pub fn collect_sources( roots: &[PathBuf], extensions: &[&str], @@ -230,10 +234,16 @@ pub fn normalize_source_path(path: &Path) -> String { pub 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 stripped = src_path.strip_prefix("./").unwrap_or(src_path); + let rel = stripped + .components() + .skip(1) + .collect::(); + let rel = if rel.components().next().is_some() { + rel + } else { + stripped.to_path_buf() + }; let mut out = obj_dir.join(rel); out.set_extension(obj_ext.trim_start_matches('.')); out.to_string_lossy().to_string() diff --git a/src/core/builder/gas.rs b/src/core/builder/gas.rs index f120187..bb25249 100644 --- a/src/core/builder/gas.rs +++ b/src/core/builder/gas.rs @@ -18,7 +18,10 @@ pub fn build(ctx: &BuildContext) -> Result { }; let start_time = Instant::now(); let sources = collect_sources(ctx)?; - let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let obj_dir = match ctx.target_dir { + Some(dir) => Path::new(dir).join("obj"), + None => Path::new("./target").join(ctx.profile).join("obj"), + }; let objects = build_objects(assembler, &sources, &obj_dir, ctx, "o")?; if ctx.kind == "staticlib" { @@ -80,6 +83,8 @@ pub fn build(ctx: &BuildContext) -> Result { let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) + } else if ctx.kind == "elf" { + platform::elf_path(ctx.profile, &final_name, ctx.target_dir) } else { platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs index fa7e446..4a5d1f9 100644 --- a/src/core/builder/mod.rs +++ b/src/core/builder/mod.rs @@ -33,6 +33,12 @@ pub struct BuildContext<'a> { pub fn build(ctx: &BuildContext) -> Result { let compiler = ctx.compiler.to_lowercase(); + if !check_compiler_exists(ctx.compiler) { + return Err(format!( + "Compiler not found: {}. Make sure it is installed and available in PATH.", + ctx.compiler + )); + } if compiler.contains("clang-cl") { return msvc::build(ctx); } @@ -48,6 +54,14 @@ pub fn build(ctx: &BuildContext) -> Result { unix_cc::build(ctx) } +fn check_compiler_exists(compiler: &str) -> bool { + let name = if compiler.is_empty() { "cc" } else { compiler }; + std::process::Command::new(name) + .arg("--version") + .output() + .is_ok() +} + pub fn collect_sources(ctx: &BuildContext) -> Result, String> { let compiler = ctx.compiler.to_lowercase(); if compiler.contains("clang-cl") || compiler == "cl" || compiler.contains("msvc") { diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index 49b3c96..3cca499 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -18,7 +18,10 @@ pub fn build(ctx: &BuildContext) -> Result { } let start_time = Instant::now(); let sources = collect_sources(ctx)?; - let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let obj_dir = match ctx.target_dir { + Some(dir) => Path::new(dir).join("obj"), + None => Path::new("./target").join(ctx.profile).join("obj"), + }; let objects = build_objects(compiler, &sources, &obj_dir, ctx, "obj")?; if ctx.kind == "staticlib" { @@ -92,6 +95,8 @@ pub fn build(ctx: &BuildContext) -> Result { } let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, ctx.project_name, ctx.target_dir) + } else if ctx.kind == "elf" { + platform::elf_path(ctx.profile, ctx.project_name, ctx.target_dir) } else { platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) }; @@ -185,8 +190,6 @@ fn default_flags(profile: &str) -> &'static [&'static str] { } } -static OUTPUT_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); - fn build_objects( compiler: &str, sources: &[String], @@ -282,7 +285,7 @@ fn build_object( } } - let _lock = OUTPUT_MUTEX.lock().unwrap(); + let _lock = common::get_output_lock().lock().unwrap(); if !output.status.success() { eprint!("{}", clean_stdout); diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs index a44f8c9..35a522f 100644 --- a/src/core/builder/nasm.rs +++ b/src/core/builder/nasm.rs @@ -18,7 +18,10 @@ pub fn build(ctx: &BuildContext) -> Result { }; let start_time = Instant::now(); let sources = collect_sources(ctx)?; - let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let obj_dir = match ctx.target_dir { + Some(dir) => Path::new(dir).join("obj"), + None => Path::new("./target").join(ctx.profile).join("obj"), + }; let objects = build_objects(assembler, &sources, &obj_dir, ctx, "o")?; if ctx.kind == "staticlib" { @@ -80,6 +83,8 @@ pub fn build(ctx: &BuildContext) -> Result { let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) + } else if ctx.kind == "elf" { + platform::elf_path(ctx.profile, &final_name, ctx.target_dir) } else { platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index 25c04d7..1e9d8db 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -20,7 +20,10 @@ pub fn build(ctx: &BuildContext) -> Result { ctx.exclude_dirs, ctx.include_paths, )?; - let obj_dir = Path::new("./target").join(ctx.profile).join("obj"); + let obj_dir = match ctx.target_dir { + Some(dir) => Path::new(dir).join("obj"), + None => Path::new("./target").join(ctx.profile).join("obj"), + }; let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o")?; if ctx.kind == "staticlib" { @@ -51,6 +54,12 @@ pub fn build(ctx: &BuildContext) -> Result { cmd.arg("-shared"); } } + if ctx.kind == "efi" { + cmd.arg("-shared"); + cmd.arg("-nostdlib"); + cmd.arg("-Wl,-dll"); + cmd.arg("-Wl,--subsystem,10"); + } for obj in &objects { cmd.arg(obj); } @@ -73,6 +82,10 @@ pub fn build(ctx: &BuildContext) -> Result { let out_path = if ctx.kind == "sharedlib" { platform::shared_lib_path(ctx.profile, &final_name, ctx.target_dir) + } else if ctx.kind == "efi" { + platform::efi_path(ctx.profile, &final_name, ctx.target_dir) + } else if ctx.kind == "elf" { + platform::elf_path(ctx.profile, &final_name, ctx.target_dir) } else { platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; diff --git a/src/core/config.rs b/src/core/config.rs index 96a0afc..393f17b 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -10,6 +10,7 @@ const DEFAULT_LANGUAGE: &str = "c"; const DEFAULT_STANDARD: &str = "c11"; const DEFAULT_COMPILER: &str = "clang"; const DEFAULT_KIND: &str = "bin"; +const VALID_KINDS: &[&str] = &["bin", "staticlib", "sharedlib", "efi", "elf"]; #[derive(Debug)] pub enum ConfigError { @@ -242,7 +243,7 @@ impl Config { validate_toolchain(self.typed.toolchain.as_ref())?; if let Some(kind) = &self.typed.build.kind { let kind = kind.trim(); - if !kind.is_empty() && kind != "bin" && kind != "staticlib" && kind != "sharedlib" { + if !kind.is_empty() && !VALID_KINDS.contains(&kind) { return Err(ConfigError::Invalid("build.kind is invalid".into())); } } @@ -410,7 +411,7 @@ impl Config { } if let Some(kind) = table.get("kind").and_then(|v| v.as_str()) { let kind = kind.trim(); - if !kind.is_empty() && kind != "bin" && kind != "staticlib" && kind != "sharedlib" { + if !kind.is_empty() && !VALID_KINDS.contains(&kind) { return Err(ConfigError::Invalid(format!( "build.{profile}.kind is invalid" ))); @@ -919,14 +920,16 @@ mod tests { #[test] fn validate_valid_kinds() { - for kind in ["bin", "staticlib", "sharedlib"] { - let dir = temp_dir(&format!("kind_{kind}")); - let content = format!( + for &kind in VALID_KINDS { + let dir = temp_dir("valid_kind"); + let toml = format!( "[package]\nname = \"test\"\nversion = \"0.1.0\"\n\n[build]\nlanguage = \"c\"\nstandard = \"c11\"\ncompiler = \"clang\"\nkind = \"{kind}\"\n\n[dependencies]\n" ); - let path = write_toml_file(&dir, &content); - let result = Config::open(&path.to_string_lossy()); - assert!(result.is_ok(), "kind '{kind}' should be valid"); + let path = write_toml_file(&dir, &toml); + assert!( + Config::open(&path.to_string_lossy()).is_ok(), + "kind '{kind}' should be valid" + ); } } diff --git a/src/core/deps/mod.rs b/src/core/deps/mod.rs index 1413b35..d176d27 100644 --- a/src/core/deps/mod.rs +++ b/src/core/deps/mod.rs @@ -5,8 +5,20 @@ pub mod register; use crate::core::config::Config; use crate::core::deps::common::ResolvedDeps; +use crate::core::deps::lock::{DepLock, write_lock}; use std::path::Path; +fn dep_version(path: &Path) -> String { + Config::open(&path.join("dcr.toml").to_string_lossy()) + .ok() + .and_then(|c| { + c.get("package.version") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + }) + .unwrap_or_default() +} + pub fn resolve_deps( config: &Config, _profile: &str, @@ -26,7 +38,7 @@ pub fn resolve_deps( .to_string(); let deps_table = config.get("dependencies").and_then(|v| v.as_table()); - let lock_packages = Vec::new(); + let mut lock_packages: Vec = Vec::new(); if let Some(deps) = deps_table { for (name, value) in deps { @@ -45,6 +57,23 @@ pub fn resolve_deps( .to_string(), ); resolved.libs.push(name.clone()); + + lock_packages.push(DepLock { + name: name.clone(), + version: pkg_info + .get("version") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(), + checksum: String::new(), + source: format!( + "registry+{}", + pkg_info + .get("registry_url") + .and_then(|v| v.as_str()) + .unwrap_or("https://dcr-registry.pages.dev") + ), + }); } else if let Some(path) = path_dep_path(value) { let dep_root = project_root.join(path); if let Some(table) = value.as_table() { @@ -86,11 +115,25 @@ pub fn resolve_deps( push_default_lib_dirs(&mut resolved.lib_dirs, &dep_root); resolved.libs.push(name.clone()); } + + lock_packages.push(DepLock { + name: name.clone(), + version: dep_version(&dep_root), + checksum: String::new(), + source: format!("path+{}", dep_root.display()), + }); + } else if let Some(git_info) = git_dep(value) { + lock_packages.push(DepLock { + name: name.clone(), + version: git_info.version.unwrap_or_default(), + checksum: String::new(), + source: format!("git+{}", git_info.url), + }); } } } - crate::core::deps::lock::write_lock( + write_lock( project_root, &project_name, &project_version, @@ -100,6 +143,24 @@ pub fn resolve_deps( Ok(resolved) } +struct GitDep<'a> { + url: &'a str, + version: Option, +} + +fn git_dep(value: &toml::Value) -> Option> { + if let Some(table) = value.as_table() + && let Some(url) = table.get("git").and_then(|v| v.as_str()) + { + let version = table + .get("version") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + return Some(GitDep { url, version }); + } + None +} + fn path_dep_path(value: &toml::Value) -> Option<&str> { if let Some(table) = value.as_table() { return table.get("path").and_then(|v| v.as_str()); diff --git a/src/core/deps/register.rs b/src/core/deps/register.rs index a3d1d4a..aed0463 100644 --- a/src/core/deps/register.rs +++ b/src/core/deps/register.rs @@ -51,10 +51,18 @@ pub fn get_index_path() -> PathBuf { } pub fn get_registry_cache_root() -> PathBuf { - get_index_path() - .parent() - .map(Path::to_path_buf) - .unwrap_or_else(|| PathBuf::from(".")) + let index_path = get_index_path(); + if let Some(parent) = index_path.parent() { + if parent.exists() { + return parent.to_path_buf(); + } + if let Err(e) = std::fs::create_dir_all(parent) { + eprintln!("Warning: failed to create registry cache directory: {e}"); + } + parent.to_path_buf() + } else { + PathBuf::from(".") + } } pub fn package_root_from_registry_info(pkg_info: &JsonValue) -> Result { @@ -124,12 +132,16 @@ pub fn is_registry_dep(value: &TomlValue) -> bool { return !is_path_like_string(raw) && !is_git_like_string(raw); } if let Some(table) = value.as_table() { - return !table.contains_key("git") - && !table.contains_key("path") - && !table.contains_key("url") - && !table.contains_key("version"); + if table.contains_key("git") || table.contains_key("path") || table.contains_key("url") { + return false; + } + table.contains_key("version") + || table.contains_key("features") + || table.contains_key("optional") + || table.contains_key("registry") + } else { + false } - false } pub fn path_from_string_dep(value: &TomlValue) -> Option<&str> { diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 2f4b802..2768e72 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -12,14 +12,44 @@ 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 { match target_dir { Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name), - None => format!("./target/{profile}/lib{name}.a"), + None => { + let arch = std::env::consts::ARCH; + let target = format!("{arch}-unknown-linux-gnu"); + format!("./target/{target}/{profile}/lib{name}.a") + } + } +} + +pub fn elf_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name), + None => { + let arch = std::env::consts::ARCH; + let target = format!("{arch}-unknown-linux-gnu"); + format!("./target/{target}/{profile}/{name}") + } + } +} + +pub fn efi_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}.efi", dir.trim_end_matches('/'), name), + None => { + let arch = std::env::consts::ARCH; + let target = format!("{arch}-unknown-linux-gnu"); + format!("./target/{target}/{profile}/{name}.efi") + } } } pub fn shared_lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { match target_dir { Some(dir) => format!("{}/lib{}.so", dir.trim_end_matches('/'), name), - None => format!("./target/{profile}/lib{name}.so"), + None => { + let arch = std::env::consts::ARCH; + let target = format!("{arch}-unknown-linux-gnu"); + format!("./target/{target}/{profile}/lib{name}.so") + } } } @@ -53,10 +83,9 @@ mod tests { #[test] fn lib_path_default() { - assert_eq!( - lib_path("debug", "mylib", None), - "./target/debug/libmylib.a" - ); + let arch = std::env::consts::ARCH; + let expected = format!("./target/{arch}-unknown-linux-gnu/debug/libmylib.a"); + assert_eq!(lib_path("debug", "mylib", None), expected); } #[test] @@ -66,10 +95,9 @@ mod tests { #[test] fn shared_lib_path_default() { - assert_eq!( - shared_lib_path("debug", "mylib", None), - "./target/debug/libmylib.so" - ); + let arch = std::env::consts::ARCH; + let expected = format!("./target/{arch}-unknown-linux-gnu/debug/libmylib.so"); + assert_eq!(shared_lib_path("debug", "mylib", None), expected); } #[test] diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 339529d..bb651a2 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -12,6 +12,20 @@ pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { } } +pub fn elf_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}"), + } +} + +pub fn efi_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}.efi", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}.efi"), + } +} + pub fn shared_lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { match target_dir { Some(dir) => format!("{}/lib{}.dylib", dir.trim_end_matches('/'), name), diff --git a/src/platform/mod.rs b/src/platform/mod.rs index c082f79..a0741b3 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -27,6 +27,50 @@ pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { } } +pub fn elf_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + #[cfg(target_os = "linux")] + { + linux::elf_path(profile, name, target_dir) + } + #[cfg(target_os = "macos")] + { + return macos::elf_path(profile, name, target_dir); + } + #[cfg(target_os = "windows")] + { + return windows::elf_path(profile, name, target_dir); + } + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] + { + match target_dir { + Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}"), + } + } +} + +pub fn efi_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + #[cfg(target_os = "linux")] + { + linux::efi_path(profile, name, target_dir) + } + #[cfg(target_os = "macos")] + { + return macos::efi_path(profile, name, target_dir); + } + #[cfg(target_os = "windows")] + { + return windows::efi_path(profile, name, target_dir); + } + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] + { + match target_dir { + Some(dir) => format!("{}/{}.efi", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}.efi"), + } + } +} + pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { #[cfg(target_os = "linux")] { diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 71707aa..d4fa9f3 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -12,6 +12,20 @@ pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { } } +pub fn elf_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}.exe", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}.exe"), + } +} + +pub fn efi_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { + match target_dir { + Some(dir) => format!("{}/{}.efi", dir.trim_end_matches('/'), name), + None => format!("./target/{profile}/{name}.efi"), + } +} + pub fn shared_lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String { match target_dir { Some(dir) => format!("{}/{}.dll", dir.trim_end_matches('/'), name), From 4e3601b1dfbb2ace9ffdb04b39fee33c27ae93ba Mon Sep 17 00:00:00 2001 From: Dexoron Date: Tue, 26 May 2026 17:57:37 +0800 Subject: [PATCH 09/12] =?UTF-8?q?Changed:=20`openssl`=20dependency=20is=20?= =?UTF-8?q?now=20conditional=20(`cfg(not(windows))`)=20=E2=80=94=20no=20lo?= =?UTF-8?q?nger=20pulled=20in=20on=20Windows,=20fixing=20Windows=20CI=20bu?= =?UTF-8?q?ilds=20that=20lack=20Perl=20`Locale::Maketext::Simple`.=20On=20?= =?UTF-8?q?Linux=20(including=20musl=20cross-compilation)=20the=20vendored?= =?UTF-8?q?=20OpenSSL=20build=20is=20unchanged.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ Cargo.lock | 4 ++-- Cargo.toml | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 455c7b6..0fca4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,10 @@ Fixed: - Consolidated duplicate `OUTPUT_MUTEX` definitions — MSVC backend now uses the shared mutex from `common.rs`. - Registry cache root directory is created if missing (prevents cryptic errors when `~/.dcr/` does not exist). +Changed: + +- `openssl` dependency is now conditional (`cfg(not(windows))`) — no longer pulled in on Windows, fixing Windows CI builds that lack Perl `Locale::Maketext::Simple`. On Linux (including musl cross-compilation) the vendored OpenSSL build is unchanged. + ## [0.6.6] - 2026-05-18 Fixed: diff --git a/Cargo.lock b/Cargo.lock index dceb3b6..78b8f24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,9 +779,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] diff --git a/Cargo.toml b/Cargo.toml index 4f705cc..95de956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,6 @@ toml = "1.0.3" git2 = "0.20.4" serde_json = "1" ctrlc = "3" -openssl = { version = "0.10", features = ["vendored"] } \ No newline at end of file + +[target.'cfg(not(windows))'.dependencies] +openssl = { version = "0.10", features = ["vendored"] } From 2af3baf4bbeac452acb31601c3950e4ed8a01c38 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Tue, 26 May 2026 19:16:09 +0800 Subject: [PATCH 10/12] feat: enhance release workflow with additional input options and build configurations for Linux, macOS, and Windows --- .github/workflows/release.yml | 236 ++++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 85 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a060ddf..22b0d20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,8 +7,28 @@ on: workflow_dispatch: inputs: tag: - description: "Tag to build and upload assets for (e.g. v0.6.5)" + description: "Tag (or branch) to build" required: true + run_notes: + description: "Generate release notes" + type: boolean + default: true + run_linux: + description: "Build Linux (x86_64 + aarch64 + musl)" + type: boolean + default: true + run_macos: + description: "Build macOS (x86_64 + aarch64)" + type: boolean + default: true + run_windows: + description: "Build Windows (MSVC + GNU)" + type: boolean + default: true + run_publish: + description: "Publish to crates.io" + type: boolean + default: false permissions: contents: write @@ -19,14 +39,12 @@ jobs: steps: - uses: actions/checkout@v4 with: - # При ручном запуске чекаутим нужный тег ref: ${{ github.event.inputs.tag || github.ref }} - name: Prepare Release Notes shell: bash run: | set -euo pipefail - # При ручном запуске берём версию из инпута, иначе из тега if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then tag="${{ github.event.inputs.tag }}" tag="${tag#v}" @@ -51,56 +69,19 @@ jobs: name: release-notes path: .github/release-notes.md - build: + # ── Linux ────────────────────────────────────────────────────────────────── + build-linux: + if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }} + needs: notes + runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - # ── Linux GNU ────────────────────────────────────────────────────── - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - apt: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - env_cc: CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc - env_linker: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc - - # ── Linux Musl ───────────────────────────────────────────────────── - - os: ubuntu-latest - target: x86_64-unknown-linux-musl - apt: musl-tools - env_cc: CC_x86_64_unknown_linux_musl=musl-gcc - env_linker: CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc - - - os: ubuntu-latest - target: aarch64-unknown-linux-musl - musl_cross: aarch64-linux-musl-cross - env_cc: CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc - env_linker: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc - - # ── macOS ────────────────────────────────────────────────────────── - - os: macos-15-intel - target: x86_64-apple-darwin - - - os: macos-latest - target: aarch64-apple-darwin - - # ── Windows MSVC ─────────────────────────────────────────────────── - - os: windows-latest - target: x86_64-pc-windows-msvc - - - os: windows-latest - target: aarch64-pc-windows-msvc - - # ── Windows GNU ──────────────────────────────────────────────────── - - os: windows-latest - target: x86_64-pc-windows-gnu - - # gnullvm исключён: LLVM MinGW не предустановлен на GitHub Actions - - runs-on: ${{ matrix.os }} - needs: notes + - target: x86_64-unknown-linux-gnu + - target: aarch64-unknown-linux-gnu + - target: x86_64-unknown-linux-musl + - target: aarch64-unknown-linux-musl steps: - uses: actions/checkout@v4 with: @@ -112,63 +93,133 @@ jobs: name: release-notes path: .github - - name: Install cargo-zigbuild (Linux cross-compilation) - if: runner.os == 'Linux' + - name: Install cargo-zigbuild run: | pip install cargo-zigbuild echo "$HOME/.local/bin" >> $GITHUB_PATH - - name: Setup LLVM + MinGW (gnullvm) - if: contains(matrix.target, 'gnullvm') - shell: pwsh - run: | - choco install llvm mingw -y --no-progress - echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Build - shell: bash run: | - if [ "${{ runner.os }}" = "Linux" ] && [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then + if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then cargo zigbuild --release --target ${{ matrix.target }} else cargo build --release --target ${{ matrix.target }} fi - - name: Derive version string - shell: bash + - name: Derive version & asset name run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - RAW_TAG="${{ github.event.inputs.tag }}" - else - RAW_TAG="${GITHUB_REF_NAME}" - fi + RAW_TAG="${{ github.event.inputs.tag || github.ref_name }}" echo "RELEASE_TAG=${RAW_TAG}" >> "$GITHUB_ENV" echo "RELEASE_VERSION=${RAW_TAG#v}" >> "$GITHUB_ENV" + echo "ASSET_NAME=dcr-${{ matrix.target }}-${RAW_TAG#v}" >> "$GITHUB_ENV" - - name: Derive asset name (Unix) - if: runner.os != 'Windows' - shell: bash - run: | - echo "ASSET_NAME=dcr-${{ matrix.target }}-${RELEASE_VERSION}" >> "$GITHUB_ENV" - - - name: Derive asset name (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - "ASSET_NAME=dcr-${{ matrix.target }}-$($env:RELEASE_VERSION).exe" >> $env:GITHUB_ENV - - - name: Prepare Artifact (Unix) - if: runner.os != 'Windows' - shell: bash + - name: Prepare Artifact run: cp target/${{ matrix.target }}/release/dcr "$ASSET_NAME" - - name: Prepare Artifact (Windows) - if: runner.os == 'Windows' + - name: Upload Release Asset + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_TAG }} + files: ${{ env.ASSET_NAME }} + body_path: .github/release-notes.md + prerelease: ${{ contains(env.RELEASE_TAG, '-') }} + + # ── macOS ────────────────────────────────────────────────────────────────── + build-macos: + if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }} + needs: notes + strategy: + fail-fast: false + matrix: + include: + - os: macos-15-intel + target: x86_64-apple-darwin + - os: macos-latest + target: aarch64-apple-darwin + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Download Release Notes + uses: actions/download-artifact@v4 + with: + name: release-notes + path: .github + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Build + run: cargo build --release --target ${{ matrix.target }} + + - name: Derive version & asset name + run: | + RAW_TAG="${{ github.event.inputs.tag || github.ref_name }}" + echo "RELEASE_TAG=${RAW_TAG}" >> "$GITHUB_ENV" + echo "RELEASE_VERSION=${RAW_TAG#v}" >> "$GITHUB_ENV" + echo "ASSET_NAME=dcr-${{ matrix.target }}-${RAW_TAG#v}" >> "$GITHUB_ENV" + + - name: Prepare Artifact + run: cp target/${{ matrix.target }}/release/dcr "$ASSET_NAME" + + - name: Upload Release Asset + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_TAG }} + files: ${{ env.ASSET_NAME }} + body_path: .github/release-notes.md + prerelease: ${{ contains(env.RELEASE_TAG, '-') }} + + # ── Windows ──────────────────────────────────────────────────────────────── + build-windows: + if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }} + needs: notes + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-pc-windows-msvc + - target: aarch64-pc-windows-msvc + - target: x86_64-pc-windows-gnu + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Download Release Notes + uses: actions/download-artifact@v4 + with: + name: release-notes + path: .github + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Build + run: cargo build --release --target ${{ matrix.target }} + + - name: Derive version & asset name + shell: pwsh + run: | + $raw = "${{ github.event.inputs.tag || github.ref_name }}" + $ver = $raw -replace '^v', '' + "RELEASE_TAG=$raw" >> $env:GITHUB_ENV + "RELEASE_VERSION=$ver" >> $env:GITHUB_ENV + "ASSET_NAME=dcr-${{ matrix.target }}-${ver}.exe" >> $env:GITHUB_ENV + + - name: Prepare Artifact shell: pwsh run: Copy-Item "target/${{ matrix.target }}/release/dcr.exe" "$env:ASSET_NAME" @@ -178,4 +229,19 @@ jobs: tag_name: ${{ env.RELEASE_TAG }} files: ${{ env.ASSET_NAME }} body_path: .github/release-notes.md - prerelease: ${{ contains(env.RELEASE_TAG, '-') }} \ No newline at end of file + prerelease: ${{ contains(env.RELEASE_TAG, '-') }} + + # ── crates.io ────────────────────────────────────────────────────────────── + publish-crates: + if: ${{ (github.event_name == 'push' || github.event.inputs.run_publish == 'true') && !contains(github.event.inputs.tag || github.ref_name, '-') }} + needs: notes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - uses: dtolnay/rust-toolchain@stable + + - name: Publish to crates.io + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file From 2a9c91871198fd1fc43ccb39a8172655bb097565 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Tue, 26 May 2026 19:36:39 +0800 Subject: [PATCH 11/12] fix: add missing package metadata including description, license, and repository URL --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 95de956..74158bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "dcr" version = "0.6.7" edition = "2024" build = "build.rs" +description = "DCR is a utility for managing C/C++ projects in a Cargo-like style." +license = "MIT" +repository = "https://github.com/dexoron/dcr" [dependencies] glob = "0.3" From b66c846956272295dcd830bc30360c4a1e4ed419 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Tue, 26 May 2026 19:39:35 +0800 Subject: [PATCH 12/12] feat: improve release workflow with enhanced tag handling and clearer input descriptions --- .github/workflows/release.yml | 115 +++++++++++++++++----------------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22b0d20..fa2b0df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,22 +7,18 @@ on: workflow_dispatch: inputs: tag: - description: "Tag (or branch) to build" + description: "Tag to build (e.g. v0.6.7)" required: true - run_notes: - description: "Generate release notes" - type: boolean - default: true run_linux: - description: "Build Linux (x86_64 + aarch64 + musl)" + description: "Build Linux" type: boolean default: true run_macos: - description: "Build macOS (x86_64 + aarch64)" + description: "Build macOS" type: boolean default: true run_windows: - description: "Build Windows (MSVC + GNU)" + description: "Build Windows" type: boolean default: true run_publish: @@ -34,23 +30,42 @@ permissions: contents: write jobs: + # ── Resolve Tag ──────────────────────────────────────────────────────────── + setup: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.tag.outputs.value }} + version: ${{ steps.tag.outputs.version }} + prerelease: ${{ steps.tag.outputs.prerelease }} + steps: + - name: Resolve tag + id: tag + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${GITHUB_REF_NAME}" + fi + VERSION="${TAG#v}" + [[ "$TAG" == *"-"* ]] && PRERELEASE="true" || PRERELEASE="false" + echo "value=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT" + + # ── Release Notes ────────────────────────────────────────────────────────── notes: + needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.tag || github.ref }} + ref: ${{ needs.setup.outputs.tag }} - name: Prepare Release Notes shell: bash run: | set -euo pipefail - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - tag="${{ github.event.inputs.tag }}" - tag="${tag#v}" - else - tag="${GITHUB_REF_NAME#v}" - fi + tag="${{ needs.setup.outputs.version }}" notes_file=".github/release-notes.md" if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then awk -v ver="$tag" ' @@ -72,7 +87,7 @@ jobs: # ── Linux ────────────────────────────────────────────────────────────────── build-linux: if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }} - needs: notes + needs: [setup, notes] runs-on: ubuntu-latest strategy: fail-fast: false @@ -85,7 +100,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.tag || github.ref }} + ref: ${{ needs.setup.outputs.tag }} - name: Download Release Notes uses: actions/download-artifact@v4 @@ -111,28 +126,24 @@ jobs: cargo build --release --target ${{ matrix.target }} fi - - name: Derive version & asset name - run: | - RAW_TAG="${{ github.event.inputs.tag || github.ref_name }}" - echo "RELEASE_TAG=${RAW_TAG}" >> "$GITHUB_ENV" - echo "RELEASE_VERSION=${RAW_TAG#v}" >> "$GITHUB_ENV" - echo "ASSET_NAME=dcr-${{ matrix.target }}-${RAW_TAG#v}" >> "$GITHUB_ENV" - - name: Prepare Artifact - run: cp target/${{ matrix.target }}/release/dcr "$ASSET_NAME" + run: | + ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}" + cp target/${{ matrix.target }}/release/dcr "$ASSET" + echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV" - name: Upload Release Asset uses: softprops/action-gh-release@v2 with: - tag_name: ${{ env.RELEASE_TAG }} + tag_name: ${{ needs.setup.outputs.tag }} files: ${{ env.ASSET_NAME }} body_path: .github/release-notes.md - prerelease: ${{ contains(env.RELEASE_TAG, '-') }} + prerelease: ${{ needs.setup.outputs.prerelease == 'true' }} # ── macOS ────────────────────────────────────────────────────────────────── build-macos: if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }} - needs: notes + needs: [setup, notes] strategy: fail-fast: false matrix: @@ -145,7 +156,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.tag || github.ref }} + ref: ${{ needs.setup.outputs.tag }} - name: Download Release Notes uses: actions/download-artifact@v4 @@ -161,28 +172,24 @@ jobs: - name: Build run: cargo build --release --target ${{ matrix.target }} - - name: Derive version & asset name - run: | - RAW_TAG="${{ github.event.inputs.tag || github.ref_name }}" - echo "RELEASE_TAG=${RAW_TAG}" >> "$GITHUB_ENV" - echo "RELEASE_VERSION=${RAW_TAG#v}" >> "$GITHUB_ENV" - echo "ASSET_NAME=dcr-${{ matrix.target }}-${RAW_TAG#v}" >> "$GITHUB_ENV" - - name: Prepare Artifact - run: cp target/${{ matrix.target }}/release/dcr "$ASSET_NAME" + run: | + ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}" + cp target/${{ matrix.target }}/release/dcr "$ASSET" + echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV" - name: Upload Release Asset uses: softprops/action-gh-release@v2 with: - tag_name: ${{ env.RELEASE_TAG }} + tag_name: ${{ needs.setup.outputs.tag }} files: ${{ env.ASSET_NAME }} body_path: .github/release-notes.md - prerelease: ${{ contains(env.RELEASE_TAG, '-') }} + prerelease: ${{ needs.setup.outputs.prerelease == 'true' }} # ── Windows ──────────────────────────────────────────────────────────────── build-windows: if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }} - needs: notes + needs: [setup, notes] runs-on: windows-latest strategy: fail-fast: false @@ -194,7 +201,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.tag || github.ref }} + ref: ${{ needs.setup.outputs.tag }} - name: Download Release Notes uses: actions/download-artifact@v4 @@ -210,36 +217,32 @@ jobs: - name: Build run: cargo build --release --target ${{ matrix.target }} - - name: Derive version & asset name - shell: pwsh - run: | - $raw = "${{ github.event.inputs.tag || github.ref_name }}" - $ver = $raw -replace '^v', '' - "RELEASE_TAG=$raw" >> $env:GITHUB_ENV - "RELEASE_VERSION=$ver" >> $env:GITHUB_ENV - "ASSET_NAME=dcr-${{ matrix.target }}-${ver}.exe" >> $env:GITHUB_ENV - - name: Prepare Artifact shell: pwsh - run: Copy-Item "target/${{ matrix.target }}/release/dcr.exe" "$env:ASSET_NAME" + run: | + $asset = "dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}.exe" + Copy-Item "target/${{ matrix.target }}/release/dcr.exe" "$asset" + "ASSET_NAME=$asset" >> $env:GITHUB_ENV - name: Upload Release Asset uses: softprops/action-gh-release@v2 with: - tag_name: ${{ env.RELEASE_TAG }} + tag_name: ${{ needs.setup.outputs.tag }} files: ${{ env.ASSET_NAME }} body_path: .github/release-notes.md - prerelease: ${{ contains(env.RELEASE_TAG, '-') }} + prerelease: ${{ needs.setup.outputs.prerelease == 'true' }} # ── crates.io ────────────────────────────────────────────────────────────── publish-crates: - if: ${{ (github.event_name == 'push' || github.event.inputs.run_publish == 'true') && !contains(github.event.inputs.tag || github.ref_name, '-') }} - needs: notes + if: | + (github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') || + (github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true') + needs: [setup, build-linux, build-macos, build-windows] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.tag || github.ref }} + ref: ${{ needs.setup.outputs.tag }} - uses: dtolnay/rust-toolchain@stable