pyoxidizerlib/starlark/
python_packaging_policy.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use {
6    super::python_resource::ResourceCollectionContext,
7    linked_hash_map::LinkedHashMap,
8    python_packaging::{
9        location::ConcreteResourceLocation,
10        policy::{ExtensionModuleFilter, PythonPackagingPolicy, ResourceHandlingMode},
11    },
12    starlark::{
13        environment::TypeValues,
14        eval::call_stack::CallStack,
15        starlark_fun, starlark_module, starlark_parse_param_type, starlark_signature,
16        starlark_signature_extraction, starlark_signatures,
17        values::{
18            error::{RuntimeError, UnsupportedOperation, ValueError},
19            none::NoneType,
20            Mutable, TypedValue, Value, ValueResult,
21        },
22    },
23    starlark_dialect_build_targets::required_type_arg,
24    std::{
25        ops::Deref,
26        sync::{Arc, Mutex, MutexGuard},
27    },
28};
29
30#[derive(Debug, Clone)]
31pub struct PythonPackagingPolicyValue {
32    inner: Arc<Mutex<PythonPackagingPolicy>>,
33
34    /// Starlark functions to influence PythonResourceAddCollectionContext creation.
35    derive_context_callbacks: Vec<Value>,
36}
37
38impl PythonPackagingPolicyValue {
39    pub fn new(inner: PythonPackagingPolicy) -> Self {
40        Self {
41            inner: Arc::new(Mutex::new(inner)),
42            derive_context_callbacks: vec![],
43        }
44    }
45
46    pub fn inner(&self, label: &str) -> Result<MutexGuard<PythonPackagingPolicy>, ValueError> {
47        self.inner.try_lock().map_err(|e| {
48            ValueError::Runtime(RuntimeError {
49                code: "PYTHON_PACKAGING_POLICY",
50                message: format!("unable to obtain lock: {}", e),
51                label: label.to_string(),
52            })
53        })
54    }
55
56    /// Apply this policy to a resource.
57    ///
58    /// This has the effect of replacing the `PythonResourceAddCollectionContext`
59    /// instance with a fresh one derived from the policy. If no context is
60    /// currently defined on the resource, a new one will be created so there is.
61    pub fn apply_to_resource<T>(
62        &self,
63        label: &str,
64        type_values: &TypeValues,
65        call_stack: &mut CallStack,
66        value: &mut T,
67    ) -> ValueResult
68    where
69        T: TypedValue + ResourceCollectionContext + Clone,
70    {
71        let new_context = self
72            .inner(label)?
73            .derive_add_collection_context(&value.as_python_resource()?);
74        value.replace_add_collection_context(new_context)?;
75
76        for func in &self.derive_context_callbacks {
77            // This is a bit wonky. We pass in a `TypeValue`, which isn't a `Value`.
78            // To go from `TypeValue` to `Value`, we need to construct a `Value`, which
79            // takes ownership of the `TypeValue`. But we need to move a `Value` as an
80            // argument into call().
81            //
82            // Our solution for this is to create a copy of the passed object and
83            // construct a `Value` from it. After the call, we downcast it back to
84            // our T, retrieve its add context, and replace that on the original value.
85            //
86            // There might be a way to pass a `Value` into this method. But for now,
87            // this solution works.
88            let temp_value = Value::new(value.clone());
89
90            func.call(
91                call_stack,
92                type_values,
93                vec![Value::new(self.clone()), temp_value.clone()],
94                LinkedHashMap::new(),
95                None,
96                None,
97            )?;
98
99            let downcast_value = temp_value.downcast_ref::<T>().unwrap();
100            let inner: &T = downcast_value.deref();
101            value.replace_add_collection_context(inner.add_collection_context()?.unwrap())?;
102        }
103
104        Ok(Value::from(NoneType::None))
105    }
106}
107
108impl TypedValue for PythonPackagingPolicyValue {
109    type Holder = Mutable<PythonPackagingPolicyValue>;
110    const TYPE: &'static str = "PythonPackagingPolicy";
111
112    fn values_for_descendant_check_and_freeze<'a>(
113        &'a self,
114    ) -> Box<dyn Iterator<Item = Value> + 'a> {
115        Box::new(self.derive_context_callbacks.iter().cloned())
116    }
117
118    fn get_attr(&self, attribute: &str) -> ValueResult {
119        let inner = self.inner(&format!("PythonPackagingPolicy.{}", attribute))?;
120
121        let v = match attribute {
122            "allow_files" => Value::from(inner.allow_files()),
123            "allow_in_memory_shared_library_loading" => {
124                Value::from(inner.allow_in_memory_shared_library_loading())
125            }
126            "bytecode_optimize_level_zero" => Value::from(inner.bytecode_optimize_level_zero()),
127            "bytecode_optimize_level_one" => Value::from(inner.bytecode_optimize_level_one()),
128            "bytecode_optimize_level_two" => Value::from(inner.bytecode_optimize_level_two()),
129            "extension_module_filter" => Value::from(inner.extension_module_filter().as_ref()),
130            "file_scanner_classify_files" => Value::from(inner.file_scanner_classify_files()),
131            "file_scanner_emit_files" => Value::from(inner.file_scanner_emit_files()),
132            "include_distribution_sources" => Value::from(inner.include_distribution_sources()),
133            "include_distribution_resources" => Value::from(inner.include_distribution_resources()),
134            "include_classified_resources" => Value::from(inner.include_classified_resources()),
135            "include_file_resources" => Value::from(inner.include_file_resources()),
136            "include_non_distribution_sources" => {
137                Value::from(inner.include_non_distribution_sources())
138            }
139            "include_test" => Value::from(inner.include_test()),
140            "preferred_extension_module_variants" => {
141                Value::try_from(inner.preferred_extension_module_variants().clone())?
142            }
143            "resources_location" => Value::from(inner.resources_location().to_string()),
144            "resources_location_fallback" => match inner.resources_location_fallback() {
145                Some(location) => Value::from(location.to_string()),
146                None => Value::from(NoneType::None),
147            },
148            attr => {
149                return Err(ValueError::OperationNotSupported {
150                    op: UnsupportedOperation::GetAttr(attr.to_string()),
151                    left: "PythonPackagingPolicy".to_string(),
152                    right: None,
153                })
154            }
155        };
156
157        Ok(v)
158    }
159
160    fn has_attr(&self, attribute: &str) -> Result<bool, ValueError> {
161        Ok(matches!(
162            attribute,
163            "allow_files"
164                | "allow_in_memory_shared_library_loading"
165                | "bytecode_optimize_level_zero"
166                | "bytecode_optimize_level_one"
167                | "bytecode_optimize_level_two"
168                | "extension_module_filter"
169                | "file_scanner_classify_files"
170                | "file_scanner_emit_files"
171                | "include_distribution_sources"
172                | "include_distribution_resources"
173                | "include_classified_resources"
174                | "include_file_resources"
175                | "include_non_distribution_sources"
176                | "include_test"
177                | "preferred_extension_module_variants"
178                | "resources_location"
179                | "resources_location_fallback"
180        ))
181    }
182
183    fn set_attr(&mut self, attribute: &str, value: Value) -> Result<(), ValueError> {
184        let mut inner = self.inner(&format!("PythonPackagingPolicy.{}", attribute))?;
185
186        match attribute {
187            "allow_files" => {
188                inner.set_allow_files(value.to_bool());
189            }
190            "allow_in_memory_shared_library_loading" => {
191                inner.set_allow_in_memory_shared_library_loading(value.to_bool());
192            }
193            "bytecode_optimize_level_zero" => {
194                inner.set_bytecode_optimize_level_zero(value.to_bool());
195            }
196            "bytecode_optimize_level_one" => {
197                inner.set_bytecode_optimize_level_one(value.to_bool());
198            }
199            "bytecode_optimize_level_two" => {
200                inner.set_bytecode_optimize_level_two(value.to_bool());
201            }
202            "extension_module_filter" => {
203                let filter =
204                    ExtensionModuleFilter::try_from(value.to_string().as_str()).map_err(|e| {
205                        ValueError::from(RuntimeError {
206                            code: "PYOXIDIZER_BUILD",
207                            message: e,
208                            label: format!("{}.{} = {}", Self::TYPE, attribute, value),
209                        })
210                    })?;
211
212                inner.set_extension_module_filter(filter);
213            }
214            "file_scanner_classify_files" => {
215                inner.set_file_scanner_classify_files(value.to_bool());
216            }
217            "file_scanner_emit_files" => {
218                inner.set_file_scanner_emit_files(value.to_bool());
219            }
220            "include_classified_resources" => {
221                inner.set_include_classified_resources(value.to_bool());
222            }
223            "include_distribution_sources" => {
224                inner.set_include_distribution_sources(value.to_bool());
225            }
226            "include_distribution_resources" => {
227                inner.set_include_distribution_resources(value.to_bool());
228            }
229            "include_file_resources" => {
230                inner.set_include_file_resources(value.to_bool());
231            }
232            "include_non_distribution_sources" => {
233                inner.set_include_non_distribution_sources(value.to_bool());
234            }
235            "include_test" => {
236                inner.set_include_test(value.to_bool());
237            }
238            "resources_location" => {
239                inner.set_resources_location(
240                    ConcreteResourceLocation::try_from(value.to_string().as_str()).map_err(
241                        |e| {
242                            ValueError::from(RuntimeError {
243                                code: "PYOXIDIZER_BUILD",
244                                message: e,
245                                label: format!("{}.{} = {}", Self::TYPE, attribute, value),
246                            })
247                        },
248                    )?,
249                );
250            }
251            "resources_location_fallback" => {
252                if value.get_type() == "NoneType" {
253                    inner.set_resources_location_fallback(None);
254                } else {
255                    inner.set_resources_location_fallback(Some(
256                        ConcreteResourceLocation::try_from(value.to_string().as_str()).map_err(
257                            |e| {
258                                ValueError::from(RuntimeError {
259                                    code: "PYOXIDIZER_BUILD",
260                                    message: e,
261                                    label: format!("{}.{} = {}", Self::TYPE, attribute, value),
262                                })
263                            },
264                        )?,
265                    ));
266                }
267            }
268            attr => {
269                return Err(ValueError::OperationNotSupported {
270                    op: UnsupportedOperation::SetAttr(attr.to_string()),
271                    left: Self::TYPE.to_owned(),
272                    right: None,
273                });
274            }
275        }
276
277        Ok(())
278    }
279}
280
281// Starlark methods.
282impl PythonPackagingPolicyValue {
283    fn starlark_register_resource_callback(&mut self, func: &Value) -> ValueResult {
284        required_type_arg("func", "function", func)?;
285
286        self.derive_context_callbacks.push(func.clone());
287
288        Ok(Value::from(NoneType::None))
289    }
290
291    #[allow(clippy::unnecessary_wraps)]
292    fn starlark_set_preferred_extension_module_variant(
293        &mut self,
294        name: String,
295        value: String,
296    ) -> ValueResult {
297        self.inner("PythonPackagingPolicy.set_preferred_extension_module_variant()")?
298            .set_preferred_extension_module_variant(&name, &value);
299
300        Ok(Value::from(NoneType::None))
301    }
302
303    fn starlark_set_resource_handling_mode(&mut self, value: String) -> ValueResult {
304        const LABEL: &str = "PythonPackagingPolicy.set_resource_handling_mode()";
305
306        let mode = ResourceHandlingMode::try_from(value.as_str()).map_err(|e| {
307            ValueError::from(RuntimeError {
308                code: "PYTHON_PACKAGING_POLICY",
309                message: e,
310                label: LABEL.to_string(),
311            })
312        })?;
313
314        self.inner(LABEL)?.set_resource_handling_mode(mode);
315
316        Ok(Value::from(NoneType::None))
317    }
318}
319
320starlark_module! { python_packaging_policy_module =>
321    PythonPackagingPolicy.register_resource_callback(this, func) {
322        let mut this = this.downcast_mut::<PythonPackagingPolicyValue>().unwrap().unwrap();
323        this.starlark_register_resource_callback(&func)
324    }
325
326    PythonPackagingPolicy.set_preferred_extension_module_variant(
327        this,
328        name: String,
329        value: String
330    ) {
331        let mut this = this.downcast_mut::<PythonPackagingPolicyValue>().unwrap().unwrap();
332        this.starlark_set_preferred_extension_module_variant(name, value)
333    }
334
335    PythonPackagingPolicy.set_resource_handling_mode(this, mode: String) {
336        let mut this = this.downcast_mut::<PythonPackagingPolicyValue>().unwrap().unwrap();
337        this.starlark_set_resource_handling_mode(mode)
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use {
344        super::super::testutil::*,
345        super::super::{
346            python_distribution::PythonDistributionValue, python_executable::PythonExecutableValue,
347        },
348        super::*,
349        anyhow::Result,
350        indoc::indoc,
351    };
352
353    #[test]
354    fn test_basic() -> Result<()> {
355        let mut env = test_evaluation_context_builder()?.into_context()?;
356
357        env.eval("dist = default_python_distribution()")?;
358        env.eval("policy = dist.make_python_packaging_policy()")?;
359
360        let dist_value = env.eval("dist")?;
361        let dist = dist_value
362            .downcast_ref::<PythonDistributionValue>()
363            .unwrap();
364        let dist_ref = dist.distribution.as_ref().unwrap();
365
366        let policy = dist_ref.create_packaging_policy().unwrap();
367
368        // Need value to go out of scope to avoid double borrow.
369        {
370            let policy_value = env.eval("policy")?;
371            assert_eq!(policy_value.get_type(), "PythonPackagingPolicy");
372
373            let x = policy_value
374                .downcast_ref::<PythonPackagingPolicyValue>()
375                .unwrap();
376
377            // Distribution method should return a policy equivalent to what Starlark gives us.
378            assert_eq!(&policy, x.inner("test").unwrap().deref());
379        }
380
381        // attributes work
382        let value = env.eval("policy.extension_module_filter")?;
383        assert_eq!(value.get_type(), "string");
384        assert_eq!(value.to_string(), policy.extension_module_filter().as_ref());
385
386        let value =
387            env.eval("policy.extension_module_filter = 'minimal'; policy.extension_module_filter")?;
388        assert_eq!(value.to_string(), "minimal");
389
390        let value = env.eval("policy.file_scanner_classify_files")?;
391        assert_eq!(value.get_type(), "bool");
392        assert!(value.to_bool());
393
394        let value = env.eval(
395            "policy.file_scanner_classify_files = False; policy.file_scanner_classify_files",
396        )?;
397        assert_eq!(value.get_type(), "bool");
398        assert!(!value.to_bool());
399
400        let value = env.eval("policy.file_scanner_emit_files")?;
401        assert_eq!(value.get_type(), "bool");
402        assert!(!value.to_bool());
403
404        let value =
405            env.eval("policy.file_scanner_emit_files = True; policy.file_scanner_emit_files")?;
406        assert_eq!(value.get_type(), "bool");
407        assert!(value.to_bool());
408
409        let value = env.eval("policy.include_classified_resources")?;
410        assert_eq!(value.get_type(), "bool");
411        assert!(value.to_bool());
412
413        let value = env.eval(
414            "policy.include_classified_resources = False; policy.include_classified_resources",
415        )?;
416        assert_eq!(value.get_type(), "bool");
417        assert!(!value.to_bool());
418
419        let value = env.eval("policy.include_distribution_sources")?;
420        assert_eq!(value.get_type(), "bool");
421        assert_eq!(value.to_bool(), policy.include_distribution_sources());
422
423        let value = env.eval(
424            "policy.include_distribution_sources = False; policy.include_distribution_sources",
425        )?;
426        assert!(!value.to_bool());
427
428        let value = env.eval(
429            "policy.include_distribution_sources = True; policy.include_distribution_sources",
430        )?;
431        assert!(value.to_bool());
432
433        let value = env.eval("policy.include_distribution_resources")?;
434        assert_eq!(value.get_type(), "bool");
435        assert_eq!(value.to_bool(), policy.include_distribution_resources());
436
437        let value = env.eval(
438            "policy.include_distribution_resources = False; policy.include_distribution_resources",
439        )?;
440        assert!(!value.to_bool());
441
442        let value = env.eval(
443            "policy.include_distribution_resources = True; policy.include_distribution_resources",
444        )?;
445        assert!(value.to_bool());
446
447        let value = env.eval("policy.include_file_resources")?;
448        assert_eq!(value.get_type(), "bool");
449        assert!(!value.to_bool());
450
451        let value =
452            env.eval("policy.include_file_resources = True; policy.include_file_resources")?;
453        assert_eq!(value.get_type(), "bool");
454        assert!(value.to_bool());
455
456        let value = env.eval("policy.include_non_distribution_sources")?;
457        assert_eq!(value.get_type(), "bool");
458        assert_eq!(value.to_bool(), policy.include_non_distribution_sources());
459
460        let value = env.eval(
461            "policy.include_non_distribution_sources = False; policy.include_non_distribution_sources",
462        )?;
463        assert!(!value.to_bool());
464
465        let value = env.eval(
466            "policy.include_non_distribution_sources = True; policy.include_non_distribution_sources",
467        )?;
468        assert!(value.to_bool());
469
470        let value = env.eval("policy.include_test")?;
471        assert_eq!(value.get_type(), "bool");
472        assert_eq!(value.to_bool(), policy.include_test());
473
474        let value = env.eval("policy.include_test = False; policy.include_test")?;
475        assert!(!value.to_bool());
476
477        let value = env.eval("policy.include_test = True; policy.include_test")?;
478        assert!(value.to_bool());
479
480        let value = env.eval("policy.resources_location")?;
481        assert_eq!(value.get_type(), "string");
482        assert_eq!(value.to_string(), "in-memory");
483
484        let value = env.eval(
485            "policy.resources_location = 'filesystem-relative:lib'; policy.resources_location",
486        )?;
487        assert_eq!(value.to_string(), "filesystem-relative:lib");
488
489        let value = env.eval("policy.resources_location_fallback")?;
490        if dist_ref.supports_in_memory_shared_library_loading() {
491            assert_eq!(value.get_type(), "string");
492            assert_eq!(value.to_string(), "filesystem-relative:lib");
493        } else {
494            assert_eq!(value.get_type(), "NoneType");
495        }
496
497        let value = env.eval("policy.resources_location_fallback = 'filesystem-relative:lib'; policy.resources_location_fallback")?;
498        assert_eq!(value.to_string(), "filesystem-relative:lib");
499
500        let value = env.eval(
501            "policy.resources_location_fallback = None; policy.resources_location_fallback",
502        )?;
503        assert_eq!(value.get_type(), "NoneType");
504
505        let value = env.eval("policy.allow_files")?;
506        assert_eq!(value.get_type(), "bool");
507        assert!(!value.to_bool());
508
509        let value = env.eval("policy.allow_files = True; policy.allow_files")?;
510        assert_eq!(value.get_type(), "bool");
511        assert!(value.to_bool());
512
513        let value = env.eval("policy.allow_in_memory_shared_library_loading")?;
514        assert_eq!(value.get_type(), "bool");
515        assert!(!value.to_bool());
516
517        let value = env.eval("policy.allow_in_memory_shared_library_loading = True; policy.allow_in_memory_shared_library_loading")?;
518        assert_eq!(value.get_type(), "bool");
519        assert!(value.to_bool());
520
521        // bytecode_optimize_level_zero
522        let value = env.eval("policy.bytecode_optimize_level_zero")?;
523        assert_eq!(value.get_type(), "bool");
524        assert_eq!(value.to_bool(), policy.bytecode_optimize_level_zero());
525
526        let value = env.eval(
527            "policy.bytecode_optimize_level_zero = False; policy.bytecode_optimize_level_zero",
528        )?;
529        assert!(!value.to_bool());
530
531        let value = env.eval(
532            "policy.bytecode_optimize_level_zero = True; policy.bytecode_optimize_level_zero",
533        )?;
534        assert!(value.to_bool());
535
536        // bytecode_optimize_level_one
537        let value = env.eval("policy.bytecode_optimize_level_one")?;
538        assert_eq!(value.get_type(), "bool");
539        assert_eq!(value.to_bool(), policy.bytecode_optimize_level_one());
540
541        let value = env.eval(
542            "policy.bytecode_optimize_level_one = False; policy.bytecode_optimize_level_one",
543        )?;
544        assert!(!value.to_bool());
545
546        let value = env.eval(
547            "policy.bytecode_optimize_level_one = True; policy.bytecode_optimize_level_one",
548        )?;
549        assert!(value.to_bool());
550
551        // bytecode_optimize_level_two
552        let value = env.eval("policy.bytecode_optimize_level_two")?;
553        assert_eq!(value.get_type(), "bool");
554        assert_eq!(value.to_bool(), policy.bytecode_optimize_level_two());
555
556        let value = env.eval(
557            "policy.bytecode_optimize_level_two = False; policy.bytecode_optimize_level_two",
558        )?;
559        assert!(!value.to_bool());
560
561        let value = env.eval(
562            "policy.bytecode_optimize_level_two = True; policy.bytecode_optimize_level_two",
563        )?;
564        assert!(value.to_bool());
565
566        Ok(())
567    }
568
569    #[test]
570    fn test_preferred_extension_module_variants() -> Result<()> {
571        let mut env = test_evaluation_context_builder()?.into_context()?;
572
573        env.eval("dist = default_python_distribution()")?;
574        env.eval("policy = dist.make_python_packaging_policy()")?;
575
576        let value = env.eval("policy.preferred_extension_module_variants")?;
577        assert_eq!(value.get_type(), "dict");
578        assert_eq!(value.length().unwrap(), 0);
579
580        env.eval("policy.set_preferred_extension_module_variant('foo', 'bar')")?;
581
582        let value = env.eval("policy.preferred_extension_module_variants")?;
583        assert_eq!(value.get_type(), "dict");
584        assert_eq!(value.length().unwrap(), 1);
585        assert_eq!(value.at(Value::from("foo")).unwrap(), Value::from("bar"));
586
587        Ok(())
588    }
589
590    #[test]
591    fn test_register_resource_callback() -> Result<()> {
592        let mut env = test_evaluation_context_builder()?.into_context()?;
593
594        env.eval("dist = default_python_distribution()")?;
595        env.eval("policy = dist.make_python_packaging_policy()")?;
596        env.eval("def my_func(policy, resource):\n    return None")?;
597
598        env.eval("policy.register_resource_callback(my_func)")?;
599
600        let policy_value = env.eval("policy")?;
601        let policy = policy_value
602            .downcast_ref::<PythonPackagingPolicyValue>()
603            .unwrap();
604        assert_eq!(policy.derive_context_callbacks.len(), 1);
605
606        let func = policy.derive_context_callbacks[0].clone();
607        assert_eq!(func.get_type(), "function");
608        assert_eq!(func.to_str(), "my_func(policy, resource)");
609
610        Ok(())
611    }
612
613    #[test]
614    fn test_set_resource_handling_mode() -> Result<()> {
615        let mut env = test_evaluation_context_builder()?.into_context()?;
616
617        env.eval("dist = default_python_distribution()")?;
618        env.eval("policy = dist.make_python_packaging_policy()")?;
619
620        assert!(env
621            .eval("policy.set_resource_handling_mode('invalid')")
622            .is_err());
623
624        env.eval("policy.set_resource_handling_mode('classify')")?;
625        env.eval("policy.set_resource_handling_mode('files')")?;
626
627        Ok(())
628    }
629
630    #[test]
631    fn test_stdlib_extension_module_enable() -> Result<()> {
632        let mut env = test_evaluation_context_builder()?.into_context()?;
633
634        let exe_value = env.eval(indoc! {r#"
635            dist = default_python_distribution()
636            policy = dist.make_python_packaging_policy()
637            policy.extension_module_filter = "minimal"
638            policy.resources_location_fallback = "filesystem-relative:lib"
639
640            def cb(policy, resource):
641                if type(resource) == "PythonExtensionModule":
642                    if resource.name == "_ssl":
643                        resource.add_include = True
644
645            policy.register_resource_callback(cb)
646
647            exe = dist.to_python_executable(
648                name = "myapp",
649                packaging_policy = policy
650            )
651
652            exe
653        "#})?;
654
655        let exe = exe_value.downcast_ref::<PythonExecutableValue>().unwrap();
656        let inner = exe.inner("ignored").unwrap();
657
658        assert_eq!(
659            inner
660                .iter_resources()
661                .filter(|(_, r)| { r.is_extension_module && r.name == "_ssl" })
662                .count(),
663            // TODO arguably should be 1.
664            0,
665        );
666
667        Ok(())
668    }
669
670    #[test]
671    fn test_stdlib_extension_module_disable() -> Result<()> {
672        let mut env = test_evaluation_context_builder()?.into_context()?;
673
674        let exe_value = env.eval(indoc! {r#"
675            dist = default_python_distribution()
676            policy = dist.make_python_packaging_policy()
677            policy.resources_location_fallback = "filesystem-relative:lib"
678
679            def cb(policy, resource):
680                if type(resource) == "PythonExtensionModule":
681                    if resource.name == "_ssl":
682                        resource.add_include = False
683
684            policy.register_resource_callback(cb)
685
686            exe = dist.to_python_executable(
687                name = "myapp",
688                packaging_policy = policy
689            )
690
691            exe
692        "#})?;
693
694        let exe = exe_value.downcast_ref::<PythonExecutableValue>().unwrap();
695        let inner = exe.inner("ignored").unwrap();
696
697        assert_eq!(
698            inner
699                .iter_resources()
700                .filter(|(_, r)| { r.is_extension_module && r.name == "_ssl" })
701                .count(),
702            // TODO this seems buggy.
703            if cfg!(windows) { 1 } else { 0 }
704        );
705
706        Ok(())
707    }
708
709    #[test]
710    fn test_ignore_non_stdlib_extension_module() -> Result<()> {
711        let mut env = test_evaluation_context_builder()?.into_context()?;
712
713        let exe_value = env.eval(indoc! {r#"
714            dist = default_python_distribution()
715            policy = dist.make_python_packaging_policy()
716            policy.resources_location_fallback = "filesystem-relative:lib"
717
718            exe = dist.to_python_executable(
719                name = "myapp",
720                packaging_policy = policy
721            )
722
723            exe.add_python_resources(exe.pip_install(["zstandard==0.16.0"]))
724
725            exe
726        "#})?;
727
728        let exe = exe_value.downcast_ref::<PythonExecutableValue>().unwrap();
729        let inner = exe.inner("ignored").unwrap();
730
731        assert_eq!(
732            inner
733                .iter_resources()
734                .filter(|(_, r)| { r.is_extension_module && r.name == "zstandard.backend_c" })
735                .count(),
736            1
737        );
738
739        let exe_value = env.eval(indoc! {r#"
740            dist = default_python_distribution()
741            policy = dist.make_python_packaging_policy()
742            policy.resources_location_fallback = "filesystem-relative:lib"
743
744            def cb(policy, resource):
745                if type(resource) == "PythonExtensionModule":
746                    if resource.name == "zstandard.backend_c":
747                        resource.add_include = False
748
749            policy.register_resource_callback(cb)
750
751            exe = dist.to_python_executable(
752                name = "myapp",
753                packaging_policy = policy,
754            )
755
756            exe.add_python_resources(exe.pip_install(["zstandard==0.16.0"]))
757
758            exe
759        "#})?;
760
761        let exe = exe_value.downcast_ref::<PythonExecutableValue>().unwrap();
762        let inner = exe.inner("ignored").unwrap();
763
764        assert_eq!(
765            inner
766                .iter_resources()
767                .filter(|(_, r)| { r.is_extension_module && r.name == "zstandard.backend_c" })
768                .count(),
769            // TODO should be 0.
770            1
771        );
772
773        Ok(())
774    }
775}