Skip to content

Preserve package_data's mode when its copy is skipped as up to date - #5319

Open
afonsojanu wants to merge 2 commits into
pypa:mainfrom
afonsojanu:fix/preserve-executable-bit-package-data-5296
Open

Preserve package_data's mode when its copy is skipped as up to date#5319
afonsojanu wants to merge 2 commits into
pypa:mainfrom
afonsojanu:fix/preserve-executable-bit-package-data-5296

Conversation

@afonsojanu

Copy link
Copy Markdown

Summary of changes

A package_data file with a .py extension can end up living in a directory that setuptools' own package discovery also treats as a package (most commonly an implicit namespace package with no __init__.py). When that happens the file gets built twice: once as a plain module, which copies without preserving mode, and once as package data, which does preserve mode. The second copy is supposed to win, but copy_file's own staleness check can decide the destination is already up to date (both copies happen moments apart, reading the same source mtime) and skip the byte copy entirely, leaving the file stuck on whatever mode the module copy left it in. That's how an executable script shipped through package_data quietly loses its executable bit once it collides with module discovery this way.

build_package_data now copies the file's mode directly from its source right after calling copy_file, independent of whether copy_file itself decided a fresh byte copy was needed. Added a regression test reproducing the exact collision (a package registered alongside a subpackage that only exists because of a package_data file inside it), and confirmed it fails without the fix and passes with it.

Closes #5296

Pull Request Checklist

A package_data file with a .py extension, sitting in a directory that
setuptools' own package discovery also picks up as a package, gets
copied twice: once as a module through build_packages (which always
copies with preserve_mode=False), and once as package data through
build_package_data (which does preserve mode). The second copy is the
one meant to win, but copy_file's own "only copy if the source is
newer" check can decide the file is already up to date and skip it
outright, since both copies land within the same build and read the
same source mtime. Mode isn't part of that staleness comparison at
all, so a skip there left the file stuck on whatever the module copy
wrote, silently dropping things like an executable bit.

build_package_data now copies the mode directly from the source file
after calling copy_file, regardless of whether copy_file decided the
content itself needed refreshing. This is cheap and idempotent, and
keeps the two copies from disagreeing about a file's mode no matter
which one happened to run last.
@mergify

mergify Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

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.

[BUG] setuptools 84.0.0 no longer preserves executable bit on  package_data  files in wheels

1 participant