Skip to main content

cargo_image_runner/core/
context.rs

1use crate::config::Config;
2use crate::core::error::Result;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6/// Context object that carries state through the build pipeline.
7pub struct Context {
8    /// Configuration.
9    pub config: Config,
10
11    /// Workspace root directory.
12    pub workspace_root: PathBuf,
13
14    /// Target directory for build artifacts.
15    pub target_dir: PathBuf,
16
17    /// Path to the executable being run.
18    pub executable: PathBuf,
19
20    /// Whether this is a test run.
21    pub is_test: bool,
22
23    /// Cache directory for downloaded/generated files.
24    pub cache_dir: PathBuf,
25
26    /// Output directory for the current build.
27    pub output_dir: PathBuf,
28
29    /// Template variables available for substitution.
30    pub template_vars: HashMap<String, String>,
31}
32
33impl Context {
34    /// Create a new context from configuration and executable path.
35    pub fn new(config: Config, workspace_root: PathBuf, executable: PathBuf) -> Result<Self> {
36        let target_dir = workspace_root.join("target").join("image-runner");
37        let cache_dir = target_dir.join("cache");
38        let output_dir = target_dir.join("output");
39
40        // Ensure directories exist
41        std::fs::create_dir_all(&cache_dir)?;
42        std::fs::create_dir_all(&output_dir)?;
43
44        let mut ctx = Self {
45            config,
46            workspace_root: workspace_root.clone(),
47            target_dir,
48            executable: executable.clone(),
49            is_test: false,
50            cache_dir,
51            output_dir,
52            template_vars: HashMap::new(),
53        };
54
55        // Detect if this is a test run
56        ctx.detect_test();
57
58        // Initialize template variables
59        ctx.init_template_vars();
60
61        Ok(ctx)
62    }
63
64    /// Detect if the executable is a test binary.
65    ///
66    /// Tests are detected by checking if the executable name ends with a hash
67    /// (Cargo appends a hash suffix to test binaries).
68    pub fn detect_test(&mut self) {
69        if let Some(file_name) = self.executable.file_name().and_then(|n| n.to_str()) {
70            // Test binaries typically have a hash suffix like "mytest-a1b2c3d4"
71            // We look for a pattern: contains '-' and ends with hex-like characters
72            if file_name.contains('-') {
73                if let Some(suffix) = file_name.rsplit('-').next() {
74                    // Check if suffix looks like a hex hash (8+ hex characters)
75                    if suffix.len() >= 8 && suffix.chars().all(|c| c.is_ascii_hexdigit()) {
76                        self.is_test = true;
77                    }
78                }
79            }
80        }
81    }
82
83    /// Initialize template variables.
84    fn init_template_vars(&mut self) {
85        // Start with user-defined variables from config
86        self.template_vars = self.config.variables.clone();
87
88        // Add built-in variables
89        self.template_vars.insert(
90            "EXECUTABLE".to_string(),
91            self.executable.display().to_string(),
92        );
93
94        if let Some(exe_name) = self.executable.file_name().and_then(|n| n.to_str()) {
95            self.template_vars
96                .insert("EXECUTABLE_NAME".to_string(), exe_name.to_string());
97        }
98
99        self.template_vars.insert(
100            "WORKSPACE_ROOT".to_string(),
101            self.workspace_root.display().to_string(),
102        );
103
104        self.template_vars.insert(
105            "OUTPUT_DIR".to_string(),
106            self.output_dir.display().to_string(),
107        );
108
109        self.template_vars.insert(
110            "IS_TEST".to_string(),
111            if self.is_test { "1" } else { "0" }.to_string(),
112        );
113    }
114
115    /// Get the appropriate extra arguments based on whether this is a test run.
116    pub fn get_extra_args(&self) -> &[String] {
117        if self.is_test {
118            &self.config.test.extra_args
119        } else {
120            &self.config.run.extra_args
121        }
122    }
123
124    /// Get the success exit code for tests, if configured.
125    pub fn test_success_exit_code(&self) -> Option<i32> {
126        self.config.test.success_exit_code
127    }
128}