fix issues in bb13ec, support utf16, python fom PATH

* fix ucs4 build broken by bb13ec
* add utf16 decoding to unicode.from_py_object for
  narrow unicode builds
* change unicode narrow/wide cfg flag to be
  Py_UNICODE_SIZE_4 not Py_UNICODE_WIDE, which doesn't
  appear in sysconfig
* support framework builds on os x
* python27-sys exports compilation flags as cargo vars,
  and rust-python resurrects them as cfg flags
* travis runs against local python27-sys
* rust-cpython depends on git python27-sys, because
  the one on cargo is now incompatible with it (since bb13ec)
This commit is contained in:
James Salter 2015-05-17 13:14:47 +01:00
parent 6a41227969
commit b3e20e900b
8 changed files with 230 additions and 44 deletions

View File

@ -2,6 +2,8 @@ language: rust
env:
global:
- secure: g4kCg8twONwKPquuJmYrvGjo2n0lNtWTbyzFOITNn8FgCxNK2j38Qc9/UhErTR3g3rDjVzsTHZ8FTH7TJZrOK1Nzz90tJG6JHqUv77ufkcBlxgwwjilOz84uQhkDTMpLitMEeQDLEynKeWbxrjtc5LIpjEkxOPk5eiqwzKRN14c=
before_script:
- mkdir -p .cargo && echo 'paths = ["python27-sys"]' > .cargo/config
script:
- cargo build --verbose
- cargo test --verbose

View File

@ -18,8 +18,12 @@ exclude = [
".gitignore",
".travis.yml",
]
build = "build.rs"
[dependencies]
python27-sys="*"
libc="*"
num="*"
# TODO: crates.io version doesn't have necessary conditional cfg flags yet
[dependencies.python27-sys]
git = "https://github.com/dgrunwald/rust-cpython.git"

21
build.rs Normal file
View File

@ -0,0 +1,21 @@
use std::env;
const CFG_KEY: &'static str = "py_sys_config";
fn main() {
// python{27,3.x}-sys/build.rs passes python interpreter compile flags via
// environment variable (using the 'links' mechanism in the cargo.toml).
let flags = env::var("DEP_PYTHON27_PYTHON_FLAGS").unwrap();
for f in flags.split(",") {
// write out flags as --cfg so that the same #cfg blocks can be used
// in rust-cpython as in the -sys libs
let key_and_val: Vec<&str> = f.split("=").collect();
let key = key_and_val[0];
let val = key_and_val[1];
if key.starts_with("FLAG") {
println!("cargo:rustc-cfg={}=\"{}\"", CFG_KEY, &key[5..])
} else {
println!("cargo:rustc-cfg={}=\"{}_{}\"", CFG_KEY, &key[4..], val);
}
}
}

View File

