diff --git a/src/run/runner/valgrind/executor.rs b/src/run/runner/valgrind/executor.rs index 9bb41d36..b24c6d94 100644 --- a/src/run/runner/valgrind/executor.rs +++ b/src/run/runner/valgrind/executor.rs @@ -7,7 +7,7 @@ use crate::run::runner::{ExecutorName, RunData}; use crate::run::{check_system::SystemInfo, config::Config}; use super::setup::install_valgrind; -use super::{helpers::perf_maps::harvest_perf_maps, measure}; +use super::{helpers::perf_maps::harvest_perf_maps, helpers::venv_compat, measure}; pub struct ValgrindExecutor; @@ -19,6 +19,13 @@ impl Executor for ValgrindExecutor { async fn setup(&self, system_info: &SystemInfo) -> Result<()> { install_valgrind(system_info).await?; + + if let Err(error) = venv_compat::symlink_libpython(None) { + error!("Failed to symlink libpython: {}", error); + } else { + info!("Successfully added symlink for libpython in the venv"); + } + Ok(()) } diff --git a/src/run/runner/valgrind/helpers/mod.rs b/src/run/runner/valgrind/helpers/mod.rs index 4c80afa0..627f11d7 100644 --- a/src/run/runner/valgrind/helpers/mod.rs +++ b/src/run/runner/valgrind/helpers/mod.rs @@ -1,3 +1,4 @@ pub mod ignored_objects_path; pub mod introspected_nodejs; pub mod perf_maps; +pub mod venv_compat; diff --git a/src/run/runner/valgrind/helpers/venv_compat.rs b/src/run/runner/valgrind/helpers/venv_compat.rs new file mode 100644 index 00000000..c5d696ee --- /dev/null +++ b/src/run/runner/valgrind/helpers/venv_compat.rs @@ -0,0 +1,58 @@ +//! When creating a virtual environment, only `python3` is symlinked or copied which makes +//! lookups to `.venv/lib/libpython{version}.so.1.0` fail. This isn't an issue for most distributions +//! since they use absolute paths in the `python3` executable. +//! +//! However, uv uses relative paths which causes the lookups to fail: +//! ```no_run +//! > ldd .venv/bin/python3 +//! /home/project/.venv/bin/../lib/libpython3.13.so.1.0 => not found +//! ``` +//! +//! The solution to this is to add the symlink of the `libpython` shared object in the +//! virtual environment (`.venv/lib`) to make the symlink work correctly. + +use crate::prelude::*; +use std::{io::Write, os::unix::fs::PermissionsExt, process::Command}; + +/// This scripts tries to find the virtual environment using `uv python find` and by finding the +/// `python3` executable in the activated virtual environment. +const VENV_COMPAT_SCRIPT: &str = include_str!("venv_compat.sh"); + +pub fn symlink_libpython(cwd: Option<&String>) -> anyhow::Result<()> { + let rwx = std::fs::Permissions::from_mode(0o777); + let mut script_file = tempfile::Builder::new() + .suffix(".sh") + .permissions(rwx) + .tempfile()?; + script_file.write_all(VENV_COMPAT_SCRIPT.as_bytes())?; + + let mut cmd = Command::new("bash"); + cmd.arg(script_file.path()); + + if let Some(cwd) = cwd { + cmd.current_dir(cwd); + } + + debug!("Running the venv compat script"); + let output = cmd.output()?; + + let stdout = String::from_utf8(output.stdout)?; + debug!("Script output: {stdout}"); + + if !output.status.success() { + let stderr = String::from_utf8(output.stderr)?; + bail!("Failed to execute script: {stdout} {stderr}"); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_venv_compat_no_crash() { + assert!(symlink_libpython(None).is_ok()); + } +} diff --git a/src/run/runner/valgrind/helpers/venv_compat.sh b/src/run/runner/valgrind/helpers/venv_compat.sh new file mode 100755 index 00000000..d671315f --- /dev/null +++ b/src/run/runner/valgrind/helpers/venv_compat.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +function add_symlink() { + local venv_python="$1" + + system_python="$(readlink -f "$venv_python")" + if [ -z "$system_python" ]; then + echo "Error: Failed to resolve real path for $venv_python" >&2 + return 1 + fi + + system_path="$(dirname $(dirname "$system_python"))" + venv_path="$(dirname $(dirname "$venv_python"))" + echo "Python installation (system): $system_path" + echo "Python installation (venv): $venv_path" + + libpython_name="$(ldd "$venv_python" 2>/dev/null | grep -o -m1 'libpython[^[:space:]]*' || true)" + if [ -z "$libpython_name" ]; then + echo "Error: exact libpython name not found in $(ldd $venv_python)" >&2 + return 1 + fi + echo "Found linked libpython: $libpython_name" + + # Create the symlink in the virtual environment + venv_link="$venv_path/lib/$libpython_name" + system_link="$system_path/lib/$libpython_name" + if [ -e "$venv_link" ]; then + echo "Symlink already exists: $venv_link" + else + echo "Creating symlink: $system_link -> $venv_link" + ln -s "$system_link" "$venv_link" + if [ $? -ne 0 ]; then + echo "Error: Failed to create symlink $venv_link" >&2 + return 1 + fi + fi +} + +uv_python="$(uv python find 2>/dev/null || true)" +if [ -n "$uv_python" ]; then + add_symlink "$uv_python" + if [ $? -ne 0 ]; then + echo "Error: Failed to add symlink for uv venv" >&2 + exit 1 + fi +else + echo "Didn't find uv venv, continuing..." +fi + +python3_path="$(which python3 2>/dev/null || true)" +if [ -n "$python3_path" ]; then + echo "Found system Python: $python3_path" + + if ldd "$python3_path" | grep -q "libpython.*not found"; then + add_symlink "$python3_path" + if [ $? -ne 0 ]; then + echo "Error: Failed to add symlink for system Python" >&2 + exit 1 + fi + else + echo "System python is already correctly linked, continuing..." + fi +fi