15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)lastchange.py -- Chromium revision fetching utility.
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_GIT_SVN_ID_REGEX = re.compile(r'.*git-svn-id:\s*([^@]*)@([0-9]+)', re.DOTALL)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class VersionInfo(object):
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, url, revision):
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.url = url
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.revision = revision
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def FetchSVNRevision(directory, svn_url_regex):
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Fetch the Subversion branch and revision for a given directory.
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Errors are swallowed.
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A VersionInfo object or None on error.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    proc = subprocess.Popen(['svn', 'info'],
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            stdout=subprocess.PIPE,
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            stderr=subprocess.PIPE,
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            cwd=directory,
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            shell=(sys.platform=='win32'))
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except OSError:
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # command is apparently either not installed or not executable.
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not proc:
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  attrs = {}
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for line in proc.stdout:
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    line = line.strip()
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not line:
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key, val = line.split(': ', 1)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    attrs[key] = val
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    match = svn_url_regex.search(attrs['URL'])
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if match:
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      url = match.group(2)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      url = ''
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    revision = attrs['Revision']
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except KeyError:
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return VersionInfo(url, revision)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RunGitCommand(directory, command):
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Launches git subcommand.
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Errors are swallowed.
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A process object or None.
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  command = ['git'] + command
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Force shell usage under cygwin. This is a workaround for
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # mysterious loss of cwd while invoking cygwin's git.
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # We can't just pass shell=True to Popen, as under win32 this will
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # cause CMD to be used, while we explicitly want a cygwin shell.
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if sys.platform == 'cygwin':
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    command = ['sh', '-c', ' '.join(command)]
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    proc = subprocess.Popen(command,
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            stdout=subprocess.PIPE,
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            stderr=subprocess.PIPE,
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            cwd=directory,
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            shell=(sys.platform=='win32'))
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return proc
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except OSError:
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def FetchGitRevision(directory):
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Fetch the Git hash for a given directory.
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Errors are swallowed.
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A VersionInfo object or None on error.
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
10203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  hsh = ''
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  proc = RunGitCommand(directory, ['rev-parse', 'HEAD'])
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if proc:
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    output = proc.communicate()[0].strip()
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if proc.returncode == 0 and output:
10703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      hsh = output
10803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if not hsh:
10903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return None
11003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  pos = ''
11103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  proc = RunGitCommand(directory, ['show', '-s', '--format=%B', 'HEAD'])
11203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if proc:
11303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    output = proc.communicate()[0]
11403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    if proc.returncode == 0 and output:
11503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      for line in reversed(output.splitlines()):
11603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        if line.startswith('Cr-Commit-Position:'):
11703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)          pos = line.rsplit()[-1].strip()
11803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if not pos:
11903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return VersionInfo('git', hsh)
12003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  return VersionInfo('git', '%s-%s' % (hsh, pos))
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def FetchGitSVNURLAndRevision(directory, svn_url_regex):
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Fetch the Subversion URL and revision through Git.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Errors are swallowed.
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A tuple containing the Subversion URL and revision.
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
13203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  proc = RunGitCommand(directory, ['log', '-1', '--format=%b'])
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if proc:
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    output = proc.communicate()[0].strip()
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if proc.returncode == 0 and output:
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Extract the latest SVN revision and the SVN URL.
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # The target line is the last "git-svn-id: ..." line like this:
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85528 0039d316....
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      match = _GIT_SVN_ID_REGEX.search(output)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if match:
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        revision = match.group(2)
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        url_match = svn_url_regex.search(match.group(1))
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if url_match:
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          url = url_match.group(2)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          url = ''
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return url, revision
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return None, None
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def FetchGitSVNRevision(directory, svn_url_regex):
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Fetch the Git-SVN identifier for the local tree.
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Errors are swallowed.
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  url, revision = FetchGitSVNURLAndRevision(directory, svn_url_regex)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if url and revision:
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return VersionInfo(url, revision)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return None
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def FetchVersionInfo(default_lastchange, directory=None,
164868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)                     directory_regex_prior_to_src_url='chrome|blink|svn'):
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns the last change (in the form of a branch, revision tuple),
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  from some appropriate revision control system.
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  svn_url_regex = re.compile(
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      r'.*/(' + directory_regex_prior_to_src_url + r')(/.*)')
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  version_info = (FetchSVNRevision(directory, svn_url_regex) or
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  FetchGitSVNRevision(directory, svn_url_regex) or
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  FetchGitRevision(directory))
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not version_info:
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if default_lastchange and os.path.exists(default_lastchange):
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      revision = open(default_lastchange, 'r').read().strip()
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      version_info = VersionInfo(None, revision)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      version_info = VersionInfo(None, None)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return version_info
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)def GetHeaderGuard(path):
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  """
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Returns the header #define guard for the given file path.
186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  This treats everything after the last instance of "src/" as being a
187f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  relevant part of the guard. If there is no "src/", then the entire path
188f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  is used.
189f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  """
190f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  src_index = path.rfind('src/')
191f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if src_index != -1:
192f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    guard = path[src_index + 4:]
193f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  else:
194f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    guard = path
195f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  guard = guard.upper()
196f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return guard.replace('/', '_').replace('.', '_').replace('\\', '_') + '_'
197f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
198f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)def GetHeaderContents(path, define, version):
199f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  """
200f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Returns what the contents of the header file should be that indicate the given
201f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  revision. Note that the #define is specified as a string, even though it's
202f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  currently always a SVN revision number, in case we need to move to git hashes.
203f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  """
204f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  header_guard = GetHeaderGuard(path)
205f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
206f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  header_contents = """/* Generated by lastchange.py, do not edit.*/
207f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
208f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#ifndef %(header_guard)s
209f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#define %(header_guard)s
210f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
211f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#define %(define)s "%(version)s"
212f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
213f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#endif  // %(header_guard)s
214f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)"""
215f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  header_contents = header_contents % { 'header_guard': header_guard,
216f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                        'define': define,
217f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                        'version': version }
218f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return header_contents
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def WriteIfChanged(file_name, contents):
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Writes the specified contents to the specified file_name
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  iff the contents are different than the current contents.
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    old_contents = open(file_name, 'r').read()
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except EnvironmentError:
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pass
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if contents == old_contents:
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    os.unlink(file_name)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  open(file_name, 'w').write(contents)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main(argv=None):
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if argv is None:
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    argv = sys.argv
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser = optparse.OptionParser(usage="lastchange.py [options]")
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("-d", "--default-lastchange", metavar="FILE",
242f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    help="Default last change input FILE.")
243f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  parser.add_option("-m", "--version-macro",
244f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    help="Name of C #define when using --header. Defaults to " +
245f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    "LAST_CHANGE.",
246f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    default="LAST_CHANGE")
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("-o", "--output", metavar="FILE",
248f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    help="Write last change to FILE. " +
249f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    "Can be combined with --header to write both files.")
250f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  parser.add_option("", "--header", metavar="FILE",
251f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    help="Write last change to FILE as a C/C++ header. " +
252f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    "Can be combined with --output to write both files.")
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("--revision-only", action='store_true',
254f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    help="Just print the SVN revision number. Overrides any " +
255f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    "file-output-related options.")
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option("-s", "--source-dir", metavar="DIR",
257f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                    help="Use repository in the given directory.")
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  opts, args = parser.parse_args(argv[1:])
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  out_file = opts.output
261f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  header = opts.header
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while len(args) and out_file is None:
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if out_file is None:
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      out_file = args.pop(0)
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if args:
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sys.stderr.write('Unexpected arguments: %r\n\n' % args)
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parser.print_help()
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sys.exit(2)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if opts.source_dir:
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    src_dir = opts.source_dir
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    src_dir = os.path.dirname(os.path.abspath(__file__))
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  version_info = FetchVersionInfo(opts.default_lastchange, src_dir)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if version_info.revision == None:
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    version_info.revision = '0'
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if opts.revision_only:
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print version_info.revision
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    contents = "LASTCHANGE=%s\n" % version_info.revision
285f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if not out_file and not opts.header:
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sys.stdout.write(contents)
287f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    else:
288f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if out_file:
289f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        WriteIfChanged(out_file, contents)
290f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if header:
291f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        WriteIfChanged(header,
292f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                       GetHeaderContents(header, opts.version_macro,
293f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                         version_info.revision))
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(main())
300