summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/buckifier
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/rocksdb/buckifier/buckify_rocksdb.py236
-rwxr-xr-xsrc/rocksdb/buckifier/rocks_test_runner.sh6
-rw-r--r--src/rocksdb/buckifier/targets_builder.py80
-rw-r--r--src/rocksdb/buckifier/targets_cfg.py181
-rw-r--r--src/rocksdb/buckifier/util.py119
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