Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow persistent pyembed runtime projects #717

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
The policy automatically uses settings globally appropriate for the
distribution.

.. py:method:: to_python_executable(name: str, packaging_policy: PythonPackagingPolicy, config: PythonInterpreterConfig) -> PythonExecutable
.. py:method:: to_python_executable(name: str, packaging_policy: PythonPackagingPolicy, config: PythonInterpreterConfig, runtime: str) -> PythonExecutable

This method constructs a :py:class:`PythonExecutable` instance. It
essentially says *build an executable embedding Python from this
Expand All @@ -107,6 +107,12 @@

Default is what :py:meth:`make_python_interpreter_config` returns.

``runtime``
The path to a pyoxidizer Rust project that will invoke pyembed.
See :ref:`rust_projects` for more on the expected composition of Rust projects.

If not supplied, a temporary Rust project will be created.

.. important::

Libraries that extension modules link against have various software
Expand Down
7 changes: 4 additions & 3 deletions pyoxidizer/docs/pyoxidizer_rust_projects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,14 @@ to ``exit()``. Succinctly, we instantiate and run an embedded Python
interpreter. That's our executable.

The ``pyoxidizer.bzl`` is our auto-generated
:ref:`PyOxidizer configuration file <config_files>`.
:ref:`PyOxidizer configuration file <config_files>` with an additional `runtime` option set to the current directory.
This specifies that we want this project to be used as the pyembed runner instead of a temporary one.

Crate Features
==============

The auto-generated Rust project defines a number of features to control
behavior. These are documented in the sections below.
behavior when building via cargo. These are documented in the sections below.

``build-mode-standalone``
-------------------------
Expand Down Expand Up @@ -127,7 +128,7 @@ name via the ``PYOXIDIZER_BUILD_TARGET`` environment variable. e.g.::

This mode tells the build script to reuse artifacts that were already built.
(Perhaps you called ``pyoxidizer build`` or ``pyoxidizer run-build-script``
outside the context of a normal ``cargo build``.)
outside the context of a normal ``cargo build``.) This is what is used when pyoxidizer invokes cargo as part of ``pyoxidizer build``.

In this mode, the build script will look for artifacts in the directory
specified by ``PYOXIDIZER_ARTIFACT_DIR`` if set, falling back to ``OUT_DIR``.
Expand Down
107 changes: 67 additions & 40 deletions pyoxidizer/src/project_building.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,46 +427,73 @@ pub fn build_python_executable<'a>(
.context("resolving Rust toolchain")?
.cargo_exe;

let temp_dir = env.temporary_directory("pyoxidizer")?;

// Directory needs to have name of project.
let project_path = temp_dir.path().join(bin_name);
let build_path = temp_dir.path().join("build");
let artifacts_path = temp_dir.path().join("artifacts");

initialize_project(
&env.pyoxidizer_source,
&project_path,
&cargo_exe,
None,
&[],
exe.windows_subsystem(),
)
.context("initializing project")?;

let mut build = build_executable_with_rust_project(
env,
&project_path,
bin_name,
exe,
&build_path,
&artifacts_path,
target_triple,
opt_level,
release,
// Always build with locked because we crated a Cargo.lock with the
// Rust project we just created.
true,
// Don't include license for self because the Rust project is temporary and its
// licensing isn't material.
false,
)
.context("building executable with Rust project")?;

// Blank out the path since it is in the temporary directory.
build.exe_path = None;

