Bug 397436 - "Add SVN version information support for symbolstore.py". r=luser, a=bzbarsky.

This commit is contained in:
bent.mozilla@gmail.com 2007-09-26 11:01:23 -07:00
parent 82e93efeeb
commit cc3c82aa63

View File

@ -21,6 +21,7 @@
#
# Contributor(s):
# Ted Mielczarek <ted.mielczarek@gmail.com>
# Ben Turner <mozilla@songbirdnest.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -59,39 +60,177 @@ import re
import shutil
from optparse import OptionParser
# Utility classes
class VCSFileInfo:
""" A base class for version-controlled file information. Ensures that the
following attributes are generated only once (successfully):
self.root
self.revision
self.filename
The attributes are generated by a single call to the GetRoot,
GetRevision, and GetFilename methods. Those methods are explicitly not
implemented here and must be implemented in derived classes. """
def __init__(self, file):
if not file:
raise ValueError
self.file = file
def __getattr__(self, name):
""" __getattr__ is only called for attributes that are not set on self,
so setting self.[attr] will prevent future calls to the GetRoot,
GetRevision, and GetFilename methods. We don't set the values on
failure on the off chance that a future call might succeed. """
if name == "root":
root = self.GetRoot()
if root:
self.root = root
return root
elif name == "revision":
revision = self.GetRevision()
if revision:
self.revision = revision
return revision
elif name == "filename":
filename = self.GetFilename()
if filename:
self.filename = filename
return filename
raise AttributeError
def GetRoot(self):
""" This method should return the repository root for the file or 'None'
on failure. """
raise NotImplementedError
def GetRevision(self):
""" This method should return the revision number for the file or 'None'
on failure. """
raise NotImplementedError
def GetFilename(self):
""" This method should return the repository-specific filename for the
file or 'None' on failure. """
raise NotImplementedError
class CVSFileInfo(VCSFileInfo):
""" A class to maintiain version information for files in a CVS repository.
Derived from VCSFileInfo. """
def __init__(self, file, srcdir):
VCSFileInfo.__init__(self, file)
self.srcdir = srcdir
def GetRoot(self):
(path, filename) = os.path.split(self.file)
root = os.path.join(path, "CVS", "Root")
if not os.path.isfile(root):
return None
f = open(root, "r")
root_name = f.readline().strip()
f.close()
parts = root_name.split("@")
if len(parts) > 1:
# we don't want the extra colon
return parts[1].replace(":","")
print >> sys.stderr, "Failed to get CVS Root for %s" % filename
return None
def GetRevision(self):
(path, filename) = os.path.split(self.file)
entries = os.path.join(path, "CVS", "Entries")
if not os.path.isfile(entries):
return None
f = open(entries, "r")
for line in f:
parts = line.split("/")
if len(parts) > 1 and parts[1] == filename:
return parts[2]
print >> sys.stderr, "Failed to get CVS Revision for %s" % filename
return None
def GetFilename(self):
file = self.file
if self.revision and self.root:
if self.srcdir:
# strip the base path off
# but we actually want the last dir in srcdir
file = os.path.normpath(file)
# the lower() is to handle win32+vc8, where
# the source filenames come out all lowercase,
# but the srcdir can be mixed case
if file.lower().startswith(self.srcdir.lower()):
file = file[len(self.srcdir):]
(head, tail) = os.path.split(self.srcdir)
if tail == "":
tail = os.path.basename(head)
file = tail + file
return "cvs:%s:%s:%s" % (self.root, file, self.revision)
return file
class SVNFileInfo(VCSFileInfo):
url = None
repo = None
svndata = {}
def __init__(self, file):
""" We only want to run subversion's info tool once so pull all the data
here. """
VCSFileInfo.__init__(self, file)
if os.path.isfile(file):
_regex = re.compile(r'^(.+):\s(.+)$')
command = os.popen("svn info %s" % file, "r")
for line in command:
match = _regex.match(line.strip())
if match:
key = match.group(1)
if key in ["Repository Root", "Revision", "URL"]:
self.svndata[key] = match.group(2)
exitStatus = command.close()
if exitStatus:
print >> sys.stderr, "Failed to get SVN info for %s" % file
def GetRoot(self):
key = "Repository Root"
if key in self.svndata:
match = re.match(r'^\w+:\/+(\S+)', self.svndata[key])
if match:
return match.group(1)
print >> sys.stderr, "Failed to get SVN Root for %s" % self.file
return None
def GetRevision(self):
key = "Revision"
if key in self.svndata:
return self.svndata[key]
print >> sys.stderr, "Failed to get SVN Revision for %s" % self.file
return None
def GetFilename(self):
if self.root and self.revision:
if "URL" in self.svndata and "Repository Root" in self.svndata:
url, repo = self.svndata["URL"], self.svndata["Repository Root"]
file = url[len(repo) + 1:]
return "svn:%s:%s:%s" % (self.root, file, self.revision)
print >> sys.stderr, "Failed to get SVN Filename for %s" % self.file
return self.file
# Utility functions
def GetCVSRevision(file):
"""Given a full path to a file, look in CVS/Entries
for the CVS revision number"""
(path, filename) = os.path.split(file)
entries = os.path.join(path, "CVS", "Entries")
if not os.path.isfile(entries):
return None
f = open(entries, "r")
for line in f:
parts = line.split("/")
if len(parts) > 1 and parts[1] == filename:
return parts[2]
print >> sys.stderr, "Failed to get CVS Revision for %s" % filename
return None
def GetCVSRoot(file):
"""Given a full path to a file, look in CVS/Root
for the CVS Root"""
(path, filename) = os.path.split(file)
root = os.path.join(path, "CVS", "Root")
if not os.path.isfile(root):
return None
f = open(root, "r")
root_name = f.readline().strip()
f.close()
parts = root_name.split("@")
if len(parts) > 1:
# we don't want the extra colon
return parts[1].replace(":","")
print >> sys.stderr, "Failed to get CVS Root for %s" % filename
return None
# A cache of files for which VCS info has already been determined. Used to
# prevent extra filesystem activity or process launching.
vcsFileInfoCache = {}
def GetVCSFilename(file, srcdir):
"""Given a full path to a file, and the top source directory,
@ -104,30 +243,24 @@ def GetVCSFilename(file, srcdir):
(path, filename) = os.path.split(file)
if path == '' or filename == '':
return file
cvsdir = os.path.join(path, "CVS")
if os.path.isdir(cvsdir):
rev = GetCVSRevision(file)
root = GetCVSRoot(file)
if rev is not None and root is not None:
if srcdir is not None:
# strip the base path off
# but we actually want the last dir in srcdir
file = os.path.normpath(file)
# the lower() is to handle win32+vc8, where
# the source filenames come out all lowercase,
# but the srcdir can be mixed case
if file.lower().startswith(srcdir.lower()):
file = file[len(srcdir):]
(head, tail) = os.path.split(srcdir)
if tail == "":
tail = os.path.basename(head)
file = tail + file
# we want forward slashes on win32 paths
file = file.replace("\\", "/")
return "cvs:%s:%s:%s" % (root, file, rev)
file = file.replace("\\", "/")
return file
fileInfo = None
if file in vcsFileInfoCache:
# Already cached this info, use it.
fileInfo = vcsFileInfoCache[file]
else:
if os.path.isdir(os.path.join(path, "CVS")):
fileInfo = CVSFileInfo(file, srcdir)
elif os.path.isdir(os.path.join(path, ".svn")) or \
os.path.isdir(os.path.join(path, "_svn")):
fileInfo = SVNFileInfo(file);
vcsFileInfoCache[file] = fileInfo
if fileInfo:
file = fileInfo.filename
# we want forward slashes on win32 paths
return file.replace("\\", "/")
def GetPlatformSpecificDumper(**kwargs):
"""This function simply returns a instance of a subclass of Dumper
@ -194,7 +327,7 @@ class Dumper:
return self.ProcessFile(file_or_dir)
# maybe it doesn't exist?
return False
def ProcessDir(self, dir):
"""Process all the valid files in this directory. Valid files
are determined by calling ShouldProcess."""
@ -206,7 +339,7 @@ class Dumper:
if not self.ProcessFile(fullpath):
result = False
return result
def ProcessFile(self, file):
"""Dump symbols from this file into a symbol file, stored
in the proper directory structure in |symbol_path|."""
@ -269,6 +402,8 @@ class Dumper:
# logic to determine what files to extract symbols from.
class Dumper_Win32(Dumper):
fixedFilenameCaseCache = {}
def ShouldProcess(self, file):
"""This function will allow processing of pdb files that have dll
or exe files with the same base name next to them."""
@ -277,19 +412,29 @@ class Dumper_Win32(Dumper):
if os.path.isfile(path + ".exe") or os.path.isfile(path + ".dll"):
return True
return False
def FixFilenameCase(self, file):
"""Recent versions of Visual C++ put filenames into
PDB files as all lowercase. If the file exists
on the local filesystem, fix it."""
# Use a cached version if we have one.
if file in self.fixedFilenameCaseCache:
return self.fixedFilenameCaseCache[file]
result = file
(path, filename) = os.path.split(file)
if not os.path.isdir(path):
return file
lc_filename = filename.lower()
for f in os.listdir(path):
if f.lower() == lc_filename:
return os.path.join(path, f)
return file
if os.path.isdir(path):
lc_filename = filename.lower()
for f in os.listdir(path):
if f.lower() == lc_filename:
result = os.path.join(path, f)
break
# Cache the corrected version to avoid future filesystem hits.
self.fixedFilenameCaseCache[file] = result
return result
class Dumper_Linux(Dumper):
def ShouldProcess(self, file):