1#!/usr/bin/env python 2# 3# Copyright 2016 - 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# 17 18# Parses the output of parse_ltp_{make,make_install} and generates a 19# corresponding Android.mk. 20# 21# This process is split into two steps so this second step can later be replaced 22# with an Android.bp generator. 23 24import argparse 25import fileinput 26import json 27import os.path 28import re 29 30import make_parser 31import make_install_parser 32 33MAKE_DRY_RUN_FILE_NAME = 'make_dry_run.dump' 34MAKE_INSTALL_DRY_RUN_FILE_NAME = 'make_install_dry_run.dump' 35DISABLED_TESTS_FILE_NAME = 'disabled_tests.txt' 36DISABLED_LIBS_FILE_NAME = 'disabled_libs.txt' 37DISABLED_CFLAGS_FILE_NAME = 'disabled_cflags.txt' 38 39 40class BuildGenerator(object): 41 '''A class to parse make output and convert the result to Android.ltp.mk modules. 42 43 Attributes: 44 _bp_result: directory of list of strings for blueprint file keyed by target name 45 _mk_result: directory of list of strings for makefile keyed by target name 46 _custom_cflags: dict of string (module name) to lists of strings (cflags 47 to add for said module) 48 _unused_custom_cflags: set of strings; tracks the modules with custom 49 cflags that we haven't yet seen 50 _packages: list of strings of packages for package list file 51 ''' 52 53 def __init__(self, custom_cflags): 54 self._bp_result = {} 55 self._mk_result = {} 56 self._custom_cflags = custom_cflags 57 self._unused_custom_cflags = set(custom_cflags) 58 self._packages = [] 59 60 def UniqueKeepOrder(self, sequence): 61 '''Get a copy of list where items are unique and order is preserved. 62 63 Args: 64 sequence: a sequence, can be a list, tuple, or other iterable 65 66 Returns: 67 a list where items copied from input sequence are unique 68 and order is preserved. 69 ''' 70 seen = set() 71 return [x for x in sequence if not (x in seen or seen.add(x))] 72 73 def ReadCommentedText(self, file_path): 74 '''Read pound commented text file into a list of lines. 75 76 Comments or empty lines will be excluded 77 78 Args: 79 file_path: string 80 ''' 81 ret = set() 82 with open(file_path, 'r') as f: 83 lines = [line.strip() for line in f.readlines()] 84 ret = set([s for s in lines if s and not s.startswith('#')]) 85 86 return ret 87 88 def ArTargetToLibraryName(self, ar_target): 89 '''Convert ar target to library name. 90 91 Args: 92 ar_target: string 93 ''' 94 return os.path.basename(ar_target)[len('lib'):-len('.a')] 95 96 def BuildExecutable(self, cc_target, local_src_files, local_cflags, 97 local_c_includes, local_libraries, ltp_libs, 98 ltp_libs_used, ltp_names_used): 99 '''Build a test module. 100 101 Args: 102 cc_target: string 103 local_src_files: list of string 104 local_cflags: list of string 105 local_c_includes: list of string 106 local_libraries: list of string 107 ltp_libs: list of string 108 ltp_libs_used: set of string 109 ltp_names_used: set of string, set of already used cc_target basenames 110 ''' 111 base_name = os.path.basename(cc_target) 112 if base_name in ltp_names_used: 113 print 'ERROR: base name {} of cc_target {} already used. Skipping...'.format( 114 base_name, cc_target) 115 return 116 ltp_names_used.add(base_name) 117 118 if cc_target in self._custom_cflags: 119 local_cflags.extend(self._custom_cflags[cc_target]) 120 self._unused_custom_cflags.remove(cc_target) 121 122 # ltp_defaults already adds the include directory 123 local_c_includes = [i for i in local_c_includes if i != 'include'] 124 target_name = 'ltp_%s' % base_name 125 target_bp = [] 126 127 self._packages.append(target_name) 128 129 target_bp.append('cc_test {') 130 target_bp.append(' name: "%s",' % target_name) 131 target_bp.append(' stem: "%s",' % base_name) 132 target_bp.append(' defaults: ["ltp_test_defaults"],') 133 134 if len(local_src_files) == 1: 135 target_bp.append(' srcs: ["%s"],' % list(local_src_files)[0]) 136 else: 137 target_bp.append(' srcs: [') 138 for src in local_src_files: 139 target_bp.append(' "%s",' % src) 140 target_bp.append(' ],') 141 142 if len(local_cflags) == 1: 143 target_bp.append(' cflags: ["%s"],' % list(local_cflags)[0]) 144 elif len(local_cflags) > 1: 145 target_bp.append(' cflags: [') 146 for cflag in local_cflags: 147 target_bp.append(' "%s",' % cflag) 148 target_bp.append(' ],') 149 150 if len(local_c_includes) == 1: 151 target_bp.append(' local_include_dirs: ["%s"],' % list(local_c_includes)[0]) 152 elif len(local_c_includes) > 1: 153 target_bp.append(' local_include_dirs: [') 154 for d in local_c_includes: 155 target_bp.append(' "%s",' % d) 156 target_bp.append(' ],') 157 158 bionic_builtin_libs = set(['m', 'rt', 'pthread', 'util']) 159 filtered_libs = set(local_libraries).difference(bionic_builtin_libs) 160 161 static_libraries = set(i for i in local_libraries if i in ltp_libs) 162 if len(static_libraries) == 1: 163 target_bp.append(' static_libs: ["libltp_%s"],' % list(static_libraries)[0]) 164 elif len(static_libraries) > 1: 165 target_bp.append(' static_libs: [') 166 for lib in static_libraries: 167 target_bp.append(' "libltp_%s",' % lib) 168 target_bp.append(' ],') 169 170 for lib in static_libraries: 171 ltp_libs_used.add(lib) 172 173 shared_libraries = set(i for i in filtered_libs if i not in ltp_libs) 174 if len(shared_libraries) == 1: 175 target_bp.append(' shared_libs: ["lib%s"],' % list(shared_libraries)[0]) 176 elif len(shared_libraries) > 1: 177 target_bp.append(' shared_libs: [') 178 for lib in shared_libraries: 179 target_bp.append(' "lib%s",' % lib) 180 target_bp.append(' ],') 181 182 target_bp.append('}') 183 target_bp.append('') 184 self._bp_result[target_name] = target_bp 185 186 def BuildStaticLibrary(self, ar_target, local_src_files, local_cflags, 187 local_c_includes): 188 '''Build a library module. 189 190 Args: 191 ar_target: string 192 local_src_files: list of string 193 local_cflags: list of string 194 local_c_includes: list of string 195 ''' 196 target_name = 'libltp_%s' % self.ArTargetToLibraryName(ar_target) 197 target_bp = [] 198 target_bp.append('cc_library_static {') 199 target_bp.append(' name: "%s",' % target_name) 200 target_bp.append(' defaults: ["ltp_defaults"],') 201 202 if len(local_c_includes): 203 target_bp.append(' local_include_dirs: [') 204 for d in local_c_includes: 205 target_bp.append(' "%s",' % d) 206 target_bp.append(' ],') 207 208 if len(local_cflags): 209 target_bp.append(' cflags: [') 210 for cflag in local_cflags: 211 target_bp.append(' "%s",' % cflag) 212 target_bp.append(' ],') 213 214 target_bp.append(' srcs: [') 215 for src in local_src_files: 216 target_bp.append(' "%s",' % src) 217 target_bp.append(' ],') 218 219 target_bp.append('}') 220 target_bp.append('') 221 self._bp_result[target_name] = target_bp 222 223 def BuildPrebuilt(self, install_target, local_src_file): 224 '''Build a prebuild module. 225 226 Args: 227 install_target: string 228 local_src_file: string 229 ''' 230 base_name = os.path.basename(install_target) 231 mk_result = [] 232 mk_result.append('module_prebuilt := %s' % install_target) 233 mk_result.append('module_src_files := %s' % local_src_file) 234 module_dir = os.path.dirname(install_target) 235 module_stem = os.path.basename(install_target) 236 module = 'ltp_%s' % install_target.replace('/', '_') 237 self._packages.append(module) 238 239 mk_result.append('include $(ltp_build_prebuilt)') 240 mk_result.append('') 241 self._mk_result[base_name] = mk_result 242 243 def HandleParsedRule(self, line, rules): 244 '''Prepare parse rules. 245 246 Args: 247 line: string 248 rules: dictionary {string, dictionary} 249 ''' 250 groups = re.match(r'(.*)\[\'(.*)\'\] = \[(.*)\]', line).groups() 251 rule = groups[0] 252 rule_key = groups[1] 253 if groups[2] == '': 254 rule_value = [] 255 else: 256 rule_value = list(i.strip()[1:-1] for i in groups[2].split(',')) 257 258 rule_value = self.UniqueKeepOrder(rule_value) 259 rules.setdefault(rule, {})[rule_key] = rule_value 260 261 def ParseInput(self, input_list, ltp_root): 262 '''Parse a interpreted make output and produce Android.ltp.mk module. 263 264 Args: 265 input_list: list of string 266 ''' 267 disabled_tests = self.ReadCommentedText(DISABLED_TESTS_FILE_NAME) 268 disabled_libs = self.ReadCommentedText(DISABLED_LIBS_FILE_NAME) 269 disabled_cflags = self.ReadCommentedText(DISABLED_CFLAGS_FILE_NAME) 270 271 rules = {} 272 for line in input_list: 273 self.HandleParsedRule(line.strip(), rules) 274 275 # .a target -> .o files 276 ar = rules.get('ar', {}) 277 # executable target -> .o files 278 cc_link = rules.get('cc_link', {}) 279 # .o target -> .c file 280 cc_compile = rules.get('cc_compile', {}) 281 # executable target -> .c files 282 cc_compilelink = rules.get('cc_compilelink', {}) 283 # Target name -> CFLAGS passed to gcc 284 cc_flags = rules.get('cc_flags', {}) 285 # Target name -> -I paths passed to gcc 286 cc_includes = rules.get('cc_includes', {}) 287 # Target name -> -l paths passed to gcc 288 cc_libraries = rules.get('cc_libraries', {}) 289 # target -> prebuilt source 290 install = rules.get('install', {}) 291 292 # All libraries used by any LTP test (built or not) 293 ltp_libs = set(self.ArTargetToLibraryName(i) for i in ar.keys()) 294 # All libraries used by the LTP tests we actually build 295 ltp_libs_used = set() 296 ltp_names_used = set() 297 298 print( 299 "Disabled lib tests: Test cases listed here are" 300 "suggested to be disabled since they require a disabled library. " 301 "Please copy and paste them into disabled_tests.txt\n") 302 for i in cc_libraries: 303 if len(set(cc_libraries[i]).intersection(disabled_libs)) > 0: 304 if not os.path.basename(i) in disabled_tests: 305 print os.path.basename(i) 306 307 print("Disabled_cflag tests: Test cases listed here are" 308 "suggested to be disabled since they require a disabled cflag. " 309 "Please copy and paste them into disabled_tests.txt\n") 310 for i in cc_flags: 311 if len(set(cc_flags[i]).intersection(disabled_cflags)) > 0: 312 module_name = os.path.basename(i) 313 idx = module_name.find('_') 314 if idx > 0: 315 module_name = module_name[:idx] 316 print module_name 317 318 # Remove include directories that don't exist. They're an error in 319 # Soong. 320 for target in cc_includes: 321 cc_includes[target] = [i for i in cc_includes[target] if os.path.isdir(os.path.join(ltp_root, i))] 322 323 for target in cc_compilelink: 324 module_name = os.path.basename(target) 325 if module_name in disabled_tests: 326 continue 327 local_src_files = [] 328 src_files = cc_compilelink[target] 329 for i in src_files: 330 # some targets may have a mix of .c and .o files in srcs 331 # find the .c files to build those .o from cc_compile targets 332 if i.endswith('.o'): 333 local_src_files.extend(cc_compile[i]) 334 else: 335 local_src_files.append(i) 336 local_cflags = cc_flags[target] 337 local_c_includes = cc_includes[target] 338 local_libraries = cc_libraries[target] 339 if len(set(local_libraries).intersection(disabled_libs)) > 0: 340 continue 341 if len(set(local_cflags).intersection(disabled_cflags)) > 0: 342 continue 343 self.BuildExecutable(target, local_src_files, local_cflags, 344 local_c_includes, local_libraries, ltp_libs, 345 ltp_libs_used, ltp_names_used) 346 347 for target in cc_link: 348 if os.path.basename(target) in disabled_tests: 349 continue 350 local_src_files = set() 351 local_cflags = set() 352 local_c_includes = set() 353 local_libraries = cc_libraries[target] 354 # Accumulate flags for all .c files needed to build the .o files. 355 # (Android.mk requires a consistent set of flags across a given target. 356 # Thankfully using the superset of all flags in the target works fine 357 # with LTP tests.) 358 for obj in cc_link[target]: 359 for i in cc_compile[obj]: 360 local_src_files.add(i) 361 for i in cc_flags[obj]: 362 local_cflags.add(i) 363 for i in cc_includes[obj]: 364 local_c_includes.add(i) 365 if len(set(local_libraries).intersection(disabled_libs)) > 0: 366 continue 367 if len(set(local_cflags).intersection(disabled_cflags)) > 0: 368 continue 369 370 self.BuildExecutable(target, local_src_files, local_cflags, 371 local_c_includes, local_libraries, ltp_libs, 372 ltp_libs_used, ltp_names_used) 373 374 for target in ar: 375 # Disabled ltp library is already excluded 376 # since it won't be in ltp_libs_used 377 if not self.ArTargetToLibraryName(target) in ltp_libs_used: 378 continue 379 380 local_src_files = set() 381 local_cflags = set() 382 local_c_includes = set() 383 384 # TODO: disabled cflags 385 386 for obj in ar[target]: 387 for i in cc_compile[obj]: 388 local_src_files.add(i) 389 for i in cc_flags[obj]: 390 local_cflags.add(i) 391 for i in cc_includes[obj]: 392 local_c_includes.add(i) 393 394 if len(set(local_cflags).intersection(disabled_cflags)) > 0: 395 continue 396 397 local_src_files = sorted(local_src_files) 398 local_cflags = sorted(local_cflags) 399 local_c_includes = sorted(local_c_includes) 400 401 self.BuildStaticLibrary(target, local_src_files, local_cflags, 402 local_c_includes) 403 404 for target in install: 405 # Check if the absolute path to the prebuilt (relative to LTP_ROOT) 406 # is disabled. This is helpful in case there are duplicates with basename 407 # of the prebuilt. 408 # e.g. 409 # ./ testcases / kernel / fs / fs_bind / move / test01 410 # ./ testcases / kernel / fs / fs_bind / cloneNS / test01 411 # ./ testcases / kernel / fs / fs_bind / regression / test01 412 # ./ testcases / kernel / fs / fs_bind / rbind / test01 413 # ./ testcases / kernel / fs / fs_bind / bind / test01 414 if target in disabled_tests: 415 continue 416 if os.path.basename(target) in disabled_tests: 417 continue 418 local_src_files = install[target] 419 assert len(local_src_files) == 1 420 421 self.BuildPrebuilt(target, local_src_files[0]) 422 423 def WriteAndroidBp(self, output_path): 424 '''Write parse result to blueprint file. 425 426 Args: 427 output_path: string 428 ''' 429 with open(output_path, 'a') as f: 430 for k in sorted(self._bp_result.iterkeys()): 431 f.write('\n'.join(self._bp_result[k])) 432 f.write('\n') 433 self._bp_result = {} 434 435 def WriteAndroidMk(self, output_path): 436 '''Write parse result to make file. 437 438 Args: 439 output_path: string 440 ''' 441 with open(output_path, 'a') as f: 442 for k in sorted(self._mk_result.iterkeys()): 443 f.write('\n'.join(self._mk_result[k])) 444 f.write('\n') 445 self._mk_result = {} 446 447 def WritePackageList(self, output_path): 448 '''Write parse result to package list file. 449 450 Args: 451 output_path: string 452 ''' 453 with open(output_path, 'a') as f: 454 f.write('ltp_packages := \\\n ') 455 f.write(' \\\n '.join(sorted(self._packages))) 456 self._packages = [] 457 458 def ParseAll(self, ltp_root): 459 '''Parse outputs from both 'make' and 'make install'. 460 461 Args: 462 ltp_root: string 463 ''' 464 parser = make_parser.MakeParser(ltp_root) 465 self.ParseInput(parser.ParseFile(MAKE_DRY_RUN_FILE_NAME), ltp_root) 466 parser = make_install_parser.MakeInstallParser(ltp_root) 467 self.ParseInput(parser.ParseFile(MAKE_INSTALL_DRY_RUN_FILE_NAME), ltp_root) 468 469 def GetUnusedCustomCFlagsTargets(self): 470 '''Get targets that have custom cflags, but that weren't built.''' 471 return list(self._unused_custom_cflags) 472 473 474def main(): 475 parser = argparse.ArgumentParser( 476 description='Generate Android.mk from parsed LTP make output') 477 parser.add_argument( 478 '--ltp_root', dest='ltp_root', required=True, help='LTP root dir') 479 parser.add_argument( 480 '--output_mk_path', 481 dest='output_mk_path', 482 required=True, 483 help='output makefile path') 484 parser.add_argument( 485 '--output_bp_path', 486 dest='output_bp_path', 487 required=True, 488 help='output blueprint path') 489 parser.add_argument( 490 '--output_plist_path', 491 required=True, 492 help='output package list path') 493 parser.add_argument( 494 '--custom_cflags_file', 495 dest='custom_cflags_file', 496 required=True, 497 help='file with custom per-module cflags. empty means no file.') 498 args = parser.parse_args() 499 500 custom_cflags = {} 501 if args.custom_cflags_file: 502 # The file is expected to just be a JSON map of string -> [string], e.g. 503 # {"testcases/kernel/syscalls/getcwd/getcwd02": ["-DFOO", "-O3"]} 504 with open(args.custom_cflags_file) as f: 505 custom_cflags = json.load(f) 506 507 generator = BuildGenerator(custom_cflags) 508 generator.ParseAll(args.ltp_root) 509 generator.WriteAndroidMk(args.output_mk_path) 510 generator.WriteAndroidBp(args.output_bp_path) 511 generator.WritePackageList(args.output_plist_path) 512 513 unused_cflags_targs = generator.GetUnusedCustomCFlagsTargets() 514 if unused_cflags_targs: 515 print 'NOTE: Tests had custom cflags, but were never seen: {}'.format( 516 ', '.join(unused_cflags_targs)) 517 518 print 'Finished!' 519 520 521if __name__ == '__main__': 522 main() 523