diff options
Diffstat (limited to '')
-rw-r--r-- | src/rocksdb/buckifier/buckify_rocksdb.py | 171 | ||||
-rwxr-xr-x | src/rocksdb/buckifier/rocks_test_runner.sh | 5 | ||||
-rw-r--r-- | src/rocksdb/buckifier/targets_builder.py | 66 | ||||
-rw-r--r-- | src/rocksdb/buckifier/targets_cfg.py | 135 | ||||
-rw-r--r-- | src/rocksdb/buckifier/util.py | 107 |
5 files changed, 484 insertions, 0 deletions
diff --git a/src/rocksdb/buckifier/buckify_rocksdb.py b/src/rocksdb/buckifier/buckify_rocksdb.py new file mode 100644 index 00000000..96903af6 --- /dev/null +++ b/src/rocksdb/buckifier/buckify_rocksdb.py @@ -0,0 +1,171 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +from targets_builder import TARGETSBuilder +import os +import fnmatch +import sys + +from util import ColorString + +# tests to export as libraries for inclusion in other projects +_EXPORTED_TEST_LIBS = ["env_basic_test"] + +# Parse src.mk files as a Dictionary of +# VAR_NAME => list of files +def parse_src_mk(repo_path): + src_mk = repo_path + "/src.mk" + src_files = {} + for line in open(src_mk): + line = line.strip() + if len(line) == 0 or line[0] == '#': + continue + if '=' in line: + current_src = line.split('=')[0].strip() + src_files[current_src] = [] + elif '.cc' in line: + src_path = line.split('.cc')[0].strip() + '.cc' + src_files[current_src].append(src_path) + return src_files + + +# get all .cc / .c files +def get_cc_files(repo_path): + cc_files = [] + for root, dirnames, filenames in os.walk(repo_path): # noqa: B007 T25377293 Grandfathered in + root = root[(len(repo_path) + 1):] + if "java" in root: + # Skip java + continue + for filename in fnmatch.filter(filenames, '*.cc'): + cc_files.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.c'): + cc_files.append(os.path.join(root, filename)) + return cc_files + + +# Get tests from Makefile +def get_tests(repo_path): + Makefile = repo_path + "/Makefile" + + # Dictionary TEST_NAME => IS_PARALLEL + tests = {} + + found_tests = False + for line in open(Makefile): + line = line.strip() + if line.startswith("TESTS ="): + found_tests = True + elif found_tests: + if line.endswith("\\"): + # remove the trailing \ + line = line[:-1] + line = line.strip() + tests[line] = False + else: + # we consumed all the tests + break + + found_parallel_tests = False + for line in open(Makefile): + line = line.strip() + if line.startswith("PARALLEL_TEST ="): + found_parallel_tests = True + elif found_parallel_tests: + if line.endswith("\\"): + # remove the trailing \ + line = line[:-1] + line = line.strip() + tests[line] = True + else: + # we consumed all the parallel tests + break + + return tests + + +# Prepare TARGETS file for buck +def generate_targets(repo_path): + print(ColorString.info("Generating TARGETS")) + # parsed src.mk file + src_mk = parse_src_mk(repo_path) + # get all .cc files + cc_files = get_cc_files(repo_path) + # get tests from Makefile + tests = get_tests(repo_path) + + if src_mk is None or cc_files is None or tests is None: + return False + + TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path) + # rocksdb_lib + TARGETS.add_library( + "rocksdb_lib", + src_mk["LIB_SOURCES"] + + src_mk["TOOL_LIB_SOURCES"]) + # rocksdb_test_lib + TARGETS.add_library( + "rocksdb_test_lib", + src_mk.get("MOCK_LIB_SOURCES", []) + + src_mk.get("TEST_LIB_SOURCES", []) + + src_mk.get("EXP_LIB_SOURCES", []) + + src_mk.get("ANALYZER_LIB_SOURCES", []), + [":rocksdb_lib"]) + # rocksdb_tools_lib + TARGETS.add_library( + "rocksdb_tools_lib", + src_mk.get("BENCH_LIB_SOURCES", []) + + src_mk.get("ANALYZER_LIB_SOURCES", []) + + ["util/testutil.cc"], + [":rocksdb_lib"]) + + # test for every test we found in the Makefile + for test in sorted(tests): + match_src = [src for src in cc_files if ("/%s.c" % test) in src] + if len(match_src) == 0: + print(ColorString.warning("Cannot find .cc file for %s" % test)) + continue + elif len(match_src) > 1: + print(ColorString.warning("Found more than one .cc for %s" % test)) + print(match_src) + continue + + assert(len(match_src) == 1) + is_parallel = tests[test] + TARGETS.register_test(test, match_src[0], is_parallel) + + if test in _EXPORTED_TEST_LIBS: + test_library = "%s_lib" % test + TARGETS.add_library(test_library, match_src, [":rocksdb_test_lib"]) + TARGETS.flush_tests() + + print(ColorString.info("Generated TARGETS Summary:")) + print(ColorString.info("- %d libs" % TARGETS.total_lib)) + print(ColorString.info("- %d binarys" % TARGETS.total_bin)) + print(ColorString.info("- %d tests" % TARGETS.total_test)) + return True + + +def get_rocksdb_path(): + # rocksdb = {script_dir}/.. + script_dir = os.path.dirname(sys.argv[0]) + script_dir = os.path.abspath(script_dir) + rocksdb_path = os.path.abspath( + os.path.join(script_dir, "../")) + + return rocksdb_path + +def exit_with_error(msg): + print(ColorString.error(msg)) + sys.exit(1) + + +def main(): + # Generate TARGETS file for buck + ok = generate_targets(get_rocksdb_path()) + if not ok: + exit_with_error("Failed to generate TARGETS files") + +if __name__ == "__main__": + main() diff --git a/src/rocksdb/buckifier/rocks_test_runner.sh b/src/rocksdb/buckifier/rocks_test_runner.sh new file mode 100755 index 00000000..baca6c2e --- /dev/null +++ b/src/rocksdb/buckifier/rocks_test_runner.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Create a tmp directory for the test to use +TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX) +# shellcheck disable=SC2068 +TEST_TMPDIR="$TEST_DIR" $@ && rm -rf "$TEST_DIR" diff --git a/src/rocksdb/buckifier/targets_builder.py b/src/rocksdb/buckifier/targets_builder.py new file mode 100644 index 00000000..3d5822d3 --- /dev/null +++ b/src/rocksdb/buckifier/targets_builder.py @@ -0,0 +1,66 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import targets_cfg + +def pretty_list(lst, indent=8): + if lst is None or len(lst) == 0: + return "" + + if len(lst) == 1: + return "\"%s\"" % lst[0] + + separator = "\",\n%s\"" % (" " * indent) + res = separator.join(sorted(lst)) + res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 4)) + return res + + +class TARGETSBuilder: + def __init__(self, path): + self.path = path + self.targets_file = open(path, 'w') + self.targets_file.write(targets_cfg.rocksdb_target_header) + self.total_lib = 0 + self.total_bin = 0 + self.total_test = 0 + self.tests_cfg = "" + + def __del__(self): + self.targets_file.close() + + def add_library(self, name, srcs, deps=None, headers=None): + headers_attr_prefix = "" + if headers is None: + headers_attr_prefix = "auto_" + headers = "AutoHeaders.RECURSIVE_GLOB" + self.targets_file.write(targets_cfg.library_template.format( + name=name, + srcs=pretty_list(srcs), + headers_attr_prefix=headers_attr_prefix, + headers=headers, + deps=pretty_list(deps))) + self.total_lib = self.total_lib + 1 + + def add_binary(self, name, srcs, deps=None): + self.targets_file.write(targets_cfg.binary_template % ( + name, + pretty_list(srcs), + pretty_list(deps))) + self.total_bin = self.total_bin + 1 + + def register_test(self, test_name, src, is_parallel): + exec_mode = "serial" + if is_parallel: + exec_mode = "parallel" + self.tests_cfg += targets_cfg.test_cfg_template % ( + test_name, + str(src), + str(exec_mode)) + + self.total_test = self.total_test + 1 + + def flush_tests(self): + self.targets_file.write(targets_cfg.unittests_template % self.tests_cfg) + self.tests_cfg = "" diff --git a/src/rocksdb/buckifier/targets_cfg.py b/src/rocksdb/buckifier/targets_cfg.py new file mode 100644 index 00000000..f881588c --- /dev/null +++ b/src/rocksdb/buckifier/targets_cfg.py @@ -0,0 +1,135 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +rocksdb_target_header = """load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders") +load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library") +load(":defs.bzl", "test_binary") + +REPO_PATH = package_name() + "/" + +ROCKSDB_COMPILER_FLAGS = [ + "-fno-builtin-memcmp", + "-DROCKSDB_PLATFORM_POSIX", + "-DROCKSDB_LIB_IO_POSIX", + "-DROCKSDB_FALLOCATE_PRESENT", + "-DROCKSDB_MALLOC_USABLE_SIZE", + "-DROCKSDB_RANGESYNC_PRESENT", + "-DROCKSDB_SCHED_GETCPU_PRESENT", + "-DROCKSDB_SUPPORT_THREAD_LOCAL", + "-DOS_LINUX", + # Flags to enable libs we include + "-DSNAPPY", + "-DZLIB", + "-DBZIP2", + "-DLZ4", + "-DZSTD", + "-DZSTD_STATIC_LINKING_ONLY", + "-DGFLAGS=gflags", + "-DNUMA", + "-DTBB", + # Needed to compile in fbcode + "-Wno-expansion-to-defined", + # Added missing flags from output of build_detect_platform + "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX", + "-DROCKSDB_BACKTRACE", + "-Wnarrowing", +] + +ROCKSDB_EXTERNAL_DEPS = [ + ("bzip2", None, "bz2"), + ("snappy", None, "snappy"), + ("zlib", None, "z"), + ("gflags", None, "gflags"), + ("lz4", None, "lz4"), + ("zstd", None), + ("tbb", None), + ("numa", None, "numa"), + ("googletest", None, "gtest"), +] + +ROCKSDB_PREPROCESSOR_FLAGS = [ + # Directories with files for #include + "-I" + REPO_PATH + "include/", + "-I" + REPO_PATH, +] + +ROCKSDB_ARCH_PREPROCESSOR_FLAGS = { + "x86_64": [ + "-DHAVE_SSE42", + "-DHAVE_PCLMUL", + ], +} + +build_mode = read_config("fbcode", "build_mode") + +is_opt_mode = build_mode.startswith("opt") + +# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice +# doesn't harm and avoid forgetting to add it. +ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else []) + +sanitizer = read_config("fbcode", "sanitizer") + +# Do not enable jemalloc if sanitizer presents. RocksDB will further detect +# whether the binary is linked with jemalloc at runtime. +ROCKSDB_COMPILER_FLAGS += (["-DROCKSDB_JEMALLOC"] if sanitizer == "" else []) + +ROCKSDB_EXTERNAL_DEPS += ([("jemalloc", None, "headers")] if sanitizer == "" else []) +""" + + +library_template = """ +cpp_library( + name = "{name}", + srcs = [{srcs}], + {headers_attr_prefix}headers = {headers}, + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + deps = [{deps}], + external_deps = ROCKSDB_EXTERNAL_DEPS, +) +""" + +binary_template = """ +cpp_binary( + name = "%s", + srcs = [%s], + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + deps = [%s], + external_deps = ROCKSDB_EXTERNAL_DEPS, +) +""" + +test_cfg_template = """ [ + "%s", + "%s", + "%s", + ], +""" + +unittests_template = """ +# [test_name, test_src, test_type] +ROCKS_TESTS = [ +%s] + +# Generate a test rule for each entry in ROCKS_TESTS +# Do not build the tests in opt mode, since SyncPoint and other test code +# will not be included. +[ + test_binary( + parallelism = parallelism, + rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS, + rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS, + rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + test_cc = test_cc, + test_name = test_name, + ) + for test_name, test_cc, parallelism in ROCKS_TESTS + if not is_opt_mode +] +""" diff --git a/src/rocksdb/buckifier/util.py b/src/rocksdb/buckifier/util.py new file mode 100644 index 00000000..350b7335 --- /dev/null +++ b/src/rocksdb/buckifier/util.py @@ -0,0 +1,107 @@ +""" +This module keeps commonly used components. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import subprocess +import os +import time + +class ColorString: + """ Generate colorful strings on terminal """ + HEADER = '\033[95m' + BLUE = '\033[94m' + GREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + + @staticmethod + def _make_color_str(text, color): + return "".join([color, text.encode('utf-8'), ColorString.ENDC]) + + @staticmethod + def ok(text): + if ColorString.is_disabled: + return text + return ColorString._make_color_str(text, ColorString.GREEN) + + @staticmethod + def info(text): + if ColorString.is_disabled: + return text + return ColorString._make_color_str(text, ColorString.BLUE) + + @staticmethod + def header(text): + if ColorString.is_disabled: + return text + return ColorString._make_color_str(text, ColorString.HEADER) + + @staticmethod + def error(text): + if ColorString.is_disabled: + return text + return ColorString._make_color_str(text, ColorString.FAIL) + + @staticmethod + def warning(text): + if ColorString.is_disabled: + return text + return ColorString._make_color_str(text, ColorString.WARNING) + + is_disabled = False + + +def run_shell_command(shell_cmd, cmd_dir=None): + """ Run a single shell command. + @returns a tuple of shell command return code, stdout, stderr """ + + if cmd_dir is not None and not os.path.exists(cmd_dir): + run_shell_command("mkdir -p %s" % cmd_dir) + + start = time.time() + print("\t>>> Running: " + shell_cmd) + p = subprocess.Popen(shell_cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=cmd_dir) + stdout, stderr = p.communicate() + end = time.time() + + # Report time if we spent more than 5 minutes executing a command + execution_time = end - start + if execution_time > (60 * 5): + mins = (execution_time / 60) + secs = (execution_time % 60) + print("\t>time spent: %d minutes %d seconds" % (mins, secs)) + + + return p.returncode, stdout, stderr + + +def run_shell_commands(shell_cmds, cmd_dir=None, verbose=False): + """ Execute a sequence of shell commands, which is equivalent to + running `cmd1 && cmd2 && cmd3` + @returns boolean indication if all commands succeeds. + """ + + if cmd_dir: + print("\t=== Set current working directory => %s" % cmd_dir) + + for shell_cmd in shell_cmds: + ret_code, stdout, stderr = run_shell_command(shell_cmd, cmd_dir) + if stdout: + if verbose or ret_code != 0: + print(ColorString.info("stdout: \n"), stdout) + if stderr: + # contents in stderr is not necessarily to be error messages. + if verbose or ret_code != 0: + print(ColorString.error("stderr: \n"), stderr) + if ret_code != 0: + return False + + return True |