Skip to content

Fix executable bit lost on package data files that double as modules - #5318

Closed
afonsojanu wants to merge 1 commit into
pypa:mainfrom
afonsojanu:fix/package-data-executable-bit-5296
Closed

Fix executable bit lost on package data files that double as modules#5318
afonsojanu wants to merge 1 commit into
pypa:mainfrom
afonsojanu:fix/package-data-executable-bit-5296

Conversation

@afonsojanu

Copy link
Copy Markdown

Summary of changes

Since setuptools 84.0.0, a package data file whose name ends in .py can lose its executable permission bit when it ends up in a wheel. It's a double-copy issue: such a file gets copied into build/lib twice on its way there, once through build_module() (which treats it as an ordinary module and doesn't preserve mode) and once through build_package_data() (which does ask for mode to be preserved). Both target the exact same path.

copy_file() skips its own copy once source and target already agree on mtime. The first copy already brought them into agreement (via preserve_times), so by the time the second, mode-preserving copy runs, it sees nothing to do and returns before ever reaching the code that fixes the mode. Whatever mode the first copy left behind (the OS default for a newly created file) is what survives into the wheel.

build_package_data() now forces its own copy the same way self.force already does for other commands, since it needs to be authoritative about a package data file's final state no matter what an unrelated earlier copy left on the same path.

I bisected this to the "Sync with distutils @ e8eb878" merge (#5292), specifically the pypa/distutils#379 mtime fix: preserving mtime with full nanosecond precision (rather than the previous, less exact copy) is what made the two copies' timestamps compare as exactly equal, which is what makes copy_file()'s update-skip trigger on the second one. The underlying double-copy-with-different-preserve_mode-settings issue looks like it predates that, just masked by imprecise timestamp comparisons before.

Closes #5296

Pull Request Checklist

A data file that also ends in .py gets copied twice on its way into
build/lib: once by build_module(), which treats it as an ordinary
module and copies it without preserving mode, and again by
build_package_data(), which does ask for the mode to be preserved.
Both copies land on the exact same target.

copy_file() skips its own work once source and target already agree
on mtime, and preserve_times on the first copy already brought them
into agreement, since it ran first and used the same source file. So
by the time the second, mode-preserving copy runs, it sees nothing to
do and returns early, before it ever reaches the code that would fix
the mode up. The wrong permissions from the first copy are what ends
up in the wheel.

build_package_data() now forces its copy the same way self.force
already does elsewhere, since it has to be authoritative about the
final state of a package data file regardless of what an unrelated
earlier copy left behind on the same path.

Added a test mirroring the existing executable-data test, but with a
.py-suffixed data file, which is what actually exercises the double
copy.

Fixes pypa#5296
@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

@afonsojanu

Copy link
Copy Markdown
Author

Closing this in favor of #5319, which I opened separately for the same issue. Ended up investigating this from two directions at once and only caught the overlap after both were already up. #5319's fix is more targeted: it copies the mode directly instead of forcing the whole file through copy_file's update check, which avoids re-copying package data that's genuinely already up to date. Sorry for the duplicate noise.

@afonsojanu afonsojanu closed this Aug 29, 2026
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