зеркало из https://github.com/mozilla/pjs.git
Bug 397436 - "Add SVN version information support for symbolstore.py". r=luser, a=bzbarsky.
This commit is contained in:
Родитель
1f214caf79
Коммит
c8960ff9dd
|
@ -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):
|
||||
|
|
Загрузка…
Ссылка в новой задаче