Add "extension-module" feature to suppress linking pythonX.Y.so.

The symbols are instead kept unresolved, so that they can be used with
any compatible python interpreter, even if the target system uses a
statically linked python and lacks pythonX.Y.so altogether.
This commit is contained in:
Daniel Grunwald 2016-12-17 21:17:11 +01:00
parent 62a083e38f
commit e2d7781433
15 changed files with 129 additions and 29 deletions

View file

@ -46,6 +46,16 @@ default = ["python3-sys"]
# Enable additional features that require nightly rust # Enable additional features that require nightly rust
nightly = [] nightly = []
# Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters.
extension-module = [ "python3-sys/extension-module" ]
# Unfortunately we can't use the forward the same feature to either python27-sys
# or python3-sys. (honestly, we should probably merge both crates into 'python-sys')
extension-module-2-7 = [ "python27-sys/extension-module" ]
# Optional features to support explicitly specifying python minor version. # Optional features to support explicitly specifying python minor version.
# If you don't care which minor version, just specify python3-sys as a # If you don't care which minor version, just specify python3-sys as a
# feature. # feature.
@ -55,3 +65,6 @@ python-3-4 = ["python3-sys/python-3-4"]
#pep-384 = ["python3-sys/pep-384"] #pep-384 = ["python3-sys/pep-384"]
[workspace]
members = ["python27-sys", "python3-sys", "extensions/hello"]

View file

@ -49,7 +49,7 @@ doc: build
cargo doc --no-deps $(CARGO_FLAGS) cargo doc --no-deps $(CARGO_FLAGS)
extensions: build extensions: build
make -C extensions/ PY=$(PY) make -C extensions/tests PY=$(PY)
clean: clean:
rm -r target rm -r target

View file

@ -0,0 +1,21 @@
[package]
name = "hello"
version = "0.1.0"
authors = ["Daniel Grunwald <daniel@danielgrunwald.de>"]
[lib]
## Python extension modules should be compiled as 'cdylib'
crate-type = ["cdylib"]
[dependencies.cpython]
path = "../.."
features = ["extension-module"]
# The 'extension-module' feature allows using the resulting binary module
# with statically linked python interpreters.
## By default, cpython will use whichever Python 3.x interpreter is found in PATH.
## To target Python 2.7, use:
#default-features=false
#features = ["python27-sys", "extension-module-2-7"]

View file

@ -0,0 +1,38 @@
#[macro_use] extern crate cpython;
use cpython::{PyObject, PyResult, Python, PyTuple, PyDict};
// Our module is named 'hello', and can be imported using `import hello`.
// This requires that the output binary file is named `hello.so` (or Windows: `hello.pyd`).
// As the output name cannot be configured in cargo (https://github.com/rust-lang/cargo/issues/1970),
// you'll have to rename the output file.
py_module_initializer!(hello, inithello, PyInit_hello, |py, m| {
m.add(py, "__doc__", "Module documentation string")?;
m.add(py, "func", py_fn!(py, func(a: &str, b: i32)))?;
m.add(py, "run", py_fn!(py, run(*args, **kwargs)))?;
Ok(())
});
// The py_fn!()-macro can translate between Python and Rust values,
// so you can use `&str`, `i32` or `String` in the signature of a function
// callable from Python.
// The first argument of type `Python<'p>` is used to indicate that your
// function may assume that the current thread holds the global interpreter lock.
// Most functions in the `cpython` crate require that you pass this argument.
fn func(_: Python, a: &str, b: i32) -> PyResult<String> {
Ok(format!("func({}, {})", a, b))
}
fn run(py: Python, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult<PyObject> {
println!("Rust says: Hello Python!");
for arg in args.iter(py) {
println!("Rust got {}", arg);
}
if let Some(kwargs) = kwargs {
for (key, val) in kwargs.items(py) {
println!("{} = {}", key, val);
}
}
Ok(py.None())
}

View file

@ -1,4 +1,4 @@
TARGETDIR=../target/debug TARGETDIR=../../target/debug
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
.PHONY: all clean .PHONY: all clean
@ -27,10 +27,10 @@ clean:
stamps: stamps:
mkdir stamps mkdir stamps
stamps/rust-cpython-$(PY): $(call rwildcard,../src,*.rs) Makefile | stamps stamps/rust-cpython-$(PY): $(call rwildcard,../../src,*.rs) Makefile | stamps
-rm stamps/rust-cpython-* -rm stamps/rust-cpython-*
cd .. && make build PY=$(PY) cd ../.. && make build PY=$(PY)
touch $@ touch "$@"
%.so: %.rs stamps/rust-cpython-$(PY) %.so: %.rs stamps/rust-cpython-$(PY)
rustc $< -g -L $(TARGETDIR) -L $(TARGETDIR)/deps -o $@ rustc $< -g -L $(TARGETDIR) -L $(TARGETDIR)/deps -o $@

View file

@ -18,6 +18,7 @@ exclude = [
".gitignore", ".gitignore",
".travis.yml", ".travis.yml",
] ]
workspace = ".."
[dependencies] [dependencies]
libc = "0.2" libc = "0.2"
@ -36,3 +37,9 @@ regex = "0.1"
# where supporting multiple 3.x's is more important. # where supporting multiple 3.x's is more important.
default = ["python-2-7"] default = ["python-2-7"]
python-2-7 = [] python-2-7 = []
# Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters.
extension-module = [ ]

