| 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 |