prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add timestamp override to Zip builder
  Zip should have an option to override the timestamp on files in the archive instead of taking it from the file system. This can be useful for repeatable builds or for anonymizing the archive.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `SCons/Tool/zip.py`
  Content:
  ```
  1 """SCons.Tool.zip
  2 
  3 Tool-specific initialization for zip.
  4 
  5 There normally shouldn't be any need to import this module directly.
  6 It will usually be imported through the generic SCons.Tool.Tool()
  7 selection method.
  8 
  9 """
  10 
  11 #
  12 # __COPYRIGHT__
  13 #
  14 # Permission is hereby granted, free of charge, to any person obtaining
  15 # a copy of this software and associated documentation files (the
  16 # "Software"), to deal in the Software without restriction, including
  17 # without limitation the rights to use, copy, modify, merge, publish,
  18 # distribute, sublicense, and/or sell copies of the Software, and to
  19 # permit persons to whom the Software is furnished to do so, subject to
  20 # the following conditions:
  21 #
  22 # The above copyright notice and this permission notice shall be included
  23 # in all copies or substantial portions of the Software.
  24 #
  25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  32 #
  33 
  34 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
  35 
  36 import os.path
  37 
  38 import SCons.Builder
  39 import SCons.Defaults
  40 import SCons.Node.FS
  41 import SCons.Util
  42 
  43 import zipfile
  44 
  45 zip_compression = zipfile.ZIP_DEFLATED
  46 
  47 
  48 def zip(target, source, env):
  49     compression = env.get('ZIPCOMPRESSION', 0)
  50     zf = zipfile.ZipFile(str(target[0]), 'w', compression)
  51     for s in source:
  52         if s.isdir():
  53             for dirpath, dirnames, filenames in os.walk(str(s)):
  54                 for fname in filenames:
  55                     path = os.path.join(dirpath, fname)
  56                     if os.path.isfile(path):
  57                         zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))))
  58         else:
  59             zf.write(str(s), os.path.relpath(str(s), str(env.get('ZIPROOT', ''))))
  60     zf.close()
  61 
  62 # Fix PR #3569 - If you don't specify ZIPCOM and ZIPCOMSTR when creating
  63 # env, then it will ignore ZIPCOMSTR set afterwards.
  64 zipAction = SCons.Action.Action(zip, "$ZIPCOMSTR", varlist=['ZIPCOMPRESSION'])
  65 
  66 ZipBuilder = SCons.Builder.Builder(action=SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),
  67                                    source_factory=SCons.Node.FS.Entry,
  68                                    source_scanner=SCons.Defaults.DirScanner,
  69                                    suffix='$ZIPSUFFIX',
  70                                    multi=1)
  71 
  72 
  73 def generate(env):
  74     """Add Builders and construction variables for zip to an Environment."""
  75     try:
  76         bld = env['BUILDERS']['Zip']
  77     except KeyError:
  78         bld = ZipBuilder
  79         env['BUILDERS']['Zip'] = bld
  80 
  81     env['ZIP'] = 'zip'
  82     env['ZIPFLAGS'] = SCons.Util.CLVar('')
  83     env['ZIPCOM'] = zipAction
  84     env['ZIPCOMPRESSION'] = zip_compression
  85     env['ZIPSUFFIX'] = '.zip'
  86     env['ZIPROOT'] = SCons.Util.CLVar('')
  87 
  88 
  89 def exists(env):
  90     return True
  91 
  92 # Local Variables:
  93 # tab-width:4
  94 # indent-tabs-mode:nil
  95 # End:
  96 # vim: set expandtab tabstop=4 shiftwidth=4:
  97
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/SCons/Tool/zip.py b/SCons/Tool/zip.py
  --- a/SCons/Tool/zip.py
  +++ b/SCons/Tool/zip.py
  @@ -8,8 +8,9 @@
   
   """
   
  +# MIT License
   #
  -# __COPYRIGHT__
  +# Copyright The SCons Foundation
   #
   # Permission is hereby granted, free of charge, to any person obtaining
   # a copy of this software and associated documentation files (the
  @@ -29,39 +30,62 @@
   # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
   # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  -#
  -
  -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
   
  -import os.path
  +import os
   
   import SCons.Builder
   import SCons.Defaults
   import SCons.Node.FS
   import SCons.Util
   
  +import time
   import zipfile
   
  +
   zip_compression = zipfile.ZIP_DEFLATED
   
   
  -def zip(target, source, env):
  -    compression = env.get('ZIPCOMPRESSION', 0)
  -    zf = zipfile.ZipFile(str(target[0]), 'w', compression)
  +def _create_zipinfo_for_file(fname, arcname, date_time, compression):
  +    st = os.stat(fname)
  +    if not date_time:
  +        mtime = time.localtime(st.st_mtime)
  +        date_time = mtime[0:6]
  +    zinfo = zipfile.ZipInfo(filename=arcname, date_time=date_time)
  +    zinfo.external_attr = (st.st_mode & 0xFFFF) << 16  # Unix attributes
  +    zinfo.compress_type = compression
  +    zinfo.file_size = st.st_size
  +    return zinfo
  +
  +
  +def zip_builder(target, source, env):
  +    compression = env.get('ZIPCOMPRESSION', zipfile.ZIP_STORED)
  +    zip_root = str(env.get('ZIPROOT', ''))
  +    date_time = env.get('ZIP_OVERRIDE_TIMESTAMP')
  +
  +    files = []
       for s in source:
           if s.isdir():
               for dirpath, dirnames, filenames in os.walk(str(s)):
                   for fname in filenames:
                       path = os.path.join(dirpath, fname)
                       if os.path.isfile(path):
  -                        zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))))
  +                        files.append(path)
           else:
  -            zf.write(str(s), os.path.relpath(str(s), str(env.get('ZIPROOT', ''))))
  -    zf.close()
  +            files.append(str(s))
  +
  +    with zipfile.ZipFile(str(target[0]), 'w', compression) as zf:
  +        for fname in files:
  +            arcname = os.path.relpath(fname, zip_root)
  +            # TODO: Switch to ZipInfo.from_file when 3.6 becomes the base python version
  +            zinfo = _create_zipinfo_for_file(fname, arcname, date_time, compression)
  +            with open(fname, "rb") as f:
  +                zf.writestr(zinfo, f.read())
  +
   
   # Fix PR #3569 - If you don't specify ZIPCOM and ZIPCOMSTR when creating
   # env, then it will ignore ZIPCOMSTR set afterwards.
  -zipAction = SCons.Action.Action(zip, "$ZIPCOMSTR", varlist=['ZIPCOMPRESSION'])
  +zipAction = SCons.Action.Action(zip_builder, "$ZIPCOMSTR",
  +                                varlist=['ZIPCOMPRESSION', 'ZIPROOT', 'ZIP_OVERRIDE_TIMESTAMP'])
   
   ZipBuilder = SCons.Builder.Builder(action=SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),
                                      source_factory=SCons.Node.FS.Entry,
  
verification_info: {"golden_diff": "diff --git a/SCons/Tool/zip.py b/SCons/Tool/zip.py\n--- a/SCons/Tool/zip.py\n+++ b/SCons/Tool/zip.py\n@@ -8,8 +8,9 @@\n \n \"\"\"\n \n+# MIT License\n #\n-# __COPYRIGHT__\n+# Copyright The SCons Foundation\n #\n # Permission is hereby granted, free of charge, to any person obtaining\n # a copy of this software and associated documentation files (the\n@@ -29,39 +30,62 @@\n # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n-#\n-\n-__revision__ = \"__FILE__ __REVISION__ __DATE__ __DEVELOPER__\"\n \n-import os.path\n+import os\n \n import SCons.Builder\n import SCons.Defaults\n import SCons.Node.FS\n import SCons.Util\n \n+import time\n import zipfile\n \n+\n zip_compression = zipfile.ZIP_DEFLATED\n \n \n-def zip(target, source, env):\n-    compression = env.get('ZIPCOMPRESSION', 0)\n-    zf = zipfile.ZipFile(str(target[0]), 'w', compression)\n+def _create_zipinfo_for_file(fname, arcname, date_time, compression):\n+    st = os.stat(fname)\n+    if not date_time:\n+        mtime = time.localtime(st.st_mtime)\n+        date_time = mtime[0:6]\n+    zinfo = zipfile.ZipInfo(filename=arcname, date_time=date_time)\n+    zinfo.external_attr = (st.st_mode & 0xFFFF) << 16  # Unix attributes\n+    zinfo.compress_type = compression\n+    zinfo.file_size = st.st_size\n+    return zinfo\n+\n+\n+def zip_builder(target, source, env):\n+    compression = env.get('ZIPCOMPRESSION', zipfile.ZIP_STORED)\n+    zip_root = str(env.get('ZIPROOT', ''))\n+    date_time = env.get('ZIP_OVERRIDE_TIMESTAMP')\n+\n+    files = []\n     for s in source:\n         if s.isdir():\n             for dirpath, dirnames, filenames in os.walk(str(s)):\n                 for fname in filenames:\n                     path = os.path.join(dirpath, fname)\n                     if os.path.isfile(path):\n-                        zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))))\n+                        files.append(path)\n         else:\n-            zf.write(str(s), os.path.relpath(str(s), str(env.get('ZIPROOT', ''))))\n-    zf.close()\n+            files.append(str(s))\n+\n+    with zipfile.ZipFile(str(target[0]), 'w', compression) as zf:\n+        for fname in files:\n+            arcname = os.path.relpath(fname, zip_root)\n+            # TODO: Switch to ZipInfo.from_file when 3.6 becomes the base python version\n+            zinfo = _create_zipinfo_for_file(fname, arcname, date_time, compression)\n+            with open(fname, \"rb\") as f:\n+                zf.writestr(zinfo, f.read())\n+\n \n # Fix PR #3569 - If you don't specify ZIPCOM and ZIPCOMSTR when creating\n # env, then it will ignore ZIPCOMSTR set afterwards.\n-zipAction = SCons.Action.Action(zip, \"$ZIPCOMSTR\", varlist=['ZIPCOMPRESSION'])\n+zipAction = SCons.Action.Action(zip_builder, \"$ZIPCOMSTR\",\n+                                varlist=['ZIPCOMPRESSION', 'ZIPROOT', 'ZIP_OVERRIDE_TIMESTAMP'])\n \n ZipBuilder = SCons.Builder.Builder(action=SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),\n                                    source_factory=SCons.Node.FS.Entry,\n", "issue": "Add timestamp override to Zip builder\nZip should have an option to override the timestamp on files in the archive instead of taking it from the file system. This can be useful for repeatable builds or for anonymizing the archive.\n", "before_files": [{"content": "\"\"\"SCons.Tool.zip\n\nTool-specific initialization for zip.\n\nThere normally shouldn't be any need to import this module directly.\nIt will usually be imported through the generic SCons.Tool.Tool()\nselection method.\n\n\"\"\"\n\n#\n# __COPYRIGHT__\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\n# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n#\n\n__revision__ = \"__FILE__ __REVISION__ __DATE__ __DEVELOPER__\"\n\nimport os.path\n\nimport SCons.Builder\nimport SCons.Defaults\nimport SCons.Node.FS\nimport SCons.Util\n\nimport zipfile\n\nzip_compression = zipfile.ZIP_DEFLATED\n\n\ndef zip(target, source, env):\n    compression = env.get('ZIPCOMPRESSION', 0)\n    zf = zipfile.ZipFile(str(target[0]), 'w', compression)\n    for s in source:\n        if s.isdir():\n            for dirpath, dirnames, filenames in os.walk(str(s)):\n                for fname in filenames:\n                    path = os.path.join(dirpath, fname)\n                    if os.path.isfile(path):\n                        zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))))\n        else:\n            zf.write(str(s), os.path.relpath(str(s), str(env.get('ZIPROOT', ''))))\n    zf.close()\n\n# Fix PR #3569 - If you don't specify ZIPCOM and ZIPCOMSTR when creating\n# env, then it will ignore ZIPCOMSTR set afterwards.\nzipAction = SCons.Action.Action(zip, \"$ZIPCOMSTR\", varlist=['ZIPCOMPRESSION'])\n\nZipBuilder = SCons.Builder.Builder(action=SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),\n                                   source_factory=SCons.Node.FS.Entry,\n                                   source_scanner=SCons.Defaults.DirScanner,\n                                   suffix='$ZIPSUFFIX',\n                                   multi=1)\n\n\ndef generate(env):\n    \"\"\"Add Builders and construction variables for zip to an Environment.\"\"\"\n    try:\n        bld = env['BUILDERS']['Zip']\n    except KeyError:\n        bld = ZipBuilder\n        env['BUILDERS']['Zip'] = bld\n\n    env['ZIP'] = 'zip'\n    env['ZIPFLAGS'] = SCons.Util.CLVar('')\n    env['ZIPCOM'] = zipAction\n    env['ZIPCOMPRESSION'] = zip_compression\n    env['ZIPSUFFIX'] = '.zip'\n    env['ZIPROOT'] = SCons.Util.CLVar('')\n\n\ndef exists(env):\n    return True\n\n# Local Variables:\n# tab-width:4\n# indent-tabs-mode:nil\n# End:\n# vim: set expandtab tabstop=4 shiftwidth=4:\n", "path": "SCons/Tool/zip.py"}], "after_files": [{"content": "\"\"\"SCons.Tool.zip\n\nTool-specific initialization for zip.\n\nThere normally shouldn't be any need to import this module directly.\nIt will usually be imported through the generic SCons.Tool.Tool()\nselection method.\n\n\"\"\"\n\n# MIT License\n#\n# Copyright The SCons Foundation\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\n# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport os\n\nimport SCons.Builder\nimport SCons.Defaults\nimport SCons.Node.FS\nimport SCons.Util\n\nimport time\nimport zipfile\n\n\nzip_compression = zipfile.ZIP_DEFLATED\n\n\ndef _create_zipinfo_for_file(fname, arcname, date_time, compression):\n    st = os.stat(fname)\n    if not date_time:\n        mtime = time.localtime(st.st_mtime)\n        date_time = mtime[0:6]\n    zinfo = zipfile.ZipInfo(filename=arcname, date_time=date_time)\n    zinfo.external_attr = (st.st_mode & 0xFFFF) << 16  # Unix attributes\n    zinfo.compress_type = compression\n    zinfo.file_size = st.st_size\n    return zinfo\n\n\ndef zip_builder(target, source, env):\n    compression = env.get('ZIPCOMPRESSION', zipfile.ZIP_STORED)\n    zip_root = str(env.get('ZIPROOT', ''))\n    date_time = env.get('ZIP_OVERRIDE_TIMESTAMP')\n\n    files = []\n    for s in source:\n        if s.isdir():\n            for dirpath, dirnames, filenames in os.walk(str(s)):\n                for fname in filenames:\n                    path = os.path.join(dirpath, fname)\n                    if os.path.isfile(path):\n                        files.append(path)\n        else:\n            files.append(str(s))\n\n    with zipfile.ZipFile(str(target[0]), 'w', compression) as zf:\n        for fname in files:\n            arcname = os.path.relpath(fname, zip_root)\n            # TODO: Switch to ZipInfo.from_file when 3.6 becomes the base python version\n            zinfo = _create_zipinfo_for_file(fname, arcname, date_time, compression)\n            with open(fname, \"rb\") as f:\n                zf.writestr(zinfo, f.read())\n\n\n# Fix PR #3569 - If you don't specify ZIPCOM and ZIPCOMSTR when creating\n# env, then it will ignore ZIPCOMSTR set afterwards.\nzipAction = SCons.Action.Action(zip_builder, \"$ZIPCOMSTR\",\n                                varlist=['ZIPCOMPRESSION', 'ZIPROOT', 'ZIP_OVERRIDE_TIMESTAMP'])\n\nZipBuilder = SCons.Builder.Builder(action=SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),\n                                   source_factory=SCons.Node.FS.Entry,\n                                   source_scanner=SCons.Defaults.DirScanner,\n                                   suffix='$ZIPSUFFIX',\n                                   multi=1)\n\n\ndef generate(env):\n    \"\"\"Add Builders and construction variables for zip to an Environment.\"\"\"\n    try:\n        bld = env['BUILDERS']['Zip']\n    except KeyError:\n        bld = ZipBuilder\n        env['BUILDERS']['Zip'] = bld\n\n    env['ZIP'] = 'zip'\n    env['ZIPFLAGS'] = SCons.Util.CLVar('')\n    env['ZIPCOM'] = zipAction\n    env['ZIPCOMPRESSION'] = zip_compression\n    env['ZIPSUFFIX'] = '.zip'\n    env['ZIPROOT'] = SCons.Util.CLVar('')\n\n\ndef exists(env):\n    return True\n\n# Local Variables:\n# tab-width:4\n# indent-tabs-mode:nil\n# End:\n# vim: set expandtab tabstop=4 shiftwidth=4:\n", "path": "SCons/Tool/zip.py"}]}