View file

@ -297,13 +297,14 @@ fn configure_from_path(expected_version: &PythonVersion) -> Result<String, Strin
let enable_shared: &str = &lines[2]; let enable_shared: &str = &lines[2];
let exec_prefix: &str = &lines[3]; let exec_prefix: &str = &lines[3];
println!("{}", get_rustc_link_lib(&interpreter_version, let is_extension_module = env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some();
enable_shared == "1").unwrap()); if !is_extension_module || cfg!(target_os="windows") {
println!("{}", get_rustc_link_lib(&interpreter_version, enable_shared == "1").unwrap());
if libpath != "None" { if libpath != "None" {
println!("cargo:rustc-link-search=native={}", libpath); println!("cargo:rustc-link-search=native={}", libpath);
} else if cfg!(target_os="windows") { } else if cfg!(target_os="windows") {
println!("cargo:rustc-link-search=native={}\\libs", exec_prefix); println!("cargo:rustc-link-search=native={}\\libs", exec_prefix);
}
} }
return Ok(interpreter_path); return Ok(interpreter_path);

View file

@ -18,6 +18,7 @@ exclude = [
".gitignore", ".gitignore",
".travis.yml", ".travis.yml",
] ]
workspace = ".."
[dependencies] [dependencies]
libc = "0.2" libc = "0.2"
@ -30,6 +31,11 @@ regex = "0.1"
# to try to bind to. # to try to bind to.
default = ["python-3"] default = ["python-3"]
# Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters.
extension-module = [ ]
# Bind to any python 3.x. # Bind to any python 3.x.
python-3 = [] python-3 = []

View file

@ -298,8 +298,16 @@ fn configure_from_path(expected_version: &PythonVersion) -> Result<String, Strin
let ld_version: &str = &lines[3]; let ld_version: &str = &lines[3];
let exec_prefix: &str = &lines[4]; let exec_prefix: &str = &lines[4];
println!("{}", get_rustc_link_lib(&interpreter_version, let is_extension_module = env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some();
ld_version, enable_shared == "1").unwrap()); if !is_extension_module || cfg!(target_os="windows") {
println!("{}", get_rustc_link_lib(&interpreter_version,
ld_version, enable_shared == "1").unwrap());
if libpath != "None" {
println!("cargo:rustc-link-search=native={}", libpath);
} else if cfg!(target_os="windows") {
println!("cargo:rustc-link-search=native={}\\libs", exec_prefix);
}
}
let is_pep_384 = env::var_os("CARGO_FEATURE_PEP_384").is_some(); let is_pep_384 = env::var_os("CARGO_FEATURE_PEP_384").is_some();
if is_pep_384 { if is_pep_384 {
@ -312,12 +320,6 @@ fn configure_from_path(expected_version: &PythonVersion) -> Result<String, Strin
} }
} }
if libpath != "None" {
println!("cargo:rustc-link-search=native={}", libpath);
} else if cfg!(target_os="windows") {
println!("cargo:rustc-link-search=native={}\\libs", exec_prefix);
}
return Ok(interpreter_path); return Ok(interpreter_path);
} }

View file

@ -212,13 +212,12 @@ pub mod _detail {
/// ///
/// # Example /// # Example
/// ``` /// ```
/// #![crate_type = "dylib"]
/// #[macro_use] extern crate cpython; /// #[macro_use] extern crate cpython;
/// use cpython::{Python, PyResult, PyObject}; /// use cpython::{Python, PyResult, PyObject};
/// ///
/// py_module_initializer!(example, initexample, PyInit_example, |py, m| { /// py_module_initializer!(hello, inithello, PyInit_hello, |py, m| {
/// try!(m.add(py, "__doc__", "Module documentation string")); /// m.add(py, "__doc__", "Module documentation string")?;
/// try!(m.add(py, "run", py_fn!(py, run()))); /// m.add(py, "run", py_fn!(py, run()))?;
/// Ok(()) /// Ok(())
/// }); /// });
/// ///
@ -228,16 +227,29 @@ pub mod _detail {
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
/// The code must be compiled into a file `example.so`. ///
/// In your `Cargo.toml`, use the `extension-module` feature for the `cpython` dependency:
/// ```cargo
/// [dependencies.cpython]
/// version = "*"
/// features = ["extension-module"]
/// ```
/// The full example project can be found at:
/// https://github.com/dgrunwald/rust-cpython/tree/master/extensions/hello
///
/// Rust will compile the code into a file named `libhello.so`, but we have to
/// rename the file in order to use it with Python:
/// ///
/// ```bash /// ```bash
/// rustc example.rs -o example.so /// cp ./target/debug/libhello.so ./hello.so
/// ``` /// ```
/// It can then be imported into Python: /// (Note: on Mac OS you will have to rename `libhello.dynlib` to `libhello.so`)
///
/// The extension module can then be imported into Python:
/// ///
/// ```python /// ```python
/// >>> import example /// >>> import hello
/// >>> example.run() /// >>> hello.run()
/// Rust says: Hello Python! /// Rust says: Hello Python!
/// ``` /// ```
/// ///