@ -12,6 +12,7 @@ homepage = "https://github.com/dgrunwald/rust-python27-sys"
repository = "https://github.com/dgrunwald/rust-python27-sys.git"
license = "Python-2.0"
authors = ["Daniel Grunwald <daniel@danielgrunwald.de>"]
links = "python27"
build = "build.rs"
exclude = [
".gitignore",

View File

@ -38,15 +38,7 @@ static SYSCONFIG_VALUES: [&'static str; 1] = [
/// Examine python's compile flags to pass to cfg by launching
/// the interpreter and printing variables of interest from
/// sysconfig.get_config_vars.
fn get_config_vars() -> Result<HashMap<String, String>, String> {
let exec_prefix = pkg_config::Config::get_variable(
"python-2.7", "exec_prefix").unwrap();
// assume path to the pkg_config python interpreter is
// {exec_prefix}/bin/python - this might not hold for all platforms, but
// the .pc doesn't give us anything else to go on
let python_path = format!("{}/bin/python", exec_prefix);
fn get_config_vars(python_path: &String) -> Result<HashMap<String, String>, String> {
let mut script = "import sysconfig; \
config = sysconfig.get_config_vars();".to_owned();
@ -84,10 +76,14 @@ config = sysconfig.get_config_vars();".to_owned();
return Ok(var_map);
}
fn is_value(key: &str) -> bool {
SYSCONFIG_VALUES.iter().find(|x| **x == key).is_some()
}
fn cfg_line_for_var(key: &str, val: &str) -> Option<String> {
if SYSCONFIG_VALUES.iter().find(|x| **x == key).is_some() {
if is_value(key) {
// is a value; suffix the key name with the value
Some(format!("cargo:rustc-cfg={}=\"{}_{}\"", CFG_KEY, key, val))
Some(format!("cargo:rustc-cfg={}=\"{}_{}\"\n", CFG_KEY, key, val))
} else if val != "0" {
// is a flag that isn't zero
Some(format!("cargo:rustc-cfg={}=\"{}\"", CFG_KEY, key))
@ -97,19 +93,143 @@ fn cfg_line_for_var(key: &str, val: &str) -> Option<String> {
}
}
fn main() {
// By default, use pkg_config to locate a python 2.7 to use.
//
// TODO - allow the user to specify a python via environment variables
// (PYTHONHOME?) - necessary for systems with no pkg-config, or for
// compiling against pythons other than the pkg-config one.
/// Run a python script using the 'python' located by PATH.
fn run_python_script(script: &str) -> Result<String, String> {
let mut cmd = Command::new("python");
cmd.arg("-c").arg(script);
pkg_config::find_library("python-2.7").unwrap();
let config_map = get_config_vars().unwrap();
let out = try!(cmd.output().map_err(|e| {
format!("failed to run python interpreter `{:?}`: {}", cmd, e)
}));
if !out.status.success() {
let stderr = String::from_utf8(out.stderr).unwrap();
let mut msg = format!("python script failed with stderr:\n\n");
msg.push_str(&stderr);
return Err(msg);
}
let out = String::from_utf8(out.stdout).unwrap();
return Ok(out);
}
#[cfg(not(target_os="macos"))]
fn get_rustc_link_lib(enable_shared: bool) -> Result<String, String> {
if enable_shared {
Ok("cargo:rustc-link-lib=python2.7".to_owned())
} else {
Ok("cargo:rustc-link-lib=static=python2.7".to_owned())
}
}
#[cfg(target_os="macos")]
fn get_macos_linkmodel() -> Result<String, String> {
let script = "import MacOS; print MacOS.linkmodel;";
let out = run_python_script(script).unwrap();
Ok(out.trim_right().to_owned())
}
#[cfg(target_os="macos")]
fn get_rustc_link_lib(_: bool) -> Result<String, String> {
// os x can be linked to a framework or static or dynamic, and
// Py_ENABLE_SHARED is wrong; framework means shared library
match get_macos_linkmodel().unwrap().as_ref() {
"static" => Ok("cargo:rustc-link-lib=static=python2.7".to_owned()),
"dynamic" => Ok("cargo:rustc-link-lib=python2.7".to_owned()),
"framework" => Ok("cargo:rustc-link-lib=python2.7".to_owned()),
other => Err(format!("unknown linkmodel {}", other))
}
}
/// Deduce configuration from the 'python' in the current PATH and print
/// cargo vars to stdout.
///
/// Note that if that python isn't version 2.7, this will error.
fn configure_from_path() -> Result<String, String> {
let script = "import sys; import sysconfig; print(sys.version_info[0:2]); \
print(sysconfig.get_config_var('LIBDIR')); \
print(sysconfig.get_config_var('Py_ENABLE_SHARED')); \
print(sys.exec_prefix);";
let out = run_python_script(script).unwrap();
let lines: Vec<&str> = out.split("\n").collect();
let version: &str = lines[0];
let libpath: &str = lines[1];
let enable_shared: &str = lines[2];
let exec_prefix: &str = lines[3];
if version != "(2, 7)" {
return Err(format!("'python' is not version 2.7 (is {})", version));
}
println!("{}", get_rustc_link_lib(enable_shared == "1").unwrap());
println!("cargo:rustc-link-search=native={}", libpath);
return Ok(format!("{}/bin/python", exec_prefix));
}
/// Deduce configuration from the python-2.7 in pkg-config and print
/// cargo vars to stdout.
fn configure_from_pkgconfig() -> Result<String, String> {
// this emits relevant build info to stdout, which is picked up by the
// build chain (funny name for something with side-effects!)
try!(pkg_config::find_library("python-2.7"));
let exec_prefix = pkg_config::Config::get_variable("python-2.7",
"exec_prefix").unwrap();
// assume path to the pkg_config python interpreter is
// {exec_prefix}/bin/python - this might not hold for all platforms, but
// the .pc doesn't give us anything else to go on
return Ok(format!("{}/bin/python", exec_prefix));
}
fn main() {
// 1. Setup cfg variables so we can do conditional compilation in this
// library based on the python interpeter's compilation flags. This is
// necessary for e.g. matching the right unicode and threading interfaces.
//
// By default, try to use pkgconfig - this seems to be a rust norm.
//
// If you want to use a different python, setting the
// PYTHON_2.7_NO_PKG_CONFIG environment variable will cause the script
// to pick up the python in your PATH (this will work smoothly with an
// activated virtualenv). If you have troubles with your shell accepting
// '.' in a var name, try using 'env'.
let python_interpreter_path = match configure_from_pkgconfig() {
Ok(p) => p,
// no pkgconfig - either it failed or user set the environment
// variable "PYTHON_2.7_NO_PKG_CONFIG".
Err(_) => configure_from_path().unwrap()
};
let config_map = get_config_vars(&python_interpreter_path).unwrap();
for (key, val) in &config_map {
match cfg_line_for_var(key, val) {
Some(line) => println!("{}", line),
None => ()
}
}
// 2. Export python interpreter compilation flags as cargo variables that
// will be visible to dependents. All flags will be available to dependent
// build scripts in the environment variable DEP_PYTHON27_PYTHON_FLAGS as
// comma separated list; each item in the list looks like
//
// {VAL,FLAG}_{flag_name}=val;
//
// FLAG indicates the variable is always 0 or 1
// VAL indicates it can take on any value
//
// rust-cypthon/build.rs contains an example of how to unpack this data
// into cfg flags that replicate the ones present in this library, so
// you can use the same cfg syntax.
let flags: String = config_map.iter().fold("".to_owned(), |memo, (key, val)| {
if is_value(key) {
memo + format!("VAL_{}={},", key, val).as_ref()
} else if val != "0" {
memo + format!("FLAG_{}={},", key, val).as_ref()
} else {
memo
}
});
println!("cargo:python_flags={}", &flags[..flags.len()-1]);
}

View File

@ -2,16 +2,16 @@ use libc::{c_char, c_int, c_long, c_double, wchar_t};
use pyport::Py_ssize_t;
use object::*;
#[cfg(py_sys_config="Py_UNICODE_WIDE")]
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
pub const Py_UNICODE_SIZE : Py_ssize_t = 4;
#[cfg(not(py_sys_config="Py_UNICODE_WIDE"))]
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
pub const Py_UNICODE_SIZE : Py_ssize_t = 2;
pub type Py_UCS4 = u32;
#[cfg(py_sys_config="Py_UNICODE_WIDE")]
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
pub type Py_UNICODE = u32;
#[cfg(not(py_sys_config="Py_UNICODE_WIDE"))]
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
pub type Py_UNICODE = u16;
#[repr(C)]
@ -68,7 +68,7 @@ pub const Py_UNICODE_REPLACEMENT_CHARACTER : Py_UNICODE = 0xFFFD;
#[allow(dead_code)]
#[cfg(py_sys_config="Py_UNICODE_WIDE")]
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
extern "C" {
fn PyUnicodeUCS4_FromUnicode(u: *const Py_UNICODE, size: Py_ssize_t)
-> *mut PyObject;
@ -312,7 +312,7 @@ extern "C" {
}
#[allow(dead_code)]
#[cfg(not(py_sys_config="Py_UNICODE_WIDE"))]
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
extern "C" {
fn PyUnicodeUCS2_FromUnicode(u: *const Py_UNICODE, size: Py_ssize_t)
-> *mut PyObject;
@ -555,26 +555,26 @@ extern "C" {
fn _PyUnicodeUCS2_IsAlpha(ch: Py_UNICODE) -> c_int;
}
/*#[inline(always)]
#[cfg(py_sys_config="Py_UNICODE_WIDE")]
#[inline(always)]
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
pub unsafe fn PyUnicode_FromStringAndSize(u: *const c_char, size: Py_ssize_t) -> *mut PyObject {
PyUnicodeUCS4_FromStringAndSize(u, size)
}*/
}
#[inline(always)]
#[cfg(not(py_sys_config="Py_UNICODE_WIDE"))]
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
pub unsafe fn PyUnicode_FromStringAndSize(u: *const c_char, size: Py_ssize_t) -> *mut PyObject {
PyUnicodeUCS2_FromStringAndSize(u, size)
}
#[inline(always)]
#[cfg(py_sys_config="Py_UNICODE_WIDE")]
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
pub unsafe fn PyUnicode_AsUTF8String(u: *mut PyObject) -> *mut PyObject {
PyUnicodeUCS4_AsUTF8String(u)
}
#[inline(always)]
#[cfg(not(py_sys_config="Py_UNICODE_WIDE"))]
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
pub unsafe fn PyUnicode_AsUTF8String(u: *mut PyObject) -> *mut PyObject {
PyUnicodeUCS2_AsUTF8String(u)
}

View File

@ -23,6 +23,7 @@
#![feature(slice_patterns)] // for tuple_conversion macros
#![feature(utf8_error)] // for translating Utf8Error to python exception
#![allow(unused_imports, unused_variables)]
#![feature(unicode)]
//! Rust bindings to the python interpreter.
//!

View File

@ -16,6 +16,10 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
extern crate rustc_unicode;
use self::rustc_unicode::str as unicode_str;
use self::rustc_unicode::str::Utf16Item;
use std;
use std::{char, str};
use std::ascii::AsciiExt;
@ -112,10 +116,54 @@ impl <'p, 'a> ToPyObject<'p> for &'a str {
}
}
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
fn u32_as_bytes(input: &[u32]) -> &[u8] {
unsafe { std::mem::transmute(input) }
}
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
fn u16_as_bytes(input: &[u16]) -> &[u8] {
unsafe { std::mem::transmute(input) }
}
#[cfg(py_sys_config="Py_UNICODE_SIZE_4")]
fn unicode_buf_to_str<'p>(p: Python<'p>, u: &[ffi::Py_UNICODE])
-> Result<String, PyErr<'p> > {
let mut s = String::with_capacity(u.len());
for (i, &c) in u.iter().enumerate() {
match char::from_u32(c) {
Some(c) => s.push(c),
None => {
let e = try!(exc::UnicodeDecodeError::new(
p, cstr!("utf-32"),
u32_as_bytes(u), i .. i+1,
cstr!("invalid code point")));
return Err(PyErr::new(e));
}
}
}
return Ok(s);
}
#[cfg(not(py_sys_config="Py_UNICODE_SIZE_4"))]
fn unicode_buf_to_str<'p>(p: Python<'p>, u: &[ffi::Py_UNICODE])
-> Result<String, PyErr<'p> > {
let mut s = String::with_capacity(u.len());
for (i, c) in unicode_str::utf16_items(u).enumerate() {
match c {
Utf16Item::ScalarValue(c) => s.push(c),
Utf16Item::LoneSurrogate(_) => {
let e = try!(exc::UnicodeDecodeError::new(
p, cstr!("utf-16"),
u16_as_bytes(u), i .. i+1,
cstr!("invalid code point")));
return Err(PyErr::new(e));
}
}
}
return Ok(s);
}
impl <'p, 's> FromPyObject<'p, 's> for Cow<'s, str> {
fn from_py_object(o: &'s PyObject<'p>) -> PyResult<'p, Cow<'s, str>> {
let py = o.python();
@ -126,17 +174,7 @@ impl <'p, 's> FromPyObject<'p, 's> for Cow<'s, str> {
}
} else if let Ok(u) = o.cast_as::<PyUnicode>() {
let u = u.as_slice();
let mut s = String::with_capacity(u.len());
for (i, &c) in u.iter().enumerate() {
match char::from_u32(c) {
Some(c) => s.push(c),
None => {
let e = try!(exc::UnicodeDecodeError::new(
py, cstr!("utf-32"), u32_as_bytes(u), i .. i+1, cstr!("invalid code point")));
return Err(PyErr::new(e));
}
}
}
let s = unicode_buf_to_str(py, u).unwrap();
Ok(Cow::Owned(s))
} else {
Err(PyErr::new_lazy_init(py.get_type::<exc::TypeError>(), None))
@ -158,4 +196,3 @@ fn test_non_bmp() {
let py_string = s.to_py_object(py);
assert_eq!(s, py_string.extract::<Cow<str>>().unwrap());
}