1#
2# Copyright 2016 - The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import argparse
18import os
19import os.path
20import sys
21import re
22import fileinput
23
24AR = 'ar'
25CC = 'gcc'
26
27class MakeParser(object):
28    '''Parses the output of make --dry-run.
29
30    Attributes:
31        ltp_root: string, LTP root directory
32        ar_parser: archive (ar) command argument parser
33        cc_parser: gcc command argument parser
34        result: list of string, result string buffer
35        dir_stack: list of string, directory stack for parsing make commands
36    '''
37
38    def __init__(self, ltp_root):
39        self.ltp_root = ltp_root
40        ar_parser = argparse.ArgumentParser()
41        ar_parser.add_argument('-r', dest='r', action='store_true')
42        ar_parser.add_argument('-c', dest='c', action='store')
43        self.ar_parser = ar_parser
44
45        cc_parser = argparse.ArgumentParser()
46        cc_parser.add_argument('-D', dest='defines', action='append')
47        cc_parser.add_argument('-I', dest='includes', action='append')
48        cc_parser.add_argument('-l', dest='libraries', action='append')
49        cc_parser.add_argument('-c', dest='compile', action='store_true')
50        cc_parser.add_argument('-o', dest='target', action='store')
51        self.cc_parser = cc_parser
52
53        self.result = []
54        self.dir_stack = []
55
56    def GetRelativePath(self, path):
57        '''Get relative path toward LTP directory.
58
59        Args:
60            path: string, a path to convert to relative path
61        '''
62        if path[0] == '/':
63            path = os.path.realpath(path)
64        else:
65            path = os.path.realpath(self.ltp_root + os.sep + self.dir_stack[-1]
66                                    + os.sep + path)
67        return os.path.realpath(path).replace(self.ltp_root + os.sep, '')
68
69    def GetRelativePathForExtensions(self, paths, extensions):
70        '''Get relative path toward LTP directory of paths with given extension.
71
72        Args:
73            paths: list of string, paths to convert to relative path
74            extensions: list of string, extension include filter
75        '''
76        return [self.GetRelativePath(i) for i in paths if i[-1] in extensions]
77
78    def ParseAr(self, line):
79        '''Parse a archive command line.
80
81        Args:
82            line: string, a line of ar command to parse
83        '''
84        args, unparsed = self.ar_parser.parse_known_args(line.split()[1:])
85
86        sources = self.GetRelativePathForExtensions(unparsed, ['o'])
87        target = self.GetRelativePath(args.c.replace('"', ""))
88
89        assert len(sources) > 0
90
91        self.result.append("ar['%s'] = %s" % (target, sources))
92
93    def ParseCc(self, line):
94        '''Parse a gcc command line.
95
96        Args:
97            line: string, a line of gcc command to parse
98        '''
99        args, unparsed = self.cc_parser.parse_known_args(line.split()[1:])
100
101        sources = self.GetRelativePathForExtensions(unparsed, ['c', 'o'])
102        includes = [self.GetRelativePath(i)
103                    for i in args.includes] if args.includes else []
104        flags = []
105        defines = args.defines if args.defines else []
106        target = self.GetRelativePath(args.target)
107
108        if args.defines:
109            for define in args.defines:
110                flags.append('-D%s' % define)
111
112        flags.extend(i for i in unparsed if i.startswith('-Wno'))
113
114        assert len(sources) > 0
115
116        if args.compile:
117            self.result.append("cc_compile['%s'] = %s" % (target, sources))
118        else:
119            libraries = args.libraries if args.libraries else []
120            if sources[0].endswith('.o'):
121                self.result.append("cc_link['%s'] = %s" % (target, sources))
122            else:
123                self.result.append("cc_compilelink['%s'] = %s" %
124                                   (target, sources))
125            self.result.append("cc_libraries['%s'] = %s" % (target, libraries))
126
127        self.result.append("cc_flags['%s'] = %s" % (target, flags))
128        self.result.append("cc_includes['%s'] = %s" % (target, includes))
129
130    def ParseFile(self, input_path):
131        '''Parses the output of make --dry-run.
132
133        Args:
134            input_text: string, output of make --dry-run
135
136        Returns:
137            string, generated directives in the form
138                    ar['target.a'] = [ 'srcfile1.o, 'srcfile2.o', ... ]
139                    cc_link['target'] = [ 'srcfile1.o', 'srcfile2.o', ... ]
140                    cc_compile['target.o'] = [ 'srcfile1.c' ]
141                    cc_compilelink['target'] = [ 'srcfile1.c' ]
142                along with optional flags for the above directives in the form
143                    cc_flags['target'] = [ '-flag1', '-flag2', ...]
144                    cc_includes['target'] = [ 'includepath1', 'includepath2', ...]
145                    cc_libraries['target'] = [ 'library1', 'library2', ...]
146        '''
147        self.result = []
148        self.dir_stack = []
149
150        entering_directory = re.compile(r"make.*: Entering directory `(.*)'")
151        leaving_directory = re.compile(r"make.*: Leaving directory `(.*)'")
152
153        with open(input_path, 'r') as f:
154            for line in f:
155                line = line.strip()
156
157                m = entering_directory.match(line)
158                if m:
159                    self.dir_stack.append(self.GetRelativePath(m.group(1)))
160                    continue
161
162                m = leaving_directory.match(line)
163                if m:
164                    self.dir_stack.pop()
165                elif line.startswith(AR):
166                    self.ParseAr(line)
167                elif line.startswith(CC):
168                    self.ParseCc(line)
169
170        return self.result
171