1#!/usr/bin/env python 2# 3# Copyright (C) 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16""" A tool to convert json file into pb with linker config format.""" 17 18import argparse 19import collections 20import json 21import os 22import sys 23 24import linker_config_pb2 #pylint: disable=import-error 25from google.protobuf.descriptor import FieldDescriptor 26from google.protobuf.json_format import ParseDict 27from google.protobuf.text_format import MessageToString 28 29 30def LoadJsonMessage(path): 31 """ 32 Loads a message from a .json file with `//` comments strippedfor convenience. 33 """ 34 json_content = '' 35 with open(path) as f: 36 for line in f: 37 if not line.lstrip().startswith('//'): 38 json_content += line 39 obj = json.loads(json_content, object_pairs_hook=collections.OrderedDict) 40 return ParseDict(obj, linker_config_pb2.LinkerConfig()) 41 42 43def Proto(args): 44 """ 45 Merges input json files (--source) into a protobuf message (--output). 46 Fails if the output file exists. Set --force or --append to deal with the existing 47 output file. 48 --force to overwrite the output file with the input (.json files). 49 --append to append the input to the output file. 50 """ 51 pb = linker_config_pb2.LinkerConfig() 52 if os.path.isfile(args.output): 53 if args.force: 54 pass 55 elif args.append: 56 with open(args.output, 'rb') as f: 57 pb.ParseFromString(f.read()) 58 else: 59 sys.stderr.write(f'Error: {args.output} exists. Use --force or --append.\n') 60 sys.exit(1) 61 62 if args.source: 63 for input in args.source.split(':'): 64 pb.MergeFrom(LoadJsonMessage(input)) 65 66 ValidateAndWriteAsPbFile(pb, args.output) 67 68 69def Print(args): 70 with open(args.source, 'rb') as f: 71 pb = linker_config_pb2.LinkerConfig() 72 pb.ParseFromString(f.read()) 73 print(MessageToString(pb)) 74 75 76def SystemProvide(args): 77 pb = linker_config_pb2.LinkerConfig() 78 with open(args.source, 'rb') as f: 79 pb.ParseFromString(f.read()) 80 libraries = args.value.split() 81 82 def IsInLibPath(lib_name): 83 lib_path = os.path.join(args.system, 'lib', lib_name) 84 lib64_path = os.path.join(args.system, 'lib64', lib_name) 85 return os.path.exists(lib_path) or os.path.islink( 86 lib_path) or os.path.exists(lib64_path) or os.path.islink( 87 lib64_path) 88 89 installed_libraries = [lib for lib in libraries if IsInLibPath(lib)] 90 for item in installed_libraries: 91 if item not in getattr(pb, 'provideLibs'): 92 getattr(pb, 'provideLibs').append(item) 93 94 ValidateAndWriteAsPbFile(pb, args.output) 95 96 97def Append(args): 98 pb = linker_config_pb2.LinkerConfig() 99 with open(args.source, 'rb') as f: 100 pb.ParseFromString(f.read()) 101 102 if getattr(type(pb), 103 args.key).DESCRIPTOR.label == FieldDescriptor.LABEL_REPEATED: 104 for value in args.value.split(): 105 getattr(pb, args.key).append(value) 106 else: 107 setattr(pb, args.key, args.value) 108 109 ValidateAndWriteAsPbFile(pb, args.output) 110 111 112 113def Merge(args): 114 pb = linker_config_pb2.LinkerConfig() 115 for other in args.input: 116 with open(other, 'rb') as f: 117 pb.MergeFromString(f.read()) 118 119 ValidateAndWriteAsPbFile(pb, args.output) 120 121 122def Validate(args): 123 if os.path.isdir(args.input): 124 config_file = os.path.join(args.input, 'etc/linker.config.pb') 125 if os.path.exists(config_file): 126 args.input = config_file 127 Validate(args) 128 # OK if there's no linker config file. 129 return 130 131 if not os.path.isfile(args.input): 132 sys.exit(f"{args.input} is not a file") 133 134 pb = linker_config_pb2.LinkerConfig() 135 with open(args.input, 'rb') as f: 136 pb.ParseFromString(f.read()) 137 138 if args.type == 'apex': 139 # Shouldn't use provideLibs/requireLibs in APEX linker.config.pb 140 if getattr(pb, 'provideLibs'): 141 sys.exit(f'{args.input}: provideLibs is set. Use provideSharedLibs in apex_manifest') 142 if getattr(pb, 'requireLibs'): 143 sys.exit(f'{args.input}: requireLibs is set. Use requireSharedLibs in apex_manifest') 144 elif args.type == 'system': 145 if getattr(pb, 'visible'): 146 sys.exit(f'{args.input}: do not use visible, which is for APEX') 147 if getattr(pb, 'permittedPaths'): 148 sys.exit(f'{args.input}: do not use permittedPaths, which is for APEX') 149 else: 150 sys.exit(f'Unknown type: {args.type}') 151 152 # Reject contributions field at build time while keeping the runtime behavior for GRF. 153 if getattr(pb, 'contributions'): 154 sys.exit(f"{args.input}: 'contributions' is set. " 155 "It's deprecated. Instead, make the APEX 'visible' and use android_dlopen_ext().") 156 157 158def ValidateAndWriteAsPbFile(pb, output_path): 159 ValidateConfiguration(pb) 160 with open(output_path, 'wb') as f: 161 f.write(pb.SerializeToString()) 162 163 164def ValidateConfiguration(pb): 165 """ 166 Validate if the configuration is valid to be used as linker configuration 167 """ 168 169 # Validate if provideLibs and requireLibs have common module 170 provideLibs = set(getattr(pb, 'provideLibs')) 171 requireLibs = set(getattr(pb, 'requireLibs')) 172 173 intersectLibs = provideLibs.intersection(requireLibs) 174 175 if intersectLibs: 176 for lib in intersectLibs: 177 print(f'{lib} exists both in requireLibs and provideLibs', file=sys.stderr) 178 sys.exit(1) 179 180 181def GetArgParser(): 182 parser = argparse.ArgumentParser() 183 subparsers = parser.add_subparsers() 184 185 parser_proto = subparsers.add_parser( 186 'proto', 187 help='Convert the input JSON configuration file into protobuf.') 188 parser_proto.add_argument( 189 '-s', 190 '--source', 191 nargs='?', 192 type=str, 193 help='Colon-separated list of linker configuration files in JSON.') 194 parser_proto.add_argument( 195 '-o', 196 '--output', 197 required=True, 198 type=str, 199 help='Target path to create protobuf file.') 200 option_for_existing_output = parser_proto.add_mutually_exclusive_group() 201 option_for_existing_output.add_argument( 202 '-f', 203 '--force', 204 action='store_true', 205 help='Overwrite if the output file exists.') 206 option_for_existing_output.add_argument( 207 '-a', 208 '--append', 209 action='store_true', 210 help='Append the input to the output file if the output file exists.') 211 parser_proto.set_defaults(func=Proto) 212 213 print_proto = subparsers.add_parser( 214 'print', help='Print configuration in human-readable text format.') 215 print_proto.add_argument( 216 '-s', 217 '--source', 218 required=True, 219 type=str, 220 help='Source linker configuration file in protobuf.') 221 print_proto.set_defaults(func=Print) 222 223 system_provide_libs = subparsers.add_parser( 224 'systemprovide', 225 help='Append system provide libraries into the configuration.') 226 system_provide_libs.add_argument( 227 '-s', 228 '--source', 229 required=True, 230 type=str, 231 help='Source linker configuration file in protobuf.') 232 system_provide_libs.add_argument( 233 '-o', 234 '--output', 235 required=True, 236 type=str, 237 help='Target linker configuration file to write in protobuf.') 238 system_provide_libs.add_argument( 239 '--value', 240 required=True, 241 type=str, 242 help='Values of the libraries to append. If there are more than one ' 243 'it should be separated by empty space' 244 ) 245 system_provide_libs.add_argument( 246 '--system', required=True, type=str, help='Path of the system image.') 247 system_provide_libs.set_defaults(func=SystemProvide) 248 249 append = subparsers.add_parser( 250 'append', help='Append value(s) to given key.') 251 append.add_argument( 252 '-s', 253 '--source', 254 required=True, 255 type=str, 256 help='Source linker configuration file in protobuf.') 257 append.add_argument( 258 '-o', 259 '--output', 260 required=True, 261 type=str, 262 help='Target linker configuration file to write in protobuf.') 263 append.add_argument('--key', required=True, type=str, help='.') 264 append.add_argument( 265 '--value', 266 required=True, 267 type=str, 268 help='Values of the libraries to append. If there are more than one' 269 'it should be separated by empty space' 270 ) 271 append.set_defaults(func=Append) 272 273 append = subparsers.add_parser('merge', help='Merge configurations') 274 append.add_argument( 275 '-o', 276 '--out', 277 required=True, 278 type=str, 279 help='Output linker configuration file to write in protobuf.') 280 append.add_argument( 281 '-i', 282 '--input', 283 nargs='+', 284 type=str, 285 help='Linker configuration files to merge.') 286 append.set_defaults(func=Merge) 287 288 validate = subparsers.add_parser('validate', help='Validate configuration') 289 validate.add_argument( 290 '--type', 291 required=True, 292 choices=['apex', 'system'], 293 help='Type of linker configuration') 294 validate.add_argument( 295 'input', 296 help='Input can be a directory which has etc/linker.config.pb or a path' 297 ' to the linker config file') 298 validate.set_defaults(func=Validate) 299 300 return parser 301 302 303def main(): 304 parser = GetArgParser() 305 args = parser.parse_args() 306 if 'func' in args: 307 args.func(args) 308 else: 309 parser.print_help() 310 311 312if __name__ == '__main__': 313 main() 314