Skip to content

Commit

Permalink
Symlink forest in venv
Browse files Browse the repository at this point in the history
This emulates the behavior where a single site-packages directory
contains all/most PyPi packages. Packages distributed by NVIDIA
currently assume this through the use of rpath as `$ORIGIN/../../`
to reach the `nvidia` package location. Downstream libraries like torch
and jax do not set up the dynamic library search path based on sys.path
either.

This is a casual attempt, but can be refined by others.

TODO:
[] Build binaries for all architectures.

Fixes #274.
  • Loading branch information
siddharthab committed Feb 27, 2024
1 parent 82ad68f commit a91bf72
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 24 deletions.
86 changes: 63 additions & 23 deletions py/tools/py/src/pth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{
fs::{self, File},
fs::{self, DirEntry, File},
io::{BufRead, BufReader, BufWriter, Write},
path::{Path, PathBuf},
};
Expand All @@ -19,27 +19,11 @@ impl PthFile {
}
}

pub fn copy_to_site_packages(&self, dest: &Path) -> miette::Result<()> {
let dest_pth = dest.join(self.src.file_name().expect(".pth must be a file"));

if let Some(prefix) = self.prefix.as_deref() {
self.prefix_and_write_pth(dest_pth, prefix)
} else {
fs::copy(self.src.as_path(), dest_pth)
.map(|_| ())
.into_diagnostic()
.wrap_err("Unable to copy .pth file to site-packages")
}
}

fn prefix_and_write_pth<P>(&self, dest: P, prefix: &str) -> miette::Result<()>
where
P: AsRef<Path>,
{
pub fn set_up_site_packages(&self, dest: &Path) -> miette::Result<()> {
let source_pth = File::open(self.src.as_path())
.into_diagnostic()
.wrap_err("Unable to open source .pth file")?;
let dest_pth = File::create(dest)
let dest_pth = File::create(dest.join(self.src.file_name().expect(".pth must be a file")))
.into_diagnostic()
.wrap_err("Unable to create destination .pth file")?;

Expand All @@ -48,13 +32,69 @@ impl PthFile {

let mut line = String::new();
while reader.read_line(&mut line).unwrap() > 0 {
let entry = Path::new(prefix).join(Path::new(line.trim()));
let entry: PathBuf;
if self.prefix.is_some() {
entry = Path::new(self.prefix.as_deref().unwrap()).join(Path::new(line.trim()));
} else {
entry = PathBuf::from(line.trim());
}
line.clear();
writeln!(writer, "{}", entry.to_string_lossy())
.into_diagnostic()
.wrap_err("Unable to write new .pth file entry with prefix")?;
if entry.file_name().is_some_and(|x| x == "site-packages") {
let src_dir = dest.join(entry).canonicalize().unwrap();
create_symlinks(&src_dir, &src_dir, &dest)?;
} else {
writeln!(writer, "{}", entry.to_string_lossy())
.into_diagnostic()
.wrap_err("Unable to write new .pth file entry")?;
}
}

Ok(())
}
}

fn create_symlinks(dir: &Path, root_dir: &Path, dst_dir: &Path) -> miette::Result<()> {
// Create this directory at the destination.
let tgt_dir = dst_dir.join(dir.strip_prefix(root_dir).unwrap());
std::fs::create_dir_all(&tgt_dir)
.into_diagnostic()
.wrap_err(format!(
"unable to create parent directory for symlink: {}",
tgt_dir.to_string_lossy()
))?;

// Recurse.
let read_dir = fs::read_dir(dir).into_diagnostic().wrap_err(format!(
"unable to read directory {}",
dir.to_string_lossy()
))?;
for entry in read_dir {
let entry = entry.into_diagnostic().wrap_err(format!(
"unable to read directory entry {}",
dir.to_string_lossy()
))?;
let path = entry.path();
if path.is_dir() {
create_symlinks(&path, root_dir, dst_dir)?;
} else if dir != root_dir || entry.file_name() != "__init__.py" {
create_symlink(&entry, root_dir, dst_dir)?;
}
}
Ok(())
}

fn create_symlink(e: &DirEntry, root_dir: &Path, dst_dir: &Path) -> miette::Result<()> {
let tgt = e.path();
let link = dst_dir.join(tgt.strip_prefix(root_dir).unwrap());
if link.exists() && link.file_name().is_some_and(|x| x == "__init__.py") && fs::metadata(&tgt).unwrap().len() == 0 {
return Ok(());
}
std::os::unix::fs::symlink(&tgt, &link)
.into_diagnostic()
.wrap_err(format!(
"unable to create symlink: {} -> {}",
tgt.to_string_lossy(),
link.to_string_lossy()
))?;
Ok(())
}
2 changes: 1 addition & 1 deletion py/tools/py/src/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub fn create_venv(
.into_diagnostic()?;

if let Some(pth) = pth_file {
pth.copy_to_site_packages(&venv_location.join(install_paths.platlib()))?
pth.set_up_site_packages(&venv_location.join(install_paths.platlib()))?
}

Ok(())
Expand Down
Binary file modified py/tools/venv_bin/bins/venv-x86_64-unknown-linux-musl
Binary file not shown.

0 comments on commit a91bf72

Please sign in to comment.