245 Commits

Author SHA1 Message Date
cb6ae3dcc7 Merge pull request #40 from alex-xr555/log 2026-09-19 09:28:18 +08:00
alex-xr555
10123ddedb Translation of comments into English, minor changes for completeness. 2026-09-15 18:34:54 +03:00
alex-xr555
0a140c75d8 Clarified the use block 2026-09-15 18:34:54 +03:00
alex-xr555
aa1d4ba807 The logging system has been brought to a fully functional level. 2026-09-15 18:34:54 +03:00
alex-xr555
991d1c20af Absolutely all logs have been replaced with the new system on log. 2026-09-15 18:34:54 +03:00
alex-xr555
df674defe1 Start of the logging system replacement 2026-09-15 18:34:54 +03:00
7d2340fb52 Merge pull request #39 from dexoron/dev
v0.9.1
2026-09-13 15:02:16 +08:00
6c79340736 feat: add package name validation in default manifest generation; clarify pkg-config dependency requirements 2026-09-13 14:50:40 +08:00
be14cfb8e2 Update release date for version 0.9.1 2026-09-13 13:58:26 +08:00
3305915c07 Update release date for version 0.9.1 2026-09-13 13:58:04 +08:00
5cb51878a5 feat: add pkg-config system dependencies and fix dcr new in non-ascii paths 2026-09-08 20:40:49 +08:00
d587eba5a4 Update release.yml to use VERSION variable 2026-09-05 21:39:33 +08:00
bfd2b9d251 Merge pull request #36 from dexoron/dev
Release v0.9.0
2026-08-31 14:16:57 +08:00
1e3eb658dc test: normalize git dependency paths on Windows 2026-08-31 13:58:42 +08:00
f5dad74498 Release v0.9.0: complete path and git dependencies 2026-08-31 13:42:16 +08:00
3c25539c11 Merge pull request #35 from dexoron/master
update dev branch
2026-08-29 12:33:55 +08:00
54653dfd80 feat: add download mirrors and drop legacy install scripts
Introduce `--mirror` and `DCRUP_MIRROR` so `dcrup` can fetch prebuilt
binaries from multiple CDNs. Default `auto` tries cf → gh → sf → ya
and uses the first successful download.

- dcrup.sh / dcrup.ps1: mirror chain, CLI flag, env override
- remove install.sh / install.ps1 / install_bsd.sh (superseded by dcrup)
- GitHub Actions: upload releases to Cloudflare R2 next to Yandex S3
- docs: README, FAQ, changelog, installation, env vars, contributing
2026-08-13 12:59:12 +08:00
f3605a86a2 Add Yandex S3 2026-08-12 23:35:44 +08:00
9d21294cb4 Update README.md 2026-08-12 21:27:21 +08:00
8609d12179 fix release sync 2026-08-12 20:49:36 +08:00
b6ecb2aa79 fix release sync 2026-08-12 20:48:12 +08:00
dd89109f92 fix release sync 2026-08-12 20:30:45 +08:00
c6af6c5196 fix release sync 2026-08-12 20:25:36 +08:00
b6153dbaac fix release sync 2026-08-12 20:13:30 +08:00
9e2b7a897a fix release sync 2026-08-12 19:45:38 +08:00
9add069879 fix release sync 2026-08-12 19:24:31 +08:00
5eb00037ea fix release sync 2026-08-12 19:14:13 +08:00
f7353f9d03 fix git sync 2026-08-12 18:03:20 +08:00
05e49838f7 feat: add automated repository mirroring workflows and update docs
- Add GitHub Actions workflows for code and release mirroring
- Remove manual sync-releases-to-gitlab.py script
- Update .gitignore to track .github/ directory
- Document repository mirrors and VS Code extension recommendations
2026-08-12 17:58:30 +08:00
b0e426c189 Merge pull request #32 from dexoron/dev
feat: add package.dcr-version pin with load-time compatibility warnings
2026-08-08 15:00:02 +08:00
60d7420929 feat: add package.dcr-version pin with load-time compatibility warnings 2026-08-08 14:33:46 +08:00
3bd91e85f9 Merge pull request #31 from dexoron/dev
v0.8.4
2026-08-05 21:22:20 +08:00
a20c61cc88 docs: add and clean up AI-generated rustdoc comments (grok-4.5) 2026-08-05 19:21:00 +08:00
bc308d6996 fix: make cross-target tests and vscode launch path robust for macOS + Windows 2026-08-05 02:21:29 +08:00
09e7f5b880 fix(test): align vscode launch path assertion with native host layout 2026-08-05 01:04:43 +08:00
3f925f437f fix: keep native host artifact paths and cross-platform path assertions 2026-08-05 01:00:26 +08:00
b9cbef43e9 feat: add .dcr/ build facts and canonical artifact paths for IDE integrations 2026-08-05 00:38:00 +08:00
9bf1346d1b Merge pull request #30 from dexoron/dev
v0.8.3
2026-08-03 21:32:18 +08:00
794d571d58 fix: distinguish implicit host builds from explicit targets 2026-08-03 21:26:34 +08:00
d2df85a064 fix: preserve native artifact paths on Windows and macOS 2026-08-03 21:16:11 +08:00
5e8037da97 feat: host-target config resolution and cargo-style run args 2026-08-03 00:40:51 +08:00
f29322a547 Merge pull request #29 from dexoron/dev
v0.8.2
2026-07-24 19:15:55 +08:00
1a6a261cac fix(ci): fix tests 2026-07-23 21:35:59 +08:00
f92cc6ae91 fix(ci): fix tests 2026-07-23 19:59:37 +08:00
5aefbc6498 fix(ci): fix tests 2026-07-23 19:53:48 +08:00
594007f022 fix(ci): fix tests 2026-07-23 19:45:31 +08:00
d10ec84004 fix(ci): fix tests 2026-07-23 19:30:39 +08:00
574ba8b6d0 fix(ci): fix tests 2026-07-23 16:11:39 +08:00
75eace9d73 feat: add dcrup (sh/ps1/cmd) and fix cross-platform unit tests 2026-07-23 14:59:28 +08:00
2446cb7970 feat(docs): remove DPM in install steps 2026-07-23 14:03:08 +08:00
588e4dd13f feat(build): archive feature, progress UI, and workspace path fixes 2026-07-23 14:00:00 +08:00
689fea7465 Merge pull request #28 from dexoron/dev
v0.8.1
2026-07-21 18:12:56 +08:00
a9dd8c1631 feat(build): add flat-bin kind and FAT [archive] packing for OS-dev 2026-07-21 17:45:02 +08:00
9d030cba0c fix(build): skip linking/archiving for custom and none project kinds 2026-07-18 20:21:41 +08:00
1dcc8e614a Merge pull request #25 from dexoron/dev
v0.8.0
2026-07-18 20:05:12 +08:00
9eaa4635d5 refactor: modularize build core engine, update workspaces, cache, and test suite 2026-07-18 20:02:12 +08:00
a3f8444c20 Replace GitLab pipeline trigger with Cloudflare build 2026-07-05 18:24:47 +08:00
1f3fc62cb6 Add GitHub Actions workflow to trigger site rebuild 2026-07-05 18:16:45 +08:00
f8d8c95912 Rename сrowdin.yml to crowdin.yml (latin) 2026-07-05 17:26:00 +08:00
9e07f70c78 fix crowdin file name 2026-07-05 17:24:14 +08:00
0376eded3f add Crowdin 2026-07-05 17:21:33 +08:00
803d5891ce Merge pull request #21 from dexoron/dev
v0.7.4
2026-06-19 17:50:59 +08:00
9326e23a53 feat: add native Qt support and dcr lint command; refactor architecture and integration tests 2026-06-17 12:39:22 +08:00
21932ac94a feat(build): add freestanding, lto, strip, panic-abort, and codegen-units support in dcr.toml 2026-06-16 16:42:56 +08:00
97db82e4f8 Merge pull request #20 from dexoron/dev
v0.7.3
2026-06-15 22:36:12 +08:00
3d596d7280 Update install docs 2026-06-15 22:35:38 +08:00
5cb4473bd1 Update README.md 2026-06-15 22:35:06 +08:00
33f955d4e3 refactor(updater): migrate from reqwest to ureq for lightweight builds 2026-06-13 16:15:24 +08:00
838be72dd2 refactor(deps): replace git2 with system git and add vcs support 2026-06-13 15:28:14 +08:00
39fad31c4f Merge pull request #18 from dexoron/master
v0.7.2
2026-06-07 22:31:43 +08:00
c8ef38a99b release: bump version to 0.7.2 and update changelog 2026-06-07 21:41:02 +08:00
8e0c535cfd fix: improve default target triple generation for various OS environments 2026-06-07 21:29:55 +08:00
de88c160ad Merge pull request #16 from XiaoPengYouCode/codex/fix-macos-host-target-link 2026-06-07 21:21:53 +08:00
4daca925a0 Merge pull request #17 from dexoron/master
master -> dev
2026-06-07 21:17:14 +08:00
595b7ac105 Merge pull request #15 from XiaoPengYouCode/codex/fix-readme-badges 2026-06-07 21:13:10 +08:00
XiaoPengYouCode
11ae3762d0 fix: use host macOS target for default builds 2026-06-07 05:41:07 +08:00
XiaoPengYouCode
4c439317e3 docs: fix README badge rendering 2026-06-07 05:09:38 +08:00
0aa3f87f99 feat: temporarily disable Snap Store publishing and update README instructions 2026-06-06 22:18:03 +08:00
a6ce0a3332 feat: enhance release notes extraction and include release name in GitHub release 2026-06-06 21:41:50 +08:00
cea43e99c8 feat: add Windows target dependencies and update README for MinGW 2026-06-06 21:39:17 +08:00
de4fb0bc29 feat: add .SRCINFO for AUR package metadata 2026-06-06 21:33:34 +08:00
59e27b6f5d Merge pull request #14 from dexoron/dev
v0.7.1 The Multi-Arch Expansion
2026-06-06 21:28:24 +08:00
c8cb155e5a feat: update help flag 2026-06-06 21:22:46 +08:00
6424aab69d feet: add license to docs and updated Cargo.toml to add build with no features flag 2026-06-06 21:19:04 +08:00
peoplemiau1
dabeb411d3 docs: update changelog for 0.7.1 (bilingual & multi-arch) 2026-06-02 16:22:57 +03:00
peoplemiau1
b8338c752b chore: remove i686-pc-windows-gnu from build matrix 2026-06-02 16:00:08 +03:00
b70438b16d feat: update README 2026-06-02 19:38:16 +08:00
0597b5ddb2 feat: add --help for all commands, man pages 2026-06-02 19:27:44 +08:00
peoplemiau1
8866fb0796 Bump version to 0.7.1 2026-06-02 13:55:54 +03:00
peoplemiau1
7d853482aa feat: build deb and rpm packages for all linux architectures 2026-06-02 13:50:57 +03:00
peoplemiau1
ef9ca57295 Add multi-arch support to release workflows and installation scripts 2026-06-02 13:38:42 +03:00
peoplemiau1
8e1fb60692 Add Linux i686, armv7, and riscv64 architectures support to release workflow and installation script 2026-06-02 13:25:38 +03:00
peoplemiau1
d6ddb5da3d Merge origin/master into dev and resolve conflicts in release.yml 2026-06-02 13:06:15 +03:00
peoplemiau1
d26e93dd47 Optimize release binary size and parse_d_file dependency parsing 2026-06-02 12:58:06 +03:00
97d2970727 feat: add dcr-dev and dcr-dev-bin publishing to AUR 2026-06-02 17:34:17 +08:00
kie
35f9b6be94 Update README.md 2026-06-02 11:27:09 +03:00
6885a82be7 feat: add package name validation to init and new commands 2026-06-02 16:26:11 +08:00
5d587db227 fix: update AUR publishing process to handle git initialization and improve build flags 2026-06-02 16:08:58 +08:00
424f4f6929 feat: add support for publishing Snap packages with dynamic file resolution 2026-06-02 16:08:58 +08:00
443306aa5f fix: update AUR publishing process to handle git initialization and improve build flags 2026-06-02 15:17:20 +08:00
f21f50facb feat: add support for publishing Snap packages with dynamic file resolution 2026-06-02 01:27:04 +08:00
23c4f4ca87 Merge pull request #11 from dexoron/dev
Release v0.7.0
2026-06-02 01:10:33 +08:00
c42575e360 docs: rewrite documentation.
add: Nix and Snapcraft support.
2026-06-02 01:04:16 +08:00
c756d94f96 Merge pull request #10 and #8
build(nix): configure flake for dcr project
Update user input prompts to read from /dev/tty 
Create FUNDING.yml
2026-06-01 20:29:07 +08:00
Alexandr_Croitor
8e21432764 chore: remove unused dev shell tools 2026-06-01 13:23:12 +03:00
Alexandr_Croitor
772d50811f build(nix): configure flake for dcr project
- Set package name/version from Cargo.toml (dcr 0.6.9)
- Add cmake and perl for vendored libgit2/openssl builds
- Switch to stable toolchain (complete was overkill)
- Generate flake.lock
2026-05-31 22:37:57 +03:00
ea1546b13a Create FUNDING.yml 2026-06-01 02:49:06 +08:00
f5d5795872 docsL fix URL 2026-05-30 17:15:55 +08:00
e6788b4d71 fix: add missing .cxx/.cc/.hxx/.hh extensions to fmt; update docs for 0.7.0 2026-05-30 15:32:16 +08:00
865d8c52ff fix: split target into target (triple) and out_dir (output directory)
build.target now holds the target triple (e.g. x86_64-unknown-linux-gnu)
build.out_dir is a custom output directory that overrides the default
target/<triple>/<profile> path.
2026-05-30 14:18:54 +08:00
94aa6c0f01 Merge pull request #8 from babichgh/master
Update user input prompts to read from /dev/tty
2026-05-30 13:28:40 +08:00
John Babich
e462de6272 Update user input prompts to read from /dev/tty
Redirect input for user prompts to /dev/tty for better compatibility.
2026-05-29 23:58:03 +03:00
peoplemiau1
0587965283 Make standard optional, allow empty src_disable, and add none/custom kinds 2026-05-29 18:57:45 +03:00
peoplemiau1
478d1ad0c5 Remove DragonFly BSD support 2026-05-29 09:57:07 +03:00
peoplemiau1
060b92111f Fix dcr run to inherit stdout/stderr properly 2026-05-29 09:54:13 +03:00
peoplemiau1
86f0566dc2 Trigger release 2026-05-29 09:21:03 +03:00
peoplemiau1
768e2fc0dc Fix release creation race condition by adding a dedicated create-release job 2026-05-29 09:18:27 +03:00
peoplemiau1
619493bcbd Fix install.sh: support Linux ARM64 and BSD systems, add robust JSON parser checks 2026-05-29 09:13:37 +03:00
peoplemiau1
1dac8661b6 Implement incremental linking and add DragonFly BSD VM build 2026-05-29 09:09:03 +03:00
peoplemiau1
80a6d574a3 Rewrite install_bsd.sh for POSIX sh compliance 2026-05-29 08:27:23 +03:00
peoplemiau1
daf465368f Add BSD install script and update compatibility table in README 2026-05-29 07:51:10 +03:00
peoplemiau1
800fe38fba Fix GHA release: resolve stable toolchain override and sanitize Arch Linux package version 2026-05-28 23:20:48 +03:00
peoplemiau1
a7f9b79b93 Fix release CI/CD: make git2 target-specific, install zig for cross-compilation, set version to 0.7.0 2026-05-28 23:15:09 +03:00
peoplemiau1
b03660a9d7 Bump version to 0.7.0-dev and update changelog for pre-release 2026-05-28 23:01:15 +03:00
peoplemiau1
2cb02aa102 Add full FreeBSD, OpenBSD, NetBSD, and DragonflyBSD target support 2026-05-28 22:55:53 +03:00
peoplemiau1
06a8458ca7 feat: add fmt command 2026-05-28 22:55:26 +03:00
c2ef5483a0 update: update dexoron packages 2026-05-28 21:11:49 +08:00
eb24d616c5 fix: Fix GPG permissions after Docker 2026-05-28 18:16:18 +08:00
4f76f54b63 fix: update GPG signing process to resolve permission issues and clarify Release generation 2026-05-28 18:11:34 +08:00
37b9b855ec feat: streamline artifact download process and enhance repository indexing with signing 2026-05-28 18:08:06 +08:00
8f0f67ad62 fix: correct paths for RPM package artifacts and update repository indexing 2026-05-28 17:49:29 +08:00
49945aa65d feat: add --clobber flag to Download Packages from Release Assets 2026-05-28 17:44:51 +08:00
83593aa592 feat: enhance Dexoron Packages Index workflow with manual dispatch and improved artifact handling 2026-05-28 17:41:50 +08:00
2835188da9 feat: update Dexoron Packages Index workflow and improve release process for NetBSD 2026-05-28 17:09:36 +08:00
78160e2bb9 feat: set MAKE environment variable for NetBSD build process 2026-05-28 16:55:51 +08:00
7f9cd3d643 fix: correct path for Snap package output in release workflow 2026-05-28 16:53:14 +08:00
4d1bd1dbdc feat: update snap packaging directory structure and fix NetBSD package installation command 2026-05-28 16:43:27 +08:00
7bc571ea27 feat: update snap packaging paths and improve asset configuration in Cargo.toml 2026-05-28 16:10:28 +08:00
f431c3d018 feat: update package metadata for RPM generation in Cargo.toml and fix snap package path 2026-05-28 15:34:24 +08:00
9fd1652ebb chore: release v0.6.9 2026-05-28 15:18:03 +08:00
c59d6de54f feat: enhance release workflow with Arch Linux native build and AUR update options 2026-05-28 00:13:01 +08:00
73b6ce903e feat: add Arch Linux native package build process to release workflow 2026-05-28 00:07:36 +08:00
e97b9b6e00 feat: enhance package database update process with checks for existing files 2026-05-27 23:58:53 +08:00
12d1bc5111 feat: add Dexoron Packages Index workflow for automatic package updates 2026-05-27 23:55:11 +08:00
8c9dd39503 feat: update version to 0.6.9 and add packaging for Linux and BSD
- Bump version from 0.6.8 to 0.6.9 in Cargo.toml and Cargo.lock.
- Enhance GitHub Actions workflow to build and publish .deb, .rpm, and Snap packages.
- Implement AUR publishing for both dcr and dcr-bin packages.
- Add support for workspace-only projects in the build system.
- Introduce new `workspace` flag for build and run commands.
- Create snapcraft.yaml for Snap package configuration.
- Update build.rs and related files to handle workspace members and inheritance.
- Modify config handling to support optional package and build configurations.
2026-05-27 23:14:49 +08:00
e3c35793ec feat: enhance FreeBSD, OpenBSD, and NetBSD build processes in release workflow 2026-05-27 20:42:30 +08:00
a55d64c5ff feat: bump version to 0.6.8 in Cargo files 2026-05-27 20:18:49 +08:00
acdd1cc175 feat: enhance build configuration with C++ standard support and variable substitution
- Added `cxx_standard` field to the build configuration for specifying C++ standards.
- Updated documentation to reflect new `cxx_standard` and variable substitution capabilities in `cflags`, `ldflags`, and `include`.
- Implemented logic to handle C++ standard flags in the build process.
- Modified object file naming to include source file extensions.
- Updated various builder modules to support new language parsing logic.
- Introduced issue and feature request templates for better community engagement.
2026-05-27 20:04:01 +08:00
b5bbdb929b Add GPL-3.0-or-later license headers to source files
- Updated all relevant source files to include the new license header.
- Changed the default license in the configuration from MIT to GPL-3.0-or-later.
2026-05-27 17:34:56 +08:00
29120e0e29 feat: add Homebrew tap update workflow with asset download and formula generation 2026-05-26 20:20:39 +08:00
b66c846956 feat: improve release workflow with enhanced tag handling and clearer input descriptions 2026-05-26 19:39:35 +08:00
2a9c918711 fix: add missing package metadata including description, license, and repository URL 2026-05-26 19:36:39 +08:00
2af3baf4bb feat: enhance release workflow with additional input options and build configurations for Linux, macOS, and Windows 2026-05-26 19:16:09 +08:00
4e3601b1df 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. 2026-05-26 17:57:37 +08:00
7969209eae 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.

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/<arch>-unknown-linux-gnu/<profile>/` (no stale `target/<profile>/`).
- `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).
2026-05-26 16:38:23 +08:00
adfa24f8f4 feat: add AGENTS.md for quick start and CI guidelines; update documentation for dcr add and dcr build commands 2026-05-22 15:14:56 +08:00
ef2fb1e8e4 feat: update version to 0.6.7, enhance build configuration, and add dcr tree command 2026-05-22 14:37:45 +08:00
9ad968374e fix: target system
add: new field in dcr.toml - filename, extension
2026-05-21 00:02:24 +08:00
c4a49cb345 feat: add openssl dependency and update git2 configuration 2026-05-18 16:58:44 +08:00
527a789410 fix: update git2 dependency to use vendored-openssl 2026-05-18 16:46:11 +08:00
6b9e731c7b fix: update git2 dependency to disable default features and enable https 2026-05-18 16:41:01 +08:00
b2854db91b refactor: simplify Linux cross-compilation setup and install cargo-zigbuild 2026-05-18 16:31:29 +08:00
fce9c44181 Merge pull request #7 from dexoron/dev
v0.6.6
2026-05-18 16:15:34 +08:00
3b54500d4d Merge branch 'master' into dev 2026-05-18 16:15:22 +08:00
933fae749d Fixed:
- Path dependencies now work correctly: `dcr add` stores them as `{ path =
"./..." }` tables, `is_registry_dep` properly distinguishes registry strings
from path/git strings, and `deps/mod.rs` resolves both table-form and
legacy string-form path dependencies
- Registry dependency paths no longer hardcoded to `project_root/dcr-index`;
now use `package_root_from_registry_info()` which resolves relative to the
registry cache root (`~/.dcr/`)
- Registry dependencies are now actually built: `build_project_at()` is
called when `include_dir` or `lib_dir` is missing

Added:

- `SIGINT`/`Ctrl+C` handler via `ctrlc` crate — `dcr build` now checks
`BUILD_INTERRUPTED` flag at key points and aborts cleanly
- `utils/build.rs` — extracted shared utilities: `parse_version_info`,
`normalize_target_os`, `resolve_compiler`, `primary_language`,
`resolve_pkg_config_flags` and config helpers. Eliminates code duplication
between `cli/build.rs`, `cli/run.rs`, `cli/clean.rs`, `cli/gen.rs`
- `utils/fs.rs::with_dir` — extracted common directory-scoped execution
- `run_command_sync_output` in `builder/common.rs` with global `OUTPUT_MUTEX`
— synchronized compiler output across all backends (unix_cc, gas, nasm, msvc)
- New helper functions in `deps/register.rs`: `get_registry_cache_root`,
`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`)

Removed:

- Removed unused dependencies `indicatif` and `console` from `Cargo.toml`
2026-05-18 16:13:33 +08:00
kie
9ab7ce15db Merge pull request #6 from dexoron/feat-tree
feat: add dcr tree command
2026-05-15 12:44:56 +03:00
peoplemiau1
954f2870ba style: final formatting and clippy fixes for tree.rs 2026-05-15 12:39:28 +03:00
peoplemiau1
9a24a80d59 fix: address clippy warnings in tree.rs 2026-05-15 12:22:05 +03:00
peoplemiau1
a123f647c8 style: fix formatting in tree.rs 2026-05-15 12:10:09 +03:00
peoplemiau1
8f04a37eb0 feat: add dcr tree command to visualize dependencies 2026-05-15 12:06:41 +03:00
441c9f4df3 update: add dev release support and update cross build 2026-05-15 11:40:17 +08:00
60b9c4f983 Merge pull request #5 from peoplemiau1/master
refactor: optimize parallel build and fix architectural issues
2026-05-14 21:35:36 +08:00
peoplemiau1
f5fd273e29 refactor: optimize parallel build and fix architectural issues 2026-05-13 19:35:43 +03:00
d6840fc7a2 Merge pull request #4 from peoplemiau1/master
feat: parallel compilation for all builders
2026-05-14 00:02:02 +08:00
peoplemiau1
f507148849 style: format code according to project standards 2026-05-13 18:59:59 +03:00
peoplemiau1
cfc6be1e95 feat: parallel compilation for all builders 2026-05-13 18:50:42 +03:00
2de836a421 feat: Add installation of Linux cross compilation tools for builds 2026-05-13 20:39:03 +08:00
ed85f801e9 refactor: Improve registry sorting logic for package resolution 2026-05-13 19:43:25 +08:00
9d8dcda366 style: Update macOS target descriptions for clarity 2026-05-13 19:41:26 +08:00
0193834bfd feat: Enhance dependency management and project setup
- Added support for registry-based dependencies in the dependencies section.
- Updated the package section to include a project type field.
- Introduced a new CLI command `dcr setup` to load and display registry configurations.
- Implemented a new module for handling registry operations, including loading and resolving packages from registries.
- Enhanced the build process to handle library packaging, including copying headers and libraries to the appropriate target directories.
- Updated the CLI to support building library packages and added tests for library package builds.
- Improved error handling and output messages during dependency resolution and package fetching.
2026-05-13 19:24:53 +08:00
debc5d2044 Added:
- Full Git dependency support in `dcr.toml`
  - Support for `git`, `branch`, `tag`, and `rev` fields
  - Automatic cloning and checkout via `git2`
  - Dependencies are stored in `target/<profile>/deps/git/`
- New `dcr add` command for easy dependency management
  - Short syntax: `github:user/repo`, `gitlab:user/repo`, `path:/to/lib`
  - Automatic GitHub resolution for `git:user/repo`
- Visual feedback for dependency fetching with in-place status updates ("Fetching" → "Fetched")
- Modular dependency management architecture in `src/core/deps/`
2026-05-12 01:29:26 +08:00
00ed418291 New TODO.md 2026-05-11 23:22:12 +08:00
7cefbb3c3a feat: Implement cross-compilation with target inheritance and host defaults 2026-04-12 14:23:01 +08:00
045cc90d81 style: update code format 2026-04-11 13:37:29 +08:00
fd88e036ed feat: add cross-compilation support with --target flag
- Updated build and run commands to accept a --target option for specifying target platforms.
- Enhanced documentation to include cross-compilation usage and supported targets.
- Modified configuration to allow target-specific settings and toolchains.
- Introduced a new test command to run project tests with specified profiles and targets.
- Improved handling of target directories and output structure for different platforms.
- Added support for multiple targets in the configuration file.
- Implemented inheritance for workspace and dependencies based on target and profile.
2026-04-11 13:36:18 +08:00
613fbc6a6a refactor: run cargo fmt --all 2026-04-10 14:33:48 +08:00
03f72adade feat: Update version to 0.4.0, add test command, and improve documentation for testing functionality 2026-04-10 14:31:58 +08:00
cd5023243b feat: Update documentation with new sections for workspace guide, flags & environment, and troubleshooting map; enhance CLI reference and build/run notes for improved clarity. 2026-03-21 14:15:06 +08:00
d08ceaf31d Added:
- `dcr gen` command for IDE integration and build tool support
    - `dcr gen project-info`: Output project metadata as JSON
    - `dcr gen compile-commands`: Generate `compile_commands.json` for clangd and clang-tidy
    - `dcr gen vscode`: Generate VS Code `.vscode/` integration (launch.json, tasks.json, settings.json)
    - `dcr gen clion`: Generate JetBrains CLion `.idea/` integration (run configurations, build targets)
- Full workspace support for all `gen` subcommands
2026-03-19 00:03:39 +08:00
5f0dca4bb3 Added:
- `dcr gen` command for IDE integration and build tool support
    - `dcr gen project-info`: Output project metadata as JSON
    - `dcr gen compile-commands`: Generate `compile_commands.json` for clangd and clang-tidy
    - `dcr gen vscode`: Generate VS Code `.vscode/` integration (launch.json, tasks.json, settings.json)
    - `dcr gen clion`: Generate JetBrains CLion `.idea/` integration (run configurations, build targets)
- Full workspace support for all `gen` subcommands
2026-03-19 00:02:04 +08:00
0e93a1aa0f Added:
- `build.steps` for pre-build generators (e.g., `moc`/`uic`/`rcc`)
- `build.pkg_config` for `pkg-config`-driven cflags/ldflags resolution
- `[toolchain]` support for `uic`, `moc`, `rcc`
- Step command variables (`{uic}`, `{moc}`, `{rcc}`, `{cflags}`, `{stem}`, `{in}`, `{out}`)
- `{profile}` placeholder support for dependency paths
2026-03-16 20:50:32 +08:00
dd996b92d1 fix code format 2026-03-14 17:56:12 +08:00
a0e4fdbacf feat: Refactor builder modules by consolidating Unix C/C++ compilers, extracting common logic, enhancing MSVC with dependency tracking, and adding new CLI error tests. 2026-03-14 16:43:00 +08:00
4ea6267a88 Added:
- Workspace support (`[workspace]` with deps ordering)
- `clean --all` for workspace members
Changed:
- Project root discovery for `build/run/clean` (searches parent dirs)
- Workspace paths are excluded from root source scan
- Build output now uses a Cargo-style `Compiling <name> v<version>` line
- Build time now reports total time for the whole build stage (workspace included)
- Cache hits are silent (no compile line when nothing is rebuilt)
- Safer build cache: fingerprints include headers and resolved library files
2026-03-06 21:58:54 +08:00
617ba85442 Added:
- Toolchain overrides via `[toolchain]` and env (`DCR_*`)
- ASM `.S` preprocessing via GCC/Clang (`-x assembler-with-cpp`)
2026-03-03 22:25:00 +08:00
9af91a0d9f Added:
- `sharedlib` build kind (shared library output)
- ASM projects (`build.language = "asm"`)
- NASM and GAS backends
- `build.platform` architecture hint for `-march` / `/arch`
- Basic CLI integration tests
2026-03-01 20:04:08 +08:00
a247e33555 Added:
- `sharedlib` build kind (shared library output)
- ASM projects (`build.language = "asm"`)
- NASM and GAS backends
- `build.platform` architecture hint for `-march` / `/arch`
- Basic CLI integration tests
2026-03-01 19:58:43 +08:00
d1e3b0c384 Added:
- `sharedlib` build kind (shared library output)
- ASM projects (`build.language = "asm"`)
- NASM and GAS backends
- `build.platform` architecture hint for `-march` / `/arch`
- Basic CLI integration tests
2026-03-01 19:47:38 +08:00
98531c7335 Update TODO.md 2026-02-27 22:07:04 +08:00
75859a6a53 fix Cargo.toml - version 2026-02-27 21:36:13 +08:00
c22d2dbddc New docs 2026-02-26 18:09:07 +08:00
83061cc460 GITBOOK-6: No subject 2026-02-26 09:39:05 +00:00
467e64aa4f rename docs dir 2026-02-26 17:17:57 +08:00
7817834790 Added:
- Incremental builds (object caching by mtime)
- `build.kind` with `staticlib` support
- Custom output directory via `build.target`
2026-02-26 17:03:35 +08:00
9fe2604154 Custom output directory via build.target 2026-02-23 14:02:32 +08:00
cb8a3b2001 Custom output directory via build.target 2026-02-23 13:51:53 +08:00
e0fd729635 translated CONTRIBUTING.md to eng 2026-02-22 15:54:55 +08:00
e70edf8ae4 Update project TODO 2026-02-22 15:43:10 +08:00
210e82f026 Added:
- Custom build flags: `build.cflags` and `build.ldflags`
- Recursive source discovery inside `src/`
- Path dependencies with auto include/lib resolution and `dcr.lock` generation (experimental)
- Add docs/index.md and docs/dcr-toml.md
2026-02-22 15:04:02 +08:00
ff12ca2758 Update release.yml 2026-02-21 16:19:44 +08:00
468491b5b1 Added:
- Modular builders for `gcc`, `clang`, `msvc`
- Platform-specific binary path generation (Linux/macOS/Windows)

Changed:
- Build configuration moved to `[build]` (`language`, `standard`, `compiler`)
- `dcr.toml` formatting now includes the `[build]` section
- Build uses built-in `debug/release` flags per compiler
- Build compiles all `*.c/*.cpp` files in `src/` into a single binary (no incremental build)
- Updated `dcr.toml` examples in documentation
2026-02-21 16:12:32 +08:00
0f48151b68 Added:
- Modular builders for `gcc`, `clang`, `msvc`
- Platform-specific binary path generation (Linux/macOS/Windows)

Changed:
- Build configuration moved to `[build]` (`language`, `standard`, `compiler`)
- `dcr.toml` formatting now includes the `[build]` section
- Build uses built-in `debug/release` flags per compiler
- Build compiles all `*.c/*.cpp` files in `src/` into a single binary (no incremental build)
- Updated `dcr.toml` examples in documentation
2026-02-21 16:12:20 +08:00
e87e0cbc47 update aur publish 2026-02-20 18:31:30 +08:00
fff8baa3c9 update aur publish 2026-02-20 18:22:05 +08:00
ad0d4cdcb7 update aur publish 2026-02-20 18:20:50 +08:00
45806f193e update aur publish 2026-02-20 18:17:36 +08:00
9ae4bb5ab1 update release.yml and CHANGELOG.md 2026-02-20 18:09:16 +08:00
5a2025fb80 Changed:
- Reworked `dcr.toml` handling: added read, validation, and edit through `core::config`
- `new` and `init` use the new config creation logic
- `build` and `run` now require `dcr.toml` and read `compiler` and `name` from it
2026-02-20 17:54:01 +08:00
06e7af6ab6 update README.md, --update and aur 2026-02-19 14:50:16 +08:00
8495441b1a Update pkgrel 1 to 2 2026-02-19 13:14:03 +08:00
b56f502a30 fix aur 2026-02-19 13:02:01 +08:00
115692253e add AUR publish support 2026-02-18 23:16:02 +08:00
7a0ebb43a5 remove one project deps 2026-02-18 21:08:56 +08:00
4c628ff336 update CHANGELOG.md 2026-02-18 21:05:36 +08:00
95324ce199 chore: translate CLI and installer messages to English 2026-02-18 21:04:28 +08:00
d1e23a3179 Update project docs lang to Eng
Add FAQ.md and docs/
2026-02-18 19:53:39 +08:00
ae49ebe44a Fix project version on Cargo.toml 2026-02-17 18:50:34 +08:00
40a6bf1427 Fix single match in updater 2026-02-17 18:48:37 +08:00
Dexoron
4c06307921 Merge pull request #2 from dexoron/rust-rewrite
Rewrite project from Python to Rust and add Windows/MacOS supports
2026-02-17 18:31:34 +08:00
211438a4e8 Rewrite project from Python to Rust and add Windows/MacOS supports 2026-02-17 18:15:08 +08:00
ff69e50127 добавлены правила контрибьюции 2026-02-13 00:49:18 +08:00
2f28cc4e2c Удалены не используемые импорты и переменые, исправлены маленькие проблемы, добавлен файл check.sh, для проверки форматирования и типизации 2026-02-13 00:21:36 +08:00
2fcb0ae7be v0.1.2 2026-02-12 18:51:49 +08:00
676cd86b3b Обновлен консольный вывод команд 2026-02-12 18:37:30 +08:00
c9e9423d2c Исправлено импортирование VERSION в main.py 2026-02-12 16:42:36 +08:00
9e842e8021 Переименовал update в flag_update. Добавил utils/text.py для вывода сообщений с цветом. Обновил вывод --help, добавил цвета, добавил флаг --version, что бы показать версию dcr 2026-02-12 10:18:13 +08:00
35dd8812ad Добавлена команда обновления и обновлено справочное сообщение 2026-02-12 09:46:52 +08:00
675352afc0 Добавлена проверка установленной версии, если она есть, проверяет, нужно ли обновление 2026-02-12 09:33:55 +08:00
43d93762c3 Обновлен запуск проекта 2026-02-12 09:24:37 +08:00
d510efdaef Обновлен README.md с инструкциями по установке и улучшить скрипт установки. Исправлены импорты в модулях и улучшить обработку сборки в run.py/build.py. 2026-02-12 09:17:35 +08:00
Dexoron
f831183b25 Merge pull request #1 from dexoron/installer
Installer
2026-02-12 08:11:14 +08:00
f31dc0d217 Add installation instructions to README.md 2026-02-11 21:16:51 +03:00
314142ad78 Refactor install.sh: enhance logging, improve error handling, and update installation paths 2026-02-11 21:07:16 +03:00
203836fd3f added install.sh 2026-02-11 20:58:31 +03:00
b30176f05e исправлен базовый код main.c 2026-02-12 01:16:53 +08:00
166 changed files with 28623 additions and 444 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: 'https://dalink.to/dexoron'

51
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,51 @@
---
name: Bug Report
about: Something is broken or behaves unexpectedly
title: "[bug] "
labels: bug
assignees: ''
---
## Description
<!-- A clear and concise description of the bug -->
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
<!-- What should have happened -->
## Actual Behavior
<!-- What actually happened, include full error output -->
```
paste error output here
```
## Environment
- **DCR version** (`dcr --version`):
- **OS**:
- **Compiler / toolchain** (`gcc --version` or `clang --version`):
## Minimal Reproducible Project
<!-- If possible, share a minimal dcr.toml + source that reproduces the issue -->
```toml
# dcr.toml
```
```c
// src/main.c
```
## Additional Context
<!-- Anything else that might help -->

View File

@@ -0,0 +1,35 @@
---
name: Feature Request
about: Suggest an idea or improvement
title: "[feat] "
labels: enhancement
assignees: ''
---
## Problem / Motivation
<!-- What problem does this solve? What are you trying to do that's currently hard or impossible? -->
## Proposed Solution
<!-- Describe what you want. Be as specific as possible — CLI flags, config syntax, behavior -->
## Example
<!-- Show how it would look in practice (command, dcr.toml snippet, output) -->
```sh
# example command
```
```toml
# example config
```
## Alternatives Considered
<!-- Have you tried workarounds? Are there other ways to solve this? -->
## Additional Context
<!-- Links, prior art, related issues, etc. -->

166
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,166 @@
name: CI
on:
push:
branches:
- master
- main
- dev
paths:
- 'src/**'
- 'tests/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
pull_request:
paths:
- 'src/**'
- 'tests/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
CARGO_INCREMENTAL: "0"
SCCACHE_GHA_ENABLED: "true"
jobs:
checks:
name: fmt / clippy / check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: sccache
uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
shared-key: dcr-ci
cache-all-crates: true
- name: Format
run: cargo fmt --all --check
- name: Clippy (default features)
run: cargo clippy --all-targets -- -D warnings
- name: Clippy (all features)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Check (all features)
run: cargo check --all-targets --all-features
test:
name: tests (${{ matrix.os }})
needs: checks
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: sccache
uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
shared-key: dcr-ci
cache-all-crates: true
cache-on-failure: true
- name: Cache apt packages
if: runner.os == 'Linux'
uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-${{ runner.os }}-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: apt-${{ runner.os }}-
- name: Install Linux tools
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
clang \
nasm \
clang-format \
clang-tidy \
git \
llvm
- name: Install macOS tools
if: runner.os == 'macOS'
run: |
brew install nasm llvm || true
echo "$(brew --prefix llvm)/bin" >> "$GITHUB_PATH"
- name: Install Windows tools
if: runner.os == 'Windows'
shell: pwsh
run: |
choco install nasm llvm -y
echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
if (Test-Path "C:\Program Files\LLVM\bin") {
echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
}
- name: Unit tests (binary crate)
run: cargo test --bins -- --nocapture
- name: Integration tests (default features)
run: cargo test --tests -- --nocapture
- name: Integration tests (archive feature)
if: runner.os != 'Windows'
run: cargo test --tests --features archive -- --nocapture
- name: sccache stats
if: always()
run: sccache --show-stats || true
test-archive-linux:
name: archive feature smoke
needs: checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: sccache
uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
shared-key: dcr-ci
cache-all-crates: true
- name: Install tools
run: |
sudo apt-get update
sudo apt-get install -y build-essential clang nasm llvm
- name: Build with archive
run: cargo build --features archive
- name: Flat-bin + unit suite with archive
run: cargo test --features archive --test cli_build --test cli_workspace --test cli_errors -- --nocapture
- name: sccache stats
if: always()
run: sccache --show-stats || true

38
.github/workflows/code-mirror.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Mirror source code to GitVerse
on:
push:
branches:
- '**'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: code-mirror-${{ github.ref }}
cancel-in-progress: true
jobs:
mirror:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Mirror all branches
env:
GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }}
GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }}
GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }}
run: |
set -euo pipefail
test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; }
remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git"
git remote add gitverse "$remote"
# Use a private ref namespace: Git will not overwrite the checked-out
# branch, and broken legacy submodule metadata is not traversed.
git fetch --no-recurse-submodules origin '+refs/heads/*:refs/mirror/*'
git push --prune --force gitverse 'refs/mirror/*:refs/heads/*'

View File

@@ -0,0 +1,109 @@
name: Dexoron Packages Index
on:
workflow_dispatch:
inputs:
tag:
description: "Версия (тег) для ручного импорта пакетов (например, v0.6.9)"
required: true
type: string
workflow_run:
workflows: ["Release"]
types:
- completed
permissions:
contents: read
jobs:
update-packages-index:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' || github.event_workflow_run.conclusion == 'success' }}
steps:
- name: Checkout Central Packages Repo
uses: actions/checkout@v4
with:
repository: 'dexoron/packages'
token: ${{ secrets.PACKAGES_REPO_TOKEN }}
path: 'central-packages'
- name: Import GPG Key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
# --- Загрузка артефактов (сохранена прежняя логика) ---
- name: Download Artifacts
if: ${{ github.event_name == 'workflow_run' }}
uses: actions/download-artifact@v4
with:
path: central-packages/
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Download Packages from Release Assets
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
run: |
gh release download "$TAG" --repo dexoron/dcr --pattern "*.pkg.tar.zst" --dir central-packages/archlinux/x86_64/ --clobber
gh release download "$TAG" --repo dexoron/dcr --pattern "*.deb" --dir central-packages/debian/ --clobber
gh release download "$TAG" --repo dexoron/dcr --pattern "*.rpm" --dir central-packages/fedora/x86_64/ --clobber
# --- ИНДЕКСАЦИЯ И ПОДПИСЬ ---
- name: Update Pacman Database
run: |
docker run --rm \
-v ${{ github.workspace }}/central-packages:/packages \
-v ~/.gnupg:/root/.gnupg \
archlinux:latest \
bash -c '
cd /packages/archlinux/x86_64 && \
packages=(*.pkg.tar.zst) && \
repo-add --sign dexoron.db.tar.gz "${packages[@]}" && \
ln -sf dexoron.db.tar.gz dexoron.db && \
ln -sf dexoron.files.tar.gz dexoron.files
'
- name: Fix GPG permissions after Docker
run: sudo chown -R runner:runner ~/.gnupg
- name: Update Debian APT Repository Index
run: |
sudo apt-get update && sudo apt-get install -y dpkg-dev
cd central-packages/debian
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
echo "Origin: Dexoron Packages" > Release
echo "Label: Dexoron Packages" >> Release
echo "Suite: stable" >> Release
echo "Codename: stable" >> Release
echo "Architectures: amd64" >> Release
echo "Components: main" >> Release
echo "Description: Dexoron binary package repository" >> Release
echo "Date: $(date -uR)" >> Release
echo "SHA256:" >> Release
echo " $(sha256sum Packages.gz | awk '{print $1" "$2}')" >> Release
gpg --batch --yes --pinentry-mode loopback --local-user 74322D39483E9C9E8C86C0AA0770A5C2A2A82DAC --detach-sign --armor --output Release.gpg Release
gpg --batch --yes --pinentry-mode loopback --local-user 74322D39483E9C9E8C86C0AA0770A5C2A2A82DAC --clearsign --output InRelease Release
- name: Update Fedora DNF Repository Index
run: |
sudo apt-get update && sudo apt-get install -y createrepo-c
cd central-packages/fedora/x86_64
createrepo_c .
gpg --batch --yes --detach-sign --armor --output repodata/repomd.xml.asc repodata/repomd.xml
- name: Push to dexoron/packages
run: |
cd central-packages
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
if ! git diff --cached --quiet; then
git commit -m "chore: update and sign repositories"
git push
fi

290
.github/workflows/release-mirror.yml vendored Normal file
View File

@@ -0,0 +1,290 @@
name: Mirror releases to GitVerse, SourceCraft, and GitLab
on:
workflow_run:
workflows:
- Release
types:
- completed
workflow_dispatch:
inputs:
tag:
description: Git tag of the GitHub release to mirror (for example v0.7.1)
required: true
type: string
permissions:
contents: read
jobs:
setup:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release.outputs.tag }}
found: ${{ steps.release.outputs.found }}
steps:
- name: Resolve release tag
id: release
env:
GH_TOKEN: ${{ github.token }}
MANUAL_TAG: ${{ inputs.tag }}
WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
tag="$MANUAL_TAG"
if [ -z "$tag" ] && [[ "$WORKFLOW_HEAD_BRANCH" == v* ]]; then
tag="$WORKFLOW_HEAD_BRANCH"
fi
if [ -z "$tag" ] && [ -n "$WORKFLOW_HEAD_SHA" ]; then
releases=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
-H 'Accept: application/vnd.github+json' \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases?per_page=100")
while IFS= read -r candidate; do
ref=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
-H 'Accept: application/vnd.github+json' \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${candidate}")
sha=$(jq -r '.object.sha' <<< "$ref")
if [ "$(jq -r '.object.type' <<< "$ref")" = tag ]; then
sha=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
-H 'Accept: application/vnd.github+json' \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/git/tags/${sha}" | jq -r '.object.sha')
fi
if [ "$sha" = "$WORKFLOW_HEAD_SHA" ]; then
tag="$candidate"
break
fi
done < <(jq -r '.[].tag_name' <<< "$releases")
fi
if [ -z "$tag" ] || [[ "$tag" != v* ]]; then
echo 'No release tag was found; release.yml may have failed before creating a GitHub release.'
echo 'found=false' >> "$GITHUB_OUTPUT"
exit 0
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo 'found=true' >> "$GITHUB_OUTPUT"
- name: Download GitHub release metadata and assets
if: steps.release.outputs.found == 'true'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.release.outputs.tag }}
run: |
set -euo pipefail
mkdir -p assets
curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
-H 'Accept: application/vnd.github+json' \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" > release.json
jq -e '.tag_name == $tag' --arg tag "$TAG" release.json >/dev/null
total=$(jq '.assets | length' release.json)
downloaded=0
failed=0
while IFS=$' ' read -r name url; do
if curl -fsSL -L -H "Authorization: Bearer $GH_TOKEN" \
-H 'Accept: application/octet-stream' -o "assets/$name" "$url"; then
downloaded=$((downloaded+1))
else
echo "::warning::Failed to download asset: $name"
failed=$((failed+1))
fi
done < <(jq -r '.assets[] | [.name, .browser_download_url] | @tsv' release.json)
echo "Downloaded ${downloaded}/${total} assets (${failed} failed)"
[ "$failed" -eq 0 ] || { echo 'Not all release assets were downloaded.' >&2; exit 1; }
- name: Upload release metadata artifact
if: steps.release.outputs.found == 'true'
uses: actions/upload-artifact@v4
with:
name: release-mirror-data
path: |
release.json
assets/*
if-no-files-found: error
mirror-gitverse:
needs: setup
if: needs.setup.outputs.found == 'true'
runs-on: ubuntu-latest
steps:
- name: Download release metadata
uses: actions/download-artifact@v4
with:
name: release-mirror-data
- name: Sync tag and release to GitVerse
env:
GITVERSE_PAT: ${{ secrets.GITVERSE_PAT }}
GITVERSE_ORG: ${{ vars.GITVERSE_ORG || 'dcr_labs' }}
GITVERSE_REPO: ${{ vars.GITVERSE_REPO || 'dcr' }}
TAG: ${{ needs.setup.outputs.tag }}
run: |
set -euo pipefail
test -n "$GITVERSE_PAT" || { echo 'GITVERSE_PAT is not configured' >&2; exit 1; }
project="${GITVERSE_ORG}%2F${GITVERSE_REPO}"
remote="https://oauth2:${GITVERSE_PAT}@gitverse.ru/${GITVERSE_ORG}/${GITVERSE_REPO}.git"
git clone --mirror "https://github.com/${GITHUB_REPOSITORY}.git" mirror.git
git -C mirror.git remote add gitverse "$remote"
git -C mirror.git push gitverse "refs/tags/${TAG}:refs/tags/${TAG}"
title=$(jq -r '.name // .tag_name' release.json)
notes=$(jq -r '.body // ""' release.json)
payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \
'{name:$title, tag_name:$tag, description:$notes, ref:$tag}')
http=$(curl -sS -o gitverse-release.json -w '%{http_code}' \
-H "PRIVATE-TOKEN: $GITVERSE_PAT" \
"https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}" || true)
if [ "$http" = '200' ]; then
echo "GitVerse release ${TAG} already exists; skipping create."
else
curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
-H 'Content-Type: application/json' --data "$payload" \
"https://gitverse.ru/api/v4/projects/${project}/releases" || \
curl -fsSL -X PUT -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
-H 'Content-Type: application/json' --data "$payload" \
"https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}"
fi
uploaded=0; failed=0
for file in assets/*; do
[ -f "$file" ] || continue
name=$(basename "$file")
upload_response=$(curl -sS -w "\n%{http_code}" -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
-F "file=@$file" "https://gitverse.ru/api/v4/projects/${project}/uploads")
http_code=$(echo "$upload_response" | tail -n1)
body=$(echo "$upload_response" | sed '$d')
if [ "$http_code" != "201" ] && [ "$http_code" != "200" ]; then
echo "::warning::GitVerse: failed to upload $name (HTTP $http_code)"
failed=$((failed+1))
continue
fi
url=$(jq -r '.url // .file_path' <<< "$body" 2>/dev/null || echo "")
if [ -z "$url" ]; then
echo "::warning::GitVerse: failed to parse upload response for $name"
failed=$((failed+1))
continue
fi
link=$(jq -n --arg name "$name" --arg url "$url" '{name:$name,url:$url}')
link_response=$(curl -sS -w "\n%{http_code}" -X POST -H "PRIVATE-TOKEN: $GITVERSE_PAT" \
-H 'Content-Type: application/json' --data "$link" \
"https://gitverse.ru/api/v4/projects/${project}/releases/${TAG}/assets/links")
link_http=$(echo "$link_response" | tail -n1)
if [ "$link_http" = "201" ] || [ "$link_http" = "200" ] || [ "$link_http" = "409" ]; then
uploaded=$((uploaded+1))
else
echo "::warning::GitVerse: failed to attach asset $name (HTTP $link_http)"
failed=$((failed+1))
fi
done
echo "GitVerse: uploaded ${uploaded}, failed ${failed}"
[ "$failed" -eq 0 ] || exit 1
mirror-sourcecraft:
needs: setup
if: needs.setup.outputs.found == 'true'
runs-on: ubuntu-latest
steps:
- name: Download release metadata
uses: actions/download-artifact@v4
with:
name: release-mirror-data
- name: Sync tag and release to SourceCraft
env:
SOURCECRAFT_PAT: ${{ secrets.SOURCECRAFT_PAT }}
SOURCECRAFT_ORG: ${{ vars.SOURCECRAFT_ORG || 'dexoron-dev' }}
SOURCECRAFT_REPO: ${{ vars.SOURCECRAFT_REPO || 'dcr' }}
TAG: ${{ needs.setup.outputs.tag }}
run: |
set -euo pipefail
test -n "$SOURCECRAFT_PAT" || { echo 'SOURCECRAFT_PAT is not configured' >&2; exit 1; }
remote="https://anyname:${SOURCECRAFT_PAT}@git.sourcecraft.dev/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}.git"
git clone "$remote" sourcecraft
git -C sourcecraft remote add github "https://github.com/${GITHUB_REPOSITORY}.git"
git -C sourcecraft fetch github "refs/tags/${TAG}:refs/tags/${TAG}"
git -C sourcecraft push origin "refs/tags/${TAG}"
title=$(jq -r '.name // .tag_name' release.json)
notes=$(jq -r '.body // ""' release.json)
payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \
'{tag:$tag,title:$title,release_notes:$notes,publish:true}')
base="https://api.sourcecraft.tech/repos/${SOURCECRAFT_ORG}/${SOURCECRAFT_REPO}"
http=$(curl -sS -o /dev/null -w '%{http_code}' \
-H "Authorization: Bearer $SOURCECRAFT_PAT" "${base}/releases/tag/${TAG}" || true)
if [ "$http" != '200' ]; then
curl -fsSL -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \
-H 'Content-Type: application/json' --data "$payload" "${base}/releases" || \
curl -fsSL -X PUT -H "Authorization: Bearer $SOURCECRAFT_PAT" \
-H 'Content-Type: application/json' --data "$payload" "${base}/releases/${TAG}" || \
echo 'SourceCraft release already exists; continuing with assets.'
else
echo "SourceCraft release ${TAG} already exists; continuing with assets."
fi
uploaded=0; failed=0
for file in assets/*; do
[ -f "$file" ] || continue
name=$(basename "$file")
upload_response=$(curl -sS -w "\n%{http_code}" -X POST -H "Authorization: Bearer $SOURCECRAFT_PAT" \
-F "file=@$file" \
"${base}/releases/tag/${TAG}/assets")
http_code=$(echo "$upload_response" | tail -n1)
if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then
uploaded=$((uploaded+1))
elif [ "$http_code" = "409" ]; then
uploaded=$((uploaded+1))
else
echo "::warning::SourceCraft: failed to attach asset $name (HTTP $http_code)"
failed=$((failed+1))
fi
done
echo "SourceCraft: uploaded ${uploaded}, failed ${failed}"
[ "$failed" -eq 0 ] || exit 1
mirror-gitlab:
needs: setup
if: needs.setup.outputs.found == 'true'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Download release metadata
uses: actions/download-artifact@v4
with:
name: release-mirror-data
- name: Sync release to GitLab
env:
GITLAB_PAT: ${{ secrets.GITLAB_PAT }}
GITLAB_PROJECT: ${{ vars.GITLAB_PROJECT || 'dexoron-dev/dcr-tool/dcr' }}
TAG: ${{ needs.setup.outputs.tag }}
run: |
set -euo pipefail
test -n "$GITLAB_PAT" || { echo 'GITLAB_PAT is not configured' >&2; exit 0; }
project_id="${GITLAB_PROJECT//\//%2F}"
api="https://gitlab.com/api/v4/projects/${project_id}"
title=$(jq -r '.name // .tag_name' release.json)
notes=$(jq -r '.body // ""' release.json)
payload=$(jq -n --arg tag "$TAG" --arg title "$title" --arg notes "$notes" \
'{tag_name:$tag, name:$title, description:$notes}')
http=$(curl -sS -o /dev/null -w '%{http_code}' \
-H "PRIVATE-TOKEN: $GITLAB_PAT" "${api}/releases/${TAG}" || true)
if [ "$http" = '200' ]; then
echo "GitLab release ${TAG} already exists; skipping"
exit 0
fi
echo "Creating release on GitLab..."
curl -fsSL -X POST -H "PRIVATE-TOKEN: $GITLAB_PAT" \
-H 'Content-Type: application/json' --data "$payload" "${api}/releases" || true

1329
.github/workflows/release.yml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
name: Trigger site rebuild
on:
push:
branches: [master]
paths:
- 'docs/**'
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger Cloudflare Pages build
run: curl -sS -X POST "${{ secrets.CF_PAGES_DEPLOY_HOOK }}"

21
.gitignore vendored
View File

@@ -1,9 +1,12 @@
__pycache__/
build/
dist/
wheels/
*.egg-info
.venv
sandbox/*
.ruff_cache/
.vscode/
target/
sandbox/**
.idea/
.vscode/
.cargo/
.env
personal/
.*
!.gitignore
!.github/
graphify-out/

View File

@@ -1 +0,0 @@
3.14.2

View File

@@ -1,14 +1,963 @@
# Changelog
## [v0.9.1] - 2026-09-13 "Системные pkg-config-зависимости / System pkg-config Dependencies"
### RU
**Добавлено:**
- **Зависимости через `pkg-config`** — `fmt = { pkg-config = "fmt" }` или массив имён пакетов (`pkg-config = ["openssl", "libcrypto"]`) подключает системные библиотеки через `.pc`-метаданные.
- **Поддержка префиксов в `dcr add`** — команды `dcr add fmt pkg-config:fmt` и `dcr add openssl pkg:openssl,libcrypto` добавляют зависимости от системных пакетов в `dcr.toml`.
- **Полный набор флагов** — DCR собирает и пробрасывает compiler/linker flags (`-I`, `-L`, `-l`, `-pthread` и другие), включая транзитивные зависимости и интеграцию с `dcr gen` / `dcr test`.
- **Фиксация в `dcr.lock`** — записывается источник `pkg-config+...` и обнаруженная версия пакета.
- **Отображение в `dcr tree`** — `pkg-config` зависимости наглядно помечаются в дереве зависимостей.
**Исправлено:**
- **Ошибка создания проекта в папках со спецсимволами** — `dcr new` больше не завершается ошибкой `Failed to create dcr.toml`, если родительская директория содержит пробелы или не-ASCII символы.
### EN
**Added:**
- **`pkg-config` dependencies** — `fmt = { pkg-config = "fmt" }` or an array of package names (`pkg-config = ["openssl", "libcrypto"]`) connects system libraries through their `.pc` metadata.
- **Prefix support in `dcr add`** — `dcr add fmt pkg-config:fmt` and `dcr add openssl pkg:openssl,libcrypto` add system package dependencies to `dcr.toml`.
- **Complete flag propagation** — compiler/linker flags (`-I`, `-L`, `-l`, `-pthread`, and others) are preserved and passed to builds, `dcr test`, and IDE integrations via `dcr gen`.
- **Lockfile entries** — `dcr.lock` records the `pkg-config+...` source and detected package version.
- **`dcr tree` representation** — `pkg-config` dependencies are clearly labeled in the dependency tree.
**Fixed:**
- **Project creation failure in special character directories** — `dcr new` no longer fails with `Failed to create dcr.toml` when invoked inside parent folders with spaces or non-ASCII characters.
## [v0.9.0] - 2026-08-31 "Полные Path/Git-зависимости и готовые библиотеки / Complete Path/Git Dependencies & Prebuilt Libraries"
### RU
**Добавлено:**
- **Зеркала загрузок в `dcrup`** — `--mirror auto|cf|gh|sf|ya` и `DCRUP_MIRROR`.
По умолчанию `auto`: **cf → gh → sf → ya** (CloudFlare CDN, GitHub, SourceForge, Yandex CDN).
- **Полные Path/Git-зависимости** — DCR-пакеты из `path` и `git` собираются перед потребителем; Git-код кешируется в `~/.dcr/git`, поддерживает `branch`/`tag`/`rev` и фиксирует точный commit в `dcr.lock`.
- **Транзитивные зависимости и защита от циклов** — DCR рекурсивно собирает DCR-пакеты и показывает цепочку циклической зависимости.
- **Внешние готовые библиотеки через path/Git** — `include`, `lib` и `libs` подключают библиотеку без `dcr.toml`; Git prebuilt-источники тоже фиксируются по точному commit.
- **Явный режим зависимости** — `mode = "build"` и `mode = "prebuild"` позволяют выбрать сборку DCR-пакета или подключение готовых артефактов, даже когда репозиторий содержит оба варианта.
**Удалено:**
- **`install.sh` / `install.ps1` / `install_bsd.sh`** — устарели, заменены `dcrup`.
### EN
**Added:**
- **Download mirrors in `dcrup`** — `--mirror auto|cf|gh|sf|ya` and `DCRUP_MIRROR`.
Default `auto` tries **cf → gh → sf → ya** (CloudFlare CDN, GitHub, SourceForge, Yandex CDN).
- **Complete Path/Git dependencies** — DCR packages from `path` and `git` are built before their consumer; Git sources are cached in `~/.dcr/git`, support `branch`/`tag`/`rev`, and lock the exact commit in `dcr.lock`.
- **Transitive dependencies and cycle protection** — DCR recursively builds DCR packages and reports the dependency cycle chain.
- **Prebuilt external libraries through path/Git** — `include`, `lib`, and `libs` link libraries without `dcr.toml`; Git prebuilt sources are locked to their exact commit as well.
- **Explicit dependency mode** — `mode = "build"` and `mode = "prebuild"` select DCR package builds or ready-made artifacts when a repository contains both forms.
**Removed:**
- **`install.sh` / `install.ps1` / `install_bsd.sh`** — superseded by `dcrup`.
## [v0.8.5] - 2026-08-08 "Фиксация версии dcr-version и предупреждения совместимости / Config Version Pinning & Compatibility Warnings"
### RU
**Добавлено:**
- **`package.dcr-version`** — опциональный pin версии CLI в `dcr.toml`.
`dcr new` / `dcr init` пишут текущую версию. При загрузке конфига:
- нет / пустой pin → warn «добавь dcr-version»;
- dcr старше pin → warn «обнови dcr»;
- dcr новее pin → warn «проверь changelog, подними pin»;
- равенство — тишина.
Команды не падают, только предупреждение (раз на path за процесс).
### EN
**Added:**
- **`package.dcr-version`** — optional CLI version pin in `dcr.toml`.
`dcr new` / `dcr init` write the current version. On config load:
- missing / empty pin → warn "add dcr-version";
- dcr older than pin → warn "update dcr";
- dcr newer than pin → warn "check changelog, update pin";
- equal → silent.
Commands do not fail, warning only (once per path per process).
## [0.8.4] - 2026-08-04 "Инфраструктура .dcr/ и интеграция с IDE / Internal .dcr/ Layout & IDE Integration"
### RU
**Добавлено:**
- **Директория `.dcr/`** — единый machine-readable источник фактов о сборке для IDE/расширений:
- `.dcr/compile_commands.json`
- `.dcr/build-info.json` (`schemaVersion`, `artifact_path`, `profile`, …)
- `.dcr/toolchain.json` (compiler/debugger paths, moc/uic/rcc)
- `.dcr/ide/` (заготовки lock-файлов для расширений)
- **`dcr gen project-info`** — поля `target`, `target_dir`, `artifact_path`, `artifact_kind`, `source_roots`, `include_globs`, `exclude_globs`; пути абсолютные/канонические.
- **`dcr build --print-artifact-path`** — после успешной сборки одна строка absolute path на stdout.
- **`dcr gen … --quiet` / `-q`** — без human-лога на stdout для `compile-commands` / `project-info`.
- **`.clangd`** — при первом `gen` создаётся с `CompilationDatabase: .dcr` (маркер `# managed by dcr gen`); ручной `.clangd` не перезаписывается.
- **Хелперы** `canonicalize_path`, `atomic_write`, `ensure_dcr_dir`; pure `resolve_artifact_path` через `platform::*`.
- **`dcr new` / `dcr init`** — `.dcr/` в `.gitignore`.
**Изменено (breaking):**
- **`compile_commands.json` перенесён** из корня проекта в **`.dcr/compile_commands.json`**.
clangd находит базу через `.clangd` / `clangd.arguments --compile-commands-dir=.dcr`.
CLion `misc.xml` и VSCode `settings.json` обновлены под новый путь.
- **Атомарная запись** `compile_commands.json` (tmp + rename).
- **`gen vscode` / `gen clion` launch/run** — `program`/`executable` через тот же artifact resolver, что `project-info` / `build-info.json` (Linux triple, Windows `.exe`, custom `target`/`filename`).
**Исправлено:**
- Launch-конфиги больше не указывают `target/<profile>/<name>` без triple на Linux.
- Симлинки/mount: root и пути в compile database канонизируются best-effort.
### EN
**Added:**
- **`.dcr/` directory** — shared machine-readable build facts for IDE integrations:
- `.dcr/compile_commands.json`
- `.dcr/build-info.json` (`schemaVersion`, `artifact_path`, `profile`, …)
- `.dcr/toolchain.json` (compiler/debugger paths, moc/uic/rcc)
- `.dcr/ide/` (placeholder for IDE lock files)
- **`dcr gen project-info`** — `target`, `target_dir`, `artifact_path`, `artifact_kind`, `source_roots`, `include_globs`, `exclude_globs`; absolute/canonical paths.
- **`dcr build --print-artifact-path`** — one absolute artifact path line on stdout after a successful build.
- **`dcr gen … --quiet` / `-q`** — suppress human success logs for gen subcommands.
- **`.clangd`** — created on first gen with `CompilationDatabase: .dcr` (`# managed by dcr gen`); custom `.clangd` is not overwritten.
- **Helpers** `canonicalize_path`, `atomic_write`, `ensure_dcr_dir`; pure `resolve_artifact_path` via `platform::*`.
- **`dcr new` / `dcr init`** — add `.dcr/` to `.gitignore`.
**Changed (breaking):**
- **`compile_commands.json` moved** from project root to **`.dcr/compile_commands.json`**.
clangd discovers it via `.clangd` / `--compile-commands-dir=.dcr`.
CLion and VSCode generators updated accordingly.
- **Atomic write** for compile_commands (tmp + rename).
- **`gen vscode` / `gen clion` launch/run** — use the same artifact path resolver as `project-info` / `build-info.json`.
**Fixed:**
- Launch configs no longer hardcode `target/<profile>/<name>` without the Linux triple segment.
- Symlink/mount roots: best-effort canonicalize for root and compile database paths.
## [0.8.3] - 2026-08-03 "Конфигурация host target, аргументы run и нативные пути артефактов / Host Target Config, Run Args & Native Artifact Paths"
### RU
**Добавлено:**
- **`dcr run -- <args>...`** — cargo-style: всё после bare `--` передаётся собранному бинарнику
(`dcr run --release -- --test_help`). Работает и для прямого запуска, и для `[run].cmd`.
- **Интеграционные / unit-тесты** — host target sections применяют `ldflags` без `--target`; проброс аргументов после `--`; парсер `split_double_dash`.
**Изменено:**
- **Native-сборка резолвит host triple** — без `--target` / без `build.target` конфиг-секции вида
`[build.x86_64-unknown-linux-gnu]` (и аналоги host) снова применяются (`cflags` / `ldflags` / toolchain).
Раньше target был «пустым», секции молча игнорировались, а артефакты уже лежали под host triple — линковка без `-L`/`-l`.
- **Пустой target** — `normalize_target_os("")` больше не warn'ит `Unknown target ''`.
- **`dcr run --help` / man `dcr-run`** — документирован разделитель `--` и forwarding args.
**Исправлено:**
- **Нативные пути артефактов на Windows и macOS** — без явного `--target` / `build.target` артефакты снова сохраняются в `target/<profile>`, поэтому `dcr run` и flat-bin находят результат сборки.
- Target-specific `ldflags` на native Linux/macOS/Windows (host triple) не доходили до линкера.
- Ложный warning на пустой target string.
### EN
**Added:**
- **`dcr run -- <args>...`** — cargo-style: arguments after a bare `--` are forwarded to the built binary
(`dcr run --release -- --test_help`). Works for direct binary runs and `[run].cmd`.
- **Tests** — host target sections apply `ldflags` without CLI `--target`; run-arg forwarding; `split_double_dash` unit tests.
**Changed:**
- **Native builds resolve the host triple** — without `--target` / without package `build.target`, host sections such as
`[build.x86_64-unknown-linux-gnu]` (and host equivalents) apply again (`cflags` / `ldflags` / toolchain).
Previously the target was empty, those sections were silently ignored while artifacts already used the host triple path — link failed without `-L`/`-l` (e.g. sandbox micro-lang + `libsct-elf`).
- **Empty target** — `normalize_target_os("")` no longer warns `Unknown target ''`.
- **`dcr run --help` / man `dcr-run`** — document `--` and argument forwarding.
**Fixed:**
- **Native artifact paths on Windows and macOS** — without an explicit `--target` / `build.target`, artifacts again use `target/<profile>`, so `dcr run` and flat-bin find the build output.
- Target-specific `ldflags` on native host builds not reaching the linker.
- Spurious warning for an empty target string.
## [0.8.2] - 2026-07-22 "CLI Progress, archive Feature, Workspace Path Fixes / CLI Progress, archive Feature, Workspace Path Fixes"
### RU
**Добавлено:**
- **Прогресс компиляции `[N/M]`** — live-строка `compile pkg v0.1.0 [12/41]` на TTY (stderr), чтобы длинные mono-package сборки не выглядели «зависшими».
- **Свой стиль статуса build** — фиксированная колонка глаголов: `project`, `compile`, `dep`, `ready`, `pack`, `done`, `run` (не cargo-clone).
- **Cargo feature `archive`** — `fatfs` и упаковка FAT-образов опциональны (`cargo build --features archive`). Release CI собирает с feature; без feature `[archive]` даёт явную ошибку.
- **Интеграционные тесты** — `flat_bin_nasm_build`, `package_build_target_used_without_cli_flag`, усилен workspace `clean --all`.
- **Расширенный CI** — unit + integration на Linux/macOS/Windows, отдельный job с `--features archive`, rust-cache, clippy default + all-features.
**Изменено:**
- **CLI host triple больше не форсится** — без `--target` используется `build.target` пакета/member (важно для bare-metal / post_steps ISO).
- **Relative `-I`/`-L`/`-T` (и `-isystem`/`-idirafter`)** в cflags/ldflags абсолютизируются относительно корня пакета (workspace CWD = root).
- **`dcr clean --all`** — не спамит `target not found` по members без локального `target/` (общий root `target/`).
- **`error` / `warn`** — красный / жёлтый, вывод на stderr; `Подсказка:` → `Hint:`.
- **`dcr --help` / man** — добавлены `add`, `lint`, `setup`; unknown command → exit code 1.
- **Сообщения NotFound** — `linker not found: ld.lld …` / `{tool} not found …` вместо сырого `os error 2`.
### EN
**Added:**
- **Compile progress `[N/M]`** — live TTY line `compile pkg v0.1.0 [12/41]` so long mono-package builds do not look hung.
- **DCR status verbs** — fixed-width column: `project`, `compile`, `dep`, `ready`, `pack`, `done`, `run`.
- **Cargo feature `archive`** — optional `fatfs` / FAT packing (`cargo build --features archive`). Release CI builds with the feature; without it `[archive]` errors clearly.
- **Integration tests** — `flat_bin_nasm_build`, package `build.target` without CLI flag, stricter workspace `clean --all`.
- **Expanded CI** — unit + integration on Linux/macOS/Windows, archive feature job, rust-cache, clippy default + all-features.
**Changed:**
- **CLI no longer forces host triple** — without `--target`, package/member `build.target` is used (bare-metal / ISO post_steps).
- **Relative `-I`/`-L`/`-T` (and `-isystem`/`-idirafter`)** in flags are absolutized against the package root when workspace CWD is the root.
- **`dcr clean --all`** — no spam for members without a local `target/` (shared root `target/`).
- **`error` / `warn`** — red / yellow on stderr; Russian `Подсказка:` → `Hint:`.
- **`dcr --help` / man** — list `add`, `lint`, `setup`; unknown command exits 1.
- **NotFound messages** — `linker not found: ld.lld …` / `{tool} not found …` instead of raw `os error 2`.
## [0.8.1] - 2026-07-20 "OS-Dev: flat-bin, FAT-образы / OS-Dev: flat-bin & FAT Images"
### RU
**Добавлено:**
- **`build.kind = "flat-bin"`** — сырой бинарный артефакт (расширение по умолчанию `.bin`):
- **NASM** — `-f bin`, прямой вывод `<stem>.bin`
- **FASM** — прямой вывод (ожидается `format binary` в исходнике)
- **GAS / MASM / LLVM IR (`llc`)** — объект → `objcopy -O binary` → `<stem>.bin`
- **C/C++ (gcc/clang/MSVC)** — compile → link (nostdlib/static) → `objcopy -O binary` → `<name>.bin`
- Требуется `llvm-objcopy` / `objcopy` / `gobjcopy` в PATH (кроме NASM/FASM direct)
- **Секция `[archive]`** — после успешной сборки DCR собирает образ диска (FAT12/16/32): размер, offset, volume label, опциональный bootsector и `layout`. С 0.8.2 — за feature `archive`.
- **Одиночные файлы в `build.roots`** — корень может быть файлом (`.c`, `.asm`, header), а не только каталогом.
**Изменено:**
- **`dcr build --force` / `dcr run --force`** — force также перезапускает `build.steps` и `build.post_steps`.
- **`dcr run` на workspace-only root** — если задан `[run].cmd`, сначала build + run по конфигу корня; иначе делегирование члену workspace.
### EN
**Added:**
- **`build.kind = "flat-bin"`** — raw binary (default `.bin`) across NASM/FASM/GAS/MASM/LLC and C/C++ (objcopy where needed).
- **`[archive]` section** — FAT12/16/32 disk images after build (from 0.8.2 gated by feature `archive`).
- **Single-file `build.roots`**.
**Changed:**
- **`--force`** re-runs steps/post_steps; workspace-only **`dcr run`** prefers root `[run].cmd` when set.
## [0.8.0] - 2026-07-18 "Модульный Движок Сборки, Новая Архитектура Ядра и Улучшение Workspaces / Modular Build Engine, New Core Architecture, & Workspace Improvements"
### RU
**Добавлено:**
- **Выделенное ядро сборки (Build Engine extraction)** — вся логика оркестрации компиляции и сборки полностью вынесена из интерфейсного модуля `src/cli/build.rs` в изолированный, переиспользуемый модуль ядра `src/core/build/engine.rs`.
- **Полиморфная модель языков (Трейт `Language`)** — введение абстрактного трейта `Language` (`src/core/build/language/mod.rs`). Логика поиска файлов, сопоставления расширений и компиляторов распределена по выделенным языковым бэкендам для C, C++ (включая шаги генерации Qt), LLVM IR и ассемблера (GAS, NASM, FASM, MASM).
- **Абстракция сборщиков (Трейт `Builder`)** — введение трейта `Builder` для полиморфного управления процессами компиляции (`src/core/build/builder/mod.rs`). Логика вызова компиляторов семейства GCC/Clang вынесена в общий модуль `builder/cc_common.rs`, а MSVC — в `builder/msvc/`.
- **Поязыковая конфигурация (`[build.<lang>]`)** — добавлена поддержка индивидуальных настроек для каждого языка в `dcr.toml` (например, `[build.c]`, `[build.cpp]`, `[build.asm]`), позволяя независимо настраивать компилятор, стандарт и флаги сборки.
- **Интеграция путей зависимостей в Workspaces** — сборщик теперь автоматически инжектирует пути заголовков (включая исходники `src/` членов, папки `include/` и директорию `target/include`) и библиотек (`target/lib`, а также специфичные для целевой платформы папки сборки) зависимых членов workspace при сборке.
- **Система событий `BuildReporter`** — весь вывод сборщика переведён на событийную модель (`src/core/build/report.rs`), что делает движок пригодным для программного встраивания (например, в IDE) без необходимости перехвата stdout/stderr.
- **Поддержка отмены сборки** — добавлен потокобезопасный токен отмены (`Arc<AtomicBool>`), позволяющий безопасно прервать выполнение компиляции на любом этапе.
- **Сохранение кастомных ключей TOML** — парсер `dcr.toml` переведен на `toml_edit`, что гарантирует сохранение всех неизвестных/пользовательских ключей, комментариев и форматирования при перезаписи файла (например, через `dcr add`).
- **Разрешение заголовков для локальных path-зависимостей** — исправлена проблема, при которой потребители не могли найти заголовочные файлы статических/динамических библиотек, лежащие в упакованной папке `target/include` локальных path-зависимостей.
- **Новая система интеграционных тестов** — старый громоздкий файл `tests/cli_basic.rs` разделен на изолированные и структурированные интеграционные тесты для каждой команды и фичи: `cli_build.rs`, `cli_deps.rs`, `cli_lint.rs`, `cli_new.rs`, `cli_qt.rs`, `cli_test.rs`, `cli_workspace.rs`.
**Изменено:**
- **Дедупликация генерации артефактов** — логика линковки бинарных файлов (ELF, PE, EXE), динамических библиотек (`.so`, `.dll`, `.dylib`) и архивации статических библиотек (`.a`, `.lib`) полностью перенесена в выделенный модуль `src/core/build/builder/artifact.rs`.
- **Запуск членов workspace из корня** — команда `dcr run` при вызове из корня workspace-only проекта теперь запускает сборку выбранного пакета в контексте workspace (через флаг `--workspace`), исключая изолированные ошибки сборки.
- **Отказ от глобального состояния** — все операции сборки изолированы внутри структуры `BuildContext`, обеспечивая многопоточную безопасность сборщика.
- **Вынос логики кэширования** — проверка инкрементальных изменений, отслеживание заголовков (`#include`) и вычисление хэшей сборки перенесено в изолированный модуль `src/core/build/cache.rs`.
- **Модуляризация кастомных шагов** — обработка этапа `build.steps` для препроцессоров вынесена в `src/core/build/steps.rs`.
### EN
**Added:**
- **Dedicated Build Engine (`src/core/build/engine.rs`)** — decoupled the build orchestration logic completely from the CLI front-end wrapper in `src/cli/build.rs` to a reusable, decoupled core build engine.
- **Polymorphic Language Model (`Language` trait)** — introduced the abstract `Language` trait (`src/core/build/language/mod.rs`). File scanning, compiler resolution, and flags handling are now encapsulated in dedicated language modules for C, C++ (including Qt code generation), LLVM IR, and ASM (GAS, NASM, FASM, MASM).
- **Polymorphic Compilation Dispatch (`Builder` trait)** — introduced the `Builder` trait to generalize builder invocations (`src/core/build/builder/mod.rs`). Consolidated GCC/Clang logic into `builder/cc_common.rs` and Microsoft Visual C++ logic into `builder/msvc/`.
- **Per-Language Configuration overrides (`[build.<lang>]`)** — introduced support for configuring language-specific compiler, standard, and flags (e.g. `[build.c]`, `[build.cpp]`, `[build.asm]`) independently in `dcr.toml`.
- **Automatic Workspace Dependency Injection** — during workspace builds, include/lib paths of dependent workspace members (including their source headers, local `include/` folders, and build target `target/include` / `target/lib` folders) are automatically resolved and injected.
- **`BuildReporter` Event System** — decoupled build orchestration output into a structured event-driven reporter model (`src/core/build/report.rs`), making DCR suitable for library embedding and IDE integrations without stderr capturing.
- **Build Cancellation Support** — introduced thread-safe cancellation tokens (`Arc<AtomicBool>`) allowing clients to safely abort compile runs mid-execution.
- **TOML Formatting & Custom Keys Preservation** — migrated the config editor to `toml_edit`, preserving all unknown/user-defined TOML keys, structures, and comments during file write operations (e.g., in `dcr add`).
- **Path Dependencies Target Include Resolution** — local path dependencies configured via tables now correctly expose headers built and packaged into their `target/include` folder to consumer packages.
- **Modular Integration Test Suite** — refactored the monolith `tests/cli_basic.rs` file into clean, specialized integration tests: `cli_build.rs`, `cli_deps.rs`, `cli_lint.rs`, `cli_new.rs`, `cli_qt.rs`, `cli_test.rs`, `cli_workspace.rs`.
**Changed:**
- **Consolidated Linking and Archiving** — artifact generation (linking executables/shared libraries and archiving static libraries) consolidated into a single `src/core/build/builder/artifact.rs` module.
- **Workspace-Aware member execution** — `dcr run` executed at the workspace root of a `workspace_only` project now triggers the member build inside the workspace context using the `--workspace` parameter, preventing standalone build issues.
- **Elimination of Global State** — removed global variables and states from the build core, encapsulating build logic inside isolated `BuildContext` structs for thread safety.
- **Build Cache Separation** — relocated mtime tracking, incremental caching, and header-dependency checking logic to `src/core/build/cache.rs`.
- **Modularized Pre-build Steps** — decoupled generator scripts and pre-build commands parsing into `src/core/build/steps.rs`.
## [0.7.4] - 2026-06-17 "Qt Поддержка, Linting и Рефакторинг Архитектуры / Qt Support, Linting, & Architecture Refactor"
### RU
**Добавлено:**
- **Нативная поддержка Qt** — добавлена автоматическая обработка мета-объектов (MOC, UIC, RCC) при включении `build.qt = true` в `dcr.toml`.
- **Команда `dcr lint`** — статический анализ C/C++ через `clang-tidy`. Поддерживает `--fix` для автоматического исправления. Уважает `build.roots` и `build.src_disable` из конфига.
- **Три новых ассемблерных бэкенда**:
- **MASM** (`compiler = "ml"` / `"ml64"`) — Microsoft Macro Assembler.
- **FASM** (`compiler = "fasm"`) — Flat Assembler.
- **LLVM IR** (`compiler = "llc"`) — компиляция `.ll` → объектный файл.
- **Общий ASM-пайплайн** — линковка и архивирование вынесены в `core/builder/asm.rs`. Новый ассемблерный бэкенд теперь пишется за ~30 строк.
- **Утилиты для файловой системы** — `to_hex()` и `home_dir()` вынесены в `utils/fs.rs`.
- **Утилиты для сборки** — `normalize_target()`, `normalize_kind()`, `normalize_platform()`, `default_profile_flags()` вынесены в `utils/build.rs`.
- **Утилиты для бэкендов** — `asm_lang_flag()`, `source_extensions()`, `elapsed_secs()` добавлены в `core/builder/common.rs`.
- **Полноценная автоматизация Bare-Metal / Freestanding** — добавлен конфигурационный флаг `build.freestanding = true`. При его включении (или при обнаружении bare-metal таргета) DCR автоматически инжектирует флаг `-ffreestanding` на этапе компиляции, а также `-nostdlib` и `-static` на этапе линковки.
- **Оптимизация артефактов (LTO и Strip)** — в `dcr.toml` добавлены параметры `build.lto` (авто-инжекция флага `-flto` для компилятора и линкера) и `build.strip` (автоматическое отсекание отладочной информации через флаг `-s` линкера).
- **Контроль многопоточности сборки** — добавлен параметр `build.codegen-units`, позволяющий жестко ограничить максимальное количество параллельных потоков сборки для кастомного движка `parallel_build`.
- **Управление паникой (Panic Abort)** — добавлен параметр `build.panic = "abort"`. Для C++ проектов он автоматически отключает генерацию таблиц раскрутки стека и исключений через флаги `-fno-exceptions`, `-fno-unwind-tables` и `-fno-asynchronous-unwind-tables`.
**Изменено:**
- **Интеллектуальные дефолтные флаги (DCR Defaults)** — генерация дефолтных флагов оптимизации (`-O3`/`-O0`), отладки (`-g`) и предупреждений (`-Wall -Wextra`) теперь автоматически отключается для bare-metal и freestanding целей, если пользователь переопределил `build.cflags`.
- **Глобальный вынос bare-metal детектора** — функция `is_bare_metal_target` вынесена в общие утилиты `src/utils/build.rs` для сквозного использования в конвейере сборки.
- **Рефакторинг архитектуры**:
- `src/config.rs` переименован в `src/templates.rs`.
- Начата консолидация конфигурационной логики и оркестрации сборки.
### EN
**Added:**
- **Native Qt support** — automatic meta-object handling (MOC, UIC, RCC) added when `build.qt = true` is set in `dcr.toml`.
- **`dcr lint` command** — C/C++ static analysis via `clang-tidy`. Supports `--fix` for automatic fixes. Respects `build.roots` and `build.src_disable` from config.
- **Three new assembler backends**:
- **MASM** (`compiler = "ml"` / `"ml64"`) — Microsoft Macro Assembler.
- **FASM** (`compiler = "fasm"`) — Flat Assembler.
- **LLVM IR** (`compiler = "llc"`) — compiles `.ll` files via `llc -filetype=obj`.
- **Shared ASM pipeline** — linking and archiving logic consolidated into `core/builder/asm.rs`. Adding a new assembler backend now takes ~30 lines.
- **Filesystem utilities** — `to_hex()` and `home_dir()` extracted to `utils/fs.rs`.
- **Build utilities** — `normalize_target()`, `normalize_kind()`, `normalize_platform()`, `default_profile_flags()` extracted to `utils/build.rs`.
- **Backend helpers** — `asm_lang_flag()`, `source_extensions()`, `elapsed_secs()` added to `core/builder/common.rs`.
- **Full Bare-Metal / Freestanding Automation** — introduced the `build.freestanding = true` configuration option. When enabled (or when a bare-metal target is detected), DCR automatically injects `-ffreestanding` during compilation and both `-nostdlib` and `-static` during linking.
- **Artifact Optimization (LTO & Strip)** — added `build.lto` (auto-injects `-flto` for compiler and linker) and `build.strip` (automatically strips debug symbols via linker `-s` flag) options to `dcr.toml`.
- **Compilation Thread Control** — added the `build.codegen-units` option to strictly limit the maximum number of parallel jobs utilized by the custom `parallel_build` worker pool.
- **Panic Behavior Management** — added `build.panic = "abort"` support. For C++ targets, it automatically strips exception handling and unwind tables via `-fno-exceptions`, `-fno-unwind-tables`, and `-fno-asynchronous-unwind-tables`.
**Changed:**
- **Intelligent Default Flags Generation** — automated injection of fallback optimization (`-O3`/`-O0`), debug (`-g`), and warning (`-Wall -Wextra`) flags is now suppressed for bare-metal and freestanding builds if `build.cflags` are overridden.
- **Centralized Bare-Metal Detection** — relocated the `is_bare_metal_target` helper to common build utilities (`src/utils/build.rs`) for unified access across the building core.
- **Architectural Refactoring**:
- Renamed `src/config.rs` to `src/templates.rs`.
- Started consolidation of configuration and build orchestration logic.
## [0.7.3] - 2026-06-13 "Оптимизация Зависимостей и VCS / Dependency Optimization & VCS"
### RU
**Добавлено:**
- **Параметр `--vcs` для команд `new` и `init`** — добавлена возможность явно указывать систему контроля версий через `--vcs <git|none>`.
- **Интеграция Git-информации в `--version`** — утилита теперь выводит текущий короткий хэш коммита и суффикс `-dirty`, если в репозитории DCR есть незакоммиченные изменения.
- **Автоматическая генерация `.gitignore`** — при инициализации Git-репозитория автоматически создается файл `.gitignore` с исключением директории `/target`.
- **Защита от вложенных репозиториев** — автоматическая инициализация Git теперь пропускается, если `dcr new` или `dcr init` запускаются внутри уже существующего Git-репозитория.
- **Тесты для верификации VCS** — добавлены интеграционные тесты `new_vcs_options_work` и `init_vcs_options_work` для проверки логики инициализации репозиториев.
**Изменено:**
- **Полный отказ от библиотеки `git2`** — работа с Git (клонирование зависимостей, инициализация VCS, проверка статуса) переведена на вызовы системного `git` через `Command`. Это облегчает сборку проекта и убирает необходимость в фичах `vendored` и `openssl`.
- **Миграция с `reqwest` на `ureq`** — в модуле обновления (`flag_update.rs`) тяжелый `reqwest` заменен на легковесный синхронный клиент `ureq 2.10`, что уменьшило оверхед и размер итогового бинарника.
### EN
**Added:**
- **`--vcs` option for `new` and `init` commands** — introduced explicit version control system selection via `--vcs <git|none>`.
- **Git metadata integration in `--version`** — the CLI now appends the current short commit hash and a `-dirty` suffix if there are uncommitted changes in the DCR repository.
- **Automatic `.gitignore` generation** — Git repository initialization now automatically writes a `.gitignore` file excluding the `/target` directory.
- **Nested repository prevention** — automatic Git initialization is now skipped if `dcr new` or `dcr init` is executed inside an already existing Git repository.
- **VCS verification tests** — added `new_vcs_options_work` and `init_vcs_options_work` integration tests to ensure reliable repository behavior.
**Changed:**
- **Complete removal of `git2` dependency** — all Git actions (dependency fetching, VCS setup, status checks) are now delegated to the system `git` executable via `Command`. This simplifies compilation and drops the need for `vendored` and `openssl` features.
- **Migrated from `reqwest` to `ureq`** — replaced the heavy `reqwest` crate in `flag_update.rs` with the lightweight synchronous `ureq 2.10` client, reducing overhead and the final binary size.
## [0.7.2] - 2026-06-07 "Исправление Таргетов и Линковки / Target & Linker Fixes"
### RU
**Добавлено:**
- Учтён `target_env` в `default_target_triple()` — Linux (gnu/musl) и Windows (msvc/gnu) теперь используют правильное окружение, а не хардкод.
**Исправлено:**
- **Сборка на macOS Apple Silicon падала с `_main` undefined** — `--target=` теперь передаётся и в ldflags, не только в cflags. `default_target_triple()` на macOS использует `std::env::consts::ARCH` вместо хардкода `x86_64`.
- **Бейджи GitHub Stars и GPL-3.0 не рендерились в README** — блок бейджей переведён на чистый HTML.
**Изменено:**
- Вычисление таргета по умолчанию вынесено в общую функцию `default_target_triple()` — устранено дублирование в build, run, clean.
### EN
**Added:**
- `default_target_triple()` now respects `target_env` — Linux (gnu/musl) and Windows (msvc/gnu) use the correct environment instead of hardcoded values.
**Fixed:**
- **macOS Apple Silicon builds failed with `_main` undefined** — `--target=` is now passed to ldflags, not just cflags. `default_target_triple()` uses `std::env::consts::ARCH` on macOS instead of hardcoded `x86_64`.
- **GitHub Stars and GPL-3.0 badges not rendering in README** — badge block converted to pure HTML.
**Changed:**
- Default target resolution extracted into shared `default_target_triple()` — removed duplication across build, run, clean.
## [0.7.1] - 2026-06-02 "Мультиархитектурное расширение / The Multi-Arch Expansion"
### RU
**Добавлено:**
- Вывод `--help` для всех команд с цветным форматированием.
- man-страницы (12 штук в `man/man1/`) и их установка во всех пакетах.
- Валидация имен проектов в `dcr init` и `dcr new` (только ASCII буквы, цифры, `_` и `-`).
- Метаданные `documentation` и `homepage` в `Cargo.toml`.
- Оптимизация сборки: `opt-level = "z"`, LTO, `codegen-units = 1`, `panic = "abort"`, `strip = true`.
- Поддержка новых архитектур (i686, armv7, riscv64gc, aarch64) в рабочих процессах и установочных скриптах.
- Сборка пакетов `deb` и `rpm` для всех 5 архитектур Linux через `cargo-zigbuild`.
- Пакеты AUR (`dcr-dev`, `dcr-dev-bin`) и сборка Snap в CI.
**Изменено:**
- Функция `validate_package_name` стала публичной.
- Команды `gen`, `tree`, `fmt`, `setup` больше не игнорируют аргументы и используют цветной вывод.
- Инструкция в README изменена с `| bash` на `| sh` для POSIX.
- Отключена архитектура Windows 32-bit (GNU) из-за проблем со сборкой.
**Исправлено:**
- **`dcr new <invalid-name>` создаёт директорию, затем падает** — валидация теперь происходит до операций с файлами.
- **`dcr init` создаёт dcr.toml, затем падает при невалидном имени директории** — то же исправление.
- **Man-страницы отсутствовали после установки** — теперь устанавливаются через скрипты установки, пакетные менеджеры и Homebrew.
- **Ложное срабатывание на букву диска в Windows** — в парсинге файлов зависимостей.
- **CRLF `\r\n` ломает парсер зависимостей** — теперь обрабатываются смешанные окончания строк.
- **Новая строка после обратного слеша не поглощалась** — в обработке escape-последовательностей `parse_d_file`.
### EN
**Added:**
- `--help` for all commands with styled headers and usage lines.
- Man pages (12 troff man pages in `man/man1/`) and their installation across all packaging formats.
- Project name validation in `dcr init` and `dcr new` (ASCII letters, digits, `_`, `-` only).
- `documentation` and `homepage` metadata in `Cargo.toml`.
- Release profile optimizations (`opt-level = "z"`, LTO, `codegen-units = 1`, `panic = "abort"`, `strip = true`).
- Linux i686, armv7, riscv64gc, aarch64 architecture support in release workflows and installation scripts.
- Multi-arch `deb` and `rpm` package builds for all 5 Linux architectures via `cargo-zigbuild`.
- AUR packages (`dcr-dev`, `dcr-dev-bin`) and Snap build in CI.
**Changed:**
- `validate_package_name` made public and callable from CLI commands directly.
- `gen.rs`, `tree.rs`, `fmt.rs`, `setup.rs` no longer ignore arguments and use styled output.
- README install instructions changed from `| bash` to `| sh` for POSIX compatibility.
- Disabled Windows 32-bit (GNU) target due to build issues.
Fixed:
- **`dcr new <invalid-name>` creates directory then fails** — validation now happens
before any file operations.
- **`dcr init` creates dcr.toml then fails on invalid directory name** — same fix.
- **Man pages missing after installation** — now installed via install scripts,
package managers, and Homebrew.
- **Windows drive letter false positive** — in dependency file parsing.
- **CRLF `\r\n` breaks dependency parser** — now handles mixed line endings.
- **Newline after backslash not consumed** — in `parse_d_file` escape handling.
## [0.7.0] - 2026-06-02
Added:
- **OpenBSD and NetBSD target support** — full platform routing with dynamically generated target triples using `std::env::consts::ARCH` and `std::env::consts::OS`. Affects `build`, `run`, `clean`, and the platform module.
- **`src/platform/bsd.rs`** — new dedicated BSD platform module shared by FreeBSD, OpenBSD, NetBSD, providing `bin_path`, `lib_path`, `elf_path`, `efi_path`, and `shared_lib_path`.
- **`build.out_dir` configuration option** — custom output directory that overrides the default `target/<triple>/<profile>` path for final artifacts. Supported in `build`, `run`, and config validation.
- **`dcr fmt` command** — new CLI command that formats all C/C++ source files (`*.c`, `*.cpp`, `*.h`, `*.hpp`) in `src/` and `tests/` using `clang-format`.
- **Incremental linking** — `needs_link()` function in `common.rs` that checks whether any object file is newer than the linked output, skipping unnecessary re-linking. Implemented for all backends (`unix_cc`, `msvc`, `gas`, `nasm`).
- **`build.kind = "none"` and `"custom"`** — two new project kind types for special build scenarios where no standard artifact is produced.
- **`install_bsd.sh`** — dedicated POSIX-compliant install script for BSD systems (FreeBSD, OpenBSD, NetBSD) with binary download and source build modes.
- **Linux ARM64 support in `install.sh`** — added `Linux:aarch64|Linux:arm64` target triple detection for pre-built binary downloads.
- **BSD OS detection in `install.sh`** — FreeBSD, OpenBSD, NetBSD detection and target triple resolution.
- **`rust-toolchain.toml`** — explicit `stable` channel toolchain pinning.
- **Nix support** — `flake.nix` for Nix package manager. Run via `nix run github:dexoron/dcr` or install with `nix profile install github:dexoron/dcr`. Contributed by community contributor.
- **Snapcraft publish** — `dcrup` published on Snapcraft. Install with `sudo snap install dcrup`.
- **Integration tests** — `build_with_target_config` (verifies `build.target = "linux"`) and `build_with_out_dir` (verifies custom output directory).
- **`get_build_string_with_profile` made `pub`** — so `run.rs` can resolve custom output directory configuration.
Changed:
- **`build.target` semantics changed** — now strictly holds the target triple (e.g. `x86_64-unknown-linux-gnu`) or a short name (`linux`, `macos`, `windows`). No longer doubles as a custom output directory — that functionality moved to `build.out_dir`.
- **`build.standard` made optional** — changed from `String` to `Option<String>`. Validation only enforces non-empty for non-ASM languages. Skipped in `dcr.toml` output when empty.
- **`dcr run` with `out_dir`** — now resolves target directory respecting `build.out_dir` when configured, via `get_build_string_with_profile()` from `build.rs`.
- **`collect_sources()` returns empty vec** instead of error when no source files are found, allowing `kind = "none"` or `kind = "custom"` projects to have no source files.
- **CI/CD release workflow refactored** — `git2` made target-specific (no vendored-openssl on Windows), Zig-based cross-compilation for non-x86_64 Linux targets, Arch Linux package version sanitization (dashes → dots), NetBSD `gmake` symlink.
- **README compatibility table** — FreeBSD, OpenBSD, NetBSD build/runtime status upgraded from community/best-effort to officially supported.
- **Documentation rewritten** — available at [dcr.dexoron.su/docs](https://dcr.dexoron.su/docs) or in the `docs/` directory. Installation guides, commands, and reference updated to reflect current CLI and config format.
- **Site rewritten** — `dcr-site` migrated to Docusaurus with internationalization (i18n) support, improved navigation, and updated content across all sections.
- **Dev channel dependency check in `install.sh`** — checks for `python3` or `jq` before attempting dev channel installations.
Fixed:
- **`dcr run` stdout/stderr not inherited** — child process output was captured and manually printed, breaking interactive programs. Fixed by switching to `Command::status()`.
- **Release CI race condition** — build matrix jobs could upload assets to a release that did not yet exist. Fixed by adding a dedicated `create-release` job.
- **GHA release — stable toolchain override** — resolved Rust toolchain override issues in CI.
- **Arch Linux package version sanitization** — version strings with dashes (e.g. `0.7.0-dev`) are invalid for Arch Linux `pkgver`. Fixed by replacing dashes with dots.
- **GPG permissions after Docker** — Docker operations changed GPG directory ownership. Fixed by running `chown` after Docker commands.
- **RPM package artifact paths** — RPM artifacts placed under `rpm/x86_64/` instead of `fedora/x86_64/`. Corrected in Dexoron Packages Index workflow.
- **`install.sh` JSON parsing robustness** — added `jq` as primary parser with `python3` fallback for dev channel release lookups.
Removed:
- **`format_roots()` helper** — removed from `common.rs`. Was only used by the old error path in `collect_sources()`.
- **Individual per-distro artifact download steps** — three separate `actions/download-artifact` steps replaced with unified `gh release download --clobber`.
## [0.6.9] - 2026-05-28
Added:
- **Dexoron Packages Index** — new standalone CI workflow (`.github/workflows/dexoron-packages-index.yml`)
that automatically updates the `dexoron/packages` repository indexes after a release.
Supports Arch Linux (`repo-add`), Debian (`dpkg-scanpackages`), and Fedora (`createrepo_c`).
Triggered by `workflow_dispatch` or `workflow_run` on the Release workflow.
- **Arch Linux native package** (`.pkg.tar.zst`) build inside the release workflow —
builds from source in an `archlinux:latest` Docker container using a dynamically
generated PKGBUILD.
- **AUR publish support** — new `run_aur` workflow dispatch input and `publish-aur` /
`publish-aur-bin` jobs that push PKGBUILD / `.SRCINFO` to `aur.archlinux.org`.
Changed:
- **Release workflow refactored** — Linux packages (deb, rpm, snap, arch) and BSD
builds now use `strategy.matrix` instead of separate jobs. A new
`publish-dexoron-packages` job collects all package artifacts and uploads them
to the GitHub Release in one step. Removed standalone `build-arch-package`,
`build-snap`, `build-deb-rpm` jobs. The old `run_arch_native` input is now
`run_linux_packages`.
- **Dexoron Packages Index workflow** extracted from `release.yml` into its own
file with support for Debian and RPM repository indexes in addition to Arch.
- **git2 vendoring** — enabled `vendored-libgit2` and `vendored-openssl` features
for reproducible CI builds without system libraries.
- **cargo-generate-rpm metadata** — updated to the current `package.metadata.rpm`
format (uses `files` table instead of `assets` array).
Fixed:
- **`--target=<triple>` no longer passed to GCC** — the flag is now only injected into
cflags when the resolved compiler name contains `"clang"`. GCC rejects this flag
with `unrecognized command-line option`.
- **Workspace clean test** — removed assertions for member-local `target/`
directories. Workspace members share the root `target/` directory, so their
individual `src/<name>/target/` is never created.
- **Release build test path** — corrected expected directory from `target/release`
to `target/x86_64-unknown-linux-gnu/release`.
## [0.6.8] - 2026-05-27
Added:
- `build.cxx_standard` — separate C++ language standard for `.cpp`/`.cxx`/`.cc` files.
When set, overrides `build.standard` for C++ sources. Example:
```toml
[build]
language = ["c", "c++"]
standard = "gnu11"
cxx_standard = "gnu++17"
```
- Variable substitution for `cflags`, `ldflags`, and `include` directory entries.
All `{version}`, `{version_major}`, `{name}`, `{profile}` and other step variables
are now available. Example:
```toml
[build]
cflags = ['-DPROJECT_VERSION="{version}"']
```
- FreeBSD build support in CI — new `run_bsd` workflow input and `build-bsd` job
that cross-compiles `x86_64-unknown-freebsd` via `cargo-zigbuild`.
Changed:
- License changed from MIT to **GPL-3.0-or-later**. All source files now carry
the GPL-3.0 header. Default license in `dcr new` / `dcr init` changed to
`"GPL-3.0-or-later"`. README, FAQ, and CONTRIBUTING.md updated accordingly.
- `get_list_with_profile_and_target` no longer duplicates list values when
`inherit = true` and the profile/target value is identical to the base value.
Fixed:
- Multi-language `language` array (e.g. `["c", "c++", "asm"]`) no longer drops `c++`
during internal parsing — `.cxx`/`.cpp`/`.cc` source files are correctly discovered
and compiled.
- `-std=` flag is no longer passed to C++ files when only `standard` (C standard) is
configured — uses `cxx_standard` when available, otherwise skips `-std=` for C++
sources, avoiding clang error `invalid argument '-std=c11' not allowed with 'C++'`.
- Object file collision: source files with the same stem but different extensions
(e.g. `gdt.cxx` and `gdt.S`) no longer overwrite each other. Object filenames
now include the original source extension (e.g. `gdt.cxx.o`, `gdt.S.o`).
- MSVC backend compile step now detects language per source file extension instead
of using the global `language` field, fixing mixed C/C++ compilation with MSVC.
- Assembler `-x` flag in `unix_cc.rs` and `gen.rs` is now placed **before** the
source file, as required by clang/gcc.
- `{version}` variables in `build.clean` are now correctly substituted.
## [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
[build]
filename = "KERNEL"
extension = "EFI"
```
Produces `KERNEL.EFI` (works for `bin`, `staticlib`, and `sharedlib`).
- Automatic injection of `--target=<build.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 <path>` 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 <name>` without a source argument — if a registry is configured, DCR looks up the package by name and adds it as a version dependency.
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/<arch>-unknown-linux-gnu/<profile>/` (no stale `target/<profile>/`).
- `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).
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:
- Path dependencies now work correctly: `dcr add` stores them as `{ path =
"./..." }` tables, `is_registry_dep` properly distinguishes registry strings
from path/git strings, and `deps/mod.rs` resolves both table-form and
legacy string-form path dependencies
- Registry dependency paths no longer hardcoded to `project_root/dcr-index`;
now use `package_root_from_registry_info()` which resolves relative to the
registry cache root (`~/.dcr/`)
- Registry dependencies are now actually built: `build_project_at()` is
called when `include_dir` or `lib_dir` is missing
Added:
- `SIGINT`/`Ctrl+C` handler via `ctrlc` crate — `dcr build` now checks
`BUILD_INTERRUPTED` flag at key points and aborts cleanly
- `utils/build.rs` — extracted shared utilities: `parse_version_info`,
`normalize_target_os`, `resolve_compiler`, `primary_language`,
`resolve_pkg_config_flags` and config helpers. Eliminates code duplication
between `cli/build.rs`, `cli/run.rs`, `cli/clean.rs`, `cli/gen.rs`
- `utils/fs.rs::with_dir` — extracted common directory-scoped execution
- `run_command_sync_output` in `builder/common.rs` with global `OUTPUT_MUTEX`
— synchronized compiler output across all backends (unix_cc, gas, nasm, msvc)
- New helper functions in `deps/register.rs`: `get_registry_cache_root`,
`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`)
- `dcr tree` command — visual dependency tree viewer (similar to `cargo tree`)
Removed:
- Removed unused dependencies `indicatif` and `console` from `Cargo.toml`
## [0.6.5] - 2026-05-13
Added:
- New dependency registry system
- Package type support (`lib`, `app`, `none`) in `dcr.toml`
- Library packaging functionality: automatically generates `include` and `lib` directories in `target/` for `lib` type projects
- Expanded CI build targets:
- Linux: `aarch64-unknown-linux-gnu`, `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`
- Windows: `aarch64-pc-windows-msvc`, `x86_64-pc-windows-gnu`, `aarch64-pc-windows-gnullvm`
Changed:
- `src/core/deps/` modular architecture overhaul
- Native platform pathing defaults for Linux (now dynamic architecture detection)
- Improved `run` command output handling
## [0.6.0] - 2026-05-12
Added:
- Full Git dependency support in `dcr.toml`
- Support for `git`, `branch`, `tag`, and `rev` fields
- Automatic cloning and checkout via `git2`
- Dependencies are stored in `target/<profile>/deps/git/`
- New `dcr add` command for easy dependency management
- Short syntax: `github:user/repo`, `gitlab:user/repo`, `path:/to/lib`
- Automatic GitHub resolution for `git:user/repo`
- Visual feedback for dependency fetching with in-place status updates ("Fetching" → "Fetched")
- Modular dependency management architecture in `src/core/deps/`
## [0.5.1] - 2026-04-12
Fixed:
- Windows compatibility for target-specific config without --target flag
Added:
- Default host target detection for automatic target overrides
- Unit tests for target normalization
Improved:
- Config value lookup optimization
- Documentation for Windows cross-compilation
## [0.5.0] - 2026-04-10
Added:
- Full inheritance system for all config sections: `build`, `toolchain`, `run`, `workspace`, `dependencies`
- Order: `section.target.profile` → `section.profile.target` → `section.target` → `section.profile` → `section`
- `inherit = false` to disable inheritance completely
- Target-specific overrides: `[build.linux]`, `[toolchain.windows]`, `[run.macos]`, etc.
- Cross-compilation support with `--target <triple>`
- Full target triples and short names (`linux`, `macos`, `windows`)
- Target-specific config sections: `[build.<target>]`, `[toolchain.<target>]`, `[run.<target>]`
- Workspace and dependencies support target/profile inheritance
- Multiple targets build: `[build.targets]` to build for multiple targets simultaneously
- Target directory structure: `target/<target>/<profile>/` (native or triple)
- Updated documentation with cross-compilation guide and Arch Linux examples
## [0.4.0] - 2026-04-10
Added:
- Test command: `dcr test` and `dcr tests`
- Automatic compilation and linking for tests without manual configuration
- `dcr test --init` creates test template and header
- Test framework with EXPECT/SKIP macros in `dcr_test.h`
- Improved test integration: added test CLI module and basic test suite
- Documentation updates for testing functionality
## [0.3.0] - 2026-03-18
Added:
- `dcr gen` command for IDE integration and build tool support
- - `dcr gen project-info`: Output project metadata as JSON
- - `dcr gen compile-commands`: Generate `compile_commands.json` for clangd and clang-tidy
- - `dcr gen vscode`: Generate VS Code `.vscode/` integration (launch.json, tasks.json, settings.json)
- - `dcr gen clion`: Generate JetBrains CLion `.idea/` integration (run configurations, build targets)
- Full workspace support for all `gen` subcommands
## [0.2.10] - 2026-03-16
Added:
- `build.steps` for pre-build generators (e.g., `moc`/`uic`/`rcc`)
- `build.pkg_config` for `pkg-config`-driven cflags/ldflags resolution
- `[toolchain]` support for `uic`, `moc`, `rcc`
- Step command variables (`{uic}`, `{moc}`, `{rcc}`, `{cflags}`, `{stem}`, `{in}`, `{out}`)
- `{profile}` placeholder support for dependency paths
## [0.2.9] - 2026-03-14
Added:
- Header dependency tracking for fine-grained incremental builds (rebuilds when `#include` files change)
- Complete internal rewrite of the builder module: merged duplicate compilation backends
Changed:
- `cflags` and `default_flags` no longer incorrectly leak to the linker stage in GCC/Clang
- Reduced builder codebase size by over 20% while increasing reliability
- Greatly expanded test suite with 34 new tests covering configuration, CLI errors, and path operations
## [0.2.8] - 2026-03-06
Added:
- Workspace support (`[workspace]` with deps ordering)
- `clean --all` for workspace members
Changed:
- Project root discovery for `build/run/clean` (searches parent dirs)
- Workspace paths are excluded from root source scan
- Build output now uses a Cargo-style `Compiling <name> v<version>` line
- Build time now reports total time for the whole build stage (workspace included)
- Cache hits are silent (no compile line when nothing is rebuilt)
- Safer build cache: fingerprints include headers and resolved library files
## [0.2.7] - 2026-03-03
Added:
- Toolchain overrides via `[toolchain]` and env (`DCR_*`)
- ASM `.S` preprocessing via GCC/Clang (`-x assembler-with-cpp`)
## [0.2.6] - 2026-03-01
Added:
- `sharedlib` build kind (shared library output)
- ASM projects (`build.language = "asm"`)
- NASM and GAS backends
- `build.platform` architecture hint for `-march` / `/arch`
- Basic CLI integration tests
## [0.2.5] - 2026-02-26
Added:
- Incremental builds (object caching by mtime)
- `build.kind` with `staticlib` support
- Custom output directory via `build.target`
## [0.2.4] - 2026-02-22
Added:
- Custom build flags: `build.cflags` and `build.ldflags`
- Recursive source discovery inside `src/`
- Path dependencies with auto include/lib resolution and `dcr.lock` generation (experimental)
- Add docs/index.md and docs/dcr-toml.md
## [0.2.3] - 2026-02-21
Added:
- Modular builders for `gcc`, `clang`, `msvc`
- Platform-specific binary path generation (Linux/macOS/Windows)
Changed:
- Build configuration moved to `[build]` (`language`, `standard`, `compiler`)
- `dcr.toml` formatting now includes the `[build]` section
- Build uses built-in `debug/release` flags per compiler
- Build compiles all `*.c/*.cpp` files in `src/` into a single binary (no incremental build)
- Updated `dcr.toml` examples in documentation
## [0.2.2] - 2026-02-20
Changed:
- Reworked `dcr.toml` handling: added read, validation, and edit through `core::config`
- `new` and `init` use the new config creation logic
- `build` and `run` now require `dcr.toml` and read `compiler` and `name` from it
## [0.2.1] - 2026-02-18
Changed:
- Translated user-facing CLI messages to English
- Unified error and warning output via `utils::log::{error, warn}`
- Updated `--help` output: translated headers and examples to English, used `printc`, and applied `BOLD_*` styles
- Translated installer script messages in `install.sh` and `install.ps1` to English
## [0.2.0] - 2026-02-17
Changed:
- Project migrated from Python to Rust
- CLI and commands (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) ported to Rust
- Updated `--update` flag: added support for GNU/Linux, Windows, macOS
- Updated `README.md`, `CONTRIBUTING.md`, and `install.sh` for the Rust implementation
- Added `install.ps1` for Windows
- Updated `install.sh` for GNU/Linux and macOS
Added:
- Added `install.ps1` for Windows
- Support for GNU/Linux, Windows, macOS (x86_64/arm)
Important:
- Code was ported with neural networks; future versions will include bug fixes and logic changes
## [0.1.2] - 2026-02-12
Added:
- Update command
- `install.sh` install script and README instructions
- `--version` flag
Changed:
- Improved CLI output, added colors, and updated `--help`
- Updated project run/build handling in `run.py`/`build.py`
## [0.1.1] - 2026-02-11
Изменено:
- Обновлена справка `--help`
- `main.py` теперь корректно запускается при прямом выполнении
Changed:
- Updated `--help`
- `main.py` now runs correctly when executed directly
## [0.1.0] - 2026-02-11
Первый публичный релиз.
Добавлено:
- Базовые команды `new`, `init`, `build`, `run`, `clean`
- Профили сборки `debug` и `release`
- Шаблоны `dcr.toml` и `src/main.c`
First public release.
Added:
- Base commands `new`, `init`, `build`, `run`, `clean`
- Build profiles `debug` and `release`
- `dcr.toml` and `src/main.c` templates

87
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,87 @@
# Contributing
Thanks for your interest in the project.
## Requirements
- Use Rust stable.
- Before opening a PR, the code must pass formatting, linting, and build.
## Quick Start
```bash
git clone https://github.com/dexoron/dcr.git
# or a source mirror: GitVerse / SourceCraft / SourceForge / GitLab (see README)
cd dcr
cargo check
```
## Code Style
- Format code with `cargo fmt`.
- Check warnings with `cargo clippy`.
- Keep functions small and focused.
- Prefer clear names over clever ones.
## Target System
DCR supports cross-compilation via `--target <triple>` or short names:
- `dcr build --target linux --release` (maps to `x86_64-unknown-linux-gnu`)
- `dcr build --target macos --release` (maps to `x86_64-apple-darwin`)
- `dcr build --target windows --release` (maps to `x86_64-pc-windows-msvc`)
- Full triples: `dcr build --target aarch64-linux-gnu --release`
- Configure multiple targets in `dcr.toml`:
```toml
[build]
targets = ["linux", "macos"]
[build.linux]
compiler = "gcc"
[build.macos]
compiler = "clang"
```
- If no `--target`, builds for targets in `build.targets` or native if empty.
## Checks
Before PR, run:
```bash
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo check
cargo test
```
## CI/CD and Releases
- CI: `.github/workflows/ci.yml`
- Release: `.github/workflows/release.yml`
- Releases are triggered by tags `v*` and publish binaries for:
- `x86_64-unknown-linux-gnu`
- `x86_64-apple-darwin`
- `aarch64-apple-darwin`
- `x86_64-pc-windows-msvc`
## Commits and PRs
- Write clear commit messages.
- In PRs, include a short description and rationale.
- Update documentation when behavior changes.
## License
This project is licensed under the **GNU General Public License v3.0**. By contributing, you agree that your contributions will be licensed under the same license.
## Questions
If anything is unclear, open an issue or ask in the PR.
You can also reach out directly:
- TG: @dexoron
- DS: dexoron

1682
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

105
Cargo.toml Normal file
View File

@@ -0,0 +1,105 @@
[package]
name = "dcr"
version = "0.9.1"
edition = "2024"
build = "build.rs"
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
license = "GPL-3.0-or-later"
repository = "https://github.com/dexoron/dcr"
documentation = "https://dcr-tool.ru/docs"
homepage = "https://dcr-tool.ru"
[package.metadata.binstall]
pkg-fmt = "bin"
pkg-url = "{ repo }/releases/download/v{ version }/dcr-{ target }-{ version }{ bin-suffix }"
[package.metadata.deb]
maintainer = "Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>"
copyright = "2026, Dexoron"
extended-description = """
Cargo-like utility to manage C/C++ projects.
"""
section = "devel"
priority = "optional"
assets = [
["target/release/dcr", "usr/bin/dcr", "755"],
["man/man1/dcr.1", "usr/share/man/man1/dcr.1", "644"],
["man/man1/dcr-add.1", "usr/share/man/man1/dcr-add.1", "644"],
["man/man1/dcr-build.1", "usr/share/man/man1/dcr-build.1", "644"],
["man/man1/dcr-clean.1", "usr/share/man/man1/dcr-clean.1", "644"],
["man/man1/dcr-fmt.1", "usr/share/man/man1/dcr-fmt.1", "644"],
["man/man1/dcr-gen.1", "usr/share/man/man1/dcr-gen.1", "644"],
["man/man1/dcr-init.1", "usr/share/man/man1/dcr-init.1", "644"],
["man/man1/dcr-new.1", "usr/share/man/man1/dcr-new.1", "644"],
["man/man1/dcr-run.1", "usr/share/man/man1/dcr-run.1", "644"],
["man/man1/dcr-setup.1", "usr/share/man/man1/dcr-setup.1", "644"],
["man/man1/dcr-test.1", "usr/share/man/man1/dcr-test.1", "644"],
["man/man1/dcr-tree.1", "usr/share/man/man1/dcr-tree.1", "644"],
]
[package.metadata.generate-rpm]
summary = "Cargo-like utility to manage C/C++ projects"
description = """
Cargo-like utility to manage C/C++ projects.
"""
assets = [
{ source = "target/release/dcr", dest = "/usr/bin/dcr", mode = "755" },
{ source = "man/man1/dcr.1", dest = "/usr/share/man/man1/dcr.1", mode = "644" },
{ source = "man/man1/dcr-add.1", dest = "/usr/share/man/man1/dcr-add.1", mode = "644" },
{ source = "man/man1/dcr-build.1", dest = "/usr/share/man/man1/dcr-build.1", mode = "644" },
{ source = "man/man1/dcr-clean.1", dest = "/usr/share/man/man1/dcr-clean.1", mode = "644" },
{ source = "man/man1/dcr-fmt.1", dest = "/usr/share/man/man1/dcr-fmt.1", mode = "644" },
{ source = "man/man1/dcr-gen.1", dest = "/usr/share/man/man1/dcr-gen.1", mode = "644" },
{ source = "man/man1/dcr-init.1", dest = "/usr/share/man/man1/dcr-init.1", mode = "644" },
{ source = "man/man1/dcr-new.1", dest = "/usr/share/man/man1/dcr-new.1", mode = "644" },
{ source = "man/man1/dcr-run.1", dest = "/usr/share/man/man1/dcr-run.1", mode = "644" },
{ source = "man/man1/dcr-setup.1", dest = "/usr/share/man/man1/dcr-setup.1", mode = "644" },
{ source = "man/man1/dcr-test.1", dest = "/usr/share/man/man1/dcr-test.1", mode = "644" },
{ source = "man/man1/dcr-tree.1", dest = "/usr/share/man/man1/dcr-tree.1", mode = "644" },
]
[features]
default = []
archive = ["dep:fatfs"]
[dependencies]
glob = "0.3"
ureq = { version = "2.10", default-features = false, features = ["json", "native-certs"] }
serde = { version = "1", features = ["derive"] }
self-replace = "1"
sha2 = "0.10"
toml = "1.0.3"
toml_edit = "0.22"
serde_json = "1"
ctrlc = "3"
fatfs = { version = "0.3", optional = true }
# Logging
log = "0.4.34"
env_logger = "0.11.11"
owo-colors = "4"
[profile.release]
opt-level = "z"
lto = "thin"
codegen-units = 1
panic = "abort"
strip = true
[profile.fast]
inherits = "release"
opt-level = 1
lto = false
codegen-units = 16
panic = "abort"
strip = true
[profile.dev]
incremental = false
opt-level = 0
debug = "line-tables-only"
[profile.dev.package."*"]
opt-level = 1
debug = false

134
FAQ.md Normal file
View File

@@ -0,0 +1,134 @@
# FAQ
## What is this project about?
`dcr` is a tool for managing C/C++ projects and their dependencies.
It is inspired by Cargo, but it is not a direct equivalent of Makefile, CMake, or other classic build systems.
The goal is to provide a simpler and easier-to-configure development workflow.
## How is the project developed?
The project is written in Rust and is primarily developed by one maintainer, Dexoron.
AI tools are used during development, but they do not write the project end-to-end. Their role is limited to supporting specific tasks (for example, suggestions, wording checks, and translations).
## Which tools are used for development?
Main development stack:
- `RustRover` 2025 - development IDE.
- `Codex` (`GPT-5.3-Codex (medium)`) - primary AI tool, integrated via JetBrains AI Chat.
- `Rust` - main project language.
- `Cargo` - tool for building, testing, and dependency management in Rust projects.
## Is the project open source?
Yes, the project is open source.
You can propose ideas, report issues, and contribute code changes.
The project is distributed under the GPL-3.0 license (see `LICENSE`).
## How can I contribute?
Thank you for contributing.
You can:
- help with code;
- help with translations;
- help with documentation;
- suggest an idea or report a bug.
### If you want to submit changes (code, translation, documentation)
1. Fork the required repository: `dexoron/dcr` (main project) or `dexoron/dcr-site` (project website).
2. Make your changes following the rules in `CONTRIBUTING.md`.
3. Push changes to your fork.
4. Open a Pull Request.
5. Wait for review.
### If you want to report a problem or suggest an idea
1. Open the relevant repository: `dexoron/dcr` or `dexoron/dcr-site`.
2. Go to the `Issues` tab.
3. Create an Issue and specify its type (bug, suggestion, question/help).
4. Describe the problem or idea clearly and concisely.
5. Wait for feedback.
## How do I install `dcr`?
Available installation methods may vary by project version.
Recommended path:
- check instructions in `README.md`;
- use `dcrup install stable` (default mirror chain: **cf → gh → sf → ya**);
- pin a host with `--mirror cf|gh|sf|ya` or `DCRUP_MIRROR` if needed;
- if there is no prebuilt package for your platform, build from source using `cargo build --release`.
## How do I choose a download mirror?
`dcrup` defaults to `--mirror auto`: CloudFlare CDN, then GitHub, then SourceForge, then Yandex CDN.
```sh
dcrup install stable --mirror cf
export DCRUP_MIRROR=ya
```
## How can I get started quickly?
Minimal workflow:
1. Install `dcr`.
2. Create a project (or open an existing C/C++ project).
3. Initialize `dcr` configuration.
4. Add project dependencies.
5. Build using `dcr`.
For detailed commands and examples, see `README.md` and the project documentation.
## What is the project status?
`dcr` is under active development.
This means some interfaces and behaviors may change between versions.
Before upgrading, check the changelog and release notes.
## Which systems does `dcr` support?
`dcr` targets modern C/C++ development environments.
Actual compatibility depends on the `dcr` version, operating system, and toolchain.
Before starting, check documentation for:
- supported operating systems;
- supported compilers and linkers;
- version-specific limitations.
## How is `dcr` different from Make/CMake?
`dcr` does not aim to be a direct replacement for Make/CMake.
Its main focus is simplifying project and dependency management with a Cargo-like experience.
If you need low-level, full control over build steps, classic tools may be a better fit.
If you want a simpler and more uniform configuration model, `dcr` may be more convenient.
## Where can I find documentation and examples?
Primary sources:
- `README.md` in the repository;
- project documentation (see the project website);
- `Issues` for known problems and discussions.
## How do I report a bug properly?
To make a bug report easy to reproduce, include:
1. `dcr` version.
2. OS and compiler/toolchain version.
3. Steps to reproduce.
4. Expected behavior.
5. Actual behavior and error output.
6. Minimal project example (if possible).
Submit bugs and issues through `Issues` in the relevant repository.

688
LICENSE
View File

@@ -1,21 +1,675 @@
MIT License
# GNU GENERAL PUBLIC LICENSE
Copyright (c) 2026 Dexoron (Bezotechestvo Vladimir) [main@dexoron.su]
Version 3, 29 June 2007
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom
to share and change all versions of a program--to make sure it remains
free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies
also to any other work released this way by its authors. You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you
have certain responsibilities if you distribute copies of the
software, or if you modify it: responsibilities to respect the freedom
of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the
manufacturer can do so. This is fundamentally incompatible with the
aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for
individuals to use, which is precisely where it is most unacceptable.
Therefore, we have designed this version of the GPL to prohibit the
practice for those products. If such problems arise substantially in
other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the
freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish
to avoid the special danger that patents applied to a free program
could make it effectively proprietary. To prevent this, the GPL
assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU General Public
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that numbered version or
of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public
License, you may choose any version ever published by the Free
Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU General Public License can be used, that proxy's public
statement of acceptance of a version permanently authorizes you to
choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, your
program's commands might be different; for a GUI interface, you would
use an "about box".
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your
program into proprietary programs. If your program is a subroutine
library, you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
GNU Lesser General Public License instead of this License. But first,
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.

341
README.md
View File

@@ -1,103 +1,292 @@
# DCR (Dexoron Cargo Realization)
<h1 align="center">DCR — Dexoron Cargo Realization</h1>
DCR — это утилита для управления проектами на C. Она задает единый формат проекта, дает команды для создания, сборки и запуска, и использует один конфигурационный файл `dcr.toml`.
<p align="center"><strong>A Cargo-style build tool for C/C++ projects</strong></p>
Проект находится на стадии MVP. Текущая реализация написана на Python и в будущем будет переписана.
<p align="center">
<a href="https://github.com/dexoron/dcr/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/dexoron/dcr/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/dexoron/dcr/releases/latest"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/dexoron/dcr"></a>
<a href="https://github.com/dexoron/dcr/releases"><img alt="Platform" src="https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey"></a>
<br>
<a href="https://aur.archlinux.org/packages/dcr"><img alt="AUR" src="https://img.shields.io/aur/version/dcr"></a>
<a href="https://crates.io/crates/dcr"><img alt="Crates.io" src="https://img.shields.io/crates/v/dcr"></a>
<a href="https://github.com/dexoron/homebrew-dexoron"><img alt="Homebrew" src="https://img.shields.io/badge/homebrew-dexoron%2Fdexoron-orange"></a>
<br>
<a href="https://github.com/dexoron/dcr/stargazers"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/dexoron/dcr?style=flat"></a>
<a href="LICENSE"><img alt="License: GPL-3.0" src="https://img.shields.io/badge/License-GPL--3.0-blue.svg"></a>
</p>
## Зачем DCR
- Единая структура проекта без ручной настройки
- Простые команды для типовых задач
- Прозрачная компиляция и предсказуемые профили сборки
<!-- Snap badge removed: classic confinement was denied for this project. -->
## Возможности
- Создание нового проекта или инициализация текущей директории
- Сборка проекта в профилях `debug` и `release`
- Запуск собранного бинарника
- Очистка результатов сборки
- Генерация минимального шаблона C-проекта
---
## Быстрый старт
Создать новый проект:
`dcr new hello`
## Why DCR?
Или инициализировать текущую директорию (директория должна быть пустой):
`dcr init`
- **No boilerplate** — one config file, predictable structure
- **Cargo-like workflow** — `build`, `run`, `clean`, `test`, `add`, `fmt`
- **Cross-compilation** — full target triple support with short names
- **IDE integration** — VS Code, CLion, `compile_commands.json` out of the box
- **Dependencies** — path, git, and registry-based with lock file
- **Man pages** — `man dcr` and per-command pages (`man dcr-build`, `man dcr-new`, etc.)
---
## Installation
<table>
<tr>
<td><b>Arch Linux (AUR)</b></td>
<td>
```sh
yay -S dcr
# or
paru -S dcr
```
> 💡 Do you like DCR? Vote for the package on the [AUR](https://aur.archlinux.org/packages/dcr) page—it will help the project get into the official "extra" repository!
</td>
</tr>
<tr>
<td><b>macOS/Linux(GNU) (Homebrew)</b></td>
<td>
```sh
brew tap dexoron/dexoron
brew install dcr
```
</td>
</tr>
<tr>
<td><b>Nix (flake)</b></td>
<td>
```sh
nix run github:dexoron/dcr
# or install to profile:
nix profile install github:dexoron/dcr
```
</td>
</tr>
<tr>
<td><b>Cargo (crates.io)</b></td>
<td>
```sh
cargo install dcr
```
</td>
</tr>
<tr>
<td><b>dcrup (Linux / macOS / BSD / Windows(Bash))</b></td>
<td>
```sh
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
export PATH="$HOME/.dcr/bin:$PATH"
dcrup install stable # auto: cf → gh → sf → ya
# dcrup install stable --mirror cf # CloudFlare CDN
# dcrup install stable --mirror ya # Yandex CDN (RU)
```
</td>
</tr>
<tr>
<td><b>dcrup (Windows PowerShell)</b></td>
<td>
```powershell
irm https://ps1.dcr-tool.ru | iex
# or:
# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
# powershell -File .\dcrup.ps1 self-install
# $env:Path += ";$env:USERPROFILE\.dcr\bin"
dcrup install stable # auto: cf → gh → sf → ya
# dcrup install stable --mirror cf # CloudFlare CDN
# dcrup install stable --mirror ya # Yandex CDN (RU)
```
</td>
</tr>
<tr>
<td><b>Snap (Linux)</b></td>
<td>
> Snap Store publishing temporarily disabled — see [#51671](https://forum.snapcraft.io/t/classic-confinement-request-for-dcrup/51671).
> Download the `.snap` from [GitHub Releases](https://github.com/dexoron/dcr/releases/latest) and install manually:
```sh
sudo snap install --classic --dangerous dcrup_*.snap
```
</td>
</tr>
<tr>
<td><b>From source</b></td>
<td>
```sh
git clone https://github.com/dexoron/dcr.git
cd dcr && cargo build --release --features archive
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
```
</td>
</tr>
</table>
---
## Quick Start
```sh
dcr new hello
cd hello
dcr run
```
**Project structure:**
Структура проекта:
```
hello/
- src/
- - main.c
- dcr.toml
├── dcr.toml # project config
└── src/
└── main.c
```
## Команды
---
### `dcr new <name>`
Создает проект в текущей директории с указанным именем.
## dcr.toml Example
### `dcr init`
Создает проект в текущей директории. Имя проекта будет равно имени директории. Директория должна быть пустой.
### `dcr build [profile]`
Собирает проект. Если профиль не указан, используется `--debug`.
### `dcr run [profile]`
Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`.
Запуск вручную:
`./target/<profile>/main`
### `dcr clean`
Удаляет папку `target` в корне проекта.
## Профили сборки
Поддерживаются два профиля:
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
- `--release` — флаги: `-O3 -DNDEBUG -march=native`
## Конфигурация
Основной файл проекта — `dcr.toml`. Сейчас он создается автоматически и служит точкой входа для будущих настроек.
Пример `dcr.toml`:
```
```toml
[package]
name = "hello"
version = "0.1.0"
[build]
language = "c"
standard = "c11"
compiler = "clang"
kind = "bin"
[dependencies]
```
## Установка в PATH
Чтобы вызывать `dcr` из любой директории, можно сделать symlink в `~/.local/bin`:
---
## Base Commands
| Command | Description |
|---|---|
| `dcr new <name>` | Create a new project |
| `dcr init` | Initialize current directory |
| `dcr add <dep>` | Add a dependency |
| `dcr build [--release]` | Build the project |
| `dcr run [--release]` | Build and run |
| `dcr clean` | Remove build artifacts |
| `dcr test` | Run project tests |
| `dcr fmt` | Format C/C++ sources (clang-format) |
| `dcr tree` | View dependency tree |
| `dcr gen <vscode\|clion\|compile-commands>` | Generate IDE integration |
| `dcr setup` | Initialize DCR registry |
other in [docs/](docs/), `dcr --help` and `dcr <command> --help`
---
## Platforms (pre-built binary)
<div align="center">
| OS | x86_64 | aarch64 | armv7 | i686 | riscv64 |
|:---:|:---:|:---:|:---:|:---:|:---:|
| Linux (GNU) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Linux (Musl) | ✅ | ✅ | ✅ | ✅ | 🚧 |
| macOS | ✅ | ✅ | ❌ | ❌ | ❌ |
| Windows (MSVC) | ✅ | ✅ | ❌ | ✅ | ❌ |
| Windows (MinGW) | ✅ | 🚧 | ❌ | ❌ | ❌ |
| FreeBSD | ✅ | 🚧 | ❌ | ❌ | ❌ |
| OpenBSD | ✅ | 🚧 | ❌ | ❌ | ❌ |
| NetBSD | ✅ | 🚧 | ❌ | ❌ | ❌ |
✅ pre-built binary · 🚧 planned · ❌ not supported by platform or CI
</div>
---
## Documentation
Full docs at **[dcr.dexoron.su](https://dcr.dexoron.su)** or in the [`docs/`](docs/) directory.
---
## Repository Mirrors
The primary repository is hosted on GitHub. Source and release mirrors:
| Region | Mirror | Contents |
|---|---|---|
| Worldwide | [GitHub](https://github.com/dexoron/dcr) | Source code and releases |
| Worldwide | [SourceForge](https://sourceforge.net/projects/dcr-tool/) | Source code and releases |
| Russia | [GitVerse](https://gitverse.ru/dcr_labs/dcr) | Source code |
| Russia | [SourceCraft](https://sourcecraft.dev/dexoron-dev/dcr) | Source code |
Release CDN (used by `dcrup`):
| Code | Host | Role |
|---|---|---|
| `cf` | [cdn-global.dcr-tool.ru](https://cdn-global.dcr-tool.ru) (CloudFlare) | Worldwide CDN |
| `gh` | GitHub Releases | Primary host |
| `sf` | SourceForge | Worldwide fallback |
| `ya` | [cdn-ru.dcr-tool.ru](https://cdn-ru.dcr-tool.ru) (Yandex) | Paid RU fallback for restrictions |
`dcrup install` defaults to `--mirror auto` and tries **cf → gh → sf → ya**. Override with `--mirror cf|gh|sf|ya` or `DCRUP_MIRROR`.
---
## VS Code Extension
Install the recommended [DCR extension by Dexoron-Labs](https://marketplace.visualstudio.com/items?itemName=Dexoron-Labs.dcr) for VS Code. Do not install Microsoft C/C++ extensions for DCR projects; DCR uses `clangd` instead. If you need debugging with Microsoft's debugger, use MS C/C++ only for its debugger component.
Recommended companion extensions:
- [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)
- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), or MS C/C++ for debugger support only
---
## Contributors
<div align="center">
| | Name | Role | GitHub |
|:---:|---|---|---|
| 👤 | Dexoron (Bezotechestvo Vladimir) | Maintainer, Creator | [@dexoron](https://github.com/dexoron) |
| 👤 | Kai | Maintainer | [@peoplemiau1](https://github.com/peoplemiau1) |
</div>
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). Before a PR:
```sh
ln -s "$PWD/dcr" ~/.local/bin/dcr
```
Убедитесь, что `~/.local/bin` находится в `PATH`.
## Пример проекта
`src/main.c` содержит минимальный `Hello, World!`:
```c
#include <stdio.h>
int main(void) {
puts("Hello, World!");
return 0;
}
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test
```
## Требования
- C-компилятор (например, `gcc` или `clang`)
- Python 3.14.2 (для текущей версии DCR)
- uv (менеджер проекта для Python)
---
## Статус и планы
Проект в стадии MVP. Планируется переписывание на более подходящий язык и расширение функциональности:
- более гибкая конфигурация через `dcr.toml`
- расширяемые профили сборки
- менеджер пакетов
<div align="center">
## Лицензия
См. файл `LICENSE`.
GPL-3.0 — see [LICENSE](LICENSE)<br/>
DCR is a build tool, not a library. The GPL applies only to DCR's own source code, not to projects built with it.<br/>
Made with ❤️ by [Dexoron](https://github.com/dexoron) and contributors.
</div>

54
build.rs Normal file
View File

@@ -0,0 +1,54 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
fn main() {
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown-target".to_string());
println!("cargo:rustc-env=DCR_TARGET={target}");
// Let's try to retrieve the commit hash for the version
let commit = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout)
.ok()
.map(|s| s.trim().to_string())
} else {
None
}
});
// Check if there are any uncommitted changes in the repository
let is_dirty = std::process::Command::new("git")
.args(["status", "--porcelain"])
.output()
.ok()
.map(|o| o.status.success() && !o.stdout.is_empty())
.unwrap_or(false);
if let Some(commit_hash) = commit {
let dirty_suffix = if is_dirty { "-dirty" } else { "" };
println!(
"cargo:rustc-env=DCR_GIT_INFO= (git {}{})",
commit_hash, dirty_suffix
);
} else {
println!("cargo:rustc-env=DCR_GIT_INFO=");
}
}

3
crowdin.yml Normal file
View File

@@ -0,0 +1,3 @@
files:
- source: /docs/**/*.mdx
translation: /docs-crowdin-export/%locale%/%original_path%/%original_file_name%.%file_extension%

5
dcr
View File

@@ -1,5 +0,0 @@
#!/bin/sh
BASE_DIR="$(dirname "$(readlink -f "$0")")"
SRC_DIR="$BASE_DIR/src"
PYTHONPATH="$SRC_DIR" exec python3 -m dcr.main "$@"

13
dcrup.cmd Executable file
View File

@@ -0,0 +1,13 @@
@echo off
setlocal
set "SCRIPT_DIR=%~dp0"
if exist "%SCRIPT_DIR%dcrup.ps1" (
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%dcrup.ps1" %*
exit /b %ERRORLEVEL%
)
if exist "%USERPROFILE%\.dcr\bin\dcrup.ps1" (
powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\.dcr\bin\dcrup.ps1" %*
exit /b %ERRORLEVEL%
)
echo [error] dcrup.ps1 not found near dcrup.cmd >&2
exit /b 1

434
dcrup.ps1 Normal file
View File

@@ -0,0 +1,434 @@
#Requires -Version 5.1
$ErrorActionPreference = 'Stop'
$DcrupHome = if ($env:DCRUP_HOME) { $env:DCRUP_HOME } else { Join-Path $env:USERPROFILE '.dcr' }
$DcrupBin = if ($env:DCRUP_BIN) { $env:DCRUP_BIN } else { Join-Path $DcrupHome 'bin' }
$DcrupTc = Join-Path $DcrupHome 'toolchains'
$DcrupMeta = Join-Path $DcrupHome 'meta'
$RepoUrl = if ($env:DCRUP_REPO) { $env:DCRUP_REPO } else { 'https://github.com/dexoron/dcr' }
$DcrupMirror = if ($env:DCRUP_MIRROR) { $env:DCRUP_MIRROR } else { 'auto' }
$ApiLatest = 'https://api.github.com/repos/dexoron/dcr/releases/latest'
$ApiAll = 'https://api.github.com/repos/dexoron/dcr/releases'
$Features = if ($env:DCR_FEATURES) { $env:DCR_FEATURES } else { 'archive' }
function Info($m) { Write-Host "[dcrup] $m" -ForegroundColor Cyan }
function Ok($m) { Write-Host "[ok] $m" -ForegroundColor Green }
function Warn($m) { Write-Host "[warn] $m" -ForegroundColor Yellow }
function Die($m) { Write-Host "[error] $m" -ForegroundColor Red; exit 1 }
function Show-Usage {
@'
dcrup — install and switch DCR versions (Windows)
Usage:
dcrup install <spec> [--build|--release] [--force] [--mirror auto|cf|gh|sf|ya]
dcrup default <spec>
dcrup update
dcrup list
dcrup show
dcrup which
dcrup uninstall [<spec>|--all]
dcrup self-install [--to DIR]
dcrup help
Spec:
stable | dev | night
VERSION → VERSION@stable (e.g. 0.8.2)
VERSION@stable
VERSION@dev
night → always build from branch "dev" HEAD
Modes:
--release download prebuilt binary (default for stable/dev)
--build cargo build --release --features archive
night always --build (prebuilt not available)
Mirrors (prebuilt download only):
--mirror auto try cf → gh → sf → ya, first successful download wins (default)
--mirror cf download from cdn-global.dcr-tool.ru
--mirror gh download from github.com/dexoron/dcr
--mirror sf download from sourceforge.net/projects/dcr-tool
--mirror ya download from cdn-ru.dcr-tool.ru
DCRUP_MIRROR env override (default: auto)
Env:
DCRUP_HOME default ~/.dcr
DCRUP_BIN default $DCRUP_HOME/bin
DCR_FEATURES default archive
DCRUP_REPO git/GitHub repo URL
DCRUP_MIRROR auto|cf|gh|sf|ya (default: auto)
'@ | Write-Host
}
function Ensure-Dirs {
New-Item -ItemType Directory -Force -Path $DcrupTc, $DcrupBin, $DcrupMeta | Out-Null
}
function Get-Target {
$arch = $env:PROCESSOR_ARCHITECTURE
if ($env:PROCESSOR_ARCHITEW6432) { $arch = $env:PROCESSOR_ARCHITEW6432 }
switch ($arch) {
'AMD64' { return 'x86_64-pc-windows-msvc' }
'ARM64' { return 'aarch64-pc-windows-msvc' }
'x86' { return 'i686-pc-windows-msvc' }
default { Die "unsupported arch: $arch" }
}
}
function Normalize-Version([string]$v) {
if ($v.StartsWith('v')) { return $v.Substring(1) }
return $v
}
function Parse-Spec([string]$raw) {
$script:SpecRaw = $raw
$script:SpecChannel = $null
$script:SpecVersion = $null
$script:SpecFloating = $false
if ($raw -in @('stable', 'dev', 'night')) {
$script:SpecChannel = $raw
$script:SpecFloating = $true
return
}
if ($raw -match '^(.*)@(.*)$') {
$script:SpecVersion = Normalize-Version $Matches[1]
$script:SpecChannel = $Matches[2]
if ($script:SpecChannel -eq 'night') { Die 'cannot pin version on night' }
if ($script:SpecChannel -notin @('stable', 'dev')) { Die "unknown channel: $($script:SpecChannel)" }
return
}
$script:SpecVersion = Normalize-Version $raw
$script:SpecChannel = 'stable'
}
function Toolchain-Id([string]$extra = '') {
if ($script:SpecChannel -eq 'night') {
return "night-$extra"
}
if ($script:SpecFloating) {
return "$($script:SpecChannel)-$extra"
}
return "$($script:SpecVersion)-$($script:SpecChannel)"
}
function Active-Path { Join-Path $DcrupMeta 'active' }
function Read-Active {
$p = Active-Path
if (Test-Path $p) { return (Get-Content $p -Raw).Trim() }
return $null
}
function Write-Active([string]$id) {
Set-Content -Path (Active-Path) -Value $id -NoNewline
}
function Link-Default([string]$id) {
$src = Join-Path (Join-Path $DcrupTc $id) 'dcr.exe'
if (-not (Test-Path $src)) { Die "toolchain not installed: $id" }
$dst = Join-Path $DcrupBin 'dcr.exe'
Copy-Item $src $dst -Force
Write-Active $id
Ok "default → $id ($dst)"
}
function Set-Mirror([string]$value) {
if ($value -notin @('auto', 'cf', 'gh', 'sf', 'ya')) {
Die "invalid --mirror: $value (use auto|cf|gh|sf|ya)"
}
$script:DcrupMirror = $value
}
function Get-MirrorUrl([string]$mirror, [string]$tag, [string]$asset) {
switch ($mirror) {
'cf' { return "https://cdn-global.dcr-tool.ru/$tag/$asset" }
'gh' { return "https://github.com/dexoron/dcr/releases/download/$tag/$asset" }
'sf' { return "https://sourceforge.net/projects/dcr-tool/files/$tag/$asset/download" }
'ya' { return "https://cdn-ru.dcr-tool.ru/$tag/$asset" }
default { Die "invalid mirror: $mirror" }
}
}
function Get-MirrorsToTry {
switch ($script:DcrupMirror) {
'auto' { return @('cf', 'gh', 'sf', 'ya') }
{ $_ -in @('cf', 'gh', 'sf', 'ya') } { return @($script:DcrupMirror) }
default { Die "invalid mirror: $($script:DcrupMirror) (use auto|cf|gh|sf|ya)" }
}
}
function Download-FromMirrors([string]$tag, [string]$asset, [string]$destFile) {
$tmp = "$destFile.download"
foreach ($m in (Get-MirrorsToTry)) {
$url = Get-MirrorUrl $m $tag $asset
Info "trying mirror $m …"
Remove-Item $tmp -ErrorAction SilentlyContinue
try {
Invoke-WebRequest -Uri $url -OutFile $tmp -UserAgent 'dcrup'
$size = (Get-Item $tmp).Length
if ($size -gt 100000) {
Move-Item $tmp $destFile -Force
return $m
}
Warn "mirror $m returned a too-small file ($size bytes)"
} catch {
Warn "mirror $m failed"
}
Remove-Item $tmp -ErrorAction SilentlyContinue
}
return $null
}
function Fetch-Release($channel, $version) {
if ($version) {
return Invoke-RestMethod -Uri "https://api.github.com/repos/dexoron/dcr/releases/tags/v$version"
}
if ($channel -eq 'dev') {
$all = Invoke-RestMethod -Uri $ApiAll
$pre = $all | Where-Object { $_.prerelease } | Select-Object -First 1
if (-not $pre) { Die 'no prerelease (dev) found' }
return $pre
}
return Invoke-RestMethod -Uri $ApiLatest
}
function Install-Prebuilt([switch]$Force) {
$triple = Get-Target
$rel = Fetch-Release $script:SpecChannel $script:SpecVersion
$tag = $rel.tag_name
$ver = Normalize-Version $tag
$assetName = "dcr-$triple-$ver.exe"
$id = if ($script:SpecFloating) { Toolchain-Id $ver } else { Toolchain-Id }
$dest = Join-Path $DcrupTc $id
$bin = Join-Path $dest 'dcr.exe'
if ((Test-Path $bin) -and -not $Force) {
Ok "already installed: $id"
Link-Default $id
return
}
Ensure-Dirs
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Info "downloading $assetName (mirror=$($script:DcrupMirror)) …"
$mirror = Download-FromMirrors $tag $assetName $bin
if (-not $mirror) { Die "no mirror reachable for $assetName (try: --mirror cf|gh|sf|ya)" }
Info "mirror → $mirror"
@"
channel=$($script:SpecChannel)
version=$ver
tag=$tag
source=release
triple=$triple
mirror=$mirror
"@ | Set-Content (Join-Path $dest 'dcrup-meta')
Ok "installed prebuilt $id ($triple via $mirror)"
Link-Default $id
}
function Install-Build([switch]$Force) {
if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Die 'git not found' }
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { Die 'cargo not found' }
$tmp = Join-Path $env:TEMP ("dcrup-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $tmp | Out-Null
try {
$src = Join-Path $tmp 'src'
if ($script:SpecChannel -eq 'night') {
Info "cloning dev for night …"
git clone --depth 1 --branch dev $RepoUrl $src 2>$null
if ($LASTEXITCODE -ne 0) { git clone --depth 1 $RepoUrl $src | Out-Null }
$sha = (git -C $src rev-parse --short HEAD).Trim()
$id = Toolchain-Id $sha
$ver = 'night'
} elseif ($script:SpecFloating) {
$rel = Fetch-Release $script:SpecChannel $null
$tag = $rel.tag_name
$ver = Normalize-Version $tag
$id = Toolchain-Id $ver
Info "cloning $tag …"
git clone --depth 1 --branch $tag $RepoUrl $src 2>$null
if ($LASTEXITCODE -ne 0) {
git clone --depth 1 $RepoUrl $src | Out-Null
git -C $src fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
git -C $src checkout $tag
}
$sha = (git -C $src rev-parse --short HEAD).Trim()
} else {
$tag = "v$($script:SpecVersion)"
$ver = $script:SpecVersion
$id = Toolchain-Id
Info "cloning $tag …"
git clone --depth 1 --branch $tag $RepoUrl $src 2>$null
if ($LASTEXITCODE -ne 0) {
git clone --depth 1 $RepoUrl $src | Out-Null
git -C $src fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
git -C $src checkout $tag
}
$sha = (git -C $src rev-parse --short HEAD).Trim()
}
$dest = Join-Path $DcrupTc $id
$bin = Join-Path $dest 'dcr.exe'
if ((Test-Path $bin) -and -not $Force) {
Ok "already installed: $id"
Link-Default $id
return
}
$triple = Get-Target
Info "building release (features: $Features) …"
Push-Location $src
cargo build --release --features $Features --target $triple
if ($LASTEXITCODE -ne 0) { Die 'cargo build failed' }
Pop-Location
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item (Join-Path $src "target\$triple\release\dcr.exe") $bin -Force
@"
channel=$($script:SpecChannel)
version=$ver
git=$sha
source=build
features=$Features
"@ | Set-Content (Join-Path $dest 'dcrup-meta')
Ok "installed build $id"
Link-Default $id
} finally {
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
}
}
function Ensure-UserPath {
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if (-not $userPath) { $userPath = '' }
if (-not ($userPath -split ';' | Where-Object { $_ -eq $DcrupBin })) {
$new = if ($userPath) { "$userPath;$DcrupBin" } else { $DcrupBin }
[Environment]::SetEnvironmentVariable('Path', $new, 'User')
$env:Path = "$env:Path;$DcrupBin"
Warn "added to user PATH: $DcrupBin (restart shell if needed)"
}
}
function Install-CmdShim {
$cmd = Join-Path $DcrupBin 'dcrup.cmd'
$ps1 = Join-Path $DcrupBin 'dcrup.ps1'
@"
@echo off
setlocal
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0dcrup.ps1" %*
"@ | Set-Content -Path $cmd -Encoding ASCII
if (-not (Test-Path $ps1)) {
Copy-Item $PSCommandPath $ps1 -Force
}
}
function Cmd-Install {
param([string[]]$Args)
$mode = 'release'
$force = $false
$spec = $null
$i = 0
while ($i -lt $Args.Count) {
$a = $Args[$i]
switch -Regex ($a) {
'^--build$' { $mode = 'build' }
'^--release$' { $mode = 'release' }
'^--force$' { $force = $true }
'^--mirror$' {
if ($i + 1 -ge $Args.Count) { Die '--mirror needs auto|cf|gh|sf|ya' }
$i++
Set-Mirror $Args[$i]
}
'^--mirror=(.+)$' { Set-Mirror $Matches[1] }
'^-' { Die "unknown flag: $a" }
default {
if ($spec) { Die "unexpected: $a" }
$spec = $a
}
}
$i++
}
if (-not $spec) { Die 'usage: dcrup install <spec> [--build|--release] [--force] [--mirror auto|cf|gh|sf|ya]' }
Parse-Spec $spec
Ensure-Dirs
if ($script:SpecChannel -eq 'night') { $mode = 'build' }
Info "install spec=$spec channel=$($script:SpecChannel) mode=$mode mirror=$($script:DcrupMirror)"
if ($mode -eq 'build') { Install-Build -Force:$force } else { Install-Prebuilt -Force:$force }
Ensure-UserPath
Install-CmdShim
}
function Cmd-List {
Ensure-Dirs
$active = Read-Active
Get-ChildItem $DcrupTc -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$mark = if ($_.Name -eq $active) { ' *' } else { ' ' }
Write-Host "$mark $($_.Name)"
}
}
function Cmd-Show {
Ensure-Dirs
Write-Host "DCRUP_HOME=$DcrupHome"
Write-Host "DCRUP_BIN=$DcrupBin"
Write-Host "active=$(Read-Active)"
$exe = Join-Path $DcrupBin 'dcr.exe'
if (Test-Path $exe) { & $exe --version }
}
function Cmd-SelfInstall {
Ensure-Dirs
Copy-Item $PSCommandPath (Join-Path $DcrupBin 'dcrup.ps1') -Force
Install-CmdShim
Ensure-UserPath
Ok "dcrup installed to $DcrupBin (use: dcrup …)"
}
$cmd = if ($args.Count -ge 1) { $args[0] } else { 'help' }
$rest = @()
if ($args.Count -gt 1) { $rest = $args[1..($args.Count - 1)] }
switch ($cmd) {
{ $_ -in @('help', '-h', '--help') } { Show-Usage }
'install' { Cmd-Install $rest }
'list' { Cmd-List }
'show' { Cmd-Show }
'which' {
$p = Join-Path $DcrupBin 'dcr.exe'
if (Test-Path $p) { $p } else { Die 'dcr not installed' }
}
'default' {
if ($rest.Count -ne 1) { Die 'usage: dcrup default <spec>' }
Parse-Spec $rest[0]
if ($script:SpecFloating -or $script:SpecChannel -eq 'night') {
$id = Get-ChildItem $DcrupTc -Directory |
Where-Object { $_.Name -like "$($script:SpecChannel)-*" } |
Sort-Object Name | Select-Object -Last 1
if (-not $id) { Die "not installed: $($rest[0])" }
Link-Default $id.Name
} else {
Link-Default (Toolchain-Id)
}
}
'update' {
$a = Read-Active
if (-not $a) { Die 'no active toolchain' }
if ($a -like 'night-*') { Cmd-Install @('night', '--build', '--force') }
elseif ($a -like 'stable-*') { Cmd-Install @('stable', '--force') }
elseif ($a -like 'dev-*') { Cmd-Install @('dev', '--force') }
else { Ok "pinned $a — install a newer version explicitly" }
}
'uninstall' {
if ($rest -contains '--all') {
Remove-Item -Recurse -Force $DcrupTc -ErrorAction SilentlyContinue
Remove-Item (Join-Path $DcrupBin 'dcr.exe') -ErrorAction SilentlyContinue
Remove-Item (Active-Path) -ErrorAction SilentlyContinue
Ok 'removed all'
} else {
Die 'use: dcrup uninstall --all (or install a replacement first)'
}
}
'self-install' { Cmd-SelfInstall }
default { Die "unknown command: $cmd" }
}

668
dcrup.sh Executable file
View File

@@ -0,0 +1,668 @@
#!/usr/bin/env bash
set -Eeuo pipefail
DCRUP_HOME="${DCRUP_HOME:-$HOME/.dcr}"
DCRUP_BIN="${DCRUP_BIN:-$DCRUP_HOME/bin}"
DCRUP_TC="${DCRUP_HOME}/toolchains"
DCRUP_META="${DCRUP_HOME}/meta"
REPO_URL="${DCRUP_REPO:-https://github.com/dexoron/dcr}"
DCRUP_MIRROR="${DCRUP_MIRROR:-auto}"
API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
FEATURES="${DCR_FEATURES:-archive}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log() { printf "${CYAN}[dcrup]${NC} %s\n" "$*"; }
ok() { printf "${GREEN}[ok]${NC} %s\n" "$*"; }
warn() { printf "${YELLOW}[warn]${NC} %s\n" "$*"; }
die() { printf "${RED}[error]${NC} %s\n" "$*" >&2; exit 1; }
usage() {
cat <<'EOF'
dcrup — install and switch DCR versions
Usage:
dcrup install <spec> [--build|--release] [--force] [--libc gnu|musl|auto] [--mirror auto|cf|gh|sf|ya]
dcrup default <spec>
dcrup update
dcrup list
dcrup show
dcrup which
dcrup uninstall [<spec>|--all]
dcrup self-install [--to DIR]
dcrup help
Spec:
stable | dev | night
VERSION → VERSION@stable (e.g. 0.8.2)
VERSION@stable
VERSION@dev
night → always build from branch "dev" HEAD
Modes:
--release download prebuilt binary (default for stable/dev)
--build cargo build --release --features archive
night always --build (prebuilt not available)
Linux libc (prebuilt triple only):
--libc gnu x86_64-unknown-linux-gnu (default)
--libc musl x86_64-unknown-linux-musl
--libc auto musl if host ldd/libc is musl, else gnu
Mirrors (prebuilt download only):
--mirror auto try cf → gh → sf → ya, first successful download wins (default)
--mirror cf download from cdn-global.dcr-tool.ru
--mirror gh download from github.com/dexoron/dcr
--mirror sf download from sourceforge.net/projects/dcr-tool
--mirror ya download from cdn-ru.dcr-tool.ru
DCRUP_MIRROR env override (default: auto)
Env:
DCRUP_HOME default ~/.dcr
DCRUP_BIN default $DCRUP_HOME/bin
DCR_FEATURES default archive
DCRUP_REPO git/GitHub repo URL
DCRUP_LIBC gnu|musl|auto (default: gnu)
DCRUP_MIRROR auto|cf|gh|sf|ya (default: auto)
EOF
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
detect_linux_libc() {
local pref="${DCRUP_LIBC:-gnu}"
case "$pref" in
gnu|glibc) echo "gnu" ;;
musl) echo "musl" ;;
auto)
if command -v ldd >/dev/null 2>&1; then
if ldd --version 2>&1 | grep -qi musl; then
echo "musl"
return 0
fi
fi
if [[ -e /lib/ld-musl-x86_64.so.1 || -e /lib/ld-musl-aarch64.so.1 ]]; then
echo "musl"
return 0
fi
if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then
echo "gnu"
return 0
fi
echo "gnu"
;;
*) die "invalid DCRUP_LIBC / --libc: $pref (use gnu|musl|auto)" ;;
esac
}
detect_triple() {
local os arch libc
os="$(uname -s)"
arch="$(uname -m)"
case "$os:$arch" in
Linux:x86_64)
libc="$(detect_linux_libc)"
if [[ "$libc" == "musl" ]]; then
echo "x86_64-unknown-linux-musl"
else
echo "x86_64-unknown-linux-gnu"
fi
;;
Linux:aarch64|Linux:arm64)
libc="$(detect_linux_libc)"
if [[ "$libc" == "musl" ]]; then
echo "aarch64-unknown-linux-musl"
else
echo "aarch64-unknown-linux-gnu"
fi
;;
Linux:i686|Linux:i386)
libc="$(detect_linux_libc)"
if [[ "$libc" == "musl" ]]; then
echo "i686-unknown-linux-musl"
else
echo "i686-unknown-linux-gnu"
fi
;;
Linux:armv7l|Linux:armv7)
libc="$(detect_linux_libc)"
if [[ "$libc" == "musl" ]]; then
echo "armv7-unknown-linux-musleabihf"
else
echo "armv7-unknown-linux-gnueabihf"
fi
;;
Linux:riscv64)
libc="$(detect_linux_libc)"
if [[ "$libc" == "musl" ]]; then
echo "riscv64gc-unknown-linux-musl"
else
echo "riscv64gc-unknown-linux-gnu"
fi
;;
Darwin:x86_64) echo "x86_64-apple-darwin" ;;
Darwin:arm64|Darwin:aarch64) echo "aarch64-apple-darwin" ;;
FreeBSD:x86_64|FreeBSD:amd64) echo "x86_64-unknown-freebsd" ;;
FreeBSD:aarch64|FreeBSD:arm64) echo "aarch64-unknown-freebsd" ;;
OpenBSD:x86_64|OpenBSD:amd64) echo "x86_64-unknown-openbsd" ;;
NetBSD:x86_64|NetBSD:amd64) echo "x86_64-unknown-netbsd" ;;
MINGW*|MSYS*|CYGWIN*)
case "$arch" in
x86_64|amd64) echo "x86_64-pc-windows-gnu" ;;
aarch64|arm64) echo "aarch64-pc-windows-gnullvm" ;;
i686|i386) echo "i686-pc-windows-gnu" ;;
*) die "unsupported Windows arch under MSYS: $arch" ;;
esac
;;
*) die "unsupported platform: $os/$arch" ;;
esac
}
normalize_version() {
local v="$1"
v="${v#v}"
printf '%s' "$v"
}
parse_spec() {
local raw="$1"
SPEC_RAW="$raw"
SPEC_CHANNEL=""
SPEC_VERSION=""
SPEC_FLOATING=0
case "$raw" in
"") die "missing version/channel spec" ;;
stable|dev|night)
SPEC_CHANNEL="$raw"
SPEC_VERSION=""
SPEC_FLOATING=1
;;
*@*)
SPEC_VERSION="$(normalize_version "${raw%@*}")"
SPEC_CHANNEL="${raw#*@}"
case "$SPEC_CHANNEL" in
stable|dev) ;;
night) die "cannot pin a version on night (use: dcrup install night)" ;;
*) die "unknown channel in spec: $SPEC_CHANNEL (use stable|dev)" ;;
esac
[[ -n "$SPEC_VERSION" ]] || die "empty version in spec: $raw"
;;
*)
SPEC_VERSION="$(normalize_version "$raw")"
SPEC_CHANNEL="stable"
[[ -n "$SPEC_VERSION" ]] || die "invalid spec: $raw"
;;
esac
}
toolchain_id() {
if [[ "$SPEC_CHANNEL" == "night" ]]; then
local sha="${1:-unknown}"
echo "night-${sha}"
elif [[ "$SPEC_FLOATING" -eq 1 ]]; then
local ver="${1:-latest}"
echo "${SPEC_CHANNEL}-${ver}"
else
echo "${SPEC_VERSION}-${SPEC_CHANNEL}"
fi
}
ensure_dirs() {
mkdir -p "$DCRUP_TC" "$DCRUP_BIN" "$DCRUP_META"
}
active_file() { echo "$DCRUP_META/active"; }
read_active() {
if [[ -f "$(active_file)" ]]; then
cat "$(active_file)"
fi
}
write_active() {
printf '%s\n' "$1" >"$(active_file)"
}
link_default() {
local id="$1"
local src="$DCRUP_TC/$id/dcr"
[[ -x "$src" ]] || die "toolchain not installed: $id"
ln -sfn "$src" "$DCRUP_BIN/dcr"
write_active "$id"
ok "default → $id ($DCRUP_BIN/dcr)"
}
fetch_json() {
need_cmd curl
curl -fsSL "$1"
}
json_tag() {
printf '%s' "$1" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1
}
set_mirror() {
case "$1" in
auto|cf|gh|sf|ya) DCRUP_MIRROR="$1" ;;
*) die "invalid --mirror: $1 (use auto|cf|gh|sf|ya)" ;;
esac
}
mirror_url() {
local mirror="$1" tag="$2" asset="$3"
case "$mirror" in
cf) printf 'https://cdn-global.dcr-tool.ru/%s/%s' "$tag" "$asset" ;;
gh) printf 'https://github.com/dexoron/dcr/releases/download/%s/%s' "$tag" "$asset" ;;
sf) printf 'https://sourceforge.net/projects/dcr-tool/files/%s/%s/download' "$tag" "$asset" ;;
ya) printf 'https://cdn-ru.dcr-tool.ru/%s/%s' "$tag" "$asset" ;;
*) return 1 ;;
esac
}
mirror_chain() { echo "cf gh sf ya"; }
mirrors_to_try() {
case "$DCRUP_MIRROR" in
auto) mirror_chain ;;
cf|gh|sf|ya) echo "$DCRUP_MIRROR" ;;
*) die "invalid mirror: $DCRUP_MIRROR (use auto|cf|gh|sf|ya)" ;;
esac
}
download_from_mirrors() {
local tag="$1" asset="$2" dest="$3"
local tmp="$dest/dcr.download" m url size
USED_MIRROR=""
for m in $(mirrors_to_try); do
url="$(mirror_url "$m" "$tag" "$asset")"
log "trying mirror $m …"
rm -f "$tmp"
if curl -fL --retry 2 --retry-delay 1 --connect-timeout 10 --max-time 180 \
-A "dcrup" "$url" -o "$tmp"; then
size="$(wc -c <"$tmp" | tr -d ' ')"
if [[ "${size:-0}" -gt 100000 ]]; then
mv "$tmp" "$dest/dcr"
USED_MIRROR="$m"
return 0
fi
warn "mirror $m returned a too-small file (${size:-0} bytes)"
else
warn "mirror $m failed"
fi
rm -f "$tmp"
done
return 1
}
latest_stable_json() {
fetch_json "$API_LATEST"
}
latest_dev_json() {
need_cmd curl
local json result
json="$(fetch_json "$API_ALL")"
if command -v jq >/dev/null 2>&1; then
result="$(printf '%s' "$json" | jq -e -c '[.[] | select(.prerelease == true)][0]')" || true
elif command -v python3 >/dev/null 2>&1; then
result="$(printf '%s' "$json" | python3 -c '
import sys, json
rels = json.load(sys.stdin)
pre = [r for r in rels if r.get("prerelease")]
print(json.dumps(pre[0]) if pre else "", end="")
')"
else
die "dev channel needs jq or python3 to parse GitHub API"
fi
[[ -n "$result" && "$result" != "null" ]] || die "no prerelease (dev) found"
printf '%s' "$result"
}
release_json_for_spec() {
if [[ "$SPEC_FLOATING" -eq 1 ]]; then
case "$SPEC_CHANNEL" in
stable) latest_stable_json ;;
dev) latest_dev_json ;;
night) die "night has no GitHub release binary" ;;
esac
else
fetch_json "https://api.github.com/repos/dexoron/dcr/releases/tags/v${SPEC_VERSION}"
fi
}
install_prebuilt() {
local triple asset tag version id dest mirror
need_cmd curl
triple="$(detect_triple)"
json="$(release_json_for_spec)"
tag="$(json_tag "$json")"
[[ -n "$tag" ]] || die "failed to parse release tag"
version="$(normalize_version "$tag")"
asset="dcr-${triple}-${version}"
case "$triple" in
*windows*) asset="${asset}.exe" ;;
esac
id="$(toolchain_id "$version")"
dest="$DCRUP_TC/$id"
if [[ -x "$dest/dcr" && "${FORCE:-0}" != "1" ]]; then
ok "already installed: $id"
link_default "$id"
return 0
fi
mkdir -p "$dest"
log "downloading $asset (mirror=$DCRUP_MIRROR) …"
download_from_mirrors "$tag" "$asset" "$dest" \
|| die "no mirror reachable for $asset (try: --mirror cf|gh|sf|ya)"
mirror="$USED_MIRROR"
log "mirror → $mirror"
chmod +x "$dest/dcr"
printf '%s\n' \
"channel=$SPEC_CHANNEL" \
"version=$version" \
"tag=$tag" \
"source=release" \
"triple=$triple" \
"mirror=$mirror" \
"libc=${DCRUP_LIBC:-gnu}" \
>"$dest/dcrup-meta"
ok "installed prebuilt $id ($triple via $mirror)"
link_default "$id"
}
install_build() {
local tmp id dest sha version branch_or_tag
need_cmd git
need_cmd cargo
tmp="$(mktemp -d "${TMPDIR:-/tmp}/dcrup.XXXXXX")"
cleanup_tmp() { rm -rf "$tmp"; }
trap cleanup_tmp EXIT
if [[ "$SPEC_CHANNEL" == "night" ]]; then
log "cloning $REPO_URL (branch dev) for night …"
git clone --depth 1 --branch dev "$REPO_URL" "$tmp/src" 2>/dev/null \
|| git clone --depth 1 "$REPO_URL" "$tmp/src"
sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
version="night"
id="$(toolchain_id "$sha")"
elif [[ "$SPEC_FLOATING" -eq 1 ]]; then
local json tag
json="$(release_json_for_spec)"
tag="$(json_tag "$json")"
[[ -n "$tag" ]] || die "failed to resolve floating channel tag"
version="$(normalize_version "$tag")"
id="$(toolchain_id "$version")"
log "cloning $REPO_URL @ $tag …"
git clone --depth 1 --branch "$tag" "$REPO_URL" "$tmp/src" 2>/dev/null \
|| {
git clone --depth 1 "$REPO_URL" "$tmp/src"
git -C "$tmp/src" fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
git -C "$tmp/src" checkout "$tag"
}
sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
else
version="$SPEC_VERSION"
id="$(toolchain_id)"
local tag="v${SPEC_VERSION}"
log "cloning $REPO_URL @ $tag …"
git clone --depth 1 --branch "$tag" "$REPO_URL" "$tmp/src" 2>/dev/null \
|| {
git clone --depth 1 "$REPO_URL" "$tmp/src"
git -C "$tmp/src" fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
git -C "$tmp/src" checkout "$tag"
}
sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
fi
dest="$DCRUP_TC/$id"
if [[ -x "$dest/dcr" && "${FORCE:-0}" != "1" ]]; then
ok "already installed: $id"
link_default "$id"
return 0
fi
log "building release (features: $FEATURES) …"
(
cd "$tmp/src"
# shellcheck disable=SC2086
cargo build --release --features $FEATURES
)
mkdir -p "$dest"
cp "$tmp/src/target/release/dcr" "$dest/dcr"
chmod +x "$dest/dcr"
printf '%s\n' "channel=$SPEC_CHANNEL" "version=$version" "git=$sha" "source=build" "features=$FEATURES" >"$dest/dcrup-meta"
ok "installed build $id"
link_default "$id"
trap - EXIT
cleanup_tmp
}
cmd_install() {
local mode="release" FORCE=0
local spec=""
local libc_cli=""
while [[ $# -gt 0 ]]; do
case "$1" in
--build) mode="build"; shift ;;
--release) mode="release"; shift ;;
--force) FORCE=1; shift ;;
--libc)
[[ $# -ge 2 ]] || die "--libc needs gnu|musl|auto"
libc_cli="$2"
shift 2
;;
--libc=*)
libc_cli="${1#--libc=}"
shift
;;
--mirror)
[[ $# -ge 2 ]] || die "--mirror needs auto|cf|gh|sf|ya"
set_mirror "$2"
shift 2
;;
--mirror=*)
set_mirror "${1#--mirror=}"
shift
;;
-h|--help) usage; exit 0 ;;
-*) die "unknown flag: $1" ;;
*)
[[ -z "$spec" ]] || die "unexpected argument: $1"
spec="$1"
shift
;;
esac
done
[[ -n "$spec" ]] || die "usage: dcrup install <spec> [--build|--release] [--force] [--libc gnu|musl|auto] [--mirror auto|cf|gh|sf|ya]"
parse_spec "$spec"
ensure_dirs
if [[ -n "$libc_cli" ]]; then
case "$libc_cli" in
gnu|glibc|musl|auto) DCRUP_LIBC="$libc_cli" ;;
*) die "invalid --libc: $libc_cli (use gnu|musl|auto)" ;;
esac
fi
export DCRUP_LIBC="${DCRUP_LIBC:-gnu}"
if [[ "$SPEC_CHANNEL" == "night" ]]; then
mode="build"
fi
export FORCE
log "install spec=$SPEC_RAW channel=$SPEC_CHANNEL version=${SPEC_VERSION:-∅} mode=$mode libc=$DCRUP_LIBC mirror=$DCRUP_MIRROR"
if [[ "$mode" == "build" ]]; then
install_build
else
install_prebuilt
fi
check_path
}
cmd_default() {
[[ $# -eq 1 ]] || die "usage: dcrup default <spec>"
parse_spec "$1"
ensure_dirs
local id
if [[ "$SPEC_CHANNEL" == "night" || "$SPEC_FLOATING" -eq 1 ]]; then
id="$(ls -1 "$DCRUP_TC" 2>/dev/null | grep -E "^${SPEC_CHANNEL}-" | sort | tail -n1 || true)"
[[ -n "$id" ]] || die "no installed toolchain for channel $SPEC_CHANNEL (run: dcrup install $SPEC_CHANNEL)"
else
id="$(toolchain_id)"
[[ -d "$DCRUP_TC/$id" ]] || die "not installed: $id (run: dcrup install $1)"
fi
link_default "$id"
}
cmd_update() {
ensure_dirs
local active
active="$(read_active)"
[[ -n "$active" ]] || die "no active toolchain (run: dcrup install stable)"
case "$active" in
night-*)
log "updating night (rebuild HEAD) …"
FORCE=1 cmd_install night --build --force
;;
stable-*|dev-*)
local ch="${active%%-*}"
log "updating floating channel $ch …"
FORCE=1 cmd_install "$ch" --force
;;
*-stable|*-dev)
ok "pinned toolchain $active — not floating; install a newer version explicitly"
;;
*)
warn "unknown active id $active; try: dcrup install stable"
;;
esac
}
cmd_list() {
ensure_dirs
local active
active="$(read_active)"
if [[ ! -d "$DCRUP_TC" ]] || [[ -z "$(ls -A "$DCRUP_TC" 2>/dev/null || true)" ]]; then
warn "no toolchains installed"
return 0
fi
local d base
for d in "$DCRUP_TC"/*; do
[[ -d "$d" ]] || continue
base="$(basename "$d")"
if [[ "$base" == "$active" ]]; then
printf ' * %s\n' "$base"
else
printf ' %s\n' "$base"
fi
done
}
cmd_show() {
ensure_dirs
local active
active="$(read_active)"
echo "DCRUP_HOME=$DCRUP_HOME"
echo "DCRUP_BIN=$DCRUP_BIN"
echo "active=${active:-none}"
if [[ -n "$active" && -f "$DCRUP_TC/$active/dcrup-meta" ]]; then
echo "--- meta ---"
cat "$DCRUP_TC/$active/dcrup-meta"
fi
if [[ -x "$DCRUP_BIN/dcr" ]]; then
echo "--- dcr --version ---"
"$DCRUP_BIN/dcr" --version 2>/dev/null || true
fi
}
cmd_which() {
if [[ -L "$DCRUP_BIN/dcr" || -e "$DCRUP_BIN/dcr" ]]; then
readlink -f "$DCRUP_BIN/dcr" 2>/dev/null || readlink "$DCRUP_BIN/dcr" 2>/dev/null || echo "$DCRUP_BIN/dcr"
else
die "dcr not linked (run: dcrup install stable)"
fi
}
cmd_uninstall() {
ensure_dirs
if [[ "${1:-}" == "--all" ]]; then
rm -rf "$DCRUP_TC"
rm -f "$DCRUP_BIN/dcr" "$(active_file)"
ok "removed all toolchains"
return 0
fi
[[ $# -eq 1 ]] || die "usage: dcrup uninstall <spec>|--all"
parse_spec "$1"
local id
if [[ "$SPEC_FLOATING" -eq 1 || "$SPEC_CHANNEL" == "night" ]]; then
id="$(ls -1 "$DCRUP_TC" 2>/dev/null | grep -E "^${SPEC_CHANNEL}-" | sort | tail -n1 || true)"
else
id="$(toolchain_id)"
fi
[[ -n "$id" && -d "$DCRUP_TC/$id" ]] || die "not installed: $1"
rm -rf "$DCRUP_TC/$id"
if [[ "$(read_active)" == "$id" ]]; then
rm -f "$DCRUP_BIN/dcr" "$(active_file)"
warn "removed active toolchain; run: dcrup install stable"
fi
ok "removed $id"
}
check_path() {
case ":$PATH:" in
*":$DCRUP_BIN:"*) ;;
*)
warn "$DCRUP_BIN is not in PATH"
echo "Add to ~/.bashrc / ~/.zshrc / ~/.profile:"
echo " export PATH=\"$DCRUP_BIN:\$PATH\""
;;
esac
}
cmd_self_install() {
local dest="$DCRUP_BIN"
while [[ $# -gt 0 ]]; do
case "$1" in
--to) dest="$2"; shift 2 ;;
*) die "usage: dcrup self-install [--to DIR]" ;;
esac
done
ensure_dirs
mkdir -p "$dest"
local self
self="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
cp "$self" "$dest/dcrup"
chmod +x "$dest/dcrup"
ok "dcrup installed to $dest/dcrup"
check_path
echo "Then: dcrup install stable"
}
main() {
local cmd="${1:-}"
shift || true
case "$cmd" in
""|-h|--help|help) usage ;;
install) cmd_install "$@" ;;
default) cmd_default "$@" ;;
update) cmd_update "$@" ;;
list) cmd_list "$@" ;;
show) cmd_show "$@" ;;
which) cmd_which "$@" ;;
uninstall) cmd_uninstall "$@" ;;
self-install) cmd_self_install "$@" ;;
*) die "unknown command: $cmd (try: dcrup help)" ;;
esac
}
main "$@"

67
docs/README.mdx Normal file
View File

@@ -0,0 +1,67 @@
---
sidebar_label: Introduction
---
# DCR — C/C++ Build Tool & Package Manager
<details>
<summary>Quick reference</summary>
```bash
# Installation (dcrup)
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
export PATH="$HOME/.dcr/bin:$PATH"
dcrup install stable # auto: cf → gh → sf → ya
# dcrup install stable --mirror cf # CloudFlare / GitHub / SourceForge / Yandex
# Create a project
dcr new my-app
cd my-app
# Build and run
dcr build
dcr run
# Run tests
dcr test
# Add a dependency
dcr add fmt
# Generate IDE files
dcr gen vscode
```
</details>
## Contents
| Section | Description |
|---------|-------------|
| [Getting Started](/docs/getting-started/installation) | Installation and first steps |
| [Commands](/docs/commands/project-commands) | All CLI commands |
| [Reference](/docs/reference/dcr-toml) | Configuration, build, dependencies, platforms |
| [Testing](/docs/testing/test-framework) | Built-in test framework |
| [Recipes](/docs/recipes/cross-to-windows) | Common scenarios |
| [IDE Integration](/docs/ide-integration) | VS Code, CLion, compile_commands.json |
| [Self Update](/docs/self-update) | Auto-update |
| [FAQ](/docs/faq) | Frequently asked questions |
| [Changelog](/docs/changelog) | Version history |
| [License](/docs/license) | DCR and vendored library licenses |
| [Contributing](/docs/contributing) | DCR development |
## Features
- **Incremental builds** — mtime + `.d` headers + SHA256 fingerprint
- **Parallel compilation** — thread::scope, atomic task queue
- **7 backends** — gcc/clang (unix_cc), MSVC/clang-cl (msvc), GAS, NASM, MASM, FASM, LLVM IR
- **8 project kinds** — bin, staticlib, sharedlib, efi, elf, none, custom, flat-bin
- **Disk images** — optional `[archive]` packs FAT12/16/32 images after build
- **Cross-compilation** — short names and full triples
- **Profiles** — debug / release with field overrides
- **Workspaces** — multi-package projects with topological sort
- **Registry + Git + Path** — three dependency mechanisms
- **IDE generation** — VS Code, CLion, compile_commands.json, JSON metadata
- **pkg-config** — automatic system library discovery
- **Per-command `--help`** — `dcr build --help`, `dcr new --help`, etc.
- **Man pages** — `man dcr`, `man dcr-build`, `man dcr-new`

287
docs/changelog.mdx Normal file
View File

@@ -0,0 +1,287 @@
---
sidebar_label: Changelog
---
# Changelog
## 0.9.1 (2026-09-13)
- **`pkg-config` dependencies** — declare `fmt = { pkg-config = "fmt" }` or multiple package names in `[dependencies]`; DCR applies `.pc` compiler/linker flags and records the resolved package in `dcr.lock`.
- **`dcr add` support for `pkg-config`** — easily add system package dependencies with `dcr add fmt pkg-config:fmt` or `dcr add openssl pkg:openssl,libcrypto`.
- **`dcr tree` & IDE integration** — `pkg-config` packages are marked in `dcr tree` and resolved into compiler flags in `dcr gen` (`compile_commands.json`).
- **Fixed `dcr new` in non-ASCII / spaced paths** — initializing projects from folders with non-ASCII characters or spaces no longer fails validation.
## 0.9.0 (2026-08-31)
- **Download mirrors in `dcrup`** — `--mirror auto|cf|gh|sf|ya` and `DCRUP_MIRROR`.
Default `auto` tries **cf → gh → sf → ya** (CloudFlare CDN, GitHub, SourceForge, Yandex CDN).
- **Complete Path/Git dependencies** — DCR packages from `path` and `git` are built before their consumer. Git sources are cached in `~/.dcr/git`, support `branch`/`tag`/`rev`, and record the exact commit in `dcr.lock`.
- **Build and prebuild modes** — `path` and `git` dependencies can build a DCR package from `dcr.toml` or link existing `include`/`lib`/`libs` artifacts; `mode = "build"` / `"prebuild"` selects explicitly when both forms are present.
- **Prebuilt dependency examples** — documentation now shows the `dcr-math-lib` Git repository and recommended build/prebuilt layouts for both `git` and `path`.
- **Transitive dependency builds and cycle reporting** — DCR recursively builds DCR packages and reports a clear dependency chain for cycles.
- **Bare path arguments for `dcr add`** — `dcr add mylib ../mylib` now works in addition to the explicit `path:` form.
- **Removed `install.sh` / `install.ps1` / `install_bsd.sh`** — superseded by `dcrup`.
## 0.8.5 (2026-08-08)
- **`package.dcr-version`** — optional CLI version pin in `dcr.toml`.
`dcr new` / `dcr init` write the current version. On config load:
- missing / empty pin → warn "add dcr-version";
- dcr older than pin → warn "update dcr";
- dcr newer than pin → warn "check changelog, update pin";
- equal → silent.
Commands do not fail, warning only (once per path per process).
## 0.8.4 (2026-08-04)
**Added:**
- **`.dcr/` directory** — shared machine-readable build facts for IDE integrations:
- `.dcr/compile_commands.json`
- `.dcr/build-info.json` (`schemaVersion`, `artifact_path`, `profile`, …)
- `.dcr/toolchain.json` (compiler/debugger paths, moc/uic/rcc)
- `.dcr/ide/` (placeholder for IDE lock files)
- **`dcr gen project-info`** — `target`, `target_dir`, `artifact_path`, `artifact_kind`, `source_roots`, `include_globs`, `exclude_globs`; absolute/canonical paths.
- **`dcr build --print-artifact-path`** — one absolute artifact path line on stdout after a successful build.
- **`dcr gen … --quiet` / `-q`** — suppress human success logs for gen subcommands.
- **`.clangd`** — created on first gen with `CompilationDatabase: .dcr` (`# managed by dcr gen`); custom `.clangd` is not overwritten.
- **Helpers** `canonicalize_path`, `atomic_write`, `ensure_dcr_dir`; pure `resolve_artifact_path` via `platform::*`.
- **`dcr new` / `dcr init`** — add `.dcr/` to `.gitignore`.
**Changed (breaking):**
- **`compile_commands.json` moved** from project root to **`.dcr/compile_commands.json`**.
clangd discovers it via `.clangd` / `--compile-commands-dir=.dcr`.
CLion and VSCode generators updated accordingly.
- **Atomic write** for compile_commands (tmp + rename).
- **`gen vscode` / `gen clion` launch/run** — use the same artifact path resolver as `project-info` / `build-info.json`.
**Fixed:**
- Launch configs no longer hardcode `target/<profile>/<name>` without the Linux triple segment.
- Symlink/mount roots: best-effort canonicalize for root and compile database paths.
## 0.8.3 (2026-08-03)
### Added
- **`dcr run -- <args>...`** — cargo-style argument forwarding after a bare `--`
(`dcr run --release -- --test_help`). Applies to direct binary runs and `[run].cmd`.
- **Tests** — host target section `ldflags` without CLI `--target`; run-arg forwarding; `split_double_dash` unit tests.
### Changed
- **Native builds resolve the host triple** — without `--target` / package `build.target`, host sections such as
`[build.x86_64-unknown-linux-gnu]` apply again (`cflags` / `ldflags` / toolchain).
Previously the target was empty and those sections were ignored while artifacts already used the host triple path.
- **Empty target** — no more `Unknown target ''` warning.
- **`dcr run --help` / man `dcr-run`** — document `--` forwarding.
### Fixed
- **Native artifact paths on Windows and macOS** — without an explicit `--target` / `build.target`, artifacts use `target/<profile>`, so `dcr run` and flat-bin find the build output.
- Target-specific `ldflags` missing on native host builds (e.g. micro-lang + `libsct-elf`).
- Spurious empty-target warning.
## 0.8.2 (2026-07-22)
### Added
- **Compile progress `[N/M]`** — live TTY status line (`compile pkg v0.1.0 [12/41]`) so long mono-package builds do not look hung.
- **DCR status style** — fixed-width verbs: `project`, `compile`, `dep`, `ready`, `pack`, `done`, `run`.
- **Cargo feature `archive`** — optional FAT packing via `fatfs` (`cargo build --features archive`). Release CI builds with the feature; without it `[archive]` fails with a clear message.
- **Tests** — `flat_bin_nasm_build`, package `build.target` without CLI `--target`, stricter workspace `clean --all`.
- **CI expansion** — unit + integration on Linux/macOS/Windows, archive feature job, rust-cache, clippy default + all-features (see `.github/workflows/ci.yml`).
### Changed
- **No forced host triple from CLI** — without `--target`, package/member `build.target` is honored (bare-metal / ISO `post_steps` paths).
- **Relative include/lib flags** (`-I`, `-i`, `-isystem`, `-idirafter`, `-L`, `-T`) absolutized against the package root under workspace builds.
- **`dcr clean --all`** — no spam for members without a local `target/` (shared root `target/`).
- **`error` / `warn`** — red / yellow on stderr; CLI fully English (`Hint:`).
- **Help / man** — list `add`, `lint`, `setup`; unknown command exits with code 1.
- **Tool NotFound** — `linker not found: ld.lld …` / `{tool} not found …` instead of raw `os error 2`.
## 0.8.1 (2026-07-20)
### Added
- **`build.kind = "flat-bin"`** — raw binary (default `.bin`) for OS-dev payloads:
- **NASM** — `-f bin` → `<stem>.bin`
- **FASM** — direct output (`format binary` in source)
- **GAS / MASM / LLC** — object → `objcopy -O binary` → `<stem>.bin`
- **C/C++** — objects → freestanding link → `objcopy -O binary` → `<name>.bin`
- Requires `llvm-objcopy` / `objcopy` / `gobjcopy` except for NASM/FASM direct emit
- **`[archive]` section** — FAT12/16/32 disk images after build (`output`, `format`, `size`, `offset`, `label`, `bootsector`, `layout`). From 0.8.2 requires feature `archive`.
- **Single-file `build.roots`** — a root may be a single source or header file, not only a directory.
### Changed
- **`--force`** also re-runs `build.steps` and `build.post_steps`.
- **`dcr run` on workspace-only root** — if `[run].cmd` is set, run that after build; otherwise delegate to a workspace member.
## 0.8.0 (2026-07-18)
### Added
- **Dedicated Build Engine (`src/core/build/engine.rs`)** — decoupled the build orchestration logic completely from the CLI front-end wrapper in `src/cli/build.rs` to a reusable, decoupled core build engine.
- **Polymorphic Language Model (`Language` trait)** — introduced the abstract `Language` trait (`src/core/build/language/mod.rs`). File scanning, compiler resolution, and flags handling are now encapsulated in dedicated language modules for C, C++ (including Qt code generation), LLVM IR, and ASM (GAS, NASM, FASM, MASM).
- **Polymorphic Compilation Dispatch (`Builder` trait)** — introduced the `Builder` trait to generalize builder invocations (`src/core/build/builder/mod.rs`). Consolidated GCC/Clang logic into `builder/cc_common.rs` and Microsoft Visual C++ logic into `builder/msvc/`.
- **Per-Language Configuration overrides (`[build.<lang>]`)** — introduced support for configuring language-specific compiler, standard, and flags (e.g. `[build.c]`, `[build.cxx]`, `[build.asm]`) independently in `dcr.toml`.
- **Automatic Workspace Dependency Injection** — during workspace builds, include/lib paths of dependent workspace members (including their source headers, local `include/` folders, and build target `target/include` / `target/lib` folders) are automatically resolved and injected.
- **`BuildReporter` Event System** — decoupled build orchestration output into a structured event-driven reporter model (`src/core/build/report.rs`), making DCR suitable for library embedding and IDE integrations without stderr capturing.
- **Build Cancellation Support** — introduced thread-safe cancellation tokens (`Arc<AtomicBool>`) allowing clients to safely abort compile runs mid-execution.
- **TOML Formatting & Custom Keys Preservation** — migrated the config editor to `toml_edit`, preserving all unknown/user-defined TOML keys, structures, and comments during file write operations (e.g., in `dcr add`).
- **Path Dependencies Target Include Resolution** — local path dependencies configured via tables now correctly expose headers built and packaged into their `target/include` folder to consumer packages.
- **Modular Integration Test Suite** — refactored the monolith `tests/cli_basic.rs` file into clean, specialized integration tests: `cli_build.rs`, `cli_deps.rs`, `cli_lint.rs`, `cli_new.rs`, `cli_qt.rs`, `cli_test.rs`, `cli_workspace.rs`.
### Changed
- **Consolidated Linking and Archiving** — artifact generation (linking executables/shared libraries and archiving static libraries) consolidated into a single `src/core/build/builder/artifact.rs` module.
- **Workspace-Aware member execution** — `dcr run` executed at the workspace root of a `workspace_only` project now triggers the member build inside the workspace context using the `--workspace` parameter, preventing standalone build issues.
- **Elimination of Global State** — removed global variables and states from the build core, encapsulating build logic inside isolated `BuildContext` structs for thread safety.
- **Build Cache Separation** — relocated mtime tracking, incremental caching, and header-dependency checking logic to `src/core/build/cache.rs`.
- **Modularized Pre-build Steps** — decoupled generator scripts and pre-build commands parsing into `src/core/build/steps.rs`.
## 0.7.4 (2026-06-17)
### Added
- **Native Qt support** — automatic meta-object handling (MOC, UIC, RCC) added when `build.qt = true` is set in `dcr.toml`.
- **`dcr lint` command** — C/C++ static analysis via `clang-tidy`. Supports `--fix` for automatic fixes. Respects `build.roots` and `build.src_disable` from config.
- **Three new assembler backends**:
- **MASM** (`compiler = "ml"` / `"ml64"`) — Microsoft Macro Assembler.
- **FASM** (`compiler = "fasm"`) — Flat Assembler.
- **LLVM IR** (`compiler = "llc"`) — compiles `.ll` files via `llc -filetype=obj`.
- **Shared ASM pipeline** — linking and archiving logic consolidated into `core/builder/asm.rs`. Adding a new assembler backend now takes ~30 lines.
- **Filesystem utilities** — `to_hex()` and `home_dir()` extracted to `utils/fs.rs`.
- **Build utilities** — `normalize_target()`, `normalize_kind()`, `normalize_platform()`, `default_profile_flags()` extracted to `utils/build.rs`.
- **Backend helpers** — `asm_lang_flag()`, `source_extensions()`, `elapsed_secs()` added to `core/builder/common.rs`.
- **Full Bare-Metal / Freestanding Automation** — introduced the `build.freestanding = true` configuration option. When enabled (or when a bare-metal target is detected), DCR automatically injects `-ffreestanding` during compilation and both `-nostdlib` and `-static` during linking.
- **Artifact Optimization (LTO & Strip)** — added `build.lto` (auto-injects `-flto` for compiler and linker) and `build.strip` (automatically strips debug symbols via linker `-s` flag) options to `dcr.toml`.
- **Compilation Thread Control** — added the `build.codegen-units` option to strictly limit the maximum number of parallel jobs utilized by the custom `parallel_build` worker pool.
- **Panic Behavior Management** — added `build.panic = "abort"` support. For C++ targets, it automatically strips exception handling and unwind tables via `-fno-exceptions`, `-fno-unwind-tables`, and `-fno-asynchronous-unwind-tables`.
### Changed
- **Intelligent Default Flags Generation** — automated injection of fallback optimization (`-O3`/`-O0`), debug (`-g`), and warning (`-Wall -Wextra`) flags is now suppressed for bare-metal and freestanding builds if `build.cflags` are overridden.
- **Centralized Bare-Metal Detection** — relocated the `is_bare_metal_target` helper to common build utilities (`src/utils/build.rs`) for unified access across the building core.
- **Architectural Refactoring**:
- Renamed `src/config.rs` to `src/templates.rs`.
- Started consolidation of configuration and build orchestration logic.
## 0.7.3 (2026-06-13)
### Added
- **`--vcs` option for `new` and `init` commands** — introduced explicit version control system selection via `--vcs <git|none>`.
- **Git metadata integration in `--version`** — the CLI now appends the current short commit hash and a `-dirty` suffix if there are uncommitted changes in the DCR repository.
- **Automatic `.gitignore` generation** — Git repository initialization now automatically writes a `.gitignore` file excluding the `/target` directory.
- **Nested repository prevention** — automatic Git initialization is now skipped if `dcr new` or `dcr init` is executed inside an already existing Git repository.
- **VCS verification tests** — added `new_vcs_options_work` and `init_vcs_options_work` integration tests to ensure reliable repository behavior.
### Changed
- **Complete removal of `git2` dependency** — all Git actions (dependency fetching, VCS setup, status checks) are now delegated to the system `git` executable via `Command`. This simplifies compilation and drops the need for `vendored` and `openssl` features.
- **Migrated from `reqwest` to `ureq`** — replaced the heavy `reqwest` crate in `flag_update.rs` with the lightweight synchronous `ureq 2.10` client, reducing overhead and the final binary size.
## 0.7.2 (2026-06-07)
### Added
- `default_target_triple()` now respects `target_env` — Linux (gnu/musl) and Windows (msvc/gnu) use the correct environment instead of hardcoded values.
### Fixed
- **macOS Apple Silicon builds failed with `_main` undefined** — `--target=` is now passed to ldflags, not just cflags. `default_target_triple()` uses `std::env::consts::ARCH` on macOS instead of hardcoded `x86_64`.
- **GitHub Stars and GPL-3.0 badges not rendering in README** — badge block converted to pure HTML.
### Changed
- Default target resolution extracted into shared `default_target_triple()` — removed duplication across build, run, clean.
## 0.7.1 (2026-06-02)
### Added
- **`--help` for all commands** — `dcr build --help`, `dcr run --help`, `dcr new --help`,
`dcr init --help`, `dcr clean --help`, `dcr add --help`, `dcr fmt --help`,
`dcr setup --help`, `dcr tree --help`, `dcr gen --help`, `dcr --update --help`,
`dcr test --help`. All output uses styled headers (green) and usage lines (cyan).
- **Man pages** — 12 troff pages in `man/man1/`: `dcr.1`, `dcr-build.1`,
`dcr-run.1`, `dcr-new.1`, `dcr-init.1`, `dcr-clean.1`, `dcr-add.1`, `dcr-test.1`,
`dcr-gen.1`, `dcr-fmt.1`, `dcr-tree.1`, `dcr-setup.1`.
- **Man pages in all packaging** — install.sh, install_bsd.sh, AUR PKGBUILD,
Debian (cargo-deb), RPM (cargo-generate-rpm), Nix (postInstall), Homebrew
(resource), Snap, GitHub Release assets.
- **Project name validation** — `dcr init` and `dcr new` now validate names before
creating files. Only ASCII letters, digits, `_` and `-` are allowed.
- **`documentation` and `homepage` fields** — added to `Cargo.toml`.
- **Release profile optimizations** — `opt-level = "z"`, LTO, `codegen-units = 1`,
`panic = "abort"`, `strip = true` for smaller binaries.
- **Linux i686, armv7, riscv64 targets** — added to release workflow and install scripts.
- **Linux musl i686, armv7 targets** — cross-compiled via zigbuild in CI.
- **AUR packages** — `dcr-dev`, `dcr-dev-bin` publishing workflow.
- **Snap publishing** — automated Snapcraft publishing in CI.
- **Snap badge** — in README.
### Changed
- **`validate_package_name` made public** — callable from CLI commands.
- **README platform table** — reorganized: libc variants as separate OS rows,
removed extra columns. Linux architecture list updated.
- **`gen.rs` no-args output** — now uses styled output instead of raw `eprintln!`.
- **`tree.rs`, `fmt.rs`, and `setup.rs`** — now accept arguments for `--help`.
- **README install commands** — `| bash` → `| sh` for POSIX compatibility.
### Fixed
- **`dcr new <invalid-name>` creates directory then fails** — validation now
happens before any file operations.
- **`dcr init` on invalid directory name** — same fix.
- **Man pages missing in installed packages** — now shipped in all formats.
- **Windows drive letter false positive** — in dependency file parser.
- **CRLF (`\r\n`) breaks dependency parser** — now handles mixed line endings.
- **Newline after backslash not consumed** — in `parse_d_file` escape handling.
## 0.7.0 (2026-06-01)
### Added
- **OpenBSD and NetBSD target support** — full platform routing with dynamic target triples using `std::env::consts::ARCH` and `std::env::consts::OS`. Affects `build`, `run`, `clean` commands and the platform module.
- **`src/platform/bsd.rs`** — new BSD platform module (shared by FreeBSD, OpenBSD, NetBSD) providing `bin_path`, `lib_path`, `elf_path`, `efi_path`, `shared_lib_path`.
- **`build.out_dir` config option** — custom output directory that overrides the default `target/<triple>/<profile>` path for final artifacts. Supported in `build`, `run`, and config validation.
- **`dcr fmt` command** — new CLI command that formats all C/C++ source files (`*.c`, `*.cpp`, `*.h`, `*.hpp`) in `src/` and `tests/` using `clang-format`.
- **Incremental linking** — `needs_link()` in `common.rs` checks if any object file is newer than the linked output, skipping unnecessary relinking. Implemented for all backends (`unix_cc`, `msvc`, `gas`, `nasm`).
- **`build.kind = "none"` and `"custom"`** — two new project kinds for special build scenarios that don't produce standard artifacts.
- **`install_bsd.sh`** — POSIX-compliant installation script for BSD systems (FreeBSD, OpenBSD, NetBSD) with binary download and source build modes.
- **Linux ARM64 support in `install.sh`** — added `Linux:aarch64|Linux:arm64` target detection for pre-built binary downloads.
- **BSD OS detection in `install.sh`** — detects FreeBSD, OpenBSD, NetBSD and determines target triple.
- **`rust-toolchain.toml`** — explicit toolchain pinning to `stable` channel.
- **Integration tests** — `build_with_target_config` (verifies `build.target = "linux"`) and `build_with_out_dir` (validates custom output directory).
- **`get_build_string_with_profile` made `pub`** — so `run.rs` can determine custom output directory configuration.
### Changed
- **`build.target` semantics changed** — now strictly contains a target triple (e.g., `x86_64-unknown-linux-gnu`) or short name (`linux`, `macos`, `windows`). No longer used as custom output directory — this functionality moved to `build.out_dir`.
- **`build.standard` made optional** — changed from `String` to `Option<String>`. Validation only enforces non-empty for non-ASM languages. Skipped in `dcr.toml` output if empty.
- **`dcr run` with `out_dir`** — now determines target directory respecting `build.out_dir` via `get_build_string_with_profile()` from `build.rs`.
- **`collect_sources()` returns empty vector** instead of error when no sources are found, allowing `kind = "none"` or `kind = "custom"` projects with no source files.
- **CI/CD release workflow refactored** — `git2` made target-specific (no vendored-openssl on Windows), Zig-based cross-compilation for non-x86_64 Linux targets, Arch Linux package version cleanup (dashes → dots), `gmake` symlink for NetBSD.
- **README compatibility table** — FreeBSD, OpenBSD, NetBSD build/run status upgraded from "community/best-effort" to "officially supported".
- **Documentation updated** — `build-section.md` describes new `build.target` and `build.out_dir` fields. `target-directory.md` rewritten to clarify the distinction.
### Fixed
- **stdout/stderr not inherited in `dcr run`** — child process output was captured and manually printed, breaking interactive programs. Fixed by switching to `Command::status()`.
- **Race condition in release CI** — matrix build jobs could upload assets to a release that didn't exist yet. Fixed by adding a dedicated `create-release` job.
- **Release GHA — stable toolchain override** — fixed Rust toolchain override issues in CI.
- **Arch Linux package version sanitization** — version strings with dashes (e.g., `0.7.0-dev`) are invalid for `pkgver`. Fixed by replacing dashes with dots.
- **GPG permissions after Docker** — Docker operations changed GPG directory ownership. Fixed by running `chown` after Docker commands.
- **RPM artifact paths** — RPM artifacts were placed in `rpm/x86_64/` instead of `fedora/x86_64/`. Fixed in Dexoron Packages Index workflow.
- **JSON parsing reliability in `install.sh`** — added `jq` as primary parser with `python3` fallback for dev channel release lookup.
### Removed
- **`format_roots()` helper function** — removed from `common.rs`. Was only used by the old error handling path in `collect_sources()`.
- **Per-distribution artifact download steps** — three separate `actions/download-artifact` steps replaced with unified `gh release download --clobber`.

View File

@@ -0,0 +1,47 @@
---
sidebar_label: Build commands (build, run, clean)
---
# Build Commands
## `dcr build`
Builds the project according to `dcr.toml`.
```bash
dcr build # debug profile
dcr build --release # release build
dcr build --debug # explicit debug
dcr build --target aarch64-unknown-linux-gnu
dcr build --force # full rebuild (also re-runs build.steps / post_steps)
dcr build --clean # clean + build
dcr build --verbose # show compilation commands
dcr build --workspace pkg-a # build specific workspace package
```
Artifacts: `target/<triple>/<profile>/<binary-name>` (Linux with explicit target).
With `[archive]` in `dcr.toml`, a FAT disk image is packed after a successful build.
## `dcr run`
Builds (if sources are newer than artifacts) and runs the binary.
```bash
dcr run
dcr run --release
dcr run --force # force rebuild then run
```
On a **workspace-only** root: if `[run].cmd` is set, that command is used after build; otherwise DCR delegates to a workspace member.
## `dcr clean`
Removes `target/` directory (or `target/<target>/<profile>`).
```bash
dcr clean
dcr clean --release # target/release/ only
dcr clean --target windows # target/x86_64-pc-windows-msvc/
dcr clean --all # clean all workspace packages
```

View File

@@ -0,0 +1,59 @@
---
sidebar_label: Dependency commands (add, tree)
---
# Dependency Commands
## `dcr add <name> [source]`
Adds a dependency to `dcr.toml`.
```bash
dcr add fmt # registry lookup
dcr add mylib ../path/to/lib # path as source
dcr add mylib path:./lib # explicit path prefix
dcr add mylib git:https://github.com/user/repo # explicit git prefix
dcr add mylib github:user/repo # GitHub shorthand
dcr add mylib gitlab:user/repo # GitLab shorthand
dcr add fmt pkg-config:fmt # system package via pkg-config
dcr add openssl pkg:openssl,libcrypto # multiple pkg-config packages
```
Source prefixes:
- `path:` — local path
- `git:` — generic git URL
- `github:` — expands to `https://github.com/<user>/<repo>`
- `gitlab:` — expands to `https://gitlab.com/<user>/<repo>`
- `pkg-config:` / `pkg:` — one or more packages resolved by `pkg-config`
- `http://` / `https://` / `git@` — full URL
Flags:
- `--branch <name>` — git branch
- `--tag <name>` — git tag
- `--rev <hash>` — git commit
If source is omitted — DCR searches connected registries. A bare relative or absolute path is accepted as a path dependency.
For `pkg-config` dependencies, the referenced packages must already be installed
and provide `.pc` files. DCR invokes `pkg-config` during the build (using
`PATH` and `PKG_CONFIG_PATH`) and forwards the returned compiler and linker
flags; it does not download or build these packages.
## `dcr tree`
Displays the project dependency tree.
```bash
dcr tree
```
Example output:
```
my-app v0.1.0
├── fmt (registry)
│ └── spdlog (registry)
└── catch2 (registry)
```
For path dependencies, recursively shows their own dependencies (from their `dcr.toml`).

View File

@@ -0,0 +1,71 @@
---
sidebar_label: Gen commands (vscode, clion, ...)
---
# Gen Commands
## `dcr gen <target>`
Generates IDE integration files.
### vscode
```bash
dcr gen vscode
```
Creates:
- `.vscode/tasks.json` — `build` task (dcr build)
- `.vscode/launch.json` — debug launch configuration
- `.vscode/settings.json` — C/C++ settings (clangd paths, includes)
- `.vscode/extensions.json` — recommended extensions (vscode-clangd, vscode-lldb)
### clion
```bash
dcr gen clion
```
Creates:
- `.idea/externalTools.xml` — external tools (build, run, clean, test)
- `.idea/customTargets.xml` — custom build targets
- `.idea/misc.xml` — C/C++ project settings
- `.idea/runConfigurations/<name>.xml` — per-binary run configurations
### compile-commands
```bash
dcr gen compile-commands
```
Generates `compile_commands.json` — standard format for clangd, cpptools, static analyzers.
### project-info
```bash
dcr gen project-info
```
Outputs JSON array with project metadata:
```json
[
{
"name": "my-app",
"version": "0.1.0",
"root": "/path/to/project",
"profile": "debug",
"language": "c",
"standard": "c17",
"cxx_standard": null,
"compiler": "/usr/bin/clang",
"kind": "bin",
"sources": ["src/main.c"],
"include_dirs": ["src"],
"lib_dirs": [],
"libs": [],
"cflags": ["-std=c17", "-O0", "-g"],
"ldflags": []
}
]
```

View File

@@ -0,0 +1,37 @@
---
sidebar_label: Project commands (new, init)
---
# Project Commands
## `dcr new <name>`
Creates a new project with a standard structure and `dcr.toml`. The `name` argument is required.
Only ASCII letters, digits, underscores `_` and hyphens `-` are allowed in the project name.
```bash
dcr new my-app
dcr new my-app --vcs none
```
Flags:
- `--vcs <git|none>` — Initialize version control system (defaults to `git` if available, or `none`).
Generates:
- `dcr.toml` with basic fields
- `src/main.c` with a `main` template
## `dcr init`
Initializes a DCR project in the current (empty) directory.
```bash
dcr init
dcr init --vcs none
```
Flags:
- `--vcs <git|none>` — Initialize version control system (defaults to `git` if available, or `none`).
The project name is taken from the current directory name. The directory name must follow the same naming rules as `dcr new`.

View File

@@ -0,0 +1,59 @@
---
sidebar_label: Quality commands (test, fmt)
---
# Quality Commands
## `dcr test`
Runs tests.
```bash
dcr test # debug profile
dcr test --release # release profile
dcr test --debug # explicit debug
dcr test --help # detailed help
```
Before first use, initialize tests:
```bash
dcr test --init
```
This creates `tests/dcr_test.h` (framework) and `tests/test.c` (template).
What `dcr test` does:
1. Builds the project
2. Collects `tests/*.c` files (`.c` only, not `.cpp`)
3. Compiles and links each test file
4. Runs each test binary
5. Prints summary: TOTAL, PASS, SKIP, FAIL
6. Returns non-zero exit code on any FAIL
## `dcr fmt`
Formats C/C++ source files using `clang-format`.
```bash
dcr fmt
```
Processes: `src/**/*.{c,cpp,cxx,cc,h,hpp,hxx,hh}` and `tests/**/*.{c,cpp,cxx,cc,h,hpp,hxx,hh}`.
Uses `.clang-format` at the project root (if present), otherwise default clang-format style.
## `dcr lint`
Runs `clang-tidy` on C/C++ source files for static analysis.
```bash
dcr lint # show diagnostics
dcr lint --fix # apply fixes automatically
dcr lint --help # detailed help
```
Processes: `src/**/*.{c,cpp,cxx,cc}` and `tests/**/*.{c,cpp,cxx,cc}`.
Without `--fix`, clang-tidy reports warnings and errors without modifying files.
With `--fix`, clang-tidy applies automatic suggestions in place.

View File

@@ -0,0 +1,50 @@
---
sidebar_label: System commands (setup, --help, ...)
---
# System Commands
## `dcr setup`
Shows configured registries from `~/.dcr/config.toml`.
```bash
dcr setup
```
If `~/.dcr/config.toml` is not found, DCR returns an error. Create it manually:
```toml
[registry.default]
url = "https://index.dcr.pm"
priority = 1
```
## `dcr --help`
Shows help for all commands.
```bash
dcr --help
```
## `dcr --version`
Shows version and target triple.
```bash
dcr --version # dcr 0.7.0 (x86_64-unknown-linux-gnu)
```
## `dcr --update`
Self-update. Downloads the latest release binary from GitHub Releases and replaces the current one.
```bash
dcr --update
```
Features:
- Auto-detects platform and architecture
- Warns if installed via AUR (use package manager instead)
- Works on Linux, macOS, Windows

52
docs/contributing.mdx Normal file
View File

@@ -0,0 +1,52 @@
---
sidebar_label: Contributing
---
# Contributing
## Setup
```bash
git clone https://github.com/dexoron/dcr
# or a source mirror: GitVerse / SourceCraft / SourceForge / GitLab (see README)
cd dcr
cargo build
```
## Code style
All code must be formatted with `cargo fmt`:
```bash
cargo fmt
```
## Linting
```bash
cargo clippy --all-targets -- -D warnings
```
## Tests
```bash
cargo test --all-targets
```
## PR process
1. Fork the repository
2. Create a branch: `git checkout -b feature/description`
3. Make changes
4. Run `cargo fmt && cargo clippy && cargo test`
5. Open a Pull Request
## CI
CI runs (see `.github/workflows/ci.yml`):
- `cargo fmt --check`
- `cargo clippy` (default + `--all-features`)
- `cargo check --all-targets --all-features`
- Unit + integration tests on Linux, macOS, Windows
- Extra Linux job with `--features archive` (FAT images)
- Tools on runners: clang, nasm, clang-format/tidy (Linux)

75
docs/faq.mdx Normal file
View File

@@ -0,0 +1,75 @@
---
sidebar_label: FAQ
---
# FAQ
## Registry not found
```
error: registry not found
```
**Solution:** Make sure `~/.dcr/config.toml` exists:
```toml
[registry.default]
url = "https://index.dcr.pm"
priority = 1
```
And check `DCR_INDEX_PATH`:
```bash
echo $DCR_INDEX_PATH
```
## Compiler not found
```
error: compiler not found
```
**Solution:** Ensure a compiler is installed and available in PATH.
```bash
which gcc
which clang
```
Or specify explicitly via `[toolchain]` in `dcr.toml`.
## Ctrl+C during build
DCR handles SIGINT: interrupts the current compilation and exits with non-zero code.
## How to create a library?
See [Library Recipe](/docs/recipes/library-project).
## How to cross-compile to Windows?
See [Cross-Compile Recipe](/docs/recipes/cross-to-windows).
## How to create a multi-package project?
See [Workspace Recipe](/docs/recipes/multi-package-workspace).
## Bootloader / pure NASM OS image
Use `kind = "flat-bin"` (NASM `-f bin`) and optional `[archive]` for a FAT disk image.
See [OS-dev recipe](/docs/recipes/os-flat-bin-archive).
## How do I choose a download mirror?
`dcrup install` uses `--mirror auto` by default and tries **cf → gh → sf → ya**.
```bash
dcrup install stable --mirror cf
export DCRUP_MIRROR=ya
```
## Lock file
`dcr.lock` is created during builds (`dcr build`) when registry dependencies are present. To force an update — delete `dcr.lock` and run `dcr build`.

View File

@@ -0,0 +1,78 @@
---
sidebar_label: First steps
---
# First Steps
## Create a project
```bash
dcr new my-app
cd my-app
```
Structure:
```
my-app/
├── dcr.toml
└── src/
└── main.c
```
`dcr.toml`:
```toml
[package]
name = "my-app"
version = "0.1.0"
type = "none"
[build]
language = "c"
standard = "c11"
compiler = "clang"
kind = "bin"
```
## Build
```bash
dcr build
```
Output — `target/<triple>/debug/my-app` (Linux) or `target/debug/my-app.exe` (Windows).
Build profiles:
```bash
dcr build --release # release build
dcr build --debug # debug (default)
```
## Run
```bash
dcr run
```
Builds (if needed) and runs the binary.
## First test
```bash
dcr test --init # create test template
dcr test # run tests
```
## Formatting
```bash
dcr fmt # clang-format with .clang-format
```
## Linting
```bash
dcr lint # clang-tidy checks
dcr lint --fix # apply fixes automatically
```

View File

@@ -0,0 +1,153 @@
---
sidebar_label: Installation
---
# Installation
## dcrup (recommended)
Install and switch DCR versions (stable / dev / night, pin `0.8.2`, optional build from source).
### Linux / macOS / BSD / Windows (bash)
```sh
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
export PATH="$HOME/.dcr/bin:$PATH"
dcrup install stable
```
Non-interactive install of DCR in one shot:
```sh
curl -fsSL https://sh.dcr-tool.ru | sh -s -- install stable
export PATH="$HOME/.dcr/bin:$PATH"
```
### Windows (PowerShell)
```powershell
irm https://ps1.dcr-tool.ru | iex
# or download then run:
# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
# powershell -File .\dcrup.ps1 self-install
# $env:Path += ";$env:USERPROFILE\.dcr\bin"
dcrup install stable
```
Optional cmd bootstrap (if you prefer `curl` of the cmd shim):
```bat
curl -fsSL -o dcrup.cmd https://cmd.dcr-tool.ru
```
After `self-install`, the command is **`dcrup`** (no `.sh` / `.ps1`): shims live in `~/.dcr/bin` (Unix) or `%USERPROFILE%\.dcr\bin` (Windows `dcrup.cmd`).
### Common dcrup commands
```sh
dcrup install stable # latest stable prebuilt (auto: cf → gh → sf → ya)
dcrup install 0.8.2 # pin → 0.8.2@stable
dcrup install 0.8.2@dev
dcrup install stable --libc musl # Linux: musl asset (default: gnu)
dcrup install stable --mirror cf # CloudFlare CDN
dcrup install stable --mirror ya # Yandex CDN (RU fallback)
dcrup install stable --build # cargo build --features archive
dcrup install night # always build from branch dev HEAD
dcrup default 0.8.2
dcrup update
dcrup list
dcrup show
dcrup which
```
Layout: `~/.dcr/toolchains/<id>/dcr` and `~/.dcr/bin/dcr` → active version.
### Download mirrors
Prebuilt installs try **cf → gh → sf → ya** unless you pin a host.
| Code | URL | Notes |
|------|-----|--------|
| `cf` | `https://cdn-global.dcr-tool.ru/<tag>/<asset>` | CloudFlare CDN (worldwide) |
| `gh` | `https://github.com/dexoron/dcr/releases/download/<tag>/<asset>` | GitHub Releases |
| `sf` | `https://sourceforge.net/projects/dcr-tool/files/<tag>/<asset>/download` | SourceForge |
| `ya` | `https://cdn-ru.dcr-tool.ru/<tag>/<asset>` | Yandex CDN (RU fallback) |
```sh
dcrup install stable --mirror auto # default
export DCRUP_MIRROR=cf # persist for this shell
```
---
## Arch Linux (AUR)
```sh
yay -S dcr
```
## macOS / Linux (Homebrew)
```sh
brew tap dexoron/dexoron
brew install dcr
```
## Snap (Linux)
```sh
sudo snap install dcrup
```
> If classic Snap Store publishing is unavailable, install the `.snap` from [GitHub Releases](https://github.com/dexoron/dcr/releases/latest) with `--dangerous`.
## Nix (flake)
```sh
nix run github:dexoron/dcr
nix profile install github:dexoron/dcr
```
## Cargo (crates.io)
```sh
cargo install dcr
```
Note: crates.io builds may omit optional features. For FAT disk images (`[archive]`), prefer release binaries or:
```sh
cargo install dcr --features archive
```
## From source
```sh
git clone https://github.com/dexoron/dcr.git
cd dcr
cargo build --release --features archive
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
# or manage versions with dcrup install night / --build
```
## Post-install
```bash
dcr --version
dcrup show # if installed via dcrup
```
Man pages (package installs / release assets):
```bash
man dcr
man dcr-build
```
Registry (optional) — `~/.dcr/config.toml`:
```toml
[registry.default]
url = "https://index.dcr.pm"
priority = 1
```

58
docs/ide-integration.mdx Normal file
View File

@@ -0,0 +1,58 @@
---
sidebar_label: IDE Integration
---
# IDE Integration
## VS Code
Install the recommended [DCR extension by Dexoron-Labs](https://marketplace.visualstudio.com/items?itemName=Dexoron-Labs.dcr). Do not install Microsoft C/C++ extensions for DCR projects: DCR uses `clangd`. MS C/C++ is acceptable only when you need its debugger.
Recommended companion extensions:
- [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml)
- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), or MS C/C++ for debugger support only
```bash
dcr gen vscode
```
Generates in `.vscode/`:
| File | Purpose |
|------|---------|
| `tasks.json` | `build` task (dcr build) |
| `launch.json` | Debug launch configuration |
| `settings.json` | clangd/IntelliSense: include paths, standard |
| `extensions.json` | Recommends vscode-clangd, vscode-lldb |
## CLion
```bash
dcr gen clion
```
Generates in `.idea/`:
| File | Purpose |
|------|---------|
| `externalTools.xml` | Build, Run, Clean, Test as external tools |
| `customTargets.xml` | Custom build targets |
| `misc.xml` | C/C++ project settings |
| `runConfigurations/<name>.xml` | Per-binary run configurations |
## compile_commands.json
```bash
dcr gen compile-commands
```
Generates `compile_commands.json` at project root. Standard format for clangd, C/C++ IntelliSense, static analyzers.
## project-info
```bash
dcr gen project-info
```
Outputs JSON array with project metadata (see [gen-commands](/docs/commands/gen-commands)).

33
docs/license.mdx Normal file
View File

@@ -0,0 +1,33 @@
---
sidebar_label: License
---
# License
## DCR
DCR itself is [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html) licensed.
> **Note:** DCR is a build tool, not a library. The GPL applies only to DCR's own source code. Projects built with DCR are not subject to DCR's license — their licensing is determined solely by their own code and dependencies.
## Rust dependencies
DCR is written in Rust. The following notable Rust crates are statically linked:
| Crate | License |
|---|---|
| [`ureq`](https://crates.io/crates/ureq) | MIT OR Apache-2.0 |
| [`serde`](https://crates.io/crates/serde) | MIT OR Apache-2.0 |
| [`toml`](https://crates.io/crates/toml) | MIT OR Apache-2.0 |
| [`toml_edit`](https://crates.io/crates/toml_edit) | MIT OR Apache-2.0 |
| [`serde_json`](https://crates.io/crates/serde_json) | MIT OR Apache-2.0 |
| [`sha2`](https://crates.io/crates/sha2) | MIT OR Apache-2.0 |
| [`glob`](https://crates.io/crates/glob) | MIT OR Apache-2.0 |
| [`self-replace`](https://crates.io/crates/self-replace) | MIT OR Apache-2.0 |
| [`ctrlc`](https://crates.io/crates/ctrlc) | MIT OR Apache-2.0 |
Full dependency tree is available in [`Cargo.lock`](https://github.com/dexoron/dcr/blob/main/Cargo.lock). The overwhelming majority are dual-licensed under MIT OR Apache-2.0.
## Additional credits
- DCR's CLI design and project model are inspired by [Cargo](https://doc.rust-lang.org/cargo/).

View File

@@ -0,0 +1,49 @@
---
sidebar_label: Cross-compile to Windows
---
# Cross-Compile to Windows from Linux
Building a Windows binary on Linux using mingw-w64.
## Install toolchain
```bash
# Ubuntu/Debian
sudo apt install mingw-w64
# Fedora
sudo dnf install mingw64-gcc mingw64-binutils
```
## Configuration
```toml
[package]
name = "my-app"
version = "0.1.0"
type = "none"
[build]
language = "c"
standard = "c11"
compiler = "clang"
kind = "bin"
target = "x86_64-pc-windows-gnu" # explicit mingw, not msvc
```
## Build
```bash
dcr build --target x86_64-pc-windows-gnu --release
```
Artifact: `target/x86_64-pc-windows-gnu/release/my-app.exe`.
## Custom toolchain
```toml
[toolchain]
cc = "x86_64-w64-mingw32-gcc"
cxx = "x86_64-w64-mingw32-g++"
```

View File

@@ -0,0 +1,65 @@
---
sidebar_label: Library project
---
# Library Project
Creating a static library and using it in another project.
## Step 1: Create the library
```bash
dcr new my-lib
cd my-lib
```
`dcr.toml`:
```toml
[package]
name = "my-lib"
version = "0.1.0"
type = "none"
[build]
language = "c"
standard = "c11"
kind = "staticlib"
```
`src/my_lib.h`:
```c
#ifndef MY_LIB_H
#define MY_LIB_H
int add(int a, int b);
#endif
```
`src/my_lib.c`:
```c
#include "my_lib.h"
int add(int a, int b) { return a + b; }
```
## Step 2: Build
```bash
dcr build --release
```
Artifacts:
- `target/<triple>/release/libmy-lib.a` (Linux)
- `target/release/my-lib.lib` (Windows)
- `target/include/` — header files
## Step 3: Use in another project
```bash
dcr new my-app
cd my-app
dcr add my-lib ../my-lib
```
Automatically:
- Adds include path to `target/include/` of the library
- Adds lib path to `target/<triple>/release/`
- Links `libmy-lib.a` / `my-lib.lib`

View File

@@ -0,0 +1,99 @@
---
sidebar_label: Multi-package workspace
---
# Multi-Package Workspace
A project with three packages: two libraries and a binary.
## Structure
```
workspace/
├── dcr.toml # root workspace
├── lib-core/
│ ├── dcr.toml
│ └── src/core.c
├── lib-utils/
│ ├── dcr.toml
│ └── src/utils.c
└── app/
├── dcr.toml
└── src/main.c
```
## Root dcr.toml
```toml
[package]
name = "my-workspace"
version = "0.1.0"
type = "none"
[build]
inherit = true
language = "c"
standard = "c11"
workspace_only = true
[workspace.lib-core]
path = "lib-core"
[workspace.lib-utils]
path = "lib-utils"
deps = ["lib-core"]
[workspace.app]
path = "app"
deps = ["lib-core", "lib-utils"]
main = true
```
## Packages
`lib-core/dcr.toml`:
```toml
[package]
name = "lib-core"
version = "0.1.0"
type = "none"
[build]
kind = "staticlib"
```
`lib-utils/dcr.toml`:
```toml
[package]
name = "lib-utils"
version = "0.1.0"
type = "none"
[build]
kind = "staticlib"
```
`app/dcr.toml`:
```toml
[package]
name = "app"
version = "0.1.0"
type = "none"
[build]
kind = "bin"
```
## Build
```bash
cd workspace
dcr build # builds everything in correct order
dcr build --workspace app # only app (lib-core and lib-utils built as deps)
dcr run # builds and runs main package
```
Build order (topological sort):
1. `lib-core`
2. `lib-utils` (depends on lib-core)
3. `app` (depends on lib-core, lib-utils)

View File

@@ -0,0 +1,89 @@
---
sidebar_label: OS-dev (flat-bin + archive)
---
# Pure ASM / boot image (flat-bin + archive)
Minimal OS-dev style pipeline: assemble raw binaries with NASM, then pack a FAT floppy/image with an optional boot sector.
## Project layout
```
myos/
dcr.toml
src/
boot.asm # boot sector (512 bytes)
kernel.asm # payload
```
For multiple flat artifacts, use a **workspace** (one member per binary) or separate packages; a single `flat-bin` package produces one binary per source stem.
## Boot package (`flat-bin`)
```toml
[package]
name = "boot"
version = "0.1.0"
[build]
language = "asm"
compiler = "nasm"
kind = "flat-bin"
extension = "bin"
roots = ["src/boot.asm"]
```
```bash
dcr build
# → target/<…>/debug/boot.bin (NASM -f bin, no link)
```
## Disk image after build
```toml
[archive]
output = "target/{profile}/disk.img"
format = "fat12"
size = "1440K"
label = "MYOS"
bootsector = "target/{profile}/boot.bin"
[[archive.layout]]
from = "target/{profile}/kernel.bin"
to = "KERNEL.BIN"
```
- `format`: `fat12`, `fat16`, or `fat32`
- `size`: bytes or `K`/`KB`/`M`/`MB`/`G`/`GB` (default ~1.44 MiB)
- `bootsector`: written only when `offset` is omitted or `0`
- `from` may be a glob; `{profile}` is substituted in paths
## C kernel → flat binary
```toml
[build]
language = "c"
compiler = "clang"
kind = "flat-bin"
freestanding = true
filename = "kernel"
extension = "bin"
ldflags = ["-T", "linker.ld"]
```
Pipeline: objects → temporary linked ELF → `objcopy -O binary` → `kernel.bin`. Requires `objcopy` / `llvm-objcopy` in PATH.
## Other assemblers
| Tool | Notes |
|------|--------|
| **FASM** | Write `format binary` in the source; DCR writes `<stem>.bin` directly |
| **GAS** | Assemble `.s` → obj → `objcopy -O binary` |
| **LLC** | `language = "llvm_ir"` → obj → objcopy |
| **MASM** | COFF obj → objcopy (needs binutils/LLVM objcopy) |
## Notes
- Single-file `roots` are supported: `roots = ["src/boot.asm"]`.
- `--force` re-runs `build.steps` / `build.post_steps` as well as recompilation.
- Prefer `kind = "elf"` if you need a relocatable ELF kernel without stripping to raw binary.

View File

@@ -0,0 +1,81 @@
---
sidebar_label: Build profiles
---
# Build Profiles
Profiles allow overriding `[build]` fields for specific build modes.
## Configuration
```toml
[build]
language = "c"
standard = "c17"
cflags = ["-Wall"]
[build.debug]
cflags = ["-O0", "-g"]
[build.release]
cflags = ["-O3", "-DNDEBUG"]
```
## Merge rules
Fields from `[build.<profile>]` are merged on top of `[build]`:
- **Scalar fields** (strings, numbers, bools) — replaced
- **Arrays** (`cflags`, `ldflags`, ...) — **appended** (extend the `[build]` array)
Set `inherit = false` to disable array inheritance (only profile's own arrays are used).
## Built-in profiles
The default flags for each profile are composed from three config fields:
| Field | debug default | release default |
|-------|--------------|----------------|
| `opt_level` | `"0"` | `"3"` |
| `debug` | `true` | `false` |
| `warnings` | `["all", "extra"]` | `[]` |
Which produce the equivalent compiler flags:
| Profile | Effective flags |
|---------|---------------|
| `debug` | `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG` |
| `release` | `-O3 -DNDEBUG` |
Additional build options can be toggled per-profile:
```toml
[build.release]
opt_level = "z"
lto = true
strip = true
panic = "abort"
codegen-units = "1"
[build.debug]
opt_level = "1"
debug = false
warnings = ["all", "error"]
```
## Target-specific profiles
```toml
[build.linux]
cflags = ["-DLINUX"]
[build.windows.debug]
cflags = ["-DWIN32", "-O0", "-g"]
```
Application order (highest priority first):
1. `[build.<target>.<profile>]`
2. `[build.<profile>.<target>]`
3. `[build.<target>]`
4. `[build.<profile>]`
5. `[build]`

View File

@@ -0,0 +1,136 @@
---
sidebar_label: Build system
---
# Build System
## Compiler Backends
DCR supports 7 compilation backends. Selection is automatic based on file extension and `build.compiler` value.
| Backend | Files | When used |
|---------|-------|-----------|
| `unix_cc` | `.c`, `.cpp`, `.cxx`, `.cc`, `.S` | gcc/clang on Linux/macOS/BSD |
| `msvc` | `.c`, `.cpp`, `.cxx`, `.cc` | Windows (cl, clang-cl) |
| `gas` | `.s` | ARM/ARM64 assembler (no preprocessor) |
| `nasm` | `.asm`, `.s` | x86/x86_64 NASM assembler |
| `masm` | `.asm` | MASM (ml/ml64) on Windows |
| `fasm` | `.asm`, `.fasm` | Flat Assembler |
| `llvm_ir` | `.ll` | LLVM IR via `llc -filetype=obj` |
## Qt Support
DCR provides native Qt support for automatic meta-object handling (MOC, UIC, RCC).
Enable it by setting `build.qt = true` in `dcr.toml`. DCR will automatically detect Qt-related files (`.ui`, `.qrc`, `.h` with `Q_OBJECT`) and process them.
```toml
[build]
qt = true
```
*Note: Requires `qt6` modules (Core, Widgets, Gui, Svg) installed via `pkg-config`.*
Advanced customization is still possible via `build.steps` if special handling is needed:
```toml
[build.steps]
moc = "moc {in} -o {out}"
```
### Unix CC
- Compiler resolved via `resolve_compiler()`: `DCR_COMPILER` > `DCR_CC` > `[toolchain]` > `build.compiler` > `PATH`
- Supports `.d` files for header dependency tracking
- Flags: `-std=`, `-MMD -MF`, `-c -o`, `-I`, `-L`, `-l`
- Conditional flags based on config:
- `freestanding` or bare-metal target: `-ffreestanding` (compile), `-nostdlib -static` (link)
- `lto`: `-flto` (compile + link)
- `panic = "abort"`: `-fno-exceptions` (C++ only), `-fno-unwind-tables`, `-fno-asynchronous-unwind-tables`
### MSVC
- Supports cl.exe and clang-cl.exe
- Flags: `/std:`, `/Fo:`, `/Fe:`, `/I`, `/link`
### GAS / NASM / MASM / FASM / LLVM-IR
- GAS: `-I`, `-c -o`, `--defsym`
- NASM: `-I`, `-o`, `-D`, `-f` (format: win64/elf64/macho64/macho32/elf32; **`bin` when `kind = "flat-bin"`**)
- MASM: `/nologo /c /Fo<obj> <source>`
- FASM: `<source> <output>` (output path is the object file)
- LLVM IR: `-filetype=obj <source> -o <output>`
## Incremental Builds
Three levels of incrementality:
1. **mtime** — if output is newer than all inputs, skip
2. **`.d` files** — header change tracking (including transitive)
3. **SHA256 fingerprint** — recompile if compiler flags changed (stored in `.dcr_fingerprint`)
## Parallel Compilation
- `thread::scope` for thread pool
- Atomic task queue (`AtomicU64`)
- Mutex on stdout (`OUTPUT_MUTEX`)
- Thread count = `available_parallelism()`, capped by `build.codegen-units` if set
## Build Kinds
| Kind | Type | Path (Linux example) |
|------|------|---------------------|
| `bin` | Executable | `target/<triple>/<profile>/<name>` (.exe on Windows) |
| `staticlib` | Static library | `target/<triple>/<profile>/lib<name>.a` (.lib) |
| `sharedlib` | Dynamic library | `target/<triple>/<profile>/lib<name>.so` (.dll/.dylib) |
| `efi` | UEFI application | `target/<triple>/<profile>/<name>.efi` |
| `elf` | ELF without stdlib | `target/<triple>/<profile>/<name>` |
| `none` | Compile only, no link | — |
| `custom` | Full filename+extension control | `target/<triple>/<profile>/<filename>.<extension>` |
| `flat-bin` | Raw binary | ASM: `<stem>.bin` (NASM `-f bin` / FASM / GAS·MASM·LLC via objcopy); C/C++: `<name>.bin` (link + objcopy) |
## Disk images (`[archive]`)
After a successful build, if `[archive]` is present in `dcr.toml`, DCR formats a FAT volume and copies files from `layout` into the image. See [dcr.toml → archive](/docs/reference/dcr-toml#archive).
## Build Steps
DCR supports pre-build and post-build steps:
- `build.steps` — commands before compilation
- `build.post_steps` — commands after compilation
Substitutions: `{stem}`, `{in}`, `{out}`, `{profile}`, `{version}`, `{name}`.
Example Qt codegen via build steps:
```toml
[build.steps]
moc = "moc {in} -o {out}"
```
## pkg-config
Automatic lookup (read from raw config):
```toml
[build]
pkg_config = ["sdl2", "gl"]
```
DCR runs `pkg-config --cflags sdl2 gl` and `pkg-config --libs sdl2 gl` and adds results to compiler/linker flags.
## Variable Substitution
Supported variables:
| Variable | Description |
|----------|-------------|
| `{version}` | Package version |
| `{version_major}` | Major version part |
| `{version_minor}` | Minor version part |
| `{version_patch}` | Patch version |
| `{version_suffix}` | Suffix (e.g., `-rc1`) |
| `{version_suffix_dash}` | Suffix with dash |
| `{profile}` | Profile name (debug/release) |
| `{name}` | Package name |

View File

@@ -0,0 +1,64 @@
---
sidebar_label: Cross-compilation
---
# Cross-Compilation
## Short Names
DCR supports short platform names:
| Short Name | Full Triple |
|-----------|-------------|
| `linux` | `x86_64-unknown-linux-gnu` |
| `macos` | `x86_64-apple-darwin` |
| `windows` | `x86_64-pc-windows-msvc` |
```bash
dcr build --target windows
```
## Full Triples
```bash
dcr build --target aarch64-unknown-linux-gnu
dcr build --target x86_64-pc-windows-gnu # mingw
dcr build --target armv7-unknown-linux-gnueabihf
```
## clang --target
When using clang, DCR injects `--target=<triple>` into CFLAGS.
```toml
[build]
compiler = "clang"
target = "aarch64-unknown-linux-gnu"
# Auto: cflags += ["--target=aarch64-unknown-linux-gnu"]
```
## Bare-Metal / Freestanding
For targets containing `none`, `-elf`, `eabi`, or `baremetal`, DCR automatically:
1. **Disables default flags** — no system include paths, no `-l` libc
2. **Injects `-ffreestanding`** at compile time and `-nostdlib -static` at link time
You can also enable freestanding mode explicitly:
```toml
[build]
freestanding = true
```
```bash
dcr build --target aarch64-none-elf
```
## Target Directory
By default, the compilation output directories are structured as follows:
- **Linux and BSD**: Always output to `target/<triple>/<profile>/` (using host triple if no target is specified).
- **macOS and Windows (without target)**: Output to `target/<profile>/`.
- **macOS and Windows (with explicit target)**: Output to `target/<triple>/<profile>/`.

281
docs/reference/dcr-toml.mdx Normal file
View File

@@ -0,0 +1,281 @@
---
sidebar_label: dcr.toml overview
---
# dcr.toml
Main project configuration file. Located at the project root.
## Structure
```toml
[package]
# required fields
[build]
# build settings
[build.debug] # optional: debug override
[build.release] # optional: release override
[build.linux] # optional: Linux override
[build.windows] # optional: Windows override
[build.windows.debug] # target + profile combination
[toolchain]
# compiler/linker paths
[dependencies]
# project dependencies
[workspace]
# multi-package configuration
[run]
# run settings
[archive]
# optional: pack FAT disk image after build
```
## [package]
| Field | Required | Description |
|-------|----------|-------------|
| `name` | yes | Project name |
| `version` | yes | Semantic version of the **project** |
| `dcr-version` | no | Semver of the **dcr tool** this project targets. Written by `dcr new` / `dcr init` to the current tool version. |
| `type` | no | `app`, `lib`, `none` (defaults to `"none"`) |
| `license` | no | SPDX license identifier |
| `author` | no | Author |
```toml
[package]
name = "my-app"
version = "0.1.0"
dcr-version = "0.8.4"
type = "app"
license = "MIT"
author = "John Doe"
```
### `package.dcr-version`
Optional pin of the DCR CLI the project was written for. On every `dcr.toml` load:
| Situation | Behaviour |
|-----------|-----------|
| Field missing / empty | Warning: add `dcr-version` (set by `dcr new` / `dcr init`) |
| Running dcr **older** than pin | Warning: upgrade dcr (`dcr --update`) |
| Running dcr **newer** than pin | Warning: review changelog, bump the pin when ready |
| Equal | Silent |
| Invalid semver | Warning once, field ignored |
Never fails the command — warnings only. Pre-release suffixes (`1.0.0-dev`) are ignored for comparison (`1.0.0-dev` ≡ `1.0.0`).
## [build]
...
- `build.qt` — (bool) Enable automatic Qt meta-object handling (MOC, UIC, RCC). Requires `qt6` modules installed via `pkg-config`.
| Field | Default | Description |
|-------|---------|-------------|
| `language` | `"c"` | `"c"`, `"c++"`, `"cpp"`, `"cxx"`, `"asm"`, `"llvm_ir"`, `"llvm-ir"`, `"ll"` (optional, defaults to `"c"`) |
| `standard` | `"c11"` | C standard (`c11`, `c17`, `c23`) |
| `cxx_standard` | — | C++ standard (`c++17`, `c++20`, `c++23`) |
| `compiler` | `"clang"` | Preferred compiler (optional, defaults to `"clang"`) |
| `kind` | `"bin"` | `bin`, `staticlib`, `sharedlib`, `efi`, `elf`, `none`, `custom`, `flat-bin` |
| `target` | host | Target triple for cross-compilation |
| `platform` | `"native"` | `native`, `efi` |
| `cflags` | `[]` | Additional C/C++/ASM flags |
| `ldflags` | `[]` | Additional linker flags |
| `filename` | `""` | Custom output file name |
| `extension` | `""` | Custom file extension (for `flat-bin`, default is `bin`) |
| `roots` | `["src"]` | Source roots: directories and/or individual source/header files |
| `exclude` | `[]` | Exclude patterns |
| `include` | `[]` | Additional include directories |
| `src_disable` | `false` | Disable auto source discovery |
| `inherit` | `false` | Inherit build from workspace root |
| `clean` | `[]` | Glob patterns for custom clean paths |
| `out_dir` | `""` | Custom output directory |
| `workspace_only` | `false` | Workspace-only, not built standalone (no `language`/`compiler` required) |
| `freestanding` | `false` | Compile in freestanding mode (`-ffreestanding` + `-nostdlib -static`) |
| `opt_level` | — | Optimization level: `0`-`3`, `"s"`, `"z"` (derived from profile if omitted) |
| `debug` | profile-based | Emit debug symbols (`-g`): `true` in debug, `false` in release |
| `lto` | `false` | Link-time optimization (`-flto` for both compiler and linker) |
| `strip` | `false` | Strip symbols from output (`-s` in ldflags) |
| `warnings` | `[]` | Warning flags (e.g. `"all"`, `"extra"`, `"pedantic"`); engine adds `-Wall -Wextra` in debug if empty |
| `panic` | `""` | Panic strategy: `"abort"` disables exceptions and unwind tables |
| `codegen-units` | `""` | Max parallel compilation jobs (`"0"` = auto) |
| `qt` | `false` | Enable automatic Qt meta-object handling (MOC, UIC, RCC) |
Settings from raw config (not in typed struct):
- `pkg_config` — list of packages queried for build-wide compiler/linker flags
Dependencies can also declare a system package directly, for example
`fmt = { pkg-config = "fmt" }`. This records the package in `dcr.lock` and applies
the flags returned by `pkg-config --cflags` and `pkg-config --libs`.
- `ldscript` — linker script path
- `build.steps` / `build.post_steps` — codegen steps
## Per-language overrides: `[build.c]`, `[build.cxx]`, `[build.asm]`, `[build.llvm_ir]`
Each language can have its own table that overrides the flat `[build]` settings:
```toml
[build]
compiler = "clang"
standard = "c11"
[build.c]
standard = "c23"
compiler = "gcc"
[build.cxx]
standard = "c++23"
compiler = "g++"
[build.asm]
compiler = "nasm"
flags = ["-felf64"]
[build.llvm_ir]
compiler = "llc"
```
The flat `[build]` acts as fallback; per-language tables take precedence for their language.
Example:
```toml
[build]
language = "c++"
standard = "c23"
cxx_standard = "c++23"
compiler = "clang"
kind = "sharedlib"
cflags = ["-Wall", "-Wextra"]
opt_level = "z"
lto = true
strip = true
panic = "abort"
codegen-units = "2"
```
## [toolchain]
```toml
[toolchain]
cc = "/usr/bin/clang"
cxx = "/usr/bin/clang++"
as = "/usr/bin/as"
ar = "/usr/bin/ar"
ld = "/usr/bin/ld.lld"
```
Raw config also supports `uic`, `moc`, `rcc` for Qt codegen.
## [dependencies]
See [dependencies](/docs/reference/dependencies).
## [run]
```toml
[run]
cmd = "./target/{profile}/{name}"
```
Substitutions:
- `{version}` — package version
- `{version_major}`, `{version_minor}`, `{version_patch}`, `{version_suffix}`, `{version_suffix_dash}` — version parts
- `{profile}` — debug / release
- `{name}` — package name
Default `cmd` = `./target/{profile}/{name}` (macOS/Windows) or `./target/<triple>/<profile>/<name>` (Linux).
## [workspace]
See [workspaces](/docs/reference/workspaces).
## [archive]
Optional post-build step: format a FAT volume and copy built artifacts into a disk image. Runs after a successful package build (and after workspace member builds that define `[archive]`).
Requires DCR built with the `archive` Cargo feature (`cargo build --features archive`). Release binaries include this feature.
| Field | Required | Description |
|-------|----------|-------------|
| `output` | yes | Image path relative to project root (`{profile}` allowed) |
| `format` | yes | `fat12`, `fat16`, or `fat32` |
| `size` | no | Image size: bytes or `K`/`KB`/`M`/`MB`/`G`/`GB` (default `1474560` ≈ 1.44 MiB) |
| `offset` | no | Byte offset of the FAT volume inside the image (default `0`) |
| `label` | no | Volume label (max 11 chars, default `VOLUME`) |
| `bootsector` | no | Path to a 512-byte boot sector written at offset 0 when `offset` is 0 (`{profile}` allowed) |
| `layout` | no | List of `{ from, to }` entries (files or globs → path inside the volume) |
```toml
[archive]
output = "target/{profile}/disk.img"
format = "fat12"
size = "1440K"
label = "MYOS"
bootsector = "target/{profile}/boot.bin"
[[archive.layout]]
from = "target/{profile}/kernel.bin"
to = "KERNEL.BIN"
[[archive.layout]]
from = "assets/*"
to = "/"
```
Typical pairing with `kind = "flat-bin"` (NASM `-f bin`) for bootloaders and pure-ASM OS images.
## `flat-bin` (kind)
Produces a raw binary (default extension `bin`) for boot sectors, kernels, and freestanding payloads.
### Assemblers
| Tool | Language / compiler | How flat-bin is produced |
|------|---------------------|--------------------------|
| NASM | `language = "asm"`, `compiler = "nasm"` | `-f bin` → `<stem>.bin` |
| FASM | `compiler = "fasm"` | direct write (use `format binary` in source) |
| GAS | `compiler = "as"` / `"gas"` | assemble → `objcopy -O binary` |
| MASM | `compiler = "ml"` / `"ml64"` | assemble → `objcopy -O binary` |
| LLC | `language = "llvm_ir"`, `compiler = "llc"` | `-filetype=obj` → `objcopy -O binary` |
```toml
[build]
language = "asm"
compiler = "nasm"
kind = "flat-bin"
extension = "bin"
roots = ["src/boot.asm"]
```
### C / C++
Compile all sources, link with `-nostdlib -static` (plus your `ldflags` / `ldscript`), then convert the intermediate ELF/PE with `objcopy -O binary` to `<filename|name>.bin`.
```toml
[build]
language = "c"
compiler = "clang"
kind = "flat-bin"
freestanding = true
filename = "kernel"
extension = "bin"
ldscript = "linker.ld"
ldflags = ["-T", "linker.ld"]
```
Notes:
- Multi-file **ASM** packages emit one `<stem>.bin` per source; **C/C++** emit a single project binary.
- `objcopy` tools tried in order: `llvm-objcopy`, `objcopy`, `gobjcopy`.
- Incompatible with `build.qt = true`.
- `dcr run` rejects `flat-bin` (not a host executable).

View File

@@ -0,0 +1,159 @@
---
sidebar_label: Dependencies
---
# Dependencies
## Formats
The `[dependencies]` section supports registry, path, Git, and `pkg-config` dependencies:
### String (registry)
```toml
[dependencies]
fmt = "10.1.1"
spdlog = "1.12"
catch2 = "3.4.0"
```
The version string is used as-is for registry lookup.
### Table (git)
```toml
[dependencies]
dcr-math = { git = "https://github.com/dexoron/dcr-math-lib.git", tag = "v0.1.0" }
```
Fields: `git`, `branch`, `tag`, `rev`, `mode`, `include`, `lib`, `libs`.
### Table (path)
```toml
[dependencies]
mylib = { path = "../mylib" }
```
For a prebuilt external library, specify its layout explicitly. The three
arrays are paths relative to the dependency root (`include`, `lib`) and linker
names (`libs`):
```toml
[dependencies]
dcr-math = { path = "../deps/dcr-math", include = ["include"], lib = ["lib"], libs = ["dcr-math"] }
```
### Table (`pkg-config`)
Use an installed `.pc` package when the library is provided by the operating system
or another system package manager. DCR invokes `pkg-config --cflags` and
`pkg-config --libs`, preserving include, library, linker, and other flags:
```toml
[dependencies]
fmt = { pkg-config = "fmt" }
openssl = { pkg-config = ["openssl", "libcrypto"] }
```
The `pkg-config` executable must be available in `PATH`. The named `.pc` files must
be available through pkg-config's standard search paths or via `PKG_CONFIG_PATH`.
These dependencies are not downloaded or built by DCR; they are recorded in
`dcr.lock` with a `pkg-config+...` source.
## Registry
DCR uses a package registry for dependency lookup by name.
```toml
# ~/.dcr/config.toml
[registry.default]
url = "https://index.dcr.dexoron.tech"
priority = 1
```
Registry priority: order in `config.toml`. The `DCR_INDEX_PATH` variable overrides the path to `index.json`.
## Git dependencies
Git dependencies are cloned into `~/.dcr/git/`; their exact checked-out commit is written to `dcr.lock`.
Both Git and path dependencies have two modes:
- **DCR package:** the source has `dcr.toml`. DCR builds it before the consumer and uses the package name from its manifest for linking.
- **Prebuilt library:** it provides existing headers and binaries through `include`, `lib`, and `libs`. DCR passes them to the compiler/linker without building the dependency.
The mode is normally detected automatically. Use `mode = "build"` or `mode = "prebuild"` when a source contains both a manifest and prebuilt artifacts and you need to choose explicitly.
`dcr-math-lib` publishes both forms. Select its prebuilt library explicitly:
```toml
[dependencies]
dcr-math = { git = "https://github.com/dexoron/dcr-math-lib.git", tag = "v0.1.0", mode = "prebuild", include = ["include"], lib = ["lib"], libs = ["dcr-math"] }
```
DCR supports specifying branch/tag/rev for git sources:
- `branch` — switch to a branch
- `tag` — switch to a tag
- `rev` — switch to a specific commit
- `features` — feature flags (parsed, but does not affect build)
## Path dependencies
Local paths may point at a DCR package or a prebuilt external library. A path DCR package is built before its consumer, including its own path/git dependencies. DCR detects dependency cycles. A prebuilt library is never built by DCR; it is linked from the directories declared in its dependency entry.
```toml
[dependencies]
mylib = { path = "/abs/path/to/lib" }
mylib = { path = "../relative/path" }
```
For DCR packages, the package name from `dcr.toml` is used for linking (so the dependency key may differ from the library name). Header resolution includes the packaged `target/include` directory and link search includes `target/lib`.
### Recommended library layouts
Buildable DCR library:
```text
dcr-math-lib/
├── dcr.toml
└── src/
├── dcr_math.c
└── dcr_math.h
```
Prebuilt library:
```text
dcr-math-lib/
├── include/
│ └── dcr_math.h
└── lib/
└── libdcr-math.a # or .so / .lib
```
`libs` contains linker names without the `lib` prefix and file extension: use
`["dcr-math"]` for `libdcr-math.a`.
If one repository contains both forms, keep the prebuilt files in `include/` and `lib/` and select the mode explicitly:
```toml
[dependencies]
dcr-math = { git = "https://github.com/dexoron/dcr-math-lib.git", tag = "v0.1.0", mode = "prebuild", include = ["include"], lib = ["lib"], libs = ["dcr-math"] }
```
For a local copy, replace `git` with `path = "../dcr-math-lib"`. In `prebuild` mode DCR does not run the dependency build, even when `dcr.toml` is present.
## dcr.lock
Dependency lock file. Contains package names and sources.
Created during `dcr build` for resolved registry, path, and Git dependencies. Not updated during `dcr add` — only on the next `dcr build`.
## Resolution process
1. Resolve dependency sources (registry → git → path)
2. Fetch git dependencies into `~/.dcr/git/` and check out the requested ref
3. Build DCR package dependencies recursively
4. Collect `include_dirs`, `lib_dirs`, `libs` for the compiler

View File

@@ -0,0 +1,83 @@
---
sidebar_label: Environment variables
---
# Environment Variables
## DCR_COMPILER
Overrides the compiler for all languages. **Highest priority.**
```bash
export DCR_COMPILER=clang
dcr build
```
Compiler resolution priority:
1. `DCR_COMPILER` (env)
2. `DCR_CC` / `DCR_CXX` / `DCR_AS` (env)
3. `[toolchain]` (dcr.toml)
4. `build.compiler` (dcr.toml)
5. `PATH`
## DCR_CC / DCR_CXX / DCR_AS
Per-language override (lower priority than `DCR_COMPILER`, higher than `[toolchain]`).
```bash
export DCR_CC=gcc-14
export DCR_CXX=g++-14
export DCR_AS=arm-linux-gnueabihf-as
```
## DCR_LD / DCR_AR
Override linker and archiver.
```bash
export DCR_LD=ld.lld
export DCR_AR=llvm-ar
```
## DCR_DEBUG
Enables debug mode — prints all compilation commands to stderr before execution.
```bash
export DCR_DEBUG=1
dcr build
```
## DCR_INDEX_PATH
Overrides the path to the registry `index.json`.
```bash
export DCR_INDEX_PATH=/custom/path/index.json
```
Default: `~/.dcr/index.json`.
## DCRUP_MIRROR
Download mirror for `dcrup` (prebuilt binaries only).
```bash
export DCRUP_MIRROR=auto # cf → gh → sf → ya (default)
export DCRUP_MIRROR=cf # CloudFlare CDN
export DCRUP_MIRROR=gh # GitHub Releases
export DCRUP_MIRROR=sf # SourceForge
export DCRUP_MIRROR=ya # Yandex CDN (RU fallback)
```
CLI override: `dcrup install stable --mirror cf`.
## DCRUP_HOME / DCRUP_BIN / DCRUP_REPO
Installer layout and source clone URL:
```bash
export DCRUP_HOME=~/.dcr
export DCRUP_BIN=~/.dcr/bin
export DCRUP_REPO=https://github.com/dexoron/dcr
```

View File

@@ -0,0 +1,53 @@
---
sidebar_label: Platform support
---
# Platform Support
## Target Triples
DCR normalizes target triples. Short platform names are expanded to full triples.
### Linux
```
target/<arch>-unknown-linux-<env>/<profile>/
```
Architectures: `x86_64`, `aarch64`, `i686`, `armv7`, `riscv64` (host-detected).
Environments: `gnu` (default), `musl`.
Artifact type: ELF. Extensions: `.so` (sharedlib), `.a` (staticlib).
### macOS
```
target/<profile>/ (default) or target/<triple>/<profile>/ (with target)
```
Architectures: `x86_64`, `aarch64` (host-detected).
Extensions: `.dylib` (sharedlib), `.a` (staticlib).
### Windows
```
target/<profile>/ (default) or target/<triple>/<profile>/ (with target)
```
Architectures: `x86_64`, `aarch64`.
Environments: `msvc` (default), `gnu` (MinGW).
Extensions: `.exe` (bin), `.lib` (staticlib), `.dll` (sharedlib), `.efi` (UEFI).
### BSD
```
target/<arch>-unknown-<os>/<profile>/
```
Supported systems: `freebsd`, `openbsd`, `netbsd`, `dragonfly`.
## Host Detection
DCR uses `std::env::consts::ARCH` and `std::env::consts::OS` for host platform detection. Used as fallback when `target` is not specified.

View File

@@ -0,0 +1,87 @@
---
sidebar_label: Workspaces
---
# Workspaces
Workspaces allow managing multiple packages in a single repository.
## Configuration
```toml
[workspace.lib-core]
path = "lib-core"
[workspace.lib-utils]
path = "lib-utils"
deps = ["lib-core"]
[workspace.app]
path = "app"
deps = ["lib-core", "lib-utils"]
main = true
```
### Member fields
| Field | Description |
|-------|-------------|
| `path` | Path to the package (relative to workspace root) |
| `deps` | Dependencies on other members |
| `main` | Mark as the main package |
## Topological sort
DCR automatically sorts packages by dependencies: package A is built before B if B depends on A.
Cyclic dependencies are detected and cause an error.
## Build
```bash
dcr build # build all packages in dependency order
dcr build --workspace app # build only app (dependencies built automatically)
```
When building a workspace, DCR automatically injects include and library paths of dependent workspace members:
- **Include Paths**: Automatically resolves and injects header directories of dependencies, including the member's `src/` directory, local `include/` directory, and the packaged `target/include` directory.
- **Library Paths**: Injects compiled library search paths (`target/lib` as well as target-specific build folders) to allow automatic linking with member libraries.
## Clean
```bash
dcr clean # clean only root target/
dcr clean --all # clean target/ of all packages
```
## Inheritance
If a member has `inherit = true` in its `[build]` section, fields from the root `[build]` are merged into the member:
```toml
# root dcr.toml
[build]
inherit = true
language = "c"
standard = "c17"
# member inherits language and standard
```
## Workspace-only root (no build of its own)
A workspace root can set `workspace_only = true` — it won't be built itself, and doesn't need `language` or `compiler`:
```toml
[package]
name = "my-workspace"
version = "0.1.0"
[build]
workspace_only = true
kind = "bin"
[workspace]
lib-core = { path = "lib-core" }
app = { path = "app", deps = ["lib-core"] }
```

46
docs/self-update.mdx Normal file
View File

@@ -0,0 +1,46 @@
---
sidebar_label: Self Update
---
# Self Update
## dcr --update
Automatic update to the latest version. Downloads a binary (not an archive) from GitHub Releases and replaces the current executable.
```bash
dcr --update
```
## How it works
1. DCR detects the current platform and architecture
2. Fetches the latest release from `api.github.com/repos/dexoron/dcr/releases/latest`
3. Compares versions
4. Downloads the matching asset (direct binary URL, not archive)
5. Replaces the current executable via `self_replace`
## Asset naming
Pattern: `dcr-<triple>` or `dcr-<triple>.exe`
| Platform | Asset name |
|----------|-----------|
| Linux x86_64 | `dcr-x86_64-unknown-linux-gnu` |
| macOS x86_64 | `dcr-x86_64-apple-darwin` |
| macOS ARM64 | `dcr-aarch64-apple-darwin` |
| Windows x86_64 | `dcr-x86_64-pc-windows-msvc.exe` |
## AUR
If DCR was installed via AUR, `--update` shows a warning:
```
Update via package manager: yay/paru -Syu {package_name} or sudo pacman -Syu {package_name}
```
## Errors
- Cannot detect platform — error
- Cannot fetch release — error with URL
- No write permission — error (use `sudo` or manual install)

View File

@@ -0,0 +1,45 @@
---
sidebar_label: Running tests
---
# Running Tests
## Execution
```bash
dcr test
```
Builds the project, then compiles and runs test files from `tests/`.
## Profiles
```bash
dcr test # debug (default)
dcr test --release # release
dcr test --debug # explicit debug
```
## Output
```
=====================
Testsuite summary
=====================
TOTAL: 5
PASS: 3
SKIP: 1
FAIL: 1
=====================
```
## Exit code
- 0 — all tests passed (FAIL = 0)
- 1 — test failures or build error
## What gets built
- All `.c` files from `tests/` (`.cpp` not supported)
- Include path: `tests/` (for `dcr_test.h`)
- Linked with project if `package.type = "lib"` or `kind = "staticlib"`/`"sharedlib"`

View File

@@ -0,0 +1,68 @@
---
sidebar_label: Test framework (EXPECT, TEST, ...)
---
# Test Framework
DCR has a built-in minimal test framework for C.
## Macros
### `EXPECT(expr)`
Asserts that an expression is true.
```c
EXPECT(1 + 1 == 2);
EXPECT(ptr != NULL);
```
### `SKIP(reason)`
Skips a test with a message.
```c
SKIP("not implemented on Windows");
```
### `TEST(name)`
Defines a test.
```c
TEST(addition) {
EXPECT(1 + 1 == 2);
EXPECT(2 + 2 == 4);
}
```
### `TEST_CASE(name)`
Registers a test case.
```c
TEST_CASE(math) {
EXPECT(1 + 1 == 2);
}
```
## Initialization
```bash
dcr test --init
```
Creates:
- `tests/dcr_test.h` — framework header (do not edit)
- `tests/test.c` — template with example test
## Structure
```
tests/
├── dcr_test.h # framework (do not edit)
├── test.c # main tests
└── ... # additional .c test files
```
Only `.c` files are compiled (`.cpp` is not supported).

135
flake.lock generated Normal file
View File

@@ -0,0 +1,135 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1780218205,
"narHash": "sha256-c4MSlBQw5MIDRkad9UNUUnBaOHAHnhuCXrDt90FDVrg=",
"owner": "nix-community",
"repo": "fenix",
"rev": "96e0fc9f1a9b37f6477fa11c3fd48575354773ed",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1779560665,
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1770107345,
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1780179631,
"narHash": "sha256-61A3gj4V1DyPP6lVHTdui4+dvG66pkMZSMd1Kke3Rz8=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "cce33f2f1565958787bb763ae751a6a5f56b95e3",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

115
flake.nix Normal file
View File

@@ -0,0 +1,115 @@
{
description = "DCR — Cargo-like utility for managing C/C++ projects";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
treefmt-nix.url = "github:numtide/treefmt-nix";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
flake-utils,
treefmt-nix,
fenix,
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
};
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
packageName = cargoToml.package.name;
packageVersion = cargoToml.package.version;
rustToolchain = fenix.packages.${system}.stable.toolchain;
rustPlatform = pkgs.makeRustPlatform {
cargo = rustToolchain;
rustc = rustToolchain;
};
treefmtEval = treefmt-nix.lib.evalModule pkgs {
projectRootFile = "flake.nix";
programs.alejandra.enable = true;
programs.rustfmt.enable = true;
programs.taplo.enable = true;
};
app = rustPlatform.buildRustPackage {
pname = packageName;
version = packageVersion;
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
nativeBuildInputs = [
pkgs.pkg-config
pkgs.cmake
pkgs.perl
];
buildInputs = pkgs.lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.Security
pkgs.darwin.apple_sdk.frameworks.SystemConfiguration
];
postInstall = ''
install -Dm644 man/man1/*.1 -t $out/share/man/man1/
'';
# Tests require external services; run separately.
doCheck = false;
};
in {
packages = {
default = app;
dcr = app;
};
apps = let
defaultApp =
(flake-utils.lib.mkApp {
drv = app;
})
// {
meta.description = "Run ${packageName}";
};
in {
default = defaultApp;
dcr = defaultApp;
};
checks = {
dcr = app;
formatting = treefmtEval.config.build.check self;
};
formatter = treefmtEval.config.build.wrapper;
devShells.default = pkgs.mkShell {
inputsFrom = [app];
packages = [
rustToolchain
pkgs.rust-analyzer
treefmtEval.config.build.wrapper
];
shellHook = ''
export RUST_SRC_PATH="${rustToolchain}/lib/rustlib/src/rust/library"
'';
};
});
}

45
man/man1/dcr-add.1 Normal file
View File

@@ -0,0 +1,45 @@
.TH DCR-ADD 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-add \- Add a dependency to a DCR project
.SH SYNOPSIS
.B dcr add \fI<name>\fR \fI<source>\fR
[\fB\-\-branch\fR \fI<b>\fR]
[\fB\-\-tag\fR \fI<t>\fR]
[\fB\-\-rev\fR \fI<r>\fR]
.SH DESCRIPTION
Adds a dependency to the project.
.SH SOURCES
.TP
.B user/repo
GitHub shorthand.
.TP
.B github:user/repo
Full GitHub repository.
.TP
.B gitlab:user/repo
GitLab repository.
.TP
.B git:host.com/user/repo
Generic git repository.
.TP
.B path:./path/to/lib
Local path dependency.
.TP
.B pkg-config:name
System pkg-config dependency (also accepts \fBpkg:\fR or comma-separated packages).
.TP
.B <version>
Version from registry.
.SH OPTIONS
.TP
.B \-\-branch \fI<name>\fR
Use a specific branch.
.TP
.B \-\-tag \fI<name>\fR
Use a specific tag.
.TP
.B \-\-rev \fI<hash>\fR
Use a specific commit hash.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-tree (1)

48
man/man1/dcr-build.1 Normal file
View File

@@ -0,0 +1,48 @@
.TH DCR-BUILD 1 "2026-08-04" "dcr 0.8.4" "DCR Manual"
.SH NAME
dcr-build \- Compile a DCR project
.SH SYNOPSIS
.B dcr build
[\fB\-\-debug\fR | \fB\-\-release\fR]
[\fB\-\-target\fR \fI<triple>\fR]
[\fB\-\-force\fR]
[\fB\-\-clean\fR]
[\fB\-\-verbose\fR]
[\fB\-\-workspace\fR \fI<name>\fR]
[\fB\-\-print\-artifact\-path\fR]
.SH DESCRIPTION
Compiles the project. Default profile is
.B \-\-debug .
On success, updates
.B .dcr/build-info.json
and
.B .dcr/toolchain.json
when applicable.
.SH OPTIONS
.TP
.B \-\-debug
Build with debug profile (default). Enables debug symbols.
.TP
.B \-\-release
Build with release profile. Enables full optimization.
.TP
.B \-\-target \fI<triple>\fR
Cross-compile for the given target.
.TP
.B \-\-force
Force a full rebuild, ignoring cached artifacts.
.TP
.B \-\-clean
Clean before building.
.TP
.B \-\-verbose
Print detailed build output including compiler commands.
.TP
.B \-\-workspace \fI<name>\fR
Build a specific workspace member.
.TP
.B \-\-print\-artifact\-path
After a successful build, print the absolute path of the main artifact as a single line on stdout.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-gen (1)

26
man/man1/dcr-clean.1 Normal file
View File

@@ -0,0 +1,26 @@
.TH DCR-CLEAN 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-clean \- Remove build artifacts
.SH SYNOPSIS
.B dcr clean
[\fB\-\-debug\fR | \fB\-\-release\fR]
[\fB\-\-target\fR \fI<triple>\fR]
[\fB\-\-all\fR]
.SH DESCRIPTION
Removes build artifacts from the target directory.
.SH OPTIONS
.TP
.B \-\-debug
Clean debug artifacts (default).
.TP
.B \-\-release
Clean release artifacts.
.TP
.B \-\-target \fI<triple>\fR
Clean artifacts for a specific target.
.TP
.B \-\-all
Clean all workspace members.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-build (1)

10
man/man1/dcr-fmt.1 Normal file
View File

@@ -0,0 +1,10 @@
.TH DCR-FMT 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-fmt \- Format C/C++ source files
.SH SYNOPSIS
.B dcr fmt
.SH DESCRIPTION
Formats all C/C++ source files using clang-format. Scans \fBsrc/\fR
and \fBtests/\fR directories recursively.
.SH SEE ALSO
.BR dcr (1)

67
man/man1/dcr-gen.1 Normal file
View File

@@ -0,0 +1,67 @@
.TH DCR-GEN 1 "2026-08-04" "dcr 0.8.4" "DCR Manual"
.SH NAME
dcr-gen \- Generate IDE and tooling integration files
.SH SYNOPSIS
.B dcr gen \fI<subcommand>\fR
[\fB\-\-debug\fR | \fB\-\-release\fR]
[\fB\-\-quiet\fR | \fB\-q\fR]
.SH DESCRIPTION
Generates IDE and tooling integration files for DCR projects.
Machine-readable build facts are written under
.B .dcr/
(compile database, build-info, toolchain).
.SH SUBCOMMANDS
.TP
.B project-info
Print project metadata as JSON to stdout (absolute paths, artifact_path, target_dir).
Also updates
.B .dcr/build-info.json
and
.B .dcr/toolchain.json .
.TP
.B compile-commands
Generate
.B .dcr/compile_commands.json
(atomic write) and ensure
.B .clangd
points at
.B .dcr
as CompilationDatabase.
.TP
.B vscode
Generate
.B .vscode/
integration files (tasks, launch configurations) and compile commands under
.B .dcr/ .
.TP
.B clion
Generate
.B .idea/
integration files for CLion and compile commands under
.B .dcr/ .
.SH OPTIONS
.TP
.B \-\-debug
Use debug profile (default).
.TP
.B \-\-release
Use release profile.
.TP
.B \-\-quiet\fR, \fB\-q
Suppress human-readable success messages on stdout.
.SH FILES
.TP
.B .dcr/compile_commands.json
JSON Compilation Database (moved from project root; breaking change in 0.8.4).
.TP
.B .dcr/build-info.json
Last known artifact path, profile, compiler.
.TP
.B .dcr/toolchain.json
Resolved compiler/debugger (and Qt tools if configured).
.TP
.B .clangd
Created once with CompilationDatabase: .dcr; not overwritten if customized.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-build (1)

11
man/man1/dcr-init.1 Normal file
View File

@@ -0,0 +1,11 @@
.TH DCR-INIT 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-init \- Initialize a directory as a DCR project
.SH SYNOPSIS
.B dcr init
.SH DESCRIPTION
Initializes the current directory as a DCR project. The directory must
be empty.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-new (1)

13
man/man1/dcr-new.1 Normal file
View File

@@ -0,0 +1,13 @@
.TH DCR-NEW 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-new \- Create a new DCR project
.SH SYNOPSIS
.B dcr new \fI<name>\fR
.SH DESCRIPTION
Creates a new C/C++ project with the given name. The name may only
contain ASCII letters, digits, underscores and hyphens.
.SH EXAMPLE
.B dcr new my-project
.SH SEE ALSO
.BR dcr (1),
.BR dcr-init (1)

47
man/man1/dcr-run.1 Normal file
View File

@@ -0,0 +1,47 @@
.TH DCR-RUN 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-run \- Build and run a DCR project
.SH SYNOPSIS
.B dcr run
[\fB\-\-debug\fR | \fB\-\-release\fR]
[\fB\-\-target\fR \fI<triple>\fR]
[\fB\-\-force\fR]
[\fB\-\-clean\fR]
[\fB\-\-verbose\fR]
[\fB\-\-\fR \fI<args>...\fR]
.SH DESCRIPTION
Builds and runs the project. Only available for projects with
\fBkind = "bin"\fR.
Arguments after a bare
.B \-\-
are forwarded to the built binary (cargo-style).
.SH OPTIONS
.TP
.B \-\-debug
Run with debug profile (default).
.TP
.B \-\-release
Run with release profile.
.TP
.B \-\-target \fI<triple>\fR
Cross-compile for the given target.
.TP
.B \-\-force
Force a full rebuild.
.TP
.B \-\-clean
Clean before building.
.TP
.B \-\-verbose
Print detailed build output.
.TP
.B \-\- \fI<args>...\fR
Arguments passed to the built binary.
.SH EXAMPLES
.nf
dcr run --release -- --test_help
dcr run -- --foo bar
.fi
.SH SEE ALSO
.BR dcr (1),
.BR dcr-build (1)

11
man/man1/dcr-setup.1 Normal file
View File

@@ -0,0 +1,11 @@
.TH DCR-SETUP 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-setup \- Set up DCR registries
.SH SYNOPSIS
.B dcr setup
.SH DESCRIPTION
Sets up DCR registries. Downloads and indexes package registries
for dependency resolution.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-add (1)

24
man/man1/dcr-test.1 Normal file
View File

@@ -0,0 +1,24 @@
.TH DCR-TEST 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-test \- Run project tests
.SH SYNOPSIS
.B dcr test
[\fB\-\-init\fR]
[\fB\-\-debug\fR | \fB\-\-release\fR]
.SH ALIASES
.B dcr tests
.SH DESCRIPTION
Runs project tests and prints a unified testsuite report.
.SH OPTIONS
.TP
.B \-\-init
Create \fBtests/dcr_test.h\fR in the current project.
.TP
.B \-\-debug
Build and run tests with debug profile (default).
.TP
.B \-\-release
Build and run tests with release profile.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-build (1)

11
man/man1/dcr-tree.1 Normal file
View File

@@ -0,0 +1,11 @@
.TH DCR-TREE 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
.SH NAME
dcr-tree \- Display the dependency tree
.SH SYNOPSIS
.B dcr tree
.SH DESCRIPTION
Displays the dependency tree of the current project, showing all
transitive dependencies and their versions.
.SH SEE ALSO
.BR dcr (1),
.BR dcr-add (1)

215
man/man1/dcr.1 Normal file
View File

@@ -0,0 +1,215 @@
.TH DCR 1 "2026-08-31" "dcr 0.9.1" "DCR Manual"
.SH NAME
dcr \- Cargo\-like build tool and package manager for C/C++ projects
.SH SYNOPSIS
.B dcr
[\fIcommand\fR] [\fIoptions\fR]
.SH DESCRIPTION
DCR is a comprehensive build tool and package manager for C and C++ projects,
inspired by Cargo from the Rust ecosystem. It handles project creation,
building, dependency management, testing, formatting, and IDE integration.
.SH COMMANDS
.TP
.B new \fI<name>\fR
Create a new C/C++ project with the given name. Only ASCII letters, digits,
underscores and hyphens are allowed.
.TP
.B init
Initialize the current (empty) directory as a DCR project.
.TP
.B build [\fIoptions\fR]
Compile the project. Default profile is \fB\-\-debug\fR.
.TP
.B run [\fIoptions\fR]
Build and run the project. Only available for binary projects.
.TP
.B clean [\fIoptions\fR]
Remove build artifacts from the target directory.
.TP
.B test [\fI\-\-init\fR] [\fI\-\-debug\fR|\fI\-\-release\fR]
Run project tests (alias: \fBdcr tests\fR).
.TP
.B add \fI<name>\fR \fI<source>\fR [\fIoptions\fR]
Add a dependency to the project.
.TP
.B tree
Display the dependency tree of the current project.
.TP
.B fmt
Format all C/C++ source files using clang-format.
.TP
.B lint [\fI\-\-fix\fR]
Run clang-tidy static analysis on C/C++ sources.
.TP
.B gen \fI<subcommand>\fR
Generate IDE integration files (vscode, clion, compile-commands, project-info).
.TP
.B setup
Show configured package registries.
.SH GLOBAL FLAGS
.TP
.B \-\-help
Show this help message and exit.
.TP
.B \-\-version
Print version information and exit.
.TP
.B \-\-update
Update DCR to the latest version from GitHub Releases.
.SH BUILD OPTIONS
The following options apply to \fBbuild\fR and \fBrun\fR commands:
.TP
.B \-\-debug
Build with debug profile (default). Enables debug symbols and minimal optimization.
.TP
.B \-\-release
Build with release profile. Enables full optimization without debug symbols.
.TP
.B \-\-target \fI<triple>\fR
Cross-compile for the given target (e.g. \fBaarch64-linux-gnu\fR, \fBx86_64-w64-mingw32\fR).
Short names like \fBlinux\fR, \fBmacos\fR, \fBwindows\fR are also supported.
.TP
.B \-\-force
Force a full rebuild, ignoring cached build artifacts.
.TP
.B \-\-clean
Clean build artifacts before building.
.TP
.B \-\-verbose
Print detailed build output including compiler commands.
.TP
.B \-\-workspace \fI<name>\fR
Build a specific workspace member.
.SH CLEAN OPTIONS
.TP
.B \-\-debug
Clean debug artifacts (default).
.TP
.B \-\-release
Clean release artifacts.
.TP
.B \-\-target \fI<triple>\fR
Clean artifacts for a specific target.
.TP
.B \-\-all
Clean all workspace members.
.SH ADD OPTIONS
.TP
.B \-\-branch \fI<name>\fR
Use a specific branch when adding a git dependency.
.TP
.B \-\-tag \fI<name>\fR
Use a specific tag when adding a git dependency.
.TP
.B \-\-rev \fI<hash>\fR
Use a specific commit hash when adding a git dependency.
.SH DEPENDENCY SOURCES
.TP
.B user/repo
GitHub shorthand (e.g. \fBuser/repo\fR).
.TP
.B github:user/repo
Full GitHub repository URL shorthand.
.TP
.B gitlab:user/repo
GitLab repository URL shorthand.
.TP
.B git:host.com/user/repo
Generic git repository.
.TP
.B path:./path/to/lib
Local path dependency.
.TP
.B pkg-config:name
System pkg-config dependency (also accepts \fBpkg:name1,name2\fR).
.TP
.B \fI<version>\fR
Version from registry (e.g. \fB1.2.3\fR).
.SH GEN SUBCOMMANDS
.TP
.B project-info
Print project metadata as JSON to stdout.
.TP
.B compile-commands
Generate \fB.dcr/compile_commands.json\fR for IDE tooling.
.TP
.B vscode
Generate \fB.vscode/\fR integration files (tasks, launch configurations).
.TP
.B clion
Generate \fB.idea/\fR integration files for CLion.
.SH TEST OPTIONS
.TP
.B \-\-init
Create \fBtests/dcr_test.h\fR in the current project.
.TP
.B \-\-debug
Build and run tests with debug profile (default).
.TP
.B \-\-release
Build and run tests with release profile.
.SH PROJECT CONFIGURATION
DCR uses \fBdcr.toml\fR for project configuration. Key settings include:
.TP
.B package.name
Project name (required).
.TP
.B package.version
Project version (default: \fB0.1.0\fR).
.TP
.B package.dcr\-version
Semver of the DCR tool this project targets. Set automatically by
\fBdcr new\fR / \fBdcr init\fR. Missing pin, or an installed \fBdcr\fR older/newer
than the pin, prints a warning (commands still run).
.TP
.B build.language
Programming language: \fBc\fR or \fBc++\fR (default: \fBc\fR).
.TP
.B build.standard
Language standard (e.g. \fBc11\fR, \fBc17\fR, \fBc++20\fR; default: \fBc11\fR).
.TP
.B build.compiler
Compiler to use: \fBclang\fR, \fBgcc\fR, \fBcl\fR, \fBclang-cl\fR (default: \fBclang\fR).
.TP
.B build.kind
Output type: \fBbin\fR, \fBlib\fR (static or dynamic), \fBstaticlib\fR, \fBsharedlib\fR (default: \fBbin\fR).
.SH FILES
.TP
.I dcr.toml
Project manifest file.
.TP
.I ~/.dcr/config.toml
User-level DCR configuration.
.TP
.I ~/.dcr/index.json
Local registry index.
.SH ENVIRONMENT
.TP
.B DCR_COMPILER
Override the compiler used for building.
.TP
.B DCR_CC
Alternative way to override the compiler.
.SH EXAMPLES
.TP
Create a new project:
.B dcr new hello
.TP
Initialize from current directory:
.B dcr init
.TP
Build with release profile:
.B dcr build \-\-release
.TP
Cross-compile for Windows:
.B dcr build \-\-target windows
.TP
Add a dependency:
.BR "dcr add curl git:https://github.com/curl/curl" .
.TP
Generate VS Code integration:
.B dcr gen vscode
.SH SEE ALSO
.IR https://dcr.dexoron.su ", " https://github.com/dexoron/dcr
.SH AUTHORS
Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>

21
packaging/aur/README.md Normal file
View File

@@ -0,0 +1,21 @@
# AUR publishing
This repository includes an automated AUR publish workflow:
- package files: `packaging/aur/dcr/PKGBUILD`, `packaging/aur/dcr/.SRCINFO`
- workflow: `.github/workflows/aur.yml`
## One-time setup
1. Create the `dcr` package on AUR and initialize its git repository.
2. Create an SSH key pair for CI.
3. Add the public key to your AUR account.
4. Add the private key to GitHub Actions secret `AUR_SSH_PRIVATE_KEY`.
## Release flow
1. Bump project version in `Cargo.toml`.
2. Push tag `v<version>` (example: `v0.2.2`).
3. GitHub Actions will:
- compute source tarball SHA256
- render `PKGBUILD` and `.SRCINFO`
- push updates to `ssh://aur@aur.archlinux.org/dcr.git`

Submodule packaging/aur/dcr-dev-git added at 93bdce858b

1
packaging/aur/dcr-git Submodule

Submodule packaging/aur/dcr-git added at 2c5743a456

View File

@@ -0,0 +1,17 @@
pkgbase = dcr
pkgdesc = Cargo-like utility to manage C/C++ projects
pkgver = 0.2.1
pkgrel = 1
url = https://github.com/dexoron/dcr
arch = x86_64
arch = aarch64
license = MIT
makedepends = cargo
depends = gcc-libs
optdepends = gcc: build C/C++ projects with GCC
optdepends = clang: build C/C++ projects with Clang
options = !lto
source = dcr-0.2.1.tar.gz::https://github.com/dexoron/dcr/archive/refs/tags/v0.2.1.tar.gz
sha256sums = 30e334fa98cd51073c0fa84c80a982584b00156b57084311ad53419441ce6235
pkgname = dcr

View File

@@ -0,0 +1,33 @@
pkgname=dcr
pkgver=0.2.1
pkgrel=3
pkgdesc="Cargo-like utility to manage C/C++ projects"
arch=('x86_64' 'aarch64')
url="https://github.com/dexoron/dcr"
license=('MIT')
depends=('gcc-libs')
makedepends=('cargo')
options=('!lto')
optdepends=(
'gcc: build C/C++ projects with GCC'
'clang: build C/C++ projects with Clang'
)
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('30e334fa98cd51073c0fa84c80a982584b00156b57084311ad53419441ce6235')
build() {
cd "$srcdir/$pkgname-$pkgver"
export CARGO_PROFILE_RELEASE_LTO=false
export RUSTFLAGS="${RUSTFLAGS:-}"
export RUSTFLAGS="${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
cargo build --release --locked
}
package() {
cd "$srcdir/$pkgname-$pkgver"
install -Dm755 "target/release/dcr" "$pkgdir/usr/bin/dcr"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
for manpage in man/man1/*.1; do
install -Dm644 "$manpage" "$pkgdir/usr/share/man/man1/$(basename "$manpage")"
done
}

View File

@@ -0,0 +1,18 @@
name: dcrup
base: core22
version: '0.6.9'
summary: Cargo-like utility to manage C/C++ projects
description: |
A build tool and package manager for C/C++ projects inspired by Rust's Cargo.
grade: stable
confinement: classic
apps:
dcr:
command: bin/dcr
parts:
dcr:
plugin: dump
source: dist/

View File

@@ -1,17 +0,0 @@
[project]
name = "dcr"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "Dexoron", email = "probrovova1@gmail.com" }
]
requires-python = ">=3.14.2"
dependencies = []
[project.scripts]
run = "dcr.main:main"
[build-system]
requires = ["uv_build>=0.10.0,<0.11.0"]
build-backend = "uv_build"

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "stable"

View File

346
src/cli/add.rs Normal file
View File

@@ -0,0 +1,346 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::core::build_config::Config;
use crate::core::deps::register;
use crate::utils::fs::find_project_root;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use toml::Value;
use toml::map::Map;
/// Parsed arguments for the `dcr add` command.
///
/// Exactly one of `path`, `git`, `pkg_config`, or `version_from_registry` is expected to be set,
/// depending on how the dependency source was specified.
pub struct AddArgs {
/// Dependency name as it will appear under `[dependencies]` in `dcr.toml`.
pub name: String,
/// Local filesystem path when the source uses the `path:` prefix.
pub path: Option<String>,
/// Git repository URL when the source is a git/GitHub/GitLab URL or prefix.
pub git: Option<String>,
/// Optional git branch to pin the dependency to.
pub branch: Option<String>,
/// Optional git tag to pin the dependency to.
pub tag: Option<String>,
/// Optional git commit hash (revision) to pin the dependency to.
pub rev: Option<String>,
/// Version string resolved from the package registry when no explicit source is given.
pub version_from_registry: Option<String>,
/// Package names resolved through pkg-config.
pub pkg_config: Option<Vec<String>>,
}
/// Adds a dependency to the current project's `dcr.toml`.
///
/// Parses CLI arguments, locates the project root, builds a TOML dependency value
/// (path, git with optional ref, or registry version), and writes it under
/// `dependencies.<name>`.
///
/// # Parameters
/// - `args`: Tokens after `dcr add` (name, source, optional `--branch`/`--tag`/`--rev`).
///
/// # Returns
/// Process exit code: `0` on success, non-zero on usage or I/O errors.
pub fn add(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(
" dcr add <name> <source> [--branch <b>] [--tag <t>] [--rev <r>]",
BOLD_CYAN,
);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Adds a dependency to the project.");
println!();
printc("SOURCES:", BOLD_GREEN);
println!(" github:user/repo GitHub repository");
println!(" gitlab:user/repo GitLab repository");
println!(" git:host.com/user/repo Generic git repository");
println!(" path:./path/to/lib Local path dependency");
println!(" pkg-config:fmt System pkg-config dependency");
println!(" <version> Version from registry");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --branch <b> Use a specific branch");
println!(" --tag <t> Use a specific tag");
println!(" --rev <r> Use a specific commit hash");
return 0;
}
let add_args = match parse_add_args(args) {
Ok(a) => a,
Err(code) => return code,
};
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error!("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(dir)) => dir,
Ok(None) => {
error!("dcr.toml file not found");
return 1;
}
Err(_) => {
error!("Failed to find project root");
return 1;
}
};
let mut config = match Config::open(&root.join("dcr.toml").to_string_lossy()) {
Ok(cfg) => cfg,
Err(err) => {
error!("{err}");
return 1;
}
};
// Build the TOML value written under dependencies.<name>
let dep_value = if let Some(git_url) = add_args.git {
if add_args.branch.is_none() && add_args.tag.is_none() && add_args.rev.is_none() {
let mut table = Map::new();
table.insert("git".to_string(), Value::String(git_url));
Value::Table(table)
} else {
let mut table = Map::new();
table.insert("git".to_string(), Value::String(git_url));
if let Some(b) = add_args.branch {
table.insert("branch".to_string(), Value::String(b));
}
if let Some(t) = add_args.tag {
table.insert("tag".to_string(), Value::String(t));
}
if let Some(r) = add_args.rev {
table.insert("rev".to_string(), Value::String(r));
}
Value::Table(table)
}
} else if let Some(packages) = add_args.pkg_config {
let mut table = Map::new();
if packages.len() == 1 {
table.insert("pkg-config".to_string(), Value::String(packages[0].clone()));
} else {
table.insert(
"pkg-config".to_string(),
Value::Array(packages.into_iter().map(Value::String).collect()),
);
}
Value::Table(table)
} else if let Some(path) = add_args.path {
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, git, or pkg-config) must be provided");
return 1;
};
let key = format!("dependencies.{}", add_args.name);
if let Err(err) = config.edit(&key, dep_value) {
error!("Failed to update dcr.toml: {err}");
return 1;
}
println!(
" {} dependency `{}` to dcr.toml",
colored("Added", BOLD_GREEN),
add_args.name
);
0
}
/// Parses CLI arguments into an [`AddArgs`] value.
///
/// Expects `<name> [source] [--branch|--tag|--rev ...]`. If `source` is omitted,
/// the package registry is queried for a default version. Source strings may use
/// `path:`, `github:`, `gitlab:`, or `git:` prefixes, or a full git URL.
fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
if args.is_empty() {
error!("Usage: dcr add <name> <source> [--branch <branch> | --tag <tag> | --rev <rev>]");
error!("Sources:");
error!(" github:user/repo -> github.com/user/repo");
error!(" gitlab:user/repo -> gitlab.com/user/repo");
error!(" git:host.com/user/repo -> host.com/user/repo");
error!(" path:./path/to/lib -> local path");
error!(" pkg-config:fmt -> system pkg-config package");
return Err(1);
}
let mut name = String::new();
let source_spec: String;
let mut branch = None;
let mut tag = None;
let mut rev = None;
let mut iter = args.iter();
if let Some(n) = iter.next() {
if n.starts_with("--") {
error!("First argument must be the dependency name");
return Err(1);
}
name = n.clone();
}
if let Some(s) = iter.next() {
if s.starts_with("--") {
error!("Second argument must be the dependency source");
return Err(1);
}
source_spec = s.clone();
} else {
// No source provided — resolve version from the package registry
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()),
pkg_config: None,
});
}
Err(e) => {
error!("Cannot add `{}` without source: {}", name, e);
return Err(1);
}
}
}
while let Some(arg) = iter.next() {
match arg.as_str() {
"--branch" => {
branch = iter.next().cloned();
if branch.is_none() {
error!("--branch requires a value");
return Err(1);
}
}
"--tag" => {
tag = iter.next().cloned();
if tag.is_none() {
error!("--tag requires a value");
return Err(1);
}
}
"--rev" => {
rev = iter.next().cloned();
if rev.is_none() {
error!("--rev requires a value");
return Err(1);
}
}
_ => {
error!("Unknown argument: {arg}");
return Err(1);
}
}
}
let mut path = None;
let mut git = None;
let mut pkg_config = None;
// Map source prefixes / bare URLs to path or git fields
if let Some(p) = source_spec
.strip_prefix("pkg-config:")
.or_else(|| source_spec.strip_prefix("pkg:"))
{
let packages = p
.split(',')
.map(str::trim)
.filter(|name| !name.is_empty())
.map(str::to_string)
.collect::<Vec<_>>();
if packages.is_empty() {
error!("pkg-config source must name at least one package");
return Err(1);
}
pkg_config = Some(packages);
} else if let Some(p) = source_spec.strip_prefix("path:") {
path = Some(p.to_string());
} else if let Some(g) = source_spec.strip_prefix("github:") {
git = Some(format!("https://github.com/{}", g));
} else if let Some(g) = source_spec.strip_prefix("gitlab:") {
git = Some(format!("https://gitlab.com/{}", g));
} else if let Some(g) = source_spec.strip_prefix("git:") {
if g.contains('/') && !g.contains('.') {
// git:user/repo with no host — default to GitHub
git = Some(format!("https://github.com/{}", g));
} else {
// git:host.com/user/repo or an already-qualified URL
let url = if g.starts_with("http") || g.starts_with("git@") {
g.to_string()
} else {
format!("https://{}", g)
};
git = Some(url);
}
} else if source_spec.starts_with("http://")
|| source_spec.starts_with("https://")
|| source_spec.starts_with("git@")
{
git = Some(source_spec);
} else if source_spec.starts_with("./")
|| source_spec.starts_with("../")
|| std::path::Path::new(&source_spec).is_absolute()
{
path = Some(source_spec);
} else {
error!(
"Source must be a path, pkg-config:, a prefixed source (path:, git:, github:, gitlab:), or a full URL",
);
return Err(1);
}
Ok(AddArgs {
name,
path,
git,
branch,
tag,
rev,
version_from_registry: None,
pkg_config,
})
}

387
src/cli/build.rs Normal file
View File

@@ -0,0 +1,387 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::cli::clean::clean;
use crate::cli::flags::parse_build_run_flags;
use crate::cli::r#gen::{ProjectInfo, write_dcr_metadata};
use crate::core::build::builder::artifact::{absolute_artifact_path, resolve_artifact_path};
use crate::core::build::common;
use crate::core::build::{BuildEvent, BuildReporter, BuildRequest, run_build};
use crate::core::build_config::Config;
use crate::utils::build::{
get_config_opt, get_config_str, get_language_with_profile_or_default, get_string_with_profile,
normalize_kind, resolve_artifact_target_dir, resolve_compiler,
};
use crate::utils::fs::{canonicalize_path, find_project_root};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_YELLOW, colored, printc};
use std::io::IsTerminal;
use std::path::Path;
use std::sync::{Arc, atomic::AtomicBool};
pub use crate::core::build::get_build_string_with_profile;
/// Formats a left-aligned status verb with the given style for CLI output.
fn status(verb: &str, style: &str) -> String {
colored(&format!("{verb:<9}"), style)
}
/// CLI build reporter that prints human-readable progress to stderr/stdout.
struct CliReporter;
impl CliReporter {
/// Prints a single status line with a styled verb and free-form rest text.
///
/// # Parameters
/// - `verb`: Short status label (e.g. `"target"`, `"dep"`).
/// - `style`: Color/style constant for the verb.
/// - `rest`: Remaining message text after the verb.
fn line(&self, verb: &str, style: &str, rest: &str) {
eprintln!(" {} {}", status(verb, style), rest);
}
}
impl BuildReporter for CliReporter {
/// Handles build events by printing progress, status lines, and compiler output.
///
/// # Parameters
/// - `event`: Build pipeline event to report to the user.
fn on_event(&mut self, event: BuildEvent<'_>) {
match event {
BuildEvent::TargetStart {
index,
total,
target,
} => {
common::finish_progress_line();
self.line("target", BOLD_CYAN, &format!("{index}/{total} {target}"));
}
BuildEvent::ProjectStart {
name,
profile,
target,
} => {
common::finish_progress_line();
self.line(
"project",
BOLD_CYAN,
&format!("{name} ({profile}, {target})"),
);
}
BuildEvent::DepBuilding { name, version } => {
common::finish_progress_line();
self.line("dep", BOLD_YELLOW, &format!("{name} v{version}"));
}
BuildEvent::DepReady {
name,
version,
rebuilt,
} => {
common::finish_progress_line();
// Only announce deps that were actually rebuilt this run.
if rebuilt {
self.line("ready", BOLD_GREEN, &format!("{name} v{version}"));
}
}
BuildEvent::Compiling { name, version } => {
common::finish_progress_line();
let label = format!(" {} {} v{}", status("compile", BOLD_GREEN), name, version);
// Terminals use an in-place progress line; non-TTY gets a plain log line.
common::set_progress_label(Some(label.clone()));
if !std::io::stderr().is_terminal() {
eprintln!("{label}");
}
}
BuildEvent::Packing { path } => {
common::finish_progress_line();
self.line("pack", BOLD_CYAN, path);
}
BuildEvent::Finished { secs } => {
common::finish_progress_line();
self.line("done", BOLD_GREEN, &format!("in {secs}s"));
}
BuildEvent::CompilerOutput { stream, text } => {
// Pause the progress spinner so compiler text is not overwritten.
common::interrupt_progress_for_output();
if stream == "stderr" {
eprint!("{text}");
} else {
print!("{text}");
}
}
}
}
}
/// Runs the project build command from the CLI.
///
/// Parses flags, locates the project root, optionally cleans, then invokes the
/// core build pipeline. On success, may write `.dcr` metadata and print the
/// main artifact path when requested.
///
/// # Parameters
/// - `args`: Tokens after `dcr build` (profile, target, force, clean, etc.).
///
/// # Returns
/// Process exit code: `0` on success or help, non-zero on failure.
pub fn build(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(
" dcr build [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose] [--print-artifact-path]",
BOLD_CYAN,
);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Compiles the project. Default profile is --debug.");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Build with debug profile (default)");
println!(" --release Build with release profile");
println!(" --target <triple> Cross-compile for the given target");
println!(" --force Force a full rebuild");
println!(" --clean Clean before building");
println!(" --verbose Print detailed build output");
println!(" --workspace <name> Build a specific workspace member");
println!(
" --print-artifact-path Print absolute main artifact path on stdout after success"
);
return 0;
}
// Shared cancel flag so Ctrl-C can stop an in-progress build cooperatively.
let cancel = Arc::new(AtomicBool::new(false));
let cancel_clone = cancel.clone();
ctrlc::set_handler(move || {
cancel_clone.store(true, std::sync::atomic::Ordering::SeqCst);
})
.ok();
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error!("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(dir)) => canonicalize_path(&dir),
Ok(None) => {
error!("dcr.toml file not found");
return 1;
}
Err(_) => {
error!("Failed to find project root");
return 1;
}
};
let mut flags = match parse_build_run_flags(args) {
Ok(v) => v,
Err(code) => return code,
};
// Prefer CLI --target; otherwise fall back to profile target from dcr.toml.
if flags.target.is_none() {
let config_path = root.join("dcr.toml");
if let Ok(config) = Config::open(config_path.to_str().unwrap())
&& !config.is_workspace_only()
{
let bt = get_build_string_with_profile(&config, "target", &flags.profile);
if !bt.is_empty() {
flags.target = Some(bt);
}
}
}
if flags.clean {
let mut clean_args = Vec::new();
clean_args.push(format!("--{}", flags.profile));
let _ = clean(&clean_args);
}
let req = BuildRequest {
profile: flags.profile.clone(),
target: flags.target.clone(),
force: flags.force,
verbose: flags.verbose,
workspace: flags.workspace.clone(),
cancel,
};
let mut reporter = CliReporter;
match run_build(&root, &req, &mut reporter) {
Ok(_) => {
// Persist IDE/metadata and optionally echo the main artifact path.
if let Some(info) =
resolve_post_build_info(&root, &flags.profile, flags.target.as_deref())
{
let _ = write_dcr_metadata(&root, &info);
if flags.print_artifact_path
&& let Some(ref path) = info.artifact_path
{
println!("{path}");
}
} else if flags.print_artifact_path
&& let Some(path) =
fallback_artifact_path(&root, &flags.profile, flags.target.as_deref())
{
println!("{path}");
}
0
}
Err(err) => {
common::finish_progress_line();
error!("{}", err.message);
1
}
}
}
/// Collects project and artifact metadata after a successful build.
///
/// Reads `dcr.toml`, resolves language/compiler/kind/target/output settings for
/// the given profile, and computes absolute artifact and target directory paths.
///
/// # Parameters
/// - `root`: Project root with `dcr.toml`.
/// - `profile`: Build profile used for the just-finished build.
/// - `cli_target`: Optional target from the CLI (overrides config when set).
///
/// # Returns
/// `Some(ProjectInfo)` for a normal package; `None` for workspace-only roots
/// or when required config/paths cannot be resolved.
fn resolve_post_build_info(
root: &Path,
profile: &str,
cli_target: Option<&str>,
) -> Option<ProjectInfo> {
let config = Config::open(root.join("dcr.toml").to_str()?).ok()?;
if config.is_workspace_only() {
return None;
}
let name = get_config_str(&config, "package.name");
let version = get_config_str(&config, "package.version");
let language = get_language_with_profile_or_default(&config, profile);
let standard = get_string_with_profile(&config, "standard", profile);
let cxx_standard = get_string_with_profile(&config, "cxx_standard", profile);
let compiler_s = get_string_with_profile(&config, "compiler", profile);
let kind = normalize_kind(&get_string_with_profile(&config, "kind", profile)).to_string();
let build_target = cli_target
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| get_string_with_profile(&config, "target", profile));
let output_filename_s = get_string_with_profile(&config, "filename", profile);
let output_extension_s = get_string_with_profile(&config, "extension", profile);
let output_filename = if output_filename_s.is_empty() {
None
} else {
Some(output_filename_s)
};
let output_extension = if output_extension_s.is_empty() {
None
} else {
Some(output_extension_s)
};
let tc_cc = get_config_opt(&config, "toolchain.cc");
let tc_cxx = get_config_opt(&config, "toolchain.cxx");
let tc_as = get_config_opt(&config, "toolchain.as");
let resolved_compiler = resolve_compiler(
&language,
&compiler_s,
tc_cc.as_deref(),
tc_cxx.as_deref(),
tc_as.as_deref(),
);
let out_dir = get_string_with_profile(&config, "out_dir", profile);
// Explicit target (CLI or config) changes how the artifact directory is laid out.
let has_explicit = cli_target.is_some_and(|t| !t.is_empty()) || !build_target.trim().is_empty();
let target_dir =
resolve_artifact_target_dir(root, None, profile, &build_target, &out_dir, has_explicit);
let rel = resolve_artifact_path(
&kind,
profile,
&name,
Some(&target_dir),
output_filename.as_deref(),
output_extension.as_deref(),
)?;
let artifact_path = absolute_artifact_path(root, &rel)
.to_string_lossy()
.to_string();
let abs_target_dir = {
let p = Path::new(&target_dir);
let path = if p.is_absolute() {
canonicalize_path(p)
} else {
canonicalize_path(&root.join(p))
};
path.to_string_lossy().into_owned()
};
Some(ProjectInfo {
name,
version,
root: root.to_path_buf(),
profile: profile.to_string(),
language,
standard,
cxx_standard,
compiler: resolved_compiler,
kind: kind.clone(),
target: build_target,
target_dir: Some(abs_target_dir),
artifact_path: Some(artifact_path),
artifact_kind: kind,
sources: vec![],
include_dirs: vec![],
lib_dirs: vec![],
libs: vec![],
cflags: vec![],
ldflags: vec![],
source_roots: vec![],
include_globs: vec![],
exclude_globs: vec![],
output_filename,
output_extension,
out_dir,
debugger: "lldb".to_string(),
moc: None,
uic: None,
rcc: None,
workspace_root: None,
})
}
/// Best-effort absolute main artifact path when full post-build info is unavailable.
///
/// Assumes a binary (`"bin"`) kind and uses the package name plus profile,
/// target, and `out_dir` settings from config.
fn fallback_artifact_path(root: &Path, profile: &str, target: Option<&str>) -> Option<String> {
let config = Config::open(root.join("dcr.toml").to_str()?).ok()?;
let name = get_config_str(&config, "package.name");
if name.is_empty() {
return None;
}
let build_target = target.unwrap_or("").to_string();
let out_dir = get_string_with_profile(&config, "out_dir", profile);
let has_explicit = !build_target.trim().is_empty();
let target_dir =
resolve_artifact_target_dir(root, None, profile, &build_target, &out_dir, has_explicit);
let rel = resolve_artifact_path("bin", profile, &name, Some(&target_dir), None, None)?;
Some(
absolute_artifact_path(root, &rel)
.to_string_lossy()
.to_string(),
)
}

310
src/cli/clean.rs Normal file
View File

@@ -0,0 +1,310 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::core::build_config::Config;
use crate::core::workspace::parse_workspace;
use crate::utils::build::{default_profile_flags, default_target_triple, parse_version_info};
use crate::utils::fs::{check_dir, find_project_root, with_dir};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use glob::glob;
use std::fs;
use std::path::Path;
/// Entry point for the `dcr clean` command.
///
/// Removes build artifacts from the project (and optionally workspace members).
///
/// # Parameters
/// - `args`: Tokens after `dcr clean` (`--debug`/`--release`, `--target`, `--all`).
///
/// # Returns
/// Process exit code: `0` on success, `1` on failure.
pub fn clean(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(
" dcr clean [--debug | --release] [--target <triple>] [--all]",
BOLD_CYAN,
);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Removes build artifacts from the target directory.");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Clean debug artifacts (default)");
println!(" --release Clean release artifacts");
println!(" --target <triple> Clean artifacts for a specific target");
println!(" --all Clean all workspace members");
return 0;
}
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error!("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(dir)) => dir,
Ok(None) => {
error!("dcr.toml file not found");
return 1;
}
Err(_) => {
error!("Failed to find project root");
return 1;
}
};
let flags = match parse_clean_flags(args) {
Ok(v) => v,
Err(msg) => {
error!("{msg}");
return 1;
}
};
// Run cleanup relative to the project root so paths resolve consistently.
match with_dir(&root, || clean_from_root(&root, &flags)) {
Ok(()) => 0,
Err(msg) => {
error!("{msg}");
1
}
}
}
/// Parsed flags controlling which artifacts `clean` should remove.
struct CleanFlags {
/// Optional build profile name (e.g. `debug`, `release`).
profile: Option<String>,
/// Optional target triple to clean under `target/<triple>/...`.
target: Option<String>,
/// When true, also clean each workspace member that has a `target` directory.
all: bool,
}
/// Parses CLI arguments into [`CleanFlags`].
///
/// Recognizes `--all`, `--target <triple>`, and profile flags such as `--debug`
/// / `--release` (validated via [`default_profile_flags`]).
fn parse_clean_flags(args: &[String]) -> Result<CleanFlags, String> {
let mut profile: Option<String> = None;
let mut target: Option<String> = None;
let mut all = false;
let mut iter = args.iter();
while let Some(arg) = iter.next() {
if arg == "--all" {
all = true;
continue;
}
if arg == "--target" {
if let Some(t) = iter.next() {
target = Some(t.clone());
} else {
return Err("--target requires a value".to_string());
}
continue;
}
if arg.starts_with("--") {
// Accept only known profile names (e.g. debug/release), not arbitrary flags.
let candidate = arg.trim_start_matches("--").to_string();
if !default_profile_flags(&candidate).is_empty() {
if profile.is_some() {
return Err("Duplicate profile flag".to_string());
}
profile = Some(candidate);
continue;
}
}
return Err("Unknown argument".to_string());
}
Ok(CleanFlags {
profile,
target,
all,
})
}
/// Cleans the project at `root`, and optionally all workspace members.
///
/// Resolves the effective target triple from flags, `dcr.toml`, or host defaults,
/// then removes the appropriate `target` subdirectories and custom clean paths.
fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
// Prefer CLI --target, then build.target from config, then host defaults on Linux/BSD.
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())
});
let target = match target {
Some(t) => Some(crate::utils::build::normalize_target_os(&t).to_string()),
None if cfg!(target_os = "linux")
|| cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) =>
{
Some(default_target_triple())
}
None => None,
};
if flags.all
&& let Some(workspace) = parse_workspace(
&config,
flags.profile.as_deref().unwrap_or("debug"),
target.as_deref(),
root,
)?
{
for member in &workspace.members {
let member_target = member.path.join("target");
if member_target.is_dir() {
clean_project_at(&member.path, flags.profile.as_deref(), target.as_deref())?;
}
}
}
clean_project_at(root, flags.profile.as_deref(), target.as_deref())
}
/// Removes build artifacts for a single project directory.
///
/// When `profile` is set, only that profile's directory under `target` (optionally
/// scoped by `target` triple) is removed, plus custom paths for that profile.
/// When `profile` is `None`, the entire `target` tree is removed and custom paths
/// are cleaned for both `debug` and `release`.
fn clean_project_at(
project_root: &Path,
profile: Option<&str>,
target: Option<&str>,
) -> Result<(), String> {
with_dir(project_root, || {
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
let project_name = std::env::current_dir()
.ok()
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
.unwrap_or_else(|| "project".to_string());
let items = check_dir(None).map_err(|_| "Failed to read project directory".to_string())?;
if !items.contains(&"dcr.toml".to_string()) {
return Err("dcr.toml file not found".to_string());
}
println!(
" Cleaning project `{}`",
colored(&project_name, BOLD_GREEN)
);
if let Some(profile) = profile {
let target_dir = if let Some(t) = target {
format!("target/{t}/{profile}")
} else {
format!("target/{profile}")
};
let target_items = check_dir(Some("target")).unwrap_or_default();
let parent_dir = target.unwrap_or("");
// Existence check differs: with a triple, look for target/<triple>; otherwise target/<profile>.
let dir_exists = if target.is_some() {
target_items.contains(&parent_dir.to_string())
} else {
target_items.contains(&profile.to_string())
};
if !dir_exists {
warn!("Directory target/{target_dir} not found");
} else {
println!(" Profile: {}", colored(profile, BOLD_GREEN));
if let Some(t) = target {
println!(" Target: {}", colored(t, BOLD_GREEN));
}
let _ = fs::remove_dir_all(&target_dir);
println!(
"{} Removed directory {}",
colored("\n ✔", BOLD_GREEN),
target_dir
);
}
clean_custom_paths(&config, profile)?;
return Ok(());
}
if items.contains(&"target".to_string()) {
let _ = fs::remove_dir_all("target");
println!(
"{} Removed directory target",
colored("\n ✔", BOLD_GREEN)
);
} else {
warn!("Directory target not found");
}
clean_custom_paths(&config, "debug")?;
clean_custom_paths(&config, "release")?;
Ok(())
})
}
/// Deletes extra paths listed under `build.clean` in `dcr.toml` for a profile.
///
/// Each pattern may contain `{profile}` and version placeholders, which are
/// expanded before matching with `glob`. Matched files and directories are removed.
fn clean_custom_paths(config: &Config, profile: &str) -> Result<(), String> {
let patterns = match config.get("build.clean") {
Some(v) => v
.as_array()
.ok_or_else(|| "build.clean must be an array of strings".to_string())?
.iter()
.filter_map(|item| item.as_str())
.map(|s| {
let value = s.replace("{profile}", profile);
substitute_clean_vars(&value, config)
})
.collect::<Vec<String>>(),
None => Vec::new(),
};
if patterns.is_empty() {
return Ok(());
}
for pattern in patterns {
for entry in glob(&pattern).map_err(|err| format!("glob error: {err}"))? {
let path = entry.map_err(|err| format!("glob error: {err}"))?;
if path.is_dir() {
let _ = fs::remove_dir_all(&path);
} else if path.is_file() {
let _ = fs::remove_file(&path);
}
}
}
Ok(())
}
/// Expands version-related placeholders in a clean path pattern.
///
/// Reads `package.version` from `config`, parses it, and applies
/// [`crate::utils::build::substitute_version_vars`].
fn substitute_clean_vars(value: &str, config: &Config) -> String {
let version = config
.get("package.version")
.and_then(|v| v.as_str())
.unwrap_or("");
let info = parse_version_info(version);
crate::utils::build::substitute_version_vars(value, &info)
}

236
src/cli/flag_update.rs Normal file
View File

@@ -0,0 +1,236 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
use serde::Deserialize;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/dexoron/dcr/releases/latest";
/// Metadata for the latest GitHub release of DCR.
#[derive(Debug, Deserialize)]
struct Release {
/// Release tag, typically including a leading `v` (e.g. `v1.2.3`).
tag_name: String,
/// Downloadable assets attached to the release.
assets: Vec<ReleaseAsset>,
}
/// A single downloadable asset from a GitHub release.
#[derive(Debug, Deserialize)]
struct ReleaseAsset {
/// Asset file name as published on GitHub.
name: String,
/// Direct URL used to download the asset.
browser_download_url: String,
}
/// Handles the `dcr --update` flag: checks GitHub for a newer release and
/// replaces the running binary when an update is available.
///
/// # Parameters
/// - `args`: Remaining tokens after `--update` (only `--help` is accepted).
///
/// # Returns
/// `0` on success / already up to date / pacman-managed refuse; `1` on error.
pub fn flag_update(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr --update", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Updates DCR to the latest version from GitHub Releases.");
return 0;
}
if !args.is_empty() {
warn!("Command does not support additional arguments");
return 1;
}
let current_exe = match std::env::current_exe() {
Ok(path) => path,
Err(_) => {
error!("Failed to resolve current binary path");
return 1;
}
};
// Refuse self-update when the binary is owned by pacman/AUR.
if let Some(package_name) = pacman_owned_package(&current_exe) {
warn!("This dcr binary is managed by pacman/AUR");
println!(
"Update via package manager: yay/paru -Syu {package_name} or sudo pacman -Syu {package_name}"
);
return 0;
}
let current_version = env!("CARGO_PKG_VERSION");
let target = env!("DCR_TARGET");
let release = match fetch_latest_release() {
Ok(release) => release,
Err(err) => {
error!("Failed to check for updates: {err}");
return 1;
}
};
// Normalize the tag by stripping a leading `v` before comparison.
let latest_version = release.tag_name.trim_start_matches('v');
if latest_version == current_version {
println!("Latest version is already installed: {current_version}");
return 0;
}
let candidate_names = asset_candidates(target);
let Some(asset) = release
.assets
.iter()
.find(|asset| candidate_names.iter().any(|name| name == &asset.name))
else {
error!("Binary for target {target} not found");
return 1;
};
let bytes = match download_asset(&asset.browser_download_url) {
Ok(bytes) => bytes,
Err(err) => {
error!("Failed to download update: {err}");
return 1;
}
};
let temp_path = temp_binary_path(&current_exe);
if fs::write(&temp_path, &bytes).is_err() {
error!("Failed to write temporary binary");
return 1;
}
set_executable_permissions(&temp_path);
// Replace the running executable in place, then clean up the temp file.
if self_replace::self_replace(&temp_path).is_err() {
let _ = fs::remove_file(&temp_path);
error!("Failed to replace current binary");
return 1;
}
let _ = fs::remove_file(&temp_path);
println!("Update completed: {current_version} -> {latest_version}");
0
}
/// Fetches the latest DCR release metadata from the GitHub Releases API.
fn fetch_latest_release() -> Result<Release, String> {
let response = ureq::get(LATEST_RELEASE_URL)
.set("User-Agent", "dcr-updater")
.call()
.map_err(|e| match e {
ureq::Error::Status(code, _) => format!("GitHub API returned status {code}"),
ureq::Error::Transport(e) => format!("GitHub API request failed: {e}"),
})?;
response
.into_json::<Release>()
.map_err(|_| "GitHub API response has an unexpected format".to_string())
}
/// Downloads a release asset from the given URL into memory.
fn download_asset(url: &str) -> Result<Vec<u8>, String> {
let response = ureq::get(url)
.set("User-Agent", "dcr-updater")
.call()
.map_err(|e| match e {
ureq::Error::Status(code, _) => format!("Download returned status {code}"),
ureq::Error::Transport(e) => format!("Download request failed: {e}"),
})?;
let mut reader = response.into_reader();
let mut data = Vec::new();
reader
.read_to_end(&mut data)
.map_err(|_| "Failed to read downloaded data".to_string())?;
Ok(data)
}
/// Builds the list of expected asset file names for the given build target.
fn asset_candidates(target: &str) -> Vec<String> {
let mut names = vec![format!("dcr-{target}")];
// Windows releases may publish the binary with an `.exe` suffix.
if target.contains("-windows-") || target.ends_with("-windows") {
names.push(format!("dcr-{target}.exe"));
}
names
}
/// Creates a unique temporary path next to the current executable for the download.
fn temp_binary_path(current_exe: &Path) -> PathBuf {
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|value| value.as_millis())
.unwrap_or(0);
let mut extension = format!("new-{stamp}");
if cfg!(windows) {
extension.push_str(".exe");
}
current_exe.with_extension(extension)
}
/// Sets executable permissions on Unix; no-op on other platforms.
fn set_executable_permissions(_path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = fs::metadata(_path) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = fs::set_permissions(_path, perms);
}
}
}
/// On Linux, returns the pacman package that owns `path`, if any.
#[cfg(target_os = "linux")]
fn pacman_owned_package(path: &Path) -> Option<String> {
let output = Command::new("pacman").arg("-Qoq").arg(path).output().ok()?;
if !output.status.success() {
return None;
}
let package_name = String::from_utf8_lossy(&output.stdout).trim().to_string();
if package_name.is_empty() {
return None;
}
Some(package_name)
}
/// Non-Linux stub: package-manager ownership is not checked.
#[cfg(not(target_os = "linux"))]
fn pacman_owned_package(_path: &Path) -> Option<String> {
None
}

183
src/cli/flags.rs Normal file
View File

@@ -0,0 +1,183 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::config::{PROFILE, flags};
/// Flags shared by the `build` and `run` CLI commands.
pub struct BuildRunFlags {
/// Build profile name (for example `debug` or `release`).
pub profile: String,
/// Optional target triple or platform name.
pub target: Option<String>,
/// Optional workspace member name (`--workspace <name>`).
pub workspace: Option<String>,
/// Force a rebuild even when artifacts appear up to date.
pub force: bool,
/// Clean build outputs before building.
pub clean: bool,
/// Enable verbose logging during the build or run.
pub verbose: bool,
/// Print the final artifact path to the user.
pub print_artifact_path: bool,
/// Arguments forwarded to the binary after a `--` separator.
pub bin_args: Vec<String>,
}
/// Splits CLI arguments at the first `--` separator.
///
/// Everything before `--` is treated as DCR flags; everything after is
/// forwarded to the built binary as `bin_args`.
///
/// # Parameters
/// - `args`: Full argument list for the command (without the program name).
///
/// # Returns
/// `(dcr_args, bin_args)`. If `--` is absent, `bin_args` is empty.
pub fn split_double_dash(args: &[String]) -> (&[String], &[String]) {
match args.iter().position(|a| a == "--") {
Some(i) => (&args[..i], &args[i + 1..]),
None => (args, &[]),
}
}
/// Parses build/run flags from a list of CLI arguments.
///
/// Recognizes boolean flags (`--force`, `--clean`, `--verbose`,
/// `--print-artifact-path`), value flags (`--workspace`, `--target`), and
/// profile names registered via [`flags`]. Arguments after `--` become
/// [`BuildRunFlags::bin_args`].
///
/// # Parameters
/// - `args`: CLI arguments for build/run (may include `--`).
///
/// # Returns
/// - `Ok(BuildRunFlags)` on success.
/// - `Err(1)` for unknown flags, missing values, or a duplicate profile.
pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
let (dcr_args, bin_args_slice) = split_double_dash(args);
let mut profile = PROFILE.to_string();
let mut target = None;
let mut workspace = None;
let mut force = false;
let mut clean = false;
let mut verbose = false;
let mut print_artifact_path = false;
let mut iter = dcr_args.iter();
while let Some(arg) = iter.next() {
// DCR build/run options are long flags only (`--name`).
if !arg.starts_with("--") {
warn!("Unknown argument");
return Err(1);
}
let candidate = arg.trim_start_matches("--");
if candidate == "force" {
force = true;
continue;
}
if candidate == "clean" {
clean = true;
continue;
}
if candidate == "verbose" {
verbose = true;
continue;
}
if candidate == "print-artifact-path" {
print_artifact_path = true;
continue;
}
if candidate == "workspace" {
if let Some(w) = iter.next() {
workspace = Some(w.clone());
} else {
warn!("--workspace requires a value");
return Err(1);
}
continue;
}
if candidate == "target" {
if let Some(t) = iter.next() {
target = Some(t.clone());
} else {
warn!("--target requires a value");
return Err(1);
}
continue;
}
// Profile names come from config::flags; only one profile may be set.
if flags(candidate).is_some() {
if profile != PROFILE {
warn!("Duplicate profile flag");
return Err(1);
}
profile = candidate.to_string();
continue;
}
warn!("Unknown build flag");
return Err(1);
}
Ok(BuildRunFlags {
profile,
target,
workspace,
force,
clean,
verbose,
print_artifact_path,
bin_args: bin_args_slice.to_vec(),
})
}
#[cfg(test)]
mod tests {
use super::*;
/// Converts string literals into owned `String` CLI arguments for tests.
fn s(args: &[&str]) -> Vec<String> {
args.iter().map(|a| a.to_string()).collect()
}
/// Checks that without `--`, the full list stays on the DCR side.
#[test]
fn split_double_dash_empty_tail() {
let args = s(&["--release", "--force"]);
let (left, right) = split_double_dash(&args);
assert_eq!(left, &s(&["--release", "--force"]));
assert!(right.is_empty());
}
/// Checks that arguments after `--` are split into the bin-args slice.
#[test]
fn split_double_dash_with_bin_args() {
let args = s(&["--release", "--", "--test_help", "x"]);
let (left, right) = split_double_dash(&args);
assert_eq!(left, &s(&["--release"]));
assert_eq!(right, &s(&["--test_help", "x"]));
}
/// Checks profile parsing and that bin args after `--` are forwarded.
#[test]
fn parse_run_flags_forwards_after_double_dash() {
let flags = parse_build_run_flags(&s(&["--release", "--", "--test_help"])).unwrap();
assert_eq!(flags.profile, "release");
assert_eq!(flags.bin_args, s(&["--test_help"]));
}
}

100
src/cli/fmt.rs Normal file
View File

@@ -0,0 +1,100 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use glob::glob;
use std::process::Command;
/// Formats all C/C++ source files using clang-format.
///
/// Scans the `src/` and `tests/` directories recursively for files with common
/// C/C++ extensions and formats them in place with clang-format.
///
/// If the first argument is `--help`, prints usage information and returns 0.
pub fn fmt(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr fmt", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Formats all C/C++ source files using clang-format.");
println!(" Scans src/ and tests/ directories recursively.");
return 0;
}
// Collect all matching files from src/ and tests/ using glob patterns
let patterns = [
"src/**/*.c",
"src/**/*.cpp",
"src/**/*.cxx",
"src/**/*.cc",
"src/**/*.h",
"src/**/*.hpp",
"src/**/*.hxx",
"src/**/*.hh",
"tests/**/*.c",
"tests/**/*.cpp",
"tests/**/*.cxx",
"tests/**/*.cc",
"tests/**/*.h",
"tests/**/*.hpp",
"tests/**/*.hxx",
"tests/**/*.hh",
];
let mut files = Vec::new();
for pattern in patterns {
if let Ok(paths) = glob(pattern) {
for path in paths.flatten() {
if let Some(s) = path.to_str() {
files.push(s.to_string());
}
}
}
}
if files.is_empty() {
println!(" {} No files to format", colored("Format", BOLD_GREEN));
return 0;
}
// Format the collected files
println!(
" {} {} files",
colored("Formatting", BOLD_GREEN),
files.len()
);
let status = Command::new("clang-format").arg("-i").args(&files).status();
match status {
Ok(s) if s.success() => {
println!(" {} successful", colored("Format", BOLD_GREEN));
0
}
Ok(s) => {
error!("clang-format failed with status: {s}");
1
}
Err(e) => {
error!("Failed to execute clang-format: {e}");
1
}
}
}

1769
src/cli/gen.rs Normal file

File diff suppressed because it is too large Load Diff

66
src/cli/help.rs Normal file
View File

@@ -0,0 +1,66 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
/// Displays the help information for the DCR CLI tool.
///
/// Prints usage, commands, flags, options, examples, and a tip.
///
/// # Returns
/// Always `0`.
pub fn help() -> i32 {
println!("DCR (Dexoron Cargo Realization)");
println!("C project manager inspired by Cargo.");
println!();
printc("USAGE:", BOLD_GREEN);
printc(" dcr <command> [options]", BOLD_CYAN);
println!();
printc("COMMANDS:", BOLD_GREEN);
println!(" new <name> Create a new project");
println!(" init Initialize the current directory as a project");
println!(" build [--profile] Build the project (default: --debug)");
println!(" run [--profile] Build and run the project (default: --debug)");
println!(" add <name> ... Add a dependency to dcr.toml");
println!(" tree Display the dependency tree");
println!(" test Run project tests (alias: tests)");
println!(" fmt Format all C/C++ source files using clang-format");
println!(" lint Run clang-tidy on C/C++ sources");
println!(" setup Show configured package registries");
println!(" clean Remove the target directory");
println!(" gen <subcommand> Generate IDE integration files");
printc("FLAGS:", BOLD_GREEN);
println!(" --help Show command help");
println!(" --update Update dcr to the latest version");
println!(" --version Show dcr version");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Use debug profile");
println!(" --release Use release profile");
println!(" --force Force rebuild (build/run)");
println!(" --clean Clean before build (build/run)");
println!(" --all Clean all workspace members (clean)");
println!();
printc("EXAMPLES:", BOLD_GREEN);
printc(" dcr new hello", BOLD_CYAN);
printc(" dcr build --release", BOLD_CYAN);
printc(" dcr run --debug", BOLD_CYAN);
println!();
printc("TIP:", BOLD_GREEN);
println!(" Run 'dcr <command> --help' for command-specific help.");
0
}

170
src/cli/init.rs Normal file
View File

@@ -0,0 +1,170 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::config::FILE_MAIN_C;
use crate::core::build_config::{Config, validate_package_name};
use crate::core::vcs::VcsKind;
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::fs;
use std::io::Write;
/// Initializes the current working directory as a new DCR project.
///
/// Verifies that the current directory is empty, sets up the project structure by creating
/// `dcr.toml` and `src/main.c`, and configures version control (Git) integration if applicable.
pub fn init(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr init [--vcs <git|none>]", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Initializes the current directory as a DCR project.");
println!(" The directory must be empty.");
return 0;
}
// Parse options (--vcs) and collect positional arguments.
let mut vcs_str = None;
let mut clean_args = Vec::new();
let mut iter = args.iter();
while let Some(arg) = iter.next() {
if arg == "--vcs" {
if let Some(val) = iter.next() {
vcs_str = Some(val.clone());
} else {
error!("--vcs requires a value");
return 1;
}
} else if let Some(stripped) = arg.strip_prefix("--vcs=") {
vcs_str = Some(stripped.to_string());
} else {
clean_args.push(arg.clone());
}
}
if !clean_args.is_empty() {
warn!("Command does not support additional arguments");
return 1;
}
// Resolve target project name from current directory name and enforce empty directory check.
let items = check_dir(None).unwrap_or_default();
let project_name = std::env::current_dir()
.ok()
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
.unwrap_or_else(|| "project".to_string());
if !items.is_empty() {
error!("Directory not empty");
return 1;
}
if let Err(e) = validate_package_name(&project_name) {
error!(
"Invalid project name `{}`: {e}",
colored(&project_name, BOLD_CYAN),
);
return 1;
}
let cwd = std::env::current_dir()
.map(|v| v.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string());
println!("Initializing the project in {cwd}");
// Create and initialize default manifest (dcr.toml).
if Config::create("./dcr.toml", Some(&project_name)).is_err() {
error!("Failed to create dcr.toml");
return 1;
}
println!(
" {} Created file {}",
colored("✔", BOLD_GREEN),
colored("dcr.toml", BOLD_CYAN)
);
// Create src directory and write template main.c file.
if fs::create_dir("src").is_err() {
error!("Failed to create src/");
return 1;
}
let mut main_c = match fs::File::create("./src/main.c") {
Ok(file) => file,
Err(_) => {
error!("Failed to create src/main.c");
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
error!("Failed to write src/main.c");
return 1;
}
println!(
" {} Created file {}",
colored("✔", BOLD_GREEN),
colored("src/main.c", BOLD_CYAN)
);
// Resolve target VCS provider or detect existing repositories.
let mut vcs_kind = VcsKind::Git;
if let Some(ref vcs_val) = vcs_str {
match VcsKind::parse(vcs_val) {
Ok(kind) => vcs_kind = kind,
Err(e) => {
error!("{e}");
return 1;
}
}
} else if let Ok(cwd_path) = std::env::current_dir()
&& crate::core::vcs::find_existing_vcs(&cwd_path).is_some()
{
vcs_kind = VcsKind::None;
}
if vcs_kind == VcsKind::Git {
if crate::utils::git::is_git_available() {
let project_path = std::path::Path::new(".");
if let Err(e) = crate::core::vcs::init_vcs(vcs_kind, project_path) {
warn!("Failed to initialize git repository: {e}");
} else {
println!(
" {} Initialized git repository",
colored("✔", BOLD_GREEN)
);
}
} else {
warn!(
"Git is not installed or not found in PATH. Skipping Git repository initialization.",
);
}
}
if let Err(e) = crate::utils::fs::ensure_gitignore_has_dcr(std::path::Path::new(".")) {
warn!("Failed to update .gitignore: {e}");
}
println!(
"Project `{}` successfully created\n",
colored(project_name.as_str(), BOLD_GREEN)
);
printc("Next step:", BOLD_GREEN);
printc(" dcr run", BOLD_CYAN);
0
}

195
src/cli/lint.rs Normal file
View File

@@ -0,0 +1,195 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::core::build::common;
use crate::core::build_config::Config;
use crate::utils::build::{get_bool_with_profile, get_list_with_profile};
use crate::utils::fs::find_project_root;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_YELLOW, colored, printc};
use std::path::{Path, PathBuf};
use std::process::Command;
/// Runs clang-tidy on all C/C++ source files.
///
/// Scans source roots (configurable via build.roots) and tests/ directory.
pub fn lint(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr lint [--fix]", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Runs clang-tidy on all C/C++ source files.");
println!(" Scans source roots (configurable via build.roots) and tests/ directory.");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --fix Apply clang-tidy suggestions automatically");
return 0;
}
let do_fix = args.iter().any(|a| a == "--fix");
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error!("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(r)) => r,
Ok(None) => {
error!("dcr.toml not found");
return 1;
}
Err(_) => {
error!("Failed to find project root");
return 1;
}
};
let config = match Config::open(&root.join("dcr.toml").to_string_lossy()) {
Ok(c) => c,
Err(e) => {
error!("Failed to load dcr.toml: {e}");
return 1;
}
};
let profile = "debug";
let build_roots = get_list_with_profile(&config, "roots", profile);
let src_disable = get_bool_with_profile(&config, "src_disable", profile, false);
// Construct source roots from config
let mut source_roots: Vec<String> = Vec::new();
for raw in &build_roots {
let p = Path::new(raw);
if p.is_absolute() {
source_roots.push(raw.clone());
} else {
source_roots.push(root.join(p).to_string_lossy().to_string());
}
}
if !src_disable && source_roots.is_empty() {
source_roots.push(root.join("src").to_string_lossy().to_string());
}
let cxx_extensions = ["c", "cpp", "cxx", "cc"];
let exclude_dirs: Vec<PathBuf> = Vec::new();
let include_paths: Vec<String> = Vec::new();
let roots: Vec<PathBuf> = source_roots.iter().map(PathBuf::from).collect();
let mut files =
match common::collect_sources(&roots, &cxx_extensions, &exclude_dirs, &include_paths) {
Ok(f) => f,
Err(e) => {
error!("Failed to collect source files: {e}");
return 1;
}
};
let tests_dir = root.join("tests");
if tests_dir.is_dir() {
let test_roots = vec![tests_dir];
if let Ok(mut test_files) =
common::collect_sources(&test_roots, &cxx_extensions, &exclude_dirs, &include_paths)
{
files.append(&mut test_files);
}
}
files.sort();
if files.is_empty() {
println!(" {} No files to lint", colored("Lint", BOLD_GREEN));
return 0;
}
println!(
" {} {} files {}",
colored("Linting", BOLD_GREEN),
files.len(),
if do_fix { "(with --fix)" } else { "" }
);
let mut total_issues = 0u32;
let mut failed = 0u32;
// Execute clang-tidy for each file and count issues
for file in &files {
let mut cmd = Command::new("clang-tidy");
if do_fix {
cmd.arg("--fix");
}
cmd.arg("--quiet");
cmd.arg(file);
let output = match cmd.output() {
Ok(o) => o,
Err(e) => {
error!("Failed to execute clang-tidy: {e}");
return 1;
}
};
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let combined = format!("{}{}", stdout, stderr);
let issues: Vec<&str> = combined
.lines()
.filter(|l| l.contains("warning:") || l.contains("error:"))
.collect();
let issue_count = issues.len();
if issue_count > 0 {
total_issues += issue_count as u32;
failed += 1;
print!(" {} ", colored("×", BOLD_YELLOW));
println!("{} ({} issues)", file, issue_count);
for issue in &issues {
println!(" {}", issue);
}
} else {
print!(" {} ", colored("✓", BOLD_GREEN));
println!("{}", file);
}
}
let total = files.len();
let passed = total - failed as usize;
if failed == 0 {
println!(
" {} {} files, no issues",
colored("Lint", BOLD_GREEN),
total
);
} else {
println!(
" {} {}/{} files passed, {} issues found",
colored("Lint", BOLD_YELLOW),
passed,
total,
total_issues
);
}
0
}

35
src/cli/mod.rs Normal file
View File

@@ -0,0 +1,35 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/// DCR CLI module.
///
/// This module declares submodules for all DCR commands.
pub mod add;
pub mod build;
pub mod clean;
pub mod flag_update;
pub mod flags;
pub mod fmt;
pub mod r#gen;
pub mod help;
pub mod init;
pub mod lint;
pub mod new;
pub mod run;
pub mod setup;
pub mod test;
pub mod tree;

192
src/cli/new.rs Normal file
View File

@@ -0,0 +1,192 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::config::FILE_MAIN_C;
use crate::core::build_config::{Config, validate_package_name};
use crate::core::vcs::VcsKind;
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::fs;
use std::io::Write;
/// Executes the `dcr new` command to scaffold a new C/C++ project directory structure.
///
/// Handles command-line argument parsing, directory creation, default configuration generation (`dcr.toml`),
/// template source file generation (`src/main.c`), and initial VCS setup.
pub fn new(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr new <name> [--vcs <git|none>]", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Creates a new C/C++ project with the given name.");
println!(" The name may only contain ASCII letters, digits, '_' and '-'.");
return 0;
}
let items = check_dir(None).unwrap_or_default();
// Parse CLI options (--vcs) while extracting positional arguments for the project name.
let mut vcs_str = None;
let mut clean_args = Vec::new();
let mut iter = args.iter();
while let Some(arg) = iter.next() {
if arg == "--vcs" {
if let Some(val) = iter.next() {
vcs_str = Some(val.clone());
} else {
error!("--vcs requires a value");
return 1;
}
} else if let Some(stripped) = arg.strip_prefix("--vcs=") {
vcs_str = Some(stripped.to_string());
} else {
clean_args.push(arg.clone());
}
}
if clean_args.is_empty() {
error!("Project name not specified");
return 1;
}
if clean_args.len() > 1 {
warn!("Command does not support additional arguments");
return 1;
}
let project_name = &clean_args[0];
println!(
"Creating a Project `{}`...",
colored(project_name, BOLD_CYAN)
);
if let Err(e) = validate_package_name(project_name) {
error!(
"Invalid project name `{}`: {e}",
colored(project_name, BOLD_CYAN),
);
return 1;
}
if items.contains(project_name) {
error!(
"Directory `{}` already exists\n",
colored(project_name, BOLD_CYAN)
);
printc("Hint:", BOLD_CYAN);
println!(
" Use `{}` to initialize an existing project\n or specify a different project name",
colored("dcr init", BOLD_CYAN)
);
return 1;
}
if fs::create_dir(project_name).is_err() {
error!("Failed to create directory");
return 1;
}
println!(
" {} Directory created {}",
colored("✔", BOLD_GREEN),
project_name
);
// Initialize and write project configuration manifest (dcr.toml).
let toml_path = format!("./{project_name}/dcr.toml");
if Config::create(&toml_path, Some(project_name)).is_err() {
error!("Failed to create dcr.toml");
return 1;
}
println!(
" {} Created file {}",
colored("✔", BOLD_GREEN),
colored("dcr.toml", BOLD_CYAN)
);
// Generate src directory and populate initial main.c file.
if fs::create_dir_all(format!("./{project_name}/src")).is_err() {
error!("Failed to create src/");
return 1;
}
let main_c_path = format!("./{project_name}/src/main.c");
let mut main_c = match fs::File::create(&main_c_path) {
Ok(file) => file,
Err(_) => {
error!("Failed to create src/main.c");
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
error!("Failed to write src/main.c");
return 1;
}
println!(
" {} Created file {}",
colored("✔", BOLD_GREEN),
colored("src/main.c", BOLD_CYAN)
);
// Resolve target VCS mode based on CLI flags or parent directory state.
let mut vcs_kind = VcsKind::Git;
if let Some(ref vcs_val) = vcs_str {
match VcsKind::parse(vcs_val) {
Ok(kind) => vcs_kind = kind,
Err(e) => {
error!("{e}");
return 1;
}
}
} else if let Ok(cwd) = std::env::current_dir()
&& crate::core::vcs::find_existing_vcs(&cwd).is_some()
{
vcs_kind = VcsKind::None;
}
if vcs_kind == VcsKind::Git {
if crate::utils::git::is_git_available() {
let project_path = std::path::Path::new(project_name);
if let Err(e) = crate::core::vcs::init_vcs(vcs_kind, project_path) {
warn!("Failed to initialize git repository: {e}");
} else {
println!(
" {} Initialized git repository",
colored("✔", BOLD_GREEN)
);
}
} else {
warn!(
"Git is not installed or not found in PATH. Skipping Git repository initialization.",
);
}
}
let project_path = std::path::Path::new(project_name);
if let Err(e) = crate::utils::fs::ensure_gitignore_has_dcr(project_path) {
warn!("Failed to update .gitignore: {e}");
}
println!(
"Project `{}` successfully created\n",
colored(project_name, BOLD_GREEN)
);
printc("Next step:", BOLD_GREEN);
printc(&format!(" cd {}\n dcr run", project_name), BOLD_CYAN);
0
}

435
src/cli/run.rs Normal file
View File

@@ -0,0 +1,435 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::cli::build::build;
use crate::cli::flags::parse_build_run_flags;
use crate::core::build_config::Config;
use crate::core::runner::run_binary;
use crate::utils::build::{normalize_target_os, parse_version_info, substitute_vars};
use crate::utils::fs::find_project_root;
use crate::utils::fs::with_dir;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::path::Path;
use std::process::Command;
/// Retrieves the run command from the config, preferring target-specific,
/// then profile-specific, then the base `run.cmd`.
fn get_run_cmd(
config: &Config,
profile: &str,
target: Option<&str>,
version: &str,
) -> Option<String> {
let base = config.get("run.cmd").and_then(|v| v.as_str());
let target_cmd = if let Some(t) = target {
let normalized_t = normalize_target_os(t);
config
.get(&format!("run.{}.cmd", normalized_t))
.or_else(|| config.get(&format!("run.{}.cmd", t)))
.and_then(|v| v.as_str())
} else {
None
};
let profile_cmd = config
.get(&format!("run.{}.cmd", profile))
.and_then(|v| v.as_str());
let cmd = target_cmd.or(profile_cmd).or(base)?;
let trimmed = cmd.trim();
if trimmed.is_empty() {
None
} else {
Some(substitute_run_vars(trimmed, profile, version))
}
}
/// Handles the `dcr run` subcommand: parses flags, finds the project root,
/// builds if needed, and runs the resulting binary or configured command.
///
/// # Parameters
/// - `args`: Tokens after `dcr run` (build flags; after `--` → binary args).
///
/// # Returns
/// Process exit code from the built binary / `run.cmd`, or non-zero on build/setup failure.
pub fn run(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(
" dcr run [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose] [-- <args>...]",
BOLD_CYAN,
);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Builds and runs the project. Only available for kind = \"bin\".");
println!(" Arguments after `--` are passed to the built binary (cargo-style).");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Run with debug profile (default)");
println!(" --release Run with release profile");
println!(" --target <triple> Cross-compile for the given target");
println!(" --force Force a full rebuild");
println!(" --clean Clean before building");
println!(" --verbose Print detailed build output");
println!(" -- <args>... Arguments forwarded to the binary");
return 0;
}
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error!("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(dir)) => dir,
Ok(None) => {
error!("dcr.toml file not found");
return 1;
}
Err(_) => {
error!("Failed to find project root");
return 1;
}
};
let config = match with_dir(&root, || {
Config::open("./dcr.toml").map_err(|err| err.to_string())
}) {
Ok(cfg) => cfg,
Err(err) => {
error!("{err}");
return 1;
}
};
let flags = match parse_build_run_flags(args) {
Ok(v) => v,
Err(_) => return 1,
};
if config.is_workspace_only() {
if let Some(cmd) = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), "") {
let build_status = build(&args_for_build(&flags));
if build_status == 0 {
let display = display_run_cmd(&cmd, &flags.bin_args);
println!(
" {} {}",
colored(&format!("{:<9}", "run"), BOLD_GREEN),
display
);
return run_shell_with_args(&cmd, &flags.bin_args);
}
return build_status;
}
let ws = match crate::core::workspace::parse_workspace(
&config,
&flags.profile,
flags.target.as_deref(),
&root,
) {
Ok(Some(ws)) => ws,
Ok(None) => {
error!("Workspace root has no members defined");
return 1;
}
Err(e) => {
error!("{e}");
return 1;
}
};
let member = match &flags.workspace {
Some(name) => ws.members.iter().find(|m| m.name == *name),
None => ws.main_member(),
};
let member = match member {
Some(m) => m,
None => {
if let Some(name) = &flags.workspace {
error!("Workspace member '{name}' not found");
} else {
error!("No workspace member to run (set `main = true` on one member)");
}
return 1;
}
};
// Build and run from the member's directory
return match with_dir(&member.path, || {
run_project(&member.path, &flags, Some(root.as_path()))
}) {
Ok(code) => code,
Err(e) => {
error!("{e}");
1
}
};
}
match run_project(&root, &flags, None) {
Ok(code) => code,
Err(e) => {
error!("{e}");
1
}
}
}
/// Builds and runs a single project at `root` (optionally as a workspace member).
fn run_project(
root: &Path,
flags: &crate::cli::flags::BuildRunFlags,
workspace_root: Option<&Path>,
) -> Result<i32, String> {
let config =
Config::open(root.join("dcr.toml").to_str().unwrap()).map_err(|err| err.to_string())?;
let project_name: &str = config
.get("package.name")
.and_then(|v| v.as_str())
.unwrap_or("");
let mut target = flags.target.clone();
if target.is_none() {
let bt =
crate::cli::build::get_build_string_with_profile(&config, "target", &flags.profile);
if !bt.is_empty() {
target = Some(bt);
}
}
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 out_dir =
crate::cli::build::get_build_string_with_profile(&config, "out_dir", &flags.profile);
let build_target_str = target.clone().unwrap_or_default();
let has_explicit = target.as_ref().is_some_and(|t| !t.trim().is_empty());
let normalized_target_dir = Some(crate::utils::build::resolve_artifact_target_dir(
root,
workspace_root,
&flags.profile,
&build_target_str,
&out_dir,
has_explicit,
));
let version = config
.get("package.version")
.and_then(|v| v.as_str())
.unwrap_or("");
let run_cmd = get_run_cmd(&config, &flags.profile, target.as_deref(), version);
let kind = build_kind.trim();
if run_cmd.is_none()
&& (kind == "staticlib"
|| kind == "sharedlib"
|| kind == "efi"
|| kind == "elf"
|| kind == "flat-bin")
{
return Err("Cannot run library build".to_string());
}
let build_status = if let Some(wroot) = workspace_root {
with_dir(wroot, || {
Ok(build(&args_for_workspace_build(flags, project_name)))
})
.unwrap_or(1)
} else {
build(&args_for_build(flags))
};
let bin_path = crate::platform::bin_path(
&flags.profile,
project_name,
normalized_target_dir.as_deref(),
);
if build_status == 0 {
if let Some(cmd) = run_cmd {
let display = display_run_cmd(&cmd, &flags.bin_args);
println!(
" {} {}",
colored(&format!("{:<9}", "run"), BOLD_GREEN),
display
);
return Ok(run_shell_with_args(&cmd, &flags.bin_args));
}
let display = display_bin_run(&bin_path, &flags.bin_args);
println!(
" {} {}",
colored(&format!("{:<9}", "run"), BOLD_GREEN),
display
);
return Ok(run_binary(
project_name,
&flags.profile,
normalized_target_dir.as_deref(),
&flags.bin_args,
));
}
let fallback_code = if let Some(cmd) = run_cmd {
run_shell_with_args(&cmd, &flags.bin_args)
} else {
run_binary(
project_name,
&flags.profile,
normalized_target_dir.as_deref(),
&flags.bin_args,
)
};
if fallback_code != 1 {
return Ok(fallback_code);
}
Err("Fix errors in the code to run the project".to_string())
}
/// Builds the list of arguments for the build command based on the provided flags.
fn args_for_build(flags: &crate::cli::flags::BuildRunFlags) -> Vec<String> {
let mut args = Vec::new();
args.push(format!("--{}", flags.profile));
if let Some(ref target) = flags.target {
args.push("--target".to_string());
args.push(target.clone());
}
if let Some(ref name) = flags.workspace {
args.push("--workspace".to_string());
args.push(name.clone());
}
if flags.force {
args.push("--force".to_string());
}
if flags.clean {
args.push("--clean".to_string());
}
if flags.verbose {
args.push("--verbose".to_string());
}
args
}
/// Constructs build arguments specifically for a workspace member.
fn args_for_workspace_build(
flags: &crate::cli::flags::BuildRunFlags,
member_name: &str,
) -> Vec<String> {
let mut args = Vec::new();
args.push(format!("--{}", flags.profile));
if let Some(ref target) = flags.target {
args.push("--target".to_string());
args.push(target.clone());
}
args.push("--workspace".to_string());
args.push(member_name.to_string());
if flags.force {
args.push("--force".to_string());
}
if flags.clean {
args.push("--clean".to_string());
}
if flags.verbose {
args.push("--verbose".to_string());
}
args
}
/// Executes the given shell command and returns its exit code.
fn run_shell(cmd: &str) -> i32 {
let status = if cfg!(target_os = "windows") {
Command::new("cmd").arg("/C").arg(cmd).status()
} else {
Command::new("sh").arg("-c").arg(cmd).status()
};
match status {
Ok(s) if s.success() => 0,
Ok(s) => s.code().unwrap_or(1),
Err(_) => 1,
}
}
/// Runs the command, appending escaped binary arguments if provided.
fn run_shell_with_args(cmd: &str, bin_args: &[String]) -> i32 {
if bin_args.is_empty() {
return run_shell(cmd);
}
let mut full = cmd.to_string();
for arg in bin_args {
full.push(' ');
full.push_str(&shell_escape(arg));
}
run_shell(&full)
}
/// Formats the run command string for console output, appending arguments if any.
fn display_run_cmd(cmd: &str, bin_args: &[String]) -> String {
if bin_args.is_empty() {
return cmd.to_string();
}
let mut display = cmd.to_string();
for arg in bin_args {
display.push(' ');
display.push_str(arg);
}
display
}
/// Formats the binary path for console display, appending arguments if any.
fn display_bin_run(bin_path: &str, bin_args: &[String]) -> String {
if bin_args.is_empty() {
return bin_path.to_string();
}
let mut display = bin_path.to_string();
for arg in bin_args {
display.push(' ');
display.push_str(arg);
}
display
}
/// Escapes the argument for safe shell execution, using double quotes on Windows and single quotes on Unix.
fn shell_escape(arg: &str) -> String {
if cfg!(target_os = "windows") {
// cmd.exe: wrap in double quotes and escape embedded quotes.
let escaped = arg.replace('"', "\\\"");
format!("\"{escaped}\"")
} else {
// POSIX sh: single-quote and escape embedded single quotes as '\''.
let mut out = String::from("'");
for ch in arg.chars() {
if ch == '\'' {
out.push_str("'\\''");
} else {
out.push(ch);
}
}
out.push('\'');
out
}
}
/// Replaces version info and profile in the command template.
fn substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String {
let info = parse_version_info(version);
substitute_vars(cmd, &info, profile, "")
}

47
src/cli/setup.rs Normal file
View File

@@ -0,0 +1,47 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
/// Sets up DCR registries by loading the registry manager and printing the list of registries.
pub fn setup(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr setup", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Sets up DCR registries. Downloads and indexes");
println!(" package registries for dependency resolution.");
return 0;
}
println!("Setting up DCR registries...");
// Load and process the registries, returning 0 on success or 1 on error
match crate::core::registry::RegistryManager::load() {
Ok(manager) => {
println!("Loaded {} registries.", manager.config.registry.len());
for (name, reg) in manager.config.registry {
println!("- {}: {} (priority {})", name, reg.url, reg.priority);
}
0
}
Err(e) => {
println!("Error: {}", e);
1
}
}
}

404
src/cli/test.rs Normal file
View File

@@ -0,0 +1,404 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::cli::build;
use crate::config::{FILE_DCR_TEST_H, FILE_TEST_C, flags};
use crate::core::build_config::Config;
use crate::utils::build::{
get_config_opt, get_config_str, get_language_with_profile, get_list_with_profile,
get_string_with_profile, resolve_compiler, resolve_pkg_config_flags,
};
use crate::utils::fs::{find_project_root, with_dir};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_RED, RESET, colored, printc};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
const BOLD_BLUE: &str = "\x1b[1m\x1b[94m";
/// Runs project tests and prints a unified testsuite report.
pub fn test(args: &[String]) -> i32 {
let mut init_header = false;
let mut profile = "debug";
for arg in args {
if arg == "--help" {
printc("USAGE:", BOLD_GREEN);
printc(" dcr test [--init] [--debug | --release]", BOLD_CYAN);
println!();
printc("ALIASES:", BOLD_GREEN);
println!(" dcr tests");
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Runs project tests and prints a unified testsuite report.");
println!();
printc("OPTIONS:", BOLD_GREEN);
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;
}
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error!("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(dir)) => dir,
Ok(None) => {
error!("dcr.toml file not found");
return 1;
}
Err(_) => {
error!("Failed to find project root");
return 1;
}
};
if init_header {
match with_dir(&root, ensure_test_header) {
Ok(()) => {}
Err(msg) => {
error!("{msg}");
return 1;
}
}
}
match with_dir(&root, || run_testsuite(profile)) {
Ok(code) => code,
Err(msg) => {
error!("{msg}");
1
}
}
}
/// Executes the testsuite for the given build profile.
fn run_testsuite(profile: &str) -> Result<i32, String> {
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 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);
let standard = get_string_with_profile(&config, "standard", profile);
let package_type = get_config_str(&config, "package.type");
let build_kind = get_string_with_profile(&config, "kind", profile);
let tc_cc = get_config_opt(&config, "toolchain.cc");
let tc_cxx = get_config_opt(&config, "toolchain.cxx");
let tc_as = get_config_opt(&config, "toolchain.as");
let compiler = resolve_compiler(
&language,
&compiler_cfg,
tc_cc.as_deref(),
tc_cxx.as_deref(),
tc_as.as_deref(),
);
let mut cflags: Vec<String> = flags(profile)
.unwrap_or(&[])
.iter()
.map(|v| v.to_string())
.collect();
cflags.extend(get_list_with_profile(&config, "cflags", profile));
let ldflags = get_list_with_profile(&config, "ldflags", profile);
let pkg_configs = get_list_with_profile(&config, "pkg_config", profile);
let (mut cflags, mut ldflags) = resolve_pkg_config_flags(&pkg_configs, &cflags, &ldflags)?;
let deps = crate::core::deps::resolve_deps(&config, profile, None, Path::new("."))?;
cflags.extend(deps.cflags);
ldflags.extend(deps.ldflags);
let mut includes = test_include_dirs(&config, profile);
includes.extend(deps.include_dirs);
let test_sources = collect_test_sources()?;
if test_sources.is_empty() {
return Err("tests/test.c not found; run 'dcr test --init' first".to_string());
}
let link_project_lib =
package_type == "lib" || build_kind == "staticlib" || build_kind == "sharedlib";
let mut stdout = String::new();
let mut declared = Vec::new();
let mut suite_success = true;
fs::create_dir_all("./tests/target")
.map_err(|e| format!("Failed to create tests/target: {e}"))?;
for source in &test_sources {
declared.extend(extract_test_names_path(source));
let stem = source
.file_stem()
.and_then(|v| v.to_str())
.unwrap_or("test");
let obj_path = Path::new("./tests/target").join(format!("{stem}.o"));
let bin_path =
Path::new("./tests/target").join(format!("{stem}{}", std::env::consts::EXE_SUFFIX));
compile_test_source(&compiler, &standard, &cflags, &includes, source, &obj_path)?;
link_test_binary(
&compiler,
&ldflags,
link_project_lib.then_some(name.as_str()),
&obj_path,
&bin_path,
)?;
let out = Command::new(&bin_path)
.output()
.map_err(|_| format!("Failed to run `{}`", bin_path.display()))?;
stdout.push_str(&String::from_utf8_lossy(&out.stdout));
let stderr = String::from_utf8_lossy(&out.stderr);
if !stderr.trim().is_empty() {
eprint!("{}", stderr);
}
if !out.status.success() {
suite_success = false;
}
}
let mut pass = 0;
let mut skip = 0;
let mut fail = 0;
let mut parsed_any = false;
for line in stdout.lines() {
let line = line.trim();
if let Some(name) = line.strip_prefix("[PASS] ") {
println!("{} {}", colored("[PASS]", BOLD_GREEN), name);
pass += 1;
parsed_any = true;
continue;
}
if let Some(name) = line.strip_prefix("[SKIP] ") {
println!("{} {}", colored("[SKIP]", BOLD_BLUE), name);
skip += 1;
parsed_any = true;
continue;
}
if let Some(name) = line.strip_prefix("[FAIL] ") {
println!("{} {}", colored("[FAIL]", BOLD_RED), name);
fail += 1;
parsed_any = true;
continue;
}
if let Some((status, name)) = line.split_once('\t') {
match status {
"PASS" => {
println!("{} {}", colored("[PASS]", BOLD_GREEN), name);
pass += 1;
parsed_any = true;
}
"SKIP" => {
println!("{} {}", colored("[SKIP]", BOLD_BLUE), name);
skip += 1;
parsed_any = true;
}
"FAIL" => {
println!("{} {}", colored("[FAIL]", BOLD_RED), name);
fail += 1;
parsed_any = true;
}
_ => {}
}
}
}
if !parsed_any {
if suite_success {
for name in &declared {
println!("{} {}", colored("[PASS]", BOLD_GREEN), name);
}
pass = declared.len() as i32;
} else {
println!("{} testsuite", colored("[FAIL]", BOLD_RED));
fail = 1;
}
}
let total = if parsed_any || declared.is_empty() {
pass + skip + fail
} else {
declared.len() as i32
};
println!();
println!("{}", colored("=====================", BOLD_GREEN));
println!("{}", colored(" Testsuite summary ", BOLD_GREEN));
println!("{}", colored("=====================", BOLD_GREEN));
println!("TOTAL: {}", total);
print_field("PASS", pass, BOLD_GREEN);
print_field("SKIP", skip, BOLD_BLUE);
print_field("FAIL", fail, BOLD_RED);
println!("{}", colored("=====================", BOLD_GREEN));
if fail > 0 || !suite_success {
return Ok(1);
}
Ok(0)
}
/// Collects all .c source files from the tests/ directory.
fn collect_test_sources() -> Result<Vec<PathBuf>, String> {
let tests_dir = Path::new("./tests");
if !tests_dir.is_dir() {
return Ok(Vec::new());
}
let mut sources = Vec::new();
for entry in fs::read_dir(tests_dir).map_err(|e| format!("Failed to read tests/: {e}"))? {
let path = entry
.map_err(|e| format!("Failed to read tests/: {e}"))?
.path();
if path.extension().and_then(|v| v.to_str()) == Some("c") {
sources.push(path);
}
}
sources.sort();
Ok(sources)
}
/// Returns a list of include directories for test compilation.
fn test_include_dirs(config: &Config, profile: &str) -> Vec<String> {
let mut dirs = vec![
"./tests".to_string(),
"./src".to_string(),
"./target/include".to_string(),
];
dirs.extend(get_list_with_profile(config, "include", profile));
dirs
}
/// Compiles a single test source file to an object file.
fn compile_test_source(
compiler: &str,
standard: &str,
cflags: &[String],
includes: &[String],
source: &Path,
obj_path: &Path,
) -> Result<(), String> {
let mut cmd = Command::new(compiler);
cmd.arg("-c").arg(source).arg("-o").arg(obj_path);
if !standard.trim().is_empty() {
cmd.arg(format!("-std={}", standard.trim()));
}
for flag in cflags {
cmd.arg(flag);
}
for dir in includes {
cmd.arg(format!("-I{dir}"));
}
let output = cmd
.output()
.map_err(|e| format!("Failed to compile {}: {e}", source.display()))?;
print_command_output(&output);
if !output.status.success() {
return Err(format!("Compilation of {} failed", source.display()));
}
Ok(())
}
/// Links the test object file into an executable binary.
fn link_test_binary(
compiler: &str,
ldflags: &[String],
project_lib: Option<&str>,
obj_path: &Path,
bin_path: &Path,
) -> Result<(), String> {
let mut cmd = Command::new(compiler);
cmd.arg(obj_path);
if let Some(name) = project_lib {
cmd.arg("-Ltarget/lib").arg(format!("-l{name}"));
}
for flag in ldflags {
cmd.arg(flag);
}
cmd.arg("-o").arg(bin_path);
let output = cmd
.output()
.map_err(|e| format!("Failed to link {}: {e}", bin_path.display()))?;
print_command_output(&output);
if !output.status.success() {
return Err(format!("Linking of {} failed", bin_path.display()));
}
Ok(())
}
/// Prints the stdout and stderr from a command execution.
fn print_command_output(output: &std::process::Output) {
if !output.stdout.is_empty() {
print!("{}", String::from_utf8_lossy(&output.stdout));
}
if !output.stderr.is_empty() {
eprint!("{}", String::from_utf8_lossy(&output.stderr));
}
}
/// Creates the default test files (dcr_test.h and test.c) in the tests/ directory.
fn ensure_test_header() -> Result<(), String> {
fs::create_dir_all("./tests").map_err(|_| "Failed to create tests/".to_string())?;
fs::write("./tests/dcr_test.h", FILE_DCR_TEST_H)
.map_err(|_| "Failed to write tests/dcr_test.h".to_string())?;
println!("Created tests/dcr_test.h");
fs::write("./tests/test.c", FILE_TEST_C)
.map_err(|_| "Failed to write tests/test.c".to_string())?;
println!("Created tests/test.c");
Ok(())
}
/// Extracts test case names from a C source file by parsing TEST_CASE macros.
fn extract_test_names_path(path: &Path) -> Vec<String> {
let Ok(content) = fs::read_to_string(path) else {
return Vec::new();
};
let mut names = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("TEST_CASE(")
&& let Some(name) = rest.strip_suffix("),")
{
names.push(name.to_string());
}
}
names
}
/// Prints a test result field with color coding.
fn print_field(label: &str, value: i32, color: &str) {
if value == 0 {
println!("{}: {}", label, value);
} else {
println!("{}{}: {}{}", color, label, value, RESET);
}
}

168
src/cli/tree.rs Normal file
View File

@@ -0,0 +1,168 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::prelude::*;
use crate::core::build_config::Config;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use toml::Value;
/// Displays the dependency tree of the current project.
///
/// This function checks for the --help flag and prints usage information if present.
/// Otherwise, it parses the dcr.toml configuration and prints the dependency tree.
pub fn tree(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(" dcr tree", BOLD_CYAN);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Displays the dependency tree of the current project.");
return 0;
}
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let config = match Config::open("dcr.toml") {
Ok(c) => c,
Err(_) => {
error!("dcr.toml not found in current directory");
return 1;
}
};
let name = config
.get("package.name")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let version = config
.get("package.version")
.and_then(|v| v.as_str())
.unwrap_or("0.0.0");
printc(&format!("{} v{}", name, version), BOLD_CYAN);
let mut seen = HashSet::new();
seen.insert(name.to_string());
if let Some(deps) = config.get("dependencies").and_then(|v| v.as_table()) {
print_deps(deps, &current_dir, "", &mut seen);
}
0
}
/// Recursively prints the dependency tree starting from the given dependencies table.
///
/// Sorts dependencies for consistent ordering and handles path resolution for sub-dependencies.
fn print_deps(
deps: &toml::value::Table,
base_path: &Path,
prefix: &str,
seen: &mut HashSet<String>,
) {
let mut dep_list: Vec<_> = deps.iter().collect();
dep_list.sort_by_key(|(name, _)| *name);
for (i, (name, value)) in dep_list.iter().enumerate() {
let is_last = i == dep_list.len() - 1;
let connector = if is_last { "└── " } else { "├── " };
let child_prefix = if is_last { " " } else { "│ " };
let dep_path = resolve_dep_path(value, base_path);
let mut version = String::new();
let mut sub_deps = None;
let mut resolved_path = None;
// Resolve dependency path if it's a path-based dependency
if let Some(path) = &dep_path {
let dcr_toml = path.join("dcr.toml");
if dcr_toml.exists()
&& let Ok(config) = Config::open(&dcr_toml.to_string_lossy())
{
if let Some(v) = config.get("package.version").and_then(|v| v.as_str()) {
version = format!(" v{}", v);
}
sub_deps = config
.get("dependencies")
.and_then(|v| v.as_table())
.cloned();
resolved_path = Some(path.clone());
}
}
if version.is_empty() {
version = match value {
Value::String(s) if s.starts_with("git:") || s.starts_with("github:") => {
format!(" ({})", s)
}
Value::Table(t) => {
if let Some(v) = t.get("version").and_then(|v| v.as_str()) {
format!(" v{}", v)
} else if let Some(git) = t.get("git").and_then(|v| v.as_str()) {
format!(" ({})", git)
} else if let Some(pkg) = t.get("pkg-config").or_else(|| t.get("pkg_config")) {
if let Some(s) = pkg.as_str() {
format!(" (pkg-config: {})", s)
} else if let Some(arr) = pkg.as_array() {
let names = arr
.iter()
.filter_map(|v| v.as_str())
.collect::<Vec<_>>()
.join(", ");
format!(" (pkg-config: {})", names)
} else {
"".to_string()
}
} else {
"".to_string()
}
}
_ => "".to_string(),
};
}
println!("{}{}{}{}", prefix, connector, name, version);
// Recurse only if sub-dependencies exist and this dep hasn't been seen
if let Some(s_deps) = sub_deps
&& !seen.contains(*name)
{
seen.insert(name.to_string());
let new_prefix = format!("{}{}", prefix, child_prefix);
if let Some(path) = resolved_path {
print_deps(&s_deps, &path, &new_prefix, seen);
}
seen.remove(*name);
}
}
}
/// Resolves the filesystem path for a dependency entry if it specifies a "path:" prefix or table with "path" key.
fn resolve_dep_path(value: &Value, base_path: &Path) -> Option<PathBuf> {
match value {
Value::String(s) => s
.strip_prefix("path:")
.map(|stripped| base_path.join(stripped)),
Value::Table(t) => t
.get("path")
.and_then(|v| v.as_str())
.map(|path| base_path.join(path)),
_ => None,
}
}

103
src/config.rs Normal file
View File

@@ -0,0 +1,103 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub const PROFILE: &str = "debug";
pub const FILE_MAIN_C: &str = r#"#include <stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
"#;
pub const FILE_DCR_TEST_H: &str = r#"#pragma once
#include <stdio.h>
#include <string.h>
#define DCR_TEST_PASS 0
#define DCR_TEST_FAIL 1
#define DCR_TEST_SKIP 2
#define EXPECT(expr) \
do { \
if (!(expr)) { \
fprintf(stderr, "assert failed: %s (%s:%d)\n", #expr, __FILE__, __LINE__); \
return DCR_TEST_FAIL; \
} \
} while (0)
#define SKIP(reason) \
do { \
(void)(reason); \
return DCR_TEST_SKIP; \
} while (0)
#define TEST(name) static int name(void)
#define TEST_CASE(name) {#name, name}
typedef int (*test_fn_t)(void);
typedef struct {
const char *name;
test_fn_t fn;
} test_case_t;
static inline int dcr_test_raw_mode(int argc, char **argv) {
return argc > 1 && strcmp(argv[1], "--dcr-raw") == 0;
}
static inline void dcr_test_print_result(const char *name, int rc, int raw_mode) {
const char *tag = (rc == DCR_TEST_PASS) ? "PASS" : (rc == DCR_TEST_SKIP) ? "SKIP" : "FAIL";
if (raw_mode) {
printf("%s\t%s\n", tag, name);
} else {
printf("[%s] %s\n", tag, name);
}
}
"#;
pub const FILE_TEST_C: &str = r#"#include "dcr_test.h"
TEST(test_example) {
EXPECT(1 + 1 == 2);
return DCR_TEST_PASS;
}
static test_case_t CASES[] = {
TEST_CASE(test_example),
};
int main(int argc, char **argv) {
int raw = dcr_test_raw_mode(argc, argv);
int rc = 0;
int count = (int)(sizeof(CASES) / sizeof(CASES[0]));
for (int i = 0; i < count; ++i) {
int status = CASES[i].fn();
dcr_test_print_result(CASES[i].name, status, raw);
if (status == DCR_TEST_FAIL) {
rc = 1;
}
}
return rc;
}
"#;
/// Returns build flags for the given profile as a static slice, or None if empty.
pub fn flags(profile: &str) -> Option<&'static [&'static str]> {
let f = crate::utils::build::default_profile_flags(profile);
if f.is_empty() { None } else { Some(f) }
}

276
src/core/build/archive.rs Normal file
View File

@@ -0,0 +1,276 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::core::build_config::ArchiveConfig;
use fatfs::{FileSystem, FormatVolumeOptions, format_volume};
use glob::glob;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::Path;
/// File wrapper that shifts all seek offsets (used to skip the boot sector on a FAT image).
struct OffsetFile<T> {
inner: T,
offset: u64,
}
impl<T: Read> Read for OffsetFile<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<T: Write> Write for OffsetFile<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<T: Seek> Seek for OffsetFile<T> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match pos {
SeekFrom::Start(n) => {
let actual = self.inner.seek(SeekFrom::Start(n + self.offset))?;
if actual < self.offset {
Ok(0)
} else {
Ok(actual - self.offset)
}
}
SeekFrom::Current(n) => {
let actual = self.inner.seek(SeekFrom::Current(n))?;
if actual < self.offset {
Ok(0)
} else {
Ok(actual - self.offset)
}
}
SeekFrom::End(n) => {
let actual = self.inner.seek(SeekFrom::End(n))?;
if actual < self.offset {
Ok(0)
} else {
Ok(actual - self.offset)
}
}
}
}
}
/// Parses a size string like `1.5m` or `500k` into bytes (1024-based; empty → 0).
fn parse_size(s: &str) -> Result<u64, String> {
let s = s.trim();
if s.is_empty() {
return Ok(0);
}
let mut num_str = String::new();
let mut unit = String::new();
for c in s.chars() {
if c.is_ascii_digit() || c == '.' {
num_str.push(c);
} else {
unit.push(c);
}
}
let val: f64 = num_str
.parse()
.map_err(|_| format!("invalid size number: {num_str}"))?;
let unit = unit.trim().to_lowercase();
let multiplier = match unit.as_str() {
"k" | "kb" => 1024.0,
"m" | "mb" => 1024.0 * 1024.0,
"g" | "gb" => 1024.0 * 1024.0 * 1024.0,
"" => 1.0,
_ => return Err(format!("unknown unit: {unit}")),
};
Ok((val * multiplier) as u64)
}
/// Creates a FAT image from layout config, substituting `{profile}` and optional boot sector.
pub fn pack_archive(
project_root: &Path,
config: &ArchiveConfig,
profile: &str,
) -> Result<(), String> {
let output_substituted = config.output.replace("{profile}", profile);
let output_path = project_root.join(&output_substituted);
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("failed to create output parent dir: {e}"))?;
}
let size = if let Some(sz_str) = &config.size {
parse_size(sz_str)?
} else {
1474560
};
let offset = if let Some(off_str) = &config.offset {
parse_size(off_str)?
} else {
0
};
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&output_path)
.map_err(|e| format!("failed to open output file: {e}"))?;
let current_len = file.metadata().map(|m| m.len()).unwrap_or(0);
if current_len < size {
file.set_len(size)
.map_err(|e| format!("failed to set image file size: {e}"))?;
}
if offset == 0
&& let Some(boot_rel) = &config.bootsector
{
// Load and write bootsector if present
let boot_rel_substituted = boot_rel.replace("{profile}", profile);
let boot_path = project_root.join(&boot_rel_substituted);
if boot_path.is_file() {
let mut boot_file =
File::open(&boot_path).map_err(|e| format!("failed to open bootsector: {e}"))?;
let mut boot_bytes = vec![0; 512];
let _ = boot_file
.read(&mut boot_bytes)
.map_err(|e| format!("failed to read bootsector: {e}"))?;
file.seek(SeekFrom::Start(0))
.map_err(|e| format!("failed to seek to start: {e}"))?;
file.write_all(&boot_bytes)
.map_err(|e| format!("failed to write bootsector: {e}"))?;
}
}
file.seek(SeekFrom::Start(0))
.map_err(|e| format!("failed to seek to start: {e}"))?;
let mut wrapper = OffsetFile {
inner: file,
offset,
};
let label = config.label.as_deref().unwrap_or("VOLUME");
let mut label_bytes = [b' '; 11];
let bytes_to_copy = label.len().min(11);
label_bytes[..bytes_to_copy].copy_from_slice(&label.as_bytes()[..bytes_to_copy]);
let fat_type = match config.format.to_lowercase().as_str() {
"fat12" => Some(fatfs::FatType::Fat12),
"fat16" => Some(fatfs::FatType::Fat16),
"fat32" => Some(fatfs::FatType::Fat32),
_ => None,
};
let mut options = FormatVolumeOptions::new().volume_label(label_bytes);
if let Some(t) = fat_type {
options = options.fat_type(t);
}
format_volume(&mut wrapper, options)
.map_err(|e| format!("failed to format FAT volume: {e}"))?;
wrapper
.seek(SeekFrom::Start(0))
.map_err(|e| format!("failed to seek to start after format: {e}"))?;
let fs = FileSystem::new(wrapper, fatfs::FsOptions::new())
.map_err(|e| format!("failed to mount FAT fs: {e}"))?;
let root_dir = fs.root_dir();
for layout in &config.layout {
let from_substituted = layout.from.replace("{profile}", profile);
let from_pattern = project_root
.join(&from_substituted)
.to_string_lossy()
.to_string();
let paths = if from_pattern.contains('*') || from_pattern.contains('?') {
let mut matched = Vec::new();
if let Ok(entries) = glob(&from_pattern) {
for path in entries.flatten() {
matched.push(path);
}
}
matched
} else {
let p = project_root.join(&from_substituted);
if p.exists() { vec![p] } else { Vec::new() }
};
for path in paths {
if !path.is_file() {
continue;
}
let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if filename.is_empty() {
continue;
}
let mut target_filename = filename.to_string();
let mut target_dir_str = layout.to.trim_matches('/');
let is_glob = from_pattern.contains('*') || from_pattern.contains('?');
if !is_glob
&& !layout.to.ends_with('/')
&& let Some(last_part) = Path::new(&layout.to).file_name().and_then(|n| n.to_str())
&& last_part.contains('.')
{
target_filename = last_part.to_string();
let parent_path = Path::new(&layout.to).parent().unwrap_or(Path::new(""));
target_dir_str = parent_path.to_str().unwrap_or("").trim_matches('/');
}
let mut current_dir = root_dir.clone();
if !target_dir_str.is_empty() {
let parts = target_dir_str.split('/');
for part in parts {
if part.is_empty() {
continue;
}
current_dir = match current_dir.open_dir(part) {
Ok(dir) => dir,
Err(_) => current_dir
.create_dir(part)
.map_err(|e| format!("failed to create dir {part}: {e}"))?,
};
}
}
let mut out_file = current_dir
.create_file(&target_filename)
.map_err(|e| format!("failed to create file {target_filename}: {e}"))?;
let mut in_file =
File::open(&path).map_err(|e| format!("failed to open input file: {e}"))?;
let mut buffer = Vec::new();
in_file
.read_to_end(&mut buffer)
.map_err(|e| format!("failed to read input file: {e}"))?;
out_file
.write_all(&buffer)
.map_err(|e| format!("failed to write to FAT file {target_filename}: {e}"))?;
}
}
Ok(())
}

View File

@@ -0,0 +1,426 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::core::build::builder::BuildContext;
use crate::core::build::common;
use crate::platform;
use crate::utils::build::is_bare_metal_target;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
/// Returns the artifact directory path based on the build context.
pub(crate) fn artifact_dir(ctx: &BuildContext) -> PathBuf {
match ctx.target_dir {
Some(dir) => Path::new(dir).to_path_buf(),
None => Path::new("./target").join(ctx.profile),
}
}
/// Resolves the artifact path for a given kind/profile/name.
///
/// # Parameters
/// - `kind`: Artifact kind (`bin`, `staticlib`, `sharedlib`, `flat-bin`, `efi`, `elf`, …).
/// - `profile`: Build profile (`debug` / `release` / custom).
/// - `project_name`: Default basename when `output_filename` is unset.
/// - `target_dir`: Optional artifact directory override.
/// - `output_filename` / `output_extension`: Optional name/ext overrides.
///
/// # Returns
/// Relative/absolute path string, or `None` for `none` / `custom` kinds.
pub fn resolve_artifact_path(
kind: &str,
profile: &str,
project_name: &str,
target_dir: Option<&str>,
output_filename: Option<&str>,
output_extension: Option<&str>,
) -> Option<String> {
if crate::utils::build::is_flat_bin(kind) {
let name = output_filename.unwrap_or(project_name);
let ext = output_extension.unwrap_or("bin");
let file = if ext.is_empty() {
name.to_string()
} else {
format!("{name}.{ext}")
};
let dir = match target_dir {
Some(dir) => Path::new(dir).to_path_buf(),
None => Path::new("./target").join(profile),
};
return Some(dir.join(file).to_string_lossy().to_string());
}
if matches!(kind, "none" | "custom") {
return None;
}
let name = output_filename.unwrap_or(project_name);
let ext = output_extension.unwrap_or("");
let final_name = if ext.is_empty() {
name.to_string()
} else {
format!("{}.{}", name, ext)
};
let path = match kind {
"staticlib" => platform::lib_path(profile, &final_name, target_dir),
"sharedlib" => platform::shared_lib_path(profile, &final_name, target_dir),
"efi" => platform::efi_path(profile, &final_name, target_dir),
"elf" => platform::elf_path(profile, &final_name, target_dir),
_ => platform::bin_path(profile, &final_name, target_dir),
};
Some(path)
}
/// Converts a relative or absolute path to an absolute path by joining with project root if needed.
pub fn absolute_artifact_path(project_root: &Path, relative_or_abs: &str) -> PathBuf {
let p = Path::new(relative_or_abs);
let joined = if p.is_absolute() {
p.to_path_buf()
} else {
let stripped = relative_or_abs
.strip_prefix("./")
.unwrap_or(relative_or_abs);
project_root.join(stripped)
};
crate::utils::fs::canonicalize_path(&joined)
}
/// Returns the output path for a flat binary artifact.
pub(crate) fn flat_output_path(ctx: &BuildContext) -> String {
let name = ctx.output_filename.unwrap_or(ctx.project_name);
let ext = ctx.output_extension.unwrap_or("bin");
let file = if ext.is_empty() {
name.to_string()
} else {
format!("{name}.{ext}")
};
artifact_dir(ctx).join(file).to_string_lossy().to_string()
}
/// Returns the output path for a flat binary artifact from a source file.
pub(crate) fn flat_source_output_path(ctx: &BuildContext, source: &str) -> String {
let stem = Path::new(source)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("out");
let ext = ctx.output_extension.unwrap_or("bin");
let file = if ext.is_empty() {
stem.to_string()
} else {
format!("{stem}.{ext}")
};
artifact_dir(ctx).join(file).to_string_lossy().to_string()
}
// Helper to find a suitable objcopy tool in PATH
fn resolve_objcopy() -> Result<String, String> {
for candidate in ["llvm-objcopy", "objcopy", "gobjcopy"] {
if Command::new(candidate)
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
{
return Ok(candidate.to_string());
}
}
Err(
"flat-bin requires llvm-objcopy, objcopy, or gobjcopy in PATH \
(install binutils or LLVM tools)"
.to_string(),
)
}
/// Converts an object/ELF to a raw binary via `objcopy -O binary` (skips if up to date).
pub(crate) fn objcopy_binary(ctx: &BuildContext, input: &str, output: &str) -> Result<(), String> {
if let Some(parent) = Path::new(output).parent() {
std::fs::create_dir_all(parent).map_err(|e| format!("output dir error: {e}"))?;
}
if !common::needs_rebuild(input, output) {
return Ok(());
}
let tool = resolve_objcopy()?;
let mut cmd = Command::new(&tool);
cmd.arg("-O").arg("binary").arg(input).arg(output);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
common::run_command_sync_output(&mut cmd)
}
/// Links objects into an intermediate ELF, then converts it to a flat binary via objcopy.
pub(crate) fn link_flat_binary(
ctx: &BuildContext,
objects: &[String],
default_linker: &str,
start_time: Instant,
) -> Result<f64, String> {
let out_path = flat_output_path(ctx);
if !common::needs_link(objects, &out_path) {
return Ok(common::elapsed_secs(start_time));
}
let intermediate = {
let dir = match ctx.target_dir {
Some(dir) => Path::new(dir).join("obj"),
None => Path::new("./target").join(ctx.profile).join("obj"),
};
std::fs::create_dir_all(&dir).map_err(|e| format!("obj dir error: {e}"))?;
dir.join(format!("{}.flat.elf", ctx.project_name))
.to_string_lossy()
.to_string()
};
let mut cmd = Command::new(ctx.linker.unwrap_or(default_linker));
cmd.arg("-nostdlib").arg("-static");
for obj in objects {
cmd.arg(obj);
}
for dir in ctx.lib_dirs {
cmd.arg(format!("-L{dir}"));
}
for lib in ctx.libs {
cmd.arg(format!("-l{lib}"));
}
for flag in ctx.ldflags {
cmd.arg(flag);
}
cmd.arg("-o").arg(&intermediate);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
let program = cmd.get_program().to_string_lossy().into_owned();
let output = cmd.output().map_err(|err| {
if err.kind() == std::io::ErrorKind::NotFound {
format!("linker not found: {program} (check [toolchain].ld / PATH)")
} else {
format!("flat-bin link failed: {err}")
}
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(format!(
"flat-bin link failed:\nstdout: {stdout}\nstderr: {stderr}"
));
}
objcopy_binary(ctx, &intermediate, &out_path)?;
Ok(common::elapsed_secs(start_time))
}
/// Archives objects into a static library.
pub(crate) fn archive_static(
ctx: &BuildContext,
objects: &[String],
start_time: Instant,
) -> Result<f64, String> {
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
if !common::needs_link(objects, &lib_path) {
return Ok(common::elapsed_secs(start_time));
}
let archiver = ctx
.archiver
.unwrap_or_else(|| default_archiver(ctx.compiler));
let mut cmd = Command::new(archiver);
if cfg!(target_os = "windows") && is_msvc_lib(archiver) {
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
} else {
cmd.arg("rcs").arg(&lib_path);
}
for obj in objects {
cmd.arg(obj);
}
run(ctx, cmd, start_time)
}
/// Links objects into a binary or library based on artifact kind.
pub(crate) fn link_binary(
ctx: &BuildContext,
objects: &[String],
default_linker: &str,
start_time: Instant,
) -> Result<f64, String> {
if crate::utils::build::is_flat_bin(ctx.kind) {
return link_flat_binary(ctx, objects, default_linker, start_time);
}
let mut cmd = Command::new(ctx.linker.unwrap_or(default_linker));
if ctx.kind == "sharedlib" {
if cfg!(target_os = "macos") {
cmd.arg("-dynamiclib");
} else {
cmd.arg("-shared");
}
}
if ctx.kind == "efi" {
cmd.arg("-shared");
cmd.arg("-nostdlib");
cmd.arg("-Wl,-dll");
cmd.arg("-Wl,--subsystem,10");
} else if ctx.freestanding || is_bare_metal_target(ctx.target) {
cmd.arg("-nostdlib");
cmd.arg("-static");
}
for obj in objects {
cmd.arg(obj);
}
for dir in ctx.lib_dirs {
cmd.arg(format!("-L{dir}"));
}
for lib in ctx.libs {
cmd.arg(format!("-l{lib}"));
}
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, &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)
};
if !common::needs_link(objects, &out_path) {
return Ok(common::elapsed_secs(start_time));
}
cmd.arg("-o").arg(out_path);
run(ctx, cmd, start_time)
}
/// Returns the default archiver name based on compiler and platform.
fn default_archiver(compiler: &str) -> &'static str {
let c = compiler.to_lowercase();
if cfg!(target_os = "windows") {
if c.contains("cl") && !c.contains("clang") {
"lib"
} else if c.contains("clang") {
"llvm-ar"
} else {
"ar"
}
} else {
"ar"
}
}
/// Checks if the archiver is an MSVC lib tool.
fn is_msvc_lib(archiver: &str) -> bool {
let a = archiver.to_lowercase();
a == "lib" || a == "lib.exe" || a.ends_with("\\lib.exe") || a.ends_with("/lib.exe")
}
/// Runs a command and handles success/failure with timing.
fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64, String> {
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
let program = cmd.get_program().to_string_lossy().into_owned();
let output = cmd.output().map_err(|err| {
let msg = err.to_string();
if err.kind() == std::io::ErrorKind::NotFound {
format!("linker not found: {program} (check [toolchain].ld / PATH)")
} else {
format!("Build failed: {msg}")
}
})?;
if output.status.success() {
Ok(common::elapsed_secs(start_time))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
Err(format!("Build failed:\nstdout: {stdout}\nstderr: {stderr}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Default `bin` kind resolves via platform bin path.
#[test]
fn resolve_bin_default() {
let p = resolve_artifact_path("bin", "debug", "hello", None, None, None).unwrap();
assert_eq!(p, platform::bin_path("debug", "hello", None));
}
/// Custom `target_dir` places the binary under that directory.
#[test]
fn resolve_bin_custom_target_dir() {
let p = resolve_artifact_path("bin", "debug", "hello", Some("/out"), None, None).unwrap();
let name = if cfg!(windows) { "hello.exe" } else { "hello" };
let expected = Path::new("/out").join(name);
assert_eq!(p, expected.to_string_lossy());
}
/// `staticlib` kind resolves to a platform static library path.
#[test]
fn resolve_staticlib() {
let p = resolve_artifact_path("staticlib", "release", "mylib", Some("dist"), None, None)
.unwrap();
let name = if cfg!(windows) {
"mylib.lib"
} else {
"libmylib.a"
};
let expected = Path::new("dist").join(name);
assert_eq!(p, expected.to_string_lossy());
}
/// Custom output filename and extension override the default name.
#[test]
fn resolve_with_filename_ext() {
let p = resolve_artifact_path(
"bin",
"debug",
"pkg",
Some("/out"),
Some("KERNEL"),
Some("ELF"),
)
.unwrap();
assert_eq!(
p,
std::path::Path::new("/out")
.join("KERNEL.ELF")
.to_string_lossy()
);
}
/// `none` kind has no artifact path.
#[test]
fn resolve_none_kind() {
assert!(resolve_artifact_path("none", "debug", "x", None, None, None).is_none());
}
}

View File

@@ -0,0 +1,236 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::core::build::builder::BuildContext;
use crate::core::build::builder::artifact;
use crate::core::build::common;
use crate::utils::build::{is_bare_metal_target, is_compile_only, is_flat_bin};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
/// Compiles C/C++ sources and links/archives by kind (`bin`, libs, `flat-bin`, or compile-only).
///
/// Handles Qt preprocessing when enabled. Returns elapsed seconds.
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
let compiler = if ctx.compiler.is_empty() {
"cc"
} else {
ctx.compiler
};
// Reject unsupported flat-bin with Qt
if is_flat_bin(ctx.kind) && ctx.qt {
return Err("flat-bin is not supported with build.qt = true".to_string());
}
let start_time = Instant::now();
let obj_dir = match ctx.target_dir {
Some(dir) => Path::new(dir).join("obj"),
None => Path::new("./target").join(ctx.profile).join("obj"),
};
let qt_include_path = if ctx.qt {
crate::core::build::language::cxx::qt::process_qt(ctx, &obj_dir)?
} else {
None
};
let mut all_source_roots = ctx.source_roots.to_vec();
if let Some(qt_path) = &qt_include_path {
all_source_roots.push(qt_path.clone());
}
let extensions = source_extensions(ctx.language);
let sources = common::collect_sources(
&all_source_roots,
&extensions,
ctx.exclude_dirs,
ctx.include_paths,
)?;
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o", qt_include_path)?;
if is_compile_only(ctx.kind) && !is_flat_bin(ctx.kind) {
return Ok(common::elapsed_secs(start_time));
}
if ctx.kind == "staticlib" {
return artifact::archive_static(ctx, &objects, start_time);
}
artifact::link_binary(ctx, &objects, compiler, start_time)
}
/// Collects source files from the provided source roots for the build context.
///
/// Delegates to common source collection after resolving language-specific extensions.
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
let extensions = source_extensions(ctx.language);
common::collect_sources(
ctx.source_roots,
&extensions,
ctx.exclude_dirs,
ctx.include_paths,
)
}
/// Source file extensions for `language` (owned `Vec` of static slices).
fn source_extensions(language: &str) -> Vec<&'static str> {
crate::core::build::common::source_extensions(language)
}
/// Retrieves default compiler flags for the specified build profile.
fn default_flags(profile: &str) -> &'static [&'static str] {
crate::utils::build::default_profile_flags(profile)
}
/// Compiles all provided sources into object files in parallel.
///
/// Prepares object file paths and spawns parallel tasks for each source compilation.
fn build_objects(
compiler: &str,
sources: &[String],
obj_dir: &Path,
ctx: &BuildContext,
obj_ext: &str,
qt_include_dir: Option<PathBuf>,
) -> Result<Vec<String>, String> {
let objects: Vec<String> = sources
.iter()
.map(|s| common::object_path(obj_dir, s, obj_ext))
.collect();
// Compile objects in parallel
common::parallel_build(
sources.len(),
|i| {
build_object(
compiler,
&sources[i],
&objects[i],
ctx,
qt_include_dir.as_deref(),
)
},
ctx.codegen_units,
)?;
Ok(objects)
}
/// Compiles a single source file to an object file.
///
/// Creates object directory if needed, checks for rebuild necessity, constructs and runs the compiler command with appropriate flags.
fn build_object(
compiler: &str,
source: &str,
obj_path: &str,
ctx: &BuildContext,
qt_include_dir: Option<&Path>,
) -> Result<(), String> {
if let Some(parent) = Path::new(obj_path).parent() {
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
}
// Skip if object is already up to date
if !common::needs_rebuild(source, obj_path) {
return Ok(());
}
let mut cmd = Command::new(compiler);
cmd.arg("-c");
if let Some(flag) = asm_lang_flag(source) {
cmd.arg("-x").arg(flag);
}
cmd.arg(source).arg("-o").arg(obj_path);
if ctx.kind == "sharedlib" {
cmd.arg("-fPIC");
}
if (ctx.freestanding || is_bare_metal_target(ctx.target) || is_flat_bin(ctx.kind))
&& ctx.language.to_lowercase() != "asm"
{
cmd.arg("-ffreestanding");
}
if ctx.panic_abort && ctx.language.to_lowercase() != "asm" {
if ctx.language.contains("++")
|| ctx.language.contains("cpp")
|| ctx.language.contains("cxx")
{
cmd.arg("-fno-exceptions");
}
cmd.arg("-fno-unwind-tables");
cmd.arg("-fno-asynchronous-unwind-tables");
}
if let Some(platform) = ctx.platform
&& !platform.trim().is_empty()
{
cmd.arg(format!("-march={}", platform));
}
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
let ext = Path::new(source)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
if is_cpp && !ctx.cxx_standard.is_empty() {
cmd.arg(format!("-std={}", ctx.cxx_standard));
} else if !is_cpp {
cmd.arg(format!("-std={}", ctx.standard));
}
}
let use_dcr_defaults = ctx.cflags.is_empty()
&& !(ctx.freestanding || is_bare_metal_target(ctx.target) || is_flat_bin(ctx.kind));
// Use default flags only if no custom flags and not special target
if use_dcr_defaults {
for flag in default_flags(ctx.profile) {
cmd.arg(flag);
}
}
for flag in ctx.cflags {
cmd.arg(flag);
}
for dir in ctx.include_dirs {
cmd.arg(format!("-I{dir}"));
}
if let Some(path) = qt_include_dir {
cmd.arg(format!("-I{}", path.display()));
}
let d_path = Path::new(obj_path).with_extension("d");
cmd.arg("-MMD").arg("-MF").arg(&d_path);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
common::run_command_sync_output(&mut cmd)
}
/// Returns the assembly language flag for the compiler based on source extension.
fn asm_lang_flag(source: &str) -> Option<&'static str> {
crate::core::build::common::asm_lang_flag(source)
}

View File

@@ -0,0 +1,249 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod artifact;
pub mod cc_common;
pub mod msvc;
use crate::core::build::language::asm::{fasm, gas, masm, nasm};
use crate::core::build::language::llvm_ir;
/// Configuration context for the build process, including compiler, target, and output settings.
pub struct BuildContext<'a> {
pub profile: &'a str,
pub project_name: &'a str,
pub compiler: &'a str,
pub language: &'a str,
pub standard: &'a str,
pub cxx_standard: &'a str,
pub target: Option<&'a str>,
pub target_dir: Option<&'a str>,
pub kind: &'a str,
pub platform: Option<&'a str>,
pub linker: Option<&'a str>,
pub archiver: Option<&'a str>,
pub moc: Option<&'a str>,
pub uic: Option<&'a str>,
pub rcc: Option<&'a str>,
pub package_type: Option<&'a str>,
pub freestanding: bool,
pub panic_abort: bool,
pub codegen_units: usize,
pub source_roots: &'a [std::path::PathBuf],
pub exclude_dirs: &'a [std::path::PathBuf],
pub include_paths: &'a [String],
pub include_dirs: &'a [String],
pub lib_dirs: &'a [String],
pub libs: &'a [String],
pub cflags: &'a [String],
pub ldflags: &'a [String],
pub output_filename: Option<&'a str>,
pub output_extension: Option<&'a str>,
pub verbose: bool,
pub qt: bool,
}
/// Trait defining the build interface for different compilers and languages.
pub trait Builder {
#[allow(dead_code)]
fn id(&self) -> &'static str;
fn build(&self, ctx: &BuildContext) -> Result<f64, String>;
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String>;
}
/// Concrete builder for C/C++ languages using gcc/clang.
struct Cc;
/// Concrete builder for MSVC and clang-cl.
struct Msvc;
/// Concrete builder for NASM assembly.
struct Nasm;
/// Concrete builder for GNU Assembler (gas).
struct Gas;
/// Concrete builder for MASM assembler.
struct Masm;
/// Concrete builder for FASM assembler.
struct Fasm;
/// Concrete builder for LLVM IR.
struct Llc;
impl Builder for Cc {
fn id(&self) -> &'static str {
"cc"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
cc_common::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
cc_common::collect_sources(ctx)
}
}
impl Builder for Msvc {
fn id(&self) -> &'static str {
"msvc"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
msvc::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
msvc::collect_sources(ctx)
}
}
impl Builder for Nasm {
fn id(&self) -> &'static str {
"nasm"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
nasm::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
nasm::collect_sources(ctx)
}
}
impl Builder for Gas {
fn id(&self) -> &'static str {
"gas"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
gas::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
gas::collect_sources(ctx)
}
}
impl Builder for Masm {
fn id(&self) -> &'static str {
"masm"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
masm::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
masm::collect_sources(ctx)
}
}
impl Builder for Fasm {
fn id(&self) -> &'static str {
"fasm"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
fasm::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
fasm::collect_sources(ctx)
}
}
impl Builder for Llc {
fn id(&self) -> &'static str {
"llvm_ir"
}
fn build(&self, ctx: &BuildContext) -> Result<f64, String> {
llvm_ir::build(ctx)
}
fn collect_sources(&self, ctx: &BuildContext) -> Result<Vec<String>, String> {
llvm_ir::collect_sources(ctx)
}
}
pub fn builder_for(compiler: &str) -> &'static dyn Builder {
// Normalize compiler name to lowercase for matching.
let c = compiler.to_lowercase();
if c.contains("clang-cl") {
return &Msvc;
}
if c == "as" || c.contains("gas") {
return &Gas;
}
if c.contains("llc") {
return &Llc;
}
if c.contains("fasm") {
return &Fasm;
}
if c.contains("nasm") {
return &Nasm;
}
if c == "ml" || c == "ml64" || c.contains("masm") {
return &Masm;
}
if c == "cl" || c.contains("msvc") {
return &Msvc;
}
&Cc
}
/// Main build entrypoint that validates the compiler and delegates to the selected builder.
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
if !check_compiler_exists(ctx.compiler) {
return Err(format!(
"Compiler not found: {}. Make sure it is installed and available in PATH.",
ctx.compiler
));
}
// Delegate to the concrete builder's build method.
builder_for(ctx.compiler).build(ctx)
}
/// True if `compiler --version` can be spawned (exit status is ignored).
fn check_compiler_exists(compiler: &str) -> bool {
let name = if compiler.is_empty() {
"cc"
} else {
match compiler.to_lowercase().as_str() {
"gas" | "gnu-as" => "as",
"nasm" => "nasm",
"fasm" | "fasm64" => "fasm",
"masm" | "ml" | "ml64" => "ml",
_ => compiler,
}
};
std::process::Command::new(name)
.arg("--version")
.output()
.is_ok()
}
/// Collects source code files by delegating to the builder selected for the compiler.
pub fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
builder_for(ctx.compiler).collect_sources(ctx)
}
/// Unit test verifying that builder_for correctly identifies the builder for various compiler names.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_resolves_by_compiler_name() {
assert_eq!(builder_for("gcc").id(), "cc");
assert_eq!(builder_for("clang").id(), "cc");
assert_eq!(builder_for("clang++").id(), "cc");
assert_eq!(builder_for("").id(), "cc");
assert_eq!(builder_for("nasm").id(), "nasm");
assert_eq!(builder_for("as").id(), "gas");
assert_eq!(builder_for("llc-18").id(), "llvm_ir");
assert_eq!(builder_for("fasm").id(), "fasm");
assert_eq!(builder_for("ml64").id(), "masm");
assert_eq!(builder_for("cl").id(), "msvc");
assert_eq!(builder_for("clang-cl").id(), "msvc");
}
}

View File

@@ -0,0 +1,431 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/// MSVC build backend for DCR.
///
/// This module provides the MSVC-specific build implementation for C and C++
/// projects, including object compilation and linking for executables, static
/// libraries, shared libraries, and flat binaries.
use crate::core::build::builder::BuildContext;
use crate::core::build::builder::artifact;
use crate::core::build::common;
use crate::platform;
use crate::utils::build::{is_compile_only, is_flat_bin};
use std::fs;
use std::path::Path;
use std::process::Command;
use std::time::Instant;
/// Builds the project target using the MSVC compiler.
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
let compiler = if ctx.compiler.is_empty() {
"cl"
} else {
ctx.compiler
};
let lang = ctx.language.to_lowercase();
if lang.contains("asm") {
return Err("MSVC backend does not support build.language with asm".to_string());
}
if is_flat_bin(ctx.kind) && ctx.qt {
return Err("flat-bin is not supported with build.qt = true".to_string());
}
let start_time = Instant::now();
let sources = collect_sources(ctx)?;
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 is_compile_only(ctx.kind) && !is_flat_bin(ctx.kind) {
return Ok(common::elapsed_secs(start_time));
}
if is_flat_bin(ctx.kind) {
return link_flat_msvc(ctx, compiler, &objects, &obj_dir, start_time);
}
if ctx.kind == "staticlib" {
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
if !common::needs_link(&objects, &lib_path) {
let elapsed = common::elapsed_secs(start_time);
return Ok(elapsed);
}
let archiver = ctx.archiver.unwrap_or("lib");
let mut cmd = Command::new(archiver);
if archiver == "lib" || archiver.eq_ignore_ascii_case("lib.exe") {
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
} else {
cmd.arg("rcs").arg(&lib_path);
}
for obj in &objects {
cmd.arg(obj);
}
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
match cmd.status() {
Ok(status) if status.success() => {
let elapsed = common::elapsed_secs(start_time);
return Ok(elapsed);
}
Ok(_) => return Err("Build failed".to_string()),
Err(err) => return Err(format!("Build failed: {err}")),
}
}
let mut cmd = Command::new(compiler);
cmd.arg("/nologo");
if ctx.kind == "sharedlib" {
cmd.arg("/LD");
}
match ctx.language.to_lowercase().as_str() {
"c" => {
cmd.arg("/TC");
}
"c++" | "cpp" | "cxx" => {
cmd.arg("/TP");
}
_ => {
return Err("Unsupported language".to_string());
}
}
if !ctx.standard.is_empty() {
let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?;
cmd.arg(std_flag);
}
for obj in &objects {
cmd.arg(obj);
}
if ctx.cflags.is_empty() {
for flag in default_flags(ctx.profile) {
cmd.arg(flag);
}
}
for flag in ctx.cflags {
cmd.arg(flag);
}
for dir in ctx.lib_dirs {
cmd.arg(format!("/LIBPATH:{dir}"));
}
for lib in ctx.libs {
if lib.to_lowercase().ends_with(".lib") {
cmd.arg(lib);
} else {
cmd.arg(format!("{lib}.lib"));
}
}
if !ctx.ldflags.is_empty() {
cmd.arg("/link");
for flag in ctx.ldflags {
cmd.arg(flag);
}
}
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)
};
if !common::needs_link(&objects, &out_path) {
let elapsed = common::elapsed_secs(start_time);
return Ok(elapsed);
}
cmd.arg("-o").arg(out_path);
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
match cmd.status() {
Ok(status) if status.success() => {
let elapsed = common::elapsed_secs(start_time);
Ok(elapsed)
}
Ok(_) => Err("Build failed".to_string()),
Err(err) => Err(format!("Build failed: {err}")),
}
}
/// Links the flat binary output for MSVC build targets.
fn link_flat_msvc(
ctx: &BuildContext,
compiler: &str,
objects: &[String],
obj_dir: &Path,
start_time: Instant,
) -> Result<f64, String> {
let out_path = artifact::flat_output_path(ctx);
if !common::needs_link(objects, &out_path) {
return Ok(common::elapsed_secs(start_time));
}
fs::create_dir_all(obj_dir).map_err(|e| format!("obj dir error: {e}"))?;
let intermediate = obj_dir
.join(format!("{}.flat.exe", ctx.project_name))
.to_string_lossy()
.to_string();
let mut cmd = Command::new(compiler);
cmd.arg("/nologo");
for obj in objects {
cmd.arg(obj);
}
for flag in ctx.cflags {
cmd.arg(flag);
}
for dir in ctx.lib_dirs {
cmd.arg(format!("/LIBPATH:{dir}"));
}
for lib in ctx.libs {
if lib.to_lowercase().ends_with(".lib") {
cmd.arg(lib);
} else {
cmd.arg(format!("{lib}.lib"));
}
}
cmd.arg(format!("/Fe:{intermediate}"));
if !ctx.ldflags.is_empty() {
cmd.arg("/link");
for flag in ctx.ldflags {
cmd.arg(flag);
}
}
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
let status = cmd
.status()
.map_err(|e| format!("flat-bin link failed: {e}"))?;
if !status.success() {
return Err("flat-bin link failed".to_string());
}
artifact::objcopy_binary(ctx, &intermediate, &out_path)?;
Ok(common::elapsed_secs(start_time))
}
/// Collects all source files for the build based on language extensions.
pub(crate) fn collect_sources(ctx: &BuildContext) -> Result<Vec<String>, String> {
let extensions = source_extensions(ctx.language);
common::collect_sources(
ctx.source_roots,
&extensions,
ctx.exclude_dirs,
ctx.include_paths,
)
}
/// Source file extensions for `language` (owned `Vec` of static slices).
fn source_extensions(language: &str) -> Vec<&'static str> {
crate::core::build::common::source_extensions(language)
}
/// Maps the language and standard version to the corresponding MSVC flag string.
fn msvc_standard_flag(language: &str, standard: &str) -> Result<String, String> {
let lang = language.to_lowercase();
let std = standard.to_lowercase();
if lang == "c" {
return match std.as_str() {
"c11" => Ok("/std:c11".to_string()),
"c17" => Ok("/std:c17".to_string()),
_ => Err("Unsupported C standard for MSVC".to_string()),
};
}
if lang == "c++" || lang == "cpp" || lang == "cxx" {
return match std.as_str() {
"c++11" => Ok("/std:c++11".to_string()),
"c++14" => Ok("/std:c++14".to_string()),
"c++17" => Ok("/std:c++17".to_string()),
"c++20" => Ok("/std:c++20".to_string()),
"c++23" => Ok("/std:c++latest".to_string()),
_ => Err("Unsupported C++ standard for MSVC".to_string()),
};
}
Err("Unsupported language".to_string())
}
/// Returns the MSVC architecture flag based on the target platform.
fn msvc_arch_flag(platform: Option<&str>) -> Option<&'static str> {
let raw = platform?.trim();
if raw.is_empty() {
return None;
}
let p = raw.to_lowercase().replace('-', "_");
if p == "x86" || (p.starts_with('i') && p.ends_with("86") && p.len() == 4) {
return Some("/arch:IA32");
}
match p.as_str() {
"sse2" => Some("/arch:SSE2"),
"avx" => Some("/arch:AVX"),
"avx2" => Some("/arch:AVX2"),
_ => None,
}
}
/// Returns default compiler flags for the specified build profile.
fn default_flags(profile: &str) -> &'static [&'static str] {
match profile {
"release" => &["/O2", "/DNDEBUG"],
"debug" => &["/Od", "/Zi", "/W4", "/DDCR_DEBUG", "/Oy-"],
_ => &[],
}
}
/// Compiles all sources into object files using parallel processing.
fn build_objects(
compiler: &str,
sources: &[String],
obj_dir: &Path,
ctx: &BuildContext,
obj_ext: &str,
) -> Result<Vec<String>, String> {
let objects: Vec<String> = sources
.iter()
.map(|s| common::object_path(obj_dir, s, obj_ext))
.collect();
common::parallel_build(
sources.len(),
|i| build_object(compiler, &sources[i], &objects[i], ctx),
ctx.codegen_units,
)?;
Ok(objects)
}
/// Compiles a single source file to an object file.
fn build_object(
compiler: &str,
source: &str,
obj_path: &str,
ctx: &BuildContext,
) -> Result<(), String> {
if let Some(parent) = Path::new(obj_path).parent() {
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
}
if !common::needs_rebuild(source, obj_path) {
return Ok(());
}
let mut cmd = Command::new(compiler);
cmd.arg("/nologo");
let ext = Path::new(source)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
if is_cpp {
cmd.arg("/TP");
} else {
cmd.arg("/TC");
}
let std_val = if is_cpp && !ctx.cxx_standard.is_empty() {
ctx.cxx_standard
} else if !is_cpp && !ctx.standard.is_empty() {
ctx.standard
} else {
""
};
if !std_val.is_empty() {
let std_flag = msvc_standard_flag(if is_cpp { "c++" } else { "c" }, std_val)?;
cmd.arg(std_flag);
}
if let Some(flag) = msvc_arch_flag(ctx.platform) {
cmd.arg(flag);
}
if ctx.cflags.is_empty() {
for flag in default_flags(ctx.profile) {
cmd.arg(flag);
}
}
for flag in ctx.cflags {
cmd.arg(flag);
}
for dir in ctx.include_dirs {
cmd.arg(format!("/I{dir}"));
}
cmd.arg("/c").arg(source).arg(format!("/Fo:{}", obj_path));
cmd.arg("/showIncludes");
if std::env::var("DCR_DEBUG").is_ok() {
eprintln!("[dcr] {:?}", cmd);
}
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let mut headers = Vec::new();
let mut clean_stdout = String::new();
// Parse compiler output to extract include file notes for dependency generation
for line in stdout.lines() {
if let Some(stripped) = line.strip_prefix("Note: including file:") {
headers.push(stripped.trim().to_string());
} else if let Some(stripped) = line.strip_prefix("Примечание: включение файла:")
{
headers.push(stripped.trim().to_string());
} else {
clean_stdout.push_str(line);
clean_stdout.push('\n');
}
}
let _lock = common::get_output_lock().lock().unwrap();
if !output.status.success() {
eprint!("{}", clean_stdout);
eprint!("{}", stderr);
return Err("Build failed".to_string());
}
let trimmed_out = clean_stdout.trim();
let trimmed_err = stderr.trim();
let src_filename = Path::new(source)
.file_name()
.and_then(|v| v.to_str())
.unwrap_or("");
if !trimmed_out.is_empty() && trimmed_out != src_filename {
print!("{}", clean_stdout);
}
if !trimmed_err.is_empty() {
eprintln!("{}", trimmed_err);
}
let d_path = Path::new(obj_path).with_extension("d");
let mut d_content = format!("{}: \\\n", obj_path.replace('\\', "/"));
for h in headers {
let escaped = h.replace('\\', "/").replace(" ", "\\ ");
d_content.push_str(&format!(" {} \\\n", escaped));
}
fs::write(&d_path, d_content).map_err(|err| format!("d file error: {err}"))?;
Ok(())
}

284
src/core/build/cache.rs Normal file
View File

@@ -0,0 +1,284 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::core::build::builder::BuildContext;
use crate::core::build::common;
use sha2::{Digest, Sha256};
use std::fs;
use std::path::{Path, PathBuf};
/// Computes a build fingerprint by hashing context fields and file metadata (SHA-256).
///
/// Includes `dcr.toml` (required) and `dcr.lock` when present, plus path/size/mtime
/// for each source, header, and library file.
///
/// # Parameters
/// - `ctx`: Build context (profile, tools, flags, dirs, …).
/// - `sources` / `headers` / `lib_files`: Inputs whose metadata enters the hash.
///
/// # Returns
/// Hex-encoded fingerprint, or an error if `dcr.toml` or a listed file cannot be read.
pub(crate) fn compute_build_fingerprint(
ctx: &BuildContext,
sources: &[String],
headers: &[PathBuf],
lib_files: &[PathBuf],
) -> Result<String, String> {
let mut hasher = Sha256::new();
hasher.update(ctx.profile.as_bytes());
hasher.update(ctx.project_name.as_bytes());
hasher.update(ctx.compiler.as_bytes());
hasher.update(ctx.language.as_bytes());
hasher.update(ctx.standard.as_bytes());
hasher.update(ctx.kind.as_bytes());
if let Some(v) = ctx.target_dir {
hasher.update(v.as_bytes());
}
if let Some(v) = ctx.platform {
hasher.update(v.as_bytes());
}
if let Some(v) = ctx.linker {
hasher.update(v.as_bytes());
}
if let Some(v) = ctx.archiver {
hasher.update(v.as_bytes());
}
for value in ctx.include_dirs {
hasher.update(value.as_bytes());
}
for value in ctx.lib_dirs {
hasher.update(value.as_bytes());
}
for value in ctx.libs {
hasher.update(value.as_bytes());
}
for value in ctx.cflags {
hasher.update(value.as_bytes());
}
for value in ctx.ldflags {
hasher.update(value.as_bytes());
}
// Include project metadata from dcr.toml in the fingerprint
let toml =
fs::read_to_string("dcr.toml").map_err(|err| format!("Failed to read dcr.toml: {err}"))?;
hasher.update(toml.as_bytes());
if let Ok(lock) = fs::read_to_string("dcr.lock") {
hasher.update(lock.as_bytes());
}
// Include source, header, and library files in the fingerprint
for source in sources {
let path = Path::new(source);
update_hasher_with_file(&mut hasher, path)?;
}
for header in headers {
update_hasher_with_file(&mut hasher, header)?;
}
for lib in lib_files {
update_hasher_with_file(&mut hasher, lib)?;
}
Ok(to_hex(&hasher.finalize()))
}
/// Whether the build can be skipped: main artifact exists and cache hash matches.
///
/// # Parameters
/// - `ctx`: Used to locate the output artifact and `.dcr-build.hash`.
/// - `fingerprint`: Fresh hash from [`compute_build_fingerprint`].
///
/// # Returns
/// `true` only when both the artifact and matching cache file are present.
pub(crate) fn should_skip_build(ctx: &BuildContext, fingerprint: &str) -> bool {
let output = build_output_path(ctx);
if !Path::new(&output).is_file() {
return false;
}
let cache_path = build_cache_path(ctx.profile, ctx.target_dir);
let cached = fs::read_to_string(cache_path).unwrap_or_default();
cached.trim() == fingerprint
}
/// Persists the build fingerprint to the cache file on disk.
pub(crate) fn write_build_fingerprint(ctx: &BuildContext, fingerprint: &str) -> Result<(), String> {
let cache_path = build_cache_path(ctx.profile, ctx.target_dir);
if let Some(parent) = cache_path.parent() {
fs::create_dir_all(parent).map_err(|err| format!("Failed to create cache dir: {err}"))?;
}
fs::write(cache_path, format!("{fingerprint}\n"))
.map_err(|err| format!("Failed to write cache: {err}"))
}
/// Returns the path to the build cache hash file based on profile and target directory.
fn build_cache_path(profile: &str, target_dir: Option<&str>) -> PathBuf {
match target_dir {
Some(dir) => Path::new(dir).join(".dcr-build.hash"),
None => Path::new("./target").join(profile).join(".dcr-build.hash"),
}
}
/// Resolves the output path for the build artifact using the provided context.
pub fn build_output_path(ctx: &BuildContext) -> String {
crate::core::build::builder::artifact::resolve_artifact_path(
ctx.kind,
ctx.profile,
ctx.project_name,
ctx.target_dir,
ctx.output_filename,
ctx.output_extension,
)
.unwrap_or_else(|| {
crate::platform::bin_path(
ctx.profile,
ctx.output_filename.unwrap_or(ctx.project_name),
ctx.target_dir,
)
})
}
/// Collects header files from source roots and include directories, filtering out excluded paths.
pub(crate) fn collect_header_files(
ctx: &BuildContext,
project_root: &Path,
) -> Result<Vec<PathBuf>, String> {
let mut out = Vec::new();
let mut roots = Vec::new();
if ctx.source_roots.is_empty() {
roots.push(project_root.join("src"));
} else {
roots.extend(ctx.source_roots.iter().cloned());
}
let abs_root = project_root
.canonicalize()
.unwrap_or_else(|_| project_root.to_path_buf());
for dir in ctx.include_dirs {
let p = Path::new(dir).to_path_buf();
if let Ok(abs_p) = p.canonicalize() {
if abs_p.starts_with(&abs_root) {
roots.push(abs_p);
}
} else if p.starts_with(&abs_root) {
roots.push(p);
}
}
for root in roots {
if !root.exists() {
continue;
}
if root.is_file() {
if is_header_file(&root) {
out.push(root);
}
} else {
collect_header_files_rec(&root, &mut out, ctx.exclude_dirs, ctx.include_paths)?;
}
}
out.sort();
out.dedup();
Ok(out)
}
/// Recursively scans a directory to collect header files, respecting exclusion rules.
fn collect_header_files_rec(
dir: &Path,
out: &mut Vec<PathBuf>,
exclude_dirs: &[PathBuf],
include_paths: &[String],
) -> Result<(), String> {
if common::is_excluded(dir, exclude_dirs, include_paths) && include_paths.is_empty() {
return Ok(());
}
for entry in fs::read_dir(dir).map_err(|err| format!("read_dir error: {err}"))? {
let entry = entry.map_err(|err| format!("read_dir error: {err}"))?;
let path = entry.path();
if path.is_dir() {
if common::is_excluded(&path, exclude_dirs, include_paths) && include_paths.is_empty() {
continue;
}
collect_header_files_rec(&path, out, exclude_dirs, include_paths)?;
continue;
}
if !path.is_file() {
continue;
}
if common::is_excluded(&path, exclude_dirs, include_paths) {
continue;
}
if is_header_file(&path) {
out.push(path);
}
}
Ok(())
}
/// Checks if the given path has a header file extension.
fn is_header_file(path: &Path) -> bool {
let ext = path.extension().and_then(|v| v.to_str()).unwrap_or("");
matches!(ext, "h" | "hpp" | "hh" | "hxx" | "inc")
}
/// Collects library files from lib directories using platform-specific candidates.
pub(crate) fn collect_lib_files(ctx: &BuildContext) -> Vec<PathBuf> {
let mut out = Vec::new();
for dir in ctx.lib_dirs {
let dir_path = Path::new(dir);
for lib in ctx.libs {
for candidate in lib_candidates(lib) {
let path = dir_path.join(candidate);
if path.is_file() {
out.push(path);
}
}
}
}
out
}
/// Candidate library filenames for the **host** OS (`cfg!(target_os = …)`).
fn lib_candidates(name: &str) -> Vec<String> {
if cfg!(target_os = "windows") {
return vec![format!("{name}.lib")];
}
if cfg!(target_os = "macos") {
return vec![
format!("lib{name}.a"),
format!("lib{name}.dylib"),
format!("lib{name}.so"),
];
}
vec![
format!("lib{name}.a"),
format!("lib{name}.so"),
format!("lib{name}.so.0"),
]
}
/// Updates the hasher with the file's path, size, and last modification time.
fn update_hasher_with_file(hasher: &mut Sha256, path: &Path) -> Result<(), String> {
hasher.update(path.to_string_lossy().as_bytes());
let meta = fs::metadata(path).map_err(|err| format!("source read error: {err}"))?;
hasher.update(meta.len().to_le_bytes());
if let Ok(modified) = meta.modified()
&& let Ok(duration) = modified.duration_since(std::time::UNIX_EPOCH)
{
hasher.update(duration.as_nanos().to_le_bytes());
}
Ok(())
}
/// Converts a byte slice containing the SHA256 hash to a hexadecimal string.
fn to_hex(bytes: &[u8]) -> String {
crate::utils::fs::to_hex(bytes)
}

1050
src/core/build/common.rs Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More