Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure versions are compatible before loading model #1237

Merged
merged 9 commits into from
Oct 19, 2022
2 changes: 1 addition & 1 deletion crates/fj-app/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use fj_math::Scalar;

/// Fornjot - Experimental CAD System
#[derive(clap::Parser)]
#[command(version = env!("FJ_VERSION_STRING"))]
#[command(version = fj::version::VERSION_FULL)]
pub struct Args {
/// The model to open
#[arg(short, long)]
Expand Down
29 changes: 28 additions & 1 deletion crates/fj-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use std::{
thread,
};

use fj::abi;
use fj::{abi, version::RawVersion};
use notify::Watcher as _;
use thiserror::Error;

Expand Down Expand Up @@ -143,6 +143,23 @@ impl Model {
// https://github.com/hannobraun/Fornjot/issues/71
let shape = unsafe {
let lib = libloading::Library::new(&self.lib_path)?;

let version_pkg: libloading::Symbol<fn() -> RawVersion> =
lib.get(b"version_pkg")?;

let version_pkg = version_pkg();
if fj::version::VERSION_PKG != version_pkg.as_str() {
let host = String::from_utf8_lossy(
fj::version::VERSION_PKG.as_bytes(),
)
.into_owned();
let model =
String::from_utf8_lossy(version_pkg.as_str().as_bytes())
.into_owned();

return Err(Error::VersionMismatch { host, model });
}

let init: libloading::Symbol<abi::InitFunction> =
lib.get(abi::INIT_FUNCTION_NAME.as_bytes())?;

Expand Down Expand Up @@ -394,6 +411,16 @@ pub enum Error {
#[error("Error loading model from dynamic library")]
LibLoading(#[from] libloading::Error),

/// Host version and model version do not match
#[error("Host version ({host}) and model version ({model}) do not match")]
VersionMismatch {
/// The host version
host: String,

/// The model version
model: String,
},

/// Initializing a model failed.
#[error("Unable to initialize the model")]
InitializeModel(#[source] fj::models::Error),
Expand Down
40 changes: 27 additions & 13 deletions crates/fj-app/build.rs → crates/fj/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,38 @@ use std::{
};

fn main() {
println!("cargo:rustc-env=FJ_VERSION_STRING={}", version_string());
let version = Version::determine();

println!("cargo:rustc-env=FJ_VERSION_PKG={}", version.pkg_version);
println!("cargo:rustc-env=FJ_VERSION_FULL={}", version.full_string);
}

struct Version {
pkg_version: String,
full_string: String,
}
impl Version {
fn determine() -> Self {
let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap();
let commit = git_description();

fn version_string() -> String {
let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap();
let commit = git_description();
let official_release =
std::env::var("FJ_OFFICIAL_RELEASE").as_deref() == Ok("1");
println!("cargo:rerun-if-env-changed=FJ_OFFICIAL_RELEASE");

let official_release =
std::env::var("FJ_OFFICIAL_RELEASE").as_deref() == Ok("1");
println!("cargo:rerun-if-env-changed=FJ_OFFICIAL_RELEASE");
let full_string = match (commit, official_release) {
(Some(commit), true) => format!("{pkg_version} ({commit})"),
(Some(commit), false) => {
format!("{pkg_version} ({commit}, unreleased)")
}
(None, true) => pkg_version.clone(),
(None, false) => format!("{pkg_version} (unreleased)"),
};

match (commit, official_release) {
(Some(commit), true) => format!("{pkg_version} ({commit})"),
(Some(commit), false) => {
format!("{pkg_version} ({commit}, unreleased)")
Self {
pkg_version,
full_string,
}
(None, true) => pkg_version,
(None, false) => format!("{pkg_version} (unreleased)"),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/fj/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod models;
mod shape_2d;
mod sweep;
mod transform;
pub mod version;

pub use self::{
angle::*, group::Group, shape_2d::*, sweep::Sweep, transform::Transform,
Expand Down
54 changes: 54 additions & 0 deletions crates/fj/src/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! API for checking compatibility between the Fornjot app and a model

use core::slice;

/// The Fornjot package version
///
/// Can be used to check for compatibility between a model and the Fornjot app
/// that runs it.
///
/// This is just the version specified in the Cargo package, which will stay
/// constant between releases, even though changes are made throughout. A match
/// of this version does not conclusively determine that the app and a model are
/// compatible.
pub static VERSION_PKG: &str = env!("FJ_VERSION_PKG");

/// The full Fornjot version
///
/// Can be used to check for compatibility between a model and the Fornjot app
/// that runs it.
pub static VERSION_FULL: &str = env!("FJ_VERSION_FULL");

/// C-ABI-compatible representation of a version
///
/// Used by the Fornjot application to check for compatibility between a model
/// and the app.
#[repr(C)]
pub struct RawVersion {
/// The pointer to the `str`
pub ptr: *const u8,

/// The length of the `str`
pub len: usize,
}

impl RawVersion {
/// Convert the `RawVersion` into a string
///
/// # Safety
///
/// Must be a `RawVersion` returned from one of the hidden version functions
/// in this module.
pub unsafe fn as_str(&self) -> &str {
let slice = slice::from_raw_parts(self.ptr, self.len);
std::str::from_utf8(slice).unwrap()
}
}

#[no_mangle]
extern "C" fn version_pkg() -> RawVersion {
RawVersion {
ptr: VERSION_PKG.as_ptr(),
len: VERSION_PKG.len(),
}
}