# coding: utf-8 import json import locale import re import os import subprocess import sys import time import six as six_ INDENT = " " * 4 def _get_vcs_dictionary(vcs_type, *arg): if vcs_type == 'git': return _GitVersion.parse(*arg) else: raise Exception("Unknown VCS type {}".format(str(vcs_type))) def _get_user_locale(): try: if six_.PY3: return [locale.getencoding()] else: return [locale.getdefaultlocale()[1]] except Exception: return [] class _GitVersion(): @classmethod def parse(cls, commit_hash, author_info, summary_info, body_info, tag_info, branch_info, depth=None): r""" Parses output of git rev-parse HEAD git log -1 --format='format:%an <%ae>' git log -1 --format='format:%s' git log -1 --grep='^git-svn-id: ' --format='format:%b' or git log -1 --grep='^Revision: r?\d*' --format='format:%b git describe --exact-match --tags HEAD git describe --exact-match --all HEAD and depth as computed by _get_git_depth '""" info = {} info['hash'] = commit_hash info['commit_author'] = _SystemInfo._to_text(author_info) info['summary'] = _SystemInfo._to_text(summary_info) if 'svn_commit_revision' not in info: url = re.search("git?-svn?-id: (.*)@(\\d*).*", body_info) if url: info['svn_url'] = url.group(1) info['svn_commit_revision'] = int(url.group(2)) if 'svn_commit_revision' not in info: rev = re.search('Revision: r?(\\d*).*', body_info) if rev: info['svn_commit_revision'] = int(rev.group(1)) info['tag'] = tag_info info['branch'] = branch_info info['scm_text'] = cls._format_scm_data(info) info['vcs'] = 'git' if depth: info['patch_number'] = int(depth) return info @staticmethod def _format_scm_data(info): scm_data = "Git info:\n" scm_data += INDENT + "Commit: " + info['hash'] + "\n" scm_data += INDENT + "Branch: " + info['branch'] + "\n" scm_data += INDENT + "Author: " + info['commit_author'] + "\n" scm_data += INDENT + "Summary: " + info['summary'] + "\n" if 'svn_commit_revision' in info or 'svn_url' in info: scm_data += INDENT + "git-svn info:\n" if 'svn_url' in info: scm_data += INDENT + "URL: " + info['svn_url'] + "\n" if 'svn_commit_revision' in info: scm_data += INDENT + "Last Changed Rev: " + str(info['svn_commit_revision']) + "\n" return scm_data @staticmethod def external_data(arc_root): env = os.environ.copy() env['TZ'] = '' hash_args = ['rev-parse', 'HEAD'] author_args = ['log', '-1', '--format=format:%an <%ae>'] summary_args = ['log', '-1', '--format=format:%s'] svn_args = ['log', '-1', '--grep=^git-svn-id: ', '--format=format:%b'] svn_args_alt = ['log', '-1', '--grep=^Revision: r\\?\\d*', '--format=format:%b'] tag_args = ['describe', '--exact-match', '--tags', 'HEAD'] branch_args = ['describe', '--exact-match', '--all', 'HEAD'] # using local 'Popen' wrapper commit = _SystemInfo._system_command_call(['git'] + hash_args, env=env, cwd=arc_root).rstrip() author = _SystemInfo._system_command_call(['git'] + author_args, env=env, cwd=arc_root) commit = _SystemInfo._system_command_call(['git'] + hash_args, env=env, cwd=arc_root).rstrip() author = _SystemInfo._system_command_call(['git'] + author_args, env=env, cwd=arc_root) summary = _SystemInfo._system_command_call(['git'] + summary_args, env=env, cwd=arc_root) svn_id = _SystemInfo._system_command_call(['git'] + svn_args, env=env, cwd=arc_root) if not svn_id: svn_id = _SystemInfo._system_command_call(['git'] + svn_args_alt, env=env, cwd=arc_root) try: tag_info = _SystemInfo._system_command_call(['git'] + tag_args, env=env, cwd=arc_root).splitlines() except Exception: tag_info = [''.encode('utf-8')] try: branch_info = _SystemInfo._system_command_call(['git'] + branch_args, env=env, cwd=arc_root).splitlines() except Exception: branch_info = [''.encode('utf-8')] depth = six_.text_type(_GitVersion._get_git_depth(env, arc_root)).encode('utf-8') # logger.debug('Git info commit:{}, author:{}, summary:{}, svn_id:{}'.format(commit, author, summary, svn_id)) return [commit, author, summary, svn_id, tag_info[0], branch_info[0], depth] # YT's patch number. @staticmethod def _get_git_depth(env, arc_root): graph = {} full_history_args = ["log", "--full-history", "--format=%H %P", "HEAD"] history = _SystemInfo._system_command_call(['git'] + full_history_args, env=env, cwd=arc_root).decode('utf-8') head = None for line in history.splitlines(): values = line.split() if values: if head is None: head = values[0] graph[values[0]] = values[1:] assert head cache = {} stack = [(head, None, False)] while stack: commit, child, calculated = stack.pop() if commit in cache: calculated = True if calculated: if child is not None: cache[child] = max(cache.get(child, 0), cache[commit] + 1) else: stack.append((commit, child, True)) parents = graph[commit] if not parents: cache[commit] = 0 else: for parent in parents: stack.append((parent, commit, False)) return cache[head] class _SystemInfo: LOCALE_LIST = _get_user_locale() + [sys.getfilesystemencoding(), 'utf-8'] @classmethod def get_locale(cls): import codecs for i in cls.LOCALE_LIST: if not i: continue try: codecs.lookup(i) return i except LookupError: continue @staticmethod def _to_text(s): if isinstance(s, six_.binary_type): return s.decode(_SystemInfo.get_locale(), errors='replace') return s @staticmethod def get_user(): sys_user = os.environ.get("USER") if not sys_user: sys_user = os.environ.get("USERNAME") if not sys_user: sys_user = os.environ.get("LOGNAME") if not sys_user: sys_user = "Unknown user" return sys_user @staticmethod def get_date(stamp=None): # Format compatible with SVN-xml format. return time.strftime("%Y-%m-%dT%H:%M:%S.000000Z", time.gmtime(stamp)) @staticmethod def get_timestamp(): # Unix timestamp. return int(time.time()) @staticmethod def get_other_data(src_dir, data_file='local.ymake'): other_data = "Other info:\n" other_data += INDENT + "Build by: " + _SystemInfo.get_user() + "\n" other_data += INDENT + "Top src dir: {}\n".format(src_dir) # logger.debug("Other data: %s", other_data) return other_data @staticmethod def _get_host_info(fake_build_info=False): if fake_build_info: host_info = '*sys localhost 1.0.0 #dummy information ' elif not on_win(): host_info = ' '.join(os.uname()) else: host_info = _SystemInfo._system_command_call("VER") # XXX: check shell from cygwin to call VER this way! return INDENT + INDENT + host_info.strip() + "\n" if host_info else "" @staticmethod def _system_command_call(command, **kwargs): if isinstance(command, list): command = subprocess.list2cmdline(command) try: process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, **kwargs) stdout, stderr = process.communicate() if process.returncode != 0: # logger.debug('{}\nRunning {} failed with exit code {}\n'.format(stderr, command, process.returncode)) raise get_svn_exception()(stdout=stdout, stderr=stderr, rc=process.returncode, cmd=[command]) return stdout except OSError as e: msg = e.strerror errcodes = 'error {}'.format(e.errno) if on_win() and isinstance(e, WindowsError): errcodes += ', win-error {}'.format(e.winerror) try: import ctypes msg = six_.text_type(ctypes.FormatError(e.winerror), _SystemInfo.get_locale()).encode('utf-8') except ImportError: pass # logger.debug('System command call {} failed [{}]: {}\n'.format(command, errcodes, msg)) return None def _get_raw_data(vcs_type, vcs_root): lines = [] if vcs_type == 'git': lines = _GitVersion.external_data(vcs_root) return [l.decode('utf-8') for l in lines] def _get_json(vcs_root): try: vcs_type = "git" info = _get_vcs_dictionary(vcs_type, *_get_raw_data(vcs_type, vcs_root)) return info, vcs_root except Exception: return None, "" def _dump_json( arc_root, info, other_data=None, build_user=None, build_date=None, build_timestamp=0, custom_version='', ): j = {} j['PROGRAM_VERSION'] = info['scm_text'] + "\n" + _SystemInfo._to_text(other_data) j['CUSTOM_VERSION'] = str(_SystemInfo._to_text(custom_version)) j['SCM_DATA'] = info['scm_text'] j['ARCADIA_SOURCE_PATH'] = _SystemInfo._to_text(arc_root) j['ARCADIA_SOURCE_URL'] = info.get('url', info.get('svn_url', '')) j['ARCADIA_SOURCE_REVISION'] = info.get('revision', -1) j['ARCADIA_SOURCE_HG_HASH'] = info.get('hash', '') j['ARCADIA_SOURCE_LAST_CHANGE'] = info.get('commit_revision', info.get('svn_commit_revision', -1)) j['ARCADIA_SOURCE_LAST_AUTHOR'] = info.get('commit_author', '') j['ARCADIA_PATCH_NUMBER'] = info.get('patch_number', 0) j['BUILD_USER'] = _SystemInfo._to_text(build_user) j['VCS'] = info.get('vcs', '') j['BRANCH'] = info.get('branch', '') j['ARCADIA_TAG'] = info.get('tag', '') j['DIRTY'] = info.get('dirty', '') if 'url' in info or 'svn_url' in info: j['SVN_REVISION'] = info.get('svn_commit_revision', info.get('revision', -1)) j['SVN_ARCROOT'] = info.get('url', info.get('svn_url', '')) j['SVN_TIME'] = info.get('commit_date', info.get('svn_commit_date', '')) j['BUILD_DATE'] = build_date j['BUILD_TIMESTAMP'] = build_timestamp return json.dumps(j, sort_keys=True, indent=4, separators=(',', ': ')) def get_version_info(arc_root, custom_version=""): info, vcs_root = _get_json(arc_root) if info is None: return "" return _dump_json( vcs_root, info, other_data=_SystemInfo.get_other_data( src_dir=vcs_root, ), build_user=_SystemInfo.get_user(), build_date=_SystemInfo.get_date(None), build_timestamp=_SystemInfo.get_timestamp(), custom_version=custom_version, ) if __name__ == '__main__': with open(sys.argv[1], 'w') as f: f.write(get_version_info(sys.argv[2]))