Browse Source

dist: Add some determinism support to bdist_apps

It's necessary to set PYTHONHASHSEED=0 as well as SOURCE_DATE_EPOCH for deterministic compilation, and moreover, the generated zip files do still have timestamps in them.
rdb 4 years ago
parent
commit
68daa238b1
2 changed files with 31 additions and 5 deletions
  1. 3 3
      direct/src/dist/FreezeTool.py
  2. 28 2
      direct/src/dist/commands.py

+ 3 - 3
direct/src/dist/FreezeTool.py

@@ -958,7 +958,7 @@ class Freezer:
 
         # Scan the directory, looking for .py files.
         modules = []
-        for basename in os.listdir(pathname):
+        for basename in sorted(os.listdir(pathname)):
             if basename.endswith('.py') and basename != '__init__.py':
                 modules.append(basename[:-3])
 
@@ -992,7 +992,7 @@ class Freezer:
             modulePath = self.getModulePath(topName)
             if modulePath:
                 for dirname in modulePath:
-                    for basename in os.listdir(dirname):
+                    for basename in sorted(os.listdir(dirname)):
                         if os.path.exists(os.path.join(dirname, basename, '__init__.py')):
                             parentName = '%s.%s' % (topName, basename)
                             newParentName = '%s.%s' % (newTopName, basename)
@@ -2587,7 +2587,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             except OSError:
                 self.msg(2, "can't list directory", dir)
                 continue
-            for name in names:
+            for name in sorted(names):
                 mod = None
                 for suff in self.suffixes:
                     n = len(suff)

+ 28 - 2
direct/src/dist/commands.py

@@ -1045,6 +1045,7 @@ class build_apps(setuptools.Command):
 
         rootdir = os.getcwd()
         for dirname, subdirlist, filelist in os.walk(rootdir):
+            subdirlist.sort()
             dirpath = os.path.relpath(dirname, rootdir)
             if skip_directory(dirpath):
                 self.announce('skipping directory {}'.format(dirpath))
@@ -1414,7 +1415,8 @@ class bdist_apps(setuptools.Command):
             zf.write(build_dir, base_dir)
 
             for dirpath, dirnames, filenames in os.walk(build_dir):
-                for name in sorted(dirnames):
+                dirnames.sort()
+                for name in dirnames:
                     path = os.path.normpath(os.path.join(dirpath, name))
                     zf.write(path, path.replace(build_dir, base_dir, 1))
                 for name in filenames:
@@ -1429,16 +1431,39 @@ class bdist_apps(setuptools.Command):
         build_cmd = self.get_finalized_command('build_apps')
         binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
 
+        source_date = os.environ.get('SOURCE_DATE_EPOCH', '').strip()
+        if source_date:
+            max_mtime = int(source_date)
+        else:
+            max_mtime = None
+
         def tarfilter(tarinfo):
             if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
                 tarinfo.mode = 0o755
             else:
                 tarinfo.mode = 0o644
+
+            # This isn't interesting information to retain for distribution.
+            tarinfo.uid = 0
+            tarinfo.gid = 0
+            tarinfo.uname = ""
+            tarinfo.gname = ""
+
+            if max_mtime is not None and tarinfo.mtime >= max_mtime:
+                tarinfo.mtime = max_mtime
+
             return tarinfo
 
-        with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
+        filename = '{}.tar.{}'.format(basename, tar_compression)
+        with tarfile.open(filename, 'w|{}'.format(tar_compression)) as tf:
             tf.add(build_dir, base_dir, filter=tarfilter)
 
+        if tar_compression == 'gz' and max_mtime is not None:
+            # Python provides no elegant way to overwrite the gzip timestamp.
+            with open(filename, 'r+b') as fp:
+                fp.seek(4)
+                fp.write(struct.pack("<L", max_mtime))
+
     def create_nsis(self, basename, build_dir, is_64bit):
         # Get a list of build applications
         build_cmd = self.get_finalized_command('build_apps')
@@ -1500,6 +1525,7 @@ class bdist_apps(setuptools.Command):
         nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
         build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
         for root, dirs, files in os.walk(build_dir):
+            dirs.sort()
             for name in files:
                 basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
                 file = p3d.Filename(basefile)