1use {
6 crate::{
7 environment::default_target_triple,
8 py_packaging::distribution::DistributionCache,
9 starlark::env::{
10 populate_environment, register_starlark_dialect, PyOxidizerContext,
11 PyOxidizerEnvironmentContext,
12 },
13 },
14 anyhow::{anyhow, Result},
15 codemap::CodeMap,
16 codemap_diagnostic::{Diagnostic, Emitter},
17 log::error,
18 starlark::{
19 environment::{Environment, EnvironmentError, TypeValues},
20 eval::call_stack::CallStack,
21 syntax::dialect::Dialect,
22 values::{
23 error::{RuntimeError, ValueError},
24 Value, ValueResult,
25 },
26 },
27 starlark_dialect_build_targets::{
28 build_target, run_target, EnvironmentContext, ResolvedTarget,
29 },
30 std::{
31 collections::HashMap,
32 path::{Path, PathBuf},
33 sync::{Arc, Mutex},
34 },
35};
36
37pub struct EvaluationContextBuilder {
39 env: crate::environment::Environment,
40 config_path: PathBuf,
41 build_target_triple: String,
42 release: bool,
43 verbose: bool,
44 resolve_targets: Option<Vec<String>>,
45 build_script_mode: bool,
46 build_opt_level: String,
47 distribution_cache: Option<Arc<DistributionCache>>,
48 extra_vars: HashMap<String, Option<String>>,
49}
50
51impl EvaluationContextBuilder {
52 pub fn new(
53 env: &crate::environment::Environment,
54 config_path: impl AsRef<Path>,
55 build_target_triple: impl ToString,
56 ) -> Self {
57 Self {
58 env: env.clone(),
59 config_path: config_path.as_ref().to_path_buf(),
60 build_target_triple: build_target_triple.to_string(),
61 release: false,
62 verbose: false,
63 resolve_targets: None,
64 build_script_mode: false,
65 build_opt_level: "0".to_string(),
66 distribution_cache: None,
67 extra_vars: HashMap::new(),
68 }
69 }
70
71 pub fn into_context(self) -> Result<EvaluationContext> {
73 EvaluationContext::from_builder(self)
74 }
75
76 #[must_use]
77 pub fn config_path(mut self, value: impl AsRef<Path>) -> Self {
78 self.config_path = value.as_ref().to_path_buf();
79 self
80 }
81
82 #[must_use]
83 pub fn build_target_triple(mut self, value: impl ToString) -> Self {
84 self.build_target_triple = value.to_string();
85 self
86 }
87
88 #[must_use]
89 pub fn release(mut self, value: bool) -> Self {
90 self.release = value;
91 self
92 }
93
94 #[must_use]
95 pub fn verbose(mut self, value: bool) -> Self {
96 self.verbose = value;
97 self
98 }
99
100 #[must_use]
101 pub fn resolve_targets_optional(mut self, targets: Option<Vec<impl ToString>>) -> Self {
102 self.resolve_targets =
103 targets.map(|targets| targets.iter().map(|x| x.to_string()).collect());
104 self
105 }
106
107 #[must_use]
108 pub fn resolve_targets(mut self, targets: Vec<String>) -> Self {
109 self.resolve_targets = Some(targets);
110 self
111 }
112
113 #[must_use]
114 pub fn resolve_target_optional(mut self, target: Option<impl ToString>) -> Self {
115 self.resolve_targets = target.map(|target| vec![target.to_string()]);
116 self
117 }
118
119 #[must_use]
120 pub fn resolve_target(mut self, target: impl ToString) -> Self {
121 self.resolve_targets = Some(vec![target.to_string()]);
122 self
123 }
124
125 #[must_use]
126 pub fn build_script_mode(mut self, value: bool) -> Self {
127 self.build_script_mode = value;
128 self
129 }
130
131 #[must_use]
132 pub fn distribution_cache(mut self, cache: Arc<DistributionCache>) -> Self {
133 self.distribution_cache = Some(cache);
134 self
135 }
136
137 #[must_use]
138 pub fn extra_vars(mut self, extra_vars: HashMap<String, Option<String>>) -> Self {
139 self.extra_vars = extra_vars;
140 self
141 }
142}
143
144pub struct EvaluationContext {
152 parent_env: Environment,
153 child_env: Environment,
154 type_values: TypeValues,
155}
156
157impl TryFrom<EvaluationContextBuilder> for EvaluationContext {
158 type Error = anyhow::Error;
159
160 fn try_from(value: EvaluationContextBuilder) -> Result<Self, Self::Error> {
161 Self::from_builder(value)
162 }
163}
164
165impl EvaluationContext {
166 pub fn from_builder(builder: EvaluationContextBuilder) -> Result<Self> {
167 let context = PyOxidizerEnvironmentContext::new(
168 &builder.env,
169 builder.verbose,
170 &builder.config_path,
171 default_target_triple(),
172 &builder.build_target_triple,
173 builder.release,
174 &builder.build_opt_level,
175 builder.distribution_cache,
176 builder.extra_vars,
177 )?;
178
179 let (mut parent_env, mut type_values) = starlark::stdlib::global_environment();
180
181 register_starlark_dialect(&mut parent_env, &mut type_values)
182 .map_err(|e| anyhow!("error creating Starlark environment: {:?}", e))?;
183
184 let mut child_env = parent_env.child("pyoxidizer");
187
188 populate_environment(
189 &mut child_env,
190 &mut type_values,
191 context,
192 builder.resolve_targets,
193 builder.build_script_mode,
194 )
195 .map_err(|e| anyhow!("error populating Starlark environment: {:?}", e))?;
196
197 Ok(Self {
198 parent_env,
199 child_env,
200 type_values,
201 })
202 }
203
204 pub fn get_var(&self, name: &str) -> Result<Value, EnvironmentError> {
206 self.child_env.get(name)
207 }
208
209 pub fn set_var(&mut self, name: &str, value: Value) -> Result<(), EnvironmentError> {
211 self.child_env.set(name, value)
212 }
213
214 pub fn evaluate_file_diagnostic(&mut self, config_path: &Path) -> Result<(), Diagnostic> {
216 let map = Arc::new(Mutex::new(CodeMap::new()));
217 let file_loader_env = self.parent_env.clone();
218
219 starlark::eval::simple::eval_file(
220 &map,
221 &config_path.display().to_string(),
222 Dialect::Bzl,
223 &mut self.child_env,
224 &self.type_values,
225 file_loader_env,
226 )
227 .map_err(|e| {
228 let mut msg = Vec::new();
229 let raw_map = map.lock().unwrap();
230 {
231 let mut emitter = codemap_diagnostic::Emitter::vec(&mut msg, Some(&raw_map));
232 emitter.emit(&[e.clone()]);
233 }
234
235 error!("{}", String::from_utf8_lossy(&msg));
236
237 e
238 })?;
239
240 Ok(())
241 }
242
243 pub fn evaluate_file(&mut self, config_path: &Path) -> Result<()> {
245 self.evaluate_file_diagnostic(config_path)
246 .map_err(|d| anyhow!(d.message))
247 }
248
249 pub fn eval_diagnostic(
251 &mut self,
252 map: &Arc<Mutex<CodeMap>>,
253 path: &str,
254 code: &str,
255 ) -> Result<Value, Diagnostic> {
256 let file_loader_env = self.child_env.clone();
257
258 starlark::eval::simple::eval(
259 map,
260 path,
261 code,
262 Dialect::Bzl,
263 &mut self.child_env,
264 &self.type_values,
265 file_loader_env,
266 )
267 }
268
269 pub fn eval_code_with_path(&mut self, path: &str, code: &str) -> Result<Value> {
271 let map = std::sync::Arc::new(std::sync::Mutex::new(CodeMap::new()));
272
273 self.eval_diagnostic(&map, path, code)
274 .map_err(|diagnostic| {
275 let cloned_map_lock = Arc::clone(&map);
276 let unlocked_map = cloned_map_lock.lock().unwrap();
277
278 let mut buffer = vec![];
279 Emitter::vec(&mut buffer, Some(&unlocked_map)).emit(&[diagnostic]);
280
281 anyhow!(
282 "error running '{}': {}",
283 code,
284 String::from_utf8_lossy(&buffer)
285 )
286 })
287 }
288
289 pub fn eval(&mut self, code: &str) -> Result<Value> {
291 self.eval_code_with_path("<no_file>", code)
292 }
293
294 fn build_targets_context_value(&self) -> Result<Value> {
296 starlark_dialect_build_targets::get_context_value(&self.type_values)
297 .map_err(|_| anyhow!("could not obtain build targets context"))
298 }
299
300 pub fn pyoxidizer_context_value(&self) -> ValueResult {
302 self.type_values
303 .get_type_value(&Value::new(PyOxidizerContext::default()), "CONTEXT")
304 .ok_or_else(|| {
305 ValueError::from(RuntimeError {
306 code: "PYOXIDIZER",
307 message: "Unable to resolve context (this should never happen)".to_string(),
308 label: "".to_string(),
309 })
310 })
311 }
312
313 pub fn build_path(&self) -> Result<PathBuf, ValueError> {
314 let pyoxidizer_context_value = self.pyoxidizer_context_value()?;
315 let pyoxidizer_context = pyoxidizer_context_value
316 .downcast_ref::<PyOxidizerEnvironmentContext>()
317 .ok_or(ValueError::IncorrectParameterType)?;
318
319 pyoxidizer_context.build_path(&self.type_values)
320 }
321
322 pub fn target_build_path(&self, target: &str) -> Result<PathBuf> {
323 let context_value = self.build_targets_context_value()?;
324 let context = context_value.downcast_ref::<EnvironmentContext>().unwrap();
325
326 Ok(context.target_build_path(target))
327 }
328
329 pub fn default_target(&self) -> Result<Option<String>> {
330 let raw_context = self.build_targets_context_value()?;
331 let context = raw_context
332 .downcast_ref::<EnvironmentContext>()
333 .ok_or_else(|| anyhow!("context has incorrect type"))?;
334
335 Ok(context.default_target().map(|x| x.to_string()))
336 }
337
338 pub fn target_names(&self) -> Result<Vec<String>> {
339 let raw_context = self.build_targets_context_value()?;
340 let context = raw_context
341 .downcast_ref::<EnvironmentContext>()
342 .ok_or_else(|| anyhow!("context has incorrect type"))?;
343
344 Ok(context
345 .targets()
346 .keys()
347 .map(|x| x.to_string())
348 .collect::<Vec<_>>())
349 }
350
351 pub fn targets_to_resolve(&self) -> Result<Vec<String>> {
353 let raw_context = self.build_targets_context_value()?;
354 let context = raw_context
355 .downcast_ref::<EnvironmentContext>()
356 .ok_or_else(|| anyhow!("context has incorrect type"))?;
357
358 Ok(context.targets_to_resolve())
359 }
360
361 pub fn build_resolved_target(&mut self, target: &str) -> Result<ResolvedTarget> {
362 let mut call_stack = CallStack::default();
363
364 build_target(
365 &mut self.child_env,
366 &self.type_values,
367 &mut call_stack,
368 target,
369 )
370 }
371
372 pub fn run_target(&mut self, target: Option<&str>) -> Result<()> {
373 let mut call_stack = CallStack::default();
374
375 run_target(
376 &mut self.child_env,
377 &self.type_values,
378 &mut call_stack,
379 target,
380 )
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use {super::*, crate::testutil::*, starlark::values::dict::Dictionary};
387
388 #[test]
389 fn test_load() -> Result<()> {
390 let env = get_env()?;
391 let temp_dir = env.temporary_directory("pyoxidizer-test")?;
392
393 let load_path = temp_dir.path().join("load.bzl");
394 std::fs::write(
395 &load_path,
396 "def make_dist():\n return default_python_distribution()\n".as_bytes(),
397 )?;
398
399 let main_path = temp_dir.path().join("main.bzl");
400 std::fs::write(
401 &main_path,
402 format!(
403 "load('{}', 'make_dist')\nmake_dist()\n",
404 load_path.display().to_string().escape_default()
405 )
406 .as_bytes(),
407 )?;
408
409 let mut context = EvaluationContextBuilder::new(
410 &env,
411 main_path.clone(),
412 default_target_triple().to_string(),
413 )
414 .verbose(true)
415 .into_context()?;
416 context.evaluate_file(&main_path)?;
417
418 temp_dir.close()?;
419
420 Ok(())
421 }
422
423 #[test]
424 fn test_register_target() -> Result<()> {
425 let env = get_env()?;
426 let temp_dir = env.temporary_directory("pyoxidizer-test")?;
427
428 let config_path = temp_dir.path().join("pyoxidizer.bzl");
429 std::fs::write(&config_path, "def make_dist():\n return default_python_distribution()\nregister_target('dist', make_dist)\n".as_bytes())?;
430
431 let mut context: EvaluationContext = EvaluationContextBuilder::new(
432 &env,
433 config_path.clone(),
434 default_target_triple().to_string(),
435 )
436 .verbose(true)
437 .into_context()?;
438 context.evaluate_file(&config_path)?;
439
440 temp_dir.close()?;
441
442 Ok(())
443 }
444
445 #[test]
446 fn extra_vars() -> Result<()> {
447 let env = get_env()?;
448 let temp_dir = env.temporary_directory("pyoxidizer-test")?;
449
450 let config_path = temp_dir.path().join("pyoxidizer.bzl");
451 std::fs::write(&config_path, "my_var_copy = my_var\nempty_copy = empty\n")?;
452
453 let mut extra_vars = HashMap::new();
454 extra_vars.insert("my_var".to_string(), Some("my_value".to_string()));
455 extra_vars.insert("empty".to_string(), None);
456
457 let context =
458 EvaluationContextBuilder::new(&env, config_path, default_target_triple().to_string())
459 .extra_vars(extra_vars)
460 .into_context()?;
461
462 let vars_value = context.get_var("VARS").unwrap();
463 assert_eq!(vars_value.get_type(), "dict");
464 let vars = vars_value.downcast_ref::<Dictionary>().unwrap();
465
466 let v = vars.get(&Value::from("my_var")).unwrap().unwrap();
467 assert_eq!(v.to_string(), "my_value");
468
469 let v = vars.get(&Value::from("empty")).unwrap().unwrap();
470 assert_eq!(v.get_type(), "NoneType");
471
472 temp_dir.close()?;
473
474 Ok(())
475 }
476}