| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Creates a zip archive for the Chrome Remote Desktop Host installer. |
| |
| This script builds a zip file that contains all the files needed to build an |
| installer for Chrome Remote Desktop Host. |
| |
| This zip archive is then used by the signing bots to: |
| (1) Sign the binaries |
| (2) Build the final installer |
| |
| TODO(garykac) We should consider merging this with build-webapp.py. |
| """ |
| |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import zipfile |
| |
| sys.path.append(os.path.join( |
| os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, |
| "build", "android", "gyp")) |
| from util import build_utils |
| |
| def cleanDir(dir): |
| """Deletes and recreates the dir to make sure it is clean. |
| |
| Args: |
| dir: The directory to clean. |
| """ |
| try: |
| shutil.rmtree(dir) |
| except OSError: |
| if os.path.exists(dir): |
| raise |
| else: |
| pass |
| os.makedirs(dir, 0775) |
| |
| |
| def buildDefDictionary(definitions): |
| """Builds the definition dictionary from the VARIABLE=value array. |
| |
| Args: |
| defs: Array of variable definitions: 'VARIABLE=value'. |
| |
| Returns: |
| Dictionary with the definitions. |
| """ |
| defs = {} |
| for d in definitions: |
| (key, val) = d.split('=') |
| defs[key] = val |
| return defs |
| |
| |
| def remapSrcFile(dst_root, src_roots, src_file): |
| """Calculates destination file path and creates directory. |
| |
| Any matching |src_roots| prefix is stripped from |src_file| before |
| appending to |dst_root|. |
| |
| For example, given: |
| dst_root = '/output' |
| src_roots = ['host/installer/mac'] |
| src_file = 'host/installer/mac/Scripts/keystone_install.sh' |
| The final calculated path is: |
| '/output/Scripts/keystone_install.sh' |
| |
| The |src_file| must match one of the |src_roots| prefixes. If there are no |
| matches, then an error is reported. |
| |
| If multiple |src_roots| match, then only the first match is applied. Because |
| of this, if you have roots that share a common prefix, the longest string |
| should be first in this array. |
| |
| Args: |
| dst_root: Target directory where files are copied. |
| src_roots: Array of path prefixes which will be stripped of |src_file| |
| (if they match) before appending it to the |dst_root|. |
| src_file: Source file to be copied. |
| Returns: |
| Full path to destination file in |dst_root|. |
| """ |
| # Strip of directory prefix. |
| found_root = False |
| for root in src_roots: |
| root = os.path.normpath(root) |
| src_file = os.path.normpath(src_file) |
| if os.path.commonprefix([root, src_file]) == root: |
| src_file = os.path.relpath(src_file, root) |
| found_root = True |
| break |
| |
| if not found_root: |
| error('Unable to match prefix for %s' % src_file) |
| |
| dst_file = os.path.join(dst_root, src_file) |
| # Make sure target directory exists. |
| dst_dir = os.path.dirname(dst_file) |
| if not os.path.exists(dst_dir): |
| os.makedirs(dst_dir, 0775) |
| return dst_file |
| |
| |
| def copyFileWithDefs(src_file, dst_file, defs): |
| """Copies from src_file to dst_file, performing variable substitution. |
| |
| Any @@VARIABLE@@ in the source is replaced with the value of VARIABLE |
| in the |defs| dictionary when written to the destination file. |
| |
| Args: |
| src_file: Full or relative path to source file to copy. |
| dst_file: Relative path (and filename) where src_file should be copied. |
| defs: Dictionary of variable definitions. |
| """ |
| data = open(src_file, 'r').read() |
| for key, val in defs.iteritems(): |
| try: |
| data = data.replace('@@' + key + '@@', val) |
| except TypeError: |
| print repr(key), repr(val) |
| open(dst_file, 'w').write(data) |
| shutil.copystat(src_file, dst_file) |
| |
| |
| def copyZipIntoArchive(out_dir, files_root, zip_file): |
| """Expands the zip_file into the out_dir, preserving the directory structure. |
| |
| Args: |
| out_dir: Target directory where unzipped files are copied. |
| files_root: Path prefix which is stripped of zip_file before appending |
| it to the out_dir. |
| zip_file: Relative path (and filename) to the zip file. |
| """ |
| base_zip_name = os.path.basename(zip_file) |
| |
| # We don't use the 'zipfile' module here because it doesn't restore all the |
| # file permissions correctly. We use the 'unzip' command manually. |
| old_dir = os.getcwd(); |
| os.chdir(os.path.dirname(zip_file)) |
| subprocess.call(['unzip', '-qq', '-o', base_zip_name]) |
| os.chdir(old_dir) |
| |
| # Unzip into correct dir in out_dir. |
| out_zip_path = remapSrcFile(out_dir, files_root, zip_file) |
| out_zip_dir = os.path.dirname(out_zip_path) |
| |
| (src_dir, ignore1) = os.path.splitext(zip_file) |
| (base_dir_name, ignore2) = os.path.splitext(base_zip_name) |
| shutil.copytree(src_dir, os.path.join(out_zip_dir, base_dir_name)) |
| |
| |
| def buildHostArchive(temp_dir, zip_path, source_file_roots, source_files, |
| gen_files, gen_files_dst, defs): |
| """Builds a zip archive with the files needed to build the installer. |
| |
| Args: |
| temp_dir: Temporary dir used to build up the contents for the archive. |
| zip_path: Full path to the zip file to create. |
| source_file_roots: Array of path prefixes to strip off |files| when adding |
| to the archive. |
| source_files: The array of files to add to archive. The path structure is |
| preserved (except for the |files_root| prefix). |
| gen_files: Full path to binaries to add to archive. |
| gen_files_dst: Relative path of where to add binary files in archive. |
| This array needs to parallel |binaries_src|. |
| defs: Dictionary of variable definitions. |
| """ |
| cleanDir(temp_dir) |
| |
| for f in source_files: |
| dst_file = remapSrcFile(temp_dir, source_file_roots, f) |
| base_file = os.path.basename(f) |
| (base, ext) = os.path.splitext(f) |
| if ext == '.zip': |
| copyZipIntoArchive(temp_dir, source_file_roots, f) |
| elif ext in ['.packproj', '.pkgproj', '.plist', '.props', '.sh', '.json']: |
| copyFileWithDefs(f, dst_file, defs) |
| else: |
| shutil.copy2(f, dst_file) |
| |
| for bs, bd in zip(gen_files, gen_files_dst): |
| dst_file = os.path.join(temp_dir, bd) |
| if not os.path.exists(os.path.dirname(dst_file)): |
| os.makedirs(os.path.dirname(dst_file)) |
| if os.path.isdir(bs): |
| shutil.copytree(bs, dst_file) |
| else: |
| shutil.copy2(bs, dst_file) |
| |
| build_utils.ZipDir( |
| zip_path, temp_dir, |
| compress_fn=lambda _: zipfile.ZIP_DEFLATED, |
| zip_prefix_path=os.path.splitext(os.path.basename(zip_path))[0]) |
| |
| |
| def error(msg): |
| sys.stderr.write('ERROR: %s\n' % msg) |
| sys.exit(1) |
| |
| |
| def usage(): |
| """Display basic usage information.""" |
| print ('Usage: %s\n' |
| ' <temp-dir> <zip-path>\n' |
| ' --source-file-roots <list of roots to strip off source files...>\n' |
| ' --source-files <list of source files...>\n' |
| ' --generated-files <list of generated target files...>\n' |
| ' --generated-files-dst <dst for each generated file...>\n' |
| ' --defs <list of VARIABLE=value definitions...>' |
| ) % sys.argv[0] |
| |
| |
| def main(): |
| if len(sys.argv) < 2: |
| usage() |
| error('Too few arguments') |
| |
| temp_dir = sys.argv[1] |
| zip_path = sys.argv[2] |
| |
| arg_mode = '' |
| source_file_roots = [] |
| source_files = [] |
| generated_files = [] |
| generated_files_dst = [] |
| definitions = [] |
| for arg in sys.argv[3:]: |
| if arg == '--source-file-roots': |
| arg_mode = 'src-roots' |
| elif arg == '--source-files': |
| arg_mode = 'files' |
| elif arg == '--generated-files': |
| arg_mode = 'gen-src' |
| elif arg == '--generated-files-dst': |
| arg_mode = 'gen-dst' |
| elif arg == '--defs': |
| arg_mode = 'defs' |
| |
| elif arg_mode == 'src-roots': |
| source_file_roots.append(arg) |
| elif arg_mode == 'files': |
| source_files.append(arg) |
| elif arg_mode == 'gen-src': |
| generated_files.append(arg) |
| elif arg_mode == 'gen-dst': |
| generated_files_dst.append(arg) |
| elif arg_mode == 'defs': |
| definitions.append(arg) |
| else: |
| usage() |
| error('Expected --source-files') |
| |
| # Make sure at least one file was specified. |
| if len(source_files) == 0 and len(generated_files) == 0: |
| error('At least one input file must be specified.') |
| |
| # Sort roots to ensure the longest one is first. See comment in remapSrcFile |
| # for why this is necessary. |
| source_file_roots = map(os.path.normpath, source_file_roots) |
| source_file_roots.sort(key=len, reverse=True) |
| |
| # Verify that the 2 generated_files arrays have the same number of elements. |
| if len(generated_files) != len(generated_files_dst): |
| error('len(--generated-files) != len(--generated-files-dst)') |
| |
| defs = buildDefDictionary(definitions) |
| |
| result = buildHostArchive(temp_dir, zip_path, source_file_roots, |
| source_files, generated_files, generated_files_dst, |
| defs) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |