cargo_image_runner/config/
loader.rs1use super::Config;
2use crate::core::error::{Error, Result};
3use cargo_metadata::MetadataCommand;
4use std::path::{Path, PathBuf};
5
6pub struct ConfigLoader {
8 workspace_root: Option<PathBuf>,
10 config_file: Option<PathBuf>,
12 use_cargo_metadata: bool,
14}
15
16impl ConfigLoader {
17 pub fn new() -> Self {
19 Self {
20 workspace_root: None,
21 config_file: None,
22 use_cargo_metadata: true,
23 }
24 }
25
26 pub fn workspace_root(mut self, root: impl Into<PathBuf>) -> Self {
28 self.workspace_root = Some(root.into());
29 self
30 }
31
32 pub fn config_file(mut self, path: impl Into<PathBuf>) -> Self {
34 self.config_file = Some(path.into());
35 self
36 }
37
38 pub fn no_cargo_metadata(mut self) -> Self {
40 self.use_cargo_metadata = false;
41 self
42 }
43
44 pub fn load(self) -> Result<(Config, PathBuf)> {
51 let mut config = Config::default();
52 let workspace_root;
53
54 if self.use_cargo_metadata {
56 let (root, cargo_config) = self.load_cargo_metadata()?;
57 workspace_root = root;
58 config = Self::merge_configs(config, cargo_config);
59 } else {
60 workspace_root = self
61 .workspace_root
62 .clone()
63 .ok_or_else(|| Error::config("workspace root not specified"))?;
64 }
65
66 if let Some(ref config_path) = self.config_file {
68 let file_config = self.load_toml_file(config_path)?;
69 config = Self::merge_configs(config, file_config);
70 }
71
72 Ok((config, workspace_root))
73 }
74
75 fn load_cargo_metadata(&self) -> Result<(PathBuf, Config)> {
79 let manifest_path = std::env::var("CARGO_MANIFEST_PATH").ok();
80
81 let mut cmd = MetadataCommand::new();
82 if let Some(manifest_path) = manifest_path {
83 cmd.manifest_path(manifest_path);
84 }
85
86 let metadata = cmd.exec()?;
87 let workspace_root = metadata.workspace_root.clone().into_std_path_buf();
88
89 let workspace_config = if let Some(ws_value) = metadata.workspace_metadata.get("image-runner") {
91 Some(
92 serde_json::from_value::<Config>(ws_value.clone())
93 .map_err(|e| Error::config(format!("invalid workspace metadata: {}", e)))?,
94 )
95 } else {
96 None
97 };
98
99 let pkg_name = std::env::var("CARGO_PKG_NAME").ok();
101 let package = if let Some(ref pkg_name) = pkg_name {
102 metadata
103 .packages
104 .iter()
105 .find(|p| &p.name == pkg_name)
106 .or_else(|| metadata.root_package())
107 } else {
108 metadata.root_package()
109 };
110
111 let package_config = if let Some(package) = package {
113 if let Some(metadata_value) = package.metadata.get("image-runner") {
114 Some(
115 serde_json::from_value::<Config>(metadata_value.clone())
116 .map_err(|e| Error::config(format!("invalid Cargo.toml metadata: {}", e)))?,
117 )
118 } else {
119 None
120 }
121 } else {
122 None
123 };
124
125 let mut config = Config::default();
127 if let Some(ws_config) = workspace_config {
128 config = Self::merge_configs(config, ws_config);
129 }
130 if let Some(pkg_config) = package_config {
131 config = Self::merge_configs(config, pkg_config);
132 }
133
134 Ok((workspace_root, config))
135 }
136
137 fn load_toml_file(&self, path: &Path) -> Result<Config> {
139 let content = std::fs::read_to_string(path)
140 .map_err(|e| Error::config(format!("failed to read config file: {}", e)))?;
141
142 toml::from_str(&content)
143 .map_err(|e| Error::config(format!("failed to parse TOML config: {}", e)))
144 }
145
146 fn merge_configs(mut base: Config, override_cfg: Config) -> Config {
148 base.boot = override_cfg.boot;
155 base.bootloader = override_cfg.bootloader;
156 base.image = override_cfg.image;
157 base.runner = override_cfg.runner;
158 base.test = override_cfg.test;
159 base.run = override_cfg.run;
160
161 for (k, v) in override_cfg.variables {
163 base.variables.insert(k, v);
164 }
165
166 base
167 }
168}
169
170impl Default for ConfigLoader {
171 fn default() -> Self {
172 Self::new()
173 }
174}