Skip to content

Commit f1c5def

Browse files
committed
build: avoid rebuilds when using clippy in a virtualenv
1 parent 6bfca27 commit f1c5def

File tree

3 files changed

+101
-85
lines changed

3 files changed

+101
-85
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
5353
- Fix use of Python argument for #[pymethods] inside macro expansions. [#1505](https://github.com/PyO3/pyo3/pull/1505)
5454
- Always use cross-compiling configuration if any of the environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514)
5555
- Support `EnvironmentError`, `IOError`, and `WindowsError` on PyPy. [#1533](https://github.com/PyO3/pyo3/pull/1533)
56-
- Segfault when dereferencing `ffi::PyDateTimeAPI` without the GIL. [#1563](https://github.com/PyO3/pyo3/pull/1563)
56+
- Fix unneccessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557)
57+
- Fix segfault when dereferencing `ffi::PyDateTimeAPI` without the GIL. [#1563](https://github.com/PyO3/pyo3/pull/1563)
5758

5859
## [0.13.2] - 2021-02-12
5960
### Packaging

build.rs

Lines changed: 98 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{
22
collections::{HashMap, HashSet},
33
convert::AsRef,
44
env,
5+
ffi::OsString,
56
fs::{self, DirEntry},
67
io,
78
path::{Path, PathBuf},
@@ -35,6 +36,20 @@ macro_rules! warn {
3536
};
3637
}
3738

39+
/// Gets an environment variable owned by cargo.
40+
///
41+
/// Environment variables set by cargo are expected to be valid UTF8.
42+
fn cargo_env_var(var: &str) -> Option<String> {
43+
env::var_os(var).map(|os_string| os_string.to_str().unwrap().into())
44+
}
45+
46+
/// Gets an external environment variable, and registers the build script to rerun if
47+
/// the variable changes.
48+
fn env_var(var: &str) -> Option<OsString> {
49+
println!("cargo:rerun-if-env-changed={}", var);
50+
env::var_os(var)
51+
}
52+
3853
/// Information returned from python interpreter
3954
#[derive(Debug)]
4055
struct InterpreterConfig {
@@ -83,7 +98,7 @@ impl FromStr for PythonInterpreterKind {
8398
}
8499

85100
fn is_abi3() -> bool {
86-
env::var_os("CARGO_FEATURE_ABI3").is_some()
101+
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
87102
}
88103

89104
trait GetPrimitive {
@@ -119,40 +134,29 @@ struct CrossCompileConfig {
119134
arch: String,
120135
}
121136

122-
impl CrossCompileConfig {
123-
fn new() -> Result<Self> {
124-
Ok(CrossCompileConfig {
125-
lib_dir: CrossCompileConfig::validate_variable("PYO3_CROSS_LIB_DIR")?,
126-
os: env::var("CARGO_CFG_TARGET_OS").unwrap(),
127-
arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
128-
version: env::var_os("PYO3_CROSS_PYTHON_VERSION").map(|s| s.into_string().unwrap()),
129-
})
137+
fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
138+
for var in &[
139+
"PYO3_CROSS",
140+
"PYO3_CROSS_LIB_DIR",
141+
"PYO3_CROSS_PYTHON_VERSION",
142+
] {
143+
println!("cargo:rerun-if-env-changed={}", var);
130144
}
131145

132-
fn validate_variable(var: &str) -> Result<PathBuf> {
133-
let path = match env::var_os(var) {
134-
Some(v) => v,
135-
None => bail!(
136-
"Must provide {} environment variable when cross-compiling",
137-
var
138-
),
139-
};
146+
let cross_lib_dir = env_var("PYO3_CROSS_LIB_DIR");
147+
let cross_python_version = env_var("PYO3_CROSS_PYTHON_VERSION");
140148

141-
if fs::metadata(&path).is_err() {
142-
bail!("{} value of {:?} does not exist", var, path)
143-
}
149+
let target_arch = cargo_env_var("CARGO_CFG_TARGET_ARCH");
150+
let target_vendor = cargo_env_var("CARGO_CFG_TARGET_VENDOR");
151+
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS");
144152

145-
Ok(path.into())
146-
}
147-
}
148-
149-
fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
150-
if env::var_os("PYO3_CROSS").is_none()
151-
&& env::var_os("PYO3_CROSS_LIB_DIR").is_none()
152-
&& env::var_os("PYO3_CROSS_PYTHON_VERSION").is_none()
153+
if env_var("PYO3_CROSS").is_none() && cross_lib_dir.is_none() && cross_python_version.is_none()
153154
{
154-
let target = env::var("TARGET")?;
155-
let host = env::var("HOST")?;
155+
// No cross-compiling environment variables set; try to determine if this is a known case
156+
// which is not cross-compilation.
157+
158+
let target = cargo_env_var("TARGET").unwrap();
159+
let host = cargo_env_var("HOST").unwrap();
156160
if target == host {
157161
// Not cross-compiling
158162
return Ok(None);
@@ -173,20 +177,32 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
173177
return Ok(None);
174178
}
175179

176-
if host.starts_with(&format!(
177-
"{}-{}-{}",
178-
env::var("CARGO_CFG_TARGET_ARCH")?,
179-
env::var("CARGO_CFG_TARGET_VENDOR")?,
180-
env::var("CARGO_CFG_TARGET_OS")?
181-
)) {
182-
// Not cross-compiling if arch-vendor-os is all the same
183-
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
184-
return Ok(None);
180+
if let (Some(arch), Some(vendor), Some(os)) = (&target_arch, &target_vendor, &target_os) {
181+
if host.starts_with(&format!("{}-{}-{}", arch, vendor, os)) {
182+
// Not cross-compiling if arch-vendor-os is all the same
183+
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
184+
return Ok(None);
185+
}
185186
}
186187
}
187188

188-
// Cross-compiling on any other platform
189-
Ok(Some(CrossCompileConfig::new()?))
189+
// At this point we assume that we are cross compiling.
190+
191+
Ok(Some(CrossCompileConfig {
192+
lib_dir: cross_lib_dir
193+
.ok_or("The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling")?
194+
.into(),
195+
os: target_os.unwrap(),
196+
arch: target_arch.unwrap(),
197+
version: cross_python_version
198+
.map(|os_string| {
199+
os_string
200+
.to_str()
201+
.ok_or("PYO3_CROSS_PYTHON_VERSION is not valid utf-8.")
202+
.map(str::to_owned)
203+
})
204+
.transpose()?,
205+
}))
190206
}
191207

192208
/// A list of python interpreter compile-time preprocessor defines that
@@ -226,7 +242,7 @@ impl BuildFlags {
226242
/// the interpreter and printing variables of interest from
227243
/// sysconfig.get_config_vars.
228244
fn from_interpreter(python_path: &Path) -> Result<Self> {
229-
if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
245+
if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
230246
return Ok(Self::windows_hardcoded());
231247
}
232248

@@ -377,7 +393,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
377393
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
378394
fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<PathBuf> {
379395
let sysconfig_paths = search_lib_dir(&cross.lib_dir, &cross);
380-
let sysconfig_name = env::var_os("_PYTHON_SYSCONFIGDATA_NAME");
396+
let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
381397
let mut sysconfig_paths = sysconfig_paths
382398
.iter()
383399
.filter_map(|p| {
@@ -525,7 +541,7 @@ fn windows_hardcoded_cross_compile(
525541
fn load_cross_compile_info(
526542
cross_compile_config: CrossCompileConfig,
527543
) -> Result<(InterpreterConfig, BuildFlags)> {
528-
match env::var_os("CARGO_CFG_TARGET_FAMILY") {
544+
match cargo_env_var("CARGO_CFG_TARGET_FAMILY") {
529545
// Configure for unix platforms using the sysconfigdata file
530546
Some(os) if os == "unix" => load_cross_compile_from_sysconfigdata(cross_compile_config),
531547
// Use hardcoded interpreter config when targeting Windows
@@ -580,14 +596,14 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
580596
}
581597

582598
fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
583-
let link_name = if env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" {
599+
let link_name = if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
584600
if is_abi3() {
585601
// Link against python3.lib for the stable ABI on Windows.
586602
// See https://www.python.org/dev/peps/pep-0384/#linkage
587603
//
588604
// This contains only the limited ABI symbols.
589605
"pythonXY:python3".to_owned()
590-
} else if env::var("CARGO_CFG_TARGET_ENV").unwrap().as_str() == "gnu" {
606+
} else if cargo_env_var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu" {
591607
// https://packages.msys2.org/base/mingw-w64-python
592608
format!(
593609
"pythonXY:python{}.{}",
@@ -613,13 +629,31 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
613629
)
614630
}
615631

632+
fn get_venv_path() -> Option<PathBuf> {
633+
match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
634+
(Some(dir), None) => Some(PathBuf::from(dir)),
635+
(None, Some(dir)) => Some(PathBuf::from(dir)),
636+
(Some(_), Some(_)) => {
637+
warn!(
638+
"Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
639+
locating the Python interpreter until you unset one of them."
640+
);
641+
None
642+
}
643+
(None, None) => None,
644+
}
645+
}
646+
616647
fn find_interpreter() -> Result<PathBuf> {
617-
if let Some(exe) = env::var_os("PYO3_PYTHON") {
618-
Ok(exe.into())
619-
} else if let Some(exe) = env::var_os("PYTHON_SYS_EXECUTABLE") {
620-
// Backwards-compatible name for PYO3_PYTHON; this may be removed at some point in the future.
648+
if let Some(exe) = env_var("PYO3_PYTHON") {
621649
Ok(exe.into())
650+
} else if let Some(venv_path) = get_venv_path() {
651+
match cargo_env_var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
652+
"windows" => Ok(venv_path.join("Scripts\\python")),
653+
_ => Ok(venv_path.join("bin/python")),
654+
}
622655
} else {
656+
println!("cargo:rerun-if-env-changed=PATH");
623657
["python", "python3"]
624658
.iter()
625659
.find(|bin| {
@@ -692,7 +726,7 @@ print("calcsize_pointer", struct.calcsize("P"))
692726
let output = run_python_script(interpreter, script)?;
693727
let map: HashMap<String, String> = parse_script_output(&output);
694728
let shared = match (
695-
env::var("CARGO_CFG_TARGET_OS").unwrap().as_str(),
729+
cargo_env_var("CARGO_CFG_TARGET_OS").unwrap().as_str(),
696730
map["framework"].as_str(),
697731
map["shared"].as_str(),
698732
) {
@@ -735,8 +769,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
735769

736770
check_target_architecture(interpreter_config)?;
737771

738-
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
739-
let is_extension_module = env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some();
772+
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
773+
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
740774
match (is_extension_module, target_os.as_str()) {
741775
(_, "windows") => {
742776
// always link on windows, even with extension module
@@ -833,7 +867,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
833867

834868
fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<()> {
835869
// Try to check whether the target architecture matches the python library
836-
let rust_target = match env::var("CARGO_CFG_TARGET_POINTER_WIDTH")?.as_str() {
870+
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
871+
.unwrap()
872+
.as_str()
873+
{
837874
"64" => "64-bit",
838875
"32" => "32-bit",
839876
x => bail!("unexpected Rust target pointer width: {}", x),
@@ -868,10 +905,10 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
868905

869906
fn get_abi3_minor_version() -> Option<u8> {
870907
(PY3_MIN_MINOR..=ABI3_MAX_MINOR)
871-
.find(|i| env::var_os(format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
908+
.find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
872909
}
873910

874-
fn abi3_without_interpreter() -> Result<()> {
911+
fn configure_abi3_without_interpreter() {
875912
println!("cargo:rustc-cfg=Py_LIMITED_API");
876913
let mut flags = "FLAG_WITH_THREAD=1".to_string();
877914
let abi_version = get_abi3_minor_version().unwrap_or(ABI3_MAX_MINOR);
@@ -882,26 +919,25 @@ fn abi3_without_interpreter() -> Result<()> {
882919
println!("cargo:rustc-cfg=py_sys_config=\"WITH_THREAD\"");
883920
println!("cargo:python_flags={}", flags);
884921

885-
if env::var("CARGO_CFG_TARGET_FAMILY")? == "windows" {
922+
if cargo_env_var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" {
886923
// Unfortunately, on windows we can't build without at least providing
887924
// python.lib to the linker. While maturin tells the linker the location
888925
// of python.lib, we need to do the renaming here, otherwise cargo
889926
// complains that the crate using pyo3 does not contains a `#[link(...)]`
890927
// attribute with pythonXY.
891928
println!("cargo:rustc-link-lib=pythonXY:python3");
892929
}
893-
894-
Ok(())
895930
}
896931

897932
fn main_impl() -> Result<()> {
898933
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
899934
// We only check for the abi3-py3{ABI3_MAX_MINOR} because lower versions depend on it.
900-
if env::var_os("PYO3_NO_PYTHON").is_some()
901-
&& env::var_os(format!("CARGO_FEATURE_ABI3_PY3{}", ABI3_MAX_MINOR)).is_some()
935+
if cargo_env_var("PYO3_NO_PYTHON").is_some()
936+
&& cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", ABI3_MAX_MINOR)).is_some()
902937
{
903938
println!("cargo:rerun-if-env-changed=PYO3_NO_PYTHON");
904-
return abi3_without_interpreter();
939+
configure_abi3_without_interpreter();
940+
return Ok(());
905941
}
906942
// 1. Setup cfg variables so we can do conditional compilation in this library based on the
907943
// python interpeter's compilation flags. This is necessary for e.g. matching the right unicode
@@ -929,28 +965,6 @@ fn main_impl() -> Result<()> {
929965
println!("cargo:rustc-cfg={}=\"{}\"", CFG_KEY, flag)
930966
}
931967

932-
for var in &[
933-
"LIB",
934-
"LD_LIBRARY_PATH",
935-
"PYO3_PYTHON",
936-
"PYO3_CROSS",
937-
"PYO3_CROSS_LIB_DIR",
938-
"PYO3_CROSS_PYTHON_VERSION",
939-
] {
940-
println!("cargo:rerun-if-env-changed={}", var);
941-
}
942-
943-
if env::var_os("PYO3_PYTHON").is_none() {
944-
// When PYO3_PYTHON is not used, PYTHON_SYS_EXECUTABLE has the highest priority.
945-
// Let's watch it.
946-
println!("cargo:rerun-if-env-changed=PYTHON_SYS_EXECUTABLE");
947-
if env::var_os("PYTHON_SYS_EXECUTABLE").is_none() {
948-
// When PYTHON_SYS_EXECUTABLE is also not used, then we use PATH.
949-
// Let's watch this, too.
950-
println!("cargo:rerun-if-env-changed=PATH");
951-
}
952-
}
953-
954968
Ok(())
955969
}
956970

guide/src/building_and_distribution.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ See [github.com/japaric/rust-cross](https://github.com/japaric/rust-cross) for a
9090

9191
After you've obtained the above, you can build a cross compiled PyO3 module by setting a few extra environment variables:
9292

93+
* `PYO3_CROSS`: If present this variable forces PyO3 to configure as a cross-compilation.
9394
* `PYO3_CROSS_LIB_DIR`: This variable must be set to the directory containing the target's libpython DSO and the associated `_sysconfigdata*.py` file for Unix-like targets, or the Python DLL import libraries for the Windows target.
9495
* `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python installation. This variable is only needed if PyO3 cannot determine the version to target from `abi3-py3*` features, or if there are multiple versions of Python present in `PYO3_CROSS_LIB_DIR`.
9596

0 commit comments

Comments
 (0)