summaryrefslogtreecommitdiff
path: root/pkg/private/zip/build_zip.py
diff options
context:
space:
mode:
authoraiuto <aiuto@google.com>2021-12-06 23:49:58 -0500
committerGitHub <noreply@github.com>2021-12-06 23:49:58 -0500
commita5fc44fd1d90e4a331ec2994481663288f29b2a1 (patch)
treef145eed953230d3e9b3d3fed0e04af6c53f719f1 /pkg/private/zip/build_zip.py
parentbe550c6988cabe28dcbfd0d43d281cf3e3129f1c (diff)
downloadbazelbuild-rules_pkg-a5fc44fd1d90e4a331ec2994481663288f29b2a1.tar.gz
Split pkg_zip implementation out of pkg.bzl (#461)
* Decouple zip implementation from pkg.bzl.
Diffstat (limited to 'pkg/private/zip/build_zip.py')
-rw-r--r--pkg/private/zip/build_zip.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/pkg/private/zip/build_zip.py b/pkg/private/zip/build_zip.py
new file mode 100644
index 0000000..1b26a1b
--- /dev/null
+++ b/pkg/private/zip/build_zip.py
@@ -0,0 +1,132 @@
+# 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 json
+import zipfile
+
+from pkg.private import build_info
+from pkg.private import helpers
+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
+
+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('--manifest',
+ help='manifest of contents to add to the layer.')
+ 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)
+
+def _add_manifest_entry(options, zip_file, entry, default_mode, ts):
+ """Add an entry to the zip file.
+
+ Args:
+ options: parsed options
+ zip_file: ZipFile to write to
+ entry: manifest entry
+ default_mode: (int) file mode to use if not specified in the entry.
+ ts: (int) time stamp to add to files
+ """
+
+ entry_type, dest, src, mode, user, group = entry
+
+ # Use the pkg_tar mode/owner remaping as a fallback
+ non_abs_path = dest.strip('/')
+ dst_path = _combine_paths(options.directory, non_abs_path)
+ if entry_type == manifest.ENTRY_IS_DIR and not dst_path.endswith('/'):
+ dst_path += '/'
+ entry_info = zipfile.ZipInfo(filename=dst_path, date_time=ts)
+ # See http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+ # denotes UTF-8 encoded file name.
+ entry_info.flag_bits |= 0x800
+ if mode:
+ f_mode = int(mode, 8)
+ else:
+ f_mode = default_mode
+
+ # 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.
+ entry_info.external_attr = f_mode << 16
+ if entry_type == manifest.ENTRY_IS_FILE:
+ entry_info.compress_type = zipfile.ZIP_DEFLATED
+ with open(src, 'rb') as src:
+ zip_file.writestr(entry_info, src.read())
+ 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
+ zip_file.writestr(entry_info, '')
+ # TODO(#309): All the rest
+
+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)
+
+ with zipfile.ZipFile(args.output, mode='w') as zip_file:
+ if args.manifest:
+ with open(args.manifest, 'r') as manifest_fp:
+ manifest = json.load(manifest_fp)
+ for entry in manifest:
+ _add_manifest_entry(args, zip_file, entry, default_mode, ts)
+
+
+if __name__ == '__main__':
+ arg_parser = _create_argument_parser()
+ main(arg_parser.parse_args())