Skip to content

Commit 0c95d86

Browse files
committed
fix: valgrind crash for unresolved libpython
1 parent d3c5540 commit 0c95d86

3 files changed

Lines changed: 128 additions & 1 deletion

File tree

src/run/runner/valgrind/executor.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::run::runner::{ExecutorName, RunData};
77
use crate::run::{check_system::SystemInfo, config::Config};
88

99
use super::setup::install_valgrind;
10-
use super::{helpers::perf_maps::harvest_perf_maps, measure};
10+
use super::{helpers::perf_maps::harvest_perf_maps, helpers::venv_compat, measure};
1111

1212
pub struct ValgrindExecutor;
1313

@@ -19,6 +19,13 @@ impl Executor for ValgrindExecutor {
1919

2020
async fn setup(&self, system_info: &SystemInfo) -> Result<()> {
2121
install_valgrind(system_info).await?;
22+
23+
if let Err(error) = venv_compat::symlink_libpython(None) {
24+
error!("Failed to symlink libpython: {}", error);
25+
} else {
26+
info!("Successfully added symlink for libpython in the venv");
27+
}
28+
2229
Ok(())
2330
}
2431

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod ignored_objects_path;
22
pub mod introspected_nodejs;
33
pub mod perf_maps;
4+
pub mod venv_compat;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use crate::prelude::*;
2+
use std::{io::Write, os::unix::fs::PermissionsExt, process::Command};
3+
4+
/// When creating a virtual environment, only `python3` is symlinked or copied which makes
5+
/// lookups to `.venv/lib/libpython{version}.so.1.0` fail. This isn't an issue for most distributions
6+
/// since they use absolute paths in the `python3` executable.
7+
///
8+
/// However, uv uses relative paths which causes the lookups to fail:
9+
/// ```no_run
10+
/// > ldd .venv/bin/python3
11+
/// /home/project/.venv/bin/../lib/libpython3.13.so.1.0 => not found
12+
/// ```
13+
///
14+
/// The solution to this is to add the symlink of the `libpython` shared object in the
15+
/// virtual environment (`.venv/lib`) to make the symlink work correctly.
16+
///
17+
/// This scripts tries to find the virtual environment using `uv python find` and by finding the
18+
/// `python3` executable in the activated virtual environment.
19+
const VENV_COMPAT_SCRIPT: &str = r#"#!/usr/bin/env bash
20+
function add_symlink() {
21+
local venv_python="$1"
22+
23+
system_python="$(readlink -f "$venv_python")"
24+
if [ -z "$system_python" ]; then
25+
echo "Error: Failed to resolve real path for $venv_python" >&2
26+
return 1
27+
fi
28+
29+
system_path="$(dirname $(dirname "$system_python"))"
30+
venv_path="$(dirname $(dirname "$venv_python"))"
31+
echo "Python installation (system): $system_path"
32+
echo "Python installation (venv): $venv_path"
33+
34+
# Find libpython in the system python dir
35+
mapfile -t libpython_array < <(find "$system_path" -maxdepth 3 -type f -name "libpython*.so.1.0" 2>/dev/null)
36+
if [ ${#libpython_array[@]} -eq 0 ]; then
37+
echo "Error: libpython*.so.1.0 not found in $system_path"
38+
exit 1
39+
fi
40+
41+
libpython="${libpython_array[0]}"
42+
libpython_name=$(basename "$libpython")
43+
echo "Found libpython: $libpython"
44+
45+
# Create the symlink in the virtual environment
46+
venv_link="$venv_path/lib/$libpython_name"
47+
if [ -e "$venv_link" ]; then
48+
echo "Symlink already exists: $venv_link"
49+
else
50+
echo "Creating symlink: $venv_link -> $libpython"
51+
ln -s "$libpython" "$venv_link"
52+
fi
53+
}
54+
55+
##
56+
##
57+
##
58+
59+
uv_python="$(uv python find 2>/dev/null || true)"
60+
if [ -n "$uv_python" ]; then
61+
add_symlink "$uv_python"
62+
else
63+
echo "Didn't find uv venv, continuing..."
64+
fi
65+
66+
##
67+
##
68+
##
69+
70+
python3_path="$(which python3 2>/dev/null || true)"
71+
if [ -n "$python3_path" ]; then
72+
echo "Found system Python: $python3_path"
73+
74+
if ldd "$python3_path" | grep -q "libpython.*not found"; then
75+
add_symlink "$python3_path"
76+
else
77+
echo "System python is already correctly linked, continuing..."
78+
fi
79+
fi
80+
"#;
81+
82+
pub fn symlink_libpython(cwd: Option<&String>) -> anyhow::Result<()> {
83+
let rwx = std::fs::Permissions::from_mode(0o777);
84+
let mut script_file = tempfile::Builder::new()
85+
.suffix(".sh")
86+
.permissions(rwx)
87+
.tempfile()?;
88+
script_file.write_all(VENV_COMPAT_SCRIPT.as_bytes())?;
89+
90+
let mut cmd = Command::new("bash");
91+
cmd.arg(script_file.path());
92+
93+
if let Some(cwd) = cwd {
94+
cmd.current_dir(cwd);
95+
}
96+
97+
debug!("Running the venv compat script");
98+
let output = cmd.output()?;
99+
100+
let stdout = String::from_utf8(output.stdout)?;
101+
debug!("Script output: {stdout}");
102+
103+
if !output.status.success() {
104+
let stderr = String::from_utf8(output.stderr)?;
105+
bail!("Failed to execute script: {stdout} {stderr}");
106+
}
107+
108+
Ok(())
109+
}
110+
111+
#[cfg(test)]
112+
mod tests {
113+
use super::*;
114+
115+
#[test]
116+
fn test_venv_compat_no_crash() {
117+
assert!(symlink_libpython(None).is_ok());
118+
}
119+
}

0 commit comments

Comments
 (0)