1use crate::config::{PlatformConfiguration, ScriptDeviceConfiguration, SshDeviceConfiguration};
2use crate::platform::regular_platform::RegularPlatform;
3use crate::{Configuration, Device, Platform, PlatformManager};
4use anyhow::{anyhow, bail, Context, Result};
5use log::debug;
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use std::os::unix::fs::PermissionsExt;
9use std::process::Command;
10use std::sync::Arc;
11use std::{env, fs};
12
13pub struct PluginManager {
37 conf: Arc<Configuration>,
38 auto_detected_plugins: Vec<String>,
39}
40
41impl PluginManager {
42 pub fn probe(conf: Arc<Configuration>) -> Option<PluginManager> {
43 let auto_detected_plugins = auto_detect_plugins();
44
45 if auto_detected_plugins.is_empty() {
46 debug!("No auto-detected plugins found");
47 None
48 } else {
49 debug!("Auto-detected plugins: {:?}", auto_detected_plugins);
50 Some(Self {
51 conf,
52 auto_detected_plugins,
53 })
54 }
55 }
56 fn create_script_devices(
57 &self,
58 provider: &String,
59 script_devices: BTreeMap<String, ScriptDeviceConfiguration>,
60 ) -> Vec<Box<dyn Device>> {
61 script_devices
62 .into_iter()
63 .filter_map(|(id, conf)| {
64 if self.conf.script_devices.get(&id).is_none() {
65 debug!("registering script device {id} from {provider}");
66 Some(Box::new(crate::script::ScriptDevice { id, conf }) as _)
67 } else {
68 debug!("ignoring script device {id} from {provider} as is was already registered in configuration");
69 None
70 }
71 })
72 .collect()
73 }
74
75 fn create_ssh_devices(
76 &self,
77 provider: &String,
78 ssh_devices: BTreeMap<String, SshDeviceConfiguration>,
79 ) -> Vec<Box<dyn Device>> {
80 ssh_devices.into_iter().filter_map(|(id, conf)| {
81 if self.conf.script_devices.get(&id).is_none() {
82 debug!("registering ssh device {id} from {provider}");
83 Some(Box::new(crate::ssh::SshDevice {
84 id,
85 conf,
86 }) as _)
87 } else {
88 debug!("ignoring ssh device {id} from {provider} as is was already registered in configuration");
89 None
90 }
91 }).collect()
92 }
93}
94
95impl PlatformManager for PluginManager {
96 fn devices(&self) -> Result<Vec<Box<dyn Device>>> {
97 let mut result: Vec<Box<dyn Device>> = vec![];
98
99 self.auto_detected_plugins.iter().for_each(|provider| {
100 match get_devices_from_plugin(provider) {
101 Ok(DevicePluginOutput{script_devices, ssh_devices}) => {
102 if let Some(script_devices) = script_devices {
103 result.append(&mut self.create_script_devices(provider, script_devices))
104 }
105
106 if let Some(ssh_devices) = ssh_devices {
107 result.append(&mut self.create_ssh_devices(provider, ssh_devices))
108 }
109
110 }
111 Err(e) => {
112 debug!(
113 "failed to get devices from auto detected script provider: {provider}, {e:?}",
114 );
115 }
116 }
117 });
118
119 Ok(result)
120 }
121
122 fn platforms(&self) -> anyhow::Result<Vec<Box<dyn Platform>>> {
123 let mut script_platforms = BTreeMap::new();
124
125 self.auto_detected_plugins.iter().for_each(
126 |provider| match get_platforms_from_plugin(provider) {
127 Ok(platforms) => {
128 platforms.into_iter().for_each(|(id, platform)| {
129 if script_platforms.get(&id).is_none() && self.conf.platforms.get(&id).is_none() {
130 debug!("registering platform {id} from {provider}");
131 script_platforms.insert(id.clone(), platform);
132 } else {
133 debug!(
134 "ignoring platform {id} from plugin {provider} as is was already registered"
135 );
136 }
137 });
138 }
139 Err(e) => {
140 debug!(
141 "failed to get platforms from auto detected script provider: {provider}, {:?}",
142 e
143 );
144 }
145 },
146 );
147
148 Ok(script_platforms.into_values().collect())
149 }
150}
151
152#[derive(Debug, Serialize, Deserialize)]
153pub struct DevicePluginOutput {
154 pub ssh_devices: Option<BTreeMap<String, SshDeviceConfiguration>>,
155 pub script_devices: Option<BTreeMap<String, ScriptDeviceConfiguration>>,
156}
157
158fn get_devices_from_plugin(plugin: &str) -> Result<DevicePluginOutput> {
159 let output = Command::new(plugin).arg("devices").output()?;
160
161 if !output.status.success() {
162 bail!("failed to get devices from auto detected script provider: {:?}, non success return code", plugin);
163 }
164
165 Ok(toml::from_str(
166 &String::from_utf8(output.stdout)
167 .with_context(|| format!("Failed to parse string output from {plugin} devices"))?,
168 )
169 .with_context(|| format!("Failed to parse toml output from {plugin} devices"))?)
170}
171
172fn get_platforms_from_plugin(plugin: &str) -> Result<BTreeMap<String, Box<dyn Platform>>> {
173 let output = Command::new(plugin).arg("platforms").output()?;
174
175 if !output.status.success() {
176 bail!("failed to get platforms from auto detected script provider: {:?}, non success return code", plugin);
177 }
178
179 let platform_configs = toml::from_str::<BTreeMap<String, PlatformConfiguration>>(
180 &String::from_utf8(output.stdout)
181 .with_context(|| format!("Failed to parse string output from {plugin} platforms"))?,
182 )
183 .with_context(|| format!("Failed to parse toml output from {plugin} platforms"))?;
184
185 platform_configs
186 .into_iter()
187 .map(|(name, conf)| {
188 let triple = conf
189 .rustc_triple
190 .clone()
191 .ok_or_else(|| anyhow!("Platform {name} from {plugin} has no rustc_triple"))?;
192 let toolchain = conf
193 .toolchain
194 .clone()
195 .ok_or_else(|| anyhow!("Toolchain missing for platform {name} from {plugin}"))?;
196 Ok((
197 name.clone(),
198 RegularPlatform::new(conf, name, triple, toolchain)?,
199 ))
200 })
201 .collect()
202}
203
204fn auto_detect_plugins() -> Vec<String> {
207 let mut binaries = Vec::new();
208
209 if let Some(paths) = env::var_os("PATH") {
210 for path in env::split_paths(&paths) {
211 if let Ok(entries) = fs::read_dir(&path) {
212 for entry in entries.filter_map(|e| e.ok()) {
213 let path = entry.path();
214 if let Some(file_name) = path.file_name().and_then(|name| name.to_str()) {
215 if file_name.starts_with("cargo-dinghy-")
216 && (path.is_file()
217 && path
218 .metadata()
219 .map(|m| m.permissions().mode() & 0o111 != 0)
220 .unwrap_or(false))
221 {
222 binaries.push(file_name.to_string());
223 }
224 }
225 }
226 }
227 }
228 }
229 binaries.sort(); binaries
231}