zenoh_plugin_trait/
compatibility.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15use std::fmt::Display;
16
17use zenoh_result::{bail, ZResult};
18
19pub trait StructVersion {
20    /// The version of the structure which implements this trait.
21    fn struct_version() -> &'static str;
22    /// The features enabled during compilation of the structure implementing this trait.
23    /// Different features between the plugin and the host may cause ABI incompatibility even if the structure version is the same.
24    /// Use `concat_enabled_features!` to generate this string
25    fn struct_features() -> &'static str;
26}
27
28#[repr(C)]
29#[derive(Debug, Clone)]
30pub struct Compatibility {
31    rust_version: RustVersion,
32    zenoh_version: stabby::str::Str<'static>,
33    zenoh_features: stabby::str::Str<'static>,
34}
35
36impl Compatibility {
37    pub fn new(zenoh_version: &'static str, zenoh_features: &'static str) -> Self {
38        let rust_version = RustVersion::new();
39        Self {
40            rust_version,
41            zenoh_version: zenoh_version.into(),
42            zenoh_features: zenoh_features.into(),
43        }
44    }
45
46    pub fn check(&self, other: &Self) -> ZResult<()> {
47        fn get_version_and_commit(version: &str) -> (&str, &str) {
48            let parts = version.split('-').collect::<Vec<_>>();
49            (
50                parts.first().cloned().unwrap_or("undefined"),
51                parts.get(1).cloned().unwrap_or("undefined"),
52            )
53        }
54
55        fn version_equals(left: &str, right: &str) -> bool {
56            const RELEASE_COMMIT: &str = "release"; // fallback from `zenoh::GIT_COMMIT`
57            let (left_version, left_commit) = get_version_and_commit(left);
58            let (right_version, right_commit) = get_version_and_commit(right);
59            if left_version != right_version {
60                return false;
61            }
62            // We check equality of git hashes of zenoh crate used by plugin and host.
63            // This is mostly done for development purposes, to avoid crashes in case of
64            // internal API change during releases.
65            // If we receive "release" instead of commit hash, it means that
66            // zenoh crate is taken from crates.io (or is built in the environment without git ?).
67            // In this case we ignore the commit hash check.
68            if left_commit != right_commit
69                && left_commit != RELEASE_COMMIT
70                && right_commit != RELEASE_COMMIT
71            {
72                return false;
73            }
74            true
75        }
76
77        if self.rust_version != other.rust_version {
78            bail!(
79                "Incompatible rustc versions:\n host: {}\n plugin: {}",
80                self.rust_version,
81                other.rust_version
82            )
83        } else if !version_equals(&self.zenoh_version, &other.zenoh_version) {
84            bail!(
85                "Incompatible Zenoh versions:\n host: {}\n plugin: {}",
86                self.zenoh_version,
87                other.zenoh_version
88            )
89        } else if self.zenoh_features != other.zenoh_features {
90            bail!(
91                "Incompatible Zenoh feature sets:\n host: {}\n plugin: {}",
92                self.zenoh_features,
93                other.zenoh_features
94            )
95        } else {
96            Ok(())
97        }
98    }
99}
100
101impl Display for Compatibility {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        writeln!(f, "Rust version:\n{}", self.rust_version)?;
104        writeln!(f, "Zenoh version:\n {}", &self.zenoh_version)?;
105        writeln!(f, "Zenoh features:\n {}", &self.zenoh_features)?;
106        Ok(())
107    }
108}
109
110#[repr(C)]
111#[derive(Debug, PartialEq, Eq, Clone)]
112pub struct RustVersion {
113    major: u32,
114    minor: u32,
115    patch: u32,
116    stable: bool,
117    commit: stabby::str::Str<'static>,
118}
119
120impl Display for RustVersion {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        write!(
123            f,
124            "Rust {}.{}.{}{} commit {}",
125            self.major,
126            self.minor,
127            self.patch,
128            if self.stable { "" } else { "-nightly" },
129            self.commit
130        )
131    }
132}
133
134const RELEASE_AND_COMMIT: (&str, &str) = zenoh_macros::rustc_version_release!();
135impl RustVersion {
136    pub fn new() -> Self {
137        let (release, commit) = RELEASE_AND_COMMIT;
138        let (release, stable) = if let Some(p) = release.chars().position(|c| c == '-') {
139            (&release[..p], false)
140        } else {
141            (release, true)
142        };
143        let mut split = release.split('.').map(|s| s.trim());
144        RustVersion {
145            major: split.next().unwrap().parse().unwrap(),
146            minor: split.next().unwrap().parse().unwrap(),
147            patch: split.next().unwrap().parse().unwrap(),
148            stable,
149            commit: commit.into(),
150        }
151    }
152}
153
154impl Default for RustVersion {
155    fn default() -> Self {
156        Self::new()
157    }
158}