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 18import argparse 19import os 20import os.path 21import sys 22import re 23import fileinput 24import pprint 25 26AR = 'ar' 27AS = 'as' 28CC = 'gcc' 29 30class MakeParser(object): 31 '''Parses the output of make --dry-run. 32 33 Attributes: 34 ltp_root: string, LTP root directory 35 ar_parser: archive (ar) command argument parser 36 as_parser: assembly (as) command argument parser 37 cc_parser: gcc command argument parser 38 result: list of string, result string buffer 39 dir_stack: list of string, directory stack for parsing make commands 40 ''' 41 42 def __init__(self, ltp_root): 43 self.ltp_root = ltp_root 44 ar_parser = argparse.ArgumentParser() 45 ar_parser.add_argument('-r', dest='r', action='store_true') 46 ar_parser.add_argument('-c', dest='c', action='store') 47 self.ar_parser = ar_parser 48 49 as_parser = argparse.ArgumentParser() 50 as_parser.add_argument('-c', dest='compile', action='store_true') 51 as_parser.add_argument('-o', dest='target', action='store') 52 self.as_parser = as_parser 53 54 cc_parser = argparse.ArgumentParser() 55 cc_parser.add_argument('-D', dest='defines', action='append') 56 cc_parser.add_argument('-I', dest='includes', action='append') 57 cc_parser.add_argument('-l', dest='libraries', action='append') 58 cc_parser.add_argument('-L', dest='libraries_path', action='append') 59 cc_parser.add_argument('-c', dest='compile', action='store_true') 60 cc_parser.add_argument('-o', dest='target', action='store') 61 self.cc_parser = cc_parser 62 63 self.result = [] 64 self.dir_stack = [] 65 66 def GetRelativePath(self, path): 67 '''Get relative path toward LTP directory. 68 69 Args: 70 path: string, a path to convert to relative path 71 ''' 72 if path[0] == '/': 73 path = os.path.realpath(path) 74 else: 75 path = os.path.realpath(self.ltp_root + os.sep + self.dir_stack[-1] 76 + os.sep + path) 77 return os.path.realpath(path).replace(self.ltp_root + os.sep, '') 78 79 def GetRelativePathForExtensions(self, paths, extensions): 80 '''Get relative path toward LTP directory of paths with given extension. 81 82 Args: 83 paths: list of string, paths to convert to relative path 84 extensions: list of string, extension include filter 85 ''' 86 return [self.GetRelativePath(i) for i in paths if i[-1] in extensions] 87 88 def ParseAr(self, line): 89 '''Parse an archive command line. 90 91 Args: 92 line: string, a line of as command to parse 93 ''' 94 args, unparsed = self.ar_parser.parse_known_args(line.split()[1:]) 95 96 sources = self.GetRelativePathForExtensions(unparsed, ['o']) 97 98 # Support 'ar' command line with or without hyphens (-) 99 # e.g.: 100 # 1. ar rcs libfoo.a foo1.o foo2.o 101 # 2. ar -rc "libfoo.a" foo1.o foo2.o; ranlib "libfoo.a" 102 target = None 103 if not args.c and not args.r: 104 for path in unparsed: 105 if path.endswith('.a'): 106 target = self.GetRelativePath(path) 107 break 108 else: 109 target = self.GetRelativePath(args.c.replace('"', "")) 110 111 assert len(sources) > 0 112 assert target != None 113 114 self.result.append("ar['%s'] = %s" % (target, sources)) 115 116 def ParseAs(self, line): 117 '''Parse an assembly command line. 118 119 Args: 120 line: string, a line of as command to parse 121 ''' 122 args, unparsed = self.as_parser.parse_known_args(line.split()[1:]) 123 124 sources = self.GetRelativePathForExtensions(unparsed, ['S']) 125 126 assert len(sources) > 0 127 target = self.GetRelativePath(args.target) 128 129 if args.compile: 130 self.result.append("cc_compile['%s'] = %s" % (target, sources)) 131 else: 132 raise Exception("Unparsed assembly line: %s" % line) 133 134 def ParseCc(self, line): 135 '''Parse a gcc command line. 136 137 Args: 138 line: string, a line of gcc command to parse 139 ''' 140 args, unparsed = self.cc_parser.parse_known_args(line.split()[1:]) 141 142 sources = self.GetRelativePathForExtensions(unparsed, ['c', 'o']) 143 includes = [self.GetRelativePath(i) 144 for i in args.includes] if args.includes else [] 145 flags = [] 146 defines = args.defines if args.defines else [] 147 target = self.GetRelativePath(args.target) 148 149 if args.defines: 150 for define in args.defines: 151 flags.append('-D%s' % define) 152 153 flags.extend(i for i in unparsed if i.startswith('-Wno')) 154 155 assert len(sources) > 0 156 157 if args.compile: 158 self.result.append("cc_compile['%s'] = %s" % (target, sources)) 159 else: 160 libraries = args.libraries if args.libraries else [] 161 if sources[0].endswith('.o'): 162 self.result.append("cc_link['%s'] = %s" % (target, sources)) 163 else: 164 self.result.append("cc_compilelink['%s'] = %s" % 165 (target, sources)) 166 self.result.append("cc_libraries['%s'] = %s" % (target, libraries)) 167 168 self.result.append("cc_flags['%s'] = %s" % (target, flags)) 169 self.result.append("cc_includes['%s'] = %s" % (target, includes)) 170 171 def ParseFile(self, input_path): 172 '''Parses the output of make --dry-run. 173 174 Args: 175 input_text: string, output of make --dry-run 176 177 Returns: 178 string, generated directives in the form 179 ar['target.a'] = [ 'srcfile1.o, 'srcfile2.o', ... ] 180 cc_link['target'] = [ 'srcfile1.o', 'srcfile2.o', ... ] 181 cc_compile['target.o'] = [ 'srcfile1.c' ] 182 cc_compilelink['target'] = [ 'srcfile1.c' ] 183 along with optional flags for the above directives in the form 184 cc_flags['target'] = [ '-flag1', '-flag2', ...] 185 cc_includes['target'] = [ 'includepath1', 'includepath2', ...] 186 cc_libraries['target'] = [ 'library1', 'library2', ...] 187 ''' 188 self.result = [] 189 self.dir_stack = [] 190 191 entering_directory = re.compile(r"make.*: Entering directory [`,'](.*)'") 192 leaving_directory = re.compile(r"make.*: Leaving directory [`,'](.*)'") 193 194 with open(input_path, 'r') as f: 195 for line in f: 196 line = line.strip() 197 198 m = entering_directory.match(line) 199 if m: 200 self.dir_stack.append(self.GetRelativePath(m.group(1))) 201 continue 202 203 m = leaving_directory.match(line) 204 if m: 205 self.dir_stack.pop() 206 elif line.startswith(AR): 207 self.ParseAr(line) 208 elif line.startswith(AS): 209 self.ParseAs(line) 210 elif line.startswith(CC): 211 self.ParseCc(line) 212 213 return self.result 214 215def main(): 216 arg_parser = argparse.ArgumentParser( 217 description='Parse the LTP make --dry-run output into a list') 218 arg_parser.add_argument( 219 '--ltp-root', 220 dest='ltp_root', 221 required=True, 222 help='LTP Root dir') 223 arg_parser.add_argument( 224 '--dry-run-file', 225 dest='input_path', 226 required=True, 227 help='Path to LTP make --dry-run output file') 228 args = arg_parser.parse_args() 229 230 make_parser = MakeParser(args.ltp_root) 231 result = make_parser.ParseFile(args.input_path) 232 233 print(pprint.pprint(result)) 234 235if __name__ == '__main__': 236 main() 237