diff options
author | Mitchell Wills <mwills@google.com> | 2019-07-29 10:29:32 -0700 |
---|---|---|
committer | Mitchell Wills <mwills@google.com> | 2019-07-29 11:04:57 -0700 |
commit | 1c790ca655ff2804409a893a96c4d91771a971c5 (patch) | |
tree | 91be8da42531ccf1c0908300b1616b37cc1b1e36 /tools/generate-self-extracting-archive.py | |
parent | f4a8b6415f5baec707802964a2c522d24ca8f38c (diff) |
Add build script to create a self extracting archive with a click through license
Bug: 130257221
Test: generated an archive using the script and ran the result
Change-Id: If1147cd41fa939b0d9958196e627042b5731dc14
Diffstat (limited to 'tools/generate-self-extracting-archive.py')
-rwxr-xr-x | tools/generate-self-extracting-archive.py | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/tools/generate-self-extracting-archive.py b/tools/generate-self-extracting-archive.py new file mode 100755 index 0000000000..f0b7568116 --- /dev/null +++ b/tools/generate-self-extracting-archive.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# +# Copyright (C) 2019 The Android Open Source Project +# +# 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. + +""" +Generates a self extracting archive with a license click through. + +Usage: + generate-self-extracting-archive.py $OUTPUT_FILE $INPUT_ARCHIVE $COMMENT $LICENSE_FILE + + The comment will be included at the beginning of the output archive file. + +Output: + The output of the script is a single executable file that when run will + display the provided license and if the user accepts extract the wrapped + archive. + + The layout of the output file is roughly: + * Executable shell script that extracts the archive + * Actual archive contents + * Zip file containing the license +""" + +import tempfile +import sys +import os +import zipfile + +_HEADER_TEMPLATE = """#!/bin/sh +# +{comment_line} +# +# Usage is subject to the enclosed license agreement + +echo +echo The license for this software will now be displayed. +echo You must agree to this license before using this software. +echo +echo -n Press Enter to view the license +read dummy +echo +more << EndOfLicense +{license} +EndOfLicense + +if test $? != 0 +then + echo "ERROR: Couldn't display license file" 1>&2 + exit 1 +fi +echo +echo -n 'Type "I ACCEPT" if you agree to the terms of the license: ' +read typed +if test "$typed" != "I ACCEPT" +then + echo + echo "You didn't accept the license. Extraction aborted." + exit 2 +fi +echo +{extract_command} +if test $? != 0 +then + echo + echo "ERROR: Couldn't extract files." 1>&2 + exit 3 +else + echo + echo "Files extracted successfully." +fi +exit 0 +""" + +_PIPE_CHUNK_SIZE = 1048576 +def _pipe_bytes(src, dst): + while True: + b = src.read(_PIPE_CHUNK_SIZE) + if not b: + break + dst.write(b) + +_MAX_OFFSET_WIDTH = 8 +def _generate_extract_command(start, end, extract_name): + """Generate the extract command. + + The length of this string must be constant no matter what the start and end + offsets are so that its length can be computed before the actual command is + generated. + + Args: + start: offset in bytes of the start of the wrapped file + end: offset in bytes of the end of the wrapped file + extract_name: of the file to create when extracted + + """ + # start gets an extra character for the '+' + # for tail +1 is the start of the file, not +0 + start_str = ('+%d' % (start + 1)).rjust(_MAX_OFFSET_WIDTH + 1) + if len(start_str) != _MAX_OFFSET_WIDTH + 1: + raise Exception('Start offset too large (%d)' % start) + + end_str = ('%d' % end).rjust(_MAX_OFFSET_WIDTH) + if len(end_str) != _MAX_OFFSET_WIDTH: + raise Exception('End offset too large (%d)' % end) + + return "tail -c %s $0 | head -c %s > %s\n" % (start_str, end_str, extract_name) + + +def main(argv): + output_filename = argv[1] + input_archive_filename = argv[2] + comment = argv[3] + license_filename = argv[4] + + input_archive_size = os.stat(input_archive_filename).st_size + + with open(license_filename, 'r') as license_file: + license = license_file.read() + + comment_line = '# %s\n' % comment + extract_name = os.path.basename(input_archive_filename) + + # Compute the size of the header before writing the file out. This is required + # so that the extract command, which uses the contents offset, can be created + # and included inside the header. + header_for_size = _HEADER_TEMPLATE.format( + comment_line=comment_line, + license=license, + extract_command=_generate_extract_command(0, 0, extract_name), + ) + header_size = len(header_for_size.encode('utf-8')) + + # write the final output + with open(output_filename, 'wb') as output: + output.write(_HEADER_TEMPLATE.format( + comment_line=comment_line, + license=license, + extract_command=_generate_extract_command(header_size, input_archive_size, extract_name), + ).encode('utf-8')) + + with open(input_archive_filename, 'rb') as input_file: + _pipe_bytes(input_file, output) + + with tempfile.TemporaryFile() as trailing_zip: + with zipfile.ZipFile(trailing_zip, 'w') as myzip: + myzip.writestr('license.txt', license, compress_type=zipfile.ZIP_STORED) + + # append the trailing zip to the end of the file + trailing_zip.seek(0) + _pipe_bytes(trailing_zip, output) + +if __name__ == "__main__": + main(sys.argv) |