# # 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. # import argparse import os import os.path import sys import re import fileinput import pprint AR = 'ar' CC = 'gcc' class MakeParser(object): '''Parses the output of make --dry-run. Attributes: ltp_root: string, LTP root directory ar_parser: archive (ar) command argument parser cc_parser: gcc command argument parser result: list of string, result string buffer dir_stack: list of string, directory stack for parsing make commands ''' def __init__(self, ltp_root): self.ltp_root = ltp_root ar_parser = argparse.ArgumentParser() ar_parser.add_argument('-r', dest='r', action='store_true') ar_parser.add_argument('-c', dest='c', action='store') self.ar_parser = ar_parser cc_parser = argparse.ArgumentParser() cc_parser.add_argument('-D', dest='defines', action='append') cc_parser.add_argument('-I', dest='includes', action='append') cc_parser.add_argument('-l', dest='libraries', action='append') cc_parser.add_argument('-c', dest='compile', action='store_true') cc_parser.add_argument('-o', dest='target', action='store') self.cc_parser = cc_parser self.result = [] self.dir_stack = [] def GetRelativePath(self, path): '''Get relative path toward LTP directory. Args: path: string, a path to convert to relative path ''' if path[0] == '/': path = os.path.realpath(path) else: path = os.path.realpath(self.ltp_root + os.sep + self.dir_stack[-1] + os.sep + path) return os.path.realpath(path).replace(self.ltp_root + os.sep, '') def GetRelativePathForExtensions(self, paths, extensions): '''Get relative path toward LTP directory of paths with given extension. Args: paths: list of string, paths to convert to relative path extensions: list of string, extension include filter ''' return [self.GetRelativePath(i) for i in paths if i[-1] in extensions] def ParseAr(self, line): '''Parse a archive command line. Args: line: string, a line of ar command to parse ''' args, unparsed = self.ar_parser.parse_known_args(line.split()[1:]) sources = self.GetRelativePathForExtensions(unparsed, ['o']) # Support 'ar' command line with or without hyphens (-) # e.g.: # 1. ar rcs libfoo.a foo1.o foo2.o # 2. ar -rc "libfoo.a" foo1.o foo2.o; ranlib "libfoo.a" target = None if not args.c and not args.r: for path in unparsed: if path.endswith('.a'): target = self.GetRelativePath(path) break else: target = self.GetRelativePath(args.c.replace('"', "")) assert len(sources) > 0 assert target != None self.result.append("ar['%s'] = %s" % (target, sources)) def ParseCc(self, line): '''Parse a gcc command line. Args: line: string, a line of gcc command to parse ''' args, unparsed = self.cc_parser.parse_known_args(line.split()[1:]) sources = self.GetRelativePathForExtensions(unparsed, ['c', 'o']) includes = [self.GetRelativePath(i) for i in args.includes] if args.includes else [] flags = [] defines = args.defines if args.defines else [] target = self.GetRelativePath(args.target) if args.defines: for define in args.defines: flags.append('-D%s' % define) flags.extend(i for i in unparsed if i.startswith('-Wno')) assert len(sources) > 0 if args.compile: self.result.append("cc_compile['%s'] = %s" % (target, sources)) else: libraries = args.libraries if args.libraries else [] if sources[0].endswith('.o'): self.result.append("cc_link['%s'] = %s" % (target, sources)) else: self.result.append("cc_compilelink['%s'] = %s" % (target, sources)) self.result.append("cc_libraries['%s'] = %s" % (target, libraries)) self.result.append("cc_flags['%s'] = %s" % (target, flags)) self.result.append("cc_includes['%s'] = %s" % (target, includes)) def ParseFile(self, input_path): '''Parses the output of make --dry-run. Args: input_text: string, output of make --dry-run Returns: string, generated directives in the form ar['target.a'] = [ 'srcfile1.o, 'srcfile2.o', ... ] cc_link['target'] = [ 'srcfile1.o', 'srcfile2.o', ... ] cc_compile['target.o'] = [ 'srcfile1.c' ] cc_compilelink['target'] = [ 'srcfile1.c' ] along with optional flags for the above directives in the form cc_flags['target'] = [ '-flag1', '-flag2', ...] cc_includes['target'] = [ 'includepath1', 'includepath2', ...] cc_libraries['target'] = [ 'library1', 'library2', ...] ''' self.result = [] self.dir_stack = [] entering_directory = re.compile(r"make.*: Entering directory `(.*)'") leaving_directory = re.compile(r"make.*: Leaving directory `(.*)'") with open(input_path, 'r') as f: for line in f: line = line.strip() m = entering_directory.match(line) if m: self.dir_stack.append(self.GetRelativePath(m.group(1))) continue m = leaving_directory.match(line) if m: self.dir_stack.pop() elif line.startswith(AR): self.ParseAr(line) elif line.startswith(CC): self.ParseCc(line) return self.result def main(): arg_parser = argparse.ArgumentParser( description='Parse the LTP make --dry-run output into a list') arg_parser.add_argument( '--ltp-root', dest='ltp_root', required=True, help='LTP Root dir') arg_parser.add_argument( '--dry-run-file', dest='input_path', required=True, help='Path to LTP make --dry-run output file') args = arg_parser.parse_args() make_parser = MakeParser(args.ltp_root) result = make_parser.ParseFile(args.input_path) print pprint.pprint(result) if __name__ == '__main__': main()