Skip to content

Restore symlinked Python source traversal#3168

Open
Bben01 wants to merge 3 commits into
PyO3:mainfrom
Bben01:codex/restore-symlinked-python-sources
Open

Restore symlinked Python source traversal#3168
Bben01 wants to merge 3 commits into
PyO3:mainfrom
Bben01:codex/restore-symlinked-python-sources

Conversation

@Bben01
Copy link
Copy Markdown

@Bben01 Bben01 commented May 1, 2026

Summary

Restore traversal of symlinked directories when maturin collects Python package sources for wheels and sdists.

I dont know if it was removed intentional or not. If it was, I could add some bool to the pyproject.toml that could toggle this behavior (and set the current default to false)

Why

In our team we package multiple Python packages that share some common code. We use symlinks for those shared directories and files so the source is maintained in one place instead of duplicated across package trees.

This worked previously (from what i remember - 1.7.0), but recent behavior no longer followed symlinked directories while building a pyo3 Rust + Python layout. Symlinked files were still included, but files inside symlinked directories were omitted from the wheel.

Fix

Use follow_links(true) when walking Python package sources for both wheel and sdist generation.

Validation

  • Confirmed the new regression test fails before the fix: linked_dir/nested.py is missing from the wheel.
  • Confirmed it passes after the fix with cargo test --test run -- pyo3_mixed_py_subdir_includes_symlinked_python_files.
  • Ran cargo fmt --all and git diff --check.

AI disclosure

Fix was written by codex 5.5

Follow symlinked directories when collecting Python package files for  wheels and source distributions.

This restores support for layouts that share Python code through symlinked package directories instead of duplicating files.
@Bben01 Bben01 marked this pull request as ready for review May 1, 2026 08:51
@messense messense requested a review from Copilot May 5, 2026 12:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores traversal of symlinked directories when maturin collects Python package sources, so that files inside symlinked folders are included in both wheel and sdist outputs (matching the described historical behavior).

Changes:

  • Enable symlink-following in wheel Python source collection (ModuleWriter::write_python_part).
  • Enable symlink-following in sdist Python source collection (add_python_sources).
  • Add a Unix-only regression test to ensure wheel builds include files inside a symlinked Python directory.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/run/wheel.rs Adds a regression test covering inclusion of files inside symlinked Python directories in wheels.
src/source_distribution/pyproject.rs Changes sdist Python-source walking to follow symlinked directories.
src/module_writer/mod.rs Changes wheel Python-source walking to follow symlinked directories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/module_writer/mod.rs Outdated
Comment thread src/source_distribution/pyproject.rs Outdated
Comment thread src/source_distribution/pyproject.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comment thread src/module_writer/mod.rs Outdated
Comment thread src/module_writer/mod.rs
Comment thread src/source_distribution/pyproject.rs Outdated
Comment thread tests/run/wheel.rs
Skip `ignore` symlink-loop errors while following symlinked Python source directories so cyclic links do not fail wheel or sdist builds.

Simplify the wheel walk error handling now that the walker is rooted at each Python package, and share the symlink fixture across wheel and sdist tests.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/module_writer/mod.rs
Comment on lines +136 to +147
// Walk each python package directly (rather than rooting the walker at
// `project_root` and filtering): this keeps `follow_links(true)` scoped to
// symlinks inside python packages, instead of also descending into unrelated
// symlinked trees under the project (e.g. inside `target/` or `.venv/`).
for package in &python_packages {
for absolute in WalkBuilder::new(package)
.hidden(false)
.parents(false)
.git_global(false)
.git_exclude(false)
.follow_links(true)
.build()
Comment thread src/module_writer/mod.rs
Comment on lines +140 to +147
for package in &python_packages {
for absolute in WalkBuilder::new(package)
.hidden(false)
.parents(false)
.git_global(false)
.git_exclude(false)
.follow_links(true)
.build()
Comment on lines 75 to +87
for package in python_packages {
for entry in ignore::Walk::new(package) {
let source = entry?.into_path();
for entry in ignore::WalkBuilder::new(package).follow_links(true).build() {
let source = match entry {
Ok(entry) => entry.into_path(),
Err(err) => {
if is_symlink_loop_error(&err) {
warn!(
"Skipping symlink loop in Python package source tree while building source distribution: {err}"
);
continue;
}
return Err(err.into());
}
@messense
Copy link
Copy Markdown
Member

messense commented May 9, 2026

@Bben01 Could you clarify where your shared Python sources live relative to the project checkout?

The current PR follows symlinked Python package directories, but there is a safety tradeoff: following symlinks can package files outside the project if a link points somewhere broad or unexpected. One possible rule is to allow symlink targets that stay inside the same git repository/worktree, but reject targets outside it.

Would that match your setup, or are your shared Python sources outside the git repository that contains the maturin project?

@Bben01
Copy link
Copy Markdown
Author

Bben01 commented May 9, 2026

Yes, the sources are inside a big monorepo, and we build from that multiple packages, so it could work.

I currently don't have access to a computer but will do it as soon as possible.

If you prefer I could just make it explicit using a config flag in the pyproject.toml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants