1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Module for running scripts in different interpreters.
//!
//! This module provides integration between Rattler-Build and the rattler_build_script crate,
//! specifically handling the execution of build scripts within the Output context.
use indexmap::IndexMap;
use minijinja::Value;
use rattler_build_jinja::Jinja;
use rattler_conda_types::Platform;
// Re-export from rattler_build_script
pub use rattler_build_script::{
ExecutionArgs, InterpreterError, ResolvedScriptContents, SandboxArguments,
SandboxConfiguration, Script, ScriptContent, platform_script_extensions,
};
use crate::{
env_vars::{self},
metadata::Output,
};
impl Output {
/// Helper function to get a jinja renderer for the output's recipe context.
pub(crate) fn jinja_renderer(&self) -> impl Fn(&str) -> Result<String, String> {
let selector_config = self.build_configuration.selector_config();
let jinja = Jinja::new(selector_config.clone()).with_context(&self.recipe.context);
move |template: &str| jinja.render_str(template).map_err(|e| e.to_string())
}
/// Helper method to prepare build script execution arguments
async fn prepare_build_script(&self) -> Result<ExecutionArgs, std::io::Error> {
let host_prefix = self.build_configuration.directories.host_prefix.clone();
let target_platform = self.build_configuration.target_platform;
let mut env_vars = env_vars::vars(self, "BUILD");
env_vars.extend(env_vars::os_vars(&host_prefix, &target_platform));
env_vars.extend(env_vars::env_vars_from_variant(self.variant()));
let jinja_renderer = self.jinja_renderer();
let build_prefix = if self.recipe.build().merge_build_and_host_envs {
None
} else {
Some(&self.build_configuration.directories.build_prefix)
};
let work_dir = &self.build_configuration.directories.work_dir;
Ok(ExecutionArgs {
script: self.recipe.build().script.resolve_content(
&self.build_configuration.directories.recipe_dir,
Some(jinja_renderer),
platform_script_extensions(),
)?,
env_vars: env_vars
.into_iter()
.filter_map(|(k, v)| v.map(|v| (k, v)))
.collect(),
secrets: IndexMap::new(),
build_prefix: build_prefix.map(|p| p.to_owned()),
run_prefix: host_prefix,
execution_platform: Platform::current(),
work_dir: work_dir.clone(),
sandbox_config: self.build_configuration.sandbox_config().cloned(),
})
}
/// Run the build script for the output as defined in the recipe's build section.
///
/// This method executes the build script with the configured environment variables,
/// working directory, and other build settings. The script execution respects the
/// configured interpreter (bash/cmd/nushell) and sandbox settings.
///
/// # Errors
///
/// Returns an `std::io::Error` if:
/// - The script file cannot be read or found
/// - The script execution fails
/// - The interpreter is not supported or not available
pub async fn run_build_script(&self) -> Result<(), InterpreterError> {
let span = tracing::info_span!("Running build script");
let _enter = span.enter();
let exec_args = self.prepare_build_script().await?;
let build_prefix = if self.recipe.build().merge_build_and_host_envs {
None
} else {
Some(&self.build_configuration.directories.build_prefix)
};
// Create Jinja context with environment variables
let mut jinja = Jinja::new(self.build_configuration.selector_config())
.with_context(&self.recipe.context);
// Add env vars to jinja context
for (k, v) in &exec_args.env_vars {
jinja
.context_mut()
.insert(k.clone(), Value::from_safe_string(v.clone()));
}
let jinja_renderer = |template: &str| -> Result<String, String> {
jinja.render_str(template).map_err(|e| e.to_string())
};
self.recipe
.build()
.script
.run_script(
exec_args
.env_vars
.into_iter()
.map(|(k, v)| (k, Some(v)))
.collect(),
&self.build_configuration.directories.work_dir,
&self.build_configuration.directories.recipe_dir,
&self.build_configuration.directories.host_prefix,
build_prefix,
Some(jinja_renderer),
self.build_configuration.sandbox_config(),
)
.await?;
Ok(())
}
/// Create the build script files without executing them.
///
/// This method generates the build script and environment setup files in the working
/// directory but does not execute them. This is useful for debugging or when you want
/// to inspect or modify the scripts before running them manually.
///
/// The method creates two files:
/// - A build environment setup file (`build_env.sh`/`build_env.bat`)
/// - The main build script file (`conda_build.sh`/`conda_build.bat`)
///
/// # Errors
///
/// Returns an `std::io::Error` if:
/// - The script file cannot be read or found
/// - The script files cannot be written to the working directory
pub async fn create_build_script(&self) -> Result<(), std::io::Error> {
let span = tracing::info_span!("Creating build script");
let _enter = span.enter();
let exec_args = self.prepare_build_script().await?;
rattler_build_script::create_build_script(exec_args).await
}
}