1use {
6 super::{
7 env::{get_context, PyOxidizerEnvironmentContext},
8 file::FileValue,
9 file_resource::file_manifest_add_python_executable,
10 python_embedded_resources::PythonEmbeddedResourcesValue,
11 python_extension_module::PythonExtensionModuleValue,
12 python_module_source::PythonModuleSourceValue,
13 python_package_distribution_resource::PythonPackageDistributionResourceValue,
14 python_package_resource::PythonPackageResourceValue,
15 python_packaging_policy::PythonPackagingPolicyValue,
16 python_resource::{is_resource_starlark_compatible, python_resource_to_value},
17 util::ToValue,
18 },
19 crate::{
20 licensing::licenses_from_cargo_manifest,
21 project_building::build_python_executable,
22 py_packaging::binary::PythonBinaryBuilder,
23 py_packaging::binary::{PackedResourcesLoadMode, WindowsRuntimeDllsMode},
24 },
25 anyhow::{anyhow, Context, Result},
26 linked_hash_map::LinkedHashMap,
27 log::{info, warn},
28 python_packaging::resource::PythonModuleSource,
29 simple_file_manifest::FileData,
30 starlark::{
31 environment::TypeValues,
32 eval::call_stack::CallStack,
33 values::{
34 error::{
35 RuntimeError, UnsupportedOperation, ValueError, INCORRECT_PARAMETER_TYPE_ERROR_CODE,
36 },
37 none::NoneType,
38 {Mutable, TypedValue, Value, ValueResult},
39 },
40 {
41 starlark_fun, starlark_module, starlark_parse_param_type, starlark_signature,
42 starlark_signature_extraction, starlark_signatures,
43 },
44 },
45 starlark_dialect_build_targets::{
46 optional_dict_arg, optional_list_arg, optional_str_arg, optional_type_arg,
47 required_list_arg, ResolvedTarget, ResolvedTargetValue, RunMode, ToOptional,
48 },
49 std::{
50 collections::HashMap,
51 io::Write,
52 ops::Deref,
53 path::{Path, PathBuf},
54 sync::{Arc, Mutex, MutexGuard},
55 },
56 tugger::starlark::{
57 code_signing::{handle_signable_event, SigningAction, SigningContext},
58 file_manifest::FileManifestValue,
59 wix_bundle_builder::WiXBundleBuilderValue,
60 wix_msi_builder::WiXMsiBuilderValue,
61 },
62 tugger_code_signing::SigningDestination,
63 tugger_wix::target_triple_to_wix_arch,
64};
65
66fn error_context<F, T>(label: &str, f: F) -> Result<T, ValueError>
67where
68 F: FnOnce() -> anyhow::Result<T>,
69{
70 f().map_err(|e| {
71 ValueError::Runtime(RuntimeError {
72 code: "PYOXIDIZER_PYTHON_EXECUTABLE",
73 message: format!("{:?}", e),
74 label: label.to_string(),
75 })
76 })
77}
78
79pub fn build_internal(
80 exe: MutexGuard<Box<dyn PythonBinaryBuilder>>,
81 type_values: &TypeValues,
82 target: &str,
83 context: &PyOxidizerEnvironmentContext,
84) -> Result<(ResolvedTarget, PathBuf)> {
85 let build = build_python_executable(
88 context.env(),
89 &exe.name(),
90 &**exe,
91 &context.build_target_triple,
92 &context.build_opt_level,
93 context.build_release,
94 )
95 .context("building Python executable")?;
96
97 let output_path = context
98 .get_output_path(type_values, target)
99 .map_err(|_| anyhow!("unable to resolve output path"))?;
100 std::fs::create_dir_all(&output_path)
101 .with_context(|| format!("creating output directory {}", output_path.display()))?;
102
103 let dest_path = output_path.join(build.exe_name);
104 warn!("writing executable to {}", dest_path.display());
105 let mut fh =
106 std::fs::File::create(&dest_path).context(format!("creating {}", dest_path.display()))?;
107 fh.write_all(&build.exe_data)
108 .context(format!("writing {}", dest_path.display()))?;
109 simple_file_manifest::set_executable(&mut fh).context("making binary executable")?;
110
111 Ok((
112 ResolvedTarget {
113 run_mode: RunMode::Path {
114 path: dest_path.clone(),
115 },
116 output_path,
117 },
118 dest_path,
119 ))
120}
121
122pub struct PythonExecutableValue {
124 pub exe: Arc<Mutex<Box<dyn PythonBinaryBuilder>>>,
127
128 policy: Vec<Value>,
133}
134
135impl PythonExecutableValue {
136 pub fn new(exe: Box<dyn PythonBinaryBuilder>, policy: PythonPackagingPolicyValue) -> Self {
137 Self {
138 exe: Arc::new(Mutex::new(exe)),
139 policy: vec![Value::new(policy)],
140 }
141 }
142
143 pub fn inner(
144 &self,
145 label: &str,
146 ) -> Result<MutexGuard<Box<dyn PythonBinaryBuilder>>, ValueError> {
147 self.exe.try_lock().map_err(|e| {
148 ValueError::Runtime(RuntimeError {
149 code: "PYTHON_EXECUTABLE",
150 message: format!("failed to acquire lock: {}", e),
151 label: label.to_string(),
152 })
153 })
154 }
155
156 pub fn python_packaging_policy(&self) -> PythonPackagingPolicyValue {
158 self.policy[0]
159 .downcast_ref::<PythonPackagingPolicyValue>()
160 .unwrap()
161 .clone()
162 }
163}
164
165impl TypedValue for PythonExecutableValue {
166 type Holder = Mutable<PythonExecutableValue>;
167 const TYPE: &'static str = "PythonExecutable";
168
169 fn values_for_descendant_check_and_freeze<'a>(
170 &'a self,
171 ) -> Box<dyn Iterator<Item = Value> + 'a> {
172 Box::new(self.policy.iter().cloned())
173 }
174
175 fn get_attr(&self, attribute: &str) -> ValueResult {
176 let exe = self.inner(&format!("PythonExecutable.{}", attribute))?;
177
178 match attribute {
179 "licenses_filename" => Ok(exe.licenses_filename().to_value()),
180 "packed_resources_load_mode" => {
181 Ok(Value::from(exe.packed_resources_load_mode().to_string()))
182 }
183 "tcl_files_path" => match exe.tcl_files_path() {
184 Some(value) => Ok(Value::from(value.to_string())),
185 None => Ok(Value::from(NoneType::None)),
186 },
187 "windows_runtime_dlls_mode" => {
188 Ok(Value::from(exe.windows_runtime_dlls_mode().to_string()))
189 }
190 "windows_subsystem" => Ok(Value::from(exe.windows_subsystem())),
191 _ => Err(ValueError::OperationNotSupported {
192 op: UnsupportedOperation::GetAttr(attribute.to_string()),
193 left: Self::TYPE.to_string(),
194 right: None,
195 }),
196 }
197 }
198
199 fn has_attr(&self, attribute: &str) -> Result<bool, ValueError> {
200 Ok(matches!(
201 attribute,
202 "licenses_filename"
203 | "packed_resources_load_mode"
204 | "tcl_files_path"
205 | "windows_runtime_dlls_mode"
206 | "windows_subsystem"
207 ))
208 }
209
210 fn set_attr(&mut self, attribute: &str, value: Value) -> Result<(), ValueError> {
211 let mut exe = self.inner(&format!("PythonExecutable.{}", attribute))?;
212
213 match attribute {
214 "licenses_filename" => {
215 let value = optional_str_arg("licenses_filename", &value)?;
216 exe.set_licenses_filename(value);
217
218 Ok(())
219 }
220 "packed_resources_load_mode" => {
221 exe.set_packed_resources_load_mode(
222 PackedResourcesLoadMode::try_from(value.to_string().as_str()).map_err(|e| {
223 ValueError::from(RuntimeError {
224 code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
225 message: e,
226 label: format!("{}.{}", Self::TYPE, attribute),
227 })
228 })?,
229 );
230
231 Ok(())
232 }
233 "tcl_files_path" => {
234 exe.set_tcl_files_path(value.to_optional());
235
236 Ok(())
237 }
238 "windows_runtime_dlls_mode" => {
239 exe.set_windows_runtime_dlls_mode(
240 WindowsRuntimeDllsMode::try_from(value.to_string().as_str()).map_err(|e| {
241 ValueError::from(RuntimeError {
242 code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
243 message: e,
244 label: format!("{}.{}", Self::TYPE, attribute),
245 })
246 })?,
247 );
248
249 Ok(())
250 }
251 "windows_subsystem" => {
252 exe.set_windows_subsystem(value.to_string().as_str())
253 .map_err(|e| {
254 ValueError::from(RuntimeError {
255 code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
256 message: format!("{:?}", e),
257 label: format!("{}.{}", Self::TYPE, attribute),
258 })
259 })?;
260
261 Ok(())
262 }
263 _ => Err(ValueError::OperationNotSupported {
264 op: UnsupportedOperation::SetAttr(attribute.to_string()),
265 left: Self::TYPE.to_string(),
266 right: None,
267 }),
268 }
269 }
270}
271
272impl PythonExecutableValue {
274 fn build(
275 &self,
276 type_values: &TypeValues,
277 call_stack: &mut CallStack,
278 target: String,
279 ) -> ValueResult {
280 const LABEL: &str = "PythonExecutable.build()";
281
282 let pyoxidizer_context_value = get_context(type_values)?;
283 let pyoxidizer_context = pyoxidizer_context_value
284 .downcast_ref::<PyOxidizerEnvironmentContext>()
285 .ok_or(ValueError::IncorrectParameterType)?;
286
287 let exe = self.inner(LABEL)?;
288
289 let (inner, exe_path) = error_context(LABEL, || {
290 build_internal(exe, type_values, &target, &pyoxidizer_context)
291 })?;
292
293 let candidate = exe_path.clone().into();
294 let mut context = SigningContext::new(
295 "PythonExecutable.build()",
296 SigningAction::Other("python-executable-creation"),
297 exe_path.file_name().ok_or_else(|| {
298 ValueError::Runtime(RuntimeError {
299 code: "PYTHON_EXECUTABLE",
300 message: "could not determine executable filename (this should not happen)"
301 .to_string(),
302 label: LABEL.to_string(),
303 })
304 })?,
305 &candidate,
306 );
307 context.set_path(&exe_path);
308 context.set_signing_destination(SigningDestination::File(exe_path.clone()));
309
310 handle_signable_event(type_values, call_stack, context)?;
311
312 Ok(Value::new(ResolvedTargetValue { inner }))
313 }
314
315 pub fn make_python_module_source(
317 &self,
318 type_values: &TypeValues,
319 call_stack: &mut CallStack,
320 name: String,
321 source: String,
322 is_package: bool,
323 ) -> ValueResult {
324 const LABEL: &str = "PythonExecutable.make_python_module_source()";
325
326 let module = PythonModuleSource {
327 name,
328 source: FileData::Memory(source.into_bytes()),
329 is_package,
330 cache_tag: self.inner(LABEL)?.cache_tag().to_string(),
331 is_stdlib: false,
332 is_test: false,
333 };
334
335 let mut value = PythonModuleSourceValue::new(module);
336 self.python_packaging_policy().apply_to_resource(
337 LABEL,
338 type_values,
339 call_stack,
340 &mut value,
341 )?;
342
343 Ok(Value::new(value))
344 }
345
346 pub fn pip_download(
348 &mut self,
349 type_values: &TypeValues,
350 call_stack: &mut CallStack,
351 args: &Value,
352 ) -> ValueResult {
353 const LABEL: &str = "PythonExecutable.pip_download()";
354
355 required_list_arg("args", "string", args)?;
356
357 let args: Vec<String> = args.iter()?.iter().map(|x| x.to_string()).collect();
358
359 let pyoxidizer_context_value = get_context(type_values)?;
360 let pyoxidizer_context = pyoxidizer_context_value
361 .downcast_ref::<PyOxidizerEnvironmentContext>()
362 .ok_or(ValueError::IncorrectParameterType)?;
363
364 let python_packaging_policy = self.python_packaging_policy();
365
366 let mut exe = self.inner(LABEL)?;
367
368 let resources = error_context("PythonExecutable.pip_download()", || {
369 exe.pip_download(pyoxidizer_context.env(), pyoxidizer_context.verbose, &args)
370 })?;
371
372 let resources = resources
373 .iter()
374 .filter(|r| is_resource_starlark_compatible(r))
375 .map(|r| {
376 python_resource_to_value(
377 LABEL,
378 type_values,
379 call_stack,
380 r,
381 &python_packaging_policy,
382 )
383 })
384 .collect::<Result<Vec<Value>, ValueError>>()?;
385
386 Ok(Value::from(resources))
387 }
388
389 pub fn pip_install(
391 &mut self,
392 type_values: &TypeValues,
393 call_stack: &mut CallStack,
394 args: &Value,
395 extra_envs: &Value,
396 ) -> ValueResult {
397 const LABEL: &str = "PythonExecutable.pip_install()";
398
399 required_list_arg("args", "string", args)?;
400 optional_dict_arg("extra_envs", "string", "string", extra_envs)?;
401
402 let args: Vec<String> = args.iter()?.iter().map(|x| x.to_string()).collect();
403
404 let extra_envs = match extra_envs.get_type() {
405 "dict" => extra_envs
406 .iter()?
407 .iter()
408 .map(|key| {
409 let k = key.to_string();
410 let v = extra_envs.at(key).unwrap().to_string();
411 (k, v)
412 })
413 .collect(),
414 "NoneType" => HashMap::new(),
415 _ => panic!("should have validated type above"),
416 };
417
418 let pyoxidizer_context_value = get_context(type_values)?;
419 let pyoxidizer_context = pyoxidizer_context_value
420 .downcast_ref::<PyOxidizerEnvironmentContext>()
421 .ok_or(ValueError::IncorrectParameterType)?;
422
423 let python_packaging_policy = self.python_packaging_policy();
424
425 let mut exe = self.inner(LABEL)?;
426
427 let resources = error_context(LABEL, || {
428 exe.pip_install(
429 pyoxidizer_context.env(),
430 pyoxidizer_context.verbose,
431 &args,
432 &extra_envs,
433 )
434 })?;
435
436 let resources = resources
437 .iter()
438 .filter(|r| is_resource_starlark_compatible(r))
439 .map(|r| {
440 python_resource_to_value(
441 LABEL,
442 type_values,
443 call_stack,
444 r,
445 &python_packaging_policy,
446 )
447 })
448 .collect::<Result<Vec<Value>, ValueError>>()?;
449
450 Ok(Value::from(resources))
451 }
452
453 pub fn read_package_root(
455 &mut self,
456 type_values: &TypeValues,
457 call_stack: &mut CallStack,
458 path: String,
459 packages: &Value,
460 ) -> ValueResult {
461 const LABEL: &str = "PythonExecutable.read_package_root()";
462
463 required_list_arg("packages", "string", packages)?;
464
465 let packages = packages
466 .iter()?
467 .iter()
468 .map(|x| x.to_string())
469 .collect::<Vec<String>>();
470
471 let python_packaging_policy = self.python_packaging_policy();
472
473 let mut exe = self.inner(LABEL)?;
474
475 let resources =
476 error_context(LABEL, || exe.read_package_root(Path::new(&path), &packages))?;
477
478 let resources = resources
479 .iter()
480 .filter(|r| is_resource_starlark_compatible(r))
481 .map(|r| {
482 python_resource_to_value(
483 LABEL,
484 type_values,
485 call_stack,
486 r,
487 &python_packaging_policy,
488 )
489 })
490 .collect::<Result<Vec<Value>, ValueError>>()?;
491
492 Ok(Value::from(resources))
493 }
494
495 pub fn read_virtualenv(
497 &mut self,
498 type_values: &TypeValues,
499 call_stack: &mut CallStack,
500 path: String,
501 ) -> ValueResult {
502 const LABEL: &str = "PythonExecutable.read_virtualenv()";
503
504 let python_packaging_policy = self.python_packaging_policy();
505
506 let mut exe = self.inner(LABEL)?;
507
508 let resources = error_context(LABEL, || exe.read_virtualenv(Path::new(&path)))?;
509
510 let resources = resources
511 .iter()
512 .filter(|r| is_resource_starlark_compatible(r))
513 .map(|r| {
514 python_resource_to_value(
515 LABEL,
516 type_values,
517 call_stack,
518 r,
519 &python_packaging_policy,
520 )
521 })
522 .collect::<Result<Vec<Value>, ValueError>>()?;
523
524 Ok(Value::from(resources))
525 }
526
527 pub fn setup_py_install(
529 &mut self,
530 type_values: &TypeValues,
531 call_stack: &mut CallStack,
532 package_path: String,
533 extra_envs: &Value,
534 extra_global_arguments: &Value,
535 ) -> ValueResult {
536 const LABEL: &str = "PythonExecutable.setup_py_install()";
537
538 optional_dict_arg("extra_envs", "string", "string", extra_envs)?;
539 optional_list_arg("extra_global_arguments", "string", extra_global_arguments)?;
540
541 let extra_envs = match extra_envs.get_type() {
542 "dict" => extra_envs
543 .iter()?
544 .iter()
545 .map(|key| {
546 let k = key.to_string();
547 let v = extra_envs.at(key).unwrap().to_string();
548 (k, v)
549 })
550 .collect(),
551 "NoneType" => HashMap::new(),
552 _ => panic!("should have validated type above"),
553 };
554 let extra_global_arguments = match extra_global_arguments.get_type() {
555 "list" => extra_global_arguments
556 .iter()?
557 .iter()
558 .map(|x| x.to_string())
559 .collect(),
560 "NoneType" => Vec::new(),
561 _ => panic!("should have validated type above"),
562 };
563
564 let package_path = PathBuf::from(package_path);
565
566 let pyoxidizer_context_value = get_context(type_values)?;
567 let pyoxidizer_context = pyoxidizer_context_value
568 .downcast_ref::<PyOxidizerEnvironmentContext>()
569 .ok_or(ValueError::IncorrectParameterType)?;
570
571 let package_path = if package_path.is_absolute() {
572 package_path
573 } else {
574 PathBuf::from(&pyoxidizer_context.cwd).join(package_path)
575 };
576
577 let python_packaging_policy = self.python_packaging_policy();
578
579 let mut exe = self.inner(LABEL)?;
580
581 let resources = error_context(LABEL, || {
582 exe.setup_py_install(
583 pyoxidizer_context.env(),
584 &package_path,
585 pyoxidizer_context.verbose,
586 &extra_envs,
587 &extra_global_arguments,
588 )
589 })?;
590
591 let resources = resources
592 .iter()
593 .filter(|r| is_resource_starlark_compatible(r))
594 .map(|r| {
595 python_resource_to_value(
596 LABEL,
597 type_values,
598 call_stack,
599 r,
600 &python_packaging_policy,
601 )
602 })
603 .collect::<Result<Vec<Value>, ValueError>>()?;
604
605 warn!(
606 "collected {} resources from setup.py install",
607 resources.len()
608 );
609
610 Ok(Value::from(resources))
611 }
612
613 pub fn add_python_module_source(
614 &mut self,
615 label: &str,
616 module: &PythonModuleSourceValue,
617 ) -> ValueResult {
618 let inner = module.inner(label)?;
619
620 let mut exe = self.inner(label)?;
621
622 error_context(label, || {
623 for action in exe
624 .add_python_module_source(&inner.m, inner.add_context.clone())
625 .with_context(|| format!("adding {}", module.to_repr()))?
626 {
627 info!("{}", action.to_string());
628 }
629
630 Ok(())
631 })?;
632
633 Ok(Value::new(NoneType::None))
634 }
635
636 pub fn add_python_package_resource(
637 &mut self,
638 label: &str,
639 resource: &PythonPackageResourceValue,
640 ) -> ValueResult {
641 let inner = resource.inner(label)?;
642
643 let mut exe = self.inner(label)?;
644
645 error_context(label, || {
646 for action in exe
647 .add_python_package_resource(&inner.r, inner.add_context.clone())
648 .with_context(|| format!("adding {}", resource.to_repr()))?
649 {
650 info!("{}", action.to_string());
651 }
652
653 Ok(())
654 })?;
655
656 Ok(Value::new(NoneType::None))
657 }
658
659 pub fn add_python_package_distribution_resource(
660 &mut self,
661 label: &str,
662 resource: &PythonPackageDistributionResourceValue,
663 ) -> ValueResult {
664 let inner = resource.inner(label)?;
665
666 let mut exe = self.inner(label)?;
667
668 error_context(label, || {
669 for action in exe
670 .add_python_package_distribution_resource(&inner.r, inner.add_context.clone())
671 .with_context(|| format!("adding {}", resource.to_repr()))?
672 {
673 info!("{}", action.to_string());
674 }
675
676 Ok(())
677 })?;
678
679 Ok(Value::new(NoneType::None))
680 }
681
682 pub fn add_python_extension_module(
683 &mut self,
684 label: &str,
685 module: &PythonExtensionModuleValue,
686 ) -> ValueResult {
687 let inner = module.inner(label)?;
688
689 let mut exe = self.inner(label)?;
690
691 error_context(label, || {
692 for action in exe
693 .add_python_extension_module(&inner.em, inner.add_context.clone())
694 .with_context(|| format!("adding {}", module.to_repr()))?
695 {
696 info!("{}", action.to_string());
697 }
698
699 Ok(())
700 })?;
701
702 Ok(Value::new(NoneType::None))
703 }
704
705 pub fn add_file_data(&mut self, label: &str, file: &FileValue) -> ValueResult {
706 let inner = file.inner(label)?;
707
708 let mut exe = self.inner(label)?;
709
710 error_context(label, || {
711 for action in exe
712 .add_file_data(&inner.file, inner.add_context.clone())
713 .with_context(|| format!("adding {}", file.to_repr()))?
714 {
715 info!("{}", action.to_string());
716 }
717
718 Ok(())
719 })?;
720
721 Ok(Value::new(NoneType::None))
722 }
723
724 pub fn add_python_resource(&mut self, resource: &Value, label: &str) -> ValueResult {
726 match resource.get_type() {
727 FileValue::TYPE => {
728 let file = resource.downcast_ref::<FileValue>().unwrap();
729 self.add_file_data(label, file.deref())
730 }
731 PythonModuleSourceValue::TYPE => {
732 let module = resource.downcast_ref::<PythonModuleSourceValue>().unwrap();
733 self.add_python_module_source(label, module.deref())
734 }
735 PythonPackageResourceValue::TYPE => {
736 let r = resource
737 .downcast_ref::<PythonPackageResourceValue>()
738 .unwrap();
739 self.add_python_package_resource(label, r.deref())
740 }
741 PythonPackageDistributionResourceValue::TYPE => {
742 let r = resource
743 .downcast_ref::<PythonPackageDistributionResourceValue>()
744 .unwrap();
745 self.add_python_package_distribution_resource(label, r.deref())
746 }
747 PythonExtensionModuleValue::TYPE => {
748 let module = resource
749 .downcast_ref::<PythonExtensionModuleValue>()
750 .unwrap();
751 self.add_python_extension_module(label, module.deref())
752 }
753 _ => Err(ValueError::from(RuntimeError {
754 code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
755 message: "resource argument must be a Python resource type".to_string(),
756 label: ".add_python_resource()".to_string(),
757 })),
758 }
759 }
760
761 pub fn add_python_resources(&mut self, resources: &Value) -> ValueResult {
763 for resource in &resources.iter()? {
764 self.add_python_resource(&resource, "add_python_resources()")?;
765 }
766
767 Ok(Value::new(NoneType::None))
768 }
769
770 pub fn add_cargo_manifest_licensing(
772 &mut self,
773 type_values: &TypeValues,
774 manifest_path: &str,
775 all_features: bool,
776 features: &Value,
777 ) -> ValueResult {
778 const LABEL: &str = "PythonExecutable.add_cargo_manifest_licensing()";
779
780 optional_list_arg("features", "string", features)?;
781
782 let features = match features.get_type() {
783 "list" => features.iter()?.iter().map(|x| x.to_string()).collect(),
784 "NoneType" => Vec::new(),
785 _ => panic!("type should have been validated above"),
786 };
787
788 let pyoxidizer_context_value = get_context(type_values)?;
789 let pyoxidizer_context = pyoxidizer_context_value
790 .downcast_ref::<PyOxidizerEnvironmentContext>()
791 .ok_or(ValueError::IncorrectParameterType)?;
792
793 let mut exe = self.inner(LABEL)?;
794
795 error_context(LABEL, || {
796 let toolchain = pyoxidizer_context.env().ensure_rust_toolchain(None)?;
797
798 let components = licenses_from_cargo_manifest(
799 manifest_path,
800 all_features,
801 features.iter().map(|x| x.as_str()),
802 Some(exe.target_triple()),
803 &toolchain,
804 true,
805 )?;
806
807 for component in components.into_components() {
808 warn!("adding licensed component {}", component.flavor());
809 exe.add_licensed_component(component)?;
810 }
811
812 Ok(())
813 })?;
814
815 Ok(Value::new(NoneType::None))
816 }
817
818 pub fn to_embedded_resources(&self) -> ValueResult {
820 const LABEL: &str = "PythonExecutable.to_embedded_resources()";
821
822 Ok(Value::new(PythonEmbeddedResourcesValue {
823 exe: self.inner(LABEL)?.clone_trait(),
824 }))
825 }
826
827 pub fn to_file_manifest(&self, type_values: &TypeValues, prefix: String) -> ValueResult {
829 const LABEL: &str = "PythonExecutable.to_file_manifest()";
830
831 let pyoxidizer_context_value = get_context(type_values)?;
832 let pyoxidizer_context = pyoxidizer_context_value
833 .downcast_ref::<PyOxidizerEnvironmentContext>()
834 .ok_or(ValueError::IncorrectParameterType)?;
835
836 let manifest_value = FileManifestValue::new_from_args()?;
837 let mut manifest = manifest_value
838 .downcast_mut::<FileManifestValue>()
839 .unwrap()
840 .unwrap();
841
842 let exe = self.inner(LABEL)?;
843
844 error_context(LABEL, || {
845 file_manifest_add_python_executable(
846 &mut manifest,
847 pyoxidizer_context.env(),
848 &prefix,
849 &**exe,
850 &pyoxidizer_context.build_target_triple,
851 pyoxidizer_context.build_release,
852 &pyoxidizer_context.build_opt_level,
853 )
854 .context("adding PythonExecutable to FileManifest")
855 })?;
856
857 Ok(manifest_value.clone())
858 }
859
860 #[allow(clippy::too_many_arguments)]
862 pub fn to_wix_bundle_builder(
863 &self,
864 type_values: &TypeValues,
865 call_stack: &mut CallStack,
866 id_prefix: String,
867 product_name: String,
868 product_version: String,
869 product_manufacturer: String,
870 msi_builder_callback: Value,
871 ) -> ValueResult {
872 const LABEL: &str = "PythonExecutable.to_wix_bundle_builder()";
873
874 optional_type_arg("msi_builder_callback", "function", &msi_builder_callback)?;
875
876 let msi_builder_value = self.to_wix_msi_builder(
877 type_values,
878 call_stack,
879 id_prefix.clone(),
880 product_name.clone(),
881 product_version.clone(),
882 product_manufacturer.clone(),
883 )?;
884
885 if msi_builder_callback.get_type() == "function" {
886 msi_builder_callback.call(
887 call_stack,
888 type_values,
889 vec![msi_builder_value.clone()],
890 LinkedHashMap::new(),
891 None,
892 None,
893 )?;
894 }
895
896 let msi_builder = msi_builder_value
897 .downcast_ref::<WiXMsiBuilderValue>()
898 .unwrap();
899
900 let arch = target_triple_to_wix_arch(self.inner(LABEL)?.target_triple()).unwrap_or("x64");
901
902 let bundle_builder_value = WiXBundleBuilderValue::new_from_args(
903 id_prefix,
904 product_name,
905 product_version,
906 product_manufacturer,
907 arch.to_string(),
908 )?;
909 let mut bundle_builder = bundle_builder_value
910 .downcast_mut::<WiXBundleBuilderValue>()
911 .unwrap()
912 .unwrap();
913
914 match self.inner(LABEL)?.target_triple() {
916 "i686-pc-windows-msvc" => {
917 bundle_builder.add_vc_redistributable(type_values, "x86".to_string())?;
918 }
919 "x86_64-pc-windows-msvc" => {
920 bundle_builder.add_vc_redistributable(type_values, "x64".to_string())?;
921 }
922 _ => {}
923 }
924
925 bundle_builder.add_wix_msi_builder(
926 msi_builder.deref().clone(),
927 false,
928 Value::new(NoneType::None),
929 )?;
930
931 Ok(bundle_builder_value.clone())
932 }
933
934 pub fn to_wix_msi_builder(
936 &self,
937 type_values: &TypeValues,
938 call_stack: &mut CallStack,
939 id_prefix: String,
940 product_name: String,
941 product_version: String,
942 product_manufacturer: String,
943 ) -> ValueResult {
944 const LABEL: &str = "PythonExecutable.to_wix_msi_builder()";
945
946 let manifest_value = self.to_file_manifest(type_values, ".".to_string())?;
947 let manifest = manifest_value.downcast_ref::<FileManifestValue>().unwrap();
948
949 let arch = target_triple_to_wix_arch(self.inner(LABEL)?.target_triple()).unwrap_or("x64");
950
951 let builder_value = WiXMsiBuilderValue::new_from_args(
952 id_prefix,
953 product_name,
954 product_version,
955 product_manufacturer,
956 arch.to_string(),
957 )?;
958 let mut builder = builder_value
959 .downcast_mut::<WiXMsiBuilderValue>()
960 .unwrap()
961 .unwrap();
962
963 builder.add_program_files_manifest(type_values, call_stack, manifest.deref().clone())?;
964
965 Ok(builder_value.clone())
966 }
967
968 pub fn filter_resources_from_files(
970 &mut self,
971 files: &Value,
972 glob_files: &Value,
973 ) -> ValueResult {
974 const LABEL: &str = "PythonExecutable.filter_resources_from_files()";
975
976 optional_list_arg("files", "string", files)?;
977 optional_list_arg("glob_files", "string", glob_files)?;
978
979 let files = match files.get_type() {
980 "list" => files
981 .iter()?
982 .iter()
983 .map(|x| PathBuf::from(x.to_string()))
984 .collect(),
985 "NoneType" => Vec::new(),
986 _ => panic!("type should have been validated above"),
987 };
988
989 let glob_files = match glob_files.get_type() {
990 "list" => glob_files.iter()?.iter().map(|x| x.to_string()).collect(),
991 "NoneType" => Vec::new(),
992 _ => panic!("type should have been validated above"),
993 };
994
995 let files_refs = files.iter().map(|x| x.as_ref()).collect::<Vec<&Path>>();
996 let glob_files_refs = glob_files.iter().map(|x| x.as_ref()).collect::<Vec<&str>>();
997
998 let mut exe = self.inner(LABEL)?;
999
1000 error_context(LABEL, || {
1001 exe.filter_resources_from_files(&files_refs, &glob_files_refs)
1002 })?;
1003
1004 Ok(Value::new(NoneType::None))
1005 }
1006}
1007
1008starlark_module! { python_executable_env =>
1009 PythonExecutable.build(env env, call_stack cs, this, target: String) {
1010 let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
1011 this.build(env, cs, target)
1012 }
1013
1014 PythonExecutable.make_python_module_source(
1015 env env,
1016 call_stack cs,
1017 this,
1018 name: String,
1019 source: String,
1020 is_package: bool = false
1021 ) {
1022 let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
1023 this.make_python_module_source(env, cs, name, source, is_package)
1024 }
1025
1026 PythonExecutable.pip_download(
1027 env env,
1028 call_stack cs,
1029 this,
1030 args
1031 ) {
1032 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1033 this.pip_download(env, cs, &args)
1034 }
1035
1036 PythonExecutable.pip_install(
1037 env env,
1038 call_stack cs,
1039 this,
1040 args,
1041 extra_envs=NoneType::None
1042 ) {
1043 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1044 this.pip_install(env, cs, &args, &extra_envs)
1045 }
1046
1047 PythonExecutable.read_package_root(
1048 env env,
1049 call_stack cs,
1050 this,
1051 path: String,
1052 packages
1053 ) {
1054 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1055 this.read_package_root(env, cs, path, &packages)
1056 }
1057
1058 PythonExecutable.read_virtualenv(
1059 env env,
1060 call_stack cs,
1061 this,
1062 path: String
1063 ) {
1064 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1065 this.read_virtualenv(env, cs, path)
1066 }
1067
1068 PythonExecutable.setup_py_install(
1069 env env,
1070 call_stack cs,
1071 this,
1072 package_path: String,
1073 extra_envs=NoneType::None,
1074 extra_global_arguments=NoneType::None
1075 ) {
1076 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1077 this.setup_py_install(env, cs, package_path, &extra_envs, &extra_global_arguments)
1078 }
1079
1080 PythonExecutable.add_python_resource(
1081 this,
1082 resource
1083 ) {
1084 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1085 this.add_python_resource(
1086 &resource,
1087 "add_python_resource",
1088 )
1089 }
1090
1091 PythonExecutable.add_python_resources(
1092 this,
1093 resources
1094 ) {
1095 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1096 this.add_python_resources(
1097 &resources,
1098 )
1099 }
1100
1101 PythonExecutable.add_cargo_manifest_licensing(
1102 env env,
1103 this,
1104 manifest_path: String,
1105 all_features: bool = false,
1106 features=NoneType::None
1107 ) {
1108 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1109 this.add_cargo_manifest_licensing(env, &manifest_path, all_features, &features)
1110 }
1111
1112 PythonExecutable.filter_resources_from_files(
1113 this,
1114 files=NoneType::None,
1115 glob_files=NoneType::None)
1116 {
1117 let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
1118 this.filter_resources_from_files(&files, &glob_files)
1119 }
1120
1121 PythonExecutable.to_embedded_resources(this) {
1122 let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
1123 this.to_embedded_resources()
1124 }
1125
1126 PythonExecutable.to_file_manifest(env env, this, prefix: String) {
1127 let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
1128 this.to_file_manifest(env, prefix)
1129 }
1130
1131 PythonExecutable.to_wix_bundle_builder(
1132 env env,
1133 call_stack cs,
1134 this,
1135 id_prefix: String,
1136 product_name: String,
1137 product_version: String,
1138 product_manufacturer: String,
1139 msi_builder_callback = NoneType::None
1140 ) {
1141 let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
1142 this.to_wix_bundle_builder(
1143 env,
1144 cs,
1145 id_prefix,
1146 product_name,
1147 product_version,
1148 product_manufacturer,
1149 msi_builder_callback
1150 )
1151 }
1152
1153 PythonExecutable.to_wix_msi_builder(
1154 env env,
1155 call_stack cs,
1156 this,
1157 id_prefix: String,
1158 product_name: String,
1159 product_version: String,
1160 product_manufacturer: String
1161 ) {
1162 let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
1163 this.to_wix_msi_builder(env, cs, id_prefix, product_name, product_version, product_manufacturer)
1164 }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use {
1170 super::super::testutil::*,
1171 super::*,
1172 crate::{python_distributions::PYTHON_DISTRIBUTIONS, testutil::*},
1173 };
1174
1175 #[test]
1176 fn test_default_values() -> Result<()> {
1177 let mut env = test_evaluation_context_builder()?.into_context()?;
1178 add_exe(&mut env)?;
1179 let exe = env.eval("exe")?;
1180
1181 assert_eq!(exe.get_type(), "PythonExecutable");
1182
1183 let exe = exe.downcast_ref::<PythonExecutableValue>().unwrap();
1184 let inner = exe.inner("ignored").unwrap();
1185 assert!(inner
1186 .iter_resources()
1187 .any(|(_, r)| r.in_memory_source.is_some()));
1188 assert!(inner
1189 .iter_resources()
1190 .all(|(_, r)| r.in_memory_resources.is_none()));
1191
1192 Ok(())
1193 }
1194
1195 #[test]
1196 fn test_no_sources() -> Result<()> {
1197 let mut env = test_evaluation_context_builder()?.into_context()?;
1198
1199 env.eval("dist = default_python_distribution()")?;
1200 env.eval("policy = dist.make_python_packaging_policy()")?;
1201 env.eval("policy.include_distribution_sources = False")?;
1202
1203 let exe = env.eval("dist.to_python_executable('testapp', packaging_policy=policy)")?;
1204
1205 assert_eq!(exe.get_type(), "PythonExecutable");
1206
1207 let exe = exe.downcast_ref::<PythonExecutableValue>().unwrap();
1208 let inner = exe.inner("ignored").unwrap();
1209 assert!(inner
1210 .iter_resources()
1211 .all(|(_, r)| r.in_memory_source.is_none()));
1212
1213 Ok(())
1214 }
1215
1216 #[test]
1217 fn test_make_python_module_source() -> Result<()> {
1218 let mut env = test_evaluation_context_builder()?.into_context()?;
1219 add_exe(&mut env)?;
1220 let m = env.eval("exe.make_python_module_source('foo', 'import bar')")?;
1221
1222 assert_eq!(m.get_type(), PythonModuleSourceValue::TYPE);
1223 assert_eq!(m.get_attr("name").unwrap().to_str(), "foo");
1224 assert_eq!(m.get_attr("source").unwrap().to_str(), "import bar");
1225 assert!(!m.get_attr("is_package").unwrap().to_bool());
1226
1227 Ok(())
1228 }
1229
1230 #[test]
1231 fn test_make_python_module_source_callback() -> Result<()> {
1232 let mut env = test_evaluation_context_builder()?.into_context()?;
1233 env.eval("dist = default_python_distribution()")?;
1234 env.eval("policy = dist.make_python_packaging_policy()")?;
1235 env.eval(
1236 "def my_func(policy, resource):\n resource.add_source = True\n resource.add_bytecode_optimization_level_two = True\n",
1237 )?;
1238 env.eval("policy.register_resource_callback(my_func)")?;
1239 env.eval("exe = dist.to_python_executable('testapp', packaging_policy = policy)")?;
1240
1241 let m = env.eval("exe.make_python_module_source('foo', 'import bar')")?;
1242
1243 assert_eq!(m.get_type(), PythonModuleSourceValue::TYPE);
1244 assert_eq!(m.get_attr("name").unwrap().to_str(), "foo");
1245 assert_eq!(m.get_attr("source").unwrap().to_str(), "import bar");
1246 assert!(!m.get_attr("is_package").unwrap().to_bool());
1247 assert!(m.get_attr("add_source").unwrap().to_bool());
1248 assert!(m
1249 .get_attr("add_bytecode_optimization_level_two")
1250 .unwrap()
1251 .to_bool(),);
1252
1253 Ok(())
1254 }
1255
1256 #[test]
1257 fn test_pip_download_pyflakes() -> Result<()> {
1258 for target_triple in PYTHON_DISTRIBUTIONS.all_target_triples() {
1259 let mut env = test_evaluation_context_builder()?
1260 .build_target_triple(target_triple)
1261 .into_context()?;
1262
1263 env.eval("dist = default_python_distribution()")?;
1264 env.eval("exe = dist.to_python_executable('testapp')")?;
1265
1266 let resources = env.eval("exe.pip_download(['pyflakes==2.2.0'])")?;
1267
1268 assert_eq!(resources.get_type(), "list");
1269
1270 let raw_it = resources.iter().unwrap();
1271 let mut it = raw_it.iter();
1272
1273 let v = it.next().unwrap();
1274 assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
1275 let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
1276 let inner = x.inner("ignored").unwrap();
1277 assert!(inner.m.package().starts_with("pyflakes"));
1278 }
1279
1280 Ok(())
1281 }
1282
1283 #[test]
1284 fn test_pip_install_simple() -> Result<()> {
1285 let mut env = test_evaluation_context_builder()?.into_context()?;
1286
1287 env.eval("dist = default_python_distribution()")?;
1288 env.eval("policy = dist.make_python_packaging_policy()")?;
1289 env.eval("policy.include_distribution_sources = False")?;
1290 env.eval("exe = dist.to_python_executable('testapp', packaging_policy = policy)")?;
1291
1292 let resources = env.eval("exe.pip_install(['pyflakes==2.1.1'])")?;
1293 assert_eq!(resources.get_type(), "list");
1294
1295 let raw_it = resources.iter().unwrap();
1296 let mut it = raw_it.iter();
1297
1298 let v = it.next().unwrap();
1299 assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
1300 let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
1301 let inner = x.inner("ignored").unwrap();
1302 assert_eq!(inner.m.name, "pyflakes");
1303 assert!(inner.m.is_package);
1304
1305 Ok(())
1306 }
1307
1308 #[test]
1309 fn test_read_package_root_simple() -> Result<()> {
1310 let temp_dir = get_env()?.temporary_directory("pyoxidizer-test")?;
1311
1312 let root = temp_dir.path();
1313 std::fs::create_dir(root.join("bar"))?;
1314 let bar_init = root.join("bar").join("__init__.py");
1315 std::fs::write(&bar_init, "# bar")?;
1316
1317 let foo_path = root.join("foo.py");
1318 std::fs::write(&foo_path, "# foo")?;
1319
1320 let baz_path = root.join("baz.py");
1321 std::fs::write(&baz_path, "# baz")?;
1322
1323 std::fs::create_dir(root.join("extra"))?;
1324 let extra_path = root.join("extra").join("__init__.py");
1325 std::fs::write(&extra_path, "# extra")?;
1326
1327 let mut env = test_evaluation_context_builder()?.into_context()?;
1328 env.eval("dist = default_python_distribution()")?;
1329 env.eval("policy = dist.make_python_packaging_policy()")?;
1330 env.eval("policy.include_distribution_sources = False")?;
1331 env.eval("exe = dist.to_python_executable('testapp', packaging_policy = policy)")?;
1332
1333 let resources = env.eval(&format!(
1334 "exe.read_package_root(\"{}\", packages=['foo', 'bar'])",
1335 root.display().to_string().replace('\\', "/")
1336 ))?;
1337
1338 assert_eq!(resources.get_type(), "list");
1339 assert_eq!(resources.length().unwrap(), 2);
1340
1341 let raw_it = resources.iter().unwrap();
1342 let mut it = raw_it.iter();
1343
1344 let v = it.next().unwrap();
1345 assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
1346 let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
1347 let inner = x.inner("ignored").unwrap();
1348 assert_eq!(inner.m.name, "bar");
1349 assert!(inner.m.is_package);
1350 assert_eq!(inner.m.source.resolve_content().unwrap(), b"# bar");
1351 drop(inner);
1352
1353 let v = it.next().unwrap();
1354 assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
1355 let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
1356 let inner = x.inner("ignored").unwrap();
1357 assert_eq!(inner.m.name, "foo");
1358 assert!(!inner.m.is_package);
1359 assert_eq!(inner.m.source.resolve_content().unwrap(), b"# foo");
1360 drop(inner);
1361
1362 temp_dir.close()?;
1363
1364 Ok(())
1365 }
1366
1367 #[test]
1368 fn licenses_filename() -> Result<()> {
1369 let mut env = test_evaluation_context_builder()?.into_context()?;
1370 add_exe(&mut env)?;
1371
1372 let v = env.eval("exe.licenses_filename")?;
1373 assert_eq!(v.get_type(), "string");
1374 assert_eq!(v.to_string(), "COPYING.txt");
1375
1376 env.eval("exe.licenses_filename = 'licenses'")?;
1377 let v = env.eval("exe.licenses_filename")?;
1378 assert_eq!(v.get_type(), "string");
1379 assert_eq!(v.to_string(), "licenses");
1380
1381 env.eval("exe.licenses_filename = None")?;
1382 let v = env.eval("exe.licenses_filename")?;
1383 assert_eq!(v.get_type(), "NoneType");
1384
1385 Ok(())
1386 }
1387
1388 #[test]
1389 fn test_windows_runtime_dlls_mode() -> Result<()> {
1390 let mut env = test_evaluation_context_builder()?.into_context()?;
1391 add_exe(&mut env)?;
1392
1393 let value = env.eval("exe.windows_runtime_dlls_mode")?;
1394 assert_eq!(value.get_type(), "string");
1395 assert_eq!(value.to_string(), "when-present");
1396
1397 let value =
1398 env.eval("exe.windows_runtime_dlls_mode = 'never'; exe.windows_runtime_dlls_mode")?;
1399 assert_eq!(value.to_string(), "never");
1400
1401 let value =
1402 env.eval("exe.windows_runtime_dlls_mode = 'always'; exe.windows_runtime_dlls_mode")?;
1403 assert_eq!(value.to_string(), "always");
1404
1405 assert!(env.eval("exe.windows_runtime_dlls_mode = 'bad'").is_err());
1406
1407 let value = env.eval(
1408 "exe.windows_runtime_dlls_mode = 'when-present'; exe.windows_runtime_dlls_mode",
1409 )?;
1410 assert_eq!(value.to_string(), "when-present");
1411
1412 Ok(())
1413 }
1414
1415 #[test]
1416 fn test_packed_resources_load_mode() -> Result<()> {
1417 let mut env = test_evaluation_context_builder()?.into_context()?;
1418 add_exe(&mut env)?;
1419
1420 let value = env.eval("exe.packed_resources_load_mode")?;
1421 assert_eq!(value.get_type(), "string");
1422 assert_eq!(value.to_string(), "embedded:packed-resources");
1423
1424 let value =
1425 env.eval("exe.packed_resources_load_mode = 'none'; exe.packed_resources_load_mode")?;
1426 assert_eq!(value.get_type(), "string");
1427 assert_eq!(value.to_string(), "none");
1428
1429 Ok(())
1430 }
1431
1432 #[test]
1433 fn test_windows_subsystem() -> Result<()> {
1434 let mut env = test_evaluation_context_builder()?.into_context()?;
1435 add_exe(&mut env)?;
1436
1437 let value = env.eval("exe.windows_subsystem")?;
1438 assert_eq!(value.get_type(), "string");
1439 assert_eq!(value.to_string(), "console");
1440
1441 let value = env.eval("exe.windows_subsystem = 'windows'; exe.windows_subsystem")?;
1442 assert_eq!(value.get_type(), "string");
1443 assert_eq!(value.to_string(), "windows");
1444
1445 Ok(())
1446 }
1447
1448 #[test]
1449 fn test_tcl_files_path() -> Result<()> {
1450 let mut env = test_evaluation_context_builder()?.into_context()?;
1451 add_exe(&mut env)?;
1452
1453 let value = env.eval("exe.tcl_files_path")?;
1454 assert_eq!(value.get_type(), "NoneType");
1455
1456 let value = env.eval("exe.tcl_files_path = 'lib'; exe.tcl_files_path")?;
1457 assert_eq!(value.get_type(), "string");
1458 assert_eq!(value.to_string(), "lib");
1459
1460 let value = env.eval("exe.tcl_files_path = None; exe.tcl_files_path")?;
1461 assert_eq!(value.get_type(), "NoneType");
1462
1463 Ok(())
1464 }
1465
1466 #[test]
1467 fn test_to_wix_bundle_builder_callback() -> Result<()> {
1468 let mut env = test_evaluation_context_builder()?.into_context()?;
1469 add_exe(&mut env)?;
1470 env.eval("def modify(msi):\n msi.package_description = 'description'\n")?;
1471 let builder_value = env.eval("exe.to_wix_bundle_builder('id_prefix', 'product_name', '0.1', 'manufacturer', msi_builder_callback = modify)")?;
1472 let builder = builder_value
1473 .downcast_ref::<WiXBundleBuilderValue>()
1474 .unwrap();
1475
1476 assert_eq!(builder.build_msis.len(), 1);
1477 let mut writer = xml::EventWriter::new(vec![]);
1478 builder.build_msis[0]
1479 .inner("ignored")
1480 .unwrap()
1481 .builder
1482 .write_xml(&mut writer)?;
1483
1484 let xml = String::from_utf8(writer.into_inner())?;
1485 assert!(xml.find("Description=\"description\"").is_some());
1486
1487 Ok(())
1488 }
1489
1490 #[cfg(windows)]
1491 #[test]
1492 fn test_to_wix_bundle_builder() -> Result<()> {
1493 let mut env = test_evaluation_context_builder()?.into_context()?;
1494 add_exe(&mut env)?;
1495 env.eval("bundle = exe.to_wix_bundle_builder('id_prefix', 'product_name', '0.1', 'product_manufacturer')")?;
1496 env.eval("bundle.build('test_to_wix_bundle_builder')")?;
1497
1498 let exe_path = env
1499 .target_build_path("test_to_wix_bundle_builder")
1500 .unwrap()
1501 .join("product_name-0.1.exe");
1502
1503 assert!(exe_path.exists());
1504
1505 Ok(())
1506 }
1507
1508 #[cfg(windows)]
1509 #[test]
1510 fn test_to_wix_msi_builder() -> Result<()> {
1511 let mut env = test_evaluation_context_builder()?.into_context()?;
1512 add_exe(&mut env)?;
1513 env.eval("msi = exe.to_wix_msi_builder('id_prefix', 'product_name', '0.1', 'product_manufacturer')")?;
1514 env.eval("msi.build('test_to_wix_msi_builder')")?;
1515
1516 let msi_path = env
1517 .target_build_path("test_to_wix_msi_builder")
1518 .unwrap()
1519 .join("product_name-0.1.msi");
1520
1521 assert!(msi_path.exists());
1522
1523 Ok(())
1524 }
1525}