xref: /aosp_15_r20/external/ltp/android/tools/make_parser.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
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