Skip to content

Commit

Permalink
Add an API to set rpath when using macOS system Python
Browse files Browse the repository at this point in the history
  • Loading branch information
messense committed Jan 2, 2025
1 parent 5c0be2c commit c467d9e
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 12 deletions.
7 changes: 6 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::env;

use pyo3_build_config::pyo3_build_script_impl::{cargo_env_var, errors::Result};
use pyo3_build_config::{bail, print_feature_cfgs, InterpreterConfig};
use pyo3_build_config::{
add_python_framework_link_args, bail, print_feature_cfgs, InterpreterConfig,
};

fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<()> {
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() && !interpreter_config.shared {
Expand Down Expand Up @@ -42,6 +44,9 @@ fn configure_pyo3() -> Result<()> {
// Emit cfgs like `invalid_from_utf8_lint`
print_feature_cfgs();

// Make `cargo test` etc work on macOS with Xcode bundled Python
add_python_framework_link_args();

Ok(())
}

Expand Down
22 changes: 11 additions & 11 deletions guide/src/building-and-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,17 @@ rustflags = [
]
```

Using the MacOS system python3 (`/usr/bin/python3`, as opposed to python installed via homebrew, pyenv, nix, etc.) may result in runtime errors such as `Library not loaded: @rpath/Python3.framework/Versions/3.8/Python3`. These can be resolved with another addition to `.cargo/config.toml`:
Using the MacOS system python3 (`/usr/bin/python3`, as opposed to python installed via homebrew, pyenv, nix, etc.) may result in runtime errors such as `Library not loaded: @rpath/Python3.framework/Versions/3.8/Python3`.

The easiest way to set the correct linker arguments is to add a `build.rs` with the following content:

```rust,ignore
fn main() {
pyo3_build_config::add_python_framework_link_args();
}
```

Alternatively it can be resolved with another addition to `.cargo/config.toml`:

```toml
[build]
Expand All @@ -153,16 +163,6 @@ rustflags = [
]
```

Alternatively, one can include in `build.rs`:

```rust
fn main() {
println!(
"cargo:rustc-link-arg=-Wl,-rpath,/Library/Developer/CommandLineTools/Library/Frameworks"
);
}
```

For more discussion on and workarounds for MacOS linking problems [see this issue](https://github.com/PyO3/pyo3/issues/1800#issuecomment-906786649).

Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)).
Expand Down
1 change: 1 addition & 0 deletions newsfragments/4833.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `pyo3_build_config::add_python_framework_link_args` build script API to set rpath when using macOS system Python.
37 changes: 37 additions & 0 deletions pyo3-build-config/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ pub struct InterpreterConfig {
///
/// Serialized to multiple `extra_build_script_line` values.
pub extra_build_script_lines: Vec<String>,
/// macOS Python3.framework requires special rpath handling
pub python_framework_prefix: Option<String>,
}

impl InterpreterConfig {
Expand Down Expand Up @@ -245,6 +247,7 @@ WINDOWS = platform.system() == "Windows"
# macOS framework packages use shared linking
FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
FRAMEWORK_PREFIX = get_config_var("PYTHONFRAMEWORKPREFIX")
# unix-style shared library enabled
SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
Expand All @@ -253,6 +256,7 @@ print("implementation", platform.python_implementation())
print("version_major", sys.version_info[0])
print("version_minor", sys.version_info[1])
print("shared", PYPY or GRAALPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
print("python_framework_prefix", FRAMEWORK_PREFIX)
print_if_set("ld_version", get_config_var("LDVERSION"))
print_if_set("libdir", get_config_var("LIBDIR"))
print_if_set("base_prefix", base_prefix)
Expand Down Expand Up @@ -289,6 +293,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
};

let shared = map["shared"].as_str() == "True";
let python_framework_prefix = map.get("python_framework_prefix").cloned();

let version = PythonVersion {
major: map["version_major"]
Expand Down Expand Up @@ -359,6 +364,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
build_flags: BuildFlags::from_interpreter(interpreter)?,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix,
})
}

Expand Down Expand Up @@ -396,6 +402,9 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
Some(s) => !s.is_empty(),
_ => false,
};
let python_framework_prefix = sysconfigdata
.get_value("PYTHONFRAMEWORKPREFIX")
.map(str::to_string);
let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string);
let gil_disabled = match sysconfigdata.get_value("Py_GIL_DISABLED") {
Some(value) => value == "1",
Expand Down Expand Up @@ -424,6 +433,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
build_flags,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix,
})
}

Expand Down Expand Up @@ -500,6 +510,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
let mut build_flags: Option<BuildFlags> = None;
let mut suppress_build_script_link_lines = None;
let mut extra_build_script_lines = vec![];
let mut python_framework_prefix = None;

for (i, line) in lines.enumerate() {
let line = line.context("failed to read line from config")?;
Expand Down Expand Up @@ -528,6 +539,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
"extra_build_script_line" => {
extra_build_script_lines.push(value.to_string());
}
"python_framework_prefix" => parse_value!(python_framework_prefix, value),
unknown => warn!("unknown config key `{}`", unknown),
}
}
Expand Down Expand Up @@ -558,6 +570,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
build_flags,
suppress_build_script_link_lines: suppress_build_script_link_lines.unwrap_or(false),
extra_build_script_lines,
python_framework_prefix,
})
}

Expand Down Expand Up @@ -650,6 +663,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
write_option_line!(executable)?;
write_option_line!(pointer_width)?;
write_line!(build_flags)?;
write_option_line!(python_framework_prefix)?;
write_line!(suppress_build_script_link_lines)?;
for line in &self.extra_build_script_lines {
writeln!(writer, "extra_build_script_line={}", line)
Expand Down Expand Up @@ -1587,6 +1601,7 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<In
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
})
}

Expand Down Expand Up @@ -1629,6 +1644,7 @@ fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result<Interpre
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
})
}

Expand Down Expand Up @@ -2011,6 +2027,7 @@ mod tests {
version: MINIMUM_SUPPORTED_VERSION,
suppress_build_script_link_lines: true,
extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
python_framework_prefix: None,
};
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();
Expand Down Expand Up @@ -2039,6 +2056,7 @@ mod tests {
},
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();
Expand All @@ -2060,6 +2078,7 @@ mod tests {
version: MINIMUM_SUPPORTED_VERSION,
suppress_build_script_link_lines: true,
extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
python_framework_prefix: None,
};
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();
Expand All @@ -2086,6 +2105,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
)
}
Expand All @@ -2108,6 +2128,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
)
}
Expand Down Expand Up @@ -2210,6 +2231,7 @@ mod tests {
version: PythonVersion::PY37,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand Down Expand Up @@ -2239,6 +2261,7 @@ mod tests {
version: PythonVersion::PY37,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);

Expand All @@ -2265,6 +2288,7 @@ mod tests {
version: PythonVersion::PY37,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand All @@ -2288,6 +2312,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand All @@ -2311,6 +2336,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand Down Expand Up @@ -2345,6 +2371,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand Down Expand Up @@ -2379,6 +2406,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand Down Expand Up @@ -2413,6 +2441,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand Down Expand Up @@ -2449,6 +2478,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
);
}
Expand Down Expand Up @@ -2796,6 +2826,7 @@ mod tests {
version: PythonVersion { major: 3, minor: 7 },
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};

config
Expand All @@ -2818,6 +2849,7 @@ mod tests {
version: PythonVersion { major: 3, minor: 7 },
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};

assert!(config
Expand Down Expand Up @@ -2882,6 +2914,7 @@ mod tests {
version: interpreter_config.version,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
}
)
}
Expand Down Expand Up @@ -3006,6 +3039,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};
assert_eq!(
interpreter_config.build_script_outputs(),
Expand Down Expand Up @@ -3045,6 +3079,7 @@ mod tests {
build_flags: BuildFlags::default(),
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};

assert_eq!(
Expand Down Expand Up @@ -3092,6 +3127,7 @@ mod tests {
build_flags,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};

assert_eq!(
Expand Down Expand Up @@ -3125,6 +3161,7 @@ mod tests {
build_flags,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
python_framework_prefix: None,
};

assert_eq!(
Expand Down
Loading

0 comments on commit c467d9e

Please sign in to comment.