xref: /aosp_15_r20/external/ltp/android/tools/android_build_generator.py (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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.bp.
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
28import re
29
30import make_parser
31import make_install_parser
32
33MAKE_DRY_RUN_FILE_NAME = os.path.join('dump', 'make_dry_run.dump')
34MAKE_INSTALL_DRY_RUN_FILE_NAME = os.path.join('dump', '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'
38TARGET_LIST = [
39    {
40        "arch": "arm",
41        "bitness": "64",
42        "extra_test_configs": ["lowmem", "hwasan", "lowmem_hwasan"],
43        "targets": ["arm64"],
44    },
45    {
46        "arch": "arm",
47        "bitness": "32",
48        "extra_test_configs": ["lowmem"],
49        "targets": ["arm", "arm64"],
50    },
51    {
52        "arch": "riscv",
53        "bitness": "64",
54        "targets": ["riscv64"],
55    },
56    {
57        "arch": "x86",
58        "bitness": "64",
59        "targets": ["x86_64"],
60    },
61    {
62        "arch": "x86",
63        "bitness": "32",
64        "targets": ["x86", "x86_64"],
65    },
66]
67
68
69class BuildGenerator(object):
70    '''A class to parse make output and convert the result to Android.bp modules.
71
72    Attributes:
73        _bp_result: directory of list of strings for blueprint file keyed by target name
74        _prebuilt_bp_result: directory of list of strings for blueprint keyed by target
75            name
76        _custom_cflags: dict of string (module name) to lists of strings (cflags
77            to add for said module)
78        _unused_custom_cflags: set of strings; tracks the modules with custom
79            cflags that we haven't yet seen
80        _packages: list of strings of packages for package list file
81    '''
82
83    def __init__(self, custom_cflags):
84        self._bp_result = {}
85        self._prebuilt_bp_result = {}
86        self._custom_cflags = custom_cflags
87        self._unused_custom_cflags = set(custom_cflags)
88        self._packages = []
89
90    def UniqueKeepOrder(self, sequence):
91        '''Get a copy of list where items are unique and order is preserved.
92
93        Args:
94          sequence: a sequence, can be a list, tuple, or other iterable
95
96        Returns:
97            a list where items copied from input sequence are unique
98            and order is preserved.
99        '''
100        seen = set()
101        return [x for x in sequence if not (x in seen or seen.add(x))]
102
103    def ReadCommentedText(self, file_path):
104        '''Read pound commented text file into a list of lines.
105
106        Comments or empty lines will be excluded
107
108        Args:
109            file_path: string
110        '''
111        ret = set()
112        with open(file_path, 'r') as f:
113            lines = [line.strip() for line in f.readlines()]
114            ret = set([s for s in lines if s and not s.startswith('#')])
115
116        return ret
117
118    def ArTargetToLibraryName(self, ar_target):
119        '''Convert ar target to library name.
120
121        Args:
122            ar_target: string
123        '''
124        return os.path.basename(ar_target)[len('lib'):-len('.a')]
125
126    def BuildExecutable(self, cc_target, local_src_files, local_cflags,
127                        local_c_includes, local_libraries, ltp_libs,
128                        ltp_libs_used, ltp_names_used):
129        '''Build a test module.
130
131        Args:
132            cc_target: string
133            local_src_files: list of string
134            local_cflags: list of string
135            local_c_includes: list of string
136            local_libraries: list of string
137            ltp_libs: list of string
138            ltp_libs_used: set of string
139            ltp_names_used: set of string, set of already used cc_target basenames
140        '''
141        base_name = os.path.basename(cc_target)
142        if base_name in ltp_names_used:
143            print(f'ERROR: base name {base_name} of cc_target {cc_target} already used. Skipping...')
144            return
145        ltp_names_used.add(base_name)
146
147        if cc_target in self._custom_cflags:
148            local_cflags.extend(self._custom_cflags[cc_target])
149            self._unused_custom_cflags.remove(cc_target)
150
151        # ltp_defaults already adds the include directory
152        local_c_includes = [i for i in local_c_includes if i != 'include']
153        target_name = f'ltp_{base_name}'
154        target_bp = []
155
156        self._packages.append(target_name)
157
158        target_bp.append('')
159        target_bp.append('cc_test {')
160        target_bp.append('    name: "%s",' % target_name)
161        target_bp.append('    stem: "%s",' % base_name)
162        target_bp.append('    defaults: ["ltp_test_defaults"],')
163
164        if len(local_src_files) == 1:
165            target_bp.append('    srcs: ["%s"],' % list(local_src_files)[0])
166        else:
167            target_bp.append('    srcs: [')
168            for src in sorted(local_src_files):
169                target_bp.append('        "%s",' % src)
170            target_bp.append('    ],')
171
172        if len(local_cflags) == 1:
173            target_bp.append('    cflags: ["%s"],' % list(local_cflags)[0])
174        elif len(local_cflags) > 1:
175            target_bp.append('    cflags: [')
176            for cflag in sorted(local_cflags):
177                target_bp.append('        "%s",' % cflag)
178            target_bp.append('    ],')
179
180        if len(local_c_includes) == 1:
181            target_bp.append('    local_include_dirs: ["%s"],' % list(local_c_includes)[0])
182        elif len(local_c_includes) > 1:
183            target_bp.append('    local_include_dirs: [')
184            for d in sorted(local_c_includes):
185                target_bp.append('        "%s",' % d)
186            target_bp.append('    ],')
187
188        bionic_builtin_libs = set(['m', 'rt', 'pthread', 'util'])
189        filtered_libs = set(local_libraries).difference(bionic_builtin_libs)
190
191        static_libraries = set(i for i in local_libraries if i in ltp_libs)
192        if len(static_libraries) == 1:
193            target_bp.append('    static_libs: ["libltp_%s"],' % list(static_libraries)[0])
194        elif len(static_libraries) > 1:
195            target_bp.append('    static_libs: [')
196            for lib in sorted(static_libraries):
197                target_bp.append('        "libltp_%s",' % lib)
198            target_bp.append('    ],')
199
200        for lib in static_libraries:
201            ltp_libs_used.add(lib)
202
203        shared_libraries = set(i for i in filtered_libs if i not in ltp_libs)
204        if len(shared_libraries) == 1:
205            target_bp.append('    shared_libs: ["lib%s"],' % list(shared_libraries)[0])
206        elif len(shared_libraries) > 1:
207            target_bp.append('    shared_libs: [')
208            for lib in sorted(shared_libraries):
209                target_bp.append('        "lib%s",' % lib)
210            target_bp.append('    ],')
211
212        target_bp.append('}')
213        self._bp_result[target_name] = target_bp
214
215    def BuildStaticLibrary(self, ar_target, local_src_files, local_cflags,
216                           local_c_includes):
217        '''Build a library module.
218
219        Args:
220            ar_target: string
221            local_src_files: list of string
222            local_cflags: list of string
223            local_c_includes: list of string
224        '''
225        target_name = 'libltp_%s' % self.ArTargetToLibraryName(ar_target)
226        target_bp = []
227        target_bp.append('')
228        target_bp.append('cc_library_static {')
229        target_bp.append('    name: "%s",' % target_name)
230        target_bp.append('    defaults: ["ltp_defaults"],')
231
232        if len(local_c_includes):
233            target_bp.append('    local_include_dirs: [')
234            for d in local_c_includes:
235                target_bp.append('        "%s",' % d)
236            target_bp.append('    ],')
237
238        if len(local_cflags):
239            target_bp.append('    cflags: [')
240            for cflag in local_cflags:
241                target_bp.append('        "%s",' % cflag)
242            target_bp.append('    ],')
243
244        target_bp.append('    srcs: [')
245        for src in local_src_files:
246            target_bp.append('        "%s",' % src)
247        target_bp.append('    ],')
248
249        target_bp.append('}')
250        self._bp_result[target_name] = target_bp
251
252    def BuildShellScript(self, install_target, local_src_file):
253        '''Build a shell script.
254
255        Args:
256            install_target: string
257            local_src_file: string
258        '''
259        base_name = os.path.basename(install_target)
260        bp_result = []
261
262        module = 'ltp_%s' % install_target.replace('/', '_')
263        self._packages.append(module)
264
265        module_dir = os.path.dirname(install_target)
266        module_stem = os.path.basename(install_target)
267
268        bp_result.append('')
269        bp_result.append('sh_test {')
270        bp_result.append('    name: "%s",' % module)
271        bp_result.append('    src: "%s",' % local_src_file)
272        bp_result.append('    sub_dir: "vts_ltp_tests/%s",' % module_dir)
273        bp_result.append('    filename: "%s",' % module_stem)
274        bp_result.append('    compile_multilib: "both",')
275        bp_result.append('}')
276
277        self._bp_result[module] = bp_result
278
279    def BuildPrebuiltBp(self, install_target, local_src_file):
280        '''Build a prebuild module for using Android.bp.
281
282        Args:
283            install_target: string
284            local_src_file: string
285        '''
286        base_name = os.path.basename(install_target)
287        # The original local_src_file is from external/ltp, but for bp the root
288        # will be external/ltp/testcases.
289        src = local_src_file.replace('testcases/', '', 1)
290        module = 'ltp_%s' % install_target.replace('/', '_')
291        module_dir = os.path.dirname(install_target)
292        module_stem = os.path.basename(install_target)
293
294        bp_result = []
295        bp_result.append('')
296        bp_result.append('sh_test {')
297        bp_result.append('    name: "%s",' % module)
298        bp_result.append('    src: "%s",' % src)
299        bp_result.append('    sub_dir: "vts_ltp_tests/%s",' % module_dir)
300        bp_result.append('    filename: "%s",' % module_stem)
301        bp_result.append('    compile_multilib: "both",')
302        bp_result.append('    auto_gen_config: false,')
303        bp_result.append('}')
304
305        self._prebuilt_bp_result[base_name] = bp_result
306        self._packages.append(module)
307
308    def HandleParsedRule(self, line, rules):
309        '''Prepare parse rules.
310
311        Args:
312            line: string
313            rules: dictionary {string, dictionary}
314        '''
315        groups = re.match(r'(.*)\[\'(.*)\'\] = \[(.*)\]', line).groups()
316        rule = groups[0]
317        rule_key = groups[1]
318        if groups[2] == '':
319            rule_value = []
320        else:
321            rule_value = list(i.strip()[1:-1] for i in groups[2].split(','))
322
323        rule_value = self.UniqueKeepOrder(rule_value)
324        rules.setdefault(rule, {})[rule_key] = rule_value
325
326    def ParseInput(self, input_list, ltp_root):
327        '''Parse a interpreted make output and produce Android.bp module.
328
329        Args:
330            input_list: list of string
331        '''
332        disabled_tests = self.ReadCommentedText(DISABLED_TESTS_FILE_NAME)
333        disabled_libs = self.ReadCommentedText(DISABLED_LIBS_FILE_NAME)
334        disabled_cflags = self.ReadCommentedText(DISABLED_CFLAGS_FILE_NAME)
335
336        rules = {}
337        for line in input_list:
338            self.HandleParsedRule(line.strip(), rules)
339
340        # .a target -> .o files
341        ar = rules.get('ar', {})
342        # executable target -> .o files
343        cc_link = rules.get('cc_link', {})
344        # .o target -> .c file
345        cc_compile = rules.get('cc_compile', {})
346        # executable target -> .c files
347        cc_compilelink = rules.get('cc_compilelink', {})
348        # Target name -> CFLAGS passed to gcc
349        cc_flags = rules.get('cc_flags', {})
350        # Target name -> -I paths passed to gcc
351        cc_includes = rules.get('cc_includes', {})
352        # Target name -> -l paths passed to gcc
353        cc_libraries = rules.get('cc_libraries', {})
354        # target -> prebuilt source
355        install = rules.get('install', {})
356
357        # All libraries used by any LTP test (built or not)
358        ltp_libs = set(self.ArTargetToLibraryName(i) for i in ar.keys())
359        # All libraries used by the LTP tests we actually build
360        ltp_libs_used = set()
361        ltp_names_used = set()
362
363        # Remove -Wno-error from cflags, we don't want to print warnings.
364        # Silence individual warnings in ltp_defaults or fix them.
365        for target in cc_flags:
366            if '-Wno-error' in cc_flags[target]:
367                cc_flags[target].remove('-Wno-error')
368
369        print(
370            "Disabled lib tests: Test cases listed here are "
371            "suggested to be disabled since they require a disabled library. "
372            "Please copy and paste them into disabled_tests.txt\n")
373        for i in cc_libraries:
374            if len(set(cc_libraries[i]).intersection(disabled_libs)) > 0:
375                if not os.path.basename(i) in disabled_tests:
376                    print(os.path.basename(i))
377
378        print("Disabled_cflag tests: Test cases listed here are "
379              "suggested to be disabled since they require a disabled cflag. "
380              "Please copy and paste them into disabled_tests.txt\n")
381        for i in cc_flags:
382            if len(set(cc_flags[i]).intersection(disabled_cflags)) > 0:
383                module_name = os.path.basename(i)
384                idx = module_name.find('_')
385                if idx > 0:
386                    module_name = module_name[:idx]
387                print(module_name)
388
389        # Remove include directories that don't exist. They're an error in
390        # Soong.
391        for target in cc_includes:
392            cc_includes[target] = [i for i in cc_includes[target] if os.path.isdir(os.path.join(ltp_root, i))]
393
394        for target in cc_compilelink:
395            module_name = os.path.basename(target)
396            if module_name in disabled_tests:
397                continue
398            local_src_files = []
399            src_files = cc_compilelink[target]
400            for i in src_files:
401                # some targets may have a mix of .c and .o files in srcs
402                # find the .c files to build those .o from cc_compile targets
403                if i.endswith('.o'):
404                    if i not in cc_compile:
405                        raise Exception("Not found: %s when trying to compile target %s" % (i, target))
406                    local_src_files.extend(cc_compile[i])
407                else:
408                    local_src_files.append(i)
409            local_cflags = cc_flags[target]
410            local_c_includes = cc_includes[target]
411            local_libraries = cc_libraries[target]
412            if len(set(local_libraries).intersection(disabled_libs)) > 0:
413                continue
414            if len(set(local_cflags).intersection(disabled_cflags)) > 0:
415                continue
416            self.BuildExecutable(target, local_src_files, local_cflags,
417                                 local_c_includes, local_libraries, ltp_libs,
418                                 ltp_libs_used, ltp_names_used)
419
420        for target in cc_link:
421            if os.path.basename(target) in disabled_tests:
422                continue
423            local_src_files = set()
424            local_cflags = set()
425            local_c_includes = set()
426            local_libraries = cc_libraries[target]
427            # Accumulate flags for all .c files needed to build the .o files.
428            # (Android.bp requires a consistent set of flags across a given target.
429            # Thankfully using the superset of all flags in the target works fine
430            # with LTP tests.)
431            for obj in cc_link[target]:
432                for i in cc_compile[obj]:
433                    local_src_files.add(i)
434                for i in cc_flags[obj]:
435                    local_cflags.add(i)
436                for i in cc_includes[obj]:
437                    local_c_includes.add(i)
438            if len(set(local_libraries).intersection(disabled_libs)) > 0:
439                continue
440            if len(set(local_cflags).intersection(disabled_cflags)) > 0:
441                continue
442
443            self.BuildExecutable(target, local_src_files, local_cflags,
444                                 local_c_includes, local_libraries, ltp_libs,
445                                 ltp_libs_used, ltp_names_used)
446
447        for target in ar:
448            # Disabled ltp library is already excluded
449            # since it won't be in ltp_libs_used
450            if not self.ArTargetToLibraryName(target) in ltp_libs_used:
451                continue
452
453            local_src_files = set()
454            local_cflags = set()
455            local_c_includes = set()
456
457            # TODO: disabled cflags
458
459            for obj in ar[target]:
460                for i in cc_compile[obj]:
461                    local_src_files.add(i)
462                for i in cc_flags[obj]:
463                    local_cflags.add(i)
464                for i in cc_includes[obj]:
465                    local_c_includes.add(i)
466
467            if len(set(local_cflags).intersection(disabled_cflags)) > 0:
468                continue
469
470            local_src_files = sorted(local_src_files)
471            local_cflags = sorted(local_cflags)
472            local_c_includes = sorted(local_c_includes)
473
474            self.BuildStaticLibrary(target, local_src_files, local_cflags,
475                                    local_c_includes)
476
477        for target in install:
478            # Check if the absolute path to the prebuilt (relative to LTP_ROOT)
479            # is disabled. This is helpful in case there are duplicates with basename
480            # of the prebuilt.
481            #  e.g.
482            #   ./ testcases / kernel / fs / fs_bind / move / test01
483            #   ./ testcases / kernel / fs / fs_bind / cloneNS / test01
484            #   ./ testcases / kernel / fs / fs_bind / regression / test01
485            #   ./ testcases / kernel / fs / fs_bind / rbind / test01
486            #   ./ testcases / kernel / fs / fs_bind / bind / test01
487            if target in disabled_tests:
488                continue
489            if os.path.basename(target) in disabled_tests:
490                continue
491            local_src_files = install[target]
492            assert len(local_src_files) == 1
493
494            if target.startswith("testcases/bin/"):
495                self.BuildShellScript(target, local_src_files[0])
496            else:
497                self.BuildPrebuiltBp(target, local_src_files[0])
498
499    def WriteAndroidBp(self, output_path):
500        '''Write parse result to blueprint file.
501
502        Args:
503            output_path: string
504        '''
505        with open(output_path, 'a') as f:
506            for k in sorted(self._bp_result.keys()):
507                f.write('\n'.join(self._bp_result[k]))
508                f.write('\n')
509            self._bp_result = {}
510
511    def WritePrebuiltAndroidBp(self, output_path):
512        '''Write parse result to blueprint file.
513
514        Args:
515            output_path: string
516        '''
517        bp_result = []
518        bp_result.append('')
519        bp_result.append('package {')
520        bp_result.append('    default_applicable_licenses: ["external_ltp_license"],')
521        bp_result.append('}')
522        for k in sorted(self._prebuilt_bp_result.keys()):
523            bp_result.extend(self._prebuilt_bp_result[k])
524        self._prebuilt_bp_result = {}
525        with open(output_path, 'a') as f:
526            for k in sorted(self._prebuilt_bp_result.keys()):
527                f.write('\n'.join(self._prebuilt_bp_result[k]))
528                f.write('\n')
529            self._prebuilt_bp_result = {}
530            f.write('\n'.join(bp_result))
531            f.write('\n')
532
533    def ArchString(self, arch, bitness, lowmem=False, hwasan=False):
534        if bitness == '32':
535            arch_string = arch
536        else:
537            arch_string = f'{arch}_{bitness}'
538        if lowmem:
539            arch_string += '_lowmem'
540        if hwasan:
541            arch_string += '_hwasan'
542        return arch_string
543
544    def BuildConfigGenrule(self, arch, bitness, targets, extra_test_configs=None):
545        extra_test_configs = extra_test_configs if extra_test_configs else []
546        bp_result = []
547        arch_string = self.ArchString(arch, bitness)
548
549        bp_result.append('')
550        bp_result.append('genrule {')
551        bp_result.append('    name: "ltp_config_%s",' % arch_string)
552        bp_result.append('    out: ["vts_ltp_test_%s.xml"],' % arch_string)
553        bp_result.append('    tools: ["gen_ltp_config"],')
554        bp_result.append('    cmd: "$(location gen_ltp_config) --arch %s --bitness %s --low-mem %r --hwasan %r $(out)",' % (arch, bitness, lowmem, hwasan))
555        bp_result.append('}')
556
557        for config in extra_test_configs:
558            lowmem = 'lowmem' in config
559            hwasan = 'hwasan' in config
560            arch_string = self.ArchString(arch, bitness, lowmem, hwasan)
561
562            bp_result.append('')
563            bp_result.append('genrule {')
564            bp_result.append('    name: "ltp_config_%s",' % arch_string)
565            bp_result.append('    out: ["vts_ltp_test_%s.xml"],' % arch_string)
566            bp_result.append('    tools: ["gen_ltp_config"],')
567            bp_result.append('    cmd: "$(location gen_ltp_config) --arch %s --bitness %s --low-mem %r --hwasan %r $(out)",' % (arch, bitness, lowmem, hwasan))
568            bp_result.append('}')
569        return bp_result
570
571    def BuildPackageList(self):
572        bp_result = []
573        bp_result.append('')
574        bp_result.append('LTP_TESTS = [')
575        bp_result.append('    ":ltp_runtests",')
576        for package in sorted(self._packages):
577            bp_result.append('    ":%s",' % package)
578        bp_result.append(']')
579        return bp_result
580
581    def BuildLTPTestSuite(self, arch, bitness, targets, extra_test_configs=None):
582        extra_test_configs = extra_test_configs if extra_test_configs else []
583        bp_result = []
584        arch_string = self.ArchString(arch, bitness)
585
586        bp_result.append('')
587        bp_result.append('sh_test {')
588        bp_result.append('    name: "vts_ltp_test_%s",' % arch_string)
589        bp_result.append('    src: "empty.sh",')
590        bp_result.append('    test_config: ":ltp_config_%s",' % arch_string)
591        bp_result.append('    test_suites: [')
592        bp_result.append('        "general-tests",')
593        bp_result.append('        "vts",')
594        bp_result.append('    ],')
595        bp_result.append('    enabled: false,')
596
597        if bitness == '32':
598            bp_result.append('    compile_multilib: "32",')
599
600        bp_result.append('    arch: {')
601        for target in targets:
602            bp_result.append('        %s: {' % target)
603            bp_result.append('            enabled: true,')
604            bp_result.append('        },')
605        bp_result.append('    },')
606
607        if extra_test_configs:
608            bp_result.append('    extra_test_configs: [')
609            for config in extra_test_configs:
610                lowmem = 'lowmem' in config
611                hwasan = 'hwasan' in config
612                arch_string = self.ArchString(arch, bitness, lowmem, hwasan)
613                bp_result.append('        ":ltp_config_%s",' % arch_string)
614            bp_result.append('    ],')
615
616        bp_result.append('    data: LTP_TESTS,')
617        bp_result.append('}')
618        return bp_result
619
620    def WriteLtpMainAndroidBp(self, output_path):
621        '''Write the blueprint file of ltp main module.
622
623        Args:
624            output_path: string
625        '''
626        bp_result = []
627        bp_result.append('')
628        bp_result.append('package {')
629        bp_result.append('    default_applicable_licenses: ["external_ltp_license"],')
630        bp_result.append('}')
631
632        bp_result.extend(self.BuildPackageList())
633
634        for target in TARGET_LIST:
635            bp_result.extend(self.BuildConfigGenrule(**target))
636
637        for target in TARGET_LIST:
638            bp_result.extend(self.BuildLTPTestSuite(**target))
639
640        with open(output_path, 'a') as f:
641            f.write('\n'.join(bp_result))
642            f.write('\n')
643
644    def ParseAll(self, ltp_root):
645        '''Parse outputs from both 'make' and 'make install'.
646
647        Args:
648            ltp_root: string
649        '''
650        parser = make_parser.MakeParser(ltp_root)
651        self.ParseInput(parser.ParseFile(MAKE_DRY_RUN_FILE_NAME), ltp_root)
652        parser = make_install_parser.MakeInstallParser(ltp_root)
653        self.ParseInput(parser.ParseFile(MAKE_INSTALL_DRY_RUN_FILE_NAME), ltp_root)
654
655    def GetUnusedCustomCFlagsTargets(self):
656        '''Get targets that have custom cflags, but that weren't built.'''
657        return list(self._unused_custom_cflags)
658
659
660def main():
661    parser = argparse.ArgumentParser(
662        description='Generate Android.bp from parsed LTP make output')
663    parser.add_argument(
664        '--ltp_root', dest='ltp_root', required=True, help='LTP root dir')
665    parser.add_argument(
666        '--output_prebuilt_ltp_testcase_bp_path',
667        dest='output_prebuilt_ltp_testcase_bp_path',
668        required=True,
669        help='output prebuilt test case blueprint path')
670    parser.add_argument(
671        '--output_ltp_main_bp_path',
672        dest='output_ltp_main_bp_path',
673        required=True,
674        help='output ltp main blueprint path')
675    parser.add_argument(
676        '--output_bp_path',
677        dest='output_bp_path',
678        required=True,
679        help='output blueprint path')
680    parser.add_argument(
681        '--custom_cflags_file',
682        dest='custom_cflags_file',
683        required=True,
684        help='file with custom per-module cflags. empty means no file.')
685    args = parser.parse_args()
686
687    custom_cflags = {}
688    if args.custom_cflags_file:
689        # The file is expected to just be a JSON map of string -> [string], e.g.
690        # {"testcases/kernel/syscalls/getcwd/getcwd02": ["-DFOO", "-O3"]}
691        with open(args.custom_cflags_file) as f:
692            custom_cflags = json.load(f)
693
694    generator = BuildGenerator(custom_cflags)
695    generator.ParseAll(args.ltp_root)
696    generator.WritePrebuiltAndroidBp(args.output_prebuilt_ltp_testcase_bp_path)
697    generator.WriteLtpMainAndroidBp(args.output_ltp_main_bp_path)
698    generator.WriteAndroidBp(args.output_bp_path)
699
700    unused_cflags_targs = generator.GetUnusedCustomCFlagsTargets()
701    if unused_cflags_targs:
702        print('NOTE: Tests had custom cflags, but were never seen: {}'.format(
703            ', '.join(unused_cflags_targs)))
704
705    print('Finished!')
706
707
708if __name__ == '__main__':
709    main()
710