buckify_rocksdb.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. #!/usr/bin/env python3
  2. # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  3. try:
  4. from builtins import str
  5. except ImportError:
  6. from __builtin__ import str
  7. import fnmatch
  8. import json
  9. import os
  10. import sys
  11. from targets_builder import TARGETSBuilder, LiteralValue
  12. from util import ColorString
  13. # This script generates BUCK file for Buck.
  14. # Buck is a build tool specifying dependencies among different build targets.
  15. # User can pass extra dependencies as a JSON object via command line, and this
  16. # script can include these dependencies in the generate BUCK file.
  17. # Usage:
  18. # $python3 buckifier/buckify_rocksdb.py
  19. # (This generates a TARGET file without user-specified dependency for unit
  20. # tests.)
  21. # $python3 buckifier/buckify_rocksdb.py \
  22. # '{"fake": {
  23. # "extra_deps": [":test_dep", "//fakes/module:mock1"],
  24. # "extra_compiler_flags": ["-DFOO_BAR", "-Os"]
  25. # }
  26. # }'
  27. # (Generated BUCK file has test_dep and mock1 as dependencies for RocksDB
  28. # unit tests, and will use the extra_compiler_flags to compile the unit test
  29. # source.)
  30. # tests to export as libraries for inclusion in other projects
  31. _EXPORTED_TEST_LIBS = ["env_basic_test"]
  32. # Parse src.mk files as a Dictionary of
  33. # VAR_NAME => list of files
  34. def parse_src_mk(repo_path):
  35. src_mk = repo_path + "/src.mk"
  36. src_files = {}
  37. for line in open(src_mk):
  38. line = line.strip()
  39. if len(line) == 0 or line[0] == "#":
  40. continue
  41. if "=" in line:
  42. current_src = line.split("=")[0].strip()
  43. src_files[current_src] = []
  44. elif ".c" in line:
  45. src_path = line.split("\\")[0].strip()
  46. src_files[current_src].append(src_path)
  47. return src_files
  48. # get all .cc / .c files
  49. def get_cc_files(repo_path):
  50. cc_files = []
  51. for root, _dirnames, filenames in os.walk(
  52. repo_path
  53. ): # noqa: B007 T25377293 Grandfathered in
  54. root = root[(len(repo_path) + 1) :]
  55. if "java" in root:
  56. # Skip java
  57. continue
  58. for filename in fnmatch.filter(filenames, "*.cc"):
  59. cc_files.append(os.path.join(root, filename))
  60. for filename in fnmatch.filter(filenames, "*.c"):
  61. cc_files.append(os.path.join(root, filename))
  62. return cc_files
  63. # Get non_parallel tests from Makefile
  64. def get_non_parallel_tests(repo_path):
  65. Makefile = repo_path + "/Makefile"
  66. s = set({})
  67. found_non_parallel_tests = False
  68. for line in open(Makefile):
  69. line = line.strip()
  70. if line.startswith("NON_PARALLEL_TEST ="):
  71. found_non_parallel_tests = True
  72. elif found_non_parallel_tests:
  73. if line.endswith("\\"):
  74. # remove the trailing \
  75. line = line[:-1]
  76. line = line.strip()
  77. s.add(line)
  78. else:
  79. # we consumed all the non_parallel tests
  80. break
  81. return s
  82. # Parse extra dependencies passed by user from command line
  83. def get_dependencies():
  84. deps_map = {"": {"extra_deps": [], "extra_compiler_flags": []}}
  85. if len(sys.argv) < 2:
  86. return deps_map
  87. def encode_dict(data):
  88. rv = {}
  89. for k, v in data.items():
  90. if isinstance(v, dict):
  91. v = encode_dict(v)
  92. rv[k] = v
  93. return rv
  94. extra_deps = json.loads(sys.argv[1], object_hook=encode_dict)
  95. for target_alias, deps in extra_deps.items():
  96. deps_map[target_alias] = deps
  97. return deps_map
  98. # Prepare BUCK file for buck
  99. def generate_buck(repo_path, deps_map):
  100. print(ColorString.info("Generating BUCK"))
  101. # parsed src.mk file
  102. src_mk = parse_src_mk(repo_path)
  103. # get all .cc files
  104. cc_files = get_cc_files(repo_path)
  105. # get non_parallel tests from Makefile
  106. non_parallel_tests = get_non_parallel_tests(repo_path)
  107. if src_mk is None or cc_files is None or non_parallel_tests is None:
  108. return False
  109. extra_argv = ""
  110. if len(sys.argv) >= 2:
  111. # Heuristically quote and canonicalize whitespace for inclusion
  112. # in how the file was generated.
  113. extra_argv = " '{}'".format(" ".join(sys.argv[1].split()))
  114. BUCK = TARGETSBuilder("%s/BUCK" % repo_path, extra_argv)
  115. # rocksdb_lib
  116. BUCK.add_library(
  117. "rocksdb_lib",
  118. src_mk["LIB_SOURCES"] +
  119. # always add range_tree, it's only excluded on ppc64, which we don't use internally
  120. src_mk["RANGE_TREE_SOURCES"] + src_mk["TOOL_LIB_SOURCES"],
  121. deps=[
  122. "//folly/container:f14_hash",
  123. "//folly/experimental/coro:blocking_wait",
  124. "//folly/experimental/coro:collect",
  125. "//folly/experimental/coro:coroutine",
  126. "//folly/experimental/coro:task",
  127. "//folly/synchronization:distributed_mutex",
  128. ],
  129. headers=LiteralValue("glob([\"**/*.h\"])")
  130. )
  131. # rocksdb_whole_archive_lib
  132. BUCK.add_library(
  133. "rocksdb_whole_archive_lib",
  134. [],
  135. deps=[
  136. ":rocksdb_lib",
  137. ],
  138. extra_external_deps="",
  139. link_whole=True,
  140. )
  141. # rocksdb_with_faiss_lib
  142. BUCK.add_library(
  143. "rocksdb_with_faiss_lib",
  144. src_mk.get("WITH_FAISS_LIB_SOURCES", []),
  145. deps=[
  146. "//faiss:faiss",
  147. ":rocksdb_lib",
  148. ],
  149. )
  150. # rocksdb_test_lib
  151. BUCK.add_library(
  152. "rocksdb_test_lib",
  153. src_mk.get("MOCK_LIB_SOURCES", [])
  154. + src_mk.get("TEST_LIB_SOURCES", [])
  155. + src_mk.get("EXP_LIB_SOURCES", [])
  156. + src_mk.get("ANALYZER_LIB_SOURCES", []),
  157. [":rocksdb_lib"],
  158. extra_test_libs=True,
  159. )
  160. # rocksdb_with_faiss_test_lib
  161. BUCK.add_library(
  162. "rocksdb_with_faiss_test_lib",
  163. src_mk.get("MOCK_LIB_SOURCES", [])
  164. + src_mk.get("TEST_LIB_SOURCES", [])
  165. + src_mk.get("EXP_LIB_SOURCES", [])
  166. + src_mk.get("ANALYZER_LIB_SOURCES", []),
  167. deps=[
  168. ":rocksdb_with_faiss_lib",
  169. ],
  170. extra_test_libs=True,
  171. )
  172. # rocksdb_tools_lib
  173. BUCK.add_library(
  174. "rocksdb_tools_lib",
  175. src_mk.get("BENCH_LIB_SOURCES", [])
  176. + src_mk.get("ANALYZER_LIB_SOURCES", [])
  177. + ["test_util/testutil.cc"],
  178. [":rocksdb_lib"],
  179. )
  180. # rocksdb_cache_bench_tools_lib
  181. BUCK.add_library(
  182. "rocksdb_cache_bench_tools_lib",
  183. src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
  184. [":rocksdb_lib"],
  185. )
  186. # rocksdb_point_lock_bench_tools_lib
  187. BUCK.add_library(
  188. "rocksdb_point_lock_bench_tools_lib",
  189. src_mk.get("POINT_LOCK_BENCH_LIB_SOURCES", []),
  190. [":rocksdb_lib"],
  191. )
  192. # rocksdb_stress_lib
  193. BUCK.add_rocksdb_library(
  194. "rocksdb_stress_lib",
  195. src_mk.get("ANALYZER_LIB_SOURCES", [])
  196. + src_mk.get("STRESS_LIB_SOURCES", [])
  197. + ["test_util/testutil.cc"],
  198. )
  199. # ldb binary
  200. BUCK.add_binary(
  201. "ldb", ["tools/ldb.cc"], [":rocksdb_tools_lib"]
  202. )
  203. # db_stress binary
  204. BUCK.add_binary(
  205. "db_stress", ["db_stress_tool/db_stress.cc"], [":rocksdb_stress_lib"]
  206. )
  207. # db_bench binary
  208. BUCK.add_binary(
  209. "db_bench", ["tools/db_bench.cc"], [":rocksdb_tools_lib"]
  210. )
  211. # cache_bench binary
  212. BUCK.add_binary(
  213. "cache_bench", ["cache/cache_bench.cc"], [":rocksdb_cache_bench_tools_lib"]
  214. )
  215. # point_lock_bench binary
  216. BUCK.add_binary(
  217. "point_lock_bench",
  218. ["utilities/transactions/lock/point/point_lock_bench.cc"],
  219. [":rocksdb_point_lock_bench_tools_lib"]
  220. )
  221. # bench binaries
  222. for src in src_mk.get("MICROBENCH_SOURCES", []):
  223. name = src.rsplit("/", 1)[1].split(".")[0] if "/" in src else src.split(".")[0]
  224. BUCK.add_binary(name, [src], [], extra_bench_libs=True)
  225. print(f"Extra dependencies:\n{json.dumps(deps_map)}")
  226. # Dictionary test executable name -> relative source file path
  227. test_source_map = {}
  228. # c_test.c is added through BUCK.add_c_test(). If there
  229. # are more than one .c test file, we need to extend
  230. # BUCK.add_c_test() to include other C tests too.
  231. for test_src in src_mk.get("TEST_MAIN_SOURCES_C", []):
  232. if test_src != "db/c_test.c":
  233. print("Don't know how to deal with " + test_src)
  234. return False
  235. BUCK.add_c_test()
  236. try:
  237. with open(f"{repo_path}/buckifier/bench.json") as json_file:
  238. fast_fancy_bench_config_list = json.load(json_file)
  239. for config_dict in fast_fancy_bench_config_list:
  240. clean_benchmarks = {}
  241. benchmarks = config_dict["benchmarks"]
  242. for binary, benchmark_dict in benchmarks.items():
  243. clean_benchmarks[binary] = {}
  244. for benchmark, overloaded_metric_list in benchmark_dict.items():
  245. clean_benchmarks[binary][benchmark] = []
  246. for metric in overloaded_metric_list:
  247. if not isinstance(metric, dict):
  248. clean_benchmarks[binary][benchmark].append(metric)
  249. BUCK.add_fancy_bench_config(
  250. config_dict["name"],
  251. clean_benchmarks,
  252. False,
  253. config_dict["expected_runtime_one_iter"],
  254. config_dict["sl_iterations"],
  255. config_dict["regression_threshold"],
  256. )
  257. with open(f"{repo_path}/buckifier/bench-slow.json") as json_file:
  258. slow_fancy_bench_config_list = json.load(json_file)
  259. for config_dict in slow_fancy_bench_config_list:
  260. clean_benchmarks = {}
  261. benchmarks = config_dict["benchmarks"]
  262. for binary, benchmark_dict in benchmarks.items():
  263. clean_benchmarks[binary] = {}
  264. for benchmark, overloaded_metric_list in benchmark_dict.items():
  265. clean_benchmarks[binary][benchmark] = []
  266. for metric in overloaded_metric_list:
  267. if not isinstance(metric, dict):
  268. clean_benchmarks[binary][benchmark].append(metric)
  269. for config_dict in slow_fancy_bench_config_list:
  270. BUCK.add_fancy_bench_config(
  271. config_dict["name"] + "_slow",
  272. clean_benchmarks,
  273. True,
  274. config_dict["expected_runtime_one_iter"],
  275. config_dict["sl_iterations"],
  276. config_dict["regression_threshold"],
  277. )
  278. # it is better servicelab experiments break
  279. # than rocksdb github ci
  280. except Exception:
  281. pass
  282. BUCK.add_test_header()
  283. for test_src in src_mk.get("TEST_MAIN_SOURCES", []):
  284. test = test_src.split(".c")[0].strip().split("/")[-1].strip()
  285. test_source_map[test] = (test_src, False)
  286. print("" + test + " " + test_src)
  287. for test_src in src_mk.get("WITH_FAISS_TEST_MAIN_SOURCES", []):
  288. test = test_src.split(".c")[0].strip().split("/")[-1].strip()
  289. test_source_map[test] = (test_src, True)
  290. print("" + test + " " + test_src + " [FAISS]")
  291. for target_alias, deps in deps_map.items():
  292. for test, (test_src, with_faiss) in sorted(test_source_map.items()):
  293. if len(test) == 0:
  294. print(ColorString.warning("Failed to get test name for %s" % test_src))
  295. continue
  296. test_target_name = test if not target_alias else test + "_" + target_alias
  297. if test in _EXPORTED_TEST_LIBS:
  298. test_library = "%s_lib" % test_target_name
  299. BUCK.add_library(
  300. test_library,
  301. [test_src],
  302. deps=[":rocksdb_test_lib"],
  303. extra_test_libs=True,
  304. )
  305. BUCK.register_test(
  306. test_target_name,
  307. test_src,
  308. deps=json.dumps(deps["extra_deps"] + [":" + test_library]),
  309. extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
  310. )
  311. else:
  312. if with_faiss:
  313. BUCK.register_test(
  314. test_target_name,
  315. test_src,
  316. deps=json.dumps(deps["extra_deps"] + [":rocksdb_with_faiss_test_lib"]),
  317. extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
  318. )
  319. else:
  320. BUCK.register_test(
  321. test_target_name,
  322. test_src,
  323. deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
  324. extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
  325. )
  326. BUCK.export_file("tools/db_crashtest.py")
  327. print(ColorString.info("Generated BUCK Summary:"))
  328. print(ColorString.info("- %d libs" % BUCK.total_lib))
  329. print(ColorString.info("- %d binarys" % BUCK.total_bin))
  330. print(ColorString.info("- %d tests" % BUCK.total_test))
  331. return True
  332. def get_rocksdb_path():
  333. # rocksdb = {script_dir}/..
  334. script_dir = os.path.dirname(sys.argv[0])
  335. script_dir = os.path.abspath(script_dir)
  336. rocksdb_path = os.path.abspath(os.path.join(script_dir, "../"))
  337. return rocksdb_path
  338. def exit_with_error(msg):
  339. print(ColorString.error(msg))
  340. sys.exit(1)
  341. def main():
  342. deps_map = get_dependencies()
  343. # Generate BUCK file for buck
  344. ok = generate_buck(get_rocksdb_path(), deps_map)
  345. if not ok:
  346. exit_with_error("Failed to generate BUCK files")
  347. if __name__ == "__main__":
  348. main()