use {
crate::{
licensing::SAFE_SYSTEM_LIBRARIES,
location::ConcreteResourceLocation,
resource::{PythonExtensionModule, PythonExtensionModuleVariants, PythonResource},
resource_collection::PythonResourceAddCollectionContext,
},
anyhow::Result,
std::{collections::HashMap, convert::TryFrom},
tugger_licensing::LicenseFlavor,
};
#[derive(Clone, Debug, PartialEq)]
pub enum ExtensionModuleFilter {
Minimal,
All,
NoLibraries,
NoCopyleft,
}
impl TryFrom<&str> for ExtensionModuleFilter {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"minimal" => Ok(ExtensionModuleFilter::Minimal),
"all" => Ok(ExtensionModuleFilter::All),
"no-libraries" => Ok(ExtensionModuleFilter::NoLibraries),
"no-copyleft" => Ok(ExtensionModuleFilter::NoCopyleft),
t => Err(format!("{} is not a valid extension module filter", t)),
}
}
}
impl AsRef<str> for ExtensionModuleFilter {
fn as_ref(&self) -> &str {
match self {
ExtensionModuleFilter::All => "all",
ExtensionModuleFilter::Minimal => "minimal",
ExtensionModuleFilter::NoCopyleft => "no-copyleft",
ExtensionModuleFilter::NoLibraries => "no-libraries",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ResourceHandlingMode {
Classify,
Files,
}
impl TryFrom<&str> for ResourceHandlingMode {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"classify" => Ok(Self::Classify),
"files" => Ok(Self::Files),
_ => Err(format!(
"{} is not a valid resource handling mode; use \"classify\" or \"files\"",
value
)),
}
}
}
impl AsRef<str> for ResourceHandlingMode {
fn as_ref(&self) -> &str {
match self {
Self::Classify => "classify",
Self::Files => "files",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PythonPackagingPolicy {
extension_module_filter: ExtensionModuleFilter,
preferred_extension_module_variants: HashMap<String, String>,
resources_location: ConcreteResourceLocation,
resources_location_fallback: Option<ConcreteResourceLocation>,
allow_in_memory_shared_library_loading: bool,
allow_files: bool,
file_scanner_emit_files: bool,
file_scanner_classify_files: bool,
include_classified_resources: bool,
include_distribution_sources: bool,
include_non_distribution_sources: bool,
include_distribution_resources: bool,
include_test: bool,
include_file_resources: bool,
broken_extensions: HashMap<String, Vec<String>>,
bytecode_optimize_level_zero: bool,
bytecode_optimize_level_one: bool,
bytecode_optimize_level_two: bool,
}
impl Default for PythonPackagingPolicy {
fn default() -> Self {
PythonPackagingPolicy {
extension_module_filter: ExtensionModuleFilter::All,
preferred_extension_module_variants: HashMap::new(),
resources_location: ConcreteResourceLocation::InMemory,
resources_location_fallback: None,
allow_in_memory_shared_library_loading: false,
allow_files: false,
file_scanner_emit_files: false,
file_scanner_classify_files: true,
include_classified_resources: true,
include_distribution_sources: true,
include_non_distribution_sources: true,
include_distribution_resources: false,
include_test: false,
include_file_resources: false,
broken_extensions: HashMap::new(),
bytecode_optimize_level_zero: true,
bytecode_optimize_level_one: false,
bytecode_optimize_level_two: false,
}
}
}
impl PythonPackagingPolicy {
pub fn extension_module_filter(&self) -> &ExtensionModuleFilter {
&self.extension_module_filter
}
pub fn set_extension_module_filter(&mut self, filter: ExtensionModuleFilter) {
self.extension_module_filter = filter;
}
pub fn preferred_extension_module_variants(&self) -> &HashMap<String, String> {
&self.preferred_extension_module_variants
}
pub fn set_preferred_extension_module_variant(&mut self, extension: &str, variant: &str) {
self.preferred_extension_module_variants
.insert(extension.to_string(), variant.to_string());
}
pub fn resources_location(&self) -> &ConcreteResourceLocation {
&self.resources_location
}
pub fn set_resources_location(&mut self, location: ConcreteResourceLocation) {
self.resources_location = location;
}
pub fn resources_location_fallback(&self) -> &Option<ConcreteResourceLocation> {
&self.resources_location_fallback
}
pub fn set_resources_location_fallback(&mut self, location: Option<ConcreteResourceLocation>) {
self.resources_location_fallback = location;
}
pub fn allow_files(&self) -> bool {
self.allow_files
}
pub fn set_allow_files(&mut self, value: bool) {
self.allow_files = value;
}
pub fn file_scanner_emit_files(&self) -> bool {
self.file_scanner_emit_files
}
pub fn set_file_scanner_emit_files(&mut self, value: bool) {
self.file_scanner_emit_files = value;
}
pub fn file_scanner_classify_files(&self) -> bool {
self.file_scanner_classify_files
}
pub fn set_file_scanner_classify_files(&mut self, value: bool) {
self.file_scanner_classify_files = value;
}
pub fn allow_in_memory_shared_library_loading(&self) -> bool {
self.allow_in_memory_shared_library_loading
}
pub fn set_allow_in_memory_shared_library_loading(&mut self, value: bool) {
self.allow_in_memory_shared_library_loading = value;
}
pub fn include_distribution_sources(&self) -> bool {
self.include_distribution_sources
}
pub fn set_include_distribution_sources(&mut self, include: bool) {
self.include_distribution_sources = include;
}
pub fn include_distribution_resources(&self) -> bool {
self.include_distribution_resources
}
pub fn set_include_distribution_resources(&mut self, include: bool) {
self.include_distribution_resources = include;
}
pub fn include_non_distribution_sources(&self) -> bool {
self.include_non_distribution_sources
}
pub fn set_include_non_distribution_sources(&mut self, include: bool) {
self.include_non_distribution_sources = include;
}
pub fn include_test(&self) -> bool {
self.include_test
}
pub fn set_include_test(&mut self, include: bool) {
self.include_test = include;
}
pub fn include_file_resources(&self) -> bool {
self.include_file_resources
}
pub fn set_include_file_resources(&mut self, value: bool) {
self.include_file_resources = value;
}
pub fn include_classified_resources(&self) -> bool {
self.include_classified_resources
}
pub fn set_include_classified_resources(&mut self, value: bool) {
self.include_classified_resources = value;
}
pub fn bytecode_optimize_level_zero(&self) -> bool {
self.bytecode_optimize_level_zero
}
pub fn set_bytecode_optimize_level_zero(&mut self, value: bool) {
self.bytecode_optimize_level_zero = value;
}
pub fn bytecode_optimize_level_one(&self) -> bool {
self.bytecode_optimize_level_one
}
pub fn set_bytecode_optimize_level_one(&mut self, value: bool) {
self.bytecode_optimize_level_one = value;
}
pub fn bytecode_optimize_level_two(&self) -> bool {
self.bytecode_optimize_level_two
}
pub fn set_bytecode_optimize_level_two(&mut self, value: bool) {
self.bytecode_optimize_level_two = value;
}
pub fn set_resource_handling_mode(&mut self, mode: ResourceHandlingMode) {
match mode {
ResourceHandlingMode::Classify => {
self.file_scanner_emit_files = false;
self.file_scanner_classify_files = true;
self.allow_files = false;
self.include_file_resources = false;
self.include_classified_resources = true;
}
ResourceHandlingMode::Files => {
self.file_scanner_emit_files = true;
self.file_scanner_classify_files = false;
self.allow_files = true;
self.include_file_resources = true;
self.include_classified_resources = true;
}
}
}
pub fn broken_extensions_for_triple(&self, target_triple: &str) -> Option<&Vec<String>> {
self.broken_extensions.get(target_triple)
}
pub fn register_broken_extension(&mut self, target_triple: &str, extension: &str) {
if !self.broken_extensions.contains_key(target_triple) {
self.broken_extensions
.insert(target_triple.to_string(), vec![]);
}
self.broken_extensions
.get_mut(target_triple)
.unwrap()
.push(extension.to_string());
}
pub fn derive_add_collection_context(
&self,
resource: &PythonResource,
) -> PythonResourceAddCollectionContext {
let include = self.filter_python_resource(resource);
let store_source = match resource {
PythonResource::ModuleSource(ref module) => {
if module.is_stdlib {
self.include_distribution_sources
} else {
self.include_non_distribution_sources
}
}
_ => false,
};
let location = self.resources_location.clone();
let location_fallback = self.resources_location_fallback.clone();
PythonResourceAddCollectionContext {
include,
location,
location_fallback,
store_source,
optimize_level_zero: self.bytecode_optimize_level_zero,
optimize_level_one: self.bytecode_optimize_level_one,
optimize_level_two: self.bytecode_optimize_level_two,
}
}
fn filter_python_resource(&self, resource: &PythonResource) -> bool {
match resource {
PythonResource::File(_) => {
if !self.include_file_resources {
return false;
}
}
_ => {
if !self.include_classified_resources {
return false;
}
}
}
match resource {
PythonResource::ModuleSource(module) => {
if !self.include_test && module.is_test {
false
} else {
self.include_distribution_sources
}
}
PythonResource::ModuleBytecodeRequest(module) => self.include_test || !module.is_test,
PythonResource::ModuleBytecode(_) => false,
PythonResource::PackageResource(resource) => {
if resource.is_stdlib {
if self.include_distribution_resources {
self.include_test || !resource.is_test
} else {
false
}
} else {
true
}
}
PythonResource::PackageDistributionResource(_) => true,
PythonResource::ExtensionModule(_) => false,
PythonResource::PathExtension(_) => false,
PythonResource::EggFile(_) => false,
PythonResource::File(_) => true,
}
}
#[allow(clippy::if_same_then_else)]
pub fn resolve_python_extension_modules<'a>(
&self,
extensions_variants: impl Iterator<Item = &'a PythonExtensionModuleVariants>,
target_triple: &str,
) -> Result<Vec<PythonExtensionModule>> {
let mut res = vec![];
for variants in extensions_variants {
let name = &variants.default_variant().name;
if self
.broken_extensions
.get(target_triple)
.unwrap_or(&Vec::new())
.contains(name)
{
continue;
}
let ext_variants: PythonExtensionModuleVariants = variants
.iter()
.filter_map(|em| {
if em.is_minimally_required() {
Some(em.clone())
} else {
None
}
})
.collect();
if !ext_variants.is_empty() {
res.push(
ext_variants
.choose_variant(&self.preferred_extension_module_variants)
.clone(),
);
}
match self.extension_module_filter {
ExtensionModuleFilter::Minimal => {}
ExtensionModuleFilter::All => {
res.push(
variants
.choose_variant(&self.preferred_extension_module_variants)
.clone(),
);
}
ExtensionModuleFilter::NoLibraries => {
let ext_variants: PythonExtensionModuleVariants = variants
.iter()
.filter_map(|em| {
if !em.requires_libraries() {
Some(em.clone())
} else {
None
}
})
.collect();
if !ext_variants.is_empty() {
res.push(
ext_variants
.choose_variant(&self.preferred_extension_module_variants)
.clone(),
);
}
}
ExtensionModuleFilter::NoCopyleft => {
let ext_variants: PythonExtensionModuleVariants = variants
.iter()
.filter_map(|em| {
let all_safe_system_libraries = em.link_libraries.iter().all(|link| {
link.system && SAFE_SYSTEM_LIBRARIES.contains(&link.name.as_str())
});
if em.link_libraries.is_empty() || all_safe_system_libraries {
Some(em.clone())
} else if let Some(license) = &em.license {
match license.license() {
LicenseFlavor::Spdx(expression) => {
let copyleft = expression.evaluate(|req| {
if let Some(id) = req.license.id() {
id.is_copyleft()
} else {
true
}
});
if !copyleft {
Some(em.clone())
} else {
None
}
}
LicenseFlavor::OtherExpression(_) => None,
LicenseFlavor::PublicDomain => Some(em.clone()),
LicenseFlavor::None => None,
LicenseFlavor::Unknown(_) => None,
}
} else {
None
}
})
.collect();
if !ext_variants.is_empty() {
res.push(
ext_variants
.choose_variant(&self.preferred_extension_module_variants)
.clone(),
);
}
}
}
}
Ok(res)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
std::path::PathBuf,
tugger_file_manifest::{File, FileEntry},
};
#[test]
fn test_add_collection_context_file() -> Result<()> {
let mut policy = PythonPackagingPolicy::default();
policy.include_file_resources = false;
let file = File {
path: PathBuf::from("foo.py"),
entry: FileEntry {
executable: false,
data: vec![42].into(),
},
};
let add_context = policy.derive_add_collection_context(&file.clone().into());
assert!(!add_context.include);
policy.include_file_resources = true;
let add_context = policy.derive_add_collection_context(&file.into());
assert!(add_context.include);
Ok(())
}
}