diff options
Diffstat (limited to 'src/rocksdb/buckifier')
-rw-r--r-- | src/rocksdb/buckifier/buckify_rocksdb.py | 236 | ||||
-rwxr-xr-x | src/rocksdb/buckifier/rocks_test_runner.sh | 6 | ||||
-rw-r--r-- | src/rocksdb/buckifier/targets_builder.py | 80 | ||||
-rw-r--r-- | src/rocksdb/buckifier/targets_cfg.py | 181 | ||||
-rw-r--r-- | src/rocksdb/buckifier/util.py | 119 |
5 files changed, 622 insertions, 0 deletions
diff --git a/src/rocksdb/buckifier/buckify_rocksdb.py b/src/rocksdb/buckifier/buckify_rocksdb.py new file mode 100644 index 000000000..d2bba5940 --- /dev/null +++ b/src/rocksdb/buckifier/buckify_rocksdb.py @@ -0,0 +1,236 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +try: + from builtins import str +except ImportError: + from __builtin__ import str +from targets_builder import TARGETSBuilder +import json +import os +import fnmatch +import sys + +from util import ColorString + +# This script generates TARGETS file for Buck. +# Buck is a build tool specifying dependencies among different build targets. +# User can pass extra dependencies as a JSON object via command line, and this +# script can include these dependencies in the generate TARGETS file. +# Usage: +# $python buckifier/buckify_rocksdb.py +# (This generates a TARGET file without user-specified dependency for unit +# tests.) +# $python buckifier/buckify_rocksdb.py \ +# '{"fake": { \ +# "extra_deps": [":test_dep", "//fakes/module:mock1"], \ +# "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"], \ +# } \ +# }' +# (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB +# unit tests, and will use the extra_compiler_flags to compile the unit test +# source.) + +# 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 + + +# Parse extra dependencies passed by user from command line +def get_dependencies(): + deps_map = { + '': { + 'extra_deps': [], + 'extra_compiler_flags': [] + } + } + if len(sys.argv) < 2: + return deps_map + + def encode_dict(data): + rv = {} + for k, v in data.items(): + if isinstance(v, dict): + v = encode_dict(v) + rv[k] = v + return rv + extra_deps = json.loads(sys.argv[1], object_hook=encode_dict) + for target_alias, deps in extra_deps.items(): + deps_map[target_alias] = deps + return deps_map + + +# Prepare TARGETS file for buck +def generate_targets(repo_path, deps_map): + 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", []) + + ["test_util/testutil.cc"], + [":rocksdb_lib"]) + # rocksdb_stress_lib + TARGETS.add_library( + "rocksdb_stress_lib", + src_mk.get("ANALYZER_LIB_SOURCES", []) + + src_mk.get('STRESS_LIB_SOURCES', []) + + ["test_util/testutil.cc"], + [":rocksdb_lib"]) + + print("Extra dependencies:\n{0}".format(str(deps_map))) + # test for every test we found in the Makefile + for target_alias, deps in deps_map.items(): + 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] + test_target_name = \ + test if not target_alias else test + "_" + target_alias + TARGETS.register_test( + test_target_name, + match_src[0], + is_parallel, + deps['extra_deps'], + deps['extra_compiler_flags']) + + if test in _EXPORTED_TEST_LIBS: + test_library = "%s_lib" % test_target_name + 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(): + deps_map = get_dependencies() + # Generate TARGETS file for buck + ok = generate_targets(get_rocksdb_path(), deps_map) + 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 000000000..77f8f23c5 --- /dev/null +++ b/src/rocksdb/buckifier/rocks_test_runner.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +# 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 000000000..ba90bc612 --- /dev/null +++ b/src/rocksdb/buckifier/targets_builder.py @@ -0,0 +1,80 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +try: + from builtins import object + from builtins import str +except ImportError: + from __builtin__ import object + from __builtin__ import str +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(object): + 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, + extra_deps, + extra_compiler_flags): + exec_mode = "serial" + if is_parallel: + exec_mode = "parallel" + self.tests_cfg += targets_cfg.test_cfg_template % ( + test_name, + str(src), + str(exec_mode), + extra_deps, + extra_compiler_flags) + + 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 000000000..c92b10204 --- /dev/null +++ b/src/rocksdb/buckifier/targets_cfg.py @@ -0,0 +1,181 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +rocksdb_target_header = """# This file \100generated by `python buckifier/buckify_rocksdb.py` +# --> DO NOT EDIT MANUALLY <-- +# This file is a Facebook-specific integration for buck builds, so can +# only be validated by Facebook employees. +# +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", + # Needed to compile in fbcode + "-Wno-expansion-to-defined", + # Added missing flags from output of build_detect_platform + "-Wnarrowing", + "-DROCKSDB_NO_DYNAMIC_EXTENSION", +] + +ROCKSDB_EXTERNAL_DEPS = [ + ("bzip2", None, "bz2"), + ("snappy", None, "snappy"), + ("zlib", None, "z"), + ("gflags", None, "gflags"), + ("lz4", None, "lz4"), + ("zstd", None), + ("tbb", None), + ("googletest", None, "gtest"), +] + +ROCKSDB_OS_DEPS = [ + ( + "linux", + ["third-party//numa:numa", "third-party//liburing:uring"], + ), +] + +ROCKSDB_OS_PREPROCESSOR_FLAGS = [ + ( + "linux", + [ + "-DOS_LINUX", + "-DROCKSDB_FALLOCATE_PRESENT", + "-DROCKSDB_MALLOC_USABLE_SIZE", + "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX", + "-DROCKSDB_RANGESYNC_PRESENT", + "-DROCKSDB_SCHED_GETCPU_PRESENT", + "-DROCKSDB_IOURING_PRESENT", + "-DHAVE_SSE42", + "-DLIBURING", + "-DNUMA", + ], + ), + ( + "macos", + ["-DOS_MACOSX"], + ), +] + +ROCKSDB_PREPROCESSOR_FLAGS = [ + "-DROCKSDB_PLATFORM_POSIX", + "-DROCKSDB_LIB_IO_POSIX", + "-DROCKSDB_SUPPORT_THREAD_LOCAL", + + # Flags to enable libs we include + "-DSNAPPY", + "-DZLIB", + "-DBZIP2", + "-DLZ4", + "-DZSTD", + "-DZSTD_STATIC_LINKING_ONLY", + "-DGFLAGS=gflags", + "-DTBB", + + # Added missing flags from output of build_detect_platform + "-DROCKSDB_BACKTRACE", + + # Directories with files for #include + "-I" + REPO_PATH + "include/", + "-I" + REPO_PATH, +] + +ROCKSDB_ARCH_PREPROCESSOR_FLAGS = { + "x86_64": [ + "-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_OS_PREPROCESSOR_FLAGS += ([( + "linux", + ["-DROCKSDB_JEMALLOC"], +)] if sanitizer == "" else []) + +ROCKSDB_OS_DEPS += ([( + "linux", + ["third-party//jemalloc: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, + os_deps = ROCKSDB_OS_DEPS, + os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_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", + %s, + %s, + ], +""" + +unittests_template = """ +# [test_name, test_src, test_type, extra_deps, extra_compiler_flags] +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( + extra_compiler_flags = extra_compiler_flags, + extra_deps = extra_deps, + parallelism = parallelism, + rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS, + rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS, + rocksdb_os_deps = ROCKSDB_OS_DEPS, + rocksdb_os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS, + rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + test_cc = test_cc, + test_name = test_name, + ) + for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags 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 000000000..f04929a27 --- /dev/null +++ b/src/rocksdb/buckifier/util.py @@ -0,0 +1,119 @@ +# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +""" +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 +try: + from builtins import object +except ImportError: + from __builtin__ import object +import subprocess +import sys +import os +import time + +class ColorString(object): + """ 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): + # In Python2, default encoding for unicode string is ASCII + if sys.version_info.major <= 2: + return "".join( + [color, text.encode('utf-8'), ColorString.ENDC]) + # From Python3, default encoding for unicode string is UTF-8 + return "".join( + [color, text, 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 |