Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(134)

Side by Side Diff: tools/isolate/trace_inputs.py

Issue 10091011: Added function to get native path case on Windows and OSX. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase against 10080013 Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/isolate/isolate_test.py ('k') | tools/isolate/trace_inputs_smoke_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
11 dependencies list more compact. 11 dependencies list more compact.
12 """ 12 """
13 13
14 import codecs 14 import codecs
15 import csv 15 import csv
16 import logging 16 import logging
17 import optparse 17 import optparse
18 import os 18 import os
19 import posixpath 19 import posixpath
20 import re 20 import re
21 import subprocess 21 import subprocess
22 import sys 22 import sys
23 23
24 ## OS-specific imports
25
26 if sys.platform == 'win32':
27 from ctypes.wintypes import create_unicode_buffer
28 from ctypes.wintypes import windll, FormatError # pylint: disable=E0611
29 from ctypes.wintypes import GetLastError # pylint: disable=E0611
30 elif sys.platform == 'darwin':
31 import Carbon.File # pylint: disable=F0401
32
24 33
25 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 34 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
26 ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR)) 35 ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR))
27 36
28 KEY_TRACKED = 'isolate_dependency_tracked' 37 KEY_TRACKED = 'isolate_dependency_tracked'
29 KEY_UNTRACKED = 'isolate_dependency_untracked' 38 KEY_UNTRACKED = 'isolate_dependency_untracked'
30 39
31 40
41 ## OS-specific functions
42
32 if sys.platform == 'win32': 43 if sys.platform == 'win32':
33 from ctypes.wintypes import create_unicode_buffer
34 from ctypes.wintypes import windll, FormatError # pylint: disable=E0611
35 from ctypes.wintypes import GetLastError # pylint: disable=E0611
36
37
38 def QueryDosDevice(drive_letter): 44 def QueryDosDevice(drive_letter):
39 """Returns the Windows 'native' path for a DOS drive letter.""" 45 """Returns the Windows 'native' path for a DOS drive letter."""
40 assert re.match(r'^[a-zA-Z]:$', drive_letter), drive_letter 46 assert re.match(r'^[a-zA-Z]:$', drive_letter), drive_letter
41 # Guesswork. QueryDosDeviceW never returns the required number of bytes. 47 # Guesswork. QueryDosDeviceW never returns the required number of bytes.
42 chars = 1024 48 chars = 1024
43 drive_letter = unicode(drive_letter) 49 drive_letter = unicode(drive_letter)
44 p = create_unicode_buffer(chars) 50 p = create_unicode_buffer(chars)
45 if 0 == windll.kernel32.QueryDosDeviceW(drive_letter, p, chars): 51 if 0 == windll.kernel32.QueryDosDeviceW(drive_letter, p, chars):
46 err = GetLastError() 52 err = GetLastError()
47 if err: 53 if err:
48 # pylint: disable=E0602 54 # pylint: disable=E0602
49 raise WindowsError( 55 raise WindowsError(
50 err, 56 err,
51 'QueryDosDevice(%s): %s (%d)' % ( 57 'QueryDosDevice(%s): %s (%d)' % (
52 str(drive_letter), FormatError(err), err)) 58 str(drive_letter), FormatError(err), err))
53 return p.value 59 return p.value
54 60
55 61
56 def GetShortPathName(long_path): 62 def GetShortPathName(long_path):
57 """Returns the Windows short path equivalent for a 'long' path.""" 63 """Returns the Windows short path equivalent for a 'long' path."""
58 long_path = unicode(long_path) 64 long_path = unicode(long_path)
65 # Adds '\\\\?\\' when given an absolute path so the MAX_PATH (260) limit is
66 # not enforced.
67 if os.path.isabs(long_path) and not long_path.startswith('\\\\?\\'):
68 long_path = '\\\\?\\' + long_path
59 chars = windll.kernel32.GetShortPathNameW(long_path, None, 0) 69 chars = windll.kernel32.GetShortPathNameW(long_path, None, 0)
60 if chars: 70 if chars:
61 p = create_unicode_buffer(chars) 71 p = create_unicode_buffer(chars)
62 if windll.kernel32.GetShortPathNameW(long_path, p, chars): 72 if windll.kernel32.GetShortPathNameW(long_path, p, chars):
63 return p.value 73 return p.value
64 74
65 err = GetLastError() 75 err = GetLastError()
66 if err: 76 if err:
67 # pylint: disable=E0602 77 # pylint: disable=E0602
68 raise WindowsError( 78 raise WindowsError(
69 err, 79 err,
70 'GetShortPathName(%s): %s (%d)' % ( 80 'GetShortPathName(%s): %s (%d)' % (
71 str(long_path), FormatError(err), err)) 81 str(long_path), FormatError(err), err))
72 82
73 83
84 def GetLongPathName(short_path):
85 """Returns the Windows long path equivalent for a 'short' path."""
86 short_path = unicode(short_path)
87 # Adds '\\\\?\\' when given an absolute path so the MAX_PATH (260) limit is
88 # not enforced.
89 if os.path.isabs(short_path) and not short_path.startswith('\\\\?\\'):
90 short_path = '\\\\?\\' + short_path
91 chars = windll.kernel32.GetLongPathNameW(short_path, None, 0)
92 if chars:
93 p = create_unicode_buffer(chars)
94 if windll.kernel32.GetLongPathNameW(short_path, p, chars):
95 return p.value
96
97 err = GetLastError()
98 if err:
99 # pylint: disable=E0602
100 raise WindowsError(
101 err,
102 'GetLongPathName(%s): %s (%d)' % (
103 str(short_path), FormatError(err), err))
104
105
74 def get_current_encoding(): 106 def get_current_encoding():
75 """Returns the 'ANSI' code page associated to the process.""" 107 """Returns the 'ANSI' code page associated to the process."""
76 return 'cp%d' % int(windll.kernel32.GetACP()) 108 return 'cp%d' % int(windll.kernel32.GetACP())
77 109
78 110
79 class DosDriveMap(object): 111 class DosDriveMap(object):
80 """Maps \Device\HarddiskVolumeN to N: on Windows.""" 112 """Maps \Device\HarddiskVolumeN to N: on Windows."""
81 # Keep one global cache. 113 # Keep one global cache.
82 _MAPPING = {} 114 _MAPPING = {}
83 115
84 def __init__(self): 116 def __init__(self):
85 if not self._MAPPING: 117 if not self._MAPPING:
118 # This is related to UNC resolver on windows. Ignore that.
119 self._MAPPING['\\Device\\Mup'] = None
120
86 for letter in (chr(l) for l in xrange(ord('C'), ord('Z')+1)): 121 for letter in (chr(l) for l in xrange(ord('C'), ord('Z')+1)):
87 try: 122 try:
88 letter = '%s:' % letter 123 letter = '%s:' % letter
89 mapped = QueryDosDevice(letter) 124 mapped = QueryDosDevice(letter)
90 # It can happen. Assert until we see it happens in the wild. In 125 # It can happen. Assert until we see it happens in the wild. In
91 # practice, prefer the lower drive letter. 126 # practice, prefer the lower drive letter.
92 assert mapped not in self._MAPPING 127 assert mapped not in self._MAPPING
93 if mapped not in self._MAPPING: 128 if mapped not in self._MAPPING:
94 self._MAPPING[mapped] = letter 129 self._MAPPING[mapped] = letter
95 except WindowsError: # pylint: disable=E0602 130 except WindowsError: # pylint: disable=E0602
96 pass 131 pass
97 132
98 def to_dos(self, path): 133 def to_dos(self, path):
99 """Converts a native NT path to DOS path.""" 134 """Converts a native NT path to DOS path."""
100 m = re.match(r'(^\\Device\\[a-zA-Z0-9]+)(\\.*)?$', path) 135 m = re.match(r'(^\\Device\\[a-zA-Z0-9]+)(\\.*)?$', path)
101 if not m or m.group(1) not in self._MAPPING: 136 assert m, path
102 assert False, path 137 assert m.group(1) in self._MAPPING, (path, self._MAPPING)
103 drive = self._MAPPING[m.group(1)] 138 drive = self._MAPPING[m.group(1)]
104 if not m.group(2): 139 if not drive or not m.group(2):
105 return drive 140 return drive
106 return drive + m.group(2) 141 return drive + m.group(2)
107 142
108 143
144 def get_native_path_case(root, relative_path):
145 """Returns the native path case."""
146 if sys.platform == 'win32':
147 # Windows used to have an option to turn on case sensitivity on non Win32
148 # subsystem but that's out of scope here and isn't supported anymore.
149 # First process root.
150 if root:
151 root = GetLongPathName(GetShortPathName(root)) + os.path.sep
152 path = os.path.join(root, relative_path) if root else relative_path
153 # Go figure why GetShortPathName() is needed.
154 return GetLongPathName(GetShortPathName(path))[len(root):]
155 elif sys.platform == 'darwin':
156 # Technically, it's only HFS+ on OSX that is case insensitive. It's
157 # the default setting on HFS+ but can be changed.
158 root_ref, _ = Carbon.File.FSPathMakeRef(root)
159 rel_ref, _ = Carbon.File.FSPathMakeRef(os.path.join(root, relative_path))
160 return rel_ref.FSRefMakePath()[len(root_ref.FSRefMakePath())+1:]
161 else:
162 # Give up on cygwin, as GetLongPathName() can't be called.
163 return relative_path
164
165
109 def get_flavor(): 166 def get_flavor():
110 """Returns the system default flavor. Copied from gyp/pylib/gyp/common.py.""" 167 """Returns the system default flavor. Copied from gyp/pylib/gyp/common.py."""
111 flavors = { 168 flavors = {
112 'cygwin': 'win', 169 'cygwin': 'win',
113 'win32': 'win', 170 'win32': 'win',
114 'darwin': 'mac', 171 'darwin': 'mac',
115 'sunos5': 'solaris', 172 'sunos5': 'solaris',
116 'freebsd7': 'freebsd', 173 'freebsd7': 'freebsd',
117 'freebsd8': 'freebsd', 174 'freebsd8': 'freebsd',
118 } 175 }
(...skipping 711 matching lines...) Expand 10 before | Expand all | Expand 10 after
830 if handler: 887 if handler:
831 handler(line) 888 handler(line)
832 else: 889 else:
833 assert False, '%s_%s' % (line[self.EVENT_NAME], line[self.TYPE]) 890 assert False, '%s_%s' % (line[self.EVENT_NAME], line[self.TYPE])
834 891
835 def handle_EventTrace_Any(self, line): 892 def handle_EventTrace_Any(self, line):
836 pass 893 pass
837 894
838 def handle_FileIo_Create(self, line): 895 def handle_FileIo_Create(self, line):
839 m = re.match(r'^\"(.+)\"$', line[self.FILE_PATH]) 896 m = re.match(r'^\"(.+)\"$', line[self.FILE_PATH])
840 self._handle_file(self._drive_map.to_dos(m.group(1)).lower()) 897 self._handle_file(self._drive_map.to_dos(m.group(1)))
841 898
842 def handle_FileIo_Rename(self, line): 899 def handle_FileIo_Rename(self, line):
843 # TODO(maruel): Handle? 900 # TODO(maruel): Handle?
844 pass 901 pass
845 902
846 def handle_FileIo_Any(self, line): 903 def handle_FileIo_Any(self, line):
847 pass 904 pass
848 905
849 def handle_Image_DCStart(self, line): 906 def handle_Image_DCStart(self, line):
850 # TODO(maruel): Handle? 907 # TODO(maruel): Handle?
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
906 filename in self.non_existent): 963 filename in self.non_existent):
907 return 964 return
908 logging.debug('_handle_file(%s)' % filename) 965 logging.debug('_handle_file(%s)' % filename)
909 if os.path.isfile(filename): 966 if os.path.isfile(filename):
910 self.files.add(filename) 967 self.files.add(filename)
911 else: 968 else:
912 self.non_existent.add(filename) 969 self.non_existent.add(filename)
913 970
914 def __init__(self): 971 def __init__(self):
915 # Most ignores need to be determined at runtime. 972 # Most ignores need to be determined at runtime.
916 self.IGNORED = set([os.path.dirname(sys.executable).lower()]) 973 self.IGNORED = set([os.path.dirname(sys.executable)])
917 # Add many directories from environment variables. 974 # Add many directories from environment variables.
918 vars_to_ignore = ( 975 vars_to_ignore = (
919 'APPDATA', 976 'APPDATA',
920 'LOCALAPPDATA', 977 'LOCALAPPDATA',
921 'ProgramData', 978 'ProgramData',
922 'ProgramFiles', 979 'ProgramFiles',
923 'ProgramFiles(x86)', 980 'ProgramFiles(x86)',
924 'ProgramW6432', 981 'ProgramW6432',
925 'SystemRoot', 982 'SystemRoot',
926 'TEMP', 983 'TEMP',
927 'TMP', 984 'TMP',
928 ) 985 )
929 for i in vars_to_ignore: 986 for i in vars_to_ignore:
930 if os.environ.get(i): 987 if os.environ.get(i):
931 self.IGNORED.add(os.environ[i].lower()) 988 self.IGNORED.add(os.environ[i])
932 989
933 # Also add their short path name equivalents. 990 # Also add their short path name equivalents.
934 for i in list(self.IGNORED): 991 for i in list(self.IGNORED):
935 self.IGNORED.add(GetShortPathName(i).lower()) 992 self.IGNORED.add(GetShortPathName(i))
936 993
937 # Add this one last since it has no short path name equivalent. 994 # Add this one last since it has no short path name equivalent.
938 self.IGNORED.add('\\systemroot') 995 self.IGNORED.add('\\systemroot')
939 self.IGNORED = tuple(sorted(self.IGNORED)) 996 self.IGNORED = tuple(sorted(self.IGNORED))
940 997
941 @classmethod 998 @classmethod
942 def gen_trace(cls, cmd, cwd, logname): 999 def gen_trace(cls, cmd, cwd, logname):
943 logging.info('gen_trace(%s, %s, %s)' % (cmd, cwd, logname)) 1000 logging.info('gen_trace(%s, %s, %s)' % (cmd, cwd, logname))
944 # Use "logman -?" for help. 1001 # Use "logman -?" for help.
945 1002
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after
1218 assert not product_dir or not os.path.isabs(product_dir), product_dir 1275 assert not product_dir or not os.path.isabs(product_dir), product_dir
1219 1276
1220 cmd = fix_python_path(cmd) 1277 cmd = fix_python_path(cmd)
1221 assert ( 1278 assert (
1222 (os.path.isfile(logfile) and not force_trace) or os.path.isabs(cmd[0]) 1279 (os.path.isfile(logfile) and not force_trace) or os.path.isabs(cmd[0])
1223 ), cmd[0] 1280 ), cmd[0]
1224 1281
1225 # Resolve any symlink 1282 # Resolve any symlink
1226 root_dir = os.path.realpath(root_dir) 1283 root_dir = os.path.realpath(root_dir)
1227 1284
1228 if sys.platform == 'win32':
1229 # Help ourself and lowercase all the paths.
1230 # TODO(maruel): handle short path names by converting them to long path name
1231 # as needed.
1232 root_dir = root_dir.lower()
1233 if cwd_dir:
1234 cwd_dir = cwd_dir.lower()
1235 if product_dir:
1236 product_dir = product_dir.lower()
1237
1238 def print_if(txt): 1285 def print_if(txt):
1239 if cwd_dir is None: 1286 if cwd_dir is None:
1240 print(txt) 1287 print(txt)
1241 1288
1242 flavor = get_flavor() 1289 flavor = get_flavor()
1243 if flavor == 'linux': 1290 if flavor == 'linux':
1244 api = Strace() 1291 api = Strace()
1245 elif flavor == 'mac': 1292 elif flavor == 'mac':
1246 api = Dtrace() 1293 api = Dtrace()
1247 elif sys.platform == 'win32': 1294 elif sys.platform == 'win32':
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
1280 for f in non_existent: 1327 for f in non_existent:
1281 print_if(' %s' % f) 1328 print_if(' %s' % f)
1282 1329
1283 expected, unexpected = relevant_files( 1330 expected, unexpected = relevant_files(
1284 files, root_dir.rstrip(os.path.sep) + os.path.sep) 1331 files, root_dir.rstrip(os.path.sep) + os.path.sep)
1285 if unexpected: 1332 if unexpected:
1286 print_if('Unexpected: %d' % len(unexpected)) 1333 print_if('Unexpected: %d' % len(unexpected))
1287 for f in unexpected: 1334 for f in unexpected:
1288 print_if(' %s' % f) 1335 print_if(' %s' % f)
1289 1336
1337 # In case the file system is case insensitive.
1338 expected = sorted(set(get_native_path_case(root_dir, f) for f in expected))
1339
1290 simplified = extract_directories(expected, root_dir) 1340 simplified = extract_directories(expected, root_dir)
1291 print_if('Interesting: %d reduced to %d' % (len(expected), len(simplified))) 1341 print_if('Interesting: %d reduced to %d' % (len(expected), len(simplified)))
1292 for f in simplified: 1342 for f in simplified:
1293 print_if(' %s' % f) 1343 print_if(' %s' % f)
1294 1344
1295 if cwd_dir is not None: 1345 if cwd_dir is not None:
1296 def cleanuppath(x): 1346 def cleanuppath(x):
1297 """Cleans up a relative path. Converts any os.path.sep to '/' on Windows. 1347 """Cleans up a relative path. Converts any os.path.sep to '/' on Windows.
1298 """ 1348 """
1299 if x: 1349 if x:
1300 x = x.rstrip(os.path.sep).replace(os.path.sep, '/') 1350 x = x.rstrip(os.path.sep).replace(os.path.sep, '/')
1301 if x == '.': 1351 if x == '.':
1302 x = '' 1352 x = ''
1303 if x: 1353 if x:
1304 x += '/' 1354 x += '/'
1305 return x 1355 return x
1306 1356
1307 # Both are relative directories to root_dir. 1357 # Both are relative directories to root_dir.
1308 cwd_dir = cleanuppath(cwd_dir) 1358 cwd_dir = cleanuppath(cwd_dir)
1309 product_dir = cleanuppath(product_dir) 1359 product_dir = cleanuppath(product_dir)
1310 1360
1311 def fix(f): 1361 def fix(f):
1312 """Bases the file on the most restrictive variable.""" 1362 """Bases the file on the most restrictive variable."""
1313 logging.debug('fix(%s)' % f) 1363 logging.debug('fix(%s)' % f)
1314 # Important, GYP stores the files with / and not \. 1364 # Important, GYP stores the files with / and not \.
1315 if sys.platform == 'win32': 1365 f = f.replace(os.path.sep, '/')
1316 f = f.replace('\\', '/')
1317 1366
1318 if product_dir and f.startswith(product_dir): 1367 if product_dir and f.startswith(product_dir):
1319 return '<(PRODUCT_DIR)/%s' % f[len(product_dir):] 1368 return '<(PRODUCT_DIR)/%s' % f[len(product_dir):]
1320 else: 1369 else:
1321 # cwd_dir is usually the directory containing the gyp file. It may be 1370 # cwd_dir is usually the directory containing the gyp file. It may be
1322 # empty if the whole directory containing the gyp file is needed. 1371 # empty if the whole directory containing the gyp file is needed.
1323 return posix_relpath(f, cwd_dir) or './' 1372 return posix_relpath(f, cwd_dir) or './'
1324 1373
1325 corrected = [fix(f) for f in simplified] 1374 corrected = [fix(f) for f in simplified]
1326 tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f] 1375 tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f]
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
1387 os.path.abspath(options.log), 1436 os.path.abspath(options.log),
1388 args, 1437 args,
1389 options.root_dir, 1438 options.root_dir,
1390 options.cwd, 1439 options.cwd,
1391 options.product_dir, 1440 options.product_dir,
1392 options.force) 1441 options.force)
1393 1442
1394 1443
1395 if __name__ == '__main__': 1444 if __name__ == '__main__':
1396 sys.exit(main()) 1445 sys.exit(main())
OLDNEW
« no previous file with comments | « tools/isolate/isolate_test.py ('k') | tools/isolate/trace_inputs_smoke_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698