blob: a564de36deb2bd8ab721d300db389125f41902a8 [file] [log] [blame]
Caroline Ticebf3d6f02016-10-04 13:58:01 -07001#!/usr/bin/env python2
2"""Verify that a ChromeOS sub-tree was built with a particular compiler"""
3
4from __future__ import print_function
5
6import argparse
7import os
8import sys
9
10from cros_utils import command_executer
11
12COMPILERS = ['gcc', 'clang']
13
14COMPILER_STRINGS = {'gcc': 'GNU C', 'clang': 'clang version'}
15
16ERR_NO_DEBUG_INFO = 1024
17
18
19def UsageError(parser, message):
20 """Output error message and help/usage info."""
21
22 print('ERROR: %s' % message)
23 parser.print_help()
24 sys.exit(0)
25
26
27def CreateTmpDwarfFile(filename, dwarf_file, cmd_executer):
28 """Create temporary dwarfdump file, to be parsed later."""
29
30 cmd = ('readelf --debug-dump=info %s | grep -A5 DW_AT_producer > %s' %
31 (filename, dwarf_file))
32 retval = cmd_executer.RunCommand(cmd, print_to_console=False)
33 return retval
34
35
36def FindAllFiles(root_dir, cmd_executer):
37 """Create a list of all the *.debug and *.dwp files to be checked."""
38
39 file_list = []
40 tmp_list = [
41 os.path.join(dirpath, f)
42 for dirpath, dirnames, files in os.walk(root_dir)
43 for f in fnmatch.filter(files, '*.debug')
44 ]
45 for f in tmp_list:
46 if 'build-id' not in f:
47 file_list.append(f)
48 tmp_list = [
49 os.path.join(dirpath, f)
50 for dirpath, dirnames, files in os.walk(root_dir)
51 for f in fnmatch.filter(files, '*.dwp')
52 ]
53 file_list += tmp_list
54 return file_list
55
56
57def VerifyArgs(compiler, filename, tmp_dir, root_dir, options, parser):
58 """Verify that the option values and combinations are valid."""
59
60 if options.filename and options.all_files:
61 UsageError(parser, 'Cannot use both --file and --all_files.')
62 if options.filename and options.root_dir:
63 UsageError(parser, 'Cannot use both --file and --root_dir.')
64 if options.all_files and not options.root_dir:
65 UsageError(parser, 'Missing --root_dir option.')
66 if options.root_dir and not options.all_files:
67 UsageError(parser, 'Missing --all_files option.')
68 if not options.filename and not options.all_files:
69 UsageError(parser, 'Must specify either --file or --all_files.')
70
71 # Verify that the values specified are valid.
72 if filename:
73 if not os.path.exists(filename):
74 UsageError(parser, 'Cannot find %s' % filename)
75 compiler = options.compiler.lower()
76 if compiler not in COMPILERS:
77 UsageError(parser, '%s is not a valid compiler (gcc or clang).' % compiler)
78 if root_dir and not os.path.exists(root_dir):
79 UsageError(parser, '%s does not exist.' % root_dir)
80 if not os.path.exists(tmp_dir):
81 os.makedirs(tmp_dir)
82
83
84def CheckFile(filename, compiler, tmp_dir, options, cmd_executer):
85 """Verify the information in a single file."""
86
87 print('Checking %s' % filename)
88 # Verify that file contains debug information.
89 cmd = 'readelf -SW %s | grep debug_info' % filename
90 retval = cmd_executer.RunCommand(cmd, print_to_console=False)
91 if retval != 0:
92 print('No debug info in this file. Unable to verify compiler.')
93 # There's no debug info in this file, so skip it.
94 return ERR_NO_DEBUG_INFO
95
96 tmp_name = os.path.basename(filename) + '.dwarf'
97 dwarf_file = os.path.join(tmp_dir, tmp_name)
98 status = CreateTmpDwarfFile(filename, dwarf_file, cmd_executer)
99
100 if status != 0:
101 print('Unable to create dwarf file for %s (status: %d).' %
102 (filename, status))
103 return status
104
105 comp_str = COMPILER_STRINGS[compiler]
106
107 retval = 0
108 with open(dwarf_file, 'r') as in_file:
109 lines = in_file.readlines()
110 looking_for_name = False
111 for line in lines:
112 if 'DW_AT_producer' in line:
113 looking_for_name = False
114 if 'GNU AS' in line:
115 continue
116 if comp_str not in line:
117 looking_for_name = True
118 retval = 1
119 elif looking_for_name:
120 if 'DW_AT_name' in line:
121 words = line.split(':')
122 bad_file = words[-1]
123 print('FAIL: %s was not compiled with %s.' %
124 (bad_file.rstrip(), compiler))
125 looking_for_name = False
126 elif 'DW_TAG_' in line:
127 looking_for_name = False
128
129 if not options.keep_file:
130 os.remove(dwarf_file)
131
132 return retval
133
134
135def Main(argv):
136
137 cmd_executer = command_executer.GetCommandExecuter()
138 parser = argparse.ArgumentParser()
139 parser.add_argument(
140 '--file', dest='filename', help='Name of file to be verified.')
141 parser.add_argument(
142 '--compiler',
143 dest='compiler',
144 required=True,
145 help='Desired compiler (gcc or clang)')
146 parser.add_argument(
147 '--tmp_dir',
148 dest='tmp_dir',
149 help='Directory in which to put dwarf dump file.'
150 ' Defaults to /tmp')
151 parser.add_argument(
152 '--keep_file',
153 dest='keep_file',
154 default=False,
155 action='store_true',
156 help='Do not delete dwarf file when done.')
157 parser.add_argument(
158 '--all_files',
159 dest='all_files',
160 default=False,
161 action='store_true',
162 help='Find and check ALL .debug/.dwp files '
163 'in subtree. Must be used with --root_dir '
164 '(and NOT with --file).')
165 parser.add_argument(
166 '--root_dir',
167 dest='root_dir',
168 help='Root of subtree in which to look for '
169 'files. Must be used with --all_files, and'
170 ' not with --file.')
171
172 options = parser.parse_args(argv)
173
174 compiler = options.compiler
175 filename = None
176 if options.filename:
177 filename = os.path.realpath(os.path.expanduser(options.filename))
178 tmp_dir = '/tmp'
179 if options.tmp_dir:
180 tmp_dir = os.path.realpath(os.path.expanduser(options.tmp_dir))
181 root_dir = None
182 if options.root_dir:
183 root_dir = os.path.realpath(os.path.expanduser(options.root_dir))
184
185 VerifyArgs(compiler, filename, tmp_dir, root_dir, options, parser)
186
187 file_list = []
188 if filename:
189 file_list.append(filename)
190 else:
191 file_list = FindAllFiles(root_dir, cmd_executer)
192
193 bad_files = []
194 unknown_files = []
195 all_pass = True
196 for f in file_list:
197 result = CheckFile(f, compiler, tmp_dir, options, cmd_executer)
198 if result == ERR_NO_DEBUG_INFO:
199 unknown_files.append(f)
200 all_pass = False
201 elif result != 0:
202 bad_files.append(f)
203 all_pass = False
204
205 if all_pass:
206 print('\n\nSUCCESS: All compilation units were compiled with %s.\n' %
207 compiler)
208 return 0
209 else:
210 if len(bad_files) == 0:
211 print(
212 '\n\n*Mostly* SUCCESS: All files that could be checked were compiled '
213 'with %s.' % compiler)
214 print(
215 '\n\nUnable to verify the following files (no debug info in them):\n')
216 for f in unknown_files:
217 print(f)
218 else:
219 print('\n\nFAILED: The following files were not compiled with %s:\n' %
220 compiler)
221 for f in bad_files:
222 print(f)
223 if len(unknown_files) > 0:
224 print(
225 '\n\nUnable to verify the following files (no debug info in them):\n'
226 )
227 for f in unknown_files:
228 print(f)
229 return 1
230
231
232if __name__ == '__main__':
233 sys.exit(Main(sys.argv[1:]))