aboutsummaryrefslogtreecommitdiff
path: root/pkg/private/zip/build_zip.py
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/private/zip/build_zip.py')
-rw-r--r--pkg/private/zip/build_zip.py305
1 files changed, 0 insertions, 305 deletions
diff --git a/pkg/private/zip/build_zip.py b/pkg/private/zip/build_zip.py
deleted file mode 100644
index ca48a08..0000000
--- a/pkg/private/zip/build_zip.py
+++ /dev/null
@@ -1,305 +0,0 @@
-# Copyright 2015 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""This tool builds zip files from a list of inputs."""
-
-import argparse
-import datetime
-import logging
-import os
-import sys
-import zipfile
-
-from pkg.private import build_info
-from pkg.private import manifest
-
-ZIP_EPOCH = 315532800
-
-# Unix dir bit and Windows dir bit. Magic from zip spec
-UNIX_DIR_BIT = 0o40000
-MSDOS_DIR_BIT = 0x10
-UNIX_SYMLINK_BIT = 0o120000
-
-def _create_argument_parser():
- """Creates the command line arg parser."""
- parser = argparse.ArgumentParser(description='create a zip file',
- fromfile_prefix_chars='@')
- parser.add_argument('-o', '--output', type=str,
- help='The output zip file path.')
- parser.add_argument(
- '-d', '--directory', type=str, default='/',
- help='An absolute path to use as a prefix for all files in the zip.')
- parser.add_argument(
- '-t', '--timestamp', type=int, default=ZIP_EPOCH,
- help='The unix time to use for files added into the zip. values prior to'
- ' Jan 1, 1980 are ignored.')
- parser.add_argument('--stamp_from', default='',
- help='File to find BUILD_STAMP in')
- parser.add_argument(
- '-m', '--mode',
- help='The file system mode to use for files added into the zip.')
- parser.add_argument(
- '-c', '--compression_type',
- help='The compression type to use')
- parser.add_argument(
- '-l', '--compression_level',
- help='The compression level to use')
- parser.add_argument('--manifest',
- help='manifest of contents to add to the layer.',
- required=True)
- parser.add_argument(
- 'files', type=str, nargs='*',
- help='Files to be added to the zip, in the form of {srcpath}={dstpath}.')
- return parser
-
-
-def _combine_paths(left, right):
- result = left.rstrip('/') + '/' + right.lstrip('/')
-
- # important: remove leading /'s: the zip format spec says paths should never
- # have a leading slash, but Python will happily do this. The built-in zip
- # tool in Windows will complain that such a zip file is invalid.
- return result.lstrip('/')
-
-
-def parse_date(ts):
- ts = datetime.datetime.utcfromtimestamp(ts)
- return (ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second)
-
-
-class ZipWriter(object):
-
- def __init__(self, output_path: str, time_stamp: int, default_mode: int, compression_type: str, compression_level: int):
- """Create a writer.
-
- You must close() after use or use in a 'with' statement.
-
- Args:
- output_path: path to write to
- time_stamp: time stamp to add to files
- default_mode: file mode to use if not specified in the entry.
- """
- self.output_path = output_path
- self.time_stamp = time_stamp
- self.default_mode = default_mode
- compressions = {
- "deflated": zipfile.ZIP_DEFLATED,
- "lzma": zipfile.ZIP_LZMA,
- "bzip2": zipfile.ZIP_BZIP2,
- "stored": zipfile.ZIP_STORED
- }
- self.compression_type = compressions[compression_type]
- self.compression_level = compression_level
- self.zip_file = zipfile.ZipFile(self.output_path, mode='w', compression=self.compression_type)
-
- def __enter__(self):
- return self
-
- def __exit__(self, t, v, traceback):
- self.close()
-
- def close(self):
- self.zip_file.close()
- self.zip_file = None
-
- def writestr(self, entry_info, content: str, compresslevel: int):
- if sys.version_info >= (3, 7):
- self.zip_file.writestr(entry_info, content, compresslevel=compresslevel)
- else:
- # Python 3.6 and lower don't support compresslevel
- self.zip_file.writestr(entry_info, content)
- if compresslevel != 6:
- logging.warn("Custom compresslevel is not supported with python < 3.7")
-
- def make_zipinfo(self, path: str, mode: str):
- """Create a Zipinfo.
-
- Args:
- path: file path
- mode: file mode
- """
- entry_info = zipfile.ZipInfo(filename=path, date_time=self.time_stamp)
- # See http://www.pkware.com/documents/casestudies/APPNOTE.TXT
- # denotes UTF-8 encoded file name.
- entry_info.flag_bits |= 0x800
-
- # See: https://trac.edgewall.org/attachment/ticket/8919/ZipDownload.patch
- # external_attr is 4 bytes in size. The high order two bytes represent UNIX
- # permission and file type bits, while the low order two contain MS-DOS FAT
- # file attributes.
- if mode:
- f_mode = int(mode, 8)
- else:
- f_mode = self.default_mode
- entry_info.external_attr = f_mode << 16
- return entry_info
-
- def add_manifest_entry(self, entry):
- """Add an entry to the zip file.
-
- Args:
- zip_file: ZipFile to write to
- entry: manifest entry
- """
-
- entry_type = entry.type
- dest = entry.dest
- src = entry.src
- mode = entry.mode
- user = entry.user
- group = entry.group
-
- # Use the pkg_tar mode/owner remapping as a fallback
- dst_path = dest.strip('/')
- if entry_type == manifest.ENTRY_IS_DIR and not dst_path.endswith('/'):
- dst_path += '/'
- entry_info = self.make_zipinfo(path=dst_path, mode=mode)
-
- if entry_type == manifest.ENTRY_IS_FILE:
- entry_info.compress_type = self.compression_type
- # Using utf-8 for the file names is for python <3.7 compatibility.
- with open(src.encode('utf-8'), 'rb') as src_content:
- self.writestr(entry_info, src_content.read(), compresslevel=self.compression_level)
- elif entry_type == manifest.ENTRY_IS_DIR:
- entry_info.compress_type = zipfile.ZIP_STORED
- # Set directory bits
- entry_info.external_attr |= (UNIX_DIR_BIT << 16) | MSDOS_DIR_BIT
- self.zip_file.writestr(entry_info, '')
- elif entry_type == manifest.ENTRY_IS_LINK:
- entry_info.compress_type = zipfile.ZIP_STORED
- # Set directory bits
- entry_info.external_attr |= (UNIX_SYMLINK_BIT << 16)
- self.zip_file.writestr(entry_info, src.encode('utf-8'))
- elif entry_type == manifest.ENTRY_IS_TREE:
- self.add_tree(src, dst_path, mode)
- elif entry_type == manifest.ENTRY_IS_EMPTY_FILE:
- entry_info.compress_type = zipfile.ZIP_STORED
- self.zip_file.writestr(entry_info, '')
- else:
- raise Exception('Unknown type for manifest entry:', entry)
-
- def add_tree(self, tree_top: str, destpath: str, mode: int):
- """Add a tree artifact to the zip file.
-
- Args:
- tree_top: the top of the tree to add
- destpath: the path under which to place the files
- mode: if not None, file mode to apply to all files
- """
-
- # We expect /-style paths.
- tree_top = os.path.normpath(tree_top).replace(os.path.sep, '/')
-
- # Again, we expect /-style paths.
- dest = destpath.strip('/') # redundant, dests should never have / here
- dest = os.path.normpath(dest).replace(os.path.sep, '/')
- # paths should not have a leading ./
- dest = '' if dest == '.' else dest + '/'
-
- to_write = {}
- for root, dirs, files in os.walk(tree_top):
- # While `tree_top` uses '/' as a path separator, results returned by
- # `os.walk` and `os.path.join` on Windows may not.
- root = os.path.normpath(root).replace(os.path.sep, '/')
-
- rel_path_from_top = root[len(tree_top):].lstrip('/')
- if rel_path_from_top:
- dest_dir = dest + rel_path_from_top + '/'
- else:
- dest_dir = dest
- to_write[dest_dir] = None
- for file in files:
- content_path = os.path.abspath(os.path.join(root, file))
- if os.name == "nt":
- # "To specify an extended-length path, use the `\\?\` prefix. For
- # example, `\\?\D:\very long path`."[1]
- #
- # [1]: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
- to_write[dest_dir + file] = "\\\\?\\" + content_path
- else:
- to_write[dest_dir + file] = content_path
-
- for path in sorted(to_write.keys()):
- content_path = to_write[path]
- if content_path:
- # If mode is unspecified, derive the mode from the file's mode.
- if mode is None:
- f_mode = "0o755" if os.access(content_path, os.X_OK) else self.default_mode
- else:
- f_mode = mode
- entry_info = self.make_zipinfo(path=path, mode=f_mode)
- entry_info.compress_type = self.compression_type
- with open(content_path, 'rb') as src:
- self.writestr(entry_info, src.read(), compresslevel=self.compression_level)
- else:
- # Implicitly created directory
- dir_path = path
- if not dir_path.endswith('/'):
- dir_path += '/'
- entry_info = self.make_zipinfo(path=dir_path, mode="0o755")
- entry_info.compress_type = zipfile.ZIP_STORED
- # Set directory bits
- entry_info.external_attr |= (UNIX_DIR_BIT << 16) | MSDOS_DIR_BIT
- self.zip_file.writestr(entry_info, '')
-
-def _load_manifest(prefix, manifest_path):
- manifest_map = {}
-
- for entry in manifest.read_entries_from_file(manifest_path):
- entry.dest = _combine_paths(prefix, entry.dest)
- manifest_map[entry.dest] = entry
-
- # We modify the dictionary as we're iterating over it, so we need to listify
- # the keys here.
- manifest_keys = list(manifest_map.keys())
- # Add all parent directories of entries that have not been added explicitly.
- for dest in manifest_keys:
- parent = dest
- # TODO: use pathlib instead of string manipulation?
- for _ in range(dest.count("/")):
- parent, _, _ = parent.rpartition("/")
- if parent and parent not in manifest_map:
- manifest_map[parent] = manifest.ManifestEntry(
- type = manifest.ENTRY_IS_DIR,
- dest = parent,
- src = "",
- mode = "0o755",
- user = None,
- group = None,
- uid = None,
- gid = None,
- origin = "parent directory of {}".format(manifest_map[dest].origin),
- )
-
- return sorted(manifest_map.values(), key = lambda x: x.dest)
-
-def main(args):
- unix_ts = max(ZIP_EPOCH, args.timestamp)
- if args.stamp_from:
- unix_ts = build_info.get_timestamp(args.stamp_from)
- ts = parse_date(unix_ts)
- default_mode = None
- if args.mode:
- default_mode = int(args.mode, 8)
- compression_level = int(args.compression_level)
-
- manifest = _load_manifest(args.directory, args.manifest)
- with ZipWriter(
- args.output, time_stamp=ts, default_mode=default_mode, compression_type=args.compression_type, compression_level=compression_level) as zip_out:
- for entry in manifest:
- zip_out.add_manifest_entry(entry)
-
-
-if __name__ == '__main__':
- arg_parser = _create_argument_parser()
- main(arg_parser.parse_args())