mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 09:18:29 +00:00
Compare commits
5 Commits
d587eba5a4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d2340fb52 | |||
| 6c79340736 | |||
| be14cfb8e2 | |||
| 3305915c07 | |||
| 5cb51878a5 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# 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"
|
## [v0.9.0] - 2026-08-31 "Полные Path/Git-зависимости и готовые библиотеки / Complete Path/Git Dependencies & Prebuilt Libraries"
|
||||||
|
|
||||||
### RU
|
### RU
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -154,7 +154,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.9.0"
|
version = "0.9.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"fatfs",
|
"fatfs",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.9.0"
|
version = "0.9.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ sidebar_label: Changelog
|
|||||||
|
|
||||||
# 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)
|
## 0.9.0 (2026-08-31)
|
||||||
|
|
||||||
- **Download mirrors in `dcrup`** — `--mirror auto|cf|gh|sf|ya` and `DCRUP_MIRROR`.
|
- **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 git:https://github.com/user/repo # explicit git prefix
|
||||||
dcr add mylib github:user/repo # GitHub shorthand
|
dcr add mylib github:user/repo # GitHub shorthand
|
||||||
dcr add mylib gitlab:user/repo # GitLab 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:
|
Source prefixes:
|
||||||
@@ -22,6 +24,7 @@ Source prefixes:
|
|||||||
- `git:` — generic git URL
|
- `git:` — generic git URL
|
||||||
- `github:` — expands to `https://github.com/<user>/<repo>`
|
- `github:` — expands to `https://github.com/<user>/<repo>`
|
||||||
- `gitlab:` — expands to `https://gitlab.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
|
- `http://` / `https://` / `git@` — full URL
|
||||||
|
|
||||||
Flags:
|
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.
|
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`
|
## `dcr tree`
|
||||||
|
|
||||||
Displays the project dependency 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) |
|
| `qt` | `false` | Enable automatic Qt meta-object handling (MOC, UIC, RCC) |
|
||||||
|
|
||||||
Settings from raw config (not in typed struct):
|
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
|
- `ldscript` — linker script path
|
||||||
- `build.steps` / `build.post_steps` — codegen steps
|
- `build.steps` / `build.post_steps` — codegen steps
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ sidebar_label: Dependencies
|
|||||||
|
|
||||||
## Formats
|
## Formats
|
||||||
|
|
||||||
The `[dependencies]` section supports three formats:
|
The `[dependencies]` section supports registry, path, Git, and `pkg-config` dependencies:
|
||||||
|
|
||||||
### String (registry)
|
### String (registry)
|
||||||
|
|
||||||
@@ -44,6 +44,23 @@ names (`libs`):
|
|||||||
dcr-math = { path = "../deps/dcr-math", include = ["include"], lib = ["lib"], libs = ["dcr-math"] }
|
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
|
## Registry
|
||||||
|
|
||||||
DCR uses a package registry for dependency lookup by name.
|
DCR uses a package registry for dependency lookup by name.
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ Generic git repository.
|
|||||||
.B path:./path/to/lib
|
.B path:./path/to/lib
|
||||||
Local path dependency.
|
Local path dependency.
|
||||||
.TP
|
.TP
|
||||||
|
.B pkg-config:name
|
||||||
|
System pkg-config dependency (also accepts \fBpkg:\fR or comma-separated packages).
|
||||||
|
.TP
|
||||||
.B <version>
|
.B <version>
|
||||||
Version from registry.
|
Version from registry.
|
||||||
.SH OPTIONS
|
.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
|
.SH NAME
|
||||||
dcr \- Cargo\-like build tool and package manager for C/C++ projects
|
dcr \- Cargo\-like build tool and package manager for C/C++ projects
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -120,6 +120,9 @@ Generic git repository.
|
|||||||
.B path:./path/to/lib
|
.B path:./path/to/lib
|
||||||
Local path dependency.
|
Local path dependency.
|
||||||
.TP
|
.TP
|
||||||
|
.B pkg-config:name
|
||||||
|
System pkg-config dependency (also accepts \fBpkg:name1,name2\fR).
|
||||||
|
.TP
|
||||||
.B \fI<version>\fR
|
.B \fI<version>\fR
|
||||||
Version from registry (e.g. \fB1.2.3\fR).
|
Version from registry (e.g. \fB1.2.3\fR).
|
||||||
.SH GEN SUBCOMMANDS
|
.SH GEN SUBCOMMANDS
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use toml::map::Map;
|
|||||||
|
|
||||||
/// Parsed arguments for the `dcr add` command.
|
/// 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.
|
/// depending on how the dependency source was specified.
|
||||||
pub struct AddArgs {
|
pub struct AddArgs {
|
||||||
/// Dependency name as it will appear under `[dependencies]` in `dcr.toml`.
|
/// Dependency name as it will appear under `[dependencies]` in `dcr.toml`.
|
||||||
@@ -42,6 +42,8 @@ pub struct AddArgs {
|
|||||||
pub rev: Option<String>,
|
pub rev: Option<String>,
|
||||||
/// Version string resolved from the package registry when no explicit source is given.
|
/// Version string resolved from the package registry when no explicit source is given.
|
||||||
pub version_from_registry: Option<String>,
|
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`.
|
/// Adds a dependency to the current project's `dcr.toml`.
|
||||||
@@ -71,6 +73,7 @@ pub fn add(args: &[String]) -> i32 {
|
|||||||
println!(" gitlab:user/repo GitLab repository");
|
println!(" gitlab:user/repo GitLab repository");
|
||||||
println!(" git:host.com/user/repo Generic git repository");
|
println!(" git:host.com/user/repo Generic git repository");
|
||||||
println!(" path:./path/to/lib Local path dependency");
|
println!(" path:./path/to/lib Local path dependency");
|
||||||
|
println!(" pkg-config:fmt System pkg-config dependency");
|
||||||
println!(" <version> Version from registry");
|
println!(" <version> Version from registry");
|
||||||
println!();
|
println!();
|
||||||
printc("OPTIONS:", BOLD_GREEN);
|
printc("OPTIONS:", BOLD_GREEN);
|
||||||
@@ -133,6 +136,17 @@ pub fn add(args: &[String]) -> i32 {
|
|||||||
}
|
}
|
||||||
Value::Table(table)
|
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 {
|
} else if let Some(path) = add_args.path {
|
||||||
let mut table = Map::new();
|
let mut table = Map::new();
|
||||||
table.insert("path".to_string(), Value::String(path));
|
table.insert("path".to_string(), Value::String(path));
|
||||||
@@ -140,7 +154,7 @@ pub fn add(args: &[String]) -> i32 {
|
|||||||
} else if let Some(version) = add_args.version_from_registry {
|
} else if let Some(version) = add_args.version_from_registry {
|
||||||
Value::String(version)
|
Value::String(version)
|
||||||
} else {
|
} else {
|
||||||
error("Dependency source (path or git) must be provided");
|
error("Dependency source (path, git, or pkg-config) must be provided");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,6 +185,7 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
|||||||
error(" gitlab:user/repo -> gitlab.com/user/repo");
|
error(" gitlab:user/repo -> gitlab.com/user/repo");
|
||||||
error(" git:host.com/user/repo -> host.com/user/repo");
|
error(" git:host.com/user/repo -> host.com/user/repo");
|
||||||
error(" path:./path/to/lib -> local path");
|
error(" path:./path/to/lib -> local path");
|
||||||
|
error(" pkg-config:fmt -> system pkg-config package");
|
||||||
return Err(1);
|
return Err(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +236,7 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
|||||||
tag: None,
|
tag: None,
|
||||||
rev: None,
|
rev: None,
|
||||||
version_from_registry: Some(version.to_string()),
|
version_from_registry: Some(version.to_string()),
|
||||||
|
pkg_config: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -262,9 +278,25 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
|||||||
|
|
||||||
let mut path = None;
|
let mut path = None;
|
||||||
let mut git = None;
|
let mut git = None;
|
||||||
|
let mut pkg_config = None;
|
||||||
|
|
||||||
// Map source prefixes / bare URLs to path or git fields
|
// 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());
|
path = Some(p.to_string());
|
||||||
} else if let Some(g) = source_spec.strip_prefix("github:") {
|
} else if let Some(g) = source_spec.strip_prefix("github:") {
|
||||||
git = Some(format!("https://github.com/{}", g));
|
git = Some(format!("https://github.com/{}", g));
|
||||||
@@ -295,7 +327,7 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
|||||||
path = Some(source_spec);
|
path = Some(source_spec);
|
||||||
} else {
|
} else {
|
||||||
error(
|
error(
|
||||||
"Source must be a path, a prefixed source (path:, git:, github:, gitlab:), or a full URL",
|
"Source must be a path, pkg-config:, a prefixed source (path:, git:, github:, gitlab:), or a full URL",
|
||||||
);
|
);
|
||||||
return Err(1);
|
return Err(1);
|
||||||
}
|
}
|
||||||
@@ -308,5 +340,6 @@ fn parse_add_args(args: &[String]) -> Result<AddArgs, i32> {
|
|||||||
tag,
|
tag,
|
||||||
rev,
|
rev,
|
||||||
version_from_registry: None,
|
version_from_registry: None,
|
||||||
|
pkg_config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,13 @@ struct GenDeps {
|
|||||||
include_dirs: Vec<String>,
|
include_dirs: Vec<String>,
|
||||||
lib_dirs: Vec<String>,
|
lib_dirs: Vec<String>,
|
||||||
libs: 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.
|
/// directory names only when those directories exist.
|
||||||
fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) -> GenDeps {
|
fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) -> GenDeps {
|
||||||
let deps_val = match config.get("dependencies") {
|
let deps_val = match config.get("dependencies") {
|
||||||
@@ -56,6 +58,8 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
|||||||
include_dirs: vec![],
|
include_dirs: vec![],
|
||||||
lib_dirs: vec![],
|
lib_dirs: vec![],
|
||||||
libs: vec![],
|
libs: vec![],
|
||||||
|
cflags: vec![],
|
||||||
|
ldflags: vec![],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -66,6 +70,8 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
|||||||
include_dirs: vec![],
|
include_dirs: vec![],
|
||||||
lib_dirs: vec![],
|
lib_dirs: vec![],
|
||||||
libs: vec![],
|
libs: vec![],
|
||||||
|
cflags: vec![],
|
||||||
|
ldflags: vec![],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -73,12 +79,37 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
|||||||
let mut include_dirs = Vec::new();
|
let mut include_dirs = Vec::new();
|
||||||
let mut lib_dirs = Vec::new();
|
let mut lib_dirs = Vec::new();
|
||||||
let mut libs = Vec::new();
|
let mut libs = Vec::new();
|
||||||
|
let mut cflags = Vec::new();
|
||||||
|
let mut ldflags = Vec::new();
|
||||||
|
|
||||||
for (name, value) in deps_table {
|
for (name, value) in deps_table {
|
||||||
let tbl = match value.as_table() {
|
let tbl = match value.as_table() {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => continue,
|
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
|
// skip system deps
|
||||||
if tbl.get("system").and_then(|v| v.as_bool()).unwrap_or(false) {
|
if tbl.get("system").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||||
continue;
|
continue;
|
||||||
@@ -169,6 +200,8 @@ fn resolve_deps_for_gen(config: &Config, profile: &str, project_root: &Path) ->
|
|||||||
include_dirs,
|
include_dirs,
|
||||||
lib_dirs,
|
lib_dirs,
|
||||||
libs,
|
libs,
|
||||||
|
cflags,
|
||||||
|
ldflags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,8 +383,10 @@ fn collect_project_info_inner(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let resolved = resolve_deps_for_gen(&config, profile, &root);
|
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);
|
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)
|
// Build exclude/include pattern lists (same logic as cli::build)
|
||||||
let mut combined_excludes: Vec<PathBuf> = Vec::new();
|
let mut combined_excludes: Vec<PathBuf> = Vec::new();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ use crate::utils::log::{error, warn};
|
|||||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use toml::Value;
|
|
||||||
|
|
||||||
/// Initializes the current working directory as a new DCR project.
|
/// Initializes the current working directory as a new DCR project.
|
||||||
///
|
///
|
||||||
@@ -91,20 +90,10 @@ pub fn init(args: &[String]) -> i32 {
|
|||||||
println!("Initializing the project in {cwd}");
|
println!("Initializing the project in {cwd}");
|
||||||
|
|
||||||
// Create and initialize default manifest (dcr.toml).
|
// Create and initialize default manifest (dcr.toml).
|
||||||
let mut config = match Config::new("./dcr.toml") {
|
if Config::create("./dcr.toml", Some(&project_name)).is_err() {
|
||||||
Ok(cfg) => cfg,
|
|
||||||
Err(_) => {
|
|
||||||
error("Failed to create dcr.toml");
|
error("Failed to create dcr.toml");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if config
|
|
||||||
.edit("package.name", Value::String(project_name.clone()))
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
error("Failed to write dcr.toml");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
println!(
|
println!(
|
||||||
" {} Created file {}",
|
" {} Created file {}",
|
||||||
colored("✔", BOLD_GREEN),
|
colored("✔", BOLD_GREEN),
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ use crate::utils::log::{error, warn};
|
|||||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use toml::Value;
|
|
||||||
|
|
||||||
/// Executes the `dcr new` command to scaffold a new C/C++ project directory structure.
|
/// Executes the `dcr new` command to scaffold a new C/C++ project directory structure.
|
||||||
///
|
///
|
||||||
@@ -110,20 +109,10 @@ pub fn new(args: &[String]) -> i32 {
|
|||||||
|
|
||||||
// Initialize and write project configuration manifest (dcr.toml).
|
// Initialize and write project configuration manifest (dcr.toml).
|
||||||
let toml_path = format!("./{project_name}/dcr.toml");
|
let toml_path = format!("./{project_name}/dcr.toml");
|
||||||
let mut config = match Config::new(&toml_path) {
|
if Config::create(&toml_path, Some(project_name)).is_err() {
|
||||||
Ok(cfg) => cfg,
|
|
||||||
Err(_) => {
|
|
||||||
error("Failed to create dcr.toml");
|
error("Failed to create dcr.toml");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if config
|
|
||||||
.edit("package.name", Value::String(project_name.to_string()))
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
error("Failed to write dcr.toml");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
println!(
|
println!(
|
||||||
" {} Created file {}",
|
" {} Created file {}",
|
||||||
colored("✔", BOLD_GREEN),
|
colored("✔", BOLD_GREEN),
|
||||||
|
|||||||
@@ -134,8 +134,12 @@ fn run_testsuite(profile: &str) -> Result<i32, String> {
|
|||||||
cflags.extend(get_list_with_profile(&config, "cflags", profile));
|
cflags.extend(get_list_with_profile(&config, "cflags", profile));
|
||||||
let ldflags = get_list_with_profile(&config, "ldflags", profile);
|
let ldflags = get_list_with_profile(&config, "ldflags", profile);
|
||||||
let pkg_configs = get_list_with_profile(&config, "pkg_config", profile);
|
let pkg_configs = get_list_with_profile(&config, "pkg_config", profile);
|
||||||
let (cflags, ldflags) = resolve_pkg_config_flags(&pkg_configs, &cflags, &ldflags)?;
|
let (mut cflags, mut ldflags) = resolve_pkg_config_flags(&pkg_configs, &cflags, &ldflags)?;
|
||||||
let includes = test_include_dirs(&config, profile);
|
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()?;
|
let test_sources = collect_test_sources()?;
|
||||||
if test_sources.is_empty() {
|
if test_sources.is_empty() {
|
||||||
return Err("tests/test.c not found; run 'dcr test --init' first".to_string());
|
return Err("tests/test.c not found; run 'dcr test --init' first".to_string());
|
||||||
|
|||||||
@@ -115,6 +115,19 @@ fn print_deps(
|
|||||||
format!(" v{}", v)
|
format!(" v{}", v)
|
||||||
} else if let Some(git) = t.get("git").and_then(|v| v.as_str()) {
|
} else if let Some(git) = t.get("git").and_then(|v| v.as_str()) {
|
||||||
format!(" ({})", git)
|
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 {
|
} else {
|
||||||
"".to_string()
|
"".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)?;
|
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();
|
let mut combined_excludes = Vec::new();
|
||||||
for dir in exclude_dirs {
|
for dir in exclude_dirs {
|
||||||
combined_excludes.push(dir.clone());
|
combined_excludes.push(dir.clone());
|
||||||
@@ -1011,6 +1013,8 @@ fn extend_transitive_link_deps(
|
|||||||
consumer_deps.include_dirs.extend(deps.include_dirs);
|
consumer_deps.include_dirs.extend(deps.include_dirs);
|
||||||
consumer_deps.lib_dirs.extend(deps.lib_dirs);
|
consumer_deps.lib_dirs.extend(deps.lib_dirs);
|
||||||
consumer_deps.libs.extend(deps.libs);
|
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 {
|
for child_root in deps.package_roots {
|
||||||
extend_transitive_link_deps(
|
extend_transitive_link_deps(
|
||||||
Path::new(&child_root),
|
Path::new(&child_root),
|
||||||
|
|||||||
@@ -244,13 +244,19 @@ pub struct WorkspaceMemberConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
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.
|
/// 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> {
|
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 path = PathBuf::from(path);
|
||||||
let (data, doc) = if path.exists() {
|
let (data, doc) = if path.exists() {
|
||||||
load_parts(&path)?
|
load_parts(&path)?
|
||||||
} else {
|
} else {
|
||||||
let content = default_toml_text();
|
let content = default_toml_text(package_name)?;
|
||||||
let doc: DocumentMut = content.parse().map_err(ConfigError::TomlEdit)?;
|
let doc: DocumentMut = content.parse().map_err(ConfigError::TomlEdit)?;
|
||||||
fs::write(&path, doc.to_string())?;
|
fs::write(&path, doc.to_string())?;
|
||||||
let data: Value = toml::from_str(&content)?;
|
let data: Value = toml::from_str(&content)?;
|
||||||
@@ -830,14 +836,20 @@ fn load_parts(path: &Path) -> Result<(Value, DocumentMut), ConfigError> {
|
|||||||
Ok((data, doc))
|
Ok((data, doc))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_toml_text() -> String {
|
fn default_toml_text(package_name: Option<&str>) -> Result<String, ConfigError> {
|
||||||
let name = std::env::current_dir()
|
let name = package_name
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.or_else(|| {
|
||||||
|
std::env::current_dir()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
|
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
|
||||||
|
})
|
||||||
.unwrap_or_else(|| "project".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");
|
let dcr_version = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
format!(
|
Ok(format!(
|
||||||
"[package]\n\
|
"[package]\n\
|
||||||
name = \"{name}\"\n\
|
name = \"{name}\"\n\
|
||||||
version = \"{DEFAULT_VERSION}\"\n\
|
version = \"{DEFAULT_VERSION}\"\n\
|
||||||
@@ -854,7 +866,7 @@ fn default_toml_text() -> String {
|
|||||||
kind = \"{DEFAULT_KIND}\"\n\
|
kind = \"{DEFAULT_KIND}\"\n\
|
||||||
\n\
|
\n\
|
||||||
[dependencies]\n"
|
[dependencies]\n"
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dcr_version_warn_once(path_key: &str) -> bool {
|
fn dcr_version_warn_once(path_key: &str) -> bool {
|
||||||
@@ -1285,6 +1297,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]
|
#[test]
|
||||||
fn new_creates_default_config() {
|
fn new_creates_default_config() {
|
||||||
let dir = temp_dir("new_default");
|
let dir = temp_dir("new_default");
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ pub struct ResolvedDeps {
|
|||||||
pub include_dirs: Vec<String>,
|
pub include_dirs: Vec<String>,
|
||||||
pub lib_dirs: Vec<String>,
|
pub lib_dirs: Vec<String>,
|
||||||
pub libs: Vec<String>,
|
pub libs: Vec<String>,
|
||||||
|
pub cflags: Vec<String>,
|
||||||
|
pub ldflags: Vec<String>,
|
||||||
pub package_roots: Vec<String>,
|
pub package_roots: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub mod register;
|
|||||||
use crate::core::build_config::Config;
|
use crate::core::build_config::Config;
|
||||||
use crate::core::deps::common::ResolvedDeps;
|
use crate::core::deps::common::ResolvedDeps;
|
||||||
use crate::core::deps::lock::{DepLock, write_lock};
|
use crate::core::deps::lock::{DepLock, write_lock};
|
||||||
|
use crate::utils::build::{resolve_pkg_config_flags, run_pkg_config};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Returns the version string from a dependency's dcr.toml file, or an empty string if missing.
|
/// 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 {
|
if let Some(deps) = deps_table {
|
||||||
// Process each dependency declaration in the TOML table
|
// Process each dependency declaration in the TOML table
|
||||||
for (name, value) in deps {
|
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 pkg_info = register::resolve_package_from_registry(name)?;
|
||||||
let dep_root = register::package_root_from_registry_info(&pkg_info)?;
|
let dep_root = register::package_root_from_registry_info(&pkg_info)?;
|
||||||
|
|
||||||
@@ -274,6 +290,48 @@ fn git_dep(value: &toml::Value) -> Option<GitDep<'_>> {
|
|||||||
None
|
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.
|
/// 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> {
|
fn path_dep_path(value: &toml::Value) -> Option<&str> {
|
||||||
if let Some(table) = value.as_table() {
|
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);
|
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.
|
/// Verifies that push_default_lib_dirs includes the target/lib directory when it exists.
|
||||||
#[test]
|
#[test]
|
||||||
fn default_lib_dirs_include_packaged_library_output() {
|
fn default_lib_dirs_include_packaged_library_output() {
|
||||||
|
|||||||
@@ -52,6 +52,24 @@ fn dcr_add_dependencies() {
|
|||||||
"git short dep not found in toml"
|
"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)
|
// Test flags (branch)
|
||||||
let out = run_dcr(
|
let out = run_dcr(
|
||||||
&["add", "branch_lib", "github:user/repo", "--branch", "dev"],
|
&["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]
|
#[test]
|
||||||
fn path_dcr_dependency_is_built_before_consumer() {
|
fn path_dcr_dependency_is_built_before_consumer() {
|
||||||
let Some(compiler) = available_compiler() else {
|
let Some(compiler) = available_compiler() else {
|
||||||
|
|||||||
@@ -79,3 +79,24 @@ fn init_vcs_options_work() {
|
|||||||
assert!(gitignore.contains(".dcr/"));
|
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