blob: 709dbd22b5999acd5f1eda686d35a14ada4e1e8d [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2016 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Parses the output of parse_ltp_{make,make_install} and generates a
# corresponding Android.mk.
#
# This process is split into two steps so this second step can later be replaced
# with an Android.bp generator.
import argparse
import fileinput
import json
import os.path
import re
import make_parser
import make_install_parser
MAKE_DRY_RUN_FILE_NAME = 'make_dry_run.dump'
MAKE_INSTALL_DRY_RUN_FILE_NAME = 'make_install_dry_run.dump'
DISABLED_TESTS_FILE_NAME = 'disabled_tests.txt'
DISABLED_LIBS_FILE_NAME = 'disabled_libs.txt'
DISABLED_CFLAGS_FILE_NAME = 'disabled_cflags.txt'
class AndroidMkGenerator(object):
'''A class to parse make output and convert the result to Android.ltp.mk modules.
Attributes:
result: list of string, result string buffer
_custom_cflags: dict of string (module name) to lists of strings (cflags
to add for said module)
_unused_custom_cflags: set of strings; tracks the modules with custom
cflags that we haven't yet seen
'''
def __init__(self, custom_cflags):
self.result = []
self._custom_cflags = custom_cflags
self._unused_custom_cflags = set(custom_cflags)
def UniqueKeepOrder(self, sequence):
'''Get a copy of list where items are unique and order is preserved.
Args:
sequence: a sequence, can be a list, tuple, or other iterable
Returns:
a list where items copied from input sequence are unique
and order is preserved.
'''
seen = set()
return [x for x in sequence if not (x in seen or seen.add(x))]
def ReadCommentedText(self, file_path):
'''Read pound commented text file into a list of lines.
Comments or empty lines will be excluded
Args:
file_path: string
'''
ret = set()
with open(file_path, 'r') as f:
lines = [line.strip() for line in f.readlines()]
ret = set([s for s in lines if s and not s.startswith('#')])
return ret
def ArTargetToLibraryName(self, ar_target):
'''Convert ar target to library name.
Args:
ar_target: string
'''
return os.path.basename(ar_target)[len('lib'):-len('.a')]
def BuildExecutable(self, cc_target, local_src_files, local_cflags,
local_c_includes, local_libraries, ltp_libs,
ltp_libs_used, ltp_names_used):
'''Build a test module.
Args:
cc_target: string
local_src_files: list of string
local_cflags: list of string
local_c_includes: list of string
local_libraries: list of string
ltp_libs: list of string
ltp_libs_used: set of string
ltp_names_used: set of string, set of already used cc_target basenames
'''
base_name = os.path.basename(cc_target)
if base_name in ltp_names_used:
print 'ERROR: base name {} of cc_target {} already used. Skipping...'.format(
base_name, cc_target)
return
ltp_names_used.add(base_name)
if cc_target in self._custom_cflags:
local_cflags.extend(self._custom_cflags[cc_target])
self._unused_custom_cflags.remove(cc_target)
self.result.append('module_testname := %s' % cc_target)
self.result.append('module_src_files := %s' %
' '.join(local_src_files))
self.result.append('module_cflags := %s' % ' '.join(local_cflags))
self.result.append('module_c_includes := %s' %
' '.join(local_c_includes))
bionic_builtin_libs = set(['rt', 'pthread'])
filtered_libs = set(local_libraries).difference(bionic_builtin_libs)
static_libraries = set(i for i in local_libraries if i in ltp_libs)
self.result.append('module_static_libraries := %s' %
' '.join(static_libraries))
for lib in static_libraries:
ltp_libs_used.add(lib)
shared_libraries = set(i for i in filtered_libs if i not in ltp_libs)
self.result.append('module_shared_libraries := %s' %
' '.join(shared_libraries))
self.result.append('include $(ltp_build_test)')
self.result.append('')
def BuildStaticLibrary(self, ar_target, local_src_files, local_cflags,
local_c_includes):
'''Build a library module.
Args:
ar_target: string
local_src_files: list of string
local_cflags: list of string
local_c_includes: list of string
'''
self.result.append('module_libname := %s' %
self.ArTargetToLibraryName(ar_target))
self.result.append('module_src_files := %s' %
' '.join(local_src_files))
self.result.append('module_cflags := %s' % ' '.join(local_cflags))
self.result.append('module_c_includes := %s' %
' '.join(local_c_includes))
self.result.append('include $(ltp_build_library)')
self.result.append('')
def BuildPrebuilt(self, install_target, local_src_file):
'''Build a prebuild module.
Args:
install_target: string
local_src_file: string
'''
self.result.append('module_prebuilt := %s' % install_target)
self.result.append('module_src_files := %s' % local_src_file)
module_dir = os.path.dirname(install_target)
module_stem = os.path.basename(install_target)
module = 'ltp_%s' % install_target.replace('/', '_')
self.result.append('include $(ltp_build_prebuilt)')
self.result.append('')
def HandleParsedRule(self, line, rules):
'''Prepare parse rules.
Args:
line: string
rules: dictionary {string, dictionary}
'''
groups = re.match(r'(.*)\[\'(.*)\'\] = \[(.*)\]', line).groups()
rule = groups[0]
rule_key = groups[1]
if groups[2] == '':
rule_value = []
else:
rule_value = list(i.strip()[1:-1] for i in groups[2].split(','))
rule_value = self.UniqueKeepOrder(rule_value)
rules.setdefault(rule, {})[rule_key] = rule_value
def ParseInput(self, input_list):
'''Parse a interpreted make output and produce Android.ltp.mk module.
Args:
input_list: list of string
'''
disabled_tests = self.ReadCommentedText(DISABLED_TESTS_FILE_NAME)
disabled_libs = self.ReadCommentedText(DISABLED_LIBS_FILE_NAME)
disabled_cflags = self.ReadCommentedText(DISABLED_CFLAGS_FILE_NAME)
rules = {}
for line in input_list:
self.HandleParsedRule(line.strip(), rules)
# .a target -> .o files
ar = rules.get('ar', {})
# executable target -> .o files
cc_link = rules.get('cc_link', {})
# .o target -> .c file
cc_compile = rules.get('cc_compile', {})
# executable target -> .c files
cc_compilelink = rules.get('cc_compilelink', {})
# Target name -> CFLAGS passed to gcc
cc_flags = rules.get('cc_flags', {})
# Target name -> -I paths passed to gcc
cc_includes = rules.get('cc_includes', {})
# Target name -> -l paths passed to gcc
cc_libraries = rules.get('cc_libraries', {})
# target -> prebuilt source
install = rules.get('install', {})
# All libraries used by any LTP test (built or not)
ltp_libs = set(self.ArTargetToLibraryName(i) for i in ar.keys())
# All libraries used by the LTP tests we actually build
ltp_libs_used = set()
ltp_names_used = set()
print(
"Disabled lib tests: Test cases listed here are"
"suggested to be disabled since they require a disabled library. "
"Please copy and paste them into disabled_tests.txt\n")
for i in cc_libraries:
if len(set(cc_libraries[i]).intersection(disabled_libs)) > 0:
print os.path.basename(i)
print("Disabled_cflag tests: Test cases listed here are"
"suggested to be disabled since they require a disabled cflag. "
"Please copy and paste them into disabled_tests.txt\n")
for i in cc_flags:
if len(set(cc_flags[i]).intersection(disabled_cflags)) > 0:
module_name = os.path.basename(i)
idx = module_name.find('_')
if idx > 0:
module_name = module_name[:idx]
print module_name
for target in cc_compilelink:
module_name = os.path.basename(target)
if module_name in disabled_tests:
continue
local_src_files = cc_compilelink[target]
local_cflags = cc_flags[target]
local_c_includes = cc_includes[target]
local_libraries = cc_libraries[target]
if len(set(local_libraries).intersection(disabled_libs)) > 0:
continue
if len(set(local_cflags).intersection(disabled_cflags)) > 0:
continue
self.BuildExecutable(target, local_src_files, local_cflags,
local_c_includes, local_libraries, ltp_libs,
ltp_libs_used, ltp_names_used)
for target in cc_link:
if os.path.basename(target) in disabled_tests:
continue
local_src_files = set()
local_cflags = set()
local_c_includes = set()
local_libraries = cc_libraries[target]
# Accumulate flags for all .c files needed to build the .o files.
# (Android.mk requires a consistent set of flags across a given target.
# Thankfully using the superset of all flags in the target works fine
# with LTP tests.)
for obj in cc_link[target]:
for i in cc_compile[obj]:
local_src_files.add(i)
for i in cc_flags[obj]:
local_cflags.add(i)
for i in cc_includes[obj]:
local_c_includes.add(i)
if len(set(local_libraries).intersection(disabled_libs)) > 0:
continue
if len(set(local_cflags).intersection(disabled_cflags)) > 0:
continue
self.BuildExecutable(target, local_src_files, local_cflags,
local_c_includes, local_libraries, ltp_libs,
ltp_libs_used, ltp_names_used)
for target in ar:
# Disabled ltp library is already excluded
# since it won't be in ltp_libs_used
if not self.ArTargetToLibraryName(target) in ltp_libs_used:
continue
local_src_files = set()
local_cflags = set()
local_c_includes = set()
# TODO: disabled cflags
for obj in ar[target]:
for i in cc_compile[obj]:
local_src_files.add(i)
for i in cc_flags[obj]:
local_cflags.add(i)
for i in cc_includes[obj]:
local_c_includes.add(i)
if len(set(local_cflags).intersection(disabled_cflags)) > 0:
continue
self.BuildStaticLibrary(target, local_src_files, local_cflags,
local_c_includes)
for target in install:
if os.path.basename(target) in disabled_tests:
continue
local_src_files = install[target]
assert len(local_src_files) == 1
self.BuildPrebuilt(target, local_src_files[0])
def WriteResult(self, output_path):
'''Write parse result to output file.
Args:
output_path: string
'''
with open(output_path, 'a') as f:
f.write('\n'.join(self.result))
self.result = []
def ParseAll(self, ltp_root):
'''Parse outputs from both 'make' and 'make install'.
Args:
ltp_root: string
'''
parser = make_parser.MakeParser(ltp_root)
self.ParseInput(parser.ParseFile(MAKE_DRY_RUN_FILE_NAME))
parser = make_install_parser.MakeInstallParser(ltp_root)
self.ParseInput(parser.ParseFile(MAKE_INSTALL_DRY_RUN_FILE_NAME))
def GetUnusedCustomCFlagsTargets(self):
'''Get targets that have custom cflags, but that weren't built.'''
return list(self._unused_custom_cflags)
def main():
parser = argparse.ArgumentParser(
description='Generate Android.mk from parsed LTP make output')
parser.add_argument(
'--ltp_root', dest='ltp_root', required=True, help='LTP root dir')
parser.add_argument(
'--output_path',
dest='output_path',
required=True,
help='output file path')
parser.add_argument(
'--custom_cflags_file',
dest='custom_cflags_file',
required=True,
help='file with custom per-module cflags. empty means no file.')
args = parser.parse_args()
custom_cflags = {}
if args.custom_cflags_file:
# The file is expected to just be a JSON map of string -> [string], e.g.
# {"testcases/kernel/syscalls/getcwd/getcwd02": ["-DFOO", "-O3"]}
with open(args.custom_cflags_file) as f:
custom_cflags = json.load(f)
generator = AndroidMkGenerator(custom_cflags)
generator.ParseAll(args.ltp_root)
generator.WriteResult(args.output_path)
unused_cflags_targs = generator.GetUnusedCustomCFlagsTargets()
if unused_cflags_targs:
print 'NOTE: Tests had custom cflags, but were never seen: {}'.format(
', '.join(unused_cflags_targs))
print 'Finished!'
if __name__ == '__main__':
main()