OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # coding=utf-8 | 2 # coding=utf-8 |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """Runs strace or dtrace on a test and processes the logs to extract the | 7 """Runs strace or dtrace on a test and processes the logs to extract the |
8 dependencies from the source tree. | 8 dependencies from the source tree. |
9 | 9 |
10 Automatically extracts directories where all the files are used to make the | 10 Automatically extracts directories where all the files are used to make the |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
194 | 194 |
195 | 195 |
196 def posix_relpath(path, root): | 196 def posix_relpath(path, root): |
197 """posix.relpath() that keeps trailing slash.""" | 197 """posix.relpath() that keeps trailing slash.""" |
198 out = posixpath.relpath(path, root) | 198 out = posixpath.relpath(path, root) |
199 if path.endswith('/'): | 199 if path.endswith('/'): |
200 out += '/' | 200 out += '/' |
201 return out | 201 return out |
202 | 202 |
203 | 203 |
| 204 def cleanup_path(x): |
| 205 """Cleans up a relative path. Converts any os.path.sep to '/' on Windows.""" |
| 206 if x: |
| 207 x = x.rstrip(os.path.sep).replace(os.path.sep, '/') |
| 208 if x == '.': |
| 209 x = '' |
| 210 if x: |
| 211 x += '/' |
| 212 return x |
| 213 |
| 214 |
204 class Strace(object): | 215 class Strace(object): |
205 """strace implies linux.""" | 216 """strace implies linux.""" |
206 IGNORED = ( | 217 IGNORED = ( |
207 '/bin', | 218 '/bin', |
208 '/dev', | 219 '/dev', |
209 '/etc', | 220 '/etc', |
210 '/lib', | 221 '/lib', |
211 '/proc', | 222 '/proc', |
212 '/sys', | 223 '/sys', |
213 '/tmp', | 224 '/tmp', |
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
372 '_handle_file(%d, %s) -> %s' % (pid, old_filepath, filepath)) | 383 '_handle_file(%d, %s) -> %s' % (pid, old_filepath, filepath)) |
373 else: | 384 else: |
374 logging.debug('_handle_file(%d, %s)' % (pid, filepath)) | 385 logging.debug('_handle_file(%d, %s)' % (pid, filepath)) |
375 if filepath not in self.files and filepath not in self.non_existent: | 386 if filepath not in self.files and filepath not in self.non_existent: |
376 if os.path.isfile(filepath): | 387 if os.path.isfile(filepath): |
377 self.files.add(filepath) | 388 self.files.add(filepath) |
378 else: | 389 else: |
379 self.non_existent.add(filepath) | 390 self.non_existent.add(filepath) |
380 | 391 |
381 @classmethod | 392 @classmethod |
382 def gen_trace(cls, cmd, cwd, logname): | 393 def gen_trace(cls, cmd, cwd, logname, output): |
383 """Runs strace on an executable.""" | 394 """Runs strace on an executable.""" |
384 logging.info('gen_trace(%s, %s, %s)' % (cmd, cwd, logname)) | 395 logging.info('gen_trace(%s, %s, %s, %s)' % (cmd, cwd, logname, output)) |
385 silent = not isEnabledFor(logging.INFO) | |
386 stdout = stderr = None | 396 stdout = stderr = None |
387 if silent: | 397 if output: |
388 stdout = stderr = subprocess.PIPE | 398 stdout = subprocess.PIPE |
| 399 stderr = subprocess.STDOUT |
389 traces = ','.join(cls.Context.traces()) | 400 traces = ','.join(cls.Context.traces()) |
390 trace_cmd = ['strace', '-f', '-e', 'trace=%s' % traces, '-o', logname] | 401 trace_cmd = ['strace', '-f', '-e', 'trace=%s' % traces, '-o', logname] |
391 child = subprocess.Popen( | 402 child = subprocess.Popen( |
392 trace_cmd + cmd, cwd=cwd, stdout=stdout, stderr=stderr) | 403 trace_cmd + cmd, |
393 out, err = child.communicate() | 404 cwd=cwd, |
| 405 stdin=subprocess.PIPE, |
| 406 stdout=stdout, |
| 407 stderr=stderr) |
| 408 out = child.communicate()[0] |
394 # Once it's done, inject a chdir() call to cwd to be able to reconstruct | 409 # Once it's done, inject a chdir() call to cwd to be able to reconstruct |
395 # the full paths. | 410 # the full paths. |
396 # TODO(maruel): cwd should be saved at each process creation, so forks needs | 411 # TODO(maruel): cwd should be saved at each process creation, so forks needs |
397 # to be traced properly. | 412 # to be traced properly. |
398 if os.path.isfile(logname): | 413 if os.path.isfile(logname): |
399 with open(logname) as f: | 414 with open(logname) as f: |
400 content = f.read() | 415 content = f.read() |
401 with open(logname, 'w') as f: | 416 with open(logname, 'w') as f: |
402 pid = content.split(' ', 1)[0] | 417 pid = content.split(' ', 1)[0] |
403 f.write('%s chdir("%s") = 0\n' % (pid, cwd)) | 418 f.write('%s chdir("%s") = 0\n' % (pid, cwd)) |
404 f.write(content) | 419 f.write(content) |
405 | 420 return child.returncode, out |
406 if child.returncode != 0: | |
407 print 'Failure: %d' % child.returncode | |
408 # pylint: disable=E1103 | |
409 if out: | |
410 print ''.join(out.splitlines(True)[-100:]) | |
411 if err: | |
412 print ''.join(err.splitlines(True)[-100:]) | |
413 return child.returncode | |
414 | 421 |
415 @classmethod | 422 @classmethod |
416 def parse_log(cls, filename, blacklist): | 423 def parse_log(cls, filename, blacklist): |
417 """Processes a strace log and returns the files opened and the files that do | 424 """Processes a strace log and returns the files opened and the files that do |
418 not exist. | 425 not exist. |
419 | 426 |
420 It does not track directories. | 427 It does not track directories. |
421 | 428 |
422 Most of the time, files that do not exist are temporary test files that | 429 Most of the time, files that do not exist are temporary test files that |
423 should be put in /tmp instead. See http://crbug.com/116251 | 430 should be put in /tmp instead. See http://crbug.com/116251 |
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
686 if os.path.isfile(filepath): | 693 if os.path.isfile(filepath): |
687 self.files.add(filepath) | 694 self.files.add(filepath) |
688 else: | 695 else: |
689 self.non_existent.add(filepath) | 696 self.non_existent.add(filepath) |
690 | 697 |
691 @staticmethod | 698 @staticmethod |
692 def _handle_ignored(_ppid, pid, function, args, result): | 699 def _handle_ignored(_ppid, pid, function, args, result): |
693 logging.debug('%d %s(%s) = %s' % (pid, function, args, result)) | 700 logging.debug('%d %s(%s) = %s' % (pid, function, args, result)) |
694 | 701 |
695 @classmethod | 702 @classmethod |
696 def gen_trace(cls, cmd, cwd, logname): | 703 def gen_trace(cls, cmd, cwd, logname, output): |
697 """Runs dtrace on an executable.""" | 704 """Runs dtrace on an executable.""" |
698 logging.info('gen_trace(%s, %s, %s)' % (cmd, cwd, logname)) | 705 logging.info('gen_trace(%s, %s, %s, %s)' % (cmd, cwd, logname, output)) |
699 silent = not isEnabledFor(logging.INFO) | |
700 logging.info('Running: %s' % cmd) | 706 logging.info('Running: %s' % cmd) |
701 signal = 'Go!' | 707 signal = 'Go!' |
702 logging.debug('Our pid: %d' % os.getpid()) | 708 logging.debug('Our pid: %d' % os.getpid()) |
703 | 709 |
704 # Part 1: start the child process. | 710 # Part 1: start the child process. |
705 stdout = stderr = None | 711 stdout = stderr = None |
706 if silent: | 712 if output: |
707 stdout = stderr = subprocess.PIPE | 713 stdout = subprocess.PIPE |
| 714 stderr = subprocess.STDOUT |
708 child_cmd = [ | 715 child_cmd = [ |
709 sys.executable, os.path.join(BASE_DIR, 'trace_child_process.py'), | 716 sys.executable, os.path.join(BASE_DIR, 'trace_child_process.py'), |
710 ] | 717 ] |
711 child = subprocess.Popen( | 718 child = subprocess.Popen( |
712 child_cmd + cmd, | 719 child_cmd + cmd, |
713 stdin=subprocess.PIPE, | 720 stdin=subprocess.PIPE, |
714 stdout=stdout, | 721 stdout=stdout, |
715 stderr=stderr, | 722 stderr=stderr, |
716 cwd=cwd) | 723 cwd=cwd) |
717 logging.debug('Started child pid: %d' % child.pid) | 724 logging.debug('Started child pid: %d' % child.pid) |
(...skipping 20 matching lines...) Expand all Loading... |
738 # ready. | 745 # ready. |
739 with open(logname, 'r') as logfile: | 746 with open(logname, 'r') as logfile: |
740 while 'dtrace_BEGIN' not in logfile.readline(): | 747 while 'dtrace_BEGIN' not in logfile.readline(): |
741 if dtrace.poll() is not None: | 748 if dtrace.poll() is not None: |
742 break | 749 break |
743 | 750 |
744 try: | 751 try: |
745 # Part 4: We can now tell our child to go. | 752 # Part 4: We can now tell our child to go. |
746 # TODO(maruel): Another pipe than stdin could be used instead. This would | 753 # TODO(maruel): Another pipe than stdin could be used instead. This would |
747 # be more consistent with the other tracing methods. | 754 # be more consistent with the other tracing methods. |
748 out, err = child.communicate(signal) | 755 out = child.communicate(signal)[0] |
749 | 756 |
750 dtrace.wait() | 757 dtrace.wait() |
751 if dtrace.returncode != 0: | 758 if dtrace.returncode != 0: |
752 print 'dtrace failure: %d' % dtrace.returncode | 759 print 'dtrace failure: %d' % dtrace.returncode |
753 with open(logname) as logfile: | 760 with open(logname) as logfile: |
754 print ''.join(logfile.readlines()[-100:]) | 761 print ''.join(logfile.readlines()[-100:]) |
755 # Find a better way. | 762 # Find a better way. |
756 os.remove(logname) | 763 os.remove(logname) |
757 else: | 764 else: |
758 # Short the log right away to simplify our life. There isn't much | 765 # Short the log right away to simplify our life. There isn't much |
759 # advantage in keeping it out of order. | 766 # advantage in keeping it out of order. |
760 cls._sort_log(logname) | 767 cls._sort_log(logname) |
761 if child.returncode != 0: | |
762 print 'Failure: %d' % child.returncode | |
763 # pylint: disable=E1103 | |
764 if out: | |
765 print ''.join(out.splitlines(True)[-100:]) | |
766 if err: | |
767 print ''.join(err.splitlines(True)[-100:]) | |
768 except KeyboardInterrupt: | 768 except KeyboardInterrupt: |
769 # Still sort when testing. | 769 # Still sort when testing. |
770 cls._sort_log(logname) | 770 cls._sort_log(logname) |
771 raise | 771 raise |
772 | 772 |
773 return dtrace.returncode or child.returncode | 773 return dtrace.returncode or child.returncode, out |
774 | 774 |
775 @classmethod | 775 @classmethod |
776 def parse_log(cls, filename, blacklist): | 776 def parse_log(cls, filename, blacklist): |
777 """Processes a dtrace log and returns the files opened and the files that do | 777 """Processes a dtrace log and returns the files opened and the files that do |
778 not exist. | 778 not exist. |
779 | 779 |
780 It does not track directories. | 780 It does not track directories. |
781 | 781 |
782 Most of the time, files that do not exist are temporary test files that | 782 Most of the time, files that do not exist are temporary test files that |
783 should be put in /tmp instead. See http://crbug.com/116251 | 783 should be put in /tmp instead. See http://crbug.com/116251 |
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1007 | 1007 |
1008 # Also add their short path name equivalents. | 1008 # Also add their short path name equivalents. |
1009 for i in list(self.IGNORED): | 1009 for i in list(self.IGNORED): |
1010 self.IGNORED.add(GetShortPathName(i)) | 1010 self.IGNORED.add(GetShortPathName(i)) |
1011 | 1011 |
1012 # Add this one last since it has no short path name equivalent. | 1012 # Add this one last since it has no short path name equivalent. |
1013 self.IGNORED.add('\\systemroot') | 1013 self.IGNORED.add('\\systemroot') |
1014 self.IGNORED = tuple(sorted(self.IGNORED)) | 1014 self.IGNORED = tuple(sorted(self.IGNORED)) |
1015 | 1015 |
1016 @classmethod | 1016 @classmethod |
1017 def gen_trace(cls, cmd, cwd, logname): | 1017 def gen_trace(cls, cmd, cwd, logname, output): |
1018 logging.info('gen_trace(%s, %s, %s)' % (cmd, cwd, logname)) | 1018 logging.info('gen_trace(%s, %s, %s, %s)' % (cmd, cwd, logname, output)) |
1019 # Use "logman -?" for help. | 1019 # Use "logman -?" for help. |
1020 | 1020 |
1021 etl = logname + '.etl' | 1021 etl = logname + '.etl' |
1022 | 1022 |
1023 silent = not isEnabledFor(logging.INFO) | |
1024 stdout = stderr = None | 1023 stdout = stderr = None |
1025 if silent: | 1024 if output: |
1026 stdout = stderr = subprocess.PIPE | 1025 stdout = subprocess.PIPE |
| 1026 stderr = subprocess.STDOUT |
1027 | 1027 |
1028 # 1. Start the log collection. Requires administrative access. logman.exe is | 1028 # 1. Start the log collection. Requires administrative access. logman.exe is |
1029 # synchronous so no need for a "warmup" call. | 1029 # synchronous so no need for a "warmup" call. |
1030 # 'Windows Kernel Trace' is *localized* so use its GUID instead. | 1030 # 'Windows Kernel Trace' is *localized* so use its GUID instead. |
1031 # The GUID constant name is SystemTraceControlGuid. Lovely. | 1031 # The GUID constant name is SystemTraceControlGuid. Lovely. |
1032 cmd_start = [ | 1032 cmd_start = [ |
1033 'logman.exe', | 1033 'logman.exe', |
1034 'start', | 1034 'start', |
1035 'NT Kernel Logger', | 1035 'NT Kernel Logger', |
1036 '-p', '{9e814aad-3204-11d2-9a82-006008a86939}', | 1036 '-p', '{9e814aad-3204-11d2-9a82-006008a86939}', |
1037 '(process,img,file,fileio)', | 1037 '(process,img,file,fileio)', |
1038 '-o', etl, | 1038 '-o', etl, |
1039 '-ets', # Send directly to kernel | 1039 '-ets', # Send directly to kernel |
1040 ] | 1040 ] |
1041 logging.debug('Running: %s' % cmd_start) | 1041 logging.debug('Running: %s' % cmd_start) |
1042 subprocess.check_call(cmd_start, stdout=stdout, stderr=stderr) | 1042 subprocess.check_call( |
| 1043 cmd_start, |
| 1044 stdin=subprocess.PIPE, |
| 1045 stdout=subprocess.PIPE, |
| 1046 stderr=subprocess.STDOUT) |
1043 | 1047 |
1044 # 2. Run the child process. | 1048 # 2. Run the child process. |
1045 logging.debug('Running: %s' % cmd) | 1049 logging.debug('Running: %s' % cmd) |
1046 try: | 1050 try: |
1047 child = subprocess.Popen(cmd, cwd=cwd, stdout=stdout, stderr=stderr) | 1051 child = subprocess.Popen( |
1048 out, err = child.communicate() | 1052 cmd, cwd=cwd, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) |
| 1053 out = child.communicate()[0] |
1049 finally: | 1054 finally: |
1050 # 3. Stop the log collection. | 1055 # 3. Stop the log collection. |
1051 cmd_stop = [ | 1056 cmd_stop = [ |
1052 'logman.exe', | 1057 'logman.exe', |
1053 'stop', | 1058 'stop', |
1054 'NT Kernel Logger', | 1059 'NT Kernel Logger', |
1055 '-ets', # Send directly to kernel | 1060 '-ets', # Send directly to kernel |
1056 ] | 1061 ] |
1057 logging.debug('Running: %s' % cmd_stop) | 1062 logging.debug('Running: %s' % cmd_stop) |
1058 subprocess.check_call(cmd_stop, stdout=stdout, stderr=stderr) | 1063 subprocess.check_call( |
| 1064 cmd_stop, |
| 1065 stdin=subprocess.PIPE, |
| 1066 stdout=subprocess.PIPE, |
| 1067 stderr=subprocess.STDOUT) |
1059 | 1068 |
1060 # 4. Convert the traces to text representation. | 1069 # 4. Convert the traces to text representation. |
1061 # Use "tracerpt -?" for help. | 1070 # Use "tracerpt -?" for help. |
1062 LOCALE_INVARIANT = 0x7F | 1071 LOCALE_INVARIANT = 0x7F |
1063 windll.kernel32.SetThreadLocale(LOCALE_INVARIANT) | 1072 windll.kernel32.SetThreadLocale(LOCALE_INVARIANT) |
1064 cmd_convert = [ | 1073 cmd_convert = [ |
1065 'tracerpt.exe', | 1074 'tracerpt.exe', |
1066 '-l', etl, | 1075 '-l', etl, |
1067 '-o', logname, | 1076 '-o', logname, |
1068 '-gmt', # Use UTC | 1077 '-gmt', # Use UTC |
(...skipping 10 matching lines...) Expand all Loading... |
1079 cmd_convert.extend(['-of', 'CSV']) | 1088 cmd_convert.extend(['-of', 'CSV']) |
1080 elif logformat == 'csv_utf16': | 1089 elif logformat == 'csv_utf16': |
1081 # This causes it to use UTF-16, which doubles the log size but ensures the | 1090 # This causes it to use UTF-16, which doubles the log size but ensures the |
1082 # log is readable for non-ASCII characters. | 1091 # log is readable for non-ASCII characters. |
1083 cmd_convert.extend(['-of', 'CSV', '-en', 'Unicode']) | 1092 cmd_convert.extend(['-of', 'CSV', '-en', 'Unicode']) |
1084 elif logformat == 'xml': | 1093 elif logformat == 'xml': |
1085 cmd_convert.extend(['-of', 'XML']) | 1094 cmd_convert.extend(['-of', 'XML']) |
1086 else: | 1095 else: |
1087 assert False, logformat | 1096 assert False, logformat |
1088 logging.debug('Running: %s' % cmd_convert) | 1097 logging.debug('Running: %s' % cmd_convert) |
1089 subprocess.check_call(cmd_convert, stdout=stdout, stderr=stderr) | 1098 subprocess.check_call( |
| 1099 cmd_convert, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) |
1090 | 1100 |
1091 if child.returncode != 0: | 1101 return child.returncode, out |
1092 print 'Failure: %d' % child.returncode | |
1093 # pylint: disable=E1103 | |
1094 if out: | |
1095 print ''.join(out.splitlines(True)[-100:]) | |
1096 if err: | |
1097 print ''.join(err.splitlines(True)[-100:]) | |
1098 return child.returncode | |
1099 | 1102 |
1100 @classmethod | 1103 @classmethod |
1101 def parse_log(cls, filename, blacklist): | 1104 def parse_log(cls, filename, blacklist): |
1102 logging.info('parse_log(%s, %s)' % (filename, blacklist)) | 1105 logging.info('parse_log(%s, %s)' % (filename, blacklist)) |
1103 | 1106 |
1104 # Auto-detect the log format | 1107 # Auto-detect the log format |
1105 with open(filename, 'rb') as f: | 1108 with open(filename, 'rb') as f: |
1106 hdr = f.read(2) | 1109 hdr = f.read(2) |
1107 assert len(hdr) == 2 | 1110 assert len(hdr) == 2 |
1108 if hdr == '<E': | 1111 if hdr == '<E': |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1258 elif item in (True, False, None): | 1261 elif item in (True, False, None): |
1259 stdout.write('%s\n' % item) | 1262 stdout.write('%s\n' % item) |
1260 else: | 1263 else: |
1261 assert False, item | 1264 assert False, item |
1262 | 1265 |
1263 stdout.write('{\n') | 1266 stdout.write('{\n') |
1264 loop_dict(' ', variables) | 1267 loop_dict(' ', variables) |
1265 stdout.write('}\n') | 1268 stdout.write('}\n') |
1266 | 1269 |
1267 | 1270 |
| 1271 def get_api(): |
| 1272 flavor = get_flavor() |
| 1273 if flavor == 'linux': |
| 1274 return Strace() |
| 1275 elif flavor == 'mac': |
| 1276 return Dtrace() |
| 1277 elif sys.platform == 'win32': |
| 1278 return LogmanTrace() |
| 1279 else: |
| 1280 print >> sys.stderr, 'Unsupported platform %s' % sys.platform |
| 1281 sys.exit(1) |
| 1282 |
| 1283 |
| 1284 def get_blacklist(api): |
| 1285 """Returns a function to filter unimportant files normally ignored.""" |
| 1286 git_path = os.path.sep + '.git' + os.path.sep |
| 1287 svn_path = os.path.sep + '.svn' + os.path.sep |
| 1288 return lambda f: ( |
| 1289 f.startswith(api.IGNORED) or |
| 1290 f.endswith('.pyc') or |
| 1291 git_path in f or |
| 1292 svn_path in f) |
| 1293 |
| 1294 |
| 1295 def generate_dict(files, cwd_dir, product_dir): |
| 1296 """Converts the list of files into a .isolate dictionary. |
| 1297 |
| 1298 Arguments: |
| 1299 - files: list of files to generate a dictionary out of. |
| 1300 - cwd_dir: directory to base all the files from, relative to root_dir. |
| 1301 - product_dir: directory to replace with <(PRODUCT_DIR), relative to root_dir. |
| 1302 """ |
| 1303 cwd_dir = cleanup_path(cwd_dir) |
| 1304 product_dir = cleanup_path(product_dir) |
| 1305 |
| 1306 def fix(f): |
| 1307 """Bases the file on the most restrictive variable.""" |
| 1308 logging.debug('fix(%s)' % f) |
| 1309 # Important, GYP stores the files with / and not \. |
| 1310 f = f.replace(os.path.sep, '/') |
| 1311 if product_dir and f.startswith(product_dir): |
| 1312 return '<(PRODUCT_DIR)/%s' % f[len(product_dir):] |
| 1313 else: |
| 1314 # cwd_dir is usually the directory containing the gyp file. It may be |
| 1315 # empty if the whole directory containing the gyp file is needed. |
| 1316 return posix_relpath(f, cwd_dir) or './' |
| 1317 |
| 1318 corrected = [fix(f) for f in files] |
| 1319 tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f] |
| 1320 untracked = [f for f in corrected if f.endswith('/') or ' ' in f] |
| 1321 variables = {} |
| 1322 if tracked: |
| 1323 variables[KEY_TRACKED] = tracked |
| 1324 if untracked: |
| 1325 variables[KEY_UNTRACKED] = untracked |
| 1326 return variables |
| 1327 |
| 1328 |
| 1329 def trace(logfile, cmd, cwd, api, output): |
| 1330 """Traces an executable. Returns (returncode, output) from api. |
| 1331 |
| 1332 Arguments: |
| 1333 - logfile: file to write to. |
| 1334 - cmd: command to run. |
| 1335 - cwd: current directory to start the process in. |
| 1336 - api: a tracing api instance. |
| 1337 - output: if True, returns output, otherwise prints it at the console. |
| 1338 """ |
| 1339 cmd = fix_python_path(cmd) |
| 1340 assert os.path.isabs(cmd[0]), cmd[0] |
| 1341 if os.path.isfile(logfile): |
| 1342 os.remove(logfile) |
| 1343 return api.gen_trace(cmd, cwd, logfile, output) |
| 1344 |
| 1345 |
| 1346 def load_trace(logfile, root_dir, api): |
| 1347 """Loads a trace file and returns the processed file lists. |
| 1348 |
| 1349 Arguments: |
| 1350 - logfile: file to load. |
| 1351 - root_dir: root directory to use to determine if a file is relevant to the |
| 1352 trace or not. |
| 1353 - api: a tracing api instance. |
| 1354 """ |
| 1355 files, non_existent = api.parse_log(logfile, get_blacklist(api)) |
| 1356 expected, unexpected = relevant_files( |
| 1357 files, root_dir.rstrip(os.path.sep) + os.path.sep) |
| 1358 # In case the file system is case insensitive. |
| 1359 expected = sorted(set(get_native_path_case(root_dir, f) for f in expected)) |
| 1360 simplified = extract_directories(expected, root_dir) |
| 1361 return files, expected, unexpected, non_existent, simplified |
| 1362 |
| 1363 |
1268 def trace_inputs(logfile, cmd, root_dir, cwd_dir, product_dir, force_trace): | 1364 def trace_inputs(logfile, cmd, root_dir, cwd_dir, product_dir, force_trace): |
1269 """Tries to load the logs if available. If not, trace the test. | 1365 """Tries to load the logs if available. If not, trace the test. |
1270 | 1366 |
1271 Symlinks are not processed at all. | 1367 Symlinks are not processed at all. |
1272 | 1368 |
1273 Arguments: | 1369 Arguments: |
1274 - logfile: Absolute path to the OS-specific trace. | 1370 - logfile: Absolute path to the OS-specific trace. |
1275 - cmd: Command list to run. | 1371 - cmd: Command list to run. |
1276 - root_dir: Base directory where the files we care about live. | 1372 - root_dir: Base directory where the files we care about live. |
1277 - cwd_dir: Cwd to use to start the process, relative to the root_dir | 1373 - cwd_dir: Cwd to use to start the process, relative to the root_dir |
1278 directory. | 1374 directory. |
1279 - product_dir: Directory containing the executables built by the build | 1375 - product_dir: Directory containing the executables built by the build |
1280 process, relative to the root_dir directory. It is used to | 1376 process, relative to the root_dir directory. It is used to |
1281 properly replace paths with <(PRODUCT_DIR) for gyp output. | 1377 properly replace paths with <(PRODUCT_DIR) for gyp output. |
1282 - force_trace: Will force to trace unconditionally even if a trace already | 1378 - force_trace: Will force to trace unconditionally even if a trace already |
1283 exist. | 1379 exist. |
1284 """ | 1380 """ |
1285 logging.debug( | 1381 logging.debug( |
1286 'trace_inputs(%s, %s, %s, %s, %s, %s)' % ( | 1382 'trace_inputs(%s, %s, %s, %s, %s, %s)' % ( |
1287 logfile, cmd, root_dir, cwd_dir, product_dir, force_trace)) | 1383 logfile, cmd, root_dir, cwd_dir, product_dir, force_trace)) |
1288 | 1384 |
| 1385 def print_if(txt): |
| 1386 if cwd_dir is None: |
| 1387 print txt |
| 1388 |
1289 # It is important to have unambiguous path. | 1389 # It is important to have unambiguous path. |
1290 assert os.path.isabs(root_dir), root_dir | 1390 assert os.path.isabs(root_dir), root_dir |
1291 assert os.path.isabs(logfile), logfile | 1391 assert os.path.isabs(logfile), logfile |
1292 assert not cwd_dir or not os.path.isabs(cwd_dir), cwd_dir | 1392 assert not cwd_dir or not os.path.isabs(cwd_dir), cwd_dir |
1293 assert not product_dir or not os.path.isabs(product_dir), product_dir | 1393 assert not product_dir or not os.path.isabs(product_dir), product_dir |
1294 | 1394 |
1295 cmd = fix_python_path(cmd) | 1395 api = get_api() |
1296 assert ( | |
1297 (os.path.isfile(logfile) and not force_trace) or os.path.isabs(cmd[0]) | |
1298 ), cmd[0] | |
1299 | |
1300 # Resolve any symlink | 1396 # Resolve any symlink |
1301 root_dir = os.path.realpath(root_dir) | 1397 root_dir = os.path.realpath(root_dir) |
1302 | |
1303 def print_if(txt): | |
1304 if cwd_dir is None: | |
1305 print(txt) | |
1306 | |
1307 flavor = get_flavor() | |
1308 if flavor == 'linux': | |
1309 api = Strace() | |
1310 elif flavor == 'mac': | |
1311 api = Dtrace() | |
1312 elif sys.platform == 'win32': | |
1313 api = LogmanTrace() | |
1314 else: | |
1315 print >> sys.stderr, 'Unsupported platform %s' % sys.platform | |
1316 return 1 | |
1317 | |
1318 if not os.path.isfile(logfile) or force_trace: | 1398 if not os.path.isfile(logfile) or force_trace: |
1319 if os.path.isfile(logfile): | |
1320 os.remove(logfile) | |
1321 print_if('Tracing... %s' % cmd) | 1399 print_if('Tracing... %s' % cmd) |
1322 cwd = root_dir | |
1323 # Use the proper relative directory. | 1400 # Use the proper relative directory. |
1324 if cwd_dir: | 1401 cwd = root_dir if not cwd_dir else os.path.join(root_dir, cwd_dir) |
1325 cwd = os.path.join(cwd, cwd_dir) | 1402 silent = not isEnabledFor(logging.WARNING) |
1326 returncode = api.gen_trace(cmd, cwd, logfile) | 1403 returncode, _ = trace(logfile, cmd, cwd, api, silent) |
1327 if returncode and not force_trace: | 1404 if returncode and not force_trace: |
1328 return returncode | 1405 return returncode |
1329 | 1406 |
1330 git_path = os.path.sep + '.git' + os.path.sep | |
1331 svn_path = os.path.sep + '.svn' + os.path.sep | |
1332 def blacklist(f): | |
1333 """Strips ignored paths.""" | |
1334 return ( | |
1335 f.startswith(api.IGNORED) or | |
1336 f.endswith('.pyc') or | |
1337 git_path in f or | |
1338 svn_path in f) | |
1339 | |
1340 print_if('Loading traces... %s' % logfile) | 1407 print_if('Loading traces... %s' % logfile) |
1341 files, non_existent = api.parse_log(logfile, blacklist) | 1408 files, expected, unexpected, non_existent, simplified = load_trace( |
| 1409 logfile, root_dir, api) |
1342 | 1410 |
1343 print_if('Total: %d' % len(files)) | 1411 print_if('Total: %d' % len(files)) |
1344 print_if('Non existent: %d' % len(non_existent)) | 1412 print_if('Non existent: %d' % len(non_existent)) |
1345 for f in non_existent: | 1413 for f in non_existent: |
1346 print_if(' %s' % f) | 1414 print_if(' %s' % f) |
1347 | |
1348 expected, unexpected = relevant_files( | |
1349 files, root_dir.rstrip(os.path.sep) + os.path.sep) | |
1350 if unexpected: | 1415 if unexpected: |
1351 print_if('Unexpected: %d' % len(unexpected)) | 1416 print_if('Unexpected: %d' % len(unexpected)) |
1352 for f in unexpected: | 1417 for f in unexpected: |
1353 print_if(' %s' % f) | 1418 print_if(' %s' % f) |
1354 | |
1355 # In case the file system is case insensitive. | |
1356 expected = sorted(set(get_native_path_case(root_dir, f) for f in expected)) | |
1357 | |
1358 simplified = extract_directories(expected, root_dir) | |
1359 print_if('Interesting: %d reduced to %d' % (len(expected), len(simplified))) | 1419 print_if('Interesting: %d reduced to %d' % (len(expected), len(simplified))) |
1360 for f in simplified: | 1420 for f in simplified: |
1361 print_if(' %s' % f) | 1421 print_if(' %s' % f) |
1362 | 1422 |
1363 if cwd_dir is not None: | 1423 if cwd_dir is not None: |
1364 def cleanuppath(x): | |
1365 """Cleans up a relative path. Converts any os.path.sep to '/' on Windows. | |
1366 """ | |
1367 if x: | |
1368 x = x.rstrip(os.path.sep).replace(os.path.sep, '/') | |
1369 if x == '.': | |
1370 x = '' | |
1371 if x: | |
1372 x += '/' | |
1373 return x | |
1374 | |
1375 # Both are relative directories to root_dir. | |
1376 cwd_dir = cleanuppath(cwd_dir) | |
1377 product_dir = cleanuppath(product_dir) | |
1378 | |
1379 def fix(f): | |
1380 """Bases the file on the most restrictive variable.""" | |
1381 logging.debug('fix(%s)' % f) | |
1382 # Important, GYP stores the files with / and not \. | |
1383 f = f.replace(os.path.sep, '/') | |
1384 | |
1385 if product_dir and f.startswith(product_dir): | |
1386 return '<(PRODUCT_DIR)/%s' % f[len(product_dir):] | |
1387 else: | |
1388 # cwd_dir is usually the directory containing the gyp file. It may be | |
1389 # empty if the whole directory containing the gyp file is needed. | |
1390 return posix_relpath(f, cwd_dir) or './' | |
1391 | |
1392 corrected = [fix(f) for f in simplified] | |
1393 tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f] | |
1394 untracked = [f for f in corrected if f.endswith('/') or ' ' in f] | |
1395 variables = {} | |
1396 if tracked: | |
1397 variables[KEY_TRACKED] = tracked | |
1398 if untracked: | |
1399 variables[KEY_UNTRACKED] = untracked | |
1400 value = { | 1424 value = { |
1401 'conditions': [ | 1425 'conditions': [ |
1402 ['OS=="%s"' % flavor, { | 1426 ['OS=="%s"' % get_flavor(), { |
1403 'variables': variables, | 1427 'variables': generate_dict(simplified, cwd_dir, product_dir), |
1404 }], | 1428 }], |
1405 ], | 1429 ], |
1406 } | 1430 } |
1407 pretty_print(value, sys.stdout) | 1431 pretty_print(value, sys.stdout) |
1408 return 0 | 1432 return 0 |
1409 | 1433 |
1410 | 1434 |
1411 def main(): | 1435 def main(): |
1412 parser = optparse.OptionParser( | 1436 parser = optparse.OptionParser( |
1413 usage='%prog <options> [cmd line...]') | 1437 usage='%prog <options> [cmd line...]') |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1454 os.path.abspath(options.log), | 1478 os.path.abspath(options.log), |
1455 args, | 1479 args, |
1456 options.root_dir, | 1480 options.root_dir, |
1457 options.cwd, | 1481 options.cwd, |
1458 options.product_dir, | 1482 options.product_dir, |
1459 options.force) | 1483 options.force) |
1460 | 1484 |
1461 | 1485 |
1462 if __name__ == '__main__': | 1486 if __name__ == '__main__': |
1463 sys.exit(main()) | 1487 sys.exit(main()) |
OLD | NEW |