pypa / pypa/setuptools

Half the .pyc files in an egg generated by bdist_egg are automatically stale.

Open
#973 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Needs Triage
Dominant language
Python
Stars
2.9k
Forks
1.4k
Avg merge
1d 1h
Merged PRs (30d)
1

Description

When creating an egg using python setup.py bdist_egg, half (on average) of the generated .pyc files have an embedded timestamp that doesn't match the timestamp of the corresponding .py files.

The problem arises with source files (.py files) whose last modification time has an odd number of seconds. When the .pyc file is generated, it embeds the mtime of the .py file (truncated to second resolution if necessary) in bytes 4-7 as usual. But then when the zipfile is created, the timestamp of the .py file is stored as an even unix timestamp, since zip files only support 2-second resolution. That means that after unzipping the egg, the .pyc file (with its odd embedded timestamp) doesn't match the .py file, and the .pyc file has to be regenerated at import time.

In contrast, source files with an even timestamp are fine; there the .pyc file doesn't need to be regenerated.

Here's an example, using a .tar.gz for pyflakes, downloaded from PyPI. Note that for pyflakes/checker.py, the modification time doesn't match the time embedded in the .pyc (the two times differ by a second, which is enough to force a recompile), while for pyflakes/api.py, the times match.

(myenv) taniyama:temp mdickinson$ ls
pyflakes-1.5.0.tar.gz
(myenv) taniyama:temp mdickinson$ tar -zxf pyflakes-1.5.0.tar.gz 
(myenv) taniyama:temp mdickinson$ cd pyflakes-1.5.0
(myenv) taniyama:pyflakes-1.5.0 mdickinson$ python setup.py bdist_egg >/dev/null
zip_safe flag not set; analyzing archive contents...
pyflakes.checker: module references __file__
pyflakes.checker: module references __path__
pyflakes.test.test_api: module references __file__
pyflakes.test.test_undefined_names: module references __file__
pyflakes.test.test_undefined_names: module references __path__
(myenv) taniyama:pyflakes-1.5.0 mdickinson$ cd dist
(myenv) taniyama:dist mdickinson$ unzip pyflakes-1.5.0-py2.7.egg >/dev/null
(myenv) taniyama:dist mdickinson$ python
Python 2.7.13 (default, Dec 18 2016, 05:43:36) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import os, struct
>>> os.stat('pyflakes/checker.py').st_mtime
1484009994.0
>>> struct.unpack('<L', open('pyflakes/checker.pyc', 'rb').read(8)[4:])[0]  # timestamp embedded in .pyc
1484009995
>>> os.stat('pyflakes/api.py').st_mtime
1458062906.0
>>> struct.unpack('<L', open('pyflakes/api.pyc', 'rb').read(8)[4:])[0]  # timestamp embedded in .pyc
1458062906

Background: this turned up while I was investigating the slow startup time of a large Python-based application. Much of the startup time was down to *.py files being recompiled to *.pyc files, in spite of the fact that the .pyc files were already present in the installation.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the bdist_egg command and the archive timestamp handling that runs during egg creation. Reproduce the issue with source files whose modification times have odd seconds, then inspect the generated and extracted .pyc metadata. Done means extracted .py and .pyc timestamps agree so imports do not unnecessarily recompile modules.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.