From 51b4d036e1bb077b8d59198e1dae8cd1393feab8 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 30 Apr 2025 12:45:36 -0500 Subject: [PATCH] Create djls-dev crate and centralize Python linking build scripts --- CHANGELOG.md | 1 + Cargo.toml | 1 + crates/djls-dev/Cargo.toml | 8 ++++++ crates/djls-dev/src/build.rs | 47 ++++++++++++++++++++++++++++++++++ crates/djls-dev/src/lib.rs | 3 +++ crates/djls-project/Cargo.toml | 2 +- crates/djls-project/build.rs | 44 +------------------------------ crates/djls-server/Cargo.toml | 2 +- crates/djls-server/build.rs | 44 +------------------------------ crates/djls/Cargo.toml | 2 +- crates/djls/build.rs | 44 +------------------------------ 11 files changed, 66 insertions(+), 132 deletions(-) create mode 100644 crates/djls-dev/Cargo.toml create mode 100644 crates/djls-dev/src/build.rs create mode 100644 crates/djls-dev/src/lib.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cbccc6d7..1e4eb21b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/ - **Internal**: Moved task queueing functionality to `djls-server` crate, renamed from `Worker` to `Queue`, and simplified API. - **Internal**: Improved Python environment handling, including refactored activation logic. +- **Internal**: Centralized Python linking build logic into a shared `djls-dev` crate to reduce duplication. ## [5.2.0a0] diff --git a/Cargo.toml b/Cargo.toml index f1ddcc50..fe5bb907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ resolver = "2" [workspace.dependencies] djls = { path = "crates/djls" } djls-conf = { path = "crates/djls-conf" } +djls-dev = { path = "crates/djls-dev" } djls-project = { path = "crates/djls-project" } djls-server = { path = "crates/djls-server" } djls-templates = { path = "crates/djls-templates" } diff --git a/crates/djls-dev/Cargo.toml b/crates/djls-dev/Cargo.toml new file mode 100644 index 00000000..52377c86 --- /dev/null +++ b/crates/djls-dev/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "djls-dev" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +pyo3-build-config = { workspace = true, features = ["resolve-config"] } diff --git a/crates/djls-dev/src/build.rs b/crates/djls-dev/src/build.rs new file mode 100644 index 00000000..230e85a1 --- /dev/null +++ b/crates/djls-dev/src/build.rs @@ -0,0 +1,47 @@ +/// Sets up the necessary Cargo directives for linking against the Python library. +/// +/// This function should be called from the `build.rs` script of any crate +/// within the workspace whose compiled artifact (e.g., test executable, binary) +/// needs to link against the Python C API at compile time and find the +/// corresponding shared library at runtime. This is typically required for +/// crates using `pyo3` features like `Python::with_gil` or defining `#[pyfunction]`s +/// directly in their test or binary code. +/// +/// It uses `pyo3-build-config` to detect the active Python installation and +/// prints the required `cargo:rustc-link-search` and `cargo:rustc-link-lib` +/// directives to Cargo, enabling the linker to find and link against the +/// appropriate Python library (e.g., libpythonX.Y.so). +/// +/// It also adds an RPATH linker argument on Unix-like systems (`-Wl,-rpath,...`) +/// to help the resulting executable find the Python shared library at runtime +/// without needing manual `LD_LIBRARY_PATH` adjustments in typical scenarios. +pub fn setup_python_linking() { + // Instruct Cargo to rerun the calling build script if the Python config changes. + // Using PYO3_CONFIG_FILE is a reliable way to detect changes in the active Python env. + println!("cargo:rerun-if-changed=build.rs"); + + // Set up #[cfg] flags first (useful for conditional compilation) + pyo3_build_config::use_pyo3_cfgs(); + + // Get the Python interpreter configuration directly + let config = pyo3_build_config::get(); + + // Add the library search path if available + if let Some(lib_dir) = &config.lib_dir { + println!("cargo:rustc-link-search=native={}", lib_dir); + + // Add RPATH linker argument for Unix-like systems (Linux, macOS) + // This helps the test executable find the Python library at runtime. + #[cfg(not(windows))] + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir); + } else { + println!("cargo:warning=Python library directory not found in config."); + } + + // Add the library link directive if available + if let Some(lib_name) = &config.lib_name { + println!("cargo:rustc-link-lib=dylib={}", lib_name); + } else { + println!("cargo:warning=Python library name not found in config."); + } +} diff --git a/crates/djls-dev/src/lib.rs b/crates/djls-dev/src/lib.rs new file mode 100644 index 00000000..84752da6 --- /dev/null +++ b/crates/djls-dev/src/lib.rs @@ -0,0 +1,3 @@ +mod build; + +pub use build::setup_python_linking; diff --git a/crates/djls-project/Cargo.toml b/crates/djls-project/Cargo.toml index bf8c6f2e..6cd1fd48 100644 --- a/crates/djls-project/Cargo.toml +++ b/crates/djls-project/Cargo.toml @@ -10,7 +10,7 @@ tower-lsp-server = { workspace = true, features = ["proposed"] } which = "7.0.1" [build-dependencies] -pyo3-build-config = { workspace = true, features = ["resolve-config"] } +djls-dev = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/djls-project/build.rs b/crates/djls-project/build.rs index d34c78df..bcfdfd6f 100644 --- a/crates/djls-project/build.rs +++ b/crates/djls-project/build.rs @@ -1,45 +1,3 @@ -/// Build script to configure linking against the Python library. -/// -/// This script is necessary when the crate's code, particularly tests -/// that interact with the Python interpreter via `pyo3` (e.g., using -/// `Python::with_gil`, importing modules, calling Python functions), -/// needs symbols from the Python C API at link time. -/// -/// It uses `pyo3-build-config` to detect the Python installation and -/// prints the required `cargo:rustc-link-search` and `cargo:rustc-link-lib` -/// directives to Cargo, enabling the linker to find and link against the -/// appropriate Python library (e.g., libpythonX.Y.so). -/// -/// It also adds an RPATH linker argument on Unix-like systems so the -/// resulting test executable can find the Python library at runtime. -/// -/// Note: Each crate whose test target requires Python linking needs its -/// own `build.rs` with this logic. fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - // Set up #[cfg] flags first (useful for conditional compilation) - pyo3_build_config::use_pyo3_cfgs(); - - // Get the Python interpreter configuration directly - let config = pyo3_build_config::get(); - - // Add the library search path if available - if let Some(lib_dir) = &config.lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); - - // Add RPATH linker argument for Unix-like systems (Linux, macOS) - // This helps the test executable find the Python library at runtime. - #[cfg(not(windows))] - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir); - } else { - println!("cargo:warning=Python library directory not found in config."); - } - - // Add the library link directive if available - if let Some(lib_name) = &config.lib_name { - println!("cargo:rustc-link-lib=dylib={}", lib_name); - } else { - println!("cargo:warning=Python library name not found in config."); - } + djls_dev::setup_python_linking(); } diff --git a/crates/djls-server/Cargo.toml b/crates/djls-server/Cargo.toml index 611c11a2..f1faf7f8 100644 --- a/crates/djls-server/Cargo.toml +++ b/crates/djls-server/Cargo.toml @@ -18,4 +18,4 @@ tower-lsp-server = { workspace = true } percent-encoding = "2.3" [build-dependencies] -pyo3-build-config = { workspace = true, features = ["resolve-config"] } +djls-dev = { workspace = true } diff --git a/crates/djls-server/build.rs b/crates/djls-server/build.rs index d34c78df..bcfdfd6f 100644 --- a/crates/djls-server/build.rs +++ b/crates/djls-server/build.rs @@ -1,45 +1,3 @@ -/// Build script to configure linking against the Python library. -/// -/// This script is necessary when the crate's code, particularly tests -/// that interact with the Python interpreter via `pyo3` (e.g., using -/// `Python::with_gil`, importing modules, calling Python functions), -/// needs symbols from the Python C API at link time. -/// -/// It uses `pyo3-build-config` to detect the Python installation and -/// prints the required `cargo:rustc-link-search` and `cargo:rustc-link-lib` -/// directives to Cargo, enabling the linker to find and link against the -/// appropriate Python library (e.g., libpythonX.Y.so). -/// -/// It also adds an RPATH linker argument on Unix-like systems so the -/// resulting test executable can find the Python library at runtime. -/// -/// Note: Each crate whose test target requires Python linking needs its -/// own `build.rs` with this logic. fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - // Set up #[cfg] flags first (useful for conditional compilation) - pyo3_build_config::use_pyo3_cfgs(); - - // Get the Python interpreter configuration directly - let config = pyo3_build_config::get(); - - // Add the library search path if available - if let Some(lib_dir) = &config.lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); - - // Add RPATH linker argument for Unix-like systems (Linux, macOS) - // This helps the test executable find the Python library at runtime. - #[cfg(not(windows))] - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir); - } else { - println!("cargo:warning=Python library directory not found in config."); - } - - // Add the library link directive if available - if let Some(lib_name) = &config.lib_name { - println!("cargo:rustc-link-lib=dylib={}", lib_name); - } else { - println!("cargo:warning=Python library name not found in config."); - } + djls_dev::setup_python_linking(); } diff --git a/crates/djls/Cargo.toml b/crates/djls/Cargo.toml index 55a401e6..52f62f83 100644 --- a/crates/djls/Cargo.toml +++ b/crates/djls/Cargo.toml @@ -20,4 +20,4 @@ tower-lsp-server = { workspace = true } clap = { version = "4.5", features = ["derive"] } [build-dependencies] -pyo3-build-config = { workspace = true, features = ["resolve-config"] } +djls-dev = { workspace = true } diff --git a/crates/djls/build.rs b/crates/djls/build.rs index d34c78df..bcfdfd6f 100644 --- a/crates/djls/build.rs +++ b/crates/djls/build.rs @@ -1,45 +1,3 @@ -/// Build script to configure linking against the Python library. -/// -/// This script is necessary when the crate's code, particularly tests -/// that interact with the Python interpreter via `pyo3` (e.g., using -/// `Python::with_gil`, importing modules, calling Python functions), -/// needs symbols from the Python C API at link time. -/// -/// It uses `pyo3-build-config` to detect the Python installation and -/// prints the required `cargo:rustc-link-search` and `cargo:rustc-link-lib` -/// directives to Cargo, enabling the linker to find and link against the -/// appropriate Python library (e.g., libpythonX.Y.so). -/// -/// It also adds an RPATH linker argument on Unix-like systems so the -/// resulting test executable can find the Python library at runtime. -/// -/// Note: Each crate whose test target requires Python linking needs its -/// own `build.rs` with this logic. fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - // Set up #[cfg] flags first (useful for conditional compilation) - pyo3_build_config::use_pyo3_cfgs(); - - // Get the Python interpreter configuration directly - let config = pyo3_build_config::get(); - - // Add the library search path if available - if let Some(lib_dir) = &config.lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); - - // Add RPATH linker argument for Unix-like systems (Linux, macOS) - // This helps the test executable find the Python library at runtime. - #[cfg(not(windows))] - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir); - } else { - println!("cargo:warning=Python library directory not found in config."); - } - - // Add the library link directive if available - if let Some(lib_name) = &config.lib_name { - println!("cargo:rustc-link-lib=dylib={}", lib_name); - } else { - println!("cargo:warning=Python library name not found in config."); - } + djls_dev::setup_python_linking(); }