mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 18:39:42 +00:00
Compare commits
2 Commits
3bd91e85f9
...
b0e426c189
| Author | SHA1 | Date | |
|---|---|---|---|
| b0e426c189 | |||
| 60d7420929 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## [v0.8.5] - 2026-08-08 "Фиксация версии dcr-version и предупреждения совместимости / Config Version Pinning & Compatibility Warnings"
|
||||
|
||||
### RU
|
||||
|
||||
**Добавлено:**
|
||||
|
||||
- **`package.dcr-version`** — опциональный pin версии CLI в `dcr.toml`.
|
||||
`dcr new` / `dcr init` пишут текущую версию. При загрузке конфига:
|
||||
- нет / пустой pin → warn «добавь dcr-version»;
|
||||
- dcr старше pin → warn «обнови dcr»;
|
||||
- dcr новее pin → warn «проверь changelog, подними pin»;
|
||||
- равенство — тишина.
|
||||
Команды не падают, только предупреждение (раз на path за процесс).
|
||||
|
||||
### EN
|
||||
|
||||
**Added:**
|
||||
|
||||
- **`package.dcr-version`** — optional CLI version pin in `dcr.toml`.
|
||||
`dcr new` / `dcr init` write the current version. On config load:
|
||||
- missing / empty pin → warn "add dcr-version";
|
||||
- dcr older than pin → warn "update dcr";
|
||||
- dcr newer than pin → warn "check changelog, update pin";
|
||||
- equal → silent.
|
||||
Commands do not fail, warning only (once per path per process).
|
||||
|
||||
## [0.8.4] - 2026-08-04 "Инфраструктура .dcr/ и интеграция с IDE / Internal .dcr/ Layout & IDE Integration"
|
||||
|
||||
### RU
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -154,7 +154,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dcr"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"fatfs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dcr"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
||||
|
||||
@@ -4,6 +4,16 @@ sidebar_label: Changelog
|
||||
|
||||
# Changelog
|
||||
|
||||
## 0.8.5 (2026-08-08)
|
||||
|
||||
- **`package.dcr-version`** — optional CLI version pin in `dcr.toml`.
|
||||
`dcr new` / `dcr init` write the current version. On config load:
|
||||
- missing / empty pin → warn "add dcr-version";
|
||||
- dcr older than pin → warn "update dcr";
|
||||
- dcr newer than pin → warn "check changelog, update pin";
|
||||
- equal → silent.
|
||||
Commands do not fail, warning only (once per path per process).
|
||||
|
||||
## 0.8.4 (2026-08-04)
|
||||
|
||||
**Added:**
|
||||
|
||||
@@ -43,7 +43,8 @@ Main project configuration file. Located at the project root.
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `name` | yes | Project name |
|
||||
| `version` | yes | Semantic version |
|
||||
| `version` | yes | Semantic version of the **project** |
|
||||
| `dcr-version` | no | Semver of the **dcr tool** this project targets. Written by `dcr new` / `dcr init` to the current tool version. |
|
||||
| `type` | no | `app`, `lib`, `none` (defaults to `"none"`) |
|
||||
| `license` | no | SPDX license identifier |
|
||||
| `author` | no | Author |
|
||||
@@ -52,11 +53,26 @@ Main project configuration file. Located at the project root.
|
||||
[package]
|
||||
name = "my-app"
|
||||
version = "0.1.0"
|
||||
dcr-version = "0.8.4"
|
||||
type = "app"
|
||||
license = "MIT"
|
||||
author = "John Doe"
|
||||
```
|
||||
|
||||
### `package.dcr-version`
|
||||
|
||||
Optional pin of the DCR CLI the project was written for. On every `dcr.toml` load:
|
||||
|
||||
| Situation | Behaviour |
|
||||
|-----------|-----------|
|
||||
| Field missing / empty | Warning: add `dcr-version` (set by `dcr new` / `dcr init`) |
|
||||
| Running dcr **older** than pin | Warning: upgrade dcr (`dcr --update`) |
|
||||
| Running dcr **newer** than pin | Warning: review changelog, bump the pin when ready |
|
||||
| Equal | Silent |
|
||||
| Invalid semver | Warning once, field ignored |
|
||||
|
||||
Never fails the command — warnings only. Pre-release suffixes (`1.0.0-dev`) are ignored for comparison (`1.0.0-dev` ≡ `1.0.0`).
|
||||
|
||||
## [build]
|
||||
|
||||
...
|
||||
|
||||
@@ -154,6 +154,11 @@ Project name (required).
|
||||
.B package.version
|
||||
Project version (default: \fB0.1.0\fR).
|
||||
.TP
|
||||
.B package.dcr\-version
|
||||
Semver of the DCR tool this project targets. Set automatically by
|
||||
\fBdcr new\fR / \fBdcr init\fR. Missing pin, or an installed \fBdcr\fR older/newer
|
||||
than the pin, prints a warning (commands still run).
|
||||
.TP
|
||||
.B build.language
|
||||
Programming language: \fBc\fR or \fBc++\fR (default: \fBc\fR).
|
||||
.TP
|
||||
|
||||
@@ -132,11 +132,17 @@ pub struct ArchiveLayout {
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
/// Package metadata section (name, version, type).
|
||||
/// Package metadata section (name, version, type, dcr-version).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PackageConfig {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
/// Minimum/target DCR tool version this project was authored for (`package.dcr-version`).
|
||||
///
|
||||
/// Compared against the running `dcr` binary (`CARGO_PKG_VERSION`) on config load.
|
||||
/// Missing or empty means "no pin" — no warning is emitted.
|
||||
#[serde(default, rename = "dcr-version")]
|
||||
pub dcr_version: Option<String>,
|
||||
#[serde(default, rename = "type")]
|
||||
pub pkg_type: Option<String>,
|
||||
}
|
||||
@@ -258,6 +264,7 @@ impl Config {
|
||||
doc,
|
||||
};
|
||||
cfg.validate()?;
|
||||
cfg.warn_dcr_version_mismatch();
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
@@ -276,6 +283,7 @@ impl Config {
|
||||
doc,
|
||||
};
|
||||
cfg.validate()?;
|
||||
cfg.warn_dcr_version_mismatch();
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
@@ -291,6 +299,68 @@ impl Config {
|
||||
self.typed.package.as_ref()
|
||||
}
|
||||
|
||||
/// Warn once per config path about `package.dcr-version` vs this `dcr` binary.
|
||||
///
|
||||
/// - Missing/`""` pin → recommend adding `dcr-version`.
|
||||
/// - Tool older than pin → project may use features this binary does not support.
|
||||
/// - Tool newer than pin → project may rely on outdated defaults; consider bumping the pin.
|
||||
///
|
||||
/// Never fails the load; warnings only. Equal pin is silent.
|
||||
fn warn_dcr_version_mismatch(&self) {
|
||||
let Some(pkg) = self.typed.package.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let path_key = self
|
||||
.path
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| self.path.clone())
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
if !dcr_version_warn_once(&path_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tool = env!("CARGO_PKG_VERSION");
|
||||
let required = pkg
|
||||
.dcr_version
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty());
|
||||
let Some(required) = required else {
|
||||
crate::utils::log::warn(&format!(
|
||||
"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()
|
||||
));
|
||||
return;
|
||||
};
|
||||
|
||||
match crate::utils::build::compare_semver(tool, required) {
|
||||
Some(std::cmp::Ordering::Less) => {
|
||||
crate::utils::log::warn(&format!(
|
||||
"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!(
|
||||
"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."
|
||||
));
|
||||
}
|
||||
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"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Returns the build configuration if present.
|
||||
pub fn build_config(&self) -> Option<&BuildConfig> {
|
||||
@@ -765,11 +835,13 @@ fn default_toml_text() -> String {
|
||||
.ok()
|
||||
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
|
||||
.unwrap_or_else(|| "project".to_string());
|
||||
let dcr_version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
format!(
|
||||
"[package]\n\
|
||||
name = \"{name}\"\n\
|
||||
version = \"{DEFAULT_VERSION}\"\n\
|
||||
dcr-version = \"{dcr_version}\"\n\
|
||||
type = \"none\"\n\
|
||||
description = \"\"\n\
|
||||
author = \"\"\n\
|
||||
@@ -785,6 +857,17 @@ fn default_toml_text() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn dcr_version_warn_once(path_key: &str) -> bool {
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
static WARNED: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
|
||||
let set = WARNED.get_or_init(|| Mutex::new(HashSet::new()));
|
||||
let Ok(mut guard) = set.lock() else {
|
||||
return true;
|
||||
};
|
||||
guard.insert(path_key.to_string())
|
||||
}
|
||||
|
||||
fn set_doc_path(doc: &mut DocumentMut, path: &[&str], value: &Value) -> Result<(), ConfigError> {
|
||||
let mut current = doc.as_table_mut();
|
||||
for &key in &path[..path.len().saturating_sub(1)] {
|
||||
@@ -937,11 +1020,21 @@ mod tests {
|
||||
let dir = temp_dir("typed_config");
|
||||
let path = write_toml_file(
|
||||
&dir,
|
||||
"[package]\nname = \"typed\"\nversion = \"1.2.3\"\ntype = \"lib\"\n\n[build]\nlanguage = [\"c\", \"c++\"]\nstandard = \"c11\"\ncompiler = \"clang\"\nkind = \"staticlib\"\ncflags = [\"-Wall\"]\n\n[dependencies]\nfoo = \"1.0.0\"\n",
|
||||
"[package]\nname = \"typed\"\nversion = \"1.2.3\"\ndcr-version = \"0.8.4\"\ntype = \"lib\"\n\n[build]\nlanguage = [\"c\", \"c++\"]\nstandard = \"c11\"\ncompiler = \"clang\"\nkind = \"staticlib\"\ncflags = [\"-Wall\"]\n\n[dependencies]\nfoo = \"1.0.0\"\n",
|
||||
);
|
||||
let config = Config::open(&path.to_string_lossy()).unwrap();
|
||||
assert_eq!(config.package().unwrap().name, "typed");
|
||||
assert_eq!(config.typed().package.as_ref().unwrap().version, "1.2.3");
|
||||
assert_eq!(
|
||||
config
|
||||
.typed()
|
||||
.package
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.dcr_version
|
||||
.as_deref(),
|
||||
Some("0.8.4")
|
||||
);
|
||||
assert_eq!(config.build_config().unwrap().compiler, "clang");
|
||||
assert_eq!(config.build_config().unwrap().cflags, ["-Wall"]);
|
||||
assert!(config.typed().dependencies.contains_key("foo"));
|
||||
|
||||
@@ -31,6 +31,39 @@ pub struct VersionInfo {
|
||||
pub suffix_dash: String,
|
||||
}
|
||||
|
||||
/// Compares two semver-like strings (`X.Y.Z` with optional `-suffix`, suffix ignored).
|
||||
///
|
||||
/// Returns `None` if either side has no parseable numeric major component.
|
||||
/// Ordering is tool-centric: `compare_semver(tool, required)` → `Less` means tool is older.
|
||||
pub fn compare_semver(left: &str, right: &str) -> Option<std::cmp::Ordering> {
|
||||
let l = semver_numeric_parts(left)?;
|
||||
let r = semver_numeric_parts(right)?;
|
||||
Some(l.cmp(&r))
|
||||
}
|
||||
|
||||
fn semver_numeric_parts(version: &str) -> Option<(u64, u64, u64)> {
|
||||
let base = version
|
||||
.trim()
|
||||
.split_once('-')
|
||||
.map(|(h, _)| h)
|
||||
.unwrap_or(version.trim());
|
||||
if base.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut parts = base.split('.');
|
||||
let major = parts.next()?.parse::<u64>().ok()?;
|
||||
let minor = parts.next().unwrap_or("0").parse::<u64>().unwrap_or(0);
|
||||
let patch = parts
|
||||
.next()
|
||||
.unwrap_or("0")
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_digit())
|
||||
.collect::<String>()
|
||||
.parse::<u64>()
|
||||
.unwrap_or(0);
|
||||
Some((major, minor, patch))
|
||||
}
|
||||
|
||||
/// Parses a version string into VersionInfo struct.
|
||||
pub fn parse_version_info(version: &str) -> VersionInfo {
|
||||
let mut full = version.trim().to_string();
|
||||
@@ -761,6 +794,16 @@ mod tests {
|
||||
assert_eq!(info.suffix_dash, "-beta");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_semver_orders_and_ignores_prerelease() {
|
||||
use std::cmp::Ordering;
|
||||
assert_eq!(compare_semver("0.8.4", "0.8.4"), Some(Ordering::Equal));
|
||||
assert_eq!(compare_semver("0.8.3", "0.8.4"), Some(Ordering::Less));
|
||||
assert_eq!(compare_semver("0.9.0", "0.8.4"), Some(Ordering::Greater));
|
||||
assert_eq!(compare_semver("1.0.0-dev", "1.0.0"), Some(Ordering::Equal));
|
||||
assert_eq!(compare_semver("not-a-version", "0.1.0"), None);
|
||||
}
|
||||
|
||||
/// Test for normalize_target_with_profile function.
|
||||
#[test]
|
||||
fn normalize_target_with_profile() {
|
||||
|
||||
Reference in New Issue
Block a user