forked from 1b-agent/opencadstudio-upstream
feat(plugins): include rustc version in plugin compatibility gate
OpenCADStudio/plugins#901 - Record the host's rustc --version output in the embedded version_info.json. - Add rustc_version/rustc_declared/rustc_compatible to plugin manifests and release metadata, mirroring the existing acadrust gate. - Reject API v4+ plugins whose declared rustc differs from the host, with a clear one-line error message. - Propagate the rustc gate through the marketplace, install flow, and UI. - Update plugin architecture docs to describe the new ABI metadata.
This commit is contained in:
1 parent
3024869de0
commit
ed3e064a47
7 files changed
+344
-7
No files matched your search
@@ -50,10 +50,11 @@ The host and each plugin run in separate OS processes. The host re-executes itse
|
||||
- A plugin built against major `N` runs on a host whose major is `>= N` because new vtable entries and enum variants are appended at the end.
|
||||
- V4 introduces the **acadrust gate**: plugins targeting API v4 or later must resolve the same `acadrust` source as the host (see [`src/version_info.rs`](src/version_info.rs)).
|
||||
|
||||
The runtime enforces two gates:
|
||||
The runtime enforces three gates:
|
||||
|
||||
1. `ocs_plugin_api_version()` exported by the cdylib must be within `[API_VERSION_MIN_SUPPORTED, effective_max_api_version()]`.
|
||||
2. For v4+, `acadrust_sources_compatible(host_acadrust_source(), plugin_acadrust_source)` must be true.
|
||||
3. For v4+, when the plugin declares `rustc_version`, `rustc_versions_compatible(host_rustc_version(), plugin_rustc_version)` must be true.
|
||||
|
||||
---
|
||||
|
||||
@@ -215,7 +216,7 @@ Broadcasting is V4-gated inside `PluginManager::broadcast_notification`: V2/V3 p
|
||||
The crate embeds two JSON blobs at build time:
|
||||
|
||||
- **Type registry** (`OUT_DIR/type_registry.json`) — a language-binding-friendly schema for a curated allow-list of `acadrust` types, generated by `serde-reflection` in `build.rs`. See [`src/type_registry.rs`](src/type_registry.rs).
|
||||
- **Version info** (`OUT_DIR/version_info.json`) — host version, `ocs_plugin_api` version, `acadrust` version and source, API versions, build timestamp. See [`src/version_info.rs`](src/version_info.rs).
|
||||
- **Version info** (`OUT_DIR/version_info.json`) — host version, `ocs_plugin_api` version, `acadrust` version and source, `rustc` version, API versions, build timestamp. See [`src/version_info.rs`](src/version_info.rs).
|
||||
|
||||
Both are accessible without enabling the `host` feature.
|
||||
|
||||
@@ -242,6 +243,7 @@ This is defense-in-depth, not a sandbox: plugins execute native code.
|
||||
| Spawn timeout or crash | Spawn fails; plugin not loaded; error surfaced in UI/logs. |
|
||||
| Version mismatch | Host refuses plugin before running plugin code. |
|
||||
| Acadrust source mismatch (v4+) | Host refuses plugin to avoid binary incompatibilities. |
|
||||
| Rustc mismatch (v4+) | Host refuses plugin to avoid cross-compiler ABI crashes. |
|
||||
| Unknown notification discriminant | Deserializes to `HostNotification::Unknown`; plugin can ignore. |
|
||||
|
||||
---
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use cargo_lock::Lockfile;
|
||||
@@ -383,11 +384,25 @@ fn generate_version_info(out_dir: &Path) {
|
||||
.find(|p| p.name.as_str() == "acadrust")
|
||||
.expect("acadrust package in Cargo.lock");
|
||||
|
||||
let rustc_version = Command::new("rustc")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
if o.status.success() {
|
||||
Some(String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let info = serde_json::json!({
|
||||
"ocs_version": ocs.version.to_string(),
|
||||
"ocs_plugin_api_version": env!("CARGO_PKG_VERSION"),
|
||||
"acadrust_version": acadrust.version.to_string(),
|
||||
"acadrust_source": acadrust.source.as_ref().map(|s| s.to_string()),
|
||||
"rustc_version": rustc_version,
|
||||
"api_version": 4,
|
||||
"api_version_min_supported": 2,
|
||||
"build_timestamp": build_timestamp,
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
//! `acadrust` source with its own via [`acadrust_sources_compatible`]. Two
|
||||
//! sources are considered compatible only when they resolve to the same 40
|
||||
//! character git commit hash.
|
||||
//!
|
||||
//! The rustc version string is also recorded because Rust has no stable ABI:
|
||||
//! a plugin built with a different compiler than the host can pass simple calls
|
||||
//! and then crash the runner on compound types. For API v4 and later the host
|
||||
//! can compare the plugin's declared rustc with its own via
|
||||
//! [`rustc_versions_compatible`].
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -26,6 +32,7 @@ pub fn get_embedded_version_info_json() -> &'static str {
|
||||
#[derive(Debug, Clone)]
|
||||
struct VersionInfo {
|
||||
acadrust_source: String,
|
||||
rustc_version: String,
|
||||
}
|
||||
|
||||
pub const ACADRUST_GATE_API_VERSION: u32 = 4;
|
||||
@@ -48,6 +55,8 @@ fn parsed_version_info() -> &'static VersionInfo {
|
||||
INFO.get_or_init(|| VersionInfo {
|
||||
acadrust_source: json_string_value(EMBEDDED_VERSION_INFO_JSON, "acadrust_source")
|
||||
.unwrap_or_default(),
|
||||
rustc_version: json_string_value(EMBEDDED_VERSION_INFO_JSON, "rustc_version")
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,6 +65,25 @@ pub fn host_acadrust_source() -> &'static str {
|
||||
&parsed_version_info().acadrust_source
|
||||
}
|
||||
|
||||
/// Returns the host's `rustc --version` output.
|
||||
pub fn host_rustc_version() -> &'static str {
|
||||
&parsed_version_info().rustc_version
|
||||
}
|
||||
|
||||
/// Returns whether two `rustc --version` outputs are considered compatible.
|
||||
///
|
||||
/// The full version line (e.g. "rustc 1.98.0 (hash date)") is compared
|
||||
/// verbatim after normalising whitespace. This is intentionally strict: Rust
|
||||
/// has no stable ABI, so even the same nominal compiler built in different
|
||||
/// environments can produce incompatible layouts.
|
||||
pub fn rustc_versions_compatible(a: &str, b: &str) -> bool {
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return false;
|
||||
}
|
||||
a.split_whitespace().collect::<String>()
|
||||
.eq_ignore_ascii_case(&b.split_whitespace().collect::<String>())
|
||||
}
|
||||
|
||||
/// Extracts the full git commit hash from a Cargo source.
|
||||
pub fn acadrust_source_hash(source: &str) -> Option<&str> {
|
||||
let hash = source.rsplit('#').next()?;
|
||||
@@ -85,6 +113,7 @@ mod tests {
|
||||
let value: serde_json::Value = serde_json::from_str(json).expect("valid JSON");
|
||||
assert!(value.get("ocs_version").is_some());
|
||||
assert!(value.get("acadrust_source").is_some());
|
||||
assert!(value.get("rustc_version").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -96,6 +125,7 @@ mod tests {
|
||||
"ocs_plugin_api_version",
|
||||
"acadrust_version",
|
||||
"acadrust_source",
|
||||
"rustc_version",
|
||||
"api_version",
|
||||
"api_version_min_supported",
|
||||
"build_timestamp",
|
||||
@@ -125,6 +155,38 @@ mod tests {
|
||||
assert!(!host_acadrust_source().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_rustc_version_is_non_empty() {
|
||||
assert!(!host_rustc_version().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_version_comparison_is_strict() {
|
||||
assert!(rustc_versions_compatible(
|
||||
"rustc 1.98.0 (abc123 2026-01-01)",
|
||||
"rustc 1.98.0 (abc123 2026-01-01)"
|
||||
));
|
||||
assert!(!rustc_versions_compatible(
|
||||
"rustc 1.96.0 (def456 2025-11-01)",
|
||||
"rustc 1.98.0 (abc123 2026-01-01)"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_version_comparison_normalises_whitespace() {
|
||||
assert!(rustc_versions_compatible(
|
||||
"rustc 1.98.0 (abc123 2026-01-01)",
|
||||
"rustc 1.98.0 (abc123 2026-01-01)"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_version_comparison_rejects_empty() {
|
||||
assert!(!rustc_versions_compatible("", "rustc 1.98.0"));
|
||||
assert!(!rustc_versions_compatible("rustc 1.98.0", ""));
|
||||
assert!(!rustc_versions_compatible("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_gate_starts_at_api_v4() {
|
||||
assert!(!uses_acadrust_gate(3));
|
||||
|
||||
@@ -89,6 +89,25 @@ plugin compiles against. Two tiers:
|
||||
|
||||
A plugin enables the `host` feature.
|
||||
|
||||
### Build-time ABI metadata
|
||||
|
||||
For API v4 and later the host records two pieces of build-time metadata that
|
||||
affect whether a prebuilt plugin can safely be loaded:
|
||||
|
||||
- `acadrust_source` — the exact git source of the `acadrust` crate the host was
|
||||
built against.
|
||||
- `rustc_version` — the output of `rustc --version` for the compiler that built
|
||||
the host.
|
||||
|
||||
A plugin's `plugin.toml` may declare the same values under `[opencad]`. When
|
||||
either is declared, the host refuses to load the plugin unless it matches the
|
||||
host exactly. This turns the current silent runner crash into a one-line
|
||||
diagnosis such as:
|
||||
|
||||
```
|
||||
Plugin built with rustc 1.96.0, host requires 1.98.0 - rebuild required
|
||||
```
|
||||
|
||||
### `PluginManifest`
|
||||
|
||||
```rust
|
||||
@@ -161,6 +180,10 @@ ocs_plugin_api = { git = "https://github.com/HakanSeven12/OpenCADStudio", featur
|
||||
acadrust = { git = "https://github.com/HakanSeven12/acadrust", branch = "main" }
|
||||
```
|
||||
|
||||
Your release build must also use the same `rustc` as the host. Record it in
|
||||
`plugin.toml` (see below) and pin the toolchain in your CI matrix so every
|
||||
published asset matches the host's compiler.
|
||||
|
||||
```rust
|
||||
// src/lib.rs
|
||||
use ocs_plugin_api::host::{BuiltinPlugin, HostApi};
|
||||
@@ -210,6 +233,8 @@ api_version = 3
|
||||
ribbon_order = 50
|
||||
command_prefixes = ["EX_"]
|
||||
xdata_apps = []
|
||||
# For API v4+, also record the exact rustc used to build the release asset.
|
||||
# rustc_version = "rustc 1.98.0 (hash date)"
|
||||
```
|
||||
|
||||
The full, buildable scaffold is in [`docs/plugin-template/`](plugin-template);
|
||||
|
||||
+162
-1
@@ -36,6 +36,12 @@ pub struct ReleaseInfo {
|
||||
pub acadrust_declared: bool,
|
||||
/// Whether the release matches this host.
|
||||
pub acadrust_compatible: bool,
|
||||
/// Full `rustc --version` output used by the release.
|
||||
pub rustc_version: Option<String>,
|
||||
/// Whether `[opencad]` declares `rustc_version`.
|
||||
pub rustc_declared: bool,
|
||||
/// Whether the release's rustc matches this host.
|
||||
pub rustc_compatible: bool,
|
||||
}
|
||||
|
||||
/// An add-on package found on disk (not necessarily loaded or compatible).
|
||||
@@ -54,6 +60,10 @@ pub struct ExternalPlugin {
|
||||
pub acadrust_source: Option<String>,
|
||||
/// Whether `[opencad]` declares `acadrust_source`.
|
||||
pub acadrust_declared: bool,
|
||||
/// Full `rustc --version` output used by the plugin.
|
||||
pub rustc_version: Option<String>,
|
||||
/// Whether `[opencad]` declares `rustc_version`.
|
||||
pub rustc_declared: bool,
|
||||
pub ribbon_order: i32,
|
||||
pub command_prefixes: Vec<String>,
|
||||
/// The package directory under the plugins folder.
|
||||
@@ -85,10 +95,30 @@ impl ExternalPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the package's rustc matches the host.
|
||||
pub fn rustc_compatible(&self) -> bool {
|
||||
if !ocs_plugin_api::version_info::uses_acadrust_gate(self.api_version) {
|
||||
return true;
|
||||
}
|
||||
if !self.rustc_declared {
|
||||
return true;
|
||||
}
|
||||
match self.rustc_version.as_deref() {
|
||||
None | Some("") => false,
|
||||
Some(version) => ocs_plugin_api::version_info::rustc_versions_compatible(
|
||||
version,
|
||||
ocs_plugin_api::version_info::host_rustc_version(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the package can be loaded.
|
||||
#[allow(dead_code)] // plugin-host surface (issue #100); not yet wired
|
||||
pub fn loadable(&self) -> bool {
|
||||
self.api_compatible() && self.acadrust_compatible() && self.lib_present
|
||||
self.api_compatible()
|
||||
&& self.acadrust_compatible()
|
||||
&& self.rustc_compatible()
|
||||
&& self.lib_present
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +234,8 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
let mut api_version: u32 = 0;
|
||||
let mut acadrust_source: Option<String> = None;
|
||||
let mut acadrust_declared = false;
|
||||
let mut rustc_version: Option<String> = None;
|
||||
let mut rustc_declared = false;
|
||||
let mut ribbon_order: i32 = 0;
|
||||
let mut command_prefixes: Vec<String> = Vec::new();
|
||||
let mut section = None;
|
||||
@@ -236,6 +268,11 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
let v = unquote(value);
|
||||
acadrust_source = if v.is_empty() { None } else { Some(v) };
|
||||
}
|
||||
"rustc_version" if section == Some("opencad") => {
|
||||
rustc_declared = true;
|
||||
let v = unquote(value);
|
||||
rustc_version = if v.is_empty() { None } else { Some(v) };
|
||||
}
|
||||
"ribbon_order" => ribbon_order = value.parse().unwrap_or(0),
|
||||
"command_prefixes" => command_prefixes = parse_string_array(value),
|
||||
_ => {}
|
||||
@@ -251,6 +288,8 @@ pub(crate) fn parse_plugin_toml(text: &str) -> Option<ExternalPlugin> {
|
||||
api_version,
|
||||
acadrust_source,
|
||||
acadrust_declared,
|
||||
rustc_version,
|
||||
rustc_declared,
|
||||
ribbon_order,
|
||||
command_prefixes,
|
||||
dir: PathBuf::new(),
|
||||
@@ -372,6 +411,17 @@ mod loader {
|
||||
));
|
||||
continue;
|
||||
}
|
||||
if !d.rustc_compatible() {
|
||||
let host_rustc = ocs_plugin_api::version_info::host_rustc_version();
|
||||
let plugin_rustc = d.rustc_version.as_deref().unwrap_or("unknown");
|
||||
out.push((
|
||||
d.id.clone(),
|
||||
Err(format!(
|
||||
"Plugin built with {plugin_rustc}, host requires {host_rustc} - rebuild required"
|
||||
)),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let Some(path) = lib_file(&d.dir) else {
|
||||
out.push((
|
||||
d.id.clone(),
|
||||
@@ -483,6 +533,117 @@ xdata_apps = ["MYPLUGIN_RECORD"]
|
||||
assert!(!p.loadable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undeclared_rustc_falls_back_to_api_gate() {
|
||||
let toml = r#"
|
||||
[plugin]
|
||||
id = "opencad.test"
|
||||
name = "Test"
|
||||
version = "0.1.0"
|
||||
api_version = 4
|
||||
"#;
|
||||
let p = parse_plugin_toml(toml).expect("parsed");
|
||||
assert!(!p.rustc_declared);
|
||||
assert!(p.rustc_version.is_none());
|
||||
assert!(p.rustc_compatible(), "undeclared rustc is treated as compatible");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declared_empty_rustc_version_is_incompatible() {
|
||||
let toml = r#"
|
||||
[plugin]
|
||||
id = "opencad.test"
|
||||
name = "Test"
|
||||
version = "0.1.0"
|
||||
api_version = 4
|
||||
|
||||
[opencad]
|
||||
rustc_version = ""
|
||||
"#;
|
||||
let p = parse_plugin_toml(toml).expect("parsed");
|
||||
assert!(p.rustc_declared);
|
||||
assert!(p.rustc_version.is_none());
|
||||
assert!(!p.rustc_compatible(), "declared but empty rustc is incompatible");
|
||||
assert!(!p.loadable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_version_outside_opencad_is_ignored() {
|
||||
let toml = r#"
|
||||
[plugin]
|
||||
id = "opencad.test"
|
||||
api_version = 4
|
||||
rustc_version = "rustc 1.98.0"
|
||||
"#;
|
||||
let p = parse_plugin_toml(toml).expect("parsed");
|
||||
assert!(!p.rustc_declared);
|
||||
assert!(p.rustc_version.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_mismatch_detected() {
|
||||
let _host = ocs_plugin_api::version_info::host_rustc_version();
|
||||
let other = "rustc 0.0.0-fake (not matching anything)";
|
||||
let toml = format!(
|
||||
r#"
|
||||
[plugin]
|
||||
id = "opencad.test"
|
||||
name = "Test"
|
||||
version = "0.1.0"
|
||||
api_version = 4
|
||||
|
||||
[opencad]
|
||||
rustc_version = "{other}"
|
||||
"#
|
||||
);
|
||||
let p = parse_plugin_toml(&toml).expect("parsed");
|
||||
assert!(p.rustc_declared);
|
||||
assert!(!p.rustc_compatible(), "mismatched rustc should be incompatible");
|
||||
assert!(!p.loadable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_match_detected() {
|
||||
let host = ocs_plugin_api::version_info::host_rustc_version();
|
||||
let toml = format!(
|
||||
r#"
|
||||
[plugin]
|
||||
id = "opencad.test"
|
||||
name = "Test"
|
||||
version = "0.1.0"
|
||||
api_version = 4
|
||||
|
||||
[opencad]
|
||||
rustc_version = "{host}"
|
||||
"#
|
||||
);
|
||||
let p = parse_plugin_toml(&toml).expect("parsed");
|
||||
assert!(p.rustc_declared);
|
||||
assert!(p.rustc_compatible(), "matching rustc should be compatible");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rustc_gate_only_applies_to_api_v4_and_newer() {
|
||||
let toml = r#"
|
||||
[plugin]
|
||||
id = "opencad.test"
|
||||
name = "Test"
|
||||
version = "0.1.0"
|
||||
api_version = 2
|
||||
|
||||
[opencad]
|
||||
rustc_version = "rustc 0.0.0-fake"
|
||||
"#;
|
||||
let mut p = parse_plugin_toml(toml).expect("parsed");
|
||||
p.lib_present = true;
|
||||
assert!(p.rustc_declared);
|
||||
assert!(
|
||||
p.rustc_compatible(),
|
||||
"API v2 plugin should bypass rustc gate"
|
||||
);
|
||||
assert!(p.loadable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undeclared_acadrust_falls_back_to_api_gate() {
|
||||
let toml = r#"
|
||||
|
||||
@@ -236,12 +236,32 @@ pub fn fetch_release_info(repo: &str) -> Result<Vec<ReleaseInfo>, String> {
|
||||
),
|
||||
}
|
||||
};
|
||||
let rustc_version = manifest.rustc_version.clone();
|
||||
let rustc_declared = manifest.rustc_declared;
|
||||
let rustc_compatible = if !ocs_plugin_api::version_info::uses_acadrust_gate(
|
||||
manifest.api_version,
|
||||
) {
|
||||
true
|
||||
} else if !rustc_declared {
|
||||
true
|
||||
} else {
|
||||
match rustc_version.as_deref() {
|
||||
None | Some("") => false,
|
||||
Some(version) => ocs_plugin_api::version_info::rustc_versions_compatible(
|
||||
version,
|
||||
ocs_plugin_api::version_info::host_rustc_version(),
|
||||
),
|
||||
}
|
||||
};
|
||||
Ok::<_, String>(ReleaseInfo {
|
||||
tag: release.tag,
|
||||
api_version: manifest.api_version,
|
||||
acadrust_source,
|
||||
acadrust_declared,
|
||||
acadrust_compatible,
|
||||
rustc_version,
|
||||
rustc_declared,
|
||||
rustc_compatible,
|
||||
})
|
||||
})();
|
||||
match result {
|
||||
@@ -251,8 +271,8 @@ pub fn fetch_release_info(repo: &str) -> Result<Vec<ReleaseInfo>, String> {
|
||||
}
|
||||
|
||||
// Once a repo declares a fingerprint, require it on later API versions.
|
||||
let any_declared = info.iter().any(|r| r.acadrust_declared);
|
||||
if any_declared {
|
||||
let any_acadrust_declared = info.iter().any(|r| r.acadrust_declared);
|
||||
if any_acadrust_declared {
|
||||
for r in &mut info {
|
||||
if !r.acadrust_declared
|
||||
&& ocs_plugin_api::version_info::uses_acadrust_gate(r.api_version)
|
||||
@@ -261,6 +281,16 @@ pub fn fetch_release_info(repo: &str) -> Result<Vec<ReleaseInfo>, String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
let any_rustc_declared = info.iter().any(|r| r.rustc_declared);
|
||||
if any_rustc_declared {
|
||||
for r in &mut info {
|
||||
if !r.rustc_declared
|
||||
&& ocs_plugin_api::version_info::uses_acadrust_gate(r.api_version)
|
||||
{
|
||||
r.rustc_compatible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if info.is_empty() {
|
||||
Err(last_error.unwrap_or_else(|| "no installable releases found".to_string()))
|
||||
@@ -361,6 +391,30 @@ pub fn install(release: &Release, repository: &str) -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
if ocs_plugin_api::version_info::uses_acadrust_gate(manifest.api_version)
|
||||
&& manifest.rustc_declared
|
||||
{
|
||||
let Some(version) = manifest.rustc_version.as_deref() else {
|
||||
return Err(
|
||||
"Release declares rustc metadata but has no version; cannot verify ABI compatibility".to_string(),
|
||||
);
|
||||
};
|
||||
if version.is_empty() {
|
||||
return Err(
|
||||
"Release declares rustc metadata but has no version; cannot verify ABI compatibility".to_string(),
|
||||
);
|
||||
}
|
||||
if !ocs_plugin_api::version_info::rustc_versions_compatible(
|
||||
version,
|
||||
ocs_plugin_api::version_info::host_rustc_version(),
|
||||
) {
|
||||
let host_rustc = ocs_plugin_api::version_info::host_rustc_version();
|
||||
return Err(format!(
|
||||
"Plugin built with {version}, host requires {host_rustc} - rebuild required"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let dir = external::plugins_dir()
|
||||
.ok_or("cannot locate the plugins folder")?
|
||||
.join(&manifest.id);
|
||||
|
||||
@@ -203,6 +203,7 @@ fn newest_update(installed: &str, releases: &[ReleaseInfo]) -> Option<String> {
|
||||
.filter(|release| {
|
||||
ocs_plugin_api::manifest::host_accepts_plugin_version(release.api_version)
|
||||
&& release.acadrust_compatible
|
||||
&& release.rustc_compatible
|
||||
})
|
||||
.filter_map(|release| {
|
||||
semver::Version::parse(trim_version_prefix(&release.tag))
|
||||
@@ -225,13 +226,14 @@ fn external_card<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
let failed_old_api = load_error.is_some() && !p.api_compatible();
|
||||
let acadrust_mismatch = p.acadrust_declared && !p.acadrust_compatible();
|
||||
let rustc_mismatch = p.rustc_declared && !p.rustc_compatible();
|
||||
let (status, kind) = if loaded && disabled {
|
||||
(t!("Disabled"), StatusKind::Muted)
|
||||
} else if loaded {
|
||||
(t!("Loaded"), StatusKind::Success)
|
||||
} else if !p.api_compatible() || failed_old_api {
|
||||
(t!("API incompatible"), StatusKind::Danger)
|
||||
} else if acadrust_mismatch {
|
||||
} else if acadrust_mismatch || rustc_mismatch {
|
||||
(t!("Incompatible"), StatusKind::Danger)
|
||||
} else if load_error.is_some() {
|
||||
(t!("Load failed"), StatusKind::Danger)
|
||||
@@ -375,7 +377,8 @@ fn install_controls<'a>(
|
||||
let action = match selected_release {
|
||||
Some(release)
|
||||
if ocs_plugin_api::manifest::host_accepts_plugin_version(release.api_version)
|
||||
&& release.acadrust_compatible =>
|
||||
&& release.acadrust_compatible
|
||||
&& release.rustc_compatible =>
|
||||
{
|
||||
pill_button(
|
||||
t!("Install"),
|
||||
@@ -990,6 +993,9 @@ mod tests {
|
||||
acadrust_source: None,
|
||||
acadrust_declared: false,
|
||||
acadrust_compatible: true,
|
||||
rustc_version: None,
|
||||
rustc_declared: false,
|
||||
rustc_compatible: true,
|
||||
},
|
||||
ReleaseInfo {
|
||||
tag: "v2.0.0".to_string(),
|
||||
@@ -997,6 +1003,9 @@ mod tests {
|
||||
acadrust_source: None,
|
||||
acadrust_declared: false,
|
||||
acadrust_compatible: true,
|
||||
rustc_version: None,
|
||||
rustc_declared: false,
|
||||
rustc_compatible: true,
|
||||
},
|
||||
ReleaseInfo {
|
||||
tag: "v1.9.9".to_string(),
|
||||
@@ -1004,6 +1013,9 @@ mod tests {
|
||||
acadrust_source: None,
|
||||
acadrust_declared: false,
|
||||
acadrust_compatible: true,
|
||||
rustc_version: None,
|
||||
rustc_declared: false,
|
||||
rustc_compatible: true,
|
||||
},
|
||||
];
|
||||
assert_eq!(newest_update("1.3.0", &releases).as_deref(), Some("v2.0.0"));
|
||||
@@ -1021,6 +1033,9 @@ mod tests {
|
||||
),
|
||||
acadrust_declared: true,
|
||||
acadrust_compatible: true,
|
||||
rustc_version: None,
|
||||
rustc_declared: false,
|
||||
rustc_compatible: true,
|
||||
},
|
||||
ReleaseInfo {
|
||||
tag: "v2.0.0".to_string(),
|
||||
@@ -1030,6 +1045,9 @@ mod tests {
|
||||
),
|
||||
acadrust_declared: true,
|
||||
acadrust_compatible: false,
|
||||
rustc_version: None,
|
||||
rustc_declared: false,
|
||||
rustc_compatible: true,
|
||||
},
|
||||
];
|
||||
assert_eq!(newest_update("1.3.0", &releases).as_deref(), Some("v1.4.0"));
|
||||
|
||||
Reference in new issue
Block a user