PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/build/util/lastchange.py

https://gitlab.com/jonnialva90/iridium-browser
Python | 316 lines | 274 code | 11 blank | 31 comment | 15 complexity | 120ac2a8fc9fee2509cea92b2c3dbe91 MD5 | raw file
  1. #!/usr/bin/env python
  2. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """
  6. lastchange.py -- Chromium revision fetching utility.
  7. """
  8. import re
  9. import optparse
  10. import os
  11. import subprocess
  12. import sys
  13. _GIT_SVN_ID_REGEX = re.compile(r'.*git-svn-id:\s*([^@]*)@([0-9]+)', re.DOTALL)
  14. class VersionInfo(object):
  15. def __init__(self, url, revision):
  16. self.url = url
  17. self.revision = revision
  18. def FetchSVNRevision(directory, svn_url_regex):
  19. """
  20. Fetch the Subversion branch and revision for a given directory.
  21. Errors are swallowed.
  22. Returns:
  23. A VersionInfo object or None on error.
  24. """
  25. try:
  26. proc = subprocess.Popen(['svn', 'info'],
  27. stdout=subprocess.PIPE,
  28. stderr=subprocess.PIPE,
  29. cwd=directory,
  30. shell=(sys.platform=='win32'))
  31. except OSError:
  32. # command is apparently either not installed or not executable.
  33. return None
  34. if not proc:
  35. return None
  36. attrs = {}
  37. for line in proc.stdout:
  38. line = line.strip()
  39. if not line:
  40. continue
  41. key, val = line.split(': ', 1)
  42. attrs[key] = val
  43. try:
  44. match = svn_url_regex.search(attrs['URL'])
  45. if match:
  46. url = match.group(2)
  47. else:
  48. url = ''
  49. revision = attrs['Revision']
  50. except KeyError:
  51. return None
  52. return VersionInfo(url, revision)
  53. def RunGitCommand(directory, command):
  54. """
  55. Launches git subcommand.
  56. Errors are swallowed.
  57. Returns:
  58. A process object or None.
  59. """
  60. command = ['git'] + command
  61. # Force shell usage under cygwin. This is a workaround for
  62. # mysterious loss of cwd while invoking cygwin's git.
  63. # We can't just pass shell=True to Popen, as under win32 this will
  64. # cause CMD to be used, while we explicitly want a cygwin shell.
  65. if sys.platform == 'cygwin':
  66. command = ['sh', '-c', ' '.join(command)]
  67. try:
  68. proc = subprocess.Popen(command,
  69. stdout=subprocess.PIPE,
  70. stderr=subprocess.PIPE,
  71. cwd=directory,
  72. shell=(sys.platform=='win32'))
  73. return proc
  74. except OSError:
  75. return None
  76. def FetchGitRevision(directory, hash_only):
  77. """
  78. Fetch the Git hash for a given directory.
  79. Errors are swallowed.
  80. Returns:
  81. A VersionInfo object or None on error.
  82. """
  83. hsh = ''
  84. git_args = ['log', '-1', '--format=%H']
  85. if hash_only:
  86. git_args.append('--grep=^Cr-Commit-Position:')
  87. proc = RunGitCommand(directory, git_args)
  88. if proc:
  89. output = proc.communicate()[0].strip()
  90. if proc.returncode == 0 and output:
  91. hsh = output
  92. if not hsh:
  93. return None
  94. pos = ''
  95. proc = RunGitCommand(directory, ['cat-file', 'commit', hsh])
  96. if proc:
  97. output = proc.communicate()[0]
  98. if proc.returncode == 0 and output:
  99. for line in reversed(output.splitlines()):
  100. if line.startswith('Cr-Commit-Position:'):
  101. pos = line.rsplit()[-1].strip()
  102. break
  103. if hash_only or not pos:
  104. return VersionInfo('git', hsh)
  105. return VersionInfo('git', '%s-%s' % (hsh, pos))
  106. def FetchGitSVNURLAndRevision(directory, svn_url_regex, go_deeper):
  107. """
  108. Fetch the Subversion URL and revision through Git.
  109. Errors are swallowed.
  110. Returns:
  111. A tuple containing the Subversion URL and revision.
  112. """
  113. git_args = ['log', '-1', '--format=%b']
  114. if go_deeper:
  115. git_args.append('--grep=git-svn-id')
  116. proc = RunGitCommand(directory, git_args)
  117. if proc:
  118. output = proc.communicate()[0].strip()
  119. if proc.returncode == 0 and output:
  120. # Extract the latest SVN revision and the SVN URL.
  121. # The target line is the last "git-svn-id: ..." line like this:
  122. # git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85528 0039d316....
  123. match = _GIT_SVN_ID_REGEX.search(output)
  124. if match:
  125. revision = match.group(2)
  126. url_match = svn_url_regex.search(match.group(1))
  127. if url_match:
  128. url = url_match.group(2)
  129. else:
  130. url = ''
  131. return url, revision
  132. return None, None
  133. def FetchGitSVNRevision(directory, svn_url_regex, go_deeper):
  134. """
  135. Fetch the Git-SVN identifier for the local tree.
  136. Errors are swallowed.
  137. """
  138. url, revision = FetchGitSVNURLAndRevision(directory, svn_url_regex, go_deeper)
  139. if url and revision:
  140. return VersionInfo(url, revision)
  141. return None
  142. def FetchVersionInfo(default_lastchange, directory=None,
  143. directory_regex_prior_to_src_url='chrome|blink|svn',
  144. go_deeper=False, hash_only=False):
  145. """
  146. Returns the last change (in the form of a branch, revision tuple),
  147. from some appropriate revision control system.
  148. """
  149. svn_url_regex = re.compile(
  150. r'.*/(' + directory_regex_prior_to_src_url + r')(/.*)')
  151. version_info = (FetchSVNRevision(directory, svn_url_regex) or
  152. FetchGitSVNRevision(directory, svn_url_regex, go_deeper) or
  153. FetchGitRevision(directory, hash_only))
  154. if not version_info:
  155. if default_lastchange and os.path.exists(default_lastchange):
  156. revision = open(default_lastchange, 'r').read().strip()
  157. version_info = VersionInfo(None, revision)
  158. else:
  159. version_info = VersionInfo(None, None)
  160. return version_info
  161. def GetHeaderGuard(path):
  162. """
  163. Returns the header #define guard for the given file path.
  164. This treats everything after the last instance of "src/" as being a
  165. relevant part of the guard. If there is no "src/", then the entire path
  166. is used.
  167. """
  168. src_index = path.rfind('src/')
  169. if src_index != -1:
  170. guard = path[src_index + 4:]
  171. else:
  172. guard = path
  173. guard = guard.upper()
  174. return guard.replace('/', '_').replace('.', '_').replace('\\', '_') + '_'
  175. def GetHeaderContents(path, define, version):
  176. """
  177. Returns what the contents of the header file should be that indicate the given
  178. revision. Note that the #define is specified as a string, even though it's
  179. currently always a SVN revision number, in case we need to move to git hashes.
  180. """
  181. header_guard = GetHeaderGuard(path)
  182. header_contents = """/* Generated by lastchange.py, do not edit.*/
  183. #ifndef %(header_guard)s
  184. #define %(header_guard)s
  185. #define %(define)s "%(version)s"
  186. #endif // %(header_guard)s
  187. """
  188. header_contents = header_contents % { 'header_guard': header_guard,
  189. 'define': define,
  190. 'version': version }
  191. return header_contents
  192. def WriteIfChanged(file_name, contents):
  193. """
  194. Writes the specified contents to the specified file_name
  195. iff the contents are different than the current contents.
  196. """
  197. try:
  198. old_contents = open(file_name, 'r').read()
  199. except EnvironmentError:
  200. pass
  201. else:
  202. if contents == old_contents:
  203. return
  204. os.unlink(file_name)
  205. open(file_name, 'w').write(contents)
  206. def main(argv=None):
  207. if argv is None:
  208. argv = sys.argv
  209. parser = optparse.OptionParser(usage="lastchange.py [options]")
  210. parser.add_option("-d", "--default-lastchange", metavar="FILE",
  211. help="Default last change input FILE.")
  212. parser.add_option("-m", "--version-macro",
  213. help="Name of C #define when using --header. Defaults to " +
  214. "LAST_CHANGE.",
  215. default="LAST_CHANGE")
  216. parser.add_option("-o", "--output", metavar="FILE",
  217. help="Write last change to FILE. " +
  218. "Can be combined with --header to write both files.")
  219. parser.add_option("", "--header", metavar="FILE",
  220. help="Write last change to FILE as a C/C++ header. " +
  221. "Can be combined with --output to write both files.")
  222. parser.add_option("--revision-only", action='store_true',
  223. help="Just print the SVN revision number. Overrides any " +
  224. "file-output-related options.")
  225. parser.add_option("-s", "--source-dir", metavar="DIR",
  226. help="Use repository in the given directory.")
  227. parser.add_option("--git-svn-go-deeper", action='store_true',
  228. help="In a Git-SVN repo, dig down to the last committed " +
  229. "SVN change (historic behaviour).")
  230. parser.add_option("--git-hash-only", action="store_true",
  231. help="In a Git repo with commit positions, report only " +
  232. "the hash of the latest commit with a position.")
  233. opts, args = parser.parse_args(argv[1:])
  234. out_file = opts.output
  235. header = opts.header
  236. while len(args) and out_file is None:
  237. if out_file is None:
  238. out_file = args.pop(0)
  239. if args:
  240. sys.stderr.write('Unexpected arguments: %r\n\n' % args)
  241. parser.print_help()
  242. sys.exit(2)
  243. if opts.source_dir:
  244. src_dir = opts.source_dir
  245. else:
  246. src_dir = os.path.dirname(os.path.abspath(__file__))
  247. version_info = FetchVersionInfo(opts.default_lastchange,
  248. directory=src_dir,
  249. go_deeper=opts.git_svn_go_deeper,
  250. hash_only=opts.git_hash_only)
  251. if version_info.revision == None:
  252. version_info.revision = '0'
  253. if opts.revision_only:
  254. print version_info.revision
  255. else:
  256. contents = "LASTCHANGE=%s\n" % version_info.revision
  257. if not out_file and not opts.header:
  258. sys.stdout.write(contents)
  259. else:
  260. if out_file:
  261. WriteIfChanged(out_file, contents)
  262. if header:
  263. WriteIfChanged(header,
  264. GetHeaderContents(header, opts.version_macro,
  265. version_info.revision))
  266. return 0
  267. if __name__ == '__main__':
  268. sys.exit(main())