|
1 | 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | | -use std::process::ExitCode; |
3 | 2 |
|
4 | 3 | use bear::input::EventFileReader; |
| 4 | +use bear::intercept::collector::{EventCollector, EventCollectorOnTcp}; |
| 5 | +use bear::intercept::{Envelope, KEY_DESTINATION, KEY_PRELOAD_PATH}; |
5 | 6 | use bear::output::OutputWriter; |
6 | 7 | use bear::recognition::Recognition; |
7 | 8 | use bear::transformation::Transformation; |
8 | 9 | use bear::{args, config}; |
| 10 | +use crossbeam_channel::{bounded, Receiver}; |
9 | 11 | use log; |
| 12 | +use std::path::{Path, PathBuf}; |
| 13 | +use std::process::{Command, ExitCode}; |
| 14 | +use std::sync::Arc; |
| 15 | +use std::{env, thread}; |
10 | 16 |
|
11 | 17 | /// Driver function of the application. |
12 | 18 | fn main() -> anyhow::Result<ExitCode> { |
@@ -41,7 +47,7 @@ enum Application { |
41 | 47 | Intercept { |
42 | 48 | input: args::BuildCommand, |
43 | 49 | output: args::BuildEvents, |
44 | | - intercept_config: config::Intercept, |
| 50 | + config: config::Intercept, |
45 | 51 | }, |
46 | 52 | /// The semantic mode we are deduct the semantic meaning of the |
47 | 53 | /// executed commands from the build process. |
@@ -73,7 +79,7 @@ impl Application { |
73 | 79 | let result = Application::Intercept { |
74 | 80 | input, |
75 | 81 | output, |
76 | | - intercept_config, |
| 82 | + config: intercept_config, |
77 | 83 | }; |
78 | 84 | Ok(result) |
79 | 85 | } |
@@ -110,10 +116,38 @@ impl Application { |
110 | 116 | Application::Intercept { |
111 | 117 | input, |
112 | 118 | output, |
113 | | - intercept_config, |
| 119 | + config, |
114 | 120 | } => { |
115 | | - // TODO: Implement the intercept mode. |
116 | | - ExitCode::FAILURE |
| 121 | + match &config { |
| 122 | + config::Intercept::Wrapper { .. } => { |
| 123 | + let service = InterceptService::new() |
| 124 | + .expect("Failed to create the intercept service"); |
| 125 | + let environment = InterceptEnvironment::new(&config, service.address()) |
| 126 | + .expect("Failed to create the intercept environment"); |
| 127 | + |
| 128 | + // start writer thread |
| 129 | + let writer_thread = thread::spawn(move || { |
| 130 | + let mut writer = std::fs::File::create(output.file_name) |
| 131 | + .expect("Failed to create the output file"); |
| 132 | + for envelope in service.receiver().iter() { |
| 133 | + envelope |
| 134 | + .write_into(&mut writer) |
| 135 | + .expect("Failed to write the envelope"); |
| 136 | + } |
| 137 | + }); |
| 138 | + |
| 139 | + let status = environment.execute_build_command(input); |
| 140 | + |
| 141 | + writer_thread |
| 142 | + .join() |
| 143 | + .expect("Failed to join the writer thread"); |
| 144 | + |
| 145 | + status.unwrap_or(ExitCode::FAILURE) |
| 146 | + } |
| 147 | + config::Intercept::Preload { .. } => { |
| 148 | + todo!() |
| 149 | + } |
| 150 | + } |
117 | 151 | } |
118 | 152 | Application::Semantic { |
119 | 153 | event_source, |
@@ -145,3 +179,135 @@ impl Application { |
145 | 179 | } |
146 | 180 | } |
147 | 181 | } |
| 182 | + |
| 183 | +struct InterceptService { |
| 184 | + collector: Arc<EventCollectorOnTcp>, |
| 185 | + receiver: Receiver<Envelope>, |
| 186 | + collector_thread: Option<thread::JoinHandle<()>>, |
| 187 | +} |
| 188 | + |
| 189 | +impl InterceptService { |
| 190 | + pub fn new() -> anyhow::Result<Self> { |
| 191 | + let collector = EventCollectorOnTcp::new()?; |
| 192 | + let collector_arc = Arc::new(collector); |
| 193 | + let (sender, receiver) = bounded(32); |
| 194 | + |
| 195 | + let collector_in_thread = collector_arc.clone(); |
| 196 | + let collector_thread = thread::spawn(move || { |
| 197 | + collector_in_thread.collect(sender).unwrap(); |
| 198 | + }); |
| 199 | + |
| 200 | + Ok(InterceptService { |
| 201 | + collector: collector_arc, |
| 202 | + receiver, |
| 203 | + collector_thread: Some(collector_thread), |
| 204 | + }) |
| 205 | + } |
| 206 | + |
| 207 | + pub fn receiver(&self) -> Receiver<Envelope> { |
| 208 | + self.receiver.clone() |
| 209 | + } |
| 210 | + |
| 211 | + pub fn address(&self) -> String { |
| 212 | + self.collector.address() |
| 213 | + } |
| 214 | +} |
| 215 | + |
| 216 | +impl Drop for InterceptService { |
| 217 | + fn drop(&mut self) { |
| 218 | + self.collector.stop().expect("Failed to stop the collector"); |
| 219 | + if let Some(thread) = self.collector_thread.take() { |
| 220 | + thread.join().expect("Failed to join the collector thread"); |
| 221 | + } |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +enum InterceptEnvironment { |
| 226 | + Wrapper { |
| 227 | + bin_dir: tempfile::TempDir, |
| 228 | + address: String, |
| 229 | + }, |
| 230 | + Preload { |
| 231 | + path: PathBuf, |
| 232 | + address: String, |
| 233 | + }, |
| 234 | +} |
| 235 | + |
| 236 | +impl InterceptEnvironment { |
| 237 | + pub fn new(config: &config::Intercept, address: String) -> anyhow::Result<Self> { |
| 238 | + let result = match config { |
| 239 | + config::Intercept::Wrapper { |
| 240 | + path, |
| 241 | + directory, |
| 242 | + executables, |
| 243 | + } => { |
| 244 | + // Create a temporary directory and populate it with the executables. |
| 245 | + let bin_dir = tempfile::TempDir::with_prefix_in(directory, "bear-")?; |
| 246 | + for executable in executables { |
| 247 | + std::fs::hard_link(&executable, &path)?; |
| 248 | + } |
| 249 | + InterceptEnvironment::Wrapper { bin_dir, address } |
| 250 | + } |
| 251 | + config::Intercept::Preload { path } => InterceptEnvironment::Preload { |
| 252 | + path: path.clone(), |
| 253 | + address, |
| 254 | + }, |
| 255 | + }; |
| 256 | + Ok(result) |
| 257 | + } |
| 258 | + |
| 259 | + pub fn execute_build_command(self, input: args::BuildCommand) -> anyhow::Result<ExitCode> { |
| 260 | + let environment = self.environment(); |
| 261 | + let mut child = Command::new(input.arguments[0].clone()) |
| 262 | + .args(input.arguments) |
| 263 | + .envs(environment) |
| 264 | + .spawn()?; |
| 265 | + |
| 266 | + let result = child.wait()?; |
| 267 | + |
| 268 | + if result.success() { |
| 269 | + Ok(ExitCode::SUCCESS) |
| 270 | + } else { |
| 271 | + result |
| 272 | + .code() |
| 273 | + .map_or(Ok(ExitCode::FAILURE), |code| Ok(ExitCode::from(code as u8))) |
| 274 | + } |
| 275 | + } |
| 276 | + |
| 277 | + fn environment(&self) -> Vec<(String, String)> { |
| 278 | + match self { |
| 279 | + InterceptEnvironment::Wrapper { |
| 280 | + bin_dir, address, .. |
| 281 | + } => { |
| 282 | + let path_original = env::var("PATH").unwrap_or_else(|_| String::new()); |
| 283 | + let path_updated = InterceptEnvironment::insert_to_path( |
| 284 | + &path_original, |
| 285 | + Self::to_string(bin_dir.path()), |
| 286 | + ); |
| 287 | + vec![ |
| 288 | + ("PATH".to_string(), path_updated), |
| 289 | + (KEY_DESTINATION.to_string(), address.clone()), |
| 290 | + ] |
| 291 | + } |
| 292 | + InterceptEnvironment::Preload { path, address, .. } => { |
| 293 | + let path_original = env::var(KEY_PRELOAD_PATH).unwrap_or_else(|_| String::new()); |
| 294 | + let path_updated = |
| 295 | + InterceptEnvironment::insert_to_path(&path_original, Self::to_string(path)); |
| 296 | + vec![ |
| 297 | + (KEY_PRELOAD_PATH.to_string(), path_updated), |
| 298 | + (KEY_DESTINATION.to_string(), address.clone()), |
| 299 | + ] |
| 300 | + } |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | + fn insert_to_path(original: &str, first: String) -> String { |
| 305 | + let mut paths: Vec<_> = original.split(':').filter(|it| it != &first).collect(); |
| 306 | + paths.insert(0, first.as_str()); |
| 307 | + paths.join(":") |
| 308 | + } |
| 309 | + |
| 310 | + fn to_string(path: &Path) -> String { |
| 311 | + path.to_str().unwrap_or("").to_string() |
| 312 | + } |
| 313 | +} |
0 commit comments