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:
parent
6a41227969
commit
b3e20e900b
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
//!
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue