1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2012 The Chromium Authors 3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 5*8975f5c5SAndroid Build Coastguard Worker 6*8975f5c5SAndroid Build Coastguard Worker""" 7*8975f5c5SAndroid Build Coastguard Workerlastchange.py -- Chromium revision fetching utility. 8*8975f5c5SAndroid Build Coastguard Worker""" 9*8975f5c5SAndroid Build Coastguard Worker 10*8975f5c5SAndroid Build Coastguard Workerimport argparse 11*8975f5c5SAndroid Build Coastguard Workerimport collections 12*8975f5c5SAndroid Build Coastguard Workerimport datetime 13*8975f5c5SAndroid Build Coastguard Workerimport logging 14*8975f5c5SAndroid Build Coastguard Workerimport os 15*8975f5c5SAndroid Build Coastguard Workerimport subprocess 16*8975f5c5SAndroid Build Coastguard Workerimport sys 17*8975f5c5SAndroid Build Coastguard Worker 18*8975f5c5SAndroid Build Coastguard Worker_THIS_DIR = os.path.abspath(os.path.dirname(__file__)) 19*8975f5c5SAndroid Build Coastguard Worker_ROOT_DIR = os.path.abspath( 20*8975f5c5SAndroid Build Coastguard Worker os.path.join(_THIS_DIR, "..", "..", "third_party/depot_tools")) 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Workersys.path.insert(0, _ROOT_DIR) 23*8975f5c5SAndroid Build Coastguard Worker 24*8975f5c5SAndroid Build Coastguard Workerimport gclient_utils 25*8975f5c5SAndroid Build Coastguard Worker 26*8975f5c5SAndroid Build Coastguard WorkerVersionInfo = collections.namedtuple("VersionInfo", 27*8975f5c5SAndroid Build Coastguard Worker ("revision_id", "revision", "timestamp")) 28*8975f5c5SAndroid Build Coastguard Worker_EMPTY_VERSION_INFO = VersionInfo('0' * 40, '0' * 40, 0) 29*8975f5c5SAndroid Build Coastguard Worker 30*8975f5c5SAndroid Build Coastguard Workerclass GitError(Exception): 31*8975f5c5SAndroid Build Coastguard Worker pass 32*8975f5c5SAndroid Build Coastguard Worker 33*8975f5c5SAndroid Build Coastguard Worker# This function exists for compatibility with logic outside this 34*8975f5c5SAndroid Build Coastguard Worker# repository that uses this file as a library. 35*8975f5c5SAndroid Build Coastguard Worker# TODO(eliribble) remove this function after it has been ported into 36*8975f5c5SAndroid Build Coastguard Worker# the repositories that depend on it 37*8975f5c5SAndroid Build Coastguard Workerdef RunGitCommand(directory, command): 38*8975f5c5SAndroid Build Coastguard Worker """ 39*8975f5c5SAndroid Build Coastguard Worker Launches git subcommand. 40*8975f5c5SAndroid Build Coastguard Worker 41*8975f5c5SAndroid Build Coastguard Worker Errors are swallowed. 42*8975f5c5SAndroid Build Coastguard Worker 43*8975f5c5SAndroid Build Coastguard Worker Returns: 44*8975f5c5SAndroid Build Coastguard Worker A process object or None. 45*8975f5c5SAndroid Build Coastguard Worker """ 46*8975f5c5SAndroid Build Coastguard Worker command = ['git'] + command 47*8975f5c5SAndroid Build Coastguard Worker # Force shell usage under cygwin. This is a workaround for 48*8975f5c5SAndroid Build Coastguard Worker # mysterious loss of cwd while invoking cygwin's git. 49*8975f5c5SAndroid Build Coastguard Worker # We can't just pass shell=True to Popen, as under win32 this will 50*8975f5c5SAndroid Build Coastguard Worker # cause CMD to be used, while we explicitly want a cygwin shell. 51*8975f5c5SAndroid Build Coastguard Worker if sys.platform == 'cygwin': 52*8975f5c5SAndroid Build Coastguard Worker command = ['sh', '-c', ' '.join(command)] 53*8975f5c5SAndroid Build Coastguard Worker try: 54*8975f5c5SAndroid Build Coastguard Worker proc = subprocess.Popen(command, 55*8975f5c5SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 56*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.PIPE, 57*8975f5c5SAndroid Build Coastguard Worker cwd=directory, 58*8975f5c5SAndroid Build Coastguard Worker shell=(sys.platform=='win32')) 59*8975f5c5SAndroid Build Coastguard Worker return proc 60*8975f5c5SAndroid Build Coastguard Worker except OSError as e: 61*8975f5c5SAndroid Build Coastguard Worker logging.error('Command %r failed: %s' % (' '.join(command), e)) 62*8975f5c5SAndroid Build Coastguard Worker return None 63*8975f5c5SAndroid Build Coastguard Worker 64*8975f5c5SAndroid Build Coastguard Worker 65*8975f5c5SAndroid Build Coastguard Workerdef _RunGitCommand(directory, command): 66*8975f5c5SAndroid Build Coastguard Worker """Launches git subcommand. 67*8975f5c5SAndroid Build Coastguard Worker 68*8975f5c5SAndroid Build Coastguard Worker Returns: 69*8975f5c5SAndroid Build Coastguard Worker The stripped stdout of the git command. 70*8975f5c5SAndroid Build Coastguard Worker Raises: 71*8975f5c5SAndroid Build Coastguard Worker GitError on failure, including a nonzero return code. 72*8975f5c5SAndroid Build Coastguard Worker """ 73*8975f5c5SAndroid Build Coastguard Worker command = ['git'] + command 74*8975f5c5SAndroid Build Coastguard Worker # Force shell usage under cygwin. This is a workaround for 75*8975f5c5SAndroid Build Coastguard Worker # mysterious loss of cwd while invoking cygwin's git. 76*8975f5c5SAndroid Build Coastguard Worker # We can't just pass shell=True to Popen, as under win32 this will 77*8975f5c5SAndroid Build Coastguard Worker # cause CMD to be used, while we explicitly want a cygwin shell. 78*8975f5c5SAndroid Build Coastguard Worker if sys.platform == 'cygwin': 79*8975f5c5SAndroid Build Coastguard Worker command = ['sh', '-c', ' '.join(command)] 80*8975f5c5SAndroid Build Coastguard Worker try: 81*8975f5c5SAndroid Build Coastguard Worker logging.info("Executing '%s' in %s", ' '.join(command), directory) 82*8975f5c5SAndroid Build Coastguard Worker proc = subprocess.Popen(command, 83*8975f5c5SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 84*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.PIPE, 85*8975f5c5SAndroid Build Coastguard Worker cwd=directory, 86*8975f5c5SAndroid Build Coastguard Worker shell=(sys.platform=='win32')) 87*8975f5c5SAndroid Build Coastguard Worker stdout, stderr = tuple(x.decode(encoding='utf_8') 88*8975f5c5SAndroid Build Coastguard Worker for x in proc.communicate()) 89*8975f5c5SAndroid Build Coastguard Worker stdout = stdout.strip() 90*8975f5c5SAndroid Build Coastguard Worker stderr = stderr.strip() 91*8975f5c5SAndroid Build Coastguard Worker logging.debug("returncode: %d", proc.returncode) 92*8975f5c5SAndroid Build Coastguard Worker logging.debug("stdout: %s", stdout) 93*8975f5c5SAndroid Build Coastguard Worker logging.debug("stderr: %s", stderr) 94*8975f5c5SAndroid Build Coastguard Worker if proc.returncode != 0 or not stdout: 95*8975f5c5SAndroid Build Coastguard Worker raise GitError(( 96*8975f5c5SAndroid Build Coastguard Worker "Git command '{}' in {} failed: " 97*8975f5c5SAndroid Build Coastguard Worker "rc={}, stdout='{}' stderr='{}'").format( 98*8975f5c5SAndroid Build Coastguard Worker " ".join(command), directory, proc.returncode, stdout, stderr)) 99*8975f5c5SAndroid Build Coastguard Worker return stdout 100*8975f5c5SAndroid Build Coastguard Worker except OSError as e: 101*8975f5c5SAndroid Build Coastguard Worker raise GitError("Git command 'git {}' in {} failed: {}".format( 102*8975f5c5SAndroid Build Coastguard Worker " ".join(command), directory, e)) 103*8975f5c5SAndroid Build Coastguard Worker 104*8975f5c5SAndroid Build Coastguard Worker 105*8975f5c5SAndroid Build Coastguard Workerdef GetMergeBase(directory, ref): 106*8975f5c5SAndroid Build Coastguard Worker """ 107*8975f5c5SAndroid Build Coastguard Worker Return the merge-base of HEAD and ref. 108*8975f5c5SAndroid Build Coastguard Worker 109*8975f5c5SAndroid Build Coastguard Worker Args: 110*8975f5c5SAndroid Build Coastguard Worker directory: The directory containing the .git directory. 111*8975f5c5SAndroid Build Coastguard Worker ref: The ref to use to find the merge base. 112*8975f5c5SAndroid Build Coastguard Worker Returns: 113*8975f5c5SAndroid Build Coastguard Worker The git commit SHA of the merge-base as a string. 114*8975f5c5SAndroid Build Coastguard Worker """ 115*8975f5c5SAndroid Build Coastguard Worker logging.debug("Calculating merge base between HEAD and %s in %s", 116*8975f5c5SAndroid Build Coastguard Worker ref, directory) 117*8975f5c5SAndroid Build Coastguard Worker command = ['merge-base', 'HEAD', ref] 118*8975f5c5SAndroid Build Coastguard Worker return _RunGitCommand(directory, command) 119*8975f5c5SAndroid Build Coastguard Worker 120*8975f5c5SAndroid Build Coastguard Worker 121*8975f5c5SAndroid Build Coastguard Workerdef FetchGitRevision(directory, commit_filter, start_commit="HEAD"): 122*8975f5c5SAndroid Build Coastguard Worker """ 123*8975f5c5SAndroid Build Coastguard Worker Fetch the Git hash (and Cr-Commit-Position if any) for a given directory. 124*8975f5c5SAndroid Build Coastguard Worker 125*8975f5c5SAndroid Build Coastguard Worker Args: 126*8975f5c5SAndroid Build Coastguard Worker directory: The directory containing the .git directory. 127*8975f5c5SAndroid Build Coastguard Worker commit_filter: A filter to supply to grep to filter commits 128*8975f5c5SAndroid Build Coastguard Worker start_commit: A commit identifier. The result of this function 129*8975f5c5SAndroid Build Coastguard Worker will be limited to only consider commits before the provided 130*8975f5c5SAndroid Build Coastguard Worker commit. 131*8975f5c5SAndroid Build Coastguard Worker Returns: 132*8975f5c5SAndroid Build Coastguard Worker A VersionInfo object. On error all values will be 0. 133*8975f5c5SAndroid Build Coastguard Worker """ 134*8975f5c5SAndroid Build Coastguard Worker hash_ = '' 135*8975f5c5SAndroid Build Coastguard Worker 136*8975f5c5SAndroid Build Coastguard Worker git_args = ['log', '-1', '--format=%H %ct'] 137*8975f5c5SAndroid Build Coastguard Worker if commit_filter is not None: 138*8975f5c5SAndroid Build Coastguard Worker git_args.append('--grep=' + commit_filter) 139*8975f5c5SAndroid Build Coastguard Worker 140*8975f5c5SAndroid Build Coastguard Worker git_args.append(start_commit) 141*8975f5c5SAndroid Build Coastguard Worker 142*8975f5c5SAndroid Build Coastguard Worker output = _RunGitCommand(directory, git_args) 143*8975f5c5SAndroid Build Coastguard Worker hash_, commit_timestamp = output.split() 144*8975f5c5SAndroid Build Coastguard Worker if not hash_: 145*8975f5c5SAndroid Build Coastguard Worker return VersionInfo('0', '0', 0) 146*8975f5c5SAndroid Build Coastguard Worker 147*8975f5c5SAndroid Build Coastguard Worker revision = hash_ 148*8975f5c5SAndroid Build Coastguard Worker output = _RunGitCommand(directory, ['cat-file', 'commit', hash_]) 149*8975f5c5SAndroid Build Coastguard Worker for line in reversed(output.splitlines()): 150*8975f5c5SAndroid Build Coastguard Worker if line.startswith('Cr-Commit-Position:'): 151*8975f5c5SAndroid Build Coastguard Worker pos = line.rsplit()[-1].strip() 152*8975f5c5SAndroid Build Coastguard Worker logging.debug("Found Cr-Commit-Position '%s'", pos) 153*8975f5c5SAndroid Build Coastguard Worker revision = "{}-{}".format(hash_, pos) 154*8975f5c5SAndroid Build Coastguard Worker break 155*8975f5c5SAndroid Build Coastguard Worker return VersionInfo(hash_, revision, int(commit_timestamp)) 156*8975f5c5SAndroid Build Coastguard Worker 157*8975f5c5SAndroid Build Coastguard Worker 158*8975f5c5SAndroid Build Coastguard Workerdef GetHeaderGuard(path): 159*8975f5c5SAndroid Build Coastguard Worker """ 160*8975f5c5SAndroid Build Coastguard Worker Returns the header #define guard for the given file path. 161*8975f5c5SAndroid Build Coastguard Worker This treats everything after the last instance of "src/" as being a 162*8975f5c5SAndroid Build Coastguard Worker relevant part of the guard. If there is no "src/", then the entire path 163*8975f5c5SAndroid Build Coastguard Worker is used. 164*8975f5c5SAndroid Build Coastguard Worker """ 165*8975f5c5SAndroid Build Coastguard Worker src_index = path.rfind('src/') 166*8975f5c5SAndroid Build Coastguard Worker if src_index != -1: 167*8975f5c5SAndroid Build Coastguard Worker guard = path[src_index + 4:] 168*8975f5c5SAndroid Build Coastguard Worker else: 169*8975f5c5SAndroid Build Coastguard Worker guard = path 170*8975f5c5SAndroid Build Coastguard Worker guard = guard.upper() 171*8975f5c5SAndroid Build Coastguard Worker return guard.replace('/', '_').replace('.', '_').replace('\\', '_') + '_' 172*8975f5c5SAndroid Build Coastguard Worker 173*8975f5c5SAndroid Build Coastguard Worker 174*8975f5c5SAndroid Build Coastguard Workerdef GetHeaderContents(path, define, version): 175*8975f5c5SAndroid Build Coastguard Worker """ 176*8975f5c5SAndroid Build Coastguard Worker Returns what the contents of the header file should be that indicate the given 177*8975f5c5SAndroid Build Coastguard Worker revision. 178*8975f5c5SAndroid Build Coastguard Worker """ 179*8975f5c5SAndroid Build Coastguard Worker header_guard = GetHeaderGuard(path) 180*8975f5c5SAndroid Build Coastguard Worker 181*8975f5c5SAndroid Build Coastguard Worker header_contents = """/* Generated by lastchange.py, do not edit.*/ 182*8975f5c5SAndroid Build Coastguard Worker 183*8975f5c5SAndroid Build Coastguard Worker#ifndef %(header_guard)s 184*8975f5c5SAndroid Build Coastguard Worker#define %(header_guard)s 185*8975f5c5SAndroid Build Coastguard Worker 186*8975f5c5SAndroid Build Coastguard Worker#define %(define)s "%(version)s" 187*8975f5c5SAndroid Build Coastguard Worker 188*8975f5c5SAndroid Build Coastguard Worker#endif // %(header_guard)s 189*8975f5c5SAndroid Build Coastguard Worker""" 190*8975f5c5SAndroid Build Coastguard Worker header_contents = header_contents % { 'header_guard': header_guard, 191*8975f5c5SAndroid Build Coastguard Worker 'define': define, 192*8975f5c5SAndroid Build Coastguard Worker 'version': version } 193*8975f5c5SAndroid Build Coastguard Worker return header_contents 194*8975f5c5SAndroid Build Coastguard Worker 195*8975f5c5SAndroid Build Coastguard Worker 196*8975f5c5SAndroid Build Coastguard Workerdef GetGitTopDirectory(source_dir): 197*8975f5c5SAndroid Build Coastguard Worker """Get the top git directory - the directory that contains the .git directory. 198*8975f5c5SAndroid Build Coastguard Worker 199*8975f5c5SAndroid Build Coastguard Worker Args: 200*8975f5c5SAndroid Build Coastguard Worker source_dir: The directory to search. 201*8975f5c5SAndroid Build Coastguard Worker Returns: 202*8975f5c5SAndroid Build Coastguard Worker The output of "git rev-parse --show-toplevel" as a string 203*8975f5c5SAndroid Build Coastguard Worker """ 204*8975f5c5SAndroid Build Coastguard Worker return _RunGitCommand(source_dir, ['rev-parse', '--show-toplevel']) 205*8975f5c5SAndroid Build Coastguard Worker 206*8975f5c5SAndroid Build Coastguard Worker 207*8975f5c5SAndroid Build Coastguard Workerdef WriteIfChanged(file_name, contents): 208*8975f5c5SAndroid Build Coastguard Worker """ 209*8975f5c5SAndroid Build Coastguard Worker Writes the specified contents to the specified file_name 210*8975f5c5SAndroid Build Coastguard Worker iff the contents are different than the current contents. 211*8975f5c5SAndroid Build Coastguard Worker Returns if new data was written. 212*8975f5c5SAndroid Build Coastguard Worker """ 213*8975f5c5SAndroid Build Coastguard Worker try: 214*8975f5c5SAndroid Build Coastguard Worker old_contents = open(file_name, 'r').read() 215*8975f5c5SAndroid Build Coastguard Worker except EnvironmentError: 216*8975f5c5SAndroid Build Coastguard Worker pass 217*8975f5c5SAndroid Build Coastguard Worker else: 218*8975f5c5SAndroid Build Coastguard Worker if contents == old_contents: 219*8975f5c5SAndroid Build Coastguard Worker return False 220*8975f5c5SAndroid Build Coastguard Worker os.unlink(file_name) 221*8975f5c5SAndroid Build Coastguard Worker open(file_name, 'w').write(contents) 222*8975f5c5SAndroid Build Coastguard Worker return True 223*8975f5c5SAndroid Build Coastguard Worker 224*8975f5c5SAndroid Build Coastguard Worker 225*8975f5c5SAndroid Build Coastguard Workerdef GetVersion(source_dir, commit_filter, merge_base_ref): 226*8975f5c5SAndroid Build Coastguard Worker """ 227*8975f5c5SAndroid Build Coastguard Worker Returns the version information for the given source directory. 228*8975f5c5SAndroid Build Coastguard Worker """ 229*8975f5c5SAndroid Build Coastguard Worker if gclient_utils.IsEnvCog(): 230*8975f5c5SAndroid Build Coastguard Worker return _EMPTY_VERSION_INFO 231*8975f5c5SAndroid Build Coastguard Worker 232*8975f5c5SAndroid Build Coastguard Worker git_top_dir = None 233*8975f5c5SAndroid Build Coastguard Worker try: 234*8975f5c5SAndroid Build Coastguard Worker git_top_dir = GetGitTopDirectory(source_dir) 235*8975f5c5SAndroid Build Coastguard Worker except GitError as e: 236*8975f5c5SAndroid Build Coastguard Worker logging.warning("Failed to get git top directory from '%s': %s", source_dir, 237*8975f5c5SAndroid Build Coastguard Worker e) 238*8975f5c5SAndroid Build Coastguard Worker 239*8975f5c5SAndroid Build Coastguard Worker merge_base_sha = 'HEAD' 240*8975f5c5SAndroid Build Coastguard Worker if git_top_dir and merge_base_ref: 241*8975f5c5SAndroid Build Coastguard Worker try: 242*8975f5c5SAndroid Build Coastguard Worker merge_base_sha = GetMergeBase(git_top_dir, merge_base_ref) 243*8975f5c5SAndroid Build Coastguard Worker except GitError as e: 244*8975f5c5SAndroid Build Coastguard Worker logging.error( 245*8975f5c5SAndroid Build Coastguard Worker "You requested a --merge-base-ref value of '%s' but no " 246*8975f5c5SAndroid Build Coastguard Worker "merge base could be found between it and HEAD. Git " 247*8975f5c5SAndroid Build Coastguard Worker "reports: %s", merge_base_ref, e) 248*8975f5c5SAndroid Build Coastguard Worker return None 249*8975f5c5SAndroid Build Coastguard Worker 250*8975f5c5SAndroid Build Coastguard Worker version_info = None 251*8975f5c5SAndroid Build Coastguard Worker if git_top_dir: 252*8975f5c5SAndroid Build Coastguard Worker try: 253*8975f5c5SAndroid Build Coastguard Worker version_info = FetchGitRevision(git_top_dir, commit_filter, 254*8975f5c5SAndroid Build Coastguard Worker merge_base_sha) 255*8975f5c5SAndroid Build Coastguard Worker except GitError as e: 256*8975f5c5SAndroid Build Coastguard Worker logging.error("Failed to get version info: %s", e) 257*8975f5c5SAndroid Build Coastguard Worker 258*8975f5c5SAndroid Build Coastguard Worker if not version_info: 259*8975f5c5SAndroid Build Coastguard Worker logging.warning( 260*8975f5c5SAndroid Build Coastguard Worker "Falling back to a version of 0.0.0 to allow script to " 261*8975f5c5SAndroid Build Coastguard Worker "finish. This is normal if you are bootstrapping a new environment " 262*8975f5c5SAndroid Build Coastguard Worker "or do not have a git repository for any other reason. If not, this " 263*8975f5c5SAndroid Build Coastguard Worker "could represent a serious error.") 264*8975f5c5SAndroid Build Coastguard Worker # Use a dummy revision that has the same length as a Git commit hash, 265*8975f5c5SAndroid Build Coastguard Worker # same as what we use in build/util/LASTCHANGE.dummy. 266*8975f5c5SAndroid Build Coastguard Worker version_info = _EMPTY_VERSION_INFO 267*8975f5c5SAndroid Build Coastguard Worker 268*8975f5c5SAndroid Build Coastguard Worker return version_info 269*8975f5c5SAndroid Build Coastguard Worker 270*8975f5c5SAndroid Build Coastguard Worker 271*8975f5c5SAndroid Build Coastguard Workerdef main(argv=None): 272*8975f5c5SAndroid Build Coastguard Worker if argv is None: 273*8975f5c5SAndroid Build Coastguard Worker argv = sys.argv 274*8975f5c5SAndroid Build Coastguard Worker 275*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(usage="lastchange.py [options]") 276*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("-m", "--version-macro", 277*8975f5c5SAndroid Build Coastguard Worker help=("Name of C #define when using --header. Defaults to " 278*8975f5c5SAndroid Build Coastguard Worker "LAST_CHANGE.")) 279*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("-o", 280*8975f5c5SAndroid Build Coastguard Worker "--output", 281*8975f5c5SAndroid Build Coastguard Worker metavar="FILE", 282*8975f5c5SAndroid Build Coastguard Worker help=("Write last change to FILE. " 283*8975f5c5SAndroid Build Coastguard Worker "Can be combined with other file-output-related " 284*8975f5c5SAndroid Build Coastguard Worker "options to write multiple files.")) 285*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--header", 286*8975f5c5SAndroid Build Coastguard Worker metavar="FILE", 287*8975f5c5SAndroid Build Coastguard Worker help=("Write last change to FILE as a C/C++ header. " 288*8975f5c5SAndroid Build Coastguard Worker "Can be combined with other file-output-related " 289*8975f5c5SAndroid Build Coastguard Worker "options to write multiple files.")) 290*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--revision", 291*8975f5c5SAndroid Build Coastguard Worker metavar="FILE", 292*8975f5c5SAndroid Build Coastguard Worker help=("Write last change to FILE as a one-line revision. " 293*8975f5c5SAndroid Build Coastguard Worker "Can be combined with other file-output-related " 294*8975f5c5SAndroid Build Coastguard Worker "options to write multiple files.")) 295*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--merge-base-ref", 296*8975f5c5SAndroid Build Coastguard Worker default=None, 297*8975f5c5SAndroid Build Coastguard Worker help=("Only consider changes since the merge " 298*8975f5c5SAndroid Build Coastguard Worker "base between HEAD and the provided ref")) 299*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--revision-id-only", action='store_true', 300*8975f5c5SAndroid Build Coastguard Worker help=("Output the revision as a VCS revision ID only (in " 301*8975f5c5SAndroid Build Coastguard Worker "Git, a 40-character commit hash, excluding the " 302*8975f5c5SAndroid Build Coastguard Worker "Cr-Commit-Position).")) 303*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--revision-id-prefix", 304*8975f5c5SAndroid Build Coastguard Worker metavar="PREFIX", 305*8975f5c5SAndroid Build Coastguard Worker help=("Adds a string prefix to the VCS revision ID.")) 306*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--print-only", action="store_true", 307*8975f5c5SAndroid Build Coastguard Worker help=("Just print the revision string. Overrides any " 308*8975f5c5SAndroid Build Coastguard Worker "file-output-related options.")) 309*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("-s", "--source-dir", metavar="DIR", 310*8975f5c5SAndroid Build Coastguard Worker help="Use repository in the given directory.") 311*8975f5c5SAndroid Build Coastguard Worker parser.add_argument("--filter", metavar="REGEX", 312*8975f5c5SAndroid Build Coastguard Worker help=("Only use log entries where the commit message " 313*8975f5c5SAndroid Build Coastguard Worker "matches the supplied filter regex. Defaults to " 314*8975f5c5SAndroid Build Coastguard Worker "'^Change-Id:' to suppress local commits."), 315*8975f5c5SAndroid Build Coastguard Worker default='^Change-Id:') 316*8975f5c5SAndroid Build Coastguard Worker 317*8975f5c5SAndroid Build Coastguard Worker args, extras = parser.parse_known_args(argv[1:]) 318*8975f5c5SAndroid Build Coastguard Worker 319*8975f5c5SAndroid Build Coastguard Worker logging.basicConfig(level=logging.WARNING) 320*8975f5c5SAndroid Build Coastguard Worker 321*8975f5c5SAndroid Build Coastguard Worker out_file = args.output 322*8975f5c5SAndroid Build Coastguard Worker header = args.header 323*8975f5c5SAndroid Build Coastguard Worker revision = args.revision 324*8975f5c5SAndroid Build Coastguard Worker commit_filter=args.filter 325*8975f5c5SAndroid Build Coastguard Worker 326*8975f5c5SAndroid Build Coastguard Worker while len(extras) and out_file is None: 327*8975f5c5SAndroid Build Coastguard Worker if out_file is None: 328*8975f5c5SAndroid Build Coastguard Worker out_file = extras.pop(0) 329*8975f5c5SAndroid Build Coastguard Worker if extras: 330*8975f5c5SAndroid Build Coastguard Worker sys.stderr.write('Unexpected arguments: %r\n\n' % extras) 331*8975f5c5SAndroid Build Coastguard Worker parser.print_help() 332*8975f5c5SAndroid Build Coastguard Worker sys.exit(2) 333*8975f5c5SAndroid Build Coastguard Worker 334*8975f5c5SAndroid Build Coastguard Worker source_dir = args.source_dir or os.path.dirname(os.path.abspath(__file__)) 335*8975f5c5SAndroid Build Coastguard Worker 336*8975f5c5SAndroid Build Coastguard Worker version_info = GetVersion(source_dir, commit_filter, args.merge_base_ref) 337*8975f5c5SAndroid Build Coastguard Worker 338*8975f5c5SAndroid Build Coastguard Worker revision_string = version_info.revision 339*8975f5c5SAndroid Build Coastguard Worker if args.revision_id_only: 340*8975f5c5SAndroid Build Coastguard Worker revision_string = version_info.revision_id 341*8975f5c5SAndroid Build Coastguard Worker 342*8975f5c5SAndroid Build Coastguard Worker if args.revision_id_prefix: 343*8975f5c5SAndroid Build Coastguard Worker revision_string = args.revision_id_prefix + revision_string 344*8975f5c5SAndroid Build Coastguard Worker 345*8975f5c5SAndroid Build Coastguard Worker if args.print_only: 346*8975f5c5SAndroid Build Coastguard Worker print(revision_string) 347*8975f5c5SAndroid Build Coastguard Worker else: 348*8975f5c5SAndroid Build Coastguard Worker lastchange_year = datetime.datetime.fromtimestamp( 349*8975f5c5SAndroid Build Coastguard Worker version_info.timestamp, datetime.timezone.utc).year 350*8975f5c5SAndroid Build Coastguard Worker contents_lines = [ 351*8975f5c5SAndroid Build Coastguard Worker "LASTCHANGE=%s" % revision_string, 352*8975f5c5SAndroid Build Coastguard Worker "LASTCHANGE_YEAR=%s" % lastchange_year, 353*8975f5c5SAndroid Build Coastguard Worker ] 354*8975f5c5SAndroid Build Coastguard Worker contents = '\n'.join(contents_lines) + '\n' 355*8975f5c5SAndroid Build Coastguard Worker if not out_file and not header and not revision: 356*8975f5c5SAndroid Build Coastguard Worker sys.stdout.write(contents) 357*8975f5c5SAndroid Build Coastguard Worker else: 358*8975f5c5SAndroid Build Coastguard Worker if out_file: 359*8975f5c5SAndroid Build Coastguard Worker committime_file = out_file + '.committime' 360*8975f5c5SAndroid Build Coastguard Worker out_changed = WriteIfChanged(out_file, contents) 361*8975f5c5SAndroid Build Coastguard Worker if out_changed or not os.path.exists(committime_file): 362*8975f5c5SAndroid Build Coastguard Worker with open(committime_file, 'w') as timefile: 363*8975f5c5SAndroid Build Coastguard Worker timefile.write(str(version_info.timestamp)) 364*8975f5c5SAndroid Build Coastguard Worker if header: 365*8975f5c5SAndroid Build Coastguard Worker WriteIfChanged(header, 366*8975f5c5SAndroid Build Coastguard Worker GetHeaderContents(header, args.version_macro, 367*8975f5c5SAndroid Build Coastguard Worker revision_string)) 368*8975f5c5SAndroid Build Coastguard Worker if revision: 369*8975f5c5SAndroid Build Coastguard Worker WriteIfChanged(revision, revision_string) 370*8975f5c5SAndroid Build Coastguard Worker 371*8975f5c5SAndroid Build Coastguard Worker return 0 372*8975f5c5SAndroid Build Coastguard Worker 373*8975f5c5SAndroid Build Coastguard Worker 374*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 375*8975f5c5SAndroid Build Coastguard Worker sys.exit(main()) 376