1 use std::path::PathBuf; 2 3 use anyhow::Result; 4 use serde::Deserialize; 5 6 use crate::common::target_arch::TargetArch; 7 8 use std::fs; 9 use toml; 10 11 /// The main configuration file for DADK 12 #[derive(Debug, Clone, Deserialize)] 13 pub struct DadkManifestFile { 14 pub metadata: Metadata, 15 16 /// A flag variable used to indicate whether 17 /// the default value function was called during deserialization. 18 #[serde(skip)] 19 pub used_default: bool, 20 } 21 22 impl DadkManifestFile { 23 pub fn load(path: &PathBuf) -> Result<Self> { 24 // 读取文件内容 25 let content = fs::read_to_string(path)?; 26 Self::load_from_str(&content) 27 } 28 29 pub fn load_from_str(content: &str) -> Result<Self> { 30 // Parse TOML content 31 let mut manifest_toml: DadkManifestFile = toml::from_str(content)?; 32 33 manifest_toml.used_default = check_used_default(); 34 35 Ok(manifest_toml) 36 } 37 } 38 39 thread_local! { 40 /// Global variable to track if default values were used during deserialization. 41 static USED_DEFAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) }; 42 } 43 44 /// Call this function to set a flag when 45 /// default values are used during DADK manifest parsing 46 fn set_used_default() { 47 USED_DEFAULT.with(|used_default| { 48 used_default.set(true); 49 }); 50 } 51 52 /// Check if default values were used during deserialization. 53 fn check_used_default() -> bool { 54 USED_DEFAULT.with(|used_default| used_default.get()) 55 } 56 57 #[derive(Debug, Clone, Deserialize)] 58 pub struct Metadata { 59 /// Target processor architecture 60 pub arch: TargetArch, 61 /// Rootfs configuration file path 62 #[serde(default = "default_rootfs_config_path", rename = "rootfs-config")] 63 pub rootfs_config: PathBuf, 64 65 /// Hypervisor configuration file path 66 #[serde( 67 default = "default_hypervisor_config_path", 68 rename = "hypervisor-config" 69 )] 70 pub hypervisor_config: PathBuf, 71 72 /// Boot configuration file path 73 #[serde(default = "default_boot_config_path", rename = "boot-config")] 74 pub boot_config: PathBuf, 75 76 /// Sysroot directory path 77 #[serde(default = "default_sysroot_dir", rename = "sysroot-dir")] 78 pub sysroot_dir: PathBuf, 79 80 /// Root Cache directory path 81 #[serde(default = "default_cache_root_dir", rename = "cache-root-dir")] 82 pub cache_root_dir: PathBuf, 83 84 /// User configuration directory path 85 /// 这个字段只是临时用于兼容旧版本,v0.2版本重构完成后会删除 86 #[deprecated(note = "This field is deprecated and will be removed in DADK 0.2")] 87 #[serde(default = "default_user_config_dir", rename = "user-config-dir")] 88 pub user_config_dir: PathBuf, 89 } 90 91 /// Returns the default path for the rootfs configuration file. 92 fn default_rootfs_config_path() -> PathBuf { 93 set_used_default(); 94 "config/rootfs.toml".into() 95 } 96 97 /// Returns the default path for the hypervisor configuration file. 98 fn default_hypervisor_config_path() -> PathBuf { 99 set_used_default(); 100 "config/hypervisor.toml".into() 101 } 102 103 /// Returns the default path for the boot configuration file. 104 fn default_boot_config_path() -> PathBuf { 105 set_used_default(); 106 "config/boot.toml".into() 107 } 108 109 /// Returns the default path for the sysroot directory. 110 fn default_sysroot_dir() -> PathBuf { 111 set_used_default(); 112 "bin/sysroot".into() 113 } 114 115 /// Returns the default path for the cache directory. 116 fn default_cache_root_dir() -> PathBuf { 117 set_used_default(); 118 "bin/dadk_cache".into() 119 } 120 121 fn default_user_config_dir() -> PathBuf { 122 set_used_default(); 123 "user/dadk/config".into() 124 } 125 126 #[cfg(test)] 127 mod tests { 128 use super::*; 129 use std::io::Write; 130 use tempfile::NamedTempFile; 131 132 /// Test loading a complete configuration file 133 #[test] 134 fn test_full_load_success() -> Result<()> { 135 let toml_content = r#" 136 [metadata] 137 arch = "x86_64" 138 rootfs-config = "config/rootfs-x86_64.toml" 139 hypervisor-config = "config/hypervisor-x86_64.toml" 140 boot-config = "config/boot-x86_64.toml" 141 sysroot-dir = "bin/sysroot" 142 cache-root-dir = "bin/dadk_cache" 143 user-config-dir = "user/dadk/config" 144 "#; 145 146 let mut temp_file = NamedTempFile::new()?; 147 temp_file.write_all(toml_content.as_bytes())?; 148 149 let path = temp_file.path().to_path_buf(); 150 let manifest = DadkManifestFile::load(&path)?; 151 152 assert_eq!(manifest.metadata.arch, TargetArch::X86_64); 153 assert_eq!( 154 manifest.metadata.rootfs_config, 155 PathBuf::from("config/rootfs-x86_64.toml") 156 ); 157 assert_eq!( 158 manifest.metadata.hypervisor_config, 159 PathBuf::from("config/hypervisor-x86_64.toml") 160 ); 161 assert_eq!( 162 manifest.metadata.boot_config, 163 PathBuf::from("config/boot-x86_64.toml") 164 ); 165 assert_eq!(manifest.metadata.sysroot_dir, PathBuf::from("bin/sysroot")); 166 assert!(!manifest.used_default); 167 168 Ok(()) 169 } 170 171 /// Test whether an error is reported when the file does not exist. 172 #[test] 173 fn test_load_file_not_found() { 174 let path = PathBuf::from("non_existent_file.toml"); 175 let result = DadkManifestFile::load(&path); 176 177 assert!(result.is_err()); 178 } 179 180 /// Test whether an error is reported when the TOML content is invalid 181 #[test] 182 fn test_load_invalid_toml() -> Result<()> { 183 let invalid_toml_content = r#" 184 [metadata 185 arch = "x86_64" 186 "#; 187 188 let mut temp_file = NamedTempFile::new()?; 189 temp_file.write_all(invalid_toml_content.as_bytes())?; 190 191 let path = temp_file.path().to_path_buf(); 192 let result = DadkManifestFile::load(&path); 193 194 assert!(result.is_err()); 195 196 Ok(()) 197 } 198 199 /// Test whether an error is reported when the arch field is invalid 200 #[test] 201 fn test_load_invalid_arch_toml() -> Result<()> { 202 // Invalid arch value 203 let invalid_toml_content = r#" 204 [metadata] 205 arch = "abcde" 206 "#; 207 208 let mut temp_file = NamedTempFile::new()?; 209 temp_file.write_all(invalid_toml_content.as_bytes())?; 210 211 let path = temp_file.path().to_path_buf(); 212 let result = DadkManifestFile::load(&path); 213 214 assert!(result.is_err()); 215 216 Ok(()) 217 } 218 219 /// Test whether an error is reported when a required field is missing 220 #[test] 221 fn test_load_missing_required_fields() -> Result<()> { 222 let toml_content = r#" 223 [metadata] 224 # arch field is missing 225 "#; 226 227 let mut temp_file = NamedTempFile::new()?; 228 temp_file.write_all(toml_content.as_bytes())?; 229 230 let path = temp_file.path().to_path_buf(); 231 let result = DadkManifestFile::load(&path); 232 233 assert!(result.is_err()); 234 235 Ok(()) 236 } 237 238 /// Test whether default values are used 239 /// when the rootfs_config and other configuration file path fields are not set 240 #[test] 241 fn test_load_default_config_path_value() -> Result<()> { 242 let toml_content = r#" 243 [metadata] 244 arch = "x86_64" 245 "#; 246 let mut temp_file = NamedTempFile::new()?; 247 temp_file.write_all(toml_content.as_bytes())?; 248 let path = temp_file.path().to_path_buf(); 249 let manifest = DadkManifestFile::load(&path)?; 250 assert_eq!(manifest.used_default, true); 251 assert_eq!( 252 manifest.metadata.rootfs_config, 253 PathBuf::from("config/rootfs.toml") 254 ); 255 assert_eq!( 256 manifest.metadata.hypervisor_config, 257 PathBuf::from("config/hypervisor.toml") 258 ); 259 assert_eq!( 260 manifest.metadata.boot_config, 261 PathBuf::from("config/boot.toml") 262 ); 263 Ok(()) 264 } 265 } 266