temp_dir.close().context("closing temporary directory")?;
let build = match exe.runtime_path() {
Some(path) => {
let project_path =
canonicalize_path(&Path::new(path)).context("getting runtime path")?;
let build_path = project_path.join("build");
let artifacts_path = build_path.join("artifacts");
build_executable_with_rust_project(
env,
&project_path,
bin_name,
exe,
&build_path,
&artifacts_path,
target_triple,
opt_level,
release,
false,
true,
)
.context("building executable with persistent Rust project")?
}
None => {
let temp_dir = env.temporary_directory("pyoxidizer")?;

// Directory needs to have name of project.
let project_path = temp_dir.path().join(bin_name);
let build_path = temp_dir.path().join("build");
let artifacts_path = temp_dir.path().join("artifacts");

initialize_project(
&env.pyoxidizer_source,
&project_path,
&cargo_exe,
None,
&[],
exe.windows_subsystem(),
false,
)
.context("initializing project")?;

let mut build = build_executable_with_rust_project(
env,
&project_path,
bin_name,
exe,
&build_path,
&artifacts_path,
target_triple,
opt_level,
release,
// Always build with locked because we crated a Cargo.lock with the
// Rust project we just created.
true,
// Don't include license for self because the Rust project is temporary and its
// licensing isn't material.
false,
)
.context("building executable with generated Rust project")?;

// Blank out the path since it is in the temporary directory.
build.exe_path = None;

temp_dir.close().context("closing temporary directory")?;

build
}
};

Ok(build)
}
Expand Down
7 changes: 6 additions & 1 deletion pyoxidizer/src/project_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct TemplateData {
program_name: Option<String>,
code: Option<String>,
pip_install_simple: Vec<String>,
rust_init: bool,
}

impl TemplateData {
Expand All @@ -105,6 +106,7 @@ impl TemplateData {
program_name: None,
code: None,
pip_install_simple: Vec::new(),
rust_init: false,
}
}
}
Expand Down Expand Up @@ -285,12 +287,14 @@ pub fn write_new_pyoxidizer_config_file(
name: &str,
code: Option<&str>,
pip_install: &[&str],
rust_init: bool,
) -> Result<()> {
let path = project_dir.join("pyoxidizer.bzl");

let mut data = TemplateData::new();
populate_template_data(source, &mut data);
data.program_name = Some(name.to_string());
data.rust_init = rust_init;

if let Some(code) = code {
// Replace " with \" to work around
Expand Down Expand Up @@ -421,6 +425,7 @@ pub fn initialize_project(
code: Option<&str>,
pip_install: &[&str],
windows_subsystem: &str,
rust_init: bool,
) -> Result<()> {
let status = std::process::Command::new(cargo_exe)
.arg("init")
Expand All @@ -443,7 +448,7 @@ pub fn initialize_project(
write_new_build_rs(&path.join("build.rs"), name).context("writing build.rs")?;
write_new_main_rs(&path.join("src").join("main.rs"), windows_subsystem)
.context("writing main.rs")?;
write_new_pyoxidizer_config_file(source, &path, name, code, pip_install)
write_new_pyoxidizer_config_file(source, &path, name, code, pip_install, rust_init)
.context("writing PyOxidizer config file")?;
write_application_manifest(&path, name).context("writing application manifest")?;

Expand Down
4 changes: 3 additions & 1 deletion pyoxidizer/src/projectmgmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ pub fn init_config_file(

let name = project_dir.iter().last().unwrap().to_str().unwrap();

write_new_pyoxidizer_config_file(source, project_dir, name, code, pip_install)?;
write_new_pyoxidizer_config_file(source, project_dir, name, code, pip_install, false)?;

println!();
println!("A new PyOxidizer configuration file has been created.");
Expand Down Expand Up @@ -347,6 +347,7 @@ pub fn init_rust_project(env: &Environment, project_path: &Path) -> Result<()> {
None,
&[],
"console",
true,
)?;
println!();
println!(
Expand Down Expand Up @@ -617,6 +618,7 @@ pub fn generate_python_embedding_artifacts(
BinaryLibpythonLinkMode::Default,
&policy,
&interpreter_config,
None,
Some(host_dist.clone_trait()),
)?;

Expand Down
3 changes: 3 additions & 0 deletions pyoxidizer/src/py_packaging/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ pub trait PythonBinaryBuilder {
// TODO this should not need to exist if we properly supported cross-compiling.
fn target_python_exe_path(&self) -> &Path;

/// The directory to install tcl/tk files into.
fn runtime_path(&self) -> &Option<String>;

/// Apple SDK build/targeting information.
fn apple_sdk_info(&self) -> Option<&AppleSdkInfo>;

Expand Down
1 change: 1 addition & 0 deletions pyoxidizer/src/py_packaging/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ mod tests {
&policy,
&config,
None,
None,
)?;

crate::project_building::build_python_executable(
Expand Down
1 change: 1 addition & 0 deletions pyoxidizer/src/py_packaging/distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub trait PythonDistribution {
libpython_link_mode: BinaryLibpythonLinkMode,
policy: &PythonPackagingPolicy,
config: &PyembedPythonInterpreterConfig,
runtime_path: Option<String>,
host_distribution: Option<Arc<dyn PythonDistribution>>,
) -> Result<Box<dyn PythonBinaryBuilder>>;

Expand Down
13 changes: 13 additions & 0 deletions pyoxidizer/src/py_packaging/standalone_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ pub struct StandalonePythonExecutableBuilder {
/// Path to install tcl/tk files into.
tcl_files_path: Option<String>,

/// Path to the Rust project that will invoke our embedded python interpreter.
runtime_path: Option<String>,

/// Describes how Windows runtime DLLs should be handled during builds.
windows_runtime_dlls_mode: WindowsRuntimeDllsMode,
}
Expand All @@ -145,6 +148,7 @@ impl StandalonePythonExecutableBuilder {
link_mode: BinaryLibpythonLinkMode,
packaging_policy: PythonPackagingPolicy,
config: PyembedPythonInterpreterConfig,
runtime_path: Option<String>,
) -> Result<Box<Self>> {
let host_python_exe = host_distribution.python_exe_path().to_path_buf();

Expand Down Expand Up @@ -230,6 +234,7 @@ impl StandalonePythonExecutableBuilder {
licenses_filename: Some("COPYING.txt".into()),
windows_subsystem: "console".to_string(),
tcl_files_path: None,
runtime_path,
windows_runtime_dlls_mode: WindowsRuntimeDllsMode::WhenPresent,
});

Expand Down Expand Up @@ -464,6 +469,10 @@ impl PythonBinaryBuilder for StandalonePythonExecutableBuilder {
self.target_distribution.python_exe_path()
}

fn runtime_path(&self) -> &Option<String> {
&self.runtime_path
}

fn apple_sdk_info(&self) -> Option<&AppleSdkInfo> {
self.target_distribution.apple_sdk_info()
}
Expand Down Expand Up @@ -1211,6 +1220,7 @@ pub mod tests {
self.libpython_link_mode.clone(),
policy,
self.config.clone(),
None,
)?;

builder.add_distribution_resources(None)?;
Expand Down Expand Up @@ -3034,6 +3044,7 @@ pub mod tests {
BinaryLibpythonLinkMode::Default,
dist.create_packaging_policy()?,
dist.create_python_interpreter_config()?,
None,
)?;

let reqs = builder.vc_runtime_requirements();
Expand Down Expand Up @@ -3070,6 +3081,7 @@ pub mod tests {
BinaryLibpythonLinkMode::Default,
dist.create_packaging_policy()?,
dist.create_python_interpreter_config()?,
None,
)?;

// In Never mode, the set of extra files should always be empty.
Expand Down Expand Up @@ -3154,6 +3166,7 @@ pub mod tests {
BinaryLibpythonLinkMode::Default,
policy,
dist.create_python_interpreter_config()?,
None,
)?;

builder.add_distribution_resources(None)?;
Expand Down
2 changes: 2 additions & 0 deletions pyoxidizer/src/py_packaging/standalone_distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,7 @@ impl PythonDistribution for StandaloneDistribution {
libpython_link_mode: BinaryLibpythonLinkMode,
policy: &PythonPackagingPolicy,
config: &PyembedPythonInterpreterConfig,
runtime_path: Option<String>,
host_distribution: Option<Arc<dyn PythonDistribution>>,
) -> Result<Box<dyn PythonBinaryBuilder>> {
// TODO can we avoid these clones?
Expand All @@ -1263,6 +1264,7 @@ impl PythonDistribution for StandaloneDistribution {
libpython_link_mode,
policy.clone(),
config.clone(),
runtime_path,
)?;

Ok(builder as Box<dyn PythonBinaryBuilder>)
Expand Down
Loading