Skip to content

Commit 828a08b

Browse files
authored
Merge pull request #68 from PyO3/cbindgen
Add cbindgen to generate a header for cffi
2 parents 5375271 + d7f58a2 commit 828a08b

11 files changed

+126
-104
lines changed

Cargo.lock

+69-51
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
authors = ["konstin <[email protected]>"]
33
name = "pyo3-pack"
4-
version = "0.4.3"
4+
version = "0.5.0-alpha.1"
55
description = "Build and publish crates with pyo3 bindings as python packages"
66
exclude = ["get-fourtytwo/**/*", "integration-test/**/*", "sysconfig/*"]
77
readme = "Readme.md"
@@ -29,13 +29,13 @@ failure = "0.1.5"
2929
keyring = { version = "0.6.1", optional = true }
3030
reqwest = { version = "0.9.8", optional = true, default-features = false }
3131
rpassword = { version = "2.1.0", optional = true }
32-
serde_json = "1.0.35"
32+
serde_json = "1.0.36"
3333
sha2 = "0.8.0"
3434
structopt = "0.2.14"
3535
target_info = "0.1.0"
3636
toml = "0.4.10"
3737
zip = "0.5.0"
38-
serde = { version = "1.0.84", features = ["derive"] }
38+
serde = { version = "1.0.85", features = ["derive"] }
3939
human-panic = { version = "1.0.1", optional = true }
4040
regex = "1.1.0"
4141
indicatif = "0.11.0"
@@ -45,6 +45,7 @@ goblin = { version = "0.0.19", optional = true }
4545
pretty_env_logger = { version = "0.3.0", optional = true }
4646
platforms = "0.2.0"
4747
shlex = "0.1.1"
48+
cbindgen = { git = "https://github.com/konstin/cbindgen", branch = "serde_and_failure" }
4849

4950
[dev-dependencies]
5051
indoc = "0.3.1"

Changelog.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
* The [konstin2/pyo3-pack](https://cloud.docker.com/u/konstin2/repository/docker/konstin2/pyo3-pack) docker image makes it easy to build fully manylinux compliant wheels. See the readme for usage details.
13+
* pyo3-pack will generate a header for cffi crates using cbinding, which means you don't need a `build.rs` anymore. The option to provide your own header file using a `build.rs` still exists.
14+
* The `--manxlinux=[1|1-unchecked|off]` option allows to build for manylinux1, both with audithweel (`1`) and without (`1-unchecked`), but also for the native linux tag with `off`. It is forward compatible to manylinux 2010.
15+
1016
### Changed
1117

12-
* Switched to rustls. This means the upload feature can be used from the docker container and builds of pyo3-pack itself are auditwheel compliant with the default features.
13-
* The `--skip-auditwheel` has been deprecated for a new `--manxlinux=[1|1-unchecked|off]`, which allows to build for the native linux tag with `off` and is forward compatible to manylinux 2010.
18+
* The `--skip-auditwheel` flag has been deprecated in favor of `--manxlinux=[1|1-unchecked|off]`.
19+
* Switched to rustls. This means the upload feature can be used from the docker container and builds of pyo3-pack itself are manylinux compliant when compiled with the musl target.
1420

1521
## [0.4.2] - 2018-12-15
1622

Readme.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,17 @@ classifier = ["Programming Language :: Python"]
5252

5353
For pyo3 and rust-cpython, pyo3-pack can only build packages for installed python versions, so you might want to use pyenv, deadsnakes or docker for building. If you don't set your own interpreters with `-i`, a heuristic is used to search for python installations. You can get a list all found versions with the `list-python` subcommand.
5454

55-
5655
## Cffi
5756

5857
Cffi wheels are compatible with all python versions, but they need to have `cffi` installed for the python used for building (`pip install cffi`).
5958

60-
Until [eqrion/cbdingen#203](https://github.com/eqrion/cbindgen/issues/203) is resolved, you also need cbindgen 0.6.4 as build dependency and a build script that writes c headers to a file called `target/header.h` (use `extern crate cbindgen` for rust 2015 Crates):
59+
pyo3-pack will run cbindgen and generate cffi bindings. You can override this with a build script that writes a header to `target/header.h`.
60+
61+
<details>
62+
<summary>Example of a custom build script</summary>
6163

6264
```rust
63-
use cbindgen;
65+
use cbindgen; // Use `extern crate cbindgen` in rust 2015
6466
use std::env;
6567
use std::path::Path;
6668

@@ -77,6 +79,8 @@ fn main() {
7779
}
7880
```
7981

82+
</details>
83+
8084
## Manylinux and auditwheel
8185

8286
For portability reasons, native python modules on linux must only dynamically link a set of very few libraries which are installed basically everywhere, hence the name manylinux. The pypa offers a special docker container and a tool called [auditwheel](https://github.com/pypa/auditwheel/) to ensure compliance with the [manylinux rules](https://www.python.org/dev/peps/pep-0513/#the-manylinux1-policy).
@@ -89,7 +93,7 @@ For full manylinux compliance you need to compile in a cent os 5 docker containe
8993
docker run --rm -v $(pwd):/io konstin2/pyo3-pack build
9094
```
9195

92-
pyo3-pack itself is manylinux compliant with the default features. The binaries on the release pages have keyring integration (though the `password-storage` feature), which is not manylinux compliant.
96+
pyo3-pack itself is manylinux compliant when compiled for the musl target. The binaries on the release pages have additional keyring integration (through the `password-storage` feature), which is not manylinux compliant.
9397

9498
### Build
9599

points/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
name = "points"
33
version = "0.1.0"
44
authors = ["Armin Ronacher <[email protected]>"]
5-
build = "build.rs"
65
edition = "2018"
76

87
[lib]

points/build.rs

-16
This file was deleted.

src/build_context.rs

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ impl BuildContext {
179179

180180
write_cffi_module(
181181
&mut builder,
182+
self.manifest_path.parent().unwrap(),
182183
&self.module_name,
183184
&artifact,
184185
&self.interpreter[0].executable,

src/build_options.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl BuildOptions {
9797
let manifest_file = self
9898
.manifest_path
9999
.canonicalize()
100-
.map_err(|e| format_err!("Can't find {}: {}", self.manifest_path.display(), e))?;
100+
.context(format_err!("Can't find {}", self.manifest_path.display()))?;
101101

102102
if !self.manifest_path.is_file() {
103103
bail!("{} must be a path to a Cargo.toml", manifest_file.display());

src/compile.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,10 @@ fn get_build_plan(shared_args: &[&str]) -> Result<SerializedBuildPlan, Error> {
5353
.args(build_plan_args)
5454
.args(shared_args)
5555
.output()
56-
.map_err(|e| {
57-
format_err!(
58-
"Failed to get a build plan from cargo: {} ({})",
59-
e,
60-
command_formatted
61-
)
62-
})?;
56+
.context(format_err!(
57+
"Failed to get a build plan from cargo with `{}`",
58+
command_formatted
59+
))?;
6360

6461
if !build_plan.status.success() {
6562
bail!(

src/develop.rs

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub fn develop(
6969

7070
write_cffi_module(
7171
&mut builder,
72+
&build_context.manifest_path.parent().unwrap(),
7273
&build_context.module_name,
7374
&artifact,
7475
&interpreter.executable,

src/module_writer.rs

+30-19
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::Metadata21;
44
use crate::PythonInterpreter;
55
use crate::Target;
66
use base64;
7-
use failure::{bail, Context, Error};
7+
use failure::{bail, Context, Error, ResultExt};
88
use sha2::{Digest, Sha256};
99
use std::collections::HashMap;
1010
use std::ffi::OsStr;
@@ -255,17 +255,34 @@ del os
255255
"#
256256
}
257257

258-
/// Returns the content of what will become ffi.py by invocing cffi
258+
/// Returns the content of what will become ffi.py by invocing cbindgen and cffi
259+
///
260+
/// First we check if user has provided their own header at `target/header.h`, otherwise
261+
/// we run cbindgen to generate onw.
259262
///
260263
/// We're using the cffi recompiler, which reads the header, translates them into instructions
261264
/// how to load the shared library without the header and then writes those instructions to a
262265
/// file called `ffi.py`. This `ffi.py` will expose an object called `ffi`. This object is used
263266
/// in `__init__.py` to load the shared library into a module called `lib`.
264-
pub fn generate_cffi_declarations(
265-
cbindgen_header: &Path,
266-
python: &PathBuf,
267-
) -> Result<String, Error> {
267+
pub fn generate_cffi_declarations(crate_dir: &Path, python: &PathBuf) -> Result<String, Error> {
268268
let tempdir = tempdir()?;
269+
let maybe_header = crate_dir.join("target").join("header.h");
270+
271+
let header;
272+
if maybe_header.is_file() {
273+
println!("Using the existing header at {}", maybe_header.display());
274+
header = maybe_header;
275+
} else {
276+
let bindings = cbindgen::Builder::new()
277+
.with_no_includes()
278+
.with_language(cbindgen::Language::C)
279+
.with_crate(crate_dir)
280+
.generate()
281+
.context("Failed to run cbindgen")?;
282+
header = tempdir.as_ref().join("header.h");
283+
bindings.write_to_file(&header);
284+
}
285+
269286
let ffi_py = tempdir.as_ref().join("ffi.py");
270287

271288
let cffi_invocation = format!(
@@ -274,13 +291,12 @@ import cffi
274291
from cffi import recompiler
275292
276293
ffi = cffi.FFI()
277-
with open("{cbindgen_header}") as header:
294+
with open("{header}") as header:
278295
ffi.cdef(header.read())
279-
ffi.set_source("a", None)
280296
recompiler.make_py_source(ffi, "ffi", "{ffi_py}")
281297
"#,
282298
ffi_py = ffi_py.display(),
283-
cbindgen_header = cbindgen_header.display(),
299+
header = header.display(),
284300
);
285301

286302
let output = Command::new(python)
@@ -291,7 +307,9 @@ recompiler.make_py_source(ffi, "ffi", "{ffi_py}")
291307
bail!("Failed to generate cffi declarations");
292308
}
293309

294-
Ok(fs::read_to_string(ffi_py)?)
310+
let ffi_py_content = fs::read_to_string(ffi_py)?;
311+
tempdir.close()?;
312+
Ok(ffi_py_content)
295313
}
296314

297315
/// Copies the shared library into the module, which is the only extra file needed with bindings
@@ -315,23 +333,16 @@ pub fn write_bindings_module(
315333
/// Creates the cffi module with the shared library, the cffi declarations and the cffi loader
316334
pub fn write_cffi_module(
317335
writer: &mut impl ModuleWriter,
336+
crate_dir: &Path,
318337
module_name: &str,
319338
artifact: &Path,
320339
python: &PathBuf,
321340
) -> Result<(), Error> {
322341
let module = Path::new(module_name);
323342

324-
// This should do until cbindgen gets their serde issues fixed
325-
let header = artifact
326-
.parent()
327-
.unwrap()
328-
.parent()
329-
.unwrap()
330-
.join("header.h");
331-
332343
writer.add_directory(&module)?;
333344
writer.add_bytes(&module.join("__init__.py"), cffi_init_file().as_bytes())?;
334-
let cffi_declarations = generate_cffi_declarations(&header, python)?;
345+
let cffi_declarations = generate_cffi_declarations(&crate_dir, python)?;
335346
writer.add_bytes(&module.join("ffi.py"), cffi_declarations.as_bytes())?;
336347
writer.add_file(&module.join("native.so"), &artifact)?;
337348

0 commit comments

Comments
 (0)