mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 09:48:31 +00:00
Compare commits
12 Commits
v0.9.0
...
cb6ae3dcc7
| Author | SHA1 | Date | |
|---|---|---|---|
| cb6ae3dcc7 | |||
|
|
10123ddedb | ||
|
|
0a140c75d8 | ||
|
|
aa1d4ba807 | ||
|
|
991d1c20af | ||
|
|
df674defe1 | ||
| 7d2340fb52 | |||
| 6c79340736 | |||
| be14cfb8e2 | |||
| 3305915c07 | |||
| 5cb51878a5 | |||
| d587eba5a4 |
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -1191,6 +1191,7 @@ jobs:
|
||||
- name: Update Formula/dcr.rb
|
||||
run: |
|
||||
VERSION="${{ needs.setup.outputs.version }}"
|
||||
|
||||
cat > Formula/dcr.rb <<FORMULA
|
||||
class Dcr < Formula
|
||||
desc "Cargo-like utility to manage C/C++ projects"
|
||||
@@ -1199,37 +1200,36 @@ jobs:
|
||||
|
||||
on_macos do
|
||||
on_intel do
|
||||
url "https://github.com/dexoron/dcr/releases/download/v#{version}/dcr-x86_64-apple-darwin-#{version}"
|
||||
url "https://github.com/dexoron/dcr/releases/download/v${VERSION}/dcr-x86_64-apple-darwin-${VERSION}"
|
||||
sha256 "${{ steps.sha.outputs.macos_x86 }}"
|
||||
end
|
||||
|
||||
on_arm do
|
||||
url "https://github.com/dexoron/dcr/releases/download/v#{version}/dcr-aarch64-apple-darwin-#{version}"
|
||||
url "https://github.com/dexoron/dcr/releases/download/v${VERSION}/dcr-aarch64-apple-darwin-${VERSION}"
|
||||
sha256 "${{ steps.sha.outputs.macos_arm }}"
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
on_intel do
|
||||
url "https://github.com/dexoron/dcr/releases/download/v#{version}/dcr-x86_64-unknown-linux-gnu-#{version}"
|
||||
url "https://github.com/dexoron/dcr/releases/download/v${VERSION}/dcr-x86_64-unknown-linux-gnu-${VERSION}"
|
||||
sha256 "${{ steps.sha.outputs.linux_x86 }}"
|
||||
end
|
||||
|
||||
on_arm do
|
||||
url "https://github.com/dexoron/dcr/releases/download/v#{version}/dcr-aarch64-unknown-linux-gnu-#{version}"
|
||||
url "https://github.com/dexoron/dcr/releases/download/v${VERSION}/dcr-aarch64-unknown-linux-gnu-${VERSION}"
|
||||
sha256 "${{ steps.sha.outputs.linux_arm }}"
|
||||
end
|
||||
end
|
||||
|
||||
def install
|
||||
bin.install Dir["dcr*"].first => "dcr"
|
||||
end
|
||||
|
||||
resource "man-pages" do
|
||||
url "https://github.com/dexoron/dcr/releases/download/v#{version}/dcr-man-pages-#{version}.tar.gz"
|
||||
url "https://github.com/dexoron/dcr/releases/download/v${VERSION}/dcr-man-pages-${VERSION}.tar.gz"
|
||||
sha256 "${{ steps.sha.outputs.man_pages }}"
|
||||
end
|
||||
|
||||
def install
|
||||
bin.install Dir["dcr*"].first => "dcr"
|
||||
|
||||
resource("man-pages").stage do
|
||||
man1.install Dir["usr/share/man/man1/*.1"]
|
||||
end
|
||||
@@ -1326,4 +1326,4 @@ jobs:
|
||||
|
||||
aws s3 sync dist/ "s3://${{ secrets.R2_BUCKET_NAME }}/${TAG}/" \
|
||||
--endpoint-url https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com \
|
||||
--region auto
|
||||
--region auto
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,5 +1,35 @@
|
||||
# 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
|
||||
|
||||
287
Cargo.lock
generated
287
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -11,6 +20,56 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
@@ -106,6 +165,12 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -154,11 +219,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dcr"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"env_logger",
|
||||
"fatfs",
|
||||
"glob",
|
||||
"log",
|
||||
"owo-colors",
|
||||
"self-replace",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -168,6 +236,37 @@ dependencies = [
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -198,7 +297,30 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -475,12 +597,54 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"jiff-core",
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
|
||||
dependencies = [
|
||||
"jiff-core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.103"
|
||||
@@ -518,9 +682,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.30"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -570,12 +734,24 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -588,6 +764,21 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@@ -604,7 +795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -631,6 +822,35 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
@@ -757,7 +977,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -828,6 +1048,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
@@ -836,7 +1067,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -852,6 +1083,26 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
@@ -976,6 +1227,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -1032,7 +1289,7 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -1100,7 +1357,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1111,7 +1368,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1271,7 +1528,7 @@ dependencies = [
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
@@ -1287,7 +1544,7 @@ dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
@@ -1354,7 +1611,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -1375,7 +1632,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -1415,7 +1672,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dcr"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
||||
@@ -75,6 +75,11 @@ 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"
|
||||
@@ -97,4 +102,4 @@ debug = "line-tables-only"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 1
|
||||
debug = false
|
||||
debug = false
|
||||
|
||||
@@ -4,6 +4,13 @@ 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`.
|
||||
|
||||
@@ -15,6 +15,8 @@ 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:
|
||||
@@ -22,6 +24,7 @@ Source prefixes:
|
||||
- `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:
|
||||
@@ -31,6 +34,11 @@ Flags:
|
||||
|
||||
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.
|
||||
|
||||
@@ -111,7 +111,11 @@ Never fails the command — warnings only. Pre-release suffixes (`1.0.0-dev`) ar
|
||||
| `qt` | `false` | Enable automatic Qt meta-object handling (MOC, UIC, RCC) |
|
||||
|
||||
Settings from raw config (not in typed struct):
|
||||
- `pkg_config` — list of pkg-config packages
|
||||
- `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
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ sidebar_label: Dependencies
|
||||
|
||||
## Formats
|
||||
|
||||
The `[dependencies]` section supports three formats:
|
||||
The `[dependencies]` section supports registry, path, Git, and `pkg-config` dependencies:
|
||||
|
||||
### String (registry)
|
||||
|
||||
@@ -44,6 +44,23 @@ names (`libs`):
|
||||
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.
|
||||
|
||||
@@ -25,6 +25,9 @@ Generic git repository.
|
||||
.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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.TH DCR 1 "2026-06-02" "dcr 0.7.0" "DCR Manual"
|
||||
.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
|
||||
@@ -120,6 +120,9 @@ Generic git repository.
|
||||
.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
|
||||
|
||||
@@ -15,17 +15,18 @@
|
||||
// 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::log::error;
|
||||
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`, or `version_from_registry` is expected to be set,
|
||||
/// 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`.
|
||||
@@ -42,6 +43,8 @@ pub struct AddArgs {
|
||||
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`.
|
||||
@@ -71,6 +74,7 @@ pub fn add(args: &[String]) -> i32 {
|
||||
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);
|
||||
@@ -88,7 +92,7 @@ pub fn add(args: &[String]) -> i32 {
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
error!("Failed to determine current directory");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -96,11 +100,11 @@ pub fn add(args: &[String]) -> i32 {
|
||||
let root = match find_project_root(&start_dir) {
|
||||
Ok(Some(dir)) => dir,
|
||||
Ok(None) => {
|
||||
error("dcr.toml file not found");
|
||||
error!("dcr.toml file not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -108,7 +112,7 @@ pub fn add(args: &[String]) -> i32 {
|
||||
let mut config = match Config::open(&root.join("dcr.toml").to_string_lossy()) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error(&err.to_string());
|
||||
error!("{err}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -133,6 +137,17 @@ pub fn add(args: &[String]) -> i32 {
|
||||
}
|
||||
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));
|
||||
@@ -140,13 +155,13 @@ pub fn add(args: &[String]) -> i32 {
|
||||
} else if let Some(version) = add_args.version_from_registry {
|
||||
Value::String(version)
|
||||
} else {
|
||||
error("Dependency source (path or git) must be provided");
|
||||
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(&format!("Failed to update dcr.toml: {}", err));
|
||||
error!("Failed to update dcr.toml: {err}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -165,12 +180,13 @@ pub fn add(args: &[String]) -> i32 {
|
||||
/// `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!("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);
|
||||
}
|
||||
|
||||
@@ -183,7 +199,7 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
let mut iter = args.iter();
|
||||
if let Some(n) = iter.next() {
|
||||
if n.starts_with("--") {
|
||||
error("First argument must be the dependency name");
|
||||
error!("First argument must be the dependency name");
|
||||
return Err(1);
|
||||
}
|
||||
name = n.clone();
|
||||
@@ -191,7 +207,7 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
|
||||
if let Some(s) = iter.next() {
|
||||
if s.starts_with("--") {
|
||||
error("Second argument must be the dependency source");
|
||||
error!("Second argument must be the dependency source");
|
||||
return Err(1);
|
||||
}
|
||||
source_spec = s.clone();
|
||||
@@ -210,7 +226,7 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
)
|
||||
})
|
||||
.map_err(|(msg, code)| {
|
||||
error(&msg);
|
||||
error!("{msg}");
|
||||
code
|
||||
})?;
|
||||
return Ok(AddArgs {
|
||||
@@ -221,10 +237,11 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
tag: None,
|
||||
rev: None,
|
||||
version_from_registry: Some(version.to_string()),
|
||||
pkg_config: None,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error(&format!("Cannot add `{}` without source: {}", name, e));
|
||||
error!("Cannot add `{}` without source: {}", name, e);
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
@@ -235,26 +252,26 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
"--branch" => {
|
||||
branch = iter.next().cloned();
|
||||
if branch.is_none() {
|
||||
error("--branch requires a value");
|
||||
error!("--branch requires a value");
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
"--tag" => {
|
||||
tag = iter.next().cloned();
|
||||
if tag.is_none() {
|
||||
error("--tag requires a value");
|
||||
error!("--tag requires a value");
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
"--rev" => {
|
||||
rev = iter.next().cloned();
|
||||
if rev.is_none() {
|
||||
error("--rev requires a value");
|
||||
error!("--rev requires a value");
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error(&format!("Unknown argument: {}", arg));
|
||||
error!("Unknown argument: {arg}");
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
@@ -262,9 +279,25 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
|
||||
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("path:") {
|
||||
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));
|
||||
@@ -294,8 +327,8 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
{
|
||||
path = Some(source_spec);
|
||||
} else {
|
||||
error(
|
||||
"Source must be a path, a prefixed source (path:, git:, github:, gitlab:), or a full URL",
|
||||
error!(
|
||||
"Source must be a path, pkg-config:, a prefixed source (path:, git:, github:, gitlab:), or a full URL",
|
||||
);
|
||||
return Err(1);
|
||||
}
|
||||
@@ -308,5 +341,6 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
||||
tag,
|
||||
rev,
|
||||
version_from_registry: None,
|
||||
pkg_config,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// 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};
|
||||
@@ -27,7 +29,6 @@ use crate::utils::build::{
|
||||
normalize_kind, resolve_artifact_target_dir, resolve_compiler,
|
||||
};
|
||||
use crate::utils::fs::{canonicalize_path, find_project_root};
|
||||
use crate::utils::log::error;
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_YELLOW, colored, printc};
|
||||
use std::io::IsTerminal;
|
||||
use std::path::Path;
|
||||
@@ -173,18 +174,18 @@ pub fn build(args: &[String]) -> i32 {
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
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");
|
||||
error!("dcr.toml file not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -242,7 +243,7 @@ pub fn build(args: &[String]) -> i32 {
|
||||
}
|
||||
Err(err) => {
|
||||
common::finish_progress_line();
|
||||
error(&err.message);
|
||||
error!("{}", err.message);
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
// 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::log::{error, warn};
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use glob::glob;
|
||||
use std::fs;
|
||||
@@ -56,25 +57,25 @@ pub fn clean(args: &[String]) -> i32 {
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
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");
|
||||
error!("dcr.toml file not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
let flags = match parse_clean_flags(args) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => {
|
||||
error(&msg);
|
||||
error!("{msg}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -83,7 +84,7 @@ pub fn clean(args: &[String]) -> i32 {
|
||||
match with_dir(&root, || clean_from_root(&root, &flags)) {
|
||||
Ok(()) => 0,
|
||||
Err(msg) => {
|
||||
error(&msg);
|
||||
error!("{msg}");
|
||||
1
|
||||
}
|
||||
}
|
||||
@@ -229,7 +230,7 @@ fn clean_project_at(
|
||||
target_items.contains(&profile.to_string())
|
||||
};
|
||||
if !dir_exists {
|
||||
warn(&format!("Directory target/{} not found", target_dir));
|
||||
warn!("Directory target/{target_dir} not found");
|
||||
} else {
|
||||
println!(" Profile: {}", colored(profile, BOLD_GREEN));
|
||||
if let Some(t) = target {
|
||||
@@ -253,7 +254,7 @@ fn clean_project_at(
|
||||
colored("\n ✔", BOLD_GREEN)
|
||||
);
|
||||
} else {
|
||||
warn("Directory target not found");
|
||||
warn!("Directory target not found");
|
||||
}
|
||||
clean_custom_paths(&config, "debug")?;
|
||||
clean_custom_paths(&config, "release")?;
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
// 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::log::{error, warn};
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
@@ -64,21 +65,21 @@ pub fn flag_update(args: &[String]) -> i32 {
|
||||
}
|
||||
|
||||
if !args.is_empty() {
|
||||
warn("Command does not support additional arguments");
|
||||
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");
|
||||
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(¤t_exe) {
|
||||
warn("This dcr binary is managed by pacman/AUR");
|
||||
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}"
|
||||
);
|
||||
@@ -91,7 +92,7 @@ pub fn flag_update(args: &[String]) -> i32 {
|
||||
let release = match fetch_latest_release() {
|
||||
Ok(release) => release,
|
||||
Err(err) => {
|
||||
error(&format!("Failed to check for updates: {err}"));
|
||||
error!("Failed to check for updates: {err}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -109,14 +110,14 @@ pub fn flag_update(args: &[String]) -> i32 {
|
||||
.iter()
|
||||
.find(|asset| candidate_names.iter().any(|name| name == &asset.name))
|
||||
else {
|
||||
error(&format!("Binary for target {target} not found"));
|
||||
error!("Binary for target {target} not found");
|
||||
return 1;
|
||||
};
|
||||
|
||||
let bytes = match download_asset(&asset.browser_download_url) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
error(&format!("Failed to download update: {err}"));
|
||||
error!("Failed to download update: {err}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -124,7 +125,7 @@ pub fn flag_update(args: &[String]) -> i32 {
|
||||
let temp_path = temp_binary_path(¤t_exe);
|
||||
|
||||
if fs::write(&temp_path, &bytes).is_err() {
|
||||
error("Failed to write temporary binary");
|
||||
error!("Failed to write temporary binary");
|
||||
return 1;
|
||||
}
|
||||
set_executable_permissions(&temp_path);
|
||||
@@ -132,7 +133,7 @@ pub fn flag_update(args: &[String]) -> i32 {
|
||||
// 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");
|
||||
error!("Failed to replace current binary");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
// 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};
|
||||
use crate::utils::log::warn;
|
||||
|
||||
/// Flags shared by the `build` and `run` CLI commands.
|
||||
pub struct BuildRunFlags {
|
||||
@@ -82,7 +83,7 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
||||
while let Some(arg) = iter.next() {
|
||||
// DCR build/run options are long flags only (`--name`).
|
||||
if !arg.starts_with("--") {
|
||||
warn("Unknown argument");
|
||||
warn!("Unknown argument");
|
||||
return Err(1);
|
||||
}
|
||||
let candidate = arg.trim_start_matches("--");
|
||||
@@ -106,7 +107,7 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
||||
if let Some(w) = iter.next() {
|
||||
workspace = Some(w.clone());
|
||||
} else {
|
||||
warn("--workspace requires a value");
|
||||
warn!("--workspace requires a value");
|
||||
return Err(1);
|
||||
}
|
||||
continue;
|
||||
@@ -115,7 +116,7 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
||||
if let Some(t) = iter.next() {
|
||||
target = Some(t.clone());
|
||||
} else {
|
||||
warn("--target requires a value");
|
||||
warn!("--target requires a value");
|
||||
return Err(1);
|
||||
}
|
||||
continue;
|
||||
@@ -123,13 +124,13 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
||||
// Profile names come from config::flags; only one profile may be set.
|
||||
if flags(candidate).is_some() {
|
||||
if profile != PROFILE {
|
||||
warn("Duplicate profile flag");
|
||||
warn!("Duplicate profile flag");
|
||||
return Err(1);
|
||||
}
|
||||
profile = candidate.to_string();
|
||||
continue;
|
||||
}
|
||||
warn("Unknown build flag");
|
||||
warn!("Unknown build flag");
|
||||
return Err(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
// 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::log::error;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use glob::glob;
|
||||
use std::process::Command;
|
||||
@@ -88,11 +89,11 @@ pub fn fmt(args: &[String]) -> i32 {
|
||||
0
|
||||
}
|
||||
Ok(s) => {
|
||||
error(&format!("clang-format failed with status: {}", s));
|
||||
error!("clang-format failed with status: {s}");
|
||||
1
|
||||
}
|
||||
Err(e) => {
|
||||
error(&format!("Failed to execute clang-format: {}", e));
|
||||
error!("Failed to execute clang-format: {e}");
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// 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::builder::BuildContext;
|
||||
use crate::core::build::builder::artifact::{absolute_artifact_path, resolve_artifact_path};
|
||||
use crate::core::build::builder::collect_sources;
|
||||
@@ -29,7 +31,6 @@ use crate::utils::build::{
|
||||
use crate::utils::fs::{
|
||||
absolute_join, atomic_write, canonicalize_path, ensure_dcr_dir, find_project_root,
|
||||
};
|
||||
use crate::utils::log::error;
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -42,11 +43,13 @@ struct GenDeps {
|
||||
include_dirs: Vec<String>,
|
||||
lib_dirs: Vec<String>,
|
||||
libs: Vec<String>,
|
||||
cflags: Vec<String>,
|
||||
ldflags: Vec<String>,
|
||||
}
|
||||
|
||||
/// Collects path-dependency include/lib/libs from `dcr.toml` for code generation.
|
||||
/// Collects dependency metadata from `dcr.toml` for code generation.
|
||||
///
|
||||
/// Skips `system` deps and non-path entries. Falls back to common include/lib
|
||||
/// Resolves path layouts and pkg-config flags while skipping system deps. Falls back to common include/lib
|
||||
/// directory names only when those directories exist.
|
||||
fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) -> GenDeps {
|
||||
let deps_val = match config.get("dependencies") {
|
||||
@@ -56,6 +59,8 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
||||
include_dirs: vec![],
|
||||
lib_dirs: vec![],
|
||||
libs: vec![],
|
||||
cflags: vec![],
|
||||
ldflags: vec![],
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -66,6 +71,8 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
||||
include_dirs: vec![],
|
||||
lib_dirs: vec![],
|
||||
libs: vec![],
|
||||
cflags: vec![],
|
||||
ldflags: vec![],
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -73,12 +80,37 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
||||
let mut include_dirs = Vec::new();
|
||||
let mut lib_dirs = Vec::new();
|
||||
let mut libs = Vec::new();
|
||||
let mut cflags = Vec::new();
|
||||
let mut ldflags = Vec::new();
|
||||
|
||||
for (name, value) in deps_table {
|
||||
let tbl = match value.as_table() {
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
let pkg_names = tbl
|
||||
.get("pkg-config")
|
||||
.or_else(|| tbl.get("pkg_config"))
|
||||
.map(|raw| {
|
||||
raw.as_str()
|
||||
.map(|s| vec![s.to_string()])
|
||||
.or_else(|| {
|
||||
raw.as_array().map(|a| {
|
||||
a.iter()
|
||||
.filter_map(|v| v.as_str().map(str::to_string))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if !pkg_names.is_empty() {
|
||||
let (flags, link_flags) =
|
||||
crate::utils::build::resolve_pkg_config_flags_lossy(&pkg_names, &[], &[]);
|
||||
cflags.extend(flags);
|
||||
ldflags.extend(link_flags);
|
||||
continue;
|
||||
}
|
||||
// skip system deps
|
||||
if tbl.get("system").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
continue;
|
||||
@@ -169,6 +201,8 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
||||
include_dirs,
|
||||
lib_dirs,
|
||||
libs,
|
||||
cflags,
|
||||
ldflags,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +291,7 @@ pub fn r#gen(args: &[String]) -> i32 {
|
||||
"vscode" => gen_vscode(rest),
|
||||
"clion" => gen_clion(rest),
|
||||
_ => {
|
||||
error(&format!("Unknown gen subcommand: {subcommand}"));
|
||||
error!("Unknown gen subcommand: {subcommand}");
|
||||
1
|
||||
}
|
||||
}
|
||||
@@ -350,8 +384,10 @@ fn collect_project_info_inner(
|
||||
});
|
||||
|
||||
let resolved = resolve_deps_for_gen(&config, profile, &root);
|
||||
let (resolved_cflags, resolved_ldflags) =
|
||||
let (mut resolved_cflags, mut resolved_ldflags) =
|
||||
resolve_pkg_config_flags_lossy(&pkg_configs, &base_cflags, &base_ldflags);
|
||||
resolved_cflags.extend(resolved.cflags.clone());
|
||||
resolved_ldflags.extend(resolved.ldflags.clone());
|
||||
|
||||
// Build exclude/include pattern lists (same logic as cli::build)
|
||||
let mut combined_excludes: Vec<PathBuf> = Vec::new();
|
||||
@@ -818,7 +854,7 @@ fn gen_project_info(args: &[String]) -> i32 {
|
||||
let all = match collect_all(&root, &profile) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -940,7 +976,7 @@ fn gen_compile_commands(args: &[String]) -> i32 {
|
||||
let all = match collect_all(&root, &profile) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -953,7 +989,7 @@ fn gen_compile_commands(args: &[String]) -> i32 {
|
||||
/// Used by `compile-commands`, `vscode`, and `clion`.
|
||||
fn gen_compile_commands_inner(root: &Path, profile: &str, all: &[ProjectInfo], quiet: bool) -> i32 {
|
||||
if let Err(e) = ensure_dcr_dir(root) {
|
||||
error(&format!("Failed to create .dcr/: {e}"));
|
||||
error!("Failed to create .dcr/: {e}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -974,7 +1010,7 @@ fn gen_compile_commands_inner(root: &Path, profile: &str, all: &[ProjectInfo], q
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
error(&format!("Failed to write compile_commands.json: {e}"));
|
||||
error!("Failed to write compile_commands.json: {e}");
|
||||
1
|
||||
}
|
||||
}
|
||||
@@ -1128,7 +1164,7 @@ fn gen_vscode(args: &[String]) -> i32 {
|
||||
let all = match collect_all(&root, &profile) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -1141,13 +1177,13 @@ fn gen_vscode(args: &[String]) -> i32 {
|
||||
|
||||
let vscode_dir = root.join(".vscode");
|
||||
if let Err(e) = std::fs::create_dir_all(&vscode_dir) {
|
||||
error(&format!("Failed to create .vscode/: {e}"));
|
||||
error!("Failed to create .vscode/: {e}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// tasks.json
|
||||
if let Err(e) = std::fs::write(vscode_dir.join("tasks.json"), gen_tasks_json()) {
|
||||
error(&format!("Failed to write tasks.json: {e}"));
|
||||
error!("Failed to write tasks.json: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", vscode_dir.join("tasks.json").display());
|
||||
@@ -1155,7 +1191,7 @@ fn gen_vscode(args: &[String]) -> i32 {
|
||||
// launch.json — one entry per binary target
|
||||
let launch = gen_launch_json(&all, &root);
|
||||
if let Err(e) = std::fs::write(vscode_dir.join("launch.json"), launch) {
|
||||
error(&format!("Failed to write launch.json: {e}"));
|
||||
error!("Failed to write launch.json: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", vscode_dir.join("launch.json").display());
|
||||
@@ -1163,14 +1199,14 @@ fn gen_vscode(args: &[String]) -> i32 {
|
||||
// settings.json (clangd compile-commands-dir)
|
||||
let settings = gen_settings_json(&root);
|
||||
if let Err(e) = std::fs::write(vscode_dir.join("settings.json"), settings) {
|
||||
error(&format!("Failed to write settings.json: {e}"));
|
||||
error!("Failed to write settings.json: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", vscode_dir.join("settings.json").display());
|
||||
|
||||
// extensions.json — disable cpptools, recommend clangd
|
||||
if let Err(e) = std::fs::write(vscode_dir.join("extensions.json"), gen_extensions_json()) {
|
||||
error(&format!("Failed to write extensions.json: {e}"));
|
||||
error!("Failed to write extensions.json: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", vscode_dir.join("extensions.json").display());
|
||||
@@ -1418,7 +1454,7 @@ fn gen_clion(args: &[String]) -> i32 {
|
||||
let all = match collect_all(&root, &profile) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -1431,19 +1467,19 @@ fn gen_clion(args: &[String]) -> i32 {
|
||||
|
||||
let idea_dir = root.join(".idea");
|
||||
if let Err(e) = std::fs::create_dir_all(&idea_dir) {
|
||||
error(&format!("Failed to create .idea/: {e}"));
|
||||
error!("Failed to create .idea/: {e}");
|
||||
return 1;
|
||||
}
|
||||
let run_configs_dir = idea_dir.join("runConfigurations");
|
||||
if let Err(e) = std::fs::create_dir_all(&run_configs_dir) {
|
||||
error(&format!("Failed to create .idea/runConfigurations/: {e}"));
|
||||
error!("Failed to create .idea/runConfigurations/: {e}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// externalTools.xml
|
||||
let ext_tools = gen_clion_external_tools();
|
||||
if let Err(e) = std::fs::write(idea_dir.join("externalTools.xml"), ext_tools) {
|
||||
error(&format!("Failed to write externalTools.xml: {e}"));
|
||||
error!("Failed to write externalTools.xml: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", idea_dir.join("externalTools.xml").display());
|
||||
@@ -1451,7 +1487,7 @@ fn gen_clion(args: &[String]) -> i32 {
|
||||
// customTargets.xml
|
||||
let targets = gen_clion_custom_targets();
|
||||
if let Err(e) = std::fs::write(idea_dir.join("customTargets.xml"), targets) {
|
||||
error(&format!("Failed to write customTargets.xml: {e}"));
|
||||
error!("Failed to write customTargets.xml: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", idea_dir.join("customTargets.xml").display());
|
||||
@@ -1459,7 +1495,7 @@ fn gen_clion(args: &[String]) -> i32 {
|
||||
// misc.xml — point CLion at compile_commands.json
|
||||
let misc = gen_clion_misc_xml(&root);
|
||||
if let Err(e) = std::fs::write(idea_dir.join("misc.xml"), misc) {
|
||||
error(&format!("Failed to write misc.xml: {e}"));
|
||||
error!("Failed to write misc.xml: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", idea_dir.join("misc.xml").display());
|
||||
@@ -1467,7 +1503,7 @@ fn gen_clion(args: &[String]) -> i32 {
|
||||
// .idea/.gitignore
|
||||
let gitignore = "# CLion generated files\nworkspace.xml\n*.iml\n";
|
||||
if let Err(e) = std::fs::write(idea_dir.join(".gitignore"), gitignore) {
|
||||
error(&format!("Failed to write .idea/.gitignore: {e}"));
|
||||
error!("Failed to write .idea/.gitignore: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", idea_dir.join(".gitignore").display());
|
||||
@@ -1481,7 +1517,7 @@ fn gen_clion(args: &[String]) -> i32 {
|
||||
let fname = format!("{}.xml", sanitize_filename(&info.name));
|
||||
let path = run_configs_dir.join(&fname);
|
||||
if let Err(e) = std::fs::write(&path, xml) {
|
||||
error(&format!("Failed to write runConfigurations/{fname}: {e}"));
|
||||
error!("Failed to write runConfigurations/{fname}: {e}");
|
||||
return 1;
|
||||
}
|
||||
println!("Generated {}", path.display());
|
||||
@@ -1685,18 +1721,18 @@ fn parse_gen_args(args: &[String]) -> Result<(PathBuf, String, bool), i32> {
|
||||
}
|
||||
|
||||
let start = std::env::current_dir().map_err(|_| {
|
||||
error("Failed to determine current directory");
|
||||
error!("Failed to determine current directory");
|
||||
1i32
|
||||
})?;
|
||||
|
||||
let root = match find_project_root(&start) {
|
||||
Ok(Some(r)) => canonicalize_path(&r),
|
||||
Ok(None) => {
|
||||
error("dcr.toml not found");
|
||||
error!("dcr.toml not found");
|
||||
return Err(1);
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
// 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::log::{error, warn};
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use toml::Value;
|
||||
|
||||
/// Initializes the current working directory as a new DCR project.
|
||||
///
|
||||
@@ -49,7 +49,7 @@ pub fn init(args: &[String]) -> i32 {
|
||||
if let Some(val) = iter.next() {
|
||||
vcs_str = Some(val.clone());
|
||||
} else {
|
||||
error("--vcs requires a value");
|
||||
error!("--vcs requires a value");
|
||||
return 1;
|
||||
}
|
||||
} else if let Some(stripped) = arg.strip_prefix("--vcs=") {
|
||||
@@ -60,7 +60,7 @@ pub fn init(args: &[String]) -> i32 {
|
||||
}
|
||||
|
||||
if !clean_args.is_empty() {
|
||||
warn("Command does not support additional arguments");
|
||||
warn!("Command does not support additional arguments");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -72,16 +72,15 @@ pub fn init(args: &[String]) -> i32 {
|
||||
.unwrap_or_else(|| "project".to_string());
|
||||
|
||||
if !items.is_empty() {
|
||||
error("Directory not empty");
|
||||
error!("Directory not empty");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if let Err(e) = validate_package_name(&project_name) {
|
||||
error(&format!(
|
||||
"Invalid project name `{}`: {}",
|
||||
error!(
|
||||
"Invalid project name `{}`: {e}",
|
||||
colored(&project_name, BOLD_CYAN),
|
||||
e
|
||||
));
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -91,18 +90,8 @@ pub fn init(args: &[String]) -> i32 {
|
||||
println!("Initializing the project in {cwd}");
|
||||
|
||||
// Create and initialize default manifest (dcr.toml).
|
||||
let mut config = match Config::new("./dcr.toml") {
|
||||
Ok(cfg) => cfg,
|
||||
Err(_) => {
|
||||
error("Failed to create dcr.toml");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
if config
|
||||
.edit("package.name", Value::String(project_name.clone()))
|
||||
.is_err()
|
||||
{
|
||||
error("Failed to write dcr.toml");
|
||||
if Config::create("./dcr.toml", Some(&project_name)).is_err() {
|
||||
error!("Failed to create dcr.toml");
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
@@ -113,18 +102,18 @@ pub fn init(args: &[String]) -> i32 {
|
||||
|
||||
// Create src directory and write template main.c file.
|
||||
if fs::create_dir("src").is_err() {
|
||||
error("Failed to create src/");
|
||||
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");
|
||||
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");
|
||||
error!("Failed to write src/main.c");
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
@@ -139,7 +128,7 @@ pub fn init(args: &[String]) -> i32 {
|
||||
match VcsKind::parse(vcs_val) {
|
||||
Ok(kind) => vcs_kind = kind,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -153,7 +142,7 @@ pub fn init(args: &[String]) -> i32 {
|
||||
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(&format!("Failed to initialize git repository: {}", e));
|
||||
warn!("Failed to initialize git repository: {e}");
|
||||
} else {
|
||||
println!(
|
||||
" {} Initialized git repository",
|
||||
@@ -161,14 +150,14 @@ pub fn init(args: &[String]) -> i32 {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn(
|
||||
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(&format!("Failed to update .gitignore: {e}"));
|
||||
warn!("Failed to update .gitignore: {e}");
|
||||
}
|
||||
|
||||
println!(
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
// 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::log::error;
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_YELLOW, colored, printc};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
@@ -46,7 +47,7 @@ pub fn lint(args: &[String]) -> i32 {
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
error!("Failed to determine current directory");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -54,11 +55,11 @@ pub fn lint(args: &[String]) -> i32 {
|
||||
let root = match find_project_root(&start_dir) {
|
||||
Ok(Some(r)) => r,
|
||||
Ok(None) => {
|
||||
error("dcr.toml not found");
|
||||
error!("dcr.toml not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -66,7 +67,7 @@ pub fn lint(args: &[String]) -> i32 {
|
||||
let config = match Config::open(&root.join("dcr.toml").to_string_lossy()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error(&format!("Failed to load dcr.toml: {e}"));
|
||||
error!("Failed to load dcr.toml: {e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -98,7 +99,7 @@ pub fn lint(args: &[String]) -> i32 {
|
||||
match common::collect_sources(&roots, &cxx_extensions, &exclude_dirs, &include_paths) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error(&format!("Failed to collect source files: {e}"));
|
||||
error!("Failed to collect source files: {e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -142,7 +143,7 @@ pub fn lint(args: &[String]) -> i32 {
|
||||
let output = match cmd.output() {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
error(&format!("Failed to execute clang-tidy: {}", e));
|
||||
error!("Failed to execute clang-tidy: {e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
// 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::log::{error, warn};
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use toml::Value;
|
||||
|
||||
/// Executes the `dcr new` command to scaffold a new C/C++ project directory structure.
|
||||
///
|
||||
@@ -51,7 +51,7 @@ pub fn new(args: &[String]) -> i32 {
|
||||
if let Some(val) = iter.next() {
|
||||
vcs_str = Some(val.clone());
|
||||
} else {
|
||||
error("--vcs requires a value");
|
||||
error!("--vcs requires a value");
|
||||
return 1;
|
||||
}
|
||||
} else if let Some(stripped) = arg.strip_prefix("--vcs=") {
|
||||
@@ -62,11 +62,11 @@ pub fn new(args: &[String]) -> i32 {
|
||||
}
|
||||
|
||||
if clean_args.is_empty() {
|
||||
error("Project name not specified");
|
||||
error!("Project name not specified");
|
||||
return 1;
|
||||
}
|
||||
if clean_args.len() > 1 {
|
||||
warn("Command does not support additional arguments");
|
||||
warn!("Command does not support additional arguments");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -77,19 +77,18 @@ pub fn new(args: &[String]) -> i32 {
|
||||
);
|
||||
|
||||
if let Err(e) = validate_package_name(project_name) {
|
||||
error(&format!(
|
||||
"Invalid project name `{}`: {}",
|
||||
error!(
|
||||
"Invalid project name `{}`: {e}",
|
||||
colored(project_name, BOLD_CYAN),
|
||||
e
|
||||
));
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if items.contains(project_name) {
|
||||
error(&format!(
|
||||
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",
|
||||
@@ -99,7 +98,7 @@ pub fn new(args: &[String]) -> i32 {
|
||||
}
|
||||
|
||||
if fs::create_dir(project_name).is_err() {
|
||||
error("Failed to create directory");
|
||||
error!("Failed to create directory");
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
@@ -110,18 +109,8 @@ pub fn new(args: &[String]) -> i32 {
|
||||
|
||||
// Initialize and write project configuration manifest (dcr.toml).
|
||||
let toml_path = format!("./{project_name}/dcr.toml");
|
||||
let mut config = match Config::new(&toml_path) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(_) => {
|
||||
error("Failed to create dcr.toml");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
if config
|
||||
.edit("package.name", Value::String(project_name.to_string()))
|
||||
.is_err()
|
||||
{
|
||||
error("Failed to write dcr.toml");
|
||||
if Config::create(&toml_path, Some(project_name)).is_err() {
|
||||
error!("Failed to create dcr.toml");
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
@@ -132,19 +121,19 @@ pub fn new(args: &[String]) -> i32 {
|
||||
|
||||
// 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/");
|
||||
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");
|
||||
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");
|
||||
error!("Failed to write src/main.c");
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
@@ -159,7 +148,8 @@ pub fn new(args: &[String]) -> i32 {
|
||||
match VcsKind::parse(vcs_val) {
|
||||
Ok(kind) => vcs_kind = kind,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -173,7 +163,7 @@ pub fn new(args: &[String]) -> i32 {
|
||||
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(&format!("Failed to initialize git repository: {}", e));
|
||||
warn!("Failed to initialize git repository: {e}");
|
||||
} else {
|
||||
println!(
|
||||
" {} Initialized git repository",
|
||||
@@ -181,7 +171,7 @@ pub fn new(args: &[String]) -> i32 {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn(
|
||||
warn!(
|
||||
"Git is not installed or not found in PATH. Skipping Git repository initialization.",
|
||||
);
|
||||
}
|
||||
@@ -189,7 +179,7 @@ pub fn new(args: &[String]) -> i32 {
|
||||
|
||||
let project_path = std::path::Path::new(project_name);
|
||||
if let Err(e) = crate::utils::fs::ensure_gitignore_has_dcr(project_path) {
|
||||
warn(&format!("Failed to update .gitignore: {e}"));
|
||||
warn!("Failed to update .gitignore: {e}");
|
||||
}
|
||||
|
||||
println!(
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// 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;
|
||||
@@ -22,7 +24,6 @@ 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::log::error;
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
@@ -91,18 +92,18 @@ pub fn run(args: &[String]) -> i32 {
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
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");
|
||||
error!("dcr.toml file not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -111,7 +112,8 @@ pub fn run(args: &[String]) -> i32 {
|
||||
}) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error(&err);
|
||||
error!("{err}");
|
||||
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -143,11 +145,12 @@ pub fn run(args: &[String]) -> i32 {
|
||||
) {
|
||||
Ok(Some(ws)) => ws,
|
||||
Ok(None) => {
|
||||
error("Workspace root has no members defined");
|
||||
error!("Workspace root has no members defined");
|
||||
return 1;
|
||||
}
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -159,9 +162,9 @@ pub fn run(args: &[String]) -> i32 {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
if let Some(name) = &flags.workspace {
|
||||
error(&format!("Workspace member '{name}' not found"));
|
||||
error!("Workspace member '{name}' not found");
|
||||
} else {
|
||||
error("No workspace member to run (set `main = true` on one member)");
|
||||
error!("No workspace member to run (set `main = true` on one member)");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -172,7 +175,8 @@ pub fn run(args: &[String]) -> i32 {
|
||||
}) {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
|
||||
1
|
||||
}
|
||||
};
|
||||
@@ -181,7 +185,7 @@ pub fn run(args: &[String]) -> i32 {
|
||||
match run_project(&root, &flags, None) {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
error(&e);
|
||||
error!("{e}");
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// 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;
|
||||
@@ -23,7 +25,6 @@ use crate::utils::build::{
|
||||
get_string_with_profile, resolve_compiler, resolve_pkg_config_flags,
|
||||
};
|
||||
use crate::utils::fs::{find_project_root, with_dir};
|
||||
use crate::utils::log::error;
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_RED, RESET, colored, printc};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -60,25 +61,25 @@ pub fn test(args: &[String]) -> i32 {
|
||||
profile = arg.trim_start_matches("--");
|
||||
continue;
|
||||
}
|
||||
error("Unknown argument");
|
||||
error!("Unknown argument");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
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");
|
||||
error!("dcr.toml file not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
error!("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -87,7 +88,8 @@ pub fn test(args: &[String]) -> i32 {
|
||||
match with_dir(&root, ensure_test_header) {
|
||||
Ok(()) => {}
|
||||
Err(msg) => {
|
||||
error(&msg);
|
||||
error!("{msg}");
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +98,7 @@ pub fn test(args: &[String]) -> i32 {
|
||||
match with_dir(&root, || run_testsuite(profile)) {
|
||||
Ok(code) => code,
|
||||
Err(msg) => {
|
||||
error(&msg);
|
||||
error!("{msg}");
|
||||
1
|
||||
}
|
||||
}
|
||||
@@ -134,8 +136,12 @@ fn run_testsuite(profile: &str) -> Result<i32, String> {
|
||||
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 (cflags, ldflags) = resolve_pkg_config_flags(&pkg_configs, &cflags, &ldflags)?;
|
||||
let includes = test_include_dirs(&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());
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
// 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::log::error;
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -40,7 +41,7 @@ pub fn tree(args: &[String]) -> i32 {
|
||||
let config = match Config::open("dcr.toml") {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
error("dcr.toml not found in current directory");
|
||||
error!("dcr.toml not found in current directory");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -115,6 +116,19 @@ fn print_deps(
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -769,8 +769,10 @@ fn build_project_at_inner(
|
||||
)?;
|
||||
}
|
||||
|
||||
let (resolved_cflags, resolved_ldflags) =
|
||||
let (mut resolved_cflags, mut resolved_ldflags) =
|
||||
resolve_pkg_config_flags(&pkg_configs, &build_cflags, &build_ldflags)?;
|
||||
resolved_cflags.extend(resolved.cflags.clone());
|
||||
resolved_ldflags.extend(resolved.ldflags.clone());
|
||||
let mut combined_excludes = Vec::new();
|
||||
for dir in exclude_dirs {
|
||||
combined_excludes.push(dir.clone());
|
||||
@@ -1011,6 +1013,8 @@ fn extend_transitive_link_deps(
|
||||
consumer_deps.include_dirs.extend(deps.include_dirs);
|
||||
consumer_deps.lib_dirs.extend(deps.lib_dirs);
|
||||
consumer_deps.libs.extend(deps.libs);
|
||||
consumer_deps.cflags.extend(deps.cflags);
|
||||
consumer_deps.ldflags.extend(deps.ldflags);
|
||||
for child_root in deps.package_roots {
|
||||
extend_transitive_link_deps(
|
||||
Path::new(&child_root),
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
// 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 serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
@@ -244,13 +246,19 @@ pub struct WorkspaceMemberConfig {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[allow(dead_code)]
|
||||
/// Creates a new Config instance from a TOML file path. If the file does not exist, it creates a default one.
|
||||
pub fn new(path: &str) -> Result<Self, ConfigError> {
|
||||
Self::create(path, None)
|
||||
}
|
||||
|
||||
/// Creates a new Config instance with an explicit package name for the default template when creating a new file.
|
||||
pub fn create(path: &str, package_name: Option<&str>) -> Result<Self, ConfigError> {
|
||||
let path = PathBuf::from(path);
|
||||
let (data, doc) = if path.exists() {
|
||||
load_parts(&path)?
|
||||
} else {
|
||||
let content = default_toml_text();
|
||||
let content = default_toml_text(package_name)?;
|
||||
let doc: DocumentMut = content.parse().map_err(ConfigError::TomlEdit)?;
|
||||
fs::write(&path, doc.to_string())?;
|
||||
let data: Value = toml::from_str(&content)?;
|
||||
@@ -328,35 +336,35 @@ impl Config {
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty());
|
||||
let Some(required) = required else {
|
||||
crate::utils::log::warn(&format!(
|
||||
warn!(
|
||||
"package.dcr-version is missing in {}. Add e.g. dcr-version = \"{tool}\" \
|
||||
so collaborators know which dcr this project targets \
|
||||
(written automatically by `dcr new` / `dcr init`).",
|
||||
self.path.display()
|
||||
));
|
||||
so collaborators know which dcr this project targets \
|
||||
(written automatically by `dcr new` / `dcr init`).",
|
||||
self.path.display(),
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
match crate::utils::build::compare_semver(tool, required) {
|
||||
Some(std::cmp::Ordering::Less) => {
|
||||
crate::utils::log::warn(&format!(
|
||||
warn!(
|
||||
"this project requires dcr {required} (package.dcr-version), \
|
||||
but you are running dcr {tool}. Some features may be missing or misbehave. \
|
||||
Upgrade dcr (e.g. `dcr --update`) or lower package.dcr-version if intentional."
|
||||
));
|
||||
);
|
||||
}
|
||||
Some(std::cmp::Ordering::Greater) => {
|
||||
crate::utils::log::warn(&format!(
|
||||
warn!(
|
||||
"this project pins dcr {required} (package.dcr-version), \
|
||||
but you are running newer dcr {tool}. Config/defaults may be outdated. \
|
||||
Review the changelog and bump package.dcr-version when the project is verified."
|
||||
));
|
||||
but you are running newer dcr {tool}. Config/defaults may be outdated. \
|
||||
Review the changelog and bump package.dcr-version when the project is verified.",
|
||||
);
|
||||
}
|
||||
Some(std::cmp::Ordering::Equal) => {}
|
||||
None => {
|
||||
crate::utils::log::warn(&format!(
|
||||
"package.dcr-version = \"{required}\" is not a valid semver (expected X.Y.Z); ignoring"
|
||||
));
|
||||
warn!(
|
||||
"package.dcr-version = \"{required}\" is not a valid semver (expected X.Y.Z); ignoring",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -830,14 +838,20 @@ fn load_parts(path: &Path) -> Result<(Value, DocumentMut), ConfigError> {
|
||||
Ok((data, doc))
|
||||
}
|
||||
|
||||
fn default_toml_text() -> String {
|
||||
let name = std::env::current_dir()
|
||||
.ok()
|
||||
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
|
||||
fn default_toml_text(package_name: Option<&str>) -> Result<String, ConfigError> {
|
||||
let name = package_name
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| {
|
||||
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());
|
||||
validate_non_empty_string(&name, "package.name")?;
|
||||
validate_package_name(&name)?;
|
||||
let dcr_version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
format!(
|
||||
Ok(format!(
|
||||
"[package]\n\
|
||||
name = \"{name}\"\n\
|
||||
version = \"{DEFAULT_VERSION}\"\n\
|
||||
@@ -854,7 +868,7 @@ fn default_toml_text() -> String {
|
||||
kind = \"{DEFAULT_KIND}\"\n\
|
||||
\n\
|
||||
[dependencies]\n"
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn dcr_version_warn_once(path_key: &str) -> bool {
|
||||
@@ -1285,6 +1299,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_rejects_invalid_name_without_writing_manifest() {
|
||||
let dir = temp_dir("invalid_create_name");
|
||||
let path = dir.join("dcr.toml");
|
||||
for name in ["", "bad name", "../bad", "bad\"name", " bad", "bad."] {
|
||||
let result = Config::create(&path.to_string_lossy(), Some(name));
|
||||
assert!(matches!(result, Err(ConfigError::Invalid(_))), "{name:?}");
|
||||
assert!(!path.exists(), "invalid name must not create a manifest");
|
||||
}
|
||||
let config = Config::create(&path.to_string_lossy(), Some("valid-name")).unwrap();
|
||||
assert_eq!(
|
||||
config.get("package.name").and_then(Value::as_str),
|
||||
Some("valid-name")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_creates_default_config() {
|
||||
let dir = temp_dir("new_default");
|
||||
|
||||
@@ -4,5 +4,7 @@ pub struct ResolvedDeps {
|
||||
pub include_dirs: Vec<String>,
|
||||
pub lib_dirs: Vec<String>,
|
||||
pub libs: Vec<String>,
|
||||
pub cflags: Vec<String>,
|
||||
pub ldflags: Vec<String>,
|
||||
pub package_roots: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ pub mod register;
|
||||
use crate::core::build_config::Config;
|
||||
use crate::core::deps::common::ResolvedDeps;
|
||||
use crate::core::deps::lock::{DepLock, write_lock};
|
||||
use crate::utils::build::{resolve_pkg_config_flags, run_pkg_config};
|
||||
use std::path::Path;
|
||||
|
||||
/// Returns the version string from a dependency's dcr.toml file, or an empty string if missing.
|
||||
@@ -73,7 +74,22 @@ pub fn resolve_deps(
|
||||
if let Some(deps) = deps_table {
|
||||
// Process each dependency declaration in the TOML table
|
||||
for (name, value) in deps {
|
||||
if register::is_registry_dep(value) {
|
||||
if let Some(packages) = pkg_config_dep(value, name)? {
|
||||
let (cflags, ldflags) = resolve_pkg_config_flags(&packages, &[], &[])?;
|
||||
resolved.cflags.extend(cflags);
|
||||
resolved.ldflags.extend(ldflags);
|
||||
let version = packages
|
||||
.first()
|
||||
.and_then(|pkg| run_pkg_config(pkg, "--modversion").ok())
|
||||
.map(|v| v.trim().to_string())
|
||||
.unwrap_or_default();
|
||||
lock_packages.push(DepLock {
|
||||
name: name.clone(),
|
||||
version,
|
||||
checksum: String::new(),
|
||||
source: format!("pkg-config+{}", packages.join(",")),
|
||||
});
|
||||
} else if register::is_registry_dep(value) {
|
||||
let pkg_info = register::resolve_package_from_registry(name)?;
|
||||
let dep_root = register::package_root_from_registry_info(&pkg_info)?;
|
||||
|
||||
@@ -274,6 +290,48 @@ fn git_dep(value: &toml::Value) -> Option<GitDep<'_>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Extracts one or more pkg-config package names from a dependency table.
|
||||
/// Both `pkg-config` and the legacy-style `pkg_config` spelling are accepted.
|
||||
fn pkg_config_dep(
|
||||
value: &toml::Value,
|
||||
dependency_name: &str,
|
||||
) -> Result<Option<Vec<String>>, String> {
|
||||
let Some(table) = value.as_table() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let raw = table.get("pkg-config").or_else(|| table.get("pkg_config"));
|
||||
let Some(raw) = raw else {
|
||||
return Ok(None);
|
||||
};
|
||||
let packages = if let Some(pkg) = raw.as_str() {
|
||||
vec![pkg.to_string()]
|
||||
} else {
|
||||
raw.as_array()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Dependency `{dependency_name}` field `pkg-config` must be a string or array of strings"
|
||||
)
|
||||
})?
|
||||
.iter()
|
||||
.map(|value| {
|
||||
value.as_str().map(str::to_string).ok_or_else(|| {
|
||||
format!(
|
||||
"Dependency `{dependency_name}` field `pkg-config` must contain only strings"
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.collect()
|
||||
};
|
||||
if packages.is_empty() || packages.iter().any(|pkg| pkg.trim().is_empty()) {
|
||||
return Err(format!(
|
||||
"Dependency `{dependency_name}` field `pkg-config` must not be empty"
|
||||
));
|
||||
}
|
||||
Ok(Some(packages))
|
||||
}
|
||||
|
||||
/// Extracts the path for a path-based dependency, supporting both table format and legacy string format.
|
||||
fn path_dep_path(value: &toml::Value) -> Option<&str> {
|
||||
if let Some(table) = value.as_table() {
|
||||
@@ -436,6 +494,20 @@ mod tests {
|
||||
assert_eq!(path_dep_path(&Value::String("1.2.3".to_string())), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pkg_config_dep_accepts_string_and_array() {
|
||||
let single: Value = toml::from_str("pkg-config = \"fmt\"").unwrap();
|
||||
assert_eq!(
|
||||
pkg_config_dep(&single, "fmt"),
|
||||
Ok(Some(vec!["fmt".to_string()]))
|
||||
);
|
||||
let many: Value = toml::from_str("pkg_config = [\"fmt\", \"zlib\"]").unwrap();
|
||||
assert_eq!(
|
||||
pkg_config_dep(&many, "base"),
|
||||
Ok(Some(vec!["fmt".to_string(), "zlib".to_string()]))
|
||||
);
|
||||
}
|
||||
|
||||
/// Verifies that push_default_lib_dirs includes the target/lib directory when it exists.
|
||||
#[test]
|
||||
fn default_lib_dirs_include_packaged_library_output() {
|
||||
|
||||
@@ -14,19 +14,23 @@
|
||||
//
|
||||
// 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::log::error;
|
||||
mod cli;
|
||||
mod config;
|
||||
mod core;
|
||||
mod platform;
|
||||
mod prelude;
|
||||
mod utils;
|
||||
|
||||
use prelude::*;
|
||||
|
||||
/// Main entry point for DCR, a Cargo-like C/C++ project manager.
|
||||
///
|
||||
/// Collects command-line arguments via `std::env::args()` and dispatches to the
|
||||
/// appropriate subcommand handler. With fewer than two arguments, shows help and
|
||||
/// exits with code 0; otherwise exits with the handler's exit code.
|
||||
fn main() {
|
||||
utils::log::init();
|
||||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
@@ -61,7 +65,7 @@ fn main() {
|
||||
"--help" => cli::help::help(),
|
||||
"--update" => cli::flag_update::flag_update(rest),
|
||||
_ => {
|
||||
error("Unknown command or argument");
|
||||
error!("Unknown command or argument");
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
1
src/prelude.rs
Normal file
1
src/prelude.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub use log::{error, warn};
|
||||
@@ -15,8 +15,9 @@
|
||||
// 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::log::warn;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Version information parsed from a version string.
|
||||
@@ -251,10 +252,9 @@ pub fn normalize_target_os(target: &str) -> &str {
|
||||
"windows" => "x86_64-pc-windows-msvc",
|
||||
_ if target.contains('-') => target,
|
||||
_ => {
|
||||
warn(&format!(
|
||||
"Unknown target '{}', using as-is. Supported short names: linux, macos, windows",
|
||||
target
|
||||
));
|
||||
warn!(
|
||||
"Unknown target '{target}', using as-is. Supported short names: linux, macos, windows"
|
||||
);
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,30 @@
|
||||
// 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_RED, BOLD_YELLOW, colored};
|
||||
use env_logger::{Builder, WriteStyle};
|
||||
use log::{Level, LevelFilter};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::io::Write;
|
||||
|
||||
/// Logs an error message to the standard error stream.
|
||||
///
|
||||
/// The message is prefixed with a red "error" label for visibility.
|
||||
#[allow(dead_code)]
|
||||
pub fn error(msg: &str) {
|
||||
eprintln!("{}: {msg}", colored("error", BOLD_RED));
|
||||
}
|
||||
// A function for initializing the logging lib with basic settings.
|
||||
pub fn init() {
|
||||
Builder::new()
|
||||
.filter_level(LevelFilter::Warn)
|
||||
.write_style(WriteStyle::Auto)
|
||||
.format(|buf, record| {
|
||||
let colored_lv = match record.level() {
|
||||
Level::Error => record.level().red().to_string(),
|
||||
Level::Warn => record.level().yellow().to_string(),
|
||||
Level::Info => record.level().default_color().to_string(),
|
||||
_ => record.level().purple().italic().to_string(),
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn warn(msg: &str) {
|
||||
eprintln!("{}: {msg}", colored("warn", BOLD_YELLOW));
|
||||
writeln!(
|
||||
buf,
|
||||
"{}: {}",
|
||||
colored_lv.to_lowercase().bold(),
|
||||
record.args(),
|
||||
)
|
||||
})
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,24 @@ fn dcr_add_dependencies() {
|
||||
"git short dep not found in toml"
|
||||
);
|
||||
|
||||
// Test pkg-config prefix.
|
||||
let out = run_dcr(&["add", "fmt", "pkg-config:fmt"], &dir);
|
||||
assert!(out.status.success(), "dcr add pkg-config should succeed");
|
||||
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
||||
assert!(
|
||||
toml.contains("fmt = { pkg-config = \"fmt\" }"),
|
||||
"pkg-config dep not found in toml"
|
||||
);
|
||||
|
||||
// Test multiple pkg-config packages via pkg: prefix.
|
||||
let out = run_dcr(&["add", "openssl", "pkg:openssl,libcrypto"], &dir);
|
||||
assert!(out.status.success(), "dcr add multiple pkg should succeed");
|
||||
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
||||
assert!(
|
||||
toml.contains("openssl = { pkg-config = [\"openssl\", \"libcrypto\"] }"),
|
||||
"multi pkg-config dep not found in toml"
|
||||
);
|
||||
|
||||
// Test flags (branch)
|
||||
let out = run_dcr(
|
||||
&["add", "branch_lib", "github:user/repo", "--branch", "dev"],
|
||||
@@ -146,6 +164,56 @@ fn path_prebuilt_dependency_without_manifest_links() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pkg_config_dependency_resolves_system_library() {
|
||||
let Some(compiler) = available_compiler() else {
|
||||
eprintln!("no compiler found; skipping pkg-config dependency test");
|
||||
return;
|
||||
};
|
||||
if !std::process::Command::new("pkg-config")
|
||||
.args(["--exists", "zlib"])
|
||||
.status()
|
||||
.map(|status| status.success())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
eprintln!("pkg-config zlib unavailable; skipping pkg-config dependency test");
|
||||
return;
|
||||
}
|
||||
|
||||
let root = unique_sandbox_dir("pkg_config_dep");
|
||||
let app = root.join("app");
|
||||
std::fs::create_dir_all(app.join("src")).unwrap();
|
||||
std::fs::write(
|
||||
app.join("dcr.toml"),
|
||||
"[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[build]\nlanguage = \"c\"\ncompiler = \"clang\"\nkind = \"bin\"\n\n[dependencies]\nzlib = { pkg-config = \"zlib\" }\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
app.join("src/main.c"),
|
||||
"#include <zlib.h>\nint main(void) { return zlibVersion() == 0 ? 1 : 0; }\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let out = run_dcr_env(&["build"], &app, &[("DCR_COMPILER", compiler)]);
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"pkg-config dependency build failed: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let lock = std::fs::read_to_string(app.join("dcr.lock")).unwrap();
|
||||
assert!(lock.contains("source = \"pkg-config+zlib\""));
|
||||
|
||||
// Check dcr tree displays pkg-config info
|
||||
let tree_out = run_dcr(&["tree"], &app);
|
||||
assert!(tree_out.status.success(), "dcr tree should succeed");
|
||||
let tree_str = String::from_utf8_lossy(&tree_out.stdout);
|
||||
assert!(
|
||||
tree_str.contains("zlib (pkg-config: zlib)"),
|
||||
"dcr tree should format pkg-config dependency, got: {}",
|
||||
tree_str
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dcr_dependency_is_built_before_consumer() {
|
||||
let Some(compiler) = available_compiler() else {
|
||||
|
||||
@@ -79,3 +79,24 @@ fn init_vcs_options_work() {
|
||||
assert!(gitignore.contains(".dcr/"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_in_non_ascii_parent_directory() {
|
||||
let parent_dir = unique_sandbox_dir("Проекты с пробелами");
|
||||
let out = run_dcr(&["new", "my_app"], &parent_dir);
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"dcr new should succeed inside non-ascii parent dir: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
|
||||
let project_dir = parent_dir.join("my_app");
|
||||
assert!(project_dir.is_dir(), "project dir should exist");
|
||||
assert!(project_dir.join("dcr.toml").is_file(), "dcr.toml missing");
|
||||
|
||||
let toml_content = std::fs::read_to_string(project_dir.join("dcr.toml")).unwrap();
|
||||
assert!(
|
||||
toml_content.contains("name = \"my_app\""),
|
||||
"package name should be my_app in dcr.toml"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user