cubecl_environment/bundle/
manifest.rs1use alloc::string::{String, ToString};
2use alloc::vec::Vec;
3
4#[cfg(native_cache)]
5use crate::persistence::Database;
6
7#[cfg(native_cache)]
9const MANIFEST_KEY: &str = "manifest";
10
11pub const MANIFEST_SCHEMA: u32 = 1;
13
14#[derive(Debug)]
16pub enum BundleError {
17 #[cfg(native_cache)]
19 Io(std::io::Error),
20 #[cfg(native_cache)]
22 Database(rusqlite::Error),
23 NotABundle,
25 InvalidManifest(String),
27 UnsupportedSchema(u32),
29 TooLarge,
31 Flat(super::EmbeddedBundleError),
33}
34
35impl core::fmt::Display for BundleError {
36 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37 match self {
38 #[cfg(native_cache)]
39 BundleError::Io(err) => write!(f, "bundle io error: {err}"),
40 #[cfg(native_cache)]
41 BundleError::Database(err) => write!(f, "bundle database error: {err}"),
42 BundleError::NotABundle => write!(f, "the file carries no bundle manifest"),
43 BundleError::InvalidManifest(err) => write!(f, "invalid bundle manifest: {err}"),
44 BundleError::UnsupportedSchema(schema) => {
45 write!(
46 f,
47 "unsupported bundle schema {schema} (this build supports {MANIFEST_SCHEMA})"
48 )
49 }
50 BundleError::TooLarge => write!(
51 f,
52 "the flat bundle format addresses at most {} bytes; export fewer namespaces",
53 u32::MAX
54 ),
55 BundleError::Flat(err) => write!(f, "{err}"),
58 }
59 }
60}
61
62impl core::error::Error for BundleError {}
63
64impl From<super::EmbeddedBundleError> for BundleError {
65 fn from(err: super::EmbeddedBundleError) -> Self {
66 Self::Flat(err)
67 }
68}
69
70#[cfg(native_cache)]
71impl From<std::io::Error> for BundleError {
72 fn from(err: std::io::Error) -> Self {
73 Self::Io(err)
74 }
75}
76
77#[cfg(native_cache)]
78impl From<rusqlite::Error> for BundleError {
79 fn from(err: rusqlite::Error) -> Self {
80 Self::Database(err)
81 }
82}
83
84#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
88pub struct BundleManifest {
89 pub schema: u32,
91 pub name: String,
93 pub cubecl_version: String,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub created_unix_secs: Option<u64>,
99 #[serde(default, rename = "environments")]
101 pub environments: Vec<EnvironmentInfo>,
102}
103
104#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
107pub struct EnvironmentInfo {
108 #[serde(default)]
110 pub label: String,
111 #[serde(default)]
113 pub os: String,
114 #[serde(default)]
116 pub arch: String,
117 #[serde(default)]
119 pub devices: Vec<String>,
120}
121
122impl BundleManifest {
123 pub fn parse(content: &[u8]) -> Result<Self, BundleError> {
130 if content.is_empty() {
131 return Err(BundleError::NotABundle);
132 }
133
134 let manifest: Self = serde_json::from_slice(content)
135 .map_err(|err| BundleError::InvalidManifest(err.to_string()))?;
136
137 if manifest.schema != MANIFEST_SCHEMA {
138 return Err(BundleError::UnsupportedSchema(manifest.schema));
139 }
140
141 Ok(manifest)
142 }
143
144 pub fn warn_on_version_mismatch(&self) {
150 if self.cubecl_version != env!("CARGO_PKG_VERSION") {
151 log::warn!(
152 "Bundle '{}' was built for cubecl {}, running {}; its entries will be ignored.",
153 self.name,
154 self.cubecl_version,
155 env!("CARGO_PKG_VERSION"),
156 );
157 }
158 }
159
160 #[cfg(native_cache)]
162 pub fn read(database: &Database) -> Result<Self, BundleError> {
163 let content = read_meta(database, MANIFEST_KEY)?.ok_or(BundleError::NotABundle)?;
164
165 Self::parse(content.as_bytes())
166 }
167
168 #[cfg(native_cache)]
170 pub fn write(&self, database: &Database) -> Result<(), BundleError> {
171 let content = serde_json::to_string_pretty(self)
172 .map_err(|err| BundleError::InvalidManifest(err.to_string()))?;
173
174 database.with_connection(|conn| {
175 crate::persistence::sqlite::meta_set(conn, MANIFEST_KEY, &content)
176 })?;
177
178 Ok(())
179 }
180}
181
182#[cfg(native_cache)]
183fn read_meta(database: &Database, key: &str) -> Result<Option<String>, BundleError> {
184 let content = database.with_connection(|conn| crate::persistence::sqlite::meta_get(conn, key));
185
186 match content {
187 Ok(content) => Ok(content),
188 Err(err) if is_missing_meta(&err) => Err(BundleError::NotABundle),
194 Err(err) => Err(BundleError::Database(err)),
195 }
196}
197
198#[cfg(native_cache)]
204fn is_missing_meta(err: &rusqlite::Error) -> bool {
205 match err {
206 rusqlite::Error::SqliteFailure(err, _) if err.code == rusqlite::ErrorCode::NotADatabase => {
207 true
208 }
209 rusqlite::Error::SqliteFailure(_, Some(message))
210 | rusqlite::Error::SqlInputError { msg: message, .. } => message.contains("no such table"),
211 _ => false,
212 }
213}