aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobot-ya-builder <robot-ya-builder@yandex-team.com>2023-07-28 10:21:14 +0300
committerrobot-ya-builder <robot-ya-builder@yandex-team.com>2023-07-28 10:21:14 +0300
commit0a85d24502a97031d84f0a0bbf6486636cb39f13 (patch)
treec9feba2ddab333299293ce777895a83ff694bedd
parentef61acdadf82081f143c7d7611204191f1285e33 (diff)
downloadydb-0a85d24502a97031d84f0a0bbf6486636cb39f13.tar.gz
External build system generator release 44
Update tools: yexport
-rw-r--r--build/external_resources/yexport/resources.json2
-rw-r--r--scripts/export_script_gen.py126
-rw-r--r--scripts/generate_vcs_info.py321
-rw-r--r--scripts/split_unittest.py80
4 files changed, 528 insertions, 1 deletions
diff --git a/build/external_resources/yexport/resources.json b/build/external_resources/yexport/resources.json
index 2e83fa4247..571f01ee81 100644
--- a/build/external_resources/yexport/resources.json
+++ b/build/external_resources/yexport/resources.json
@@ -1,7 +1,7 @@
{
"by_platform": {
"linux": {
- "uri": "sbr:4771827313"
+ "uri": "sbr:4829977301"
}
}
}
diff --git a/scripts/export_script_gen.py b/scripts/export_script_gen.py
new file mode 100644
index 0000000000..6bf68fef7c
--- /dev/null
+++ b/scripts/export_script_gen.py
@@ -0,0 +1,126 @@
+import argparse
+import collections
+import sys
+
+
+def parse_export_file(src):
+ for line in src:
+ line = line.strip()
+
+ if line and '#' not in line:
+ words = line.split()
+ if len(words) == 2 and words[0] == 'linux_version':
+ yield {'linux_version': words[1]}
+ elif len(words) == 2:
+ yield {'lang': words[0], 'sym': words[1]}
+ elif len(words) == 1:
+ yield {'lang': 'C', 'sym': words[0]}
+ else:
+ raise Exception('unsupported exports line: "{}"'.format(line))
+
+
+def to_c(sym):
+ symbols = collections.deque(sym.split('::'))
+ c_prefixes = [ # demangle prefixes for c++ symbols
+ '_ZN', # namespace
+ '_ZTIN', # typeinfo for
+ '_ZTSN', # typeinfo name for
+ '_ZTTN', # VTT for
+ '_ZTVN', # vtable for
+ '_ZNK', # const methods
+ ]
+ c_sym = ''
+ while symbols:
+ s = symbols.popleft()
+ if s == '*':
+ c_sym += '*'
+ break
+ if '*' in s and len(s) > 1:
+ raise Exception('Unsupported format, cannot guess length of symbol: ' + s)
+ c_sym += str(len(s)) + s
+ if symbols:
+ raise Exception('Unsupported format: ' + sym)
+ if c_sym[-1] != '*':
+ c_sym += 'E*'
+ return ['{prefix}{sym}'.format(prefix=prefix, sym=c_sym) for prefix in c_prefixes]
+
+
+def to_gnu(src, dest):
+ d = collections.defaultdict(list)
+ version = None
+ for item in parse_export_file(src):
+ if item.get('linux_version'):
+ if not version:
+ version = item.get('linux_version')
+ else:
+ raise Exception('More than one linux_version defined')
+ elif item['lang'] == 'C++':
+ d['C'].extend(to_c(item['sym']))
+ else:
+ d[item['lang']].append(item['sym'])
+
+ if version:
+ dest.write('{} {{\nglobal:\n'.format(version))
+ else:
+ dest.write('{\nglobal:\n')
+
+ for k, v in d.items():
+ dest.write(' extern "' + k + '" {\n')
+
+ for x in v:
+ dest.write(' ' + x + ';\n')
+
+ dest.write(' };\n')
+
+ dest.write('local: *;\n};\n')
+
+
+def to_msvc(src, dest):
+ dest.write('EXPORTS\n')
+ for item in parse_export_file(src):
+ if item.get('linux_version'):
+ continue
+ if item.get('lang') == 'C':
+ dest.write(' {}\n'.format(item.get('sym')))
+
+
+def to_darwin(src, dest):
+ pre = ''
+ for item in parse_export_file(src):
+ if item.get('linux_version'):
+ continue
+
+ if item['lang'] == 'C':
+ dest.write(pre + '-Wl,-exported_symbol,_' + item['sym'])
+ elif item['lang'] == 'C++':
+ for sym in to_c(item['sym']):
+ dest.write(pre + '-Wl,-exported_symbol,_' + sym)
+ else:
+ raise Exception('unsupported lang: ' + item['lang'])
+ if pre == '':
+ pre = ' '
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Convert self-invented platform independent export file format to the format required by specific linker')
+ parser.add_argument('src', type=argparse.FileType('r', encoding='UTF-8'), help='platform independent export file path')
+ parser.add_argument('dest', type=argparse.FileType('w', encoding='UTF-8'), help='destination export file for required linker')
+ parser.add_argument('--format', help='destination file type format: gnu, msvc or darwin')
+
+ args = parser.parse_args()
+ if args.format == 'gnu':
+ to_gnu(args.src, args.dest)
+ elif args.format == 'msvc':
+ to_msvc(args.src, args.dest)
+ elif args.format == 'darwin':
+ to_darwin(args.src, args.dest)
+ else:
+ print('Unknown destination file format: {}'.format(args.format), file=sys.stderr)
+ sys.exit(1)
+
+ args.src.close()
+ args.dest.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/generate_vcs_info.py b/scripts/generate_vcs_info.py
new file mode 100644
index 0000000000..770d2ec802
--- /dev/null
+++ b/scripts/generate_vcs_info.py
@@ -0,0 +1,321 @@
+# 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]))
+
diff --git a/scripts/split_unittest.py b/scripts/split_unittest.py
new file mode 100644
index 0000000000..8874b8b915
--- /dev/null
+++ b/scripts/split_unittest.py
@@ -0,0 +1,80 @@
+import argparse
+import tempfile
+import shlex
+import subprocess
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--split-factor", type=int, default=0)
+ parser.add_argument("--shard", type=int, default=0)
+ parser.add_argument("--fork-mode", type=str, default="SEQUENTIAL")
+ parser.add_argument("command", nargs=argparse.REMAINDER)
+ return parser.parse_args()
+
+
+def get_sequential_chunk(tests, modulo, modulo_index):
+ chunk_size = len(tests) // modulo
+ not_used = len(tests) % modulo
+ shift = chunk_size + (modulo_index < not_used)
+ start = chunk_size * modulo_index + min(modulo_index, not_used)
+ end = start + shift
+ return [] if end > len(tests) else tests[start:end]
+
+
+def get_shuffled_chunk(tests, modulo, modulo_index):
+ result_tests = []
+ for i, test in enumerate(tests):
+ if i % modulo == modulo_index:
+ result_tests.append(test)
+ return result_tests
+
+
+def list_tests(binary):
+ with tempfile.NamedTemporaryFile() as tmpfile:
+ cmd = [binary, "--list-verbose", "--list-path", tmpfile.name]
+ subprocess.check_call(cmd)
+
+ with open(tmpfile.name) as afile:
+ lines = afile.read().strip().split("\n")
+ lines = [x.strip() for x in lines]
+ return [x for x in lines if x]
+
+
+def get_shard_tests(args):
+ test_names = list_tests(args.command[0])
+ test_names = sorted(test_names)
+
+ if args.fork_mode == "MODULO":
+ return get_shuffled_chunk(test_names, args.split_factor, args.shard)
+ elif args.fork_mode == "SEQUENTIAL":
+ return get_sequential_chunk(test_names, args.split_factor, args.shard)
+ else:
+ raise ValueError("detected unknown partition mode: {}".format(args.fork_mode))
+
+
+def get_shard_cmd_args(args):
+ return ["+{}".format(x) for x in get_shard_tests(args)]
+
+
+def main():
+ args = parse_args()
+
+ if args.split_factor:
+ shard_cmd = get_shard_cmd_args(args)
+ if shard_cmd:
+ cmd = args.command + shard_cmd
+ else:
+ print("No tests for {} shard".format(args.shard))
+ return 0
+ else:
+ cmd = args.command
+
+ rc = subprocess.call(cmd)
+ if rc:
+ print("Some tests failed. To reproduce run: {}".format(shlex.join(cmd)))
+ return rc
+
+
+if __name__ == "__main__":
+ exit(main())