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 git_path = os.path.sep + '.git' + os.path.sep | |
1286 svn_path = os.path.sep + '.svn' + os.path.sep | |
1287 return lambda f: ( | |
1288 f.startswith(api.IGNORED) or | |
1289 f.endswith('.pyc') or | |
1290 git_path in f or | |
1291 svn_path in f) | |
1292 | |
1293 | |
1294 def generate_dict(files, cwd_dir, product_dir): | |
1295 """Converts the list of files into a .isolate dictionary. | |
1296 | |
1297 Both cwd_dir and product_dir are relative directories to root_dir. | |
1298 """ | |
1299 cwd_dir = cleanup_path(cwd_dir) | |
1300 product_dir = cleanup_path(product_dir) | |
1301 | |
1302 def fix(f): | |
1303 """Bases the file on the most restrictive variable.""" | |
1304 logging.debug('fix(%s)' % f) | |
1305 # Important, GYP stores the files with / and not \. | |
1306 f = f.replace(os.path.sep, '/') | |
1307 if product_dir and f.startswith(product_dir): | |
1308 return '<(PRODUCT_DIR)/%s' % f[len(product_dir):] | |
1309 else: | |
1310 # cwd_dir is usually the directory containing the gyp file. It may be | |
1311 # empty if the whole directory containing the gyp file is needed. | |
1312 return posix_relpath(f, cwd_dir) or './' | |
1313 | |
1314 corrected = [fix(f) for f in files] | |
1315 tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f] | |
1316 untracked = [f for f in corrected if f.endswith('/') or ' ' in f] | |
1317 variables = {} | |
1318 if tracked: | |
1319 variables[KEY_TRACKED] = tracked | |
1320 if untracked: | |
1321 variables[KEY_UNTRACKED] = untracked | |
1322 return variables | |
1323 | |
1324 | |
1325 def trace(logfile, cmd, cwd, api, output): | |
1326 """Traces an executable. Returns returncode, output.""" | |
1327 cmd = fix_python_path(cmd) | |
1328 assert os.path.isabs(cmd[0]), cmd[0] | |
1329 if os.path.isfile(logfile): | |
1330 os.remove(logfile) | |
1331 return api.gen_trace(cmd, cwd, logfile, output) | |
1332 | |
1333 | |
1334 def load_trace(logfile, root_dir, api): | |
Roger Tawa OOO till Jul 10th
2012/05/11 14:17:07
Please add docs for this function, and arg docs fo
| |
1335 files, non_existent = api.parse_log(logfile, get_blacklist(api)) | |
1336 expected, unexpected = relevant_files( | |
1337 files, root_dir.rstrip(os.path.sep) + os.path.sep) | |
1338 # In case the file system is case insensitive. | |
1339 expected = sorted(set(get_native_path_case(root_dir, f) for f in expected)) | |
1340 simplified = extract_directories(expected, root_dir) | |
1341 return files, expected, unexpected, non_existent, simplified | |
1342 | |
1343 | |
1268 def trace_inputs(logfile, cmd, root_dir, cwd_dir, product_dir, force_trace): | 1344 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. | 1345 """Tries to load the logs if available. If not, trace the test. |
1270 | 1346 |
1271 Symlinks are not processed at all. | 1347 Symlinks are not processed at all. |
1272 | 1348 |
1273 Arguments: | 1349 Arguments: |
1274 - logfile: Absolute path to the OS-specific trace. | 1350 - logfile: Absolute path to the OS-specific trace. |
1275 - cmd: Command list to run. | 1351 - cmd: Command list to run. |
1276 - root_dir: Base directory where the files we care about live. | 1352 - 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 | 1353 - cwd_dir: Cwd to use to start the process, relative to the root_dir |
1278 directory. | 1354 directory. |
1279 - product_dir: Directory containing the executables built by the build | 1355 - product_dir: Directory containing the executables built by the build |
1280 process, relative to the root_dir directory. It is used to | 1356 process, relative to the root_dir directory. It is used to |
1281 properly replace paths with <(PRODUCT_DIR) for gyp output. | 1357 properly replace paths with <(PRODUCT_DIR) for gyp output. |
1282 - force_trace: Will force to trace unconditionally even if a trace already | 1358 - force_trace: Will force to trace unconditionally even if a trace already |
1283 exist. | 1359 exist. |
1284 """ | 1360 """ |
1285 logging.debug( | 1361 logging.debug( |
1286 'trace_inputs(%s, %s, %s, %s, %s, %s)' % ( | 1362 'trace_inputs(%s, %s, %s, %s, %s, %s)' % ( |
1287 logfile, cmd, root_dir, cwd_dir, product_dir, force_trace)) | 1363 logfile, cmd, root_dir, cwd_dir, product_dir, force_trace)) |
1288 | 1364 |
1365 def print_if(txt): | |
1366 if cwd_dir is None: | |
1367 print txt | |
1368 | |
1289 # It is important to have unambiguous path. | 1369 # It is important to have unambiguous path. |
1290 assert os.path.isabs(root_dir), root_dir | 1370 assert os.path.isabs(root_dir), root_dir |
1291 assert os.path.isabs(logfile), logfile | 1371 assert os.path.isabs(logfile), logfile |
1292 assert not cwd_dir or not os.path.isabs(cwd_dir), cwd_dir | 1372 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 | 1373 assert not product_dir or not os.path.isabs(product_dir), product_dir |
1294 | 1374 |
1295 cmd = fix_python_path(cmd) | 1375 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 | 1376 # Resolve any symlink |
1301 root_dir = os.path.realpath(root_dir) | 1377 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: | 1378 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) | 1379 print_if('Tracing... %s' % cmd) |
1322 cwd = root_dir | |
1323 # Use the proper relative directory. | 1380 # Use the proper relative directory. |
1324 if cwd_dir: | 1381 cwd = root_dir if not cwd_dir else os.path.join(root_dir, cwd_dir) |
1325 cwd = os.path.join(cwd, cwd_dir) | 1382 silent = not isEnabledFor(logging.WARNING) |
1326 returncode = api.gen_trace(cmd, cwd, logfile) | 1383 returncode, _ = trace(logfile, cmd, cwd, api, silent) |
1327 if returncode and not force_trace: | 1384 if returncode and not force_trace: |
1328 return returncode | 1385 return returncode |
1329 | 1386 |
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) | 1387 print_if('Loading traces... %s' % logfile) |
1341 files, non_existent = api.parse_log(logfile, blacklist) | 1388 files, expected, unexpected, non_existent, simplified = load_trace( |
1389 logfile, root_dir, api) | |
1342 | 1390 |
1343 print_if('Total: %d' % len(files)) | 1391 print_if('Total: %d' % len(files)) |
1344 print_if('Non existent: %d' % len(non_existent)) | 1392 print_if('Non existent: %d' % len(non_existent)) |
1345 for f in non_existent: | 1393 for f in non_existent: |
1346 print_if(' %s' % f) | 1394 print_if(' %s' % f) |
1347 | |
1348 expected, unexpected = relevant_files( | |
1349 files, root_dir.rstrip(os.path.sep) + os.path.sep) | |
1350 if unexpected: | 1395 if unexpected: |
1351 print_if('Unexpected: %d' % len(unexpected)) | 1396 print_if('Unexpected: %d' % len(unexpected)) |
1352 for f in unexpected: | 1397 for f in unexpected: |
1353 print_if(' %s' % f) | 1398 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))) | 1399 print_if('Interesting: %d reduced to %d' % (len(expected), len(simplified))) |
1360 for f in simplified: | 1400 for f in simplified: |
1361 print_if(' %s' % f) | 1401 print_if(' %s' % f) |
1362 | 1402 |
1363 if cwd_dir is not None: | 1403 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 = { | 1404 value = { |
1401 'conditions': [ | 1405 'conditions': [ |
1402 ['OS=="%s"' % flavor, { | 1406 ['OS=="%s"' % get_flavor(), { |
1403 'variables': variables, | 1407 'variables': generate_dict(simplified, cwd_dir, product_dir), |
1404 }], | 1408 }], |
1405 ], | 1409 ], |
1406 } | 1410 } |
1407 pretty_print(value, sys.stdout) | 1411 pretty_print(value, sys.stdout) |
1408 return 0 | 1412 return 0 |
1409 | 1413 |
1410 | 1414 |
1411 def main(): | 1415 def main(): |
1412 parser = optparse.OptionParser( | 1416 parser = optparse.OptionParser( |
1413 usage='%prog <options> [cmd line...]') | 1417 usage='%prog <options> [cmd line...]') |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1454 os.path.abspath(options.log), | 1458 os.path.abspath(options.log), |
1455 args, | 1459 args, |
1456 options.root_dir, | 1460 options.root_dir, |
1457 options.cwd, | 1461 options.cwd, |
1458 options.product_dir, | 1462 options.product_dir, |
1459 options.force) | 1463 options.force) |
1460 | 1464 |
1461 | 1465 |
1462 if __name__ == '__main__': | 1466 if __name__ == '__main__': |
1463 sys.exit(main()) | 1467 sys.exit(main()) |
OLD | NEW |