1*33f37583SAndroid Build Coastguard Worker#!/usr/bin/env python 2*33f37583SAndroid Build Coastguard Worker# 3*33f37583SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project 4*33f37583SAndroid Build Coastguard Worker# 5*33f37583SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*33f37583SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*33f37583SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*33f37583SAndroid Build Coastguard Worker# 9*33f37583SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*33f37583SAndroid Build Coastguard Worker# 11*33f37583SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*33f37583SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*33f37583SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*33f37583SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*33f37583SAndroid Build Coastguard Worker# limitations under the License. 16*33f37583SAndroid Build Coastguard Worker 17*33f37583SAndroid Build Coastguard Worker"""apex_compression_tool is a tool that can compress/decompress APEX. 18*33f37583SAndroid Build Coastguard Worker 19*33f37583SAndroid Build Coastguard WorkerExample: 20*33f37583SAndroid Build Coastguard Worker apex_compression_tool compress --input /apex/to/compress --output output/path 21*33f37583SAndroid Build Coastguard Worker apex_compression_tool decompress --input /apex/to/decompress --output dir/ 22*33f37583SAndroid Build Coastguard Worker apex_compression_tool verify-compressed --input /file/to/check 23*33f37583SAndroid Build Coastguard Worker""" 24*33f37583SAndroid Build Coastguard Workerfrom __future__ import print_function 25*33f37583SAndroid Build Coastguard Worker 26*33f37583SAndroid Build Coastguard Workerimport argparse 27*33f37583SAndroid Build Coastguard Workerimport os 28*33f37583SAndroid Build Coastguard Workerimport shutil 29*33f37583SAndroid Build Coastguard Workerimport subprocess 30*33f37583SAndroid Build Coastguard Workerimport sys 31*33f37583SAndroid Build Coastguard Workerimport tempfile 32*33f37583SAndroid Build Coastguard Workerfrom zipfile import ZipFile 33*33f37583SAndroid Build Coastguard Worker 34*33f37583SAndroid Build Coastguard Workerimport apex_manifest_pb2 35*33f37583SAndroid Build Coastguard Worker 36*33f37583SAndroid Build Coastguard Workertool_path_list = None 37*33f37583SAndroid Build Coastguard Worker 38*33f37583SAndroid Build Coastguard Worker 39*33f37583SAndroid Build Coastguard Workerdef FindBinaryPath(binary): 40*33f37583SAndroid Build Coastguard Worker for path in tool_path_list: 41*33f37583SAndroid Build Coastguard Worker binary_path = os.path.join(path, binary) 42*33f37583SAndroid Build Coastguard Worker if os.path.exists(binary_path): 43*33f37583SAndroid Build Coastguard Worker return binary_path 44*33f37583SAndroid Build Coastguard Worker raise Exception('Failed to find binary ' + binary + ' in path ' + 45*33f37583SAndroid Build Coastguard Worker ':'.join(tool_path_list)) 46*33f37583SAndroid Build Coastguard Worker 47*33f37583SAndroid Build Coastguard Worker 48*33f37583SAndroid Build Coastguard Workerdef RunCommand(cmd, verbose=False, env=None, expected_return_values=None): 49*33f37583SAndroid Build Coastguard Worker expected_return_values = expected_return_values or {0} 50*33f37583SAndroid Build Coastguard Worker env = env or {} 51*33f37583SAndroid Build Coastguard Worker env.update(os.environ.copy()) 52*33f37583SAndroid Build Coastguard Worker 53*33f37583SAndroid Build Coastguard Worker cmd[0] = FindBinaryPath(cmd[0]) 54*33f37583SAndroid Build Coastguard Worker 55*33f37583SAndroid Build Coastguard Worker if verbose: 56*33f37583SAndroid Build Coastguard Worker print('Running: ' + ' '.join(cmd)) 57*33f37583SAndroid Build Coastguard Worker p = subprocess.Popen( 58*33f37583SAndroid Build Coastguard Worker cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) 59*33f37583SAndroid Build Coastguard Worker output, _ = p.communicate() 60*33f37583SAndroid Build Coastguard Worker 61*33f37583SAndroid Build Coastguard Worker if verbose or p.returncode not in expected_return_values: 62*33f37583SAndroid Build Coastguard Worker print(output.rstrip()) 63*33f37583SAndroid Build Coastguard Worker 64*33f37583SAndroid Build Coastguard Worker assert p.returncode in expected_return_values, 'Failed to execute: ' \ 65*33f37583SAndroid Build Coastguard Worker + ' '.join(cmd) 66*33f37583SAndroid Build Coastguard Worker 67*33f37583SAndroid Build Coastguard Worker return output, p.returncode 68*33f37583SAndroid Build Coastguard Worker 69*33f37583SAndroid Build Coastguard Worker 70*33f37583SAndroid Build Coastguard Workerdef RunCompress(args, work_dir): 71*33f37583SAndroid Build Coastguard Worker """RunCompress takes an uncompressed APEX and compresses into compressed APEX 72*33f37583SAndroid Build Coastguard Worker 73*33f37583SAndroid Build Coastguard Worker Compressed apex will contain the following items: 74*33f37583SAndroid Build Coastguard Worker - original_apex: The original uncompressed APEX 75*33f37583SAndroid Build Coastguard Worker - Duplicates of various meta files inside the input APEX, e.g 76*33f37583SAndroid Build Coastguard Worker AndroidManifest.xml, public_key 77*33f37583SAndroid Build Coastguard Worker 78*33f37583SAndroid Build Coastguard Worker Args: 79*33f37583SAndroid Build Coastguard Worker args.input: file path to uncompressed APEX 80*33f37583SAndroid Build Coastguard Worker args.output: file path to where compressed APEX will be placed 81*33f37583SAndroid Build Coastguard Worker work_dir: file path to a temporary folder 82*33f37583SAndroid Build Coastguard Worker Returns: 83*33f37583SAndroid Build Coastguard Worker True if compression was executed successfully, otherwise False 84*33f37583SAndroid Build Coastguard Worker """ 85*33f37583SAndroid Build Coastguard Worker global tool_path_list 86*33f37583SAndroid Build Coastguard Worker tool_path_list = args.apex_compression_tool_path 87*33f37583SAndroid Build Coastguard Worker 88*33f37583SAndroid Build Coastguard Worker cmd = ['soong_zip'] 89*33f37583SAndroid Build Coastguard Worker cmd.extend(['-o', args.output]) 90*33f37583SAndroid Build Coastguard Worker 91*33f37583SAndroid Build Coastguard Worker # We want to put the input apex inside the compressed APEX with name 92*33f37583SAndroid Build Coastguard Worker # "original_apex". Originally this was done by creating a hard link 93*33f37583SAndroid Build Coastguard Worker # in order to put the renamed file inside the zip, but it causes some issue 94*33f37583SAndroid Build Coastguard Worker # when running this tool with Bazel in a sandbox which restricts the function 95*33f37583SAndroid Build Coastguard Worker # of creating cross-device links. So instead of creating hard links, we make a 96*33f37583SAndroid Build Coastguard Worker # copy of the original_apex here. 97*33f37583SAndroid Build Coastguard Worker original_apex = os.path.join(work_dir, 'original_apex') 98*33f37583SAndroid Build Coastguard Worker shutil.copy2(args.input, original_apex) 99*33f37583SAndroid Build Coastguard Worker cmd.extend(['-C', work_dir]) 100*33f37583SAndroid Build Coastguard Worker cmd.extend(['-f', original_apex]) 101*33f37583SAndroid Build Coastguard Worker 102*33f37583SAndroid Build Coastguard Worker # We also need to extract some files from inside of original_apex and zip 103*33f37583SAndroid Build Coastguard Worker # together with compressed apex 104*33f37583SAndroid Build Coastguard Worker with ZipFile(original_apex, 'r') as zip_obj: 105*33f37583SAndroid Build Coastguard Worker extract_dir = os.path.join(work_dir, 'extract') 106*33f37583SAndroid Build Coastguard Worker for meta_file in ['apex_manifest.json', 'apex_manifest.pb', 107*33f37583SAndroid Build Coastguard Worker 'apex_pubkey', 'apex_build_info.pb', 108*33f37583SAndroid Build Coastguard Worker 'AndroidManifest.xml']: 109*33f37583SAndroid Build Coastguard Worker if meta_file in zip_obj.namelist(): 110*33f37583SAndroid Build Coastguard Worker zip_obj.extract(meta_file, path=extract_dir) 111*33f37583SAndroid Build Coastguard Worker file_path = os.path.join(extract_dir, meta_file) 112*33f37583SAndroid Build Coastguard Worker cmd.extend(['-C', extract_dir]) 113*33f37583SAndroid Build Coastguard Worker cmd.extend(['-f', file_path]) 114*33f37583SAndroid Build Coastguard Worker cmd.extend(['-s', meta_file]) 115*33f37583SAndroid Build Coastguard Worker # Extract the image for retrieving root digest 116*33f37583SAndroid Build Coastguard Worker zip_obj.extract('apex_payload.img', path= work_dir) 117*33f37583SAndroid Build Coastguard Worker image_path = os.path.join(work_dir, 'apex_payload.img') 118*33f37583SAndroid Build Coastguard Worker 119*33f37583SAndroid Build Coastguard Worker # Set digest of original_apex to apex_manifest.pb 120*33f37583SAndroid Build Coastguard Worker apex_manifest_path = os.path.join(extract_dir, 'apex_manifest.pb') 121*33f37583SAndroid Build Coastguard Worker assert AddOriginalApexDigestToManifest(apex_manifest_path, image_path, args.verbose) 122*33f37583SAndroid Build Coastguard Worker 123*33f37583SAndroid Build Coastguard Worker # Don't forget to compress 124*33f37583SAndroid Build Coastguard Worker cmd.extend(['-L', '9']) 125*33f37583SAndroid Build Coastguard Worker 126*33f37583SAndroid Build Coastguard Worker RunCommand(cmd, verbose=args.verbose) 127*33f37583SAndroid Build Coastguard Worker 128*33f37583SAndroid Build Coastguard Worker return True 129*33f37583SAndroid Build Coastguard Worker 130*33f37583SAndroid Build Coastguard Worker 131*33f37583SAndroid Build Coastguard Workerdef AddOriginalApexDigestToManifest(capex_manifest_path, apex_image_path, verbose=False): 132*33f37583SAndroid Build Coastguard Worker # Retrieve the root digest of the image 133*33f37583SAndroid Build Coastguard Worker avbtool_cmd = [ 134*33f37583SAndroid Build Coastguard Worker 'avbtool', 135*33f37583SAndroid Build Coastguard Worker 'print_partition_digests', '--image', 136*33f37583SAndroid Build Coastguard Worker apex_image_path] 137*33f37583SAndroid Build Coastguard Worker # avbtool_cmd output has format "<name>: <value>" 138*33f37583SAndroid Build Coastguard Worker root_digest = RunCommand(avbtool_cmd, verbose=verbose)[0].decode().split(': ')[1].strip() 139*33f37583SAndroid Build Coastguard Worker # Update the manifest proto file 140*33f37583SAndroid Build Coastguard Worker with open(capex_manifest_path, 'rb') as f: 141*33f37583SAndroid Build Coastguard Worker pb = apex_manifest_pb2.ApexManifest() 142*33f37583SAndroid Build Coastguard Worker pb.ParseFromString(f.read()) 143*33f37583SAndroid Build Coastguard Worker 144*33f37583SAndroid Build Coastguard Worker assert not pb.supportsRebootlessUpdate, "Rebootless updates not supported for compressed APEXs" 145*33f37583SAndroid Build Coastguard Worker # Populate CompressedApexMetadata 146*33f37583SAndroid Build Coastguard Worker capex_metadata = apex_manifest_pb2.ApexManifest().CompressedApexMetadata() 147*33f37583SAndroid Build Coastguard Worker capex_metadata.originalApexDigest = root_digest 148*33f37583SAndroid Build Coastguard Worker # Set updated value to protobuf 149*33f37583SAndroid Build Coastguard Worker pb.capexMetadata.CopyFrom(capex_metadata) 150*33f37583SAndroid Build Coastguard Worker with open(capex_manifest_path, 'wb') as f: 151*33f37583SAndroid Build Coastguard Worker f.write(pb.SerializeToString()) 152*33f37583SAndroid Build Coastguard Worker return True 153*33f37583SAndroid Build Coastguard Worker 154*33f37583SAndroid Build Coastguard Worker 155*33f37583SAndroid Build Coastguard Workerdef ParseArgs(argv): 156*33f37583SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 157*33f37583SAndroid Build Coastguard Worker subparsers = parser.add_subparsers(required=True, dest='cmd') 158*33f37583SAndroid Build Coastguard Worker 159*33f37583SAndroid Build Coastguard Worker # Handle sub-command "compress" 160*33f37583SAndroid Build Coastguard Worker parser_compress = subparsers.add_parser('compress', 161*33f37583SAndroid Build Coastguard Worker help='compresses an APEX') 162*33f37583SAndroid Build Coastguard Worker parser_compress.add_argument('-v', '--verbose', action='store_true', 163*33f37583SAndroid Build Coastguard Worker help='verbose execution') 164*33f37583SAndroid Build Coastguard Worker parser_compress.add_argument('--input', type=str, required=True, 165*33f37583SAndroid Build Coastguard Worker help='path to input APEX file that will be ' 166*33f37583SAndroid Build Coastguard Worker 'compressed') 167*33f37583SAndroid Build Coastguard Worker parser_compress.add_argument('--output', type=str, required=True, 168*33f37583SAndroid Build Coastguard Worker help='output path to compressed APEX file') 169*33f37583SAndroid Build Coastguard Worker apex_compression_tool_path_in_environ = \ 170*33f37583SAndroid Build Coastguard Worker 'APEX_COMPRESSION_TOOL_PATH' in os.environ 171*33f37583SAndroid Build Coastguard Worker parser_compress.add_argument( 172*33f37583SAndroid Build Coastguard Worker '--apex_compression_tool_path', 173*33f37583SAndroid Build Coastguard Worker required=not apex_compression_tool_path_in_environ, 174*33f37583SAndroid Build Coastguard Worker default=os.environ['APEX_COMPRESSION_TOOL_PATH'].split(':') 175*33f37583SAndroid Build Coastguard Worker if apex_compression_tool_path_in_environ else None, 176*33f37583SAndroid Build Coastguard Worker type=lambda s: s.split(':'), 177*33f37583SAndroid Build Coastguard Worker help="""A list of directories containing all the tools used by 178*33f37583SAndroid Build Coastguard Worker apex_compression_tool (e.g. soong_zip etc.) separated by ':'. Can also 179*33f37583SAndroid Build Coastguard Worker be set using the APEX_COMPRESSION_TOOL_PATH environment variable""") 180*33f37583SAndroid Build Coastguard Worker parser_compress.set_defaults(func=RunCompress) 181*33f37583SAndroid Build Coastguard Worker 182*33f37583SAndroid Build Coastguard Worker return parser.parse_args(argv) 183*33f37583SAndroid Build Coastguard Worker 184*33f37583SAndroid Build Coastguard Worker 185*33f37583SAndroid Build Coastguard Workerclass TempDirectory(object): 186*33f37583SAndroid Build Coastguard Worker 187*33f37583SAndroid Build Coastguard Worker def __enter__(self): 188*33f37583SAndroid Build Coastguard Worker self.name = tempfile.mkdtemp() 189*33f37583SAndroid Build Coastguard Worker return self.name 190*33f37583SAndroid Build Coastguard Worker 191*33f37583SAndroid Build Coastguard Worker def __exit__(self, *unused): 192*33f37583SAndroid Build Coastguard Worker shutil.rmtree(self.name) 193*33f37583SAndroid Build Coastguard Worker 194*33f37583SAndroid Build Coastguard Worker 195*33f37583SAndroid Build Coastguard Workerdef main(argv): 196*33f37583SAndroid Build Coastguard Worker args = ParseArgs(argv) 197*33f37583SAndroid Build Coastguard Worker 198*33f37583SAndroid Build Coastguard Worker with TempDirectory() as work_dir: 199*33f37583SAndroid Build Coastguard Worker success = args.func(args, work_dir) 200*33f37583SAndroid Build Coastguard Worker 201*33f37583SAndroid Build Coastguard Worker if not success: 202*33f37583SAndroid Build Coastguard Worker sys.exit(1) 203*33f37583SAndroid Build Coastguard Worker 204*33f37583SAndroid Build Coastguard Worker 205*33f37583SAndroid Build Coastguard Workerif __name__ == '__main__': 206*33f37583SAndroid Build Coastguard Worker main(sys.argv[1:]) 207