Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / toolkit / crashreporter / tools / symbolstore.py
blob937e4d872df45589ffa9671b7e871b1d40eadd0c
1 #!/bin/env python
2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
15 # The Original Code is mozilla.org code.
17 # The Initial Developer of the Original Code is
18 # The Mozilla Foundation
19 # Portions created by the Initial Developer are Copyright (C) 2007
20 # the Initial Developer. All Rights Reserved.
22 # Contributor(s):
23 # Ted Mielczarek <ted.mielczarek@gmail.com>
24 # Ben Turner <mozilla@songbirdnest.com>
26 # Alternatively, the contents of this file may be used under the terms of
27 # either the GNU General Public License Version 2 or later (the "GPL"), or
28 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 # in which case the provisions of the GPL or the LGPL are applicable instead
30 # of those above. If you wish to allow use of your version of this file only
31 # under the terms of either the GPL or the LGPL, and not to allow others to
32 # use your version of this file under the terms of the MPL, indicate your
33 # decision by deleting the provisions above and replace them with the notice
34 # and other provisions required by the GPL or the LGPL. If you do not delete
35 # the provisions above, a recipient may use your version of this file under
36 # the terms of any one of the MPL, the GPL or the LGPL.
38 # ***** END LICENSE BLOCK *****
40 # Usage: symbolstore.py <params> <dump_syms path> <symbol store path>
41 # <debug info files or dirs>
42 # Runs dump_syms on each debug info file specified on the command line,
43 # then places the resulting symbol file in the proper directory
44 # structure in the symbol store path. Accepts multiple files
45 # on the command line, so can be called as part of a pipe using
46 # find <dir> | xargs symbolstore.pl <dump_syms> <storepath>
47 # But really, you might just want to pass it <dir>.
49 # Parameters accepted:
50 # -c : Copy debug info files to the same directory structure
51 # as sym files
52 # -a "<archs>" : Run dump_syms -a <arch> for each space separated
53 # cpu architecture in <archs> (only on OS X)
54 # -s <srcdir> : Use <srcdir> as the top source directory to
55 # generate relative filenames.
57 import sys
58 import os
59 import re
60 import shutil
61 from optparse import OptionParser
63 # Utility classes
65 class VCSFileInfo:
66 """ A base class for version-controlled file information. Ensures that the
67 following attributes are generated only once (successfully):
69 self.root
70 self.clean_root
71 self.revision
72 self.filename
74 The attributes are generated by a single call to the GetRoot,
75 GetRevision, and GetFilename methods. Those methods are explicitly not
76 implemented here and must be implemented in derived classes. """
78 def __init__(self, file):
79 if not file:
80 raise ValueError
81 self.file = file
83 def __getattr__(self, name):
84 """ __getattr__ is only called for attributes that are not set on self,
85 so setting self.[attr] will prevent future calls to the GetRoot,
86 GetRevision, and GetFilename methods. We don't set the values on
87 failure on the off chance that a future call might succeed. """
89 if name == "root":
90 root = self.GetRoot()
91 if root:
92 self.root = root
93 return root
95 elif name == "clean_root":
96 clean_root = self.GetCleanRoot()
97 if clean_root:
98 self.clean_root = clean_root
99 return clean_root
101 elif name == "revision":
102 revision = self.GetRevision()
103 if revision:
104 self.revision = revision
105 return revision
107 elif name == "filename":
108 filename = self.GetFilename()
109 if filename:
110 self.filename = filename
111 return filename
113 raise AttributeError
115 def GetRoot(self):
116 """ This method should return the unmodified root for the file or 'None'
117 on failure. """
118 raise NotImplementedError
120 def GetCleanRoot(self):
121 """ This method should return the repository root for the file or 'None'
122 on failure. """
123 raise NotImplementedErrors
125 def GetRevision(self):
126 """ This method should return the revision number for the file or 'None'
127 on failure. """
128 raise NotImplementedError
130 def GetFilename(self):
131 """ This method should return the repository-specific filename for the
132 file or 'None' on failure. """
133 raise NotImplementedError
135 class CVSFileInfo(VCSFileInfo):
136 """ A class to maintiain version information for files in a CVS repository.
137 Derived from VCSFileInfo. """
139 def __init__(self, file, srcdir):
140 VCSFileInfo.__init__(self, file)
141 self.srcdir = srcdir
143 def GetRoot(self):
144 (path, filename) = os.path.split(self.file)
145 root = os.path.join(path, "CVS", "Root")
146 if not os.path.isfile(root):
147 return None
148 f = open(root, "r")
149 root_name = f.readline().strip()
150 f.close()
151 if root_name:
152 return root_name
153 print >> sys.stderr, "Failed to get CVS Root for %s" % filename
154 return None
156 def GetCleanRoot(self):
157 parts = self.root.split('@')
158 if len(parts) > 1:
159 # we don't want the extra colon
160 return parts[1].replace(":","")
161 return self.root.replace(":","")
163 def GetRevision(self):
164 (path, filename) = os.path.split(self.file)
165 entries = os.path.join(path, "CVS", "Entries")
166 if not os.path.isfile(entries):
167 return None
168 f = open(entries, "r")
169 for line in f:
170 parts = line.split("/")
171 if len(parts) > 1 and parts[1] == filename:
172 return parts[2]
173 print >> sys.stderr, "Failed to get CVS Revision for %s" % filename
174 return None
176 def GetFilename(self):
177 file = self.file
178 if self.revision and self.clean_root:
179 if self.srcdir:
180 # strip the base path off
181 # but we actually want the last dir in srcdir
182 file = os.path.normpath(file)
183 # the lower() is to handle win32+vc8, where
184 # the source filenames come out all lowercase,
185 # but the srcdir can be mixed case
186 if file.lower().startswith(self.srcdir.lower()):
187 file = file[len(self.srcdir):]
188 (head, tail) = os.path.split(self.srcdir)
189 if tail == "":
190 tail = os.path.basename(head)
191 file = tail + file
192 return "cvs:%s:%s:%s" % (self.clean_root, file, self.revision)
193 return file
195 # This regex separates protocol and optional username/password from a url.
196 # For instance, all the following urls will be transformed into
197 # 'foo.com/bar':
199 # http://foo.com/bar
200 # svn+ssh://user@foo.com/bar
201 # svn+ssh://user:pass@foo.com/bar
203 # This is used by both SVN and HG
204 rootRegex = re.compile(r'^\S+?:/+(?:[^\s/]*@)?(\S+)$')
206 class SVNFileInfo(VCSFileInfo):
207 url = None
208 repo = None
209 svndata = {}
211 def __init__(self, file):
212 """ We only want to run subversion's info tool once so pull all the data
213 here. """
215 VCSFileInfo.__init__(self, file)
217 if os.path.isfile(file):
218 command = os.popen("svn info %s" % file, "r")
219 for line in command:
220 # The last line of the output is usually '\n'
221 if line.strip() == '':
222 continue
223 # Split into a key/value pair on the first colon
224 key, value = line.split(':', 1)
225 if key in ["Repository Root", "Revision", "URL"]:
226 self.svndata[key] = value.strip()
228 exitStatus = command.close()
229 if exitStatus:
230 print >> sys.stderr, "Failed to get SVN info for %s" % file
232 def GetRoot(self):
233 key = "Repository Root"
234 if key in self.svndata:
235 match = rootRegex.match(self.svndata[key])
236 if match:
237 return match.group(1)
238 print >> sys.stderr, "Failed to get SVN Root for %s" % self.file
239 return None
241 # File bug to get this teased out from the current GetRoot, this is temporary
242 def GetCleanRoot(self):
243 return self.root
245 def GetRevision(self):
246 key = "Revision"
247 if key in self.svndata:
248 return self.svndata[key]
249 print >> sys.stderr, "Failed to get SVN Revision for %s" % self.file
250 return None
252 def GetFilename(self):
253 if self.root and self.revision:
254 if "URL" in self.svndata and "Repository Root" in self.svndata:
255 url, repo = self.svndata["URL"], self.svndata["Repository Root"]
256 file = url[len(repo) + 1:]
257 return "svn:%s:%s:%s" % (self.root, file, self.revision)
258 print >> sys.stderr, "Failed to get SVN Filename for %s" % self.file
259 return self.file
261 class HGRepoInfo():
262 # HG info is per-repo, so cache it in a static
263 # member var
264 repos = {}
265 def __init__(self, path, rev, cleanroot):
266 self.path = path
267 self.rev = rev
268 self.cleanroot = cleanroot
270 class HGFileInfo(VCSFileInfo):
271 def __init__(self, file, srcdir):
272 VCSFileInfo.__init__(self, file)
273 # we should only have to collect this info once per-repo
274 if not srcdir in HGRepoInfo.repos:
275 rev = os.popen('hg identify -i "%s"' % srcdir, "r").readlines()[0].rstrip()
276 # could have a + if there are uncommitted local changes
277 if rev.endswith('+'):
278 rev = rev[:-1]
280 path = os.popen('hg -R "%s" showconfig paths.default' % srcdir, "r").readlines()[0].rstrip()
281 if path == '':
282 hg_root = os.environ.get("SRCSRV_ROOT")
283 if hg_root:
284 path = hg_root
285 else:
286 print >> sys.stderr, "Failed to get HG Repo for %s" % srcdir
287 if path != '': # not there?
288 match = rootRegex.match(path)
289 if match:
290 cleanroot = match.group(1)
291 if cleanroot.endswith('/'):
292 cleanroot = cleanroot[:-1]
293 HGRepoInfo.repos[srcdir] = HGRepoInfo(path, rev, cleanroot)
294 self.repo = HGRepoInfo.repos[srcdir]
295 self.file = file
296 self.srcdir = srcdir
298 def GetRoot(self):
299 return self.repo.path
301 def GetCleanRoot(self):
302 return self.repo.cleanroot
304 def GetRevision(self):
305 return self.repo.rev
307 def GetFilename(self):
308 file = self.file
309 if self.revision and self.clean_root:
310 if self.srcdir:
311 # strip the base path off
312 file = os.path.normpath(file)
313 if IsInDir(file, self.srcdir):
314 file = file[len(self.srcdir):]
315 if file.startswith('/') or file.startswith('\\'):
316 file = file[1:]
317 return "hg:%s:%s:%s" % (self.clean_root, file, self.revision)
318 return file
320 # Utility functions
322 # A cache of files for which VCS info has already been determined. Used to
323 # prevent extra filesystem activity or process launching.
324 vcsFileInfoCache = {}
326 def IsInDir(file, dir):
327 # the lower() is to handle win32+vc8, where
328 # the source filenames come out all lowercase,
329 # but the srcdir can be mixed case
330 return os.path.abspath(file).lower().startswith(os.path.abspath(dir).lower())
332 def GetVCSFilename(file, srcdir):
333 """Given a full path to a file, and the top source directory,
334 look for version control information about this file, and return
335 a tuple containing
336 1) a specially formatted filename that contains the VCS type,
337 VCS location, relative filename, and revision number, formatted like:
338 vcs:vcs location:filename:revision
339 For example:
340 cvs:cvs.mozilla.org/cvsroot:mozilla/browser/app/nsBrowserApp.cpp:1.36
341 2) the unmodified root information if it exists"""
342 (path, filename) = os.path.split(file)
343 if path == '' or filename == '':
344 return (file, None)
346 fileInfo = None
347 root = ''
348 if file in vcsFileInfoCache:
349 # Already cached this info, use it.
350 fileInfo = vcsFileInfoCache[file]
351 else:
352 if os.path.isdir(os.path.join(path, "CVS")):
353 fileInfo = CVSFileInfo(file, srcdir)
354 elif os.path.isdir(os.path.join(path, ".svn")) or \
355 os.path.isdir(os.path.join(path, "_svn")):
356 fileInfo = SVNFileInfo(file);
357 elif os.path.isdir(os.path.join(srcdir, '.hg')) and \
358 IsInDir(file, srcdir):
359 fileInfo = HGFileInfo(file, srcdir)
360 vcsFileInfoCache[file] = fileInfo
362 if fileInfo:
363 file = fileInfo.filename
364 root = fileInfo.root
366 # we want forward slashes on win32 paths
367 return (file.replace("\\", "/"), root)
369 def GetPlatformSpecificDumper(**kwargs):
370 """This function simply returns a instance of a subclass of Dumper
371 that is appropriate for the current platform."""
372 return {'win32': Dumper_Win32,
373 'cygwin': Dumper_Win32,
374 'linux2': Dumper_Linux,
375 'sunos5': Dumper_Solaris,
376 'darwin': Dumper_Mac}[sys.platform](**kwargs)
378 def SourceIndex(fileStream, outputPath, vcs_root):
379 """Takes a list of files, writes info to a data block in a .stream file"""
380 # Creates a .pdb.stream file in the mozilla\objdir to be used for source indexing
381 # Create the srcsrv data block that indexes the pdb file
382 result = True
383 pdbStreamFile = open(outputPath, "w")
384 pdbStreamFile.write('''SRCSRV: ini ------------------------------------------------\r\nVERSION=2\r\nINDEXVERSION=2\r\nVERCTRL=http\r\nSRCSRV: variables ------------------------------------------\r\nHGSERVER=''')
385 pdbStreamFile.write(vcs_root)
386 pdbStreamFile.write('''\r\nSRCSRVVERCTRL=http\r\nHTTP_EXTRACT_TARGET=%hgserver%/raw-file/%var3%/%var2%\r\nSRCSRVTRG=%http_extract_target%\r\nSRCSRV: source files ---------------------------------------\r\n''')
387 pdbStreamFile.write(fileStream) # can't do string interpolation because the source server also uses this and so there are % in the above
388 pdbStreamFile.write("SRCSRV: end ------------------------------------------------\r\n\n")
389 pdbStreamFile.close()
390 return result
392 class Dumper:
393 """This class can dump symbols from a file with debug info, and
394 store the output in a directory structure that is valid for use as
395 a Breakpad symbol server. Requires a path to a dump_syms binary--
396 |dump_syms| and a directory to store symbols in--|symbol_path|.
397 Optionally takes a list of processor architectures to process from
398 each debug file--|archs|, the full path to the top source
399 directory--|srcdir|, for generating relative source file names,
400 and an option to copy debug info files alongside the dumped
401 symbol files--|copy_debug|, mostly useful for creating a
402 Microsoft Symbol Server from the resulting output.
404 You don't want to use this directly if you intend to call
405 ProcessDir. Instead, call GetPlatformSpecificDumper to
406 get an instance of a subclass."""
407 def __init__(self, dump_syms, symbol_path,
408 archs=None, srcdir=None, copy_debug=False, vcsinfo=False, srcsrv=False):
409 # popen likes absolute paths, at least on windows
410 self.dump_syms = os.path.abspath(dump_syms)
411 self.symbol_path = symbol_path
412 if archs is None:
413 # makes the loop logic simpler
414 self.archs = ['']
415 else:
416 self.archs = ['-a %s' % a for a in archs.split()]
417 if srcdir is not None:
418 self.srcdir = os.path.normpath(srcdir)
419 else:
420 self.srcdir = None
421 self.copy_debug = copy_debug
422 self.vcsinfo = vcsinfo
423 self.srcsrv = srcsrv
425 # subclasses override this
426 def ShouldProcess(self, file):
427 return False
429 # and can override this
430 def ShouldSkipDir(self, dir):
431 return False
433 def RunFileCommand(self, file):
434 """Utility function, returns the output of file(1)"""
435 try:
436 # we use -L to read the targets of symlinks,
437 # and -b to print just the content, not the filename
438 return os.popen("file -Lb " + file).read()
439 except:
440 return ""
442 # This is a no-op except on Win32
443 def FixFilenameCase(self, file):
444 return file
446 # This is a no-op except on Win32
447 def SourceServerIndexing(self, debug_file, guid, sourceFileStream, vcs_root):
448 return ""
450 # subclasses override this if they want to support this
451 def CopyDebug(self, file, debug_file, guid):
452 pass
454 def Process(self, file_or_dir):
455 "Process a file or all the (valid) files in a directory."
456 if os.path.isdir(file_or_dir) and not self.ShouldSkipDir(file_or_dir):
457 return self.ProcessDir(file_or_dir)
458 elif os.path.isfile(file_or_dir):
459 return self.ProcessFile(file_or_dir)
460 # maybe it doesn't exist?
461 return False
463 def ProcessDir(self, dir):
464 """Process all the valid files in this directory. Valid files
465 are determined by calling ShouldProcess."""
466 result = True
467 for root, dirs, files in os.walk(dir):
468 for d in dirs[:]:
469 if self.ShouldSkipDir(d):
470 dirs.remove(d)
471 for f in files:
472 fullpath = os.path.join(root, f)
473 if self.ShouldProcess(fullpath):
474 if not self.ProcessFile(fullpath):
475 result = False
476 return result
478 def ProcessFile(self, file):
479 """Dump symbols from this file into a symbol file, stored
480 in the proper directory structure in |symbol_path|."""
481 result = False
482 sourceFileStream = ''
483 # tries to get the vcs root from the .mozconfig first - if it's not set
484 # the tinderbox vcs path will be assigned further down
485 vcs_root = os.environ.get("SRCSRV_ROOT")
486 for arch in self.archs:
487 try:
488 cmd = os.popen("%s %s %s" % (self.dump_syms, arch, file), "r")
489 module_line = cmd.next()
490 if module_line.startswith("MODULE"):
491 # MODULE os cpu guid debug_file
492 (guid, debug_file) = (module_line.split())[3:5]
493 # strip off .pdb extensions, and append .sym
494 sym_file = re.sub("\.pdb$", "", debug_file) + ".sym"
495 # we do want forward slashes here
496 rel_path = os.path.join(debug_file,
497 guid,
498 sym_file).replace("\\", "/")
499 full_path = os.path.normpath(os.path.join(self.symbol_path,
500 rel_path))
501 try:
502 os.makedirs(os.path.dirname(full_path))
503 except OSError: # already exists
504 pass
505 f = open(full_path, "w")
506 f.write(module_line)
507 # now process the rest of the output
508 for line in cmd:
509 if line.startswith("FILE"):
510 # FILE index filename
511 (x, index, filename) = line.split(None, 2)
512 if sys.platform == "sunos5":
513 start = filename.find(self.srcdir)
514 if start == -1:
515 start = 0
516 filename = filename[start:]
517 filename = self.FixFilenameCase(filename.rstrip())
518 sourcepath = filename
519 if self.vcsinfo:
520 (filename, rootname) = GetVCSFilename(filename, self.srcdir)
521 # sets vcs_root in case the loop through files were to end on an empty rootname
522 if vcs_root is None:
523 if rootname:
524 vcs_root = rootname
525 # gather up files with hg for indexing
526 if filename.startswith("hg"):
527 (ver, checkout, source_file, revision) = filename.split(":", 3)
528 sourceFileStream += sourcepath + "*" + source_file + '*' + revision + "\r\n"
529 f.write("FILE %s %s\n" % (index, filename))
530 else:
531 # pass through all other lines unchanged
532 f.write(line)
533 f.close()
534 cmd.close()
535 # we output relative paths so callers can get a list of what
536 # was generated
537 print rel_path
538 if self.copy_debug:
539 self.CopyDebug(file, debug_file, guid)
540 if self.srcsrv and vcs_root:
541 # Call on SourceServerIndexing
542 result = self.SourceServerIndexing(debug_file, guid, sourceFileStream, vcs_root)
543 result = True
544 except StopIteration:
545 pass
546 except:
547 print >> sys.stderr, "Unexpected error: ", sys.exc_info()[0]
548 raise
549 return result
551 # Platform-specific subclasses. For the most part, these just have
552 # logic to determine what files to extract symbols from.
554 class Dumper_Win32(Dumper):
555 fixedFilenameCaseCache = {}
557 def ShouldProcess(self, file):
558 """This function will allow processing of pdb files that have dll
559 or exe files with the same base name next to them."""
560 if file.endswith(".pdb"):
561 (path,ext) = os.path.splitext(file)
562 if os.path.isfile(path + ".exe") or os.path.isfile(path + ".dll"):
563 return True
564 return False
566 def FixFilenameCase(self, file):
567 """Recent versions of Visual C++ put filenames into
568 PDB files as all lowercase. If the file exists
569 on the local filesystem, fix it."""
571 # Use a cached version if we have one.
572 if file in self.fixedFilenameCaseCache:
573 return self.fixedFilenameCaseCache[file]
575 result = file
577 (path, filename) = os.path.split(file)
578 if os.path.isdir(path):
579 lc_filename = filename.lower()
580 for f in os.listdir(path):
581 if f.lower() == lc_filename:
582 result = os.path.join(path, f)
583 break
585 # Cache the corrected version to avoid future filesystem hits.
586 self.fixedFilenameCaseCache[file] = result
587 return result
589 def CopyDebug(self, file, debug_file, guid):
590 rel_path = os.path.join(debug_file,
591 guid,
592 debug_file).replace("\\", "/")
593 print rel_path
594 full_path = os.path.normpath(os.path.join(self.symbol_path,
595 rel_path))
596 shutil.copyfile(file, full_path)
597 pass
599 def SourceServerIndexing(self, debug_file, guid, sourceFileStream, vcs_root):
600 # Creates a .pdb.stream file in the mozilla\objdir to be used for source indexing
601 cwd = os.getcwd()
602 streamFilename = debug_file + ".stream"
603 stream_output_path = os.path.join(cwd, streamFilename)
604 # Call SourceIndex to create the .stream file
605 result = SourceIndex(sourceFileStream, stream_output_path, vcs_root)
607 if self.copy_debug:
608 pdbstr_path = os.environ.get("PDBSTR_PATH")
609 pdbstr = os.path.normpath(pdbstr_path)
610 pdb_rel_path = os.path.join(debug_file, guid, debug_file)
611 pdb_filename = os.path.normpath(os.path.join(self.symbol_path, pdb_rel_path))
612 # move to the dir with the stream files to call pdbstr
613 os.chdir(os.path.dirname(stream_output_path))
614 os.spawnv(os.P_WAIT, pdbstr, [pdbstr, "-w", "-p:" + pdb_filename, "-i:" + streamFilename, "-s:srcsrv"])
615 # clean up all the .stream files when done
616 os.remove(stream_output_path)
617 return result
619 class Dumper_Linux(Dumper):
620 def ShouldProcess(self, file):
621 """This function will allow processing of files that are
622 executable, or end with the .so extension, and additionally
623 file(1) reports as being ELF files. It expects to find the file
624 command in PATH."""
625 if file.endswith(".so") or os.access(file, os.X_OK):
626 return self.RunFileCommand(file).startswith("ELF")
627 return False
629 def CopyDebug(self, file, debug_file, guid):
630 # We want to strip out the debug info, and add a
631 # .gnu_debuglink section to the object, so the debugger can
632 # actually load our debug info later.
633 file_dbg = file + ".dbg"
634 os.system("objcopy --only-keep-debug %s %s" % (file, file_dbg))
635 os.system("objcopy --add-gnu-debuglink=%s %s" % (file_dbg, file))
637 rel_path = os.path.join(debug_file,
638 guid,
639 debug_file + ".dbg")
640 full_path = os.path.normpath(os.path.join(self.symbol_path,
641 rel_path))
642 shutil.copyfile(file_dbg, full_path)
643 # gzip the shipped debug files
644 os.system("gzip %s" % full_path)
645 print rel_path + ".gz"
647 class Dumper_Solaris(Dumper):
648 def RunFileCommand(self, file):
649 """Utility function, returns the output of file(1)"""
650 try:
651 output = os.popen("file " + file).read()
652 return output.split('\t')[1];
653 except:
654 return ""
656 def ShouldProcess(self, file):
657 """This function will allow processing of files that are
658 executable, or end with the .so extension, and additionally
659 file(1) reports as being ELF files. It expects to find the file
660 command in PATH."""
661 if file.endswith(".so") or os.access(file, os.X_OK):
662 return self.RunFileCommand(file).startswith("ELF")
663 return False
665 class Dumper_Mac(Dumper):
666 def ShouldProcess(self, file):
667 """This function will allow processing of files that are
668 executable, or end with the .dylib extension, and additionally
669 file(1) reports as being Mach-O files. It expects to find the file
670 command in PATH."""
671 if file.endswith(".dylib") or os.access(file, os.X_OK):
672 return self.RunFileCommand(file).startswith("Mach-O")
673 return False
675 def ShouldSkipDir(self, dir):
676 """We create .dSYM bundles on the fly, but if someone runs
677 buildsymbols twice, we should skip any bundles we created
678 previously, otherwise we'll recurse into them and try to
679 dump the inner bits again."""
680 if dir.endswith(".dSYM"):
681 return True
682 return False
684 def ProcessFile(self, file):
685 """dump_syms on Mac needs to be run on a dSYM bundle produced
686 by dsymutil(1), so run dsymutil here and pass the bundle name
687 down to the superclass method instead."""
688 dsymbundle = file + ".dSYM"
689 if os.path.exists(dsymbundle):
690 shutil.rmtree(dsymbundle)
691 # dsymutil takes --arch=foo instead of -a foo like everything else
692 os.system("dsymutil %s %s >/dev/null" % (' '.join([a.replace('-a ', '--arch=') for a in self.archs]),
693 file))
694 return Dumper.ProcessFile(self, dsymbundle)
696 # Entry point if called as a standalone program
697 def main():
698 parser = OptionParser(usage="usage: %prog [options] <dump_syms binary> <symbol store path> <debug info files>")
699 parser.add_option("-c", "--copy",
700 action="store_true", dest="copy_debug", default=False,
701 help="Copy debug info files into the same directory structure as symbol files")
702 parser.add_option("-a", "--archs",
703 action="store", dest="archs",
704 help="Run dump_syms -a <arch> for each space separated cpu architecture in ARCHS (only on OS X)")
705 parser.add_option("-s", "--srcdir",
706 action="store", dest="srcdir",
707 help="Use SRCDIR to determine relative paths to source files")
708 parser.add_option("-v", "--vcs-info",
709 action="store_true", dest="vcsinfo",
710 help="Try to retrieve VCS info for each FILE listed in the output")
711 parser.add_option("-i", "--source-index",
712 action="store_true", dest="srcsrv", default=False,
713 help="Add source index information to debug files, making them suitable for use in a source server.")
714 (options, args) = parser.parse_args()
716 #check to see if the pdbstr.exe exists
717 if options.srcsrv:
718 pdbstr = os.environ.get("PDBSTR_PATH")
719 if not os.path.exists(pdbstr):
720 print >> sys.stderr, "Invalid path to pdbstr.exe - please set/check PDBSTR_PATH.\n"
721 sys.exit(1)
723 if len(args) < 3:
724 parser.error("not enough arguments")
725 exit(1)
727 dumper = GetPlatformSpecificDumper(dump_syms=args[0],
728 symbol_path=args[1],
729 copy_debug=options.copy_debug,
730 archs=options.archs,
731 srcdir=options.srcdir,
732 vcsinfo=options.vcsinfo,
733 srcsrv=options.srcsrv)
734 for arg in args[2:]:
735 dumper.Process(arg)
737 # run main if run directly
738 if __name__ == "__main__":
739 main()