use anyhow::Result;
use slog::warn;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tempdir::TempDir;
use super::config::EmbeddedPythonConfig;
use super::distribution::{ExtensionModuleFilter, ParsedPythonDistribution};
use super::embedded_resource::EmbeddedPythonResourcesPrePackaged;
use super::libpython::{derive_importlib, link_libpython, ImportlibData};
use super::pyembed::{derive_python_config, write_data_rs};
#[derive(Debug)]
pub struct PreBuiltPythonExecutable {
pub name: String,
pub distribution: Arc<ParsedPythonDistribution>,
pub resources: EmbeddedPythonResourcesPrePackaged,
pub config: EmbeddedPythonConfig,
}
impl PreBuiltPythonExecutable {
#[allow(clippy::too_many_arguments)]
pub fn from_python_distribution(
logger: &slog::Logger,
distribution: Arc<ParsedPythonDistribution>,
name: &str,
config: &EmbeddedPythonConfig,
extension_module_filter: &ExtensionModuleFilter,
preferred_extension_module_variants: Option<HashMap<String, String>>,
include_sources: bool,
include_resources: bool,
include_test: bool,
) -> Result<Self> {
let mut resources = EmbeddedPythonResourcesPrePackaged::from_distribution(
logger,
distribution.clone(),
extension_module_filter,
preferred_extension_module_variants,
include_sources,
include_resources,
include_test,
)?;
for ext in
distribution.filter_extension_modules(&logger, &ExtensionModuleFilter::Minimal, None)?
{
if !resources.extension_modules.contains_key(&ext.module) {
resources.add_extension_module(&ext);
}
}
Ok(PreBuiltPythonExecutable {
name: name.to_string(),
distribution,
resources,
config: config.clone(),
})
}
pub fn build_libpython(
&self,
logger: &slog::Logger,
host: &str,
target: &str,
opt_level: &str,
) -> Result<PythonLibrary> {
let resources = self
.resources
.package(logger, &self.distribution.python_exe)?;
let temp_dir = TempDir::new("pyoxidizer-build-exe")?;
let temp_dir_path = temp_dir.path();
warn!(
logger,
"generating custom link library containing Python..."
);
let library_info = link_libpython(
logger,
&self.distribution,
&resources,
&temp_dir_path,
host,
target,
opt_level,
)?;
let mut cargo_metadata: Vec<String> = Vec::new();
cargo_metadata.extend(library_info.cargo_metadata);
let libpython_data = std::fs::read(&library_info.libpython_path)?;
let libpyembeddedconfig_data = std::fs::read(&library_info.libpyembeddedconfig_path)?;
Ok(PythonLibrary {
libpython_filename: PathBuf::from(library_info.libpython_path.file_name().unwrap()),
libpython_data,
libpyembeddedconfig_filename: PathBuf::from(
library_info.libpyembeddedconfig_path.file_name().unwrap(),
),
libpyembeddedconfig_data,
cargo_metadata,
})
}
pub fn build_embedded_blobs(&self, logger: &slog::Logger) -> Result<EmbeddedResourcesBlobs> {
let embedded_resources = self
.resources
.package(logger, &self.distribution.python_exe)?;
let mut module_names = Vec::new();
let mut modules = Vec::new();
let mut resources = Vec::new();
embedded_resources.write_blobs(&mut module_names, &mut modules, &mut resources);
Ok(EmbeddedResourcesBlobs {
module_names,
modules,
resources,
})
}
}
pub struct PythonLibrary {
pub libpython_filename: PathBuf,
pub libpython_data: Vec<u8>,
pub libpyembeddedconfig_filename: PathBuf,
pub libpyembeddedconfig_data: Vec<u8>,
pub cargo_metadata: Vec<String>,
}
pub struct EmbeddedResourcesBlobs {
pub module_names: Vec<u8>,
pub modules: Vec<u8>,
pub resources: Vec<u8>,
}
pub struct EmbeddedPythonBinaryPaths {
pub importlib_bootstrap: PathBuf,
pub importlib_bootstrap_external: PathBuf,
pub module_names: PathBuf,
pub py_modules: PathBuf,
pub resources: PathBuf,
pub libpython: PathBuf,
pub libpyembeddedconfig: PathBuf,
pub config_rs: PathBuf,
pub cargo_metadata: PathBuf,
}
pub struct EmbeddedPythonBinaryData {
pub config: EmbeddedPythonConfig,
pub library: PythonLibrary,
pub importlib: ImportlibData,
pub resources: EmbeddedResourcesBlobs,
pub host: String,
pub target: String,
}
impl EmbeddedPythonBinaryData {
pub fn from_pre_built_python_executable(
exe: &PreBuiltPythonExecutable,
logger: &slog::Logger,
host: &str,
target: &str,
opt_level: &str,
) -> Result<EmbeddedPythonBinaryData> {
let library = exe.build_libpython(logger, host, target, opt_level)?;
let resources = exe.build_embedded_blobs(logger)?;
warn!(
logger,
"deriving custom importlib modules to support in-memory importing"
);
let importlib = derive_importlib(&exe.distribution)?;
Ok(EmbeddedPythonBinaryData {
config: exe.config.clone(),
library,
importlib,
resources,
host: host.to_string(),
target: target.to_string(),
})
}
pub fn write_files(&self, dest_dir: &Path) -> Result<EmbeddedPythonBinaryPaths> {
let importlib_bootstrap = dest_dir.join("importlib_bootstrap");
let mut fh = File::create(&importlib_bootstrap)?;
fh.write_all(&self.importlib.bootstrap_bytecode)?;
let importlib_bootstrap_external = dest_dir.join("importlib_bootstrap_external");
let mut fh = File::create(&importlib_bootstrap_external)?;
fh.write_all(&self.importlib.bootstrap_external_bytecode)?;
let module_names = dest_dir.join("py-module-names");
let mut fh = File::create(&module_names)?;
fh.write_all(&self.resources.module_names)?;
let py_modules = dest_dir.join("py-modules");
let mut fh = File::create(&py_modules)?;
fh.write_all(&self.resources.modules)?;
let resources = dest_dir.join("python-resources");
let mut fh = File::create(&resources)?;
fh.write_all(&self.resources.resources)?;
let libpython = dest_dir.join(&self.library.libpython_filename);
let mut fh = File::create(&libpython)?;
fh.write_all(&self.library.libpython_data)?;
let libpyembeddedconfig = dest_dir.join(&self.library.libpyembeddedconfig_filename);
let mut fh = File::create(&libpyembeddedconfig)?;
fh.write_all(&self.library.libpyembeddedconfig_data)?;
let config_rs_data = derive_python_config(
&self.config,
&importlib_bootstrap,
&importlib_bootstrap_external,
&py_modules,
&resources,
);
let config_rs = dest_dir.join("data.rs");
write_data_rs(&config_rs, &config_rs_data)?;
let mut cargo_metadata_lines = Vec::new();
cargo_metadata_lines.extend(self.library.cargo_metadata.clone());
cargo_metadata_lines.push(format!(
"cargo:rustc-link-search=native={}",
dest_dir.display()
));
cargo_metadata_lines.push(format!(
"cargo:rustc-env=PYEMBED_DATA_RS_PATH={}",
config_rs.display()
));
let cargo_metadata = dest_dir.join("cargo_metadata.txt");
let mut fh = File::create(&cargo_metadata)?;
fh.write_all(cargo_metadata_lines.join("\n").as_bytes())?;
Ok(EmbeddedPythonBinaryPaths {
importlib_bootstrap,
importlib_bootstrap_external,
module_names,
py_modules,
resources,
libpython,
libpyembeddedconfig,
config_rs,
cargo_metadata,
})
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::py_packaging::distribution::ExtensionModuleFilter;
use crate::testutil::*;
pub fn get_prebuilt(logger: &slog::Logger) -> Result<PreBuiltPythonExecutable> {
let distribution = get_default_distribution()?;
let mut resources = EmbeddedPythonResourcesPrePackaged::default();
for ext in
distribution.filter_extension_modules(logger, &ExtensionModuleFilter::Minimal, None)?
{
resources.add_extension_module(&ext);
}
let config = EmbeddedPythonConfig::default();
Ok(PreBuiltPythonExecutable {
name: "testapp".to_string(),
distribution,
resources,
config,
})
}
pub fn get_embedded(logger: &slog::Logger) -> Result<EmbeddedPythonBinaryData> {
EmbeddedPythonBinaryData::from_pre_built_python_executable(
&get_prebuilt(logger)?,
&get_logger()?,
env!("HOST"),
env!("HOST"),
"0",
)
}
#[test]
fn test_write_embedded_files() -> Result<()> {
let logger = get_logger()?;
let embedded = get_embedded(&logger)?;
let temp_dir = tempdir::TempDir::new("pyoxidizer-test")?;
embedded.write_files(temp_dir.path())?;
Ok(())
}
}