Skip to content

Commit 4cd39a4

Browse files
committed
build: avoid rebuilds when using clippy in a virtualenv
1 parent a9942bd commit 4cd39a4

File tree

3 files changed

+100
-84
lines changed

3 files changed

+100
-84
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
5252
- Fix use of Python argument for #[pymethods] inside macro expansions. [#1505](https://github.com/PyO3/pyo3/pull/1505)
5353
- Always use cross-compiling configuration if any of the environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514)
5454
- Support `EnvironmentError`, `IOError`, and `WindowsError` on PyPy. [#1533](https://github.com/PyO3/pyo3/pull/1533)
55+
- Fix unneccessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557)
5556

5657
## [0.13.2] - 2021-02-12
5758
### 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},
@@ -30,6 +31,20 @@ macro_rules! warn {
3031
};
3132
}
3233

34+
/// Gets an environment variable owned by cargo.
35+
///
36+
/// Environment variables set by cargo are expected to be valid UTF8.
37+
fn cargo_env_var(var: &str) -> Option<String> {
38+
env::var_os(var).map(|os_string| os_string.to_str().unwrap().into())
39+
}
40+
41+
/// Gets an external environment variable, and registers the build script to rerun if
42+
/// the variable changes.
43+
fn env_var(var: &str) -> Option<OsString> {
44+
println!("cargo:rerun-if-env-changed={}", var);
45+
env::var_os(var)
46+
}
47+
3348
/// Information returned from python interpreter
3449
#[derive(Debug)]
3550
struct InterpreterConfig {
@@ -78,7 +93,7 @@ impl FromStr for PythonInterpreterKind {
7893
}
7994

8095
fn is_abi3() -> bool {
81-
env::var_os("CARGO_FEATURE_ABI3").is_some()
96+
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
8297
}
8398

8499
trait GetPrimitive {
@@ -114,40 +129,29 @@ struct CrossCompileConfig {
114129
arch: String,
115130
}
116131

117-
impl CrossCompileConfig {
118-
fn new() -> Result<Self> {
119-
Ok(CrossCompileConfig {
120-
lib_dir: CrossCompileConfig::validate_variable("PYO3_CROSS_LIB_DIR")?,
121-
os: env::var("CARGO_CFG_TARGET_OS").unwrap(),
122-
arch: env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
123-
version: env::var_os("PYO3_CROSS_PYTHON_VERSION").map(|s| s.into_string().unwrap()),
124-
})
132+
fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
133+
for var in &[
134+
"PYO3_CROSS",
135+
"PYO3_CROSS_LIB_DIR",
136+
"PYO3_CROSS_PYTHON_VERSION",
137+
] {
138+
println!("cargo:rerun-if-env-changed={}", var);
125139
}
126140

127-
fn validate_variable(var: &str) -> Result<PathBuf> {
128-
let path = match env::var_os(var) {
129-
Some(v) => v,
130-
None => bail!(
131-
"Must provide {} environment variable when cross-compiling",
132-
var
133-
),
134-
};
141+
let cross_lib_dir = env_var("PYO3_CROSS_LIB_DIR");
142+
let cross_python_version = env_var("PYO3_CROSS_PYTHON_VERSION");
135143

136-
if fs::metadata(&path).is_err() {
137-
bail!("{} value of {:?} does not exist", var, path)
138-
}
144+
let target_arch = cargo_env_var("CARGO_CFG_TARGET_ARCH");
145+
let target_vendor = cargo_env_var("CARGO_CFG_TARGET_VENDOR");
146+
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS");
139147

140-
Ok(path.into())
141-
}
142-
}
143-
144-
fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
145-
if env::var_os("PYO3_CROSS").is_none()
146-
&& env::var_os("PYO3_CROSS_LIB_DIR").is_none()
147-
&& env::var_os("PYO3_CROSS_PYTHON_VERSION").is_none()
148+
if env_var("PYO3_CROSS").is_none() && cross_lib_dir.is_none() && cross_python_version.is_none()
148149
{
149-
let target = env::var("TARGET")?;
150-
let host = env::var("HOST")?;
150+
// No cross-compiling environment variables set; try to determine if this is a known case
151+
// which is not cross-compilation.
152+
153+
let target = cargo_env_var("TARGET").unwrap();
154+
let host = cargo_env_var("HOST").unwrap();
151155
if target == host {
152156
// Not cross-compiling
153157
return Ok(None);
@@ -168,20 +172,32 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
168172
return Ok(None);
169173
}
170174

171-
if host.starts_with(&format!(
172-
"{}-{}-{}",
173-
env::var("CARGO_CFG_TARGET_ARCH")?,
174-
env::var("CARGO_CFG_TARGET_VENDOR")?,
175-
env::var("CARGO_CFG_TARGET_OS")?
176-
)) {
177-
// Not cross-compiling if arch-vendor-os is all the same
178-
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
179-
return Ok(None);
175+
if let (Some(arch), Some(vendor), Some(os)) = (&target_arch, &target_vendor, &target_os) {
176+
if host.starts_with(&format!("{}-{}-{}", arch, vendor, os)) {
177+
// Not cross-compiling if arch-vendor-os is all the same
178+
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
179+
return Ok(None);
180+
}
180181
}
181182
}
182183

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

187203
/// A list of python interpreter compile-time preprocessor defines that
@@ -221,7 +237,7 @@ impl BuildFlags {
221237
/// the interpreter and printing variables of interest from
222238
/// sysconfig.get_config_vars.
223239
fn from_interpreter(python_path: &Path) -> Result<Self> {
224-
if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
240+
if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
225241
return Ok(Self::windows_hardcoded());
226242
}
227243

@@ -372,7 +388,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
372388
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
373389
fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result<PathBuf> {
374390
let sysconfig_paths = search_lib_dir(&cross.lib_dir, &cross);
375-
let sysconfig_name = env::var_os("_PYTHON_SYSCONFIGDATA_NAME");
391+
let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME");
376392
let mut sysconfig_paths = sysconfig_paths
377393
.iter()
378394
.filter_map(|p| {
@@ -520,7 +536,7 @@ fn windows_hardcoded_cross_compile(
520536
fn load_cross_compile_info(
521537
cross_compile_config: CrossCompileConfig,
522538
) -> Result<(InterpreterConfig, BuildFlags)> {
523-
match env::var_os("CARGO_CFG_TARGET_FAMILY") {
539+
match cargo_env_var("CARGO_CFG_TARGET_FAMILY") {
524540
// Configure for unix platforms using the sysconfigdata file
525541
Some(os) if os == "unix" => load_cross_compile_from_sysconfigdata(cross_compile_config),
526542
// Use hardcoded interpreter config when targeting Windows
@@ -575,14 +591,14 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
575591
}
576592

577593
fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
578-
let link_name = if env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" {
594+
let link_name = if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
579595
if is_abi3() {
580596
// Link against python3.lib for the stable ABI on Windows.
581597
// See https://www.python.org/dev/peps/pep-0384/#linkage
582598
//
583599
// This contains only the limited ABI symbols.
584600
"pythonXY:python3".to_owned()
585-
} else if env::var("CARGO_CFG_TARGET_ENV").unwrap().as_str() == "gnu" {
601+
} else if cargo_env_var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu" {
586602
// https://packages.msys2.org/base/mingw-w64-python
587603
format!(
588604
"pythonXY:python{}.{}",
@@ -608,13 +624,31 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
608624
)
609625
}
610626

627+
fn get_venv_path() -> Option<PathBuf> {
628+
match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) {
629+
(Some(dir), None) => Some(PathBuf::from(dir)),
630+
(None, Some(dir)) => Some(PathBuf::from(dir)),
631+
(Some(_), Some(_)) => {
632+
warn!(
633+
"Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
634+
locating the Python interpreter until you unset one of them."
635+
);
636+
None
637+
}
638+
(None, None) => None,
639+
}
640+
}
641+
611642
fn find_interpreter() -> Result<PathBuf> {
612-
if let Some(exe) = env::var_os("PYO3_PYTHON") {
613-
Ok(exe.into())
614-
} else if let Some(exe) = env::var_os("PYTHON_SYS_EXECUTABLE") {
615-
// Backwards-compatible name for PYO3_PYTHON; this may be removed at some point in the future.
643+
if let Some(exe) = env_var("PYO3_PYTHON") {
616644
Ok(exe.into())
645+
} else if let Some(venv_path) = get_venv_path() {
646+
match cargo_env_var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
647+
"windows" => Ok(venv_path.join("Scripts\\python")),
648+
_ => Ok(venv_path.join("bin/python")),
649+
}
617650
} else {
651+
println!("cargo:rerun-if-env-changed=PATH");
618652
["python", "python3"]
619653
.iter()
620654
.find(|bin| {
@@ -687,7 +721,7 @@ print("calcsize_pointer", struct.calcsize("P"))
687721
let output = run_python_script(interpreter, script)?;
688722
let map: HashMap<String, String> = parse_script_output(&output);
689723
let shared = match (
690-
env::var("CARGO_CFG_TARGET_OS").unwrap().as_str(),
724+
cargo_env_var("CARGO_CFG_TARGET_OS").unwrap().as_str(),
691725
map["framework"].as_str(),
692726
map["shared"].as_str(),
693727
) {
@@ -730,8 +764,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
730764

731765
check_target_architecture(interpreter_config)?;
732766

733-
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
734-
let is_extension_module = env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some();
767+
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
768+
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
735769
match (is_extension_module, target_os.as_str()) {
736770
(_, "windows") => {
737771
// always link on windows, even with extension module
@@ -798,7 +832,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
798832

799833
fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<()> {
800834
// Try to check whether the target architecture matches the python library
801-
let rust_target = match env::var("CARGO_CFG_TARGET_POINTER_WIDTH")?.as_str() {
835+
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
836+
.unwrap()
837+
.as_str()
838+
{
802839
"64" => "64-bit",
803840
"32" => "32-bit",
804841
x => bail!("unexpected Rust target pointer width: {}", x),
@@ -833,10 +870,10 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
833870

834871
fn get_abi3_minor_version() -> Option<u8> {
835872
(PY3_MIN_MINOR..=ABI3_MAX_MINOR)
836-
.find(|i| env::var_os(format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
873+
.find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
837874
}
838875

839-
fn abi3_without_interpreter() -> Result<()> {
876+
fn configure_abi3_without_interpreter() {
840877
println!("cargo:rustc-cfg=Py_LIMITED_API");
841878
let mut flags = "FLAG_WITH_THREAD=1".to_string();
842879
let abi_version = get_abi3_minor_version().unwrap_or(ABI3_MAX_MINOR);
@@ -847,7 +884,7 @@ fn abi3_without_interpreter() -> Result<()> {
847884
println!("cargo:rustc-cfg=py_sys_config=\"WITH_THREAD\"");
848885
println!("cargo:python_flags={}", flags);
849886

850-
if env::var("CARGO_CFG_TARGET_FAMILY")? == "windows" {
887+
if cargo_env_var("CARGO_CFG_TARGET_FAMILY").unwrap() == "windows" {
851888
// Unfortunately, on windows we can't build without at least providing
852889
// python.lib to the linker. While maturin tells the linker the location
853890
// of python.lib, we need to do the renaming here, otherwise cargo
@@ -859,18 +896,17 @@ fn abi3_without_interpreter() -> Result<()> {
859896
// assume "Py_ENABLE_SHARED" to be set on Windows.
860897
println!("cargo:rustc-cfg=Py_SHARED");
861898
}
862-
863-
Ok(())
864899
}
865900

866901
fn main_impl() -> Result<()> {
867902
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
868903
// We only check for the abi3-py3{ABI3_MAX_MINOR} because lower versions depend on it.
869-
if env::var_os("PYO3_NO_PYTHON").is_some()
870-
&& env::var_os(format!("CARGO_FEATURE_ABI3_PY3{}", ABI3_MAX_MINOR)).is_some()
904+
if cargo_env_var("PYO3_NO_PYTHON").is_some()
905+
&& cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", ABI3_MAX_MINOR)).is_some()
871906
{
872907
println!("cargo:rerun-if-env-changed=PYO3_NO_PYTHON");
873-
return abi3_without_interpreter();
908+
configure_abi3_without_interpreter();
909+
return Ok(());
874910
}
875911
// 1. Setup cfg variables so we can do conditional compilation in this library based on the
876912
// python interpeter's compilation flags. This is necessary for e.g. matching the right unicode
@@ -898,28 +934,6 @@ fn main_impl() -> Result<()> {
898934
println!("cargo:rustc-cfg={}=\"{}\"", CFG_KEY, flag)
899935
}
900936

901-
for var in &[
902-
"LIB",
903-
"LD_LIBRARY_PATH",
904-
"PYO3_PYTHON",
905-
"PYO3_CROSS",
906-
"PYO3_CROSS_LIB_DIR",
907-
"PYO3_CROSS_PYTHON_VERSION",
908-
] {
909-
println!("cargo:rerun-if-env-changed={}", var);
910-
}
911-
912-
if env::var_os("PYO3_PYTHON").is_none() {
913-
// When PYO3_PYTHON is not used, PYTHON_SYS_EXECUTABLE has the highest priority.
914-
// Let's watch it.
915-
println!("cargo:rerun-if-env-changed=PYTHON_SYS_EXECUTABLE");
916-
if env::var_os("PYTHON_SYS_EXECUTABLE").is_none() {
917-
// When PYTHON_SYS_EXECUTABLE is also not used, then we use PATH.
918-
// Let's watch this, too.
919-
println!("cargo:rerun-if-env-changed=PATH");
920-
}
921-
}
922-
923937
// TODO: this is a hack to workaround compile_error! warnings about auto-initialize on PyPy
924938
// Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
925939
if env::var_os("PYO3_CI").is_some() {

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)