buckify_rocksdb.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  2. from __future__ import absolute_import
  3. from __future__ import division
  4. from __future__ import print_function
  5. from __future__ import unicode_literals
  6. try:
  7. from builtins import str
  8. except ImportError:
  9. from __builtin__ import str
  10. from targets_builder import TARGETSBuilder
  11. import json
  12. import os
  13. import fnmatch
  14. import sys
  15. from util import ColorString
  16. # This script generates TARGETS file for Buck.
  17. # Buck is a build tool specifying dependencies among different build targets.
  18. # User can pass extra dependencies as a JSON object via command line, and this
  19. # script can include these dependencies in the generate TARGETS file.
  20. # Usage:
  21. # $python buckifier/buckify_rocksdb.py
  22. # (This generates a TARGET file without user-specified dependency for unit
  23. # tests.)
  24. # $python buckifier/buckify_rocksdb.py \
  25. # '{"fake": { \
  26. # "extra_deps": [":test_dep", "//fakes/module:mock1"], \
  27. # "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"], \
  28. # } \
  29. # }'
  30. # (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB
  31. # unit tests, and will use the extra_compiler_flags to compile the unit test
  32. # source.)
  33. # tests to export as libraries for inclusion in other projects
  34. _EXPORTED_TEST_LIBS = ["env_basic_test"]
  35. # Parse src.mk files as a Dictionary of
  36. # VAR_NAME => list of files
  37. def parse_src_mk(repo_path):
  38. src_mk = repo_path + "/src.mk"
  39. src_files = {}
  40. for line in open(src_mk):
  41. line = line.strip()
  42. if len(line) == 0 or line[0] == '#':
  43. continue
  44. if '=' in line:
  45. current_src = line.split('=')[0].strip()
  46. src_files[current_src] = []
  47. elif '.cc' in line:
  48. src_path = line.split('.cc')[0].strip() + '.cc'
  49. src_files[current_src].append(src_path)
  50. return src_files
  51. # get all .cc / .c files
  52. def get_cc_files(repo_path):
  53. cc_files = []
  54. for root, dirnames, filenames in os.walk(repo_path): # noqa: B007 T25377293 Grandfathered in
  55. root = root[(len(repo_path) + 1):]
  56. if "java" in root:
  57. # Skip java
  58. continue
  59. for filename in fnmatch.filter(filenames, '*.cc'):
  60. cc_files.append(os.path.join(root, filename))
  61. for filename in fnmatch.filter(filenames, '*.c'):
  62. cc_files.append(os.path.join(root, filename))
  63. return cc_files
  64. # Get tests from Makefile
  65. def get_tests(repo_path):
  66. Makefile = repo_path + "/Makefile"
  67. # Dictionary TEST_NAME => IS_PARALLEL
  68. tests = {}
  69. found_tests = False
  70. for line in open(Makefile):
  71. line = line.strip()
  72. if line.startswith("TESTS ="):
  73. found_tests = True
  74. elif found_tests:
  75. if line.endswith("\\"):
  76. # remove the trailing \
  77. line = line[:-1]
  78. line = line.strip()
  79. tests[line] = False
  80. else:
  81. # we consumed all the tests
  82. break
  83. found_parallel_tests = False
  84. for line in open(Makefile):
  85. line = line.strip()
  86. if line.startswith("PARALLEL_TEST ="):
  87. found_parallel_tests = True
  88. elif found_parallel_tests:
  89. if line.endswith("\\"):
  90. # remove the trailing \
  91. line = line[:-1]
  92. line = line.strip()
  93. tests[line] = True
  94. else:
  95. # we consumed all the parallel tests
  96. break
  97. return tests
  98. # Parse extra dependencies passed by user from command line
  99. def get_dependencies():
  100. deps_map = {
  101. '': {
  102. 'extra_deps': [],
  103. 'extra_compiler_flags': []
  104. }
  105. }
  106. if len(sys.argv) < 2:
  107. return deps_map
  108. def encode_dict(data):
  109. rv = {}
  110. for k, v in data.items():
  111. if isinstance(v, dict):
  112. v = encode_dict(v)
  113. rv[k] = v
  114. return rv
  115. extra_deps = json.loads(sys.argv[1], object_hook=encode_dict)
  116. for target_alias, deps in extra_deps.items():
  117. deps_map[target_alias] = deps
  118. return deps_map
  119. # Prepare TARGETS file for buck
  120. def generate_targets(repo_path, deps_map):
  121. print(ColorString.info("Generating TARGETS"))
  122. # parsed src.mk file
  123. src_mk = parse_src_mk(repo_path)
  124. # get all .cc files
  125. cc_files = get_cc_files(repo_path)
  126. # get tests from Makefile
  127. tests = get_tests(repo_path)
  128. if src_mk is None or cc_files is None or tests is None:
  129. return False
  130. TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path)
  131. # rocksdb_lib
  132. TARGETS.add_library(
  133. "rocksdb_lib",
  134. src_mk["LIB_SOURCES"] +
  135. src_mk["TOOL_LIB_SOURCES"])
  136. # rocksdb_test_lib
  137. TARGETS.add_library(
  138. "rocksdb_test_lib",
  139. src_mk.get("MOCK_LIB_SOURCES", []) +
  140. src_mk.get("TEST_LIB_SOURCES", []) +
  141. src_mk.get("EXP_LIB_SOURCES", []) +
  142. src_mk.get("ANALYZER_LIB_SOURCES", []),
  143. [":rocksdb_lib"])
  144. # rocksdb_tools_lib
  145. TARGETS.add_library(
  146. "rocksdb_tools_lib",
  147. src_mk.get("BENCH_LIB_SOURCES", []) +
  148. src_mk.get("ANALYZER_LIB_SOURCES", []) +
  149. ["test_util/testutil.cc"],
  150. [":rocksdb_lib"])
  151. # rocksdb_stress_lib
  152. TARGETS.add_library(
  153. "rocksdb_stress_lib",
  154. src_mk.get("ANALYZER_LIB_SOURCES", [])
  155. + src_mk.get('STRESS_LIB_SOURCES', [])
  156. + ["test_util/testutil.cc"],
  157. [":rocksdb_lib"])
  158. print("Extra dependencies:\n{0}".format(str(deps_map)))
  159. # test for every test we found in the Makefile
  160. for target_alias, deps in deps_map.items():
  161. for test in sorted(tests):
  162. match_src = [src for src in cc_files if ("/%s.c" % test) in src]
  163. if len(match_src) == 0:
  164. print(ColorString.warning("Cannot find .cc file for %s" % test))
  165. continue
  166. elif len(match_src) > 1:
  167. print(ColorString.warning("Found more than one .cc for %s" % test))
  168. print(match_src)
  169. continue
  170. assert(len(match_src) == 1)
  171. is_parallel = tests[test]
  172. test_target_name = \
  173. test if not target_alias else test + "_" + target_alias
  174. TARGETS.register_test(
  175. test_target_name,
  176. match_src[0],
  177. is_parallel,
  178. deps['extra_deps'],
  179. deps['extra_compiler_flags'])
  180. if test in _EXPORTED_TEST_LIBS:
  181. test_library = "%s_lib" % test_target_name
  182. TARGETS.add_library(test_library, match_src, [":rocksdb_test_lib"])
  183. TARGETS.flush_tests()
  184. print(ColorString.info("Generated TARGETS Summary:"))
  185. print(ColorString.info("- %d libs" % TARGETS.total_lib))
  186. print(ColorString.info("- %d binarys" % TARGETS.total_bin))
  187. print(ColorString.info("- %d tests" % TARGETS.total_test))
  188. return True
  189. def get_rocksdb_path():
  190. # rocksdb = {script_dir}/..
  191. script_dir = os.path.dirname(sys.argv[0])
  192. script_dir = os.path.abspath(script_dir)
  193. rocksdb_path = os.path.abspath(
  194. os.path.join(script_dir, "../"))
  195. return rocksdb_path
  196. def exit_with_error(msg):
  197. print(ColorString.error(msg))
  198. sys.exit(1)
  199. def main():
  200. deps_map = get_dependencies()
  201. # Generate TARGETS file for buck
  202. ok = generate_targets(get_rocksdb_path(), deps_map)
  203. if not ok:
  204. exit_with_error("Failed to generate TARGETS files")
  205. if __name__ == "__main__":
  206. main()