Skip to content

Commit

Permalink
bpo-29708: support SOURCE_DATE_EPOCH env var in py_compile
Browse files Browse the repository at this point in the history
to allow for reproducible builds of python packages

See https://reproducible-builds.org/ for why this is good
and https://reproducible-builds.org/specs/source-date-epoch/
for the definition of this variable.

Background:
In some distributions like openSUSE, binary rpms contain precompiled .pyc files.

And packages like amqp or twisted dynamically generate .py files at build time
so those have the current time and that timestamp gets embedded
into the .pyc file header.
When we then adapt file timestamps in rpms to be constant,
the timestamp in the .pyc header will no more match
the .py timestamp in the filesystem.
The software will still work, but it will not use the .pyc file as it should.
  • Loading branch information
bmwiedemann committed Jan 10, 2018
1 parent f80c0ca commit 28364e0
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Doc/library/py_compile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ byte-code cache files in the directory containing the source code.
enum and controls how the generated ``.pyc`` files are invalidated at
runtime.

If the SOURCE_DATE_EPOCH environment variable is set, the timestamp entry in
the .pyc file header, will be limited to this value.
See https://reproducible-builds.org/specs/source-date-epoch/ for more info.

.. versionchanged:: 3.2
Changed default value of *cfile* to be :PEP:`3147`-compliant. Previous
default was *file* + ``'c'`` (``'o'`` if optimization was enabled).
Expand Down
8 changes: 8 additions & 0 deletions Lib/py_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1,
pass
if invalidation_mode == PycInvalidationMode.TIMESTAMP:
source_stats = loader.path_stats(file)
source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH')
if source_date_epoch:
try:
source_date_epoch = int(source_date_epoch)
except ValueError:
raise ValueError("SOURCE_DATE_EPOCH is not a valid integer")
if source_stats['mtime'] > source_date_epoch:
source_stats['mtime'] = source_date_epoch
bytecode = importlib._bootstrap_external._code_to_timestamp_pyc(
code, source_stats['mtime'], source_stats['size'])
else:
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_py_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ def test_bad_coding(self):
self.assertFalse(os.path.exists(
importlib.util.cache_from_source(bad_coding)))

def test_source_date_epoch(self):
testtime = 123456789
with support.EnvironmentVarGuard() as env:
env["SOURCE_DATE_EPOCH"] = str(testtime)
py_compile.compile(self.source_path, self.pyc_path)
self.assertTrue(os.path.exists(self.pyc_path))
self.assertFalse(os.path.exists(self.cache_path))
with open(self.pyc_path, "rb") as f:
f.read(4) # Skip the magic number.
timebytes = f.read(4) # Read timestamp.
self.assertEqual(testtime, int.from_bytes(timebytes, 'little'))

@unittest.skipIf(sys.flags.optimize > 0, 'test does not work with -O')
def test_double_dot_no_clobber(self):
# http://bugs.python.org/issue22966
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
If the SOURCE_DATE_EPOCH environment variable is set,
py_compile will use it to override the timestamps it puts into .pyc files.

0 comments on commit 28364e0

Please sign in to comment.