use {
super::{
env::{get_context, PyOxidizerEnvironmentContext},
file::FileValue,
file_resource::file_manifest_add_python_executable,
python_embedded_resources::PythonEmbeddedResourcesValue,
python_extension_module::PythonExtensionModuleValue,
python_module_source::PythonModuleSourceValue,
python_package_distribution_resource::PythonPackageDistributionResourceValue,
python_package_resource::PythonPackageResourceValue,
python_packaging_policy::PythonPackagingPolicyValue,
python_resource::{is_resource_starlark_compatible, python_resource_to_value},
util::ToValue,
},
crate::{
licensing::licenses_from_cargo_manifest,
project_building::build_python_executable,
py_packaging::binary::PythonBinaryBuilder,
py_packaging::binary::{PackedResourcesLoadMode, WindowsRuntimeDllsMode},
},
anyhow::{anyhow, Context, Result},
linked_hash_map::LinkedHashMap,
log::{info, warn},
python_packaging::resource::PythonModuleSource,
simple_file_manifest::FileData,
starlark::{
environment::TypeValues,
eval::call_stack::CallStack,
values::{
error::{
RuntimeError, UnsupportedOperation, ValueError, INCORRECT_PARAMETER_TYPE_ERROR_CODE,
},
none::NoneType,
{Mutable, TypedValue, Value, ValueResult},
},
{
starlark_fun, starlark_module, starlark_parse_param_type, starlark_signature,
starlark_signature_extraction, starlark_signatures,
},
},
starlark_dialect_build_targets::{
optional_dict_arg, optional_list_arg, optional_str_arg, optional_type_arg,
required_list_arg, ResolvedTarget, ResolvedTargetValue, RunMode, ToOptional,
},
std::{
collections::HashMap,
io::Write,
ops::Deref,
path::{Path, PathBuf},
sync::{Arc, Mutex, MutexGuard},
},
tugger::starlark::{
code_signing::{handle_signable_event, SigningAction, SigningContext},
file_manifest::FileManifestValue,
wix_bundle_builder::WiXBundleBuilderValue,
wix_msi_builder::WiXMsiBuilderValue,
},
tugger_code_signing::SigningDestination,
tugger_wix::target_triple_to_wix_arch,
};
fn error_context<F, T>(label: &str, f: F) -> Result<T, ValueError>
where
F: FnOnce() -> anyhow::Result<T>,
{
f().map_err(|e| {
ValueError::Runtime(RuntimeError {
code: "PYOXIDIZER_PYTHON_EXECUTABLE",
message: format!("{:?}", e),
label: label.to_string(),
})
})
}
pub fn build_internal(
exe: MutexGuard<Box<dyn PythonBinaryBuilder>>,
type_values: &TypeValues,
target: &str,
context: &PyOxidizerEnvironmentContext,
) -> Result<(ResolvedTarget, PathBuf)> {
let build = build_python_executable(
context.env(),
&exe.name(),
&**exe,
&context.build_target_triple,
&context.build_opt_level,
context.build_release,
)
.context("building Python executable")?;
let output_path = context
.get_output_path(type_values, target)
.map_err(|_| anyhow!("unable to resolve output path"))?;
std::fs::create_dir_all(&output_path)
.with_context(|| format!("creating output directory {}", output_path.display()))?;
let dest_path = output_path.join(build.exe_name);
warn!("writing executable to {}", dest_path.display());
let mut fh =
std::fs::File::create(&dest_path).context(format!("creating {}", dest_path.display()))?;
fh.write_all(&build.exe_data)
.context(format!("writing {}", dest_path.display()))?;
simple_file_manifest::set_executable(&mut fh).context("making binary executable")?;
Ok((
ResolvedTarget {
run_mode: RunMode::Path {
path: dest_path.clone(),
},
output_path,
},
dest_path,
))
}
pub struct PythonExecutableValue {
pub exe: Arc<Mutex<Box<dyn PythonBinaryBuilder>>>,
policy: Vec<Value>,
}
impl PythonExecutableValue {
pub fn new(exe: Box<dyn PythonBinaryBuilder>, policy: PythonPackagingPolicyValue) -> Self {
Self {
exe: Arc::new(Mutex::new(exe)),
policy: vec![Value::new(policy)],
}
}
pub fn inner(
&self,
label: &str,
) -> Result<MutexGuard<Box<dyn PythonBinaryBuilder>>, ValueError> {
self.exe.try_lock().map_err(|e| {
ValueError::Runtime(RuntimeError {
code: "PYTHON_EXECUTABLE",
message: format!("failed to acquire lock: {}", e),
label: label.to_string(),
})
})
}
pub fn python_packaging_policy(&self) -> PythonPackagingPolicyValue {
self.policy[0]
.downcast_ref::<PythonPackagingPolicyValue>()
.unwrap()
.clone()
}
}
impl TypedValue for PythonExecutableValue {
type Holder = Mutable<PythonExecutableValue>;
const TYPE: &'static str = "PythonExecutable";
fn values_for_descendant_check_and_freeze<'a>(
&'a self,
) -> Box<dyn Iterator<Item = Value> + 'a> {
Box::new(self.policy.iter().cloned())
}
fn get_attr(&self, attribute: &str) -> ValueResult {
let exe = self.inner(&format!("PythonExecutable.{}", attribute))?;
match attribute {
"licenses_filename" => Ok(exe.licenses_filename().to_value()),
"packed_resources_load_mode" => {
Ok(Value::from(exe.packed_resources_load_mode().to_string()))
}
"tcl_files_path" => match exe.tcl_files_path() {
Some(value) => Ok(Value::from(value.to_string())),
None => Ok(Value::from(NoneType::None)),
},
"windows_runtime_dlls_mode" => {
Ok(Value::from(exe.windows_runtime_dlls_mode().to_string()))
}
"windows_subsystem" => Ok(Value::from(exe.windows_subsystem())),
_ => Err(ValueError::OperationNotSupported {
op: UnsupportedOperation::GetAttr(attribute.to_string()),
left: Self::TYPE.to_string(),
right: None,
}),
}
}
fn has_attr(&self, attribute: &str) -> Result<bool, ValueError> {
Ok(matches!(
attribute,
"licenses_filename"
| "packed_resources_load_mode"
| "tcl_files_path"
| "windows_runtime_dlls_mode"
| "windows_subsystem"
))
}
fn set_attr(&mut self, attribute: &str, value: Value) -> Result<(), ValueError> {
let mut exe = self.inner(&format!("PythonExecutable.{}", attribute))?;
match attribute {
"licenses_filename" => {
let value = optional_str_arg("licenses_filename", &value)?;
exe.set_licenses_filename(value);
Ok(())
}
"packed_resources_load_mode" => {
exe.set_packed_resources_load_mode(
PackedResourcesLoadMode::try_from(value.to_string().as_str()).map_err(|e| {
ValueError::from(RuntimeError {
code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
message: e,
label: format!("{}.{}", Self::TYPE, attribute),
})
})?,
);
Ok(())
}
"tcl_files_path" => {
exe.set_tcl_files_path(value.to_optional());
Ok(())
}
"windows_runtime_dlls_mode" => {
exe.set_windows_runtime_dlls_mode(
WindowsRuntimeDllsMode::try_from(value.to_string().as_str()).map_err(|e| {
ValueError::from(RuntimeError {
code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
message: e,
label: format!("{}.{}", Self::TYPE, attribute),
})
})?,
);
Ok(())
}
"windows_subsystem" => {
exe.set_windows_subsystem(value.to_string().as_str())
.map_err(|e| {
ValueError::from(RuntimeError {
code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
message: format!("{:?}", e),
label: format!("{}.{}", Self::TYPE, attribute),
})
})?;
Ok(())
}
_ => Err(ValueError::OperationNotSupported {
op: UnsupportedOperation::SetAttr(attribute.to_string()),
left: Self::TYPE.to_string(),
right: None,
}),
}
}
}
impl PythonExecutableValue {
fn build(
&self,
type_values: &TypeValues,
call_stack: &mut CallStack,
target: String,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.build()";
let pyoxidizer_context_value = get_context(type_values)?;
let pyoxidizer_context = pyoxidizer_context_value
.downcast_ref::<PyOxidizerEnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let exe = self.inner(LABEL)?;
let (inner, exe_path) = error_context(LABEL, || {
build_internal(exe, type_values, &target, &pyoxidizer_context)
})?;
let candidate = exe_path.clone().into();
let mut context = SigningContext::new(
"PythonExecutable.build()",
SigningAction::Other("python-executable-creation"),
exe_path.file_name().ok_or_else(|| {
ValueError::Runtime(RuntimeError {
code: "PYTHON_EXECUTABLE",
message: "could not determine executable filename (this should not happen)"
.to_string(),
label: LABEL.to_string(),
})
})?,
&candidate,
);
context.set_path(&exe_path);
context.set_signing_destination(SigningDestination::File(exe_path.clone()));
handle_signable_event(type_values, call_stack, context)?;
Ok(Value::new(ResolvedTargetValue { inner }))
}
pub fn make_python_module_source(
&self,
type_values: &TypeValues,
call_stack: &mut CallStack,
name: String,
source: String,
is_package: bool,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.make_python_module_source()";
let module = PythonModuleSource {
name,
source: FileData::Memory(source.into_bytes()),
is_package,
cache_tag: self.inner(LABEL)?.cache_tag().to_string(),
is_stdlib: false,
is_test: false,
};
let mut value = PythonModuleSourceValue::new(module);
self.python_packaging_policy().apply_to_resource(
LABEL,
type_values,
call_stack,
&mut value,
)?;
Ok(Value::new(value))
}
pub fn pip_download(
&mut self,
type_values: &TypeValues,
call_stack: &mut CallStack,
args: &Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.pip_download()";
required_list_arg("args", "string", args)?;
let args: Vec<String> = args.iter()?.iter().map(|x| x.to_string()).collect();
let pyoxidizer_context_value = get_context(type_values)?;
let pyoxidizer_context = pyoxidizer_context_value
.downcast_ref::<PyOxidizerEnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let python_packaging_policy = self.python_packaging_policy();
let mut exe = self.inner(LABEL)?;
let resources = error_context("PythonExecutable.pip_download()", || {
exe.pip_download(pyoxidizer_context.env(), pyoxidizer_context.verbose, &args)
})?;
let resources = resources
.iter()
.filter(|r| is_resource_starlark_compatible(r))
.map(|r| {
python_resource_to_value(
LABEL,
type_values,
call_stack,
r,
&python_packaging_policy,
)
})
.collect::<Result<Vec<Value>, ValueError>>()?;
Ok(Value::from(resources))
}
pub fn pip_install(
&mut self,
type_values: &TypeValues,
call_stack: &mut CallStack,
args: &Value,
extra_envs: &Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.pip_install()";
required_list_arg("args", "string", args)?;
optional_dict_arg("extra_envs", "string", "string", extra_envs)?;
let args: Vec<String> = args.iter()?.iter().map(|x| x.to_string()).collect();
let extra_envs = match extra_envs.get_type() {
"dict" => extra_envs
.iter()?
.iter()
.map(|key| {
let k = key.to_string();
let v = extra_envs.at(key).unwrap().to_string();
(k, v)
})
.collect(),
"NoneType" => HashMap::new(),
_ => panic!("should have validated type above"),
};
let pyoxidizer_context_value = get_context(type_values)?;
let pyoxidizer_context = pyoxidizer_context_value
.downcast_ref::<PyOxidizerEnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let python_packaging_policy = self.python_packaging_policy();
let mut exe = self.inner(LABEL)?;
let resources = error_context(LABEL, || {
exe.pip_install(
pyoxidizer_context.env(),
pyoxidizer_context.verbose,
&args,
&extra_envs,
)
})?;
let resources = resources
.iter()
.filter(|r| is_resource_starlark_compatible(r))
.map(|r| {
python_resource_to_value(
LABEL,
type_values,
call_stack,
r,
&python_packaging_policy,
)
})
.collect::<Result<Vec<Value>, ValueError>>()?;
Ok(Value::from(resources))
}
pub fn read_package_root(
&mut self,
type_values: &TypeValues,
call_stack: &mut CallStack,
path: String,
packages: &Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.read_package_root()";
required_list_arg("packages", "string", packages)?;
let packages = packages
.iter()?
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>();
let python_packaging_policy = self.python_packaging_policy();
let mut exe = self.inner(LABEL)?;
let resources =
error_context(LABEL, || exe.read_package_root(Path::new(&path), &packages))?;
let resources = resources
.iter()
.filter(|r| is_resource_starlark_compatible(r))
.map(|r| {
python_resource_to_value(
LABEL,
type_values,
call_stack,
r,
&python_packaging_policy,
)
})
.collect::<Result<Vec<Value>, ValueError>>()?;
Ok(Value::from(resources))
}
pub fn read_virtualenv(
&mut self,
type_values: &TypeValues,
call_stack: &mut CallStack,
path: String,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.read_virtualenv()";
let python_packaging_policy = self.python_packaging_policy();
let mut exe = self.inner(LABEL)?;
let resources = error_context(LABEL, || exe.read_virtualenv(Path::new(&path)))?;
let resources = resources
.iter()
.filter(|r| is_resource_starlark_compatible(r))
.map(|r| {
python_resource_to_value(
LABEL,
type_values,
call_stack,
r,
&python_packaging_policy,
)
})
.collect::<Result<Vec<Value>, ValueError>>()?;
Ok(Value::from(resources))
}
pub fn setup_py_install(
&mut self,
type_values: &TypeValues,
call_stack: &mut CallStack,
package_path: String,
extra_envs: &Value,
extra_global_arguments: &Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.setup_py_install()";
optional_dict_arg("extra_envs", "string", "string", extra_envs)?;
optional_list_arg("extra_global_arguments", "string", extra_global_arguments)?;
let extra_envs = match extra_envs.get_type() {
"dict" => extra_envs
.iter()?
.iter()
.map(|key| {
let k = key.to_string();
let v = extra_envs.at(key).unwrap().to_string();
(k, v)
})
.collect(),
"NoneType" => HashMap::new(),
_ => panic!("should have validated type above"),
};
let extra_global_arguments = match extra_global_arguments.get_type() {
"list" => extra_global_arguments
.iter()?
.iter()
.map(|x| x.to_string())
.collect(),
"NoneType" => Vec::new(),
_ => panic!("should have validated type above"),
};
let package_path = PathBuf::from(package_path);
let pyoxidizer_context_value = get_context(type_values)?;
let pyoxidizer_context = pyoxidizer_context_value
.downcast_ref::<PyOxidizerEnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let package_path = if package_path.is_absolute() {
package_path
} else {
PathBuf::from(&pyoxidizer_context.cwd).join(package_path)
};
let python_packaging_policy = self.python_packaging_policy();
let mut exe = self.inner(LABEL)?;
let resources = error_context(LABEL, || {
exe.setup_py_install(
pyoxidizer_context.env(),
&package_path,
pyoxidizer_context.verbose,
&extra_envs,
&extra_global_arguments,
)
})?;
let resources = resources
.iter()
.filter(|r| is_resource_starlark_compatible(r))
.map(|r| {
python_resource_to_value(
LABEL,
type_values,
call_stack,
r,
&python_packaging_policy,
)
})
.collect::<Result<Vec<Value>, ValueError>>()?;
warn!(
"collected {} resources from setup.py install",
resources.len()
);
Ok(Value::from(resources))
}
pub fn add_python_module_source(
&mut self,
label: &str,
module: &PythonModuleSourceValue,
) -> ValueResult {
let inner = module.inner(label)?;
let mut exe = self.inner(label)?;
error_context(label, || {
for action in exe
.add_python_module_source(&inner.m, inner.add_context.clone())
.with_context(|| format!("adding {}", module.to_repr()))?
{
info!("{}", action.to_string());
}
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_python_package_resource(
&mut self,
label: &str,
resource: &PythonPackageResourceValue,
) -> ValueResult {
let inner = resource.inner(label)?;
let mut exe = self.inner(label)?;
error_context(label, || {
for action in exe
.add_python_package_resource(&inner.r, inner.add_context.clone())
.with_context(|| format!("adding {}", resource.to_repr()))?
{
info!("{}", action.to_string());
}
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_python_package_distribution_resource(
&mut self,
label: &str,
resource: &PythonPackageDistributionResourceValue,
) -> ValueResult {
let inner = resource.inner(label)?;
let mut exe = self.inner(label)?;
error_context(label, || {
for action in exe
.add_python_package_distribution_resource(&inner.r, inner.add_context.clone())
.with_context(|| format!("adding {}", resource.to_repr()))?
{
info!("{}", action.to_string());
}
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_python_extension_module(
&mut self,
label: &str,
module: &PythonExtensionModuleValue,
) -> ValueResult {
let inner = module.inner(label)?;
let mut exe = self.inner(label)?;
error_context(label, || {
for action in exe
.add_python_extension_module(&inner.em, inner.add_context.clone())
.with_context(|| format!("adding {}", module.to_repr()))?
{
info!("{}", action.to_string());
}
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_file_data(&mut self, label: &str, file: &FileValue) -> ValueResult {
let inner = file.inner(label)?;
let mut exe = self.inner(label)?;
error_context(label, || {
for action in exe
.add_file_data(&inner.file, inner.add_context.clone())
.with_context(|| format!("adding {}", file.to_repr()))?
{
info!("{}", action.to_string());
}
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_python_resource(&mut self, resource: &Value, label: &str) -> ValueResult {
match resource.get_type() {
FileValue::TYPE => {
let file = resource.downcast_ref::<FileValue>().unwrap();
self.add_file_data(label, file.deref())
}
PythonModuleSourceValue::TYPE => {
let module = resource.downcast_ref::<PythonModuleSourceValue>().unwrap();
self.add_python_module_source(label, module.deref())
}
PythonPackageResourceValue::TYPE => {
let r = resource
.downcast_ref::<PythonPackageResourceValue>()
.unwrap();
self.add_python_package_resource(label, r.deref())
}
PythonPackageDistributionResourceValue::TYPE => {
let r = resource
.downcast_ref::<PythonPackageDistributionResourceValue>()
.unwrap();
self.add_python_package_distribution_resource(label, r.deref())
}
PythonExtensionModuleValue::TYPE => {
let module = resource
.downcast_ref::<PythonExtensionModuleValue>()
.unwrap();
self.add_python_extension_module(label, module.deref())
}
_ => Err(ValueError::from(RuntimeError {
code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
message: "resource argument must be a Python resource type".to_string(),
label: ".add_python_resource()".to_string(),
})),
}
}
pub fn add_python_resources(&mut self, resources: &Value) -> ValueResult {
for resource in &resources.iter()? {
self.add_python_resource(&resource, "add_python_resources()")?;
}
Ok(Value::new(NoneType::None))
}
pub fn add_cargo_manifest_licensing(
&mut self,
type_values: &TypeValues,
manifest_path: &str,
all_features: bool,
features: &Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.add_cargo_manifest_licensing()";
optional_list_arg("features", "string", features)?;
let features = match features.get_type() {
"list" => features.iter()?.iter().map(|x| x.to_string()).collect(),
"NoneType" => Vec::new(),
_ => panic!("type should have been validated above"),
};
let pyoxidizer_context_value = get_context(type_values)?;
let pyoxidizer_context = pyoxidizer_context_value
.downcast_ref::<PyOxidizerEnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let mut exe = self.inner(LABEL)?;
error_context(LABEL, || {
let toolchain = pyoxidizer_context.env().ensure_rust_toolchain(None)?;
let components = licenses_from_cargo_manifest(
manifest_path,
all_features,
features.iter().map(|x| x.as_str()),
Some(exe.target_triple()),
&toolchain,
true,
)?;
for component in components.into_components() {
warn!("adding licensed component {}", component.flavor());
exe.add_licensed_component(component)?;
}
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn to_embedded_resources(&self) -> ValueResult {
const LABEL: &str = "PythonExecutable.to_embedded_resources()";
Ok(Value::new(PythonEmbeddedResourcesValue {
exe: self.inner(LABEL)?.clone_trait(),
}))
}
pub fn to_file_manifest(&self, type_values: &TypeValues, prefix: String) -> ValueResult {
const LABEL: &str = "PythonExecutable.to_file_manifest()";
let pyoxidizer_context_value = get_context(type_values)?;
let pyoxidizer_context = pyoxidizer_context_value
.downcast_ref::<PyOxidizerEnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let manifest_value = FileManifestValue::new_from_args()?;
let mut manifest = manifest_value
.downcast_mut::<FileManifestValue>()
.unwrap()
.unwrap();
let exe = self.inner(LABEL)?;
error_context(LABEL, || {
file_manifest_add_python_executable(
&mut manifest,
pyoxidizer_context.env(),
&prefix,
&**exe,
&pyoxidizer_context.build_target_triple,
pyoxidizer_context.build_release,
&pyoxidizer_context.build_opt_level,
)
.context("adding PythonExecutable to FileManifest")
})?;
Ok(manifest_value.clone())
}
#[allow(clippy::too_many_arguments)]
pub fn to_wix_bundle_builder(
&self,
type_values: &TypeValues,
call_stack: &mut CallStack,
id_prefix: String,
product_name: String,
product_version: String,
product_manufacturer: String,
msi_builder_callback: Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.to_wix_bundle_builder()";
optional_type_arg("msi_builder_callback", "function", &msi_builder_callback)?;
let msi_builder_value = self.to_wix_msi_builder(
type_values,
call_stack,
id_prefix.clone(),
product_name.clone(),
product_version.clone(),
product_manufacturer.clone(),
)?;
if msi_builder_callback.get_type() == "function" {
msi_builder_callback.call(
call_stack,
type_values,
vec![msi_builder_value.clone()],
LinkedHashMap::new(),
None,
None,
)?;
}
let msi_builder = msi_builder_value
.downcast_ref::<WiXMsiBuilderValue>()
.unwrap();
let arch = target_triple_to_wix_arch(self.inner(LABEL)?.target_triple()).unwrap_or("x64");
let bundle_builder_value = WiXBundleBuilderValue::new_from_args(
id_prefix,
product_name,
product_version,
product_manufacturer,
arch.to_string(),
)?;
let mut bundle_builder = bundle_builder_value
.downcast_mut::<WiXBundleBuilderValue>()
.unwrap()
.unwrap();
match self.inner(LABEL)?.target_triple() {
"i686-pc-windows-msvc" => {
bundle_builder.add_vc_redistributable(type_values, "x86".to_string())?;
}
"x86_64-pc-windows-msvc" => {
bundle_builder.add_vc_redistributable(type_values, "x64".to_string())?;
}
_ => {}
}
bundle_builder.add_wix_msi_builder(
msi_builder.deref().clone(),
false,
Value::new(NoneType::None),
)?;
Ok(bundle_builder_value.clone())
}
pub fn to_wix_msi_builder(
&self,
type_values: &TypeValues,
call_stack: &mut CallStack,
id_prefix: String,
product_name: String,
product_version: String,
product_manufacturer: String,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.to_wix_msi_builder()";
let manifest_value = self.to_file_manifest(type_values, ".".to_string())?;
let manifest = manifest_value.downcast_ref::<FileManifestValue>().unwrap();
let arch = target_triple_to_wix_arch(self.inner(LABEL)?.target_triple()).unwrap_or("x64");
let builder_value = WiXMsiBuilderValue::new_from_args(
id_prefix,
product_name,
product_version,
product_manufacturer,
arch.to_string(),
)?;
let mut builder = builder_value
.downcast_mut::<WiXMsiBuilderValue>()
.unwrap()
.unwrap();
builder.add_program_files_manifest(type_values, call_stack, manifest.deref().clone())?;
Ok(builder_value.clone())
}
pub fn filter_resources_from_files(
&mut self,
files: &Value,
glob_files: &Value,
) -> ValueResult {
const LABEL: &str = "PythonExecutable.filter_resources_from_files()";
optional_list_arg("files", "string", files)?;
optional_list_arg("glob_files", "string", glob_files)?;
let files = match files.get_type() {
"list" => files
.iter()?
.iter()
.map(|x| PathBuf::from(x.to_string()))
.collect(),
"NoneType" => Vec::new(),
_ => panic!("type should have been validated above"),
};
let glob_files = match glob_files.get_type() {
"list" => glob_files.iter()?.iter().map(|x| x.to_string()).collect(),
"NoneType" => Vec::new(),
_ => panic!("type should have been validated above"),
};
let files_refs = files.iter().map(|x| x.as_ref()).collect::<Vec<&Path>>();
let glob_files_refs = glob_files.iter().map(|x| x.as_ref()).collect::<Vec<&str>>();
let mut exe = self.inner(LABEL)?;
error_context(LABEL, || {
exe.filter_resources_from_files(&files_refs, &glob_files_refs)
})?;
Ok(Value::new(NoneType::None))
}
}
starlark_module! { python_executable_env =>
PythonExecutable.build(env env, call_stack cs, this, target: String) {
let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
this.build(env, cs, target)
}
PythonExecutable.make_python_module_source(
env env,
call_stack cs,
this,
name: String,
source: String,
is_package: bool = false
) {
let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
this.make_python_module_source(env, cs, name, source, is_package)
}
PythonExecutable.pip_download(
env env,
call_stack cs,
this,
args
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.pip_download(env, cs, &args)
}
PythonExecutable.pip_install(
env env,
call_stack cs,
this,
args,
extra_envs=NoneType::None
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.pip_install(env, cs, &args, &extra_envs)
}
PythonExecutable.read_package_root(
env env,
call_stack cs,
this,
path: String,
packages
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.read_package_root(env, cs, path, &packages)
}
PythonExecutable.read_virtualenv(
env env,
call_stack cs,
this,
path: String
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.read_virtualenv(env, cs, path)
}
PythonExecutable.setup_py_install(
env env,
call_stack cs,
this,
package_path: String,
extra_envs=NoneType::None,
extra_global_arguments=NoneType::None
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.setup_py_install(env, cs, package_path, &extra_envs, &extra_global_arguments)
}
PythonExecutable.add_python_resource(
this,
resource
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.add_python_resource(
&resource,
"add_python_resource",
)
}
PythonExecutable.add_python_resources(
this,
resources
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.add_python_resources(
&resources,
)
}
PythonExecutable.add_cargo_manifest_licensing(
env env,
this,
manifest_path: String,
all_features: bool = false,
features=NoneType::None
) {
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.add_cargo_manifest_licensing(env, &manifest_path, all_features, &features)
}
PythonExecutable.filter_resources_from_files(
this,
files=NoneType::None,
glob_files=NoneType::None)
{
let mut this = this.downcast_mut::<PythonExecutableValue>().unwrap().unwrap();
this.filter_resources_from_files(&files, &glob_files)
}
PythonExecutable.to_embedded_resources(this) {
let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
this.to_embedded_resources()
}
PythonExecutable.to_file_manifest(env env, this, prefix: String) {
let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
this.to_file_manifest(env, prefix)
}
PythonExecutable.to_wix_bundle_builder(
env env,
call_stack cs,
this,
id_prefix: String,
product_name: String,
product_version: String,
product_manufacturer: String,
msi_builder_callback = NoneType::None
) {
let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
this.to_wix_bundle_builder(
env,
cs,
id_prefix,
product_name,
product_version,
product_manufacturer,
msi_builder_callback
)
}
PythonExecutable.to_wix_msi_builder(
env env,
call_stack cs,
this,
id_prefix: String,
product_name: String,
product_version: String,
product_manufacturer: String
) {
let this = this.downcast_ref::<PythonExecutableValue>().unwrap();
this.to_wix_msi_builder(env, cs, id_prefix, product_name, product_version, product_manufacturer)
}
}
#[cfg(test)]
mod tests {
use {
super::super::testutil::*,
super::*,
crate::{python_distributions::PYTHON_DISTRIBUTIONS, testutil::*},
};
#[test]
fn test_default_values() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let exe = env.eval("exe")?;
assert_eq!(exe.get_type(), "PythonExecutable");
let exe = exe.downcast_ref::<PythonExecutableValue>().unwrap();
let inner = exe.inner("ignored").unwrap();
assert!(inner
.iter_resources()
.any(|(_, r)| r.in_memory_source.is_some()));
assert!(inner
.iter_resources()
.all(|(_, r)| r.in_memory_resources.is_none()));
Ok(())
}
#[test]
fn test_no_sources() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
env.eval("dist = default_python_distribution()")?;
env.eval("policy = dist.make_python_packaging_policy()")?;
env.eval("policy.include_distribution_sources = False")?;
let exe = env.eval("dist.to_python_executable('testapp', packaging_policy=policy)")?;
assert_eq!(exe.get_type(), "PythonExecutable");
let exe = exe.downcast_ref::<PythonExecutableValue>().unwrap();
let inner = exe.inner("ignored").unwrap();
assert!(inner
.iter_resources()
.all(|(_, r)| r.in_memory_source.is_none()));
Ok(())
}
#[test]
fn test_make_python_module_source() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let m = env.eval("exe.make_python_module_source('foo', 'import bar')")?;
assert_eq!(m.get_type(), PythonModuleSourceValue::TYPE);
assert_eq!(m.get_attr("name").unwrap().to_str(), "foo");
assert_eq!(m.get_attr("source").unwrap().to_str(), "import bar");
assert!(!m.get_attr("is_package").unwrap().to_bool());
Ok(())
}
#[test]
fn test_make_python_module_source_callback() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
env.eval("dist = default_python_distribution()")?;
env.eval("policy = dist.make_python_packaging_policy()")?;
env.eval(
"def my_func(policy, resource):\n resource.add_source = True\n resource.add_bytecode_optimization_level_two = True\n",
)?;
env.eval("policy.register_resource_callback(my_func)")?;
env.eval("exe = dist.to_python_executable('testapp', packaging_policy = policy)")?;
let m = env.eval("exe.make_python_module_source('foo', 'import bar')")?;
assert_eq!(m.get_type(), PythonModuleSourceValue::TYPE);
assert_eq!(m.get_attr("name").unwrap().to_str(), "foo");
assert_eq!(m.get_attr("source").unwrap().to_str(), "import bar");
assert!(!m.get_attr("is_package").unwrap().to_bool());
assert!(m.get_attr("add_source").unwrap().to_bool());
assert!(m
.get_attr("add_bytecode_optimization_level_two")
.unwrap()
.to_bool(),);
Ok(())
}
#[test]
fn test_pip_download_pyflakes() -> Result<()> {
for target_triple in PYTHON_DISTRIBUTIONS.all_target_triples() {
let mut env = test_evaluation_context_builder()?
.build_target_triple(target_triple)
.into_context()?;
env.eval("dist = default_python_distribution()")?;
env.eval("exe = dist.to_python_executable('testapp')")?;
let resources = env.eval("exe.pip_download(['pyflakes==2.2.0'])")?;
assert_eq!(resources.get_type(), "list");
let raw_it = resources.iter().unwrap();
let mut it = raw_it.iter();
let v = it.next().unwrap();
assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
let inner = x.inner("ignored").unwrap();
assert!(inner.m.package().starts_with("pyflakes"));
}
Ok(())
}
#[test]
fn test_pip_install_simple() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
env.eval("dist = default_python_distribution()")?;
env.eval("policy = dist.make_python_packaging_policy()")?;
env.eval("policy.include_distribution_sources = False")?;
env.eval("exe = dist.to_python_executable('testapp', packaging_policy = policy)")?;
let resources = env.eval("exe.pip_install(['pyflakes==2.1.1'])")?;
assert_eq!(resources.get_type(), "list");
let raw_it = resources.iter().unwrap();
let mut it = raw_it.iter();
let v = it.next().unwrap();
assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
let inner = x.inner("ignored").unwrap();
assert_eq!(inner.m.name, "pyflakes");
assert!(inner.m.is_package);
Ok(())
}
#[test]
fn test_read_package_root_simple() -> Result<()> {
let temp_dir = get_env()?.temporary_directory("pyoxidizer-test")?;
let root = temp_dir.path();
std::fs::create_dir(root.join("bar"))?;
let bar_init = root.join("bar").join("__init__.py");
std::fs::write(&bar_init, "# bar")?;
let foo_path = root.join("foo.py");
std::fs::write(&foo_path, "# foo")?;
let baz_path = root.join("baz.py");
std::fs::write(&baz_path, "# baz")?;
std::fs::create_dir(root.join("extra"))?;
let extra_path = root.join("extra").join("__init__.py");
std::fs::write(&extra_path, "# extra")?;
let mut env = test_evaluation_context_builder()?.into_context()?;
env.eval("dist = default_python_distribution()")?;
env.eval("policy = dist.make_python_packaging_policy()")?;
env.eval("policy.include_distribution_sources = False")?;
env.eval("exe = dist.to_python_executable('testapp', packaging_policy = policy)")?;
let resources = env.eval(&format!(
"exe.read_package_root(\"{}\", packages=['foo', 'bar'])",
root.display().to_string().replace('\\', "/")
))?;
assert_eq!(resources.get_type(), "list");
assert_eq!(resources.length().unwrap(), 2);
let raw_it = resources.iter().unwrap();
let mut it = raw_it.iter();
let v = it.next().unwrap();
assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
let inner = x.inner("ignored").unwrap();
assert_eq!(inner.m.name, "bar");
assert!(inner.m.is_package);
assert_eq!(inner.m.source.resolve_content().unwrap(), b"# bar");
drop(inner);
let v = it.next().unwrap();
assert_eq!(v.get_type(), PythonModuleSourceValue::TYPE);
let x = v.downcast_ref::<PythonModuleSourceValue>().unwrap();
let inner = x.inner("ignored").unwrap();
assert_eq!(inner.m.name, "foo");
assert!(!inner.m.is_package);
assert_eq!(inner.m.source.resolve_content().unwrap(), b"# foo");
drop(inner);
temp_dir.close()?;
Ok(())
}
#[test]
fn licenses_filename() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let v = env.eval("exe.licenses_filename")?;
assert_eq!(v.get_type(), "string");
assert_eq!(v.to_string(), "COPYING.txt");
env.eval("exe.licenses_filename = 'licenses'")?;
let v = env.eval("exe.licenses_filename")?;
assert_eq!(v.get_type(), "string");
assert_eq!(v.to_string(), "licenses");
env.eval("exe.licenses_filename = None")?;
let v = env.eval("exe.licenses_filename")?;
assert_eq!(v.get_type(), "NoneType");
Ok(())
}
#[test]
fn test_windows_runtime_dlls_mode() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let value = env.eval("exe.windows_runtime_dlls_mode")?;
assert_eq!(value.get_type(), "string");
assert_eq!(value.to_string(), "when-present");
let value =
env.eval("exe.windows_runtime_dlls_mode = 'never'; exe.windows_runtime_dlls_mode")?;
assert_eq!(value.to_string(), "never");
let value =
env.eval("exe.windows_runtime_dlls_mode = 'always'; exe.windows_runtime_dlls_mode")?;
assert_eq!(value.to_string(), "always");
assert!(env.eval("exe.windows_runtime_dlls_mode = 'bad'").is_err());
let value = env.eval(
"exe.windows_runtime_dlls_mode = 'when-present'; exe.windows_runtime_dlls_mode",
)?;
assert_eq!(value.to_string(), "when-present");
Ok(())
}
#[test]
fn test_packed_resources_load_mode() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let value = env.eval("exe.packed_resources_load_mode")?;
assert_eq!(value.get_type(), "string");
assert_eq!(value.to_string(), "embedded:packed-resources");
let value =
env.eval("exe.packed_resources_load_mode = 'none'; exe.packed_resources_load_mode")?;
assert_eq!(value.get_type(), "string");
assert_eq!(value.to_string(), "none");
Ok(())
}
#[test]
fn test_windows_subsystem() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let value = env.eval("exe.windows_subsystem")?;
assert_eq!(value.get_type(), "string");
assert_eq!(value.to_string(), "console");
let value = env.eval("exe.windows_subsystem = 'windows'; exe.windows_subsystem")?;
assert_eq!(value.get_type(), "string");
assert_eq!(value.to_string(), "windows");
Ok(())
}
#[test]
fn test_tcl_files_path() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
let value = env.eval("exe.tcl_files_path")?;
assert_eq!(value.get_type(), "NoneType");
let value = env.eval("exe.tcl_files_path = 'lib'; exe.tcl_files_path")?;
assert_eq!(value.get_type(), "string");
assert_eq!(value.to_string(), "lib");
let value = env.eval("exe.tcl_files_path = None; exe.tcl_files_path")?;
assert_eq!(value.get_type(), "NoneType");
Ok(())
}
#[test]
fn test_to_wix_bundle_builder_callback() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
env.eval("def modify(msi):\n msi.package_description = 'description'\n")?;
let builder_value = env.eval("exe.to_wix_bundle_builder('id_prefix', 'product_name', '0.1', 'manufacturer', msi_builder_callback = modify)")?;
let builder = builder_value
.downcast_ref::<WiXBundleBuilderValue>()
.unwrap();
assert_eq!(builder.build_msis.len(), 1);
let mut writer = xml::EventWriter::new(vec![]);
builder.build_msis[0]
.inner("ignored")
.unwrap()
.builder
.write_xml(&mut writer)?;
let xml = String::from_utf8(writer.into_inner())?;
assert!(xml.find("Description=\"description\"").is_some());
Ok(())
}
#[cfg(windows)]
#[test]
fn test_to_wix_bundle_builder() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
env.eval("bundle = exe.to_wix_bundle_builder('id_prefix', 'product_name', '0.1', 'product_manufacturer')")?;
env.eval("bundle.build('test_to_wix_bundle_builder')")?;
let exe_path = env
.target_build_path("test_to_wix_bundle_builder")
.unwrap()
.join("product_name-0.1.exe");
assert!(exe_path.exists());
Ok(())
}
#[cfg(windows)]
#[test]
fn test_to_wix_msi_builder() -> Result<()> {
let mut env = test_evaluation_context_builder()?.into_context()?;
add_exe(&mut env)?;
env.eval("msi = exe.to_wix_msi_builder('id_prefix', 'product_name', '0.1', 'product_manufacturer')")?;
env.eval("msi.build('test_to_wix_msi_builder')")?;
let msi_path = env
.target_build_path("test_to_wix_msi_builder")
.unwrap()
.join("product_name-0.1.msi");
assert!(msi_path.exists());
Ok(())
}
}