build_llvm.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (C) 2019 Intel Corporation. All rights reserved.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. #
  6. import argparse
  7. import os
  8. import pathlib
  9. import shlex
  10. import shutil
  11. import subprocess
  12. import sysconfig
  13. import sys
  14. def clone_llvm(dst_dir, llvm_repo, llvm_branch):
  15. """
  16. any error will raise CallProcessError
  17. """
  18. llvm_dir = dst_dir.joinpath("llvm").resolve()
  19. if not llvm_dir.exists():
  20. GIT_CLONE_CMD = f"git clone --depth 1 --branch {llvm_branch} {llvm_repo} llvm"
  21. subprocess.check_output(shlex.split(GIT_CLONE_CMD), cwd=dst_dir)
  22. return llvm_dir
  23. def query_llvm_version(llvm_dir):
  24. GIT_LOG_CMD = f"git log --format=format:'%h' -1"
  25. return subprocess.check_output(
  26. shlex.split(GIT_LOG_CMD), cwd=llvm_dir, universal_newlines=True, text=True
  27. )
  28. def build_llvm(llvm_dir, platform, backends, projects, use_clang=False):
  29. LLVM_COMPILE_OPTIONS = [
  30. '-DCMAKE_BUILD_TYPE:STRING="Release"',
  31. "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
  32. "-DLLVM_APPEND_VC_REV:BOOL=ON",
  33. "-DLLVM_BUILD_EXAMPLES:BOOL=OFF",
  34. "-DLLVM_BUILD_LLVM_DYLIB:BOOL=OFF",
  35. "-DLLVM_BUILD_TESTS:BOOL=OFF",
  36. "-DLLVM_CCACHE_BUILD:BOOL=ON",
  37. "-DLLVM_ENABLE_BINDINGS:BOOL=OFF",
  38. "-DLLVM_ENABLE_IDE:BOOL=OFF",
  39. "-DLLVM_ENABLE_LIBEDIT=OFF",
  40. "-DLLVM_ENABLE_TERMINFO:BOOL=OFF",
  41. "-DLLVM_ENABLE_ZLIB:BOOL=OFF",
  42. "-DLLVM_INCLUDE_BENCHMARKS:BOOL=OFF",
  43. "-DLLVM_INCLUDE_DOCS:BOOL=OFF",
  44. "-DLLVM_INCLUDE_EXAMPLES:BOOL=OFF",
  45. "-DLLVM_INCLUDE_UTILS:BOOL=OFF",
  46. "-DLLVM_INCLUDE_TESTS:BOOL=OFF",
  47. "-DLLVM_BUILD_TESTS:BOOL=OFF",
  48. "-DLLVM_OPTIMIZED_TABLEGEN:BOOL=ON",
  49. ]
  50. # use clang/clang++/lld. but macos doesn't support lld
  51. if not sys.platform.startswith("darwin") and use_clang:
  52. if shutil.which("clang") and shutil.which("clang++") and shutil.which("lld"):
  53. os.environ["CC"] = "clang"
  54. os.environ["CXX"] = "clang++"
  55. LLVM_COMPILE_OPTIONS.append('-DLLVM_USE_LINKER:STRING="lld"')
  56. print("Use the clang toolchain")
  57. else:
  58. print("Can not find clang, clang++ and lld, keep using the gcc toolchain")
  59. else:
  60. print("Use the gcc toolchain")
  61. LLVM_EXTRA_COMPILE_OPTIONS = {
  62. "arc": [
  63. '-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD:STRING="ARC"',
  64. "-DLLVM_ENABLE_LIBICUUC:BOOL=OFF",
  65. "-DLLVM_ENABLE_LIBICUDATA:BOOL=OFF",
  66. ],
  67. "xtensa": [
  68. '-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD:STRING="Xtensa"',
  69. ],
  70. "windows": [
  71. "-DCMAKE_INSTALL_PREFIX=LLVM-install",
  72. ],
  73. "default": [],
  74. }
  75. LLVM_TARGETS_TO_BUILD = [
  76. '-DLLVM_TARGETS_TO_BUILD:STRING="' + ";".join(backends) + '"'
  77. if backends
  78. else '-DLLVM_TARGETS_TO_BUILD:STRING="AArch64;ARM;Mips;RISCV;X86"'
  79. ]
  80. LLVM_PROJECTS_TO_BUILD = [
  81. '-DLLVM_ENABLE_PROJECTS:STRING="' + ";".join(projects) + '"' if projects else ""
  82. ]
  83. # lldb project requires libxml2
  84. LLVM_LIBXML2_OPTION = [
  85. "-DLLVM_ENABLE_LIBXML2:BOOL=" + ("ON" if "lldb" in projects else "OFF")
  86. ]
  87. # enabling LLVM_INCLUDE_TOOLS will increase ~300M to the final package
  88. LLVM_INCLUDE_TOOLS_OPTION = [
  89. "-DLLVM_INCLUDE_TOOLS:BOOL=ON" if projects else "-DLLVM_INCLUDE_TOOLS:BOOL=OFF"
  90. ]
  91. if not llvm_dir.exists():
  92. raise Exception(f"{llvm_dir} doesn't exist")
  93. build_dir = llvm_dir.joinpath(
  94. "win32build" if "windows" == platform else "build"
  95. ).resolve()
  96. build_dir.mkdir(exist_ok=True)
  97. lib_llvm_core_library = build_dir.joinpath("lib/libLLVMCore.a").resolve()
  98. if lib_llvm_core_library.exists():
  99. print(
  100. f"It has already been fully compiled. If want to a re-build, please remove {build_dir} manually and try again"
  101. )
  102. return None
  103. compile_options = " ".join(
  104. LLVM_COMPILE_OPTIONS
  105. + LLVM_LIBXML2_OPTION
  106. + LLVM_EXTRA_COMPILE_OPTIONS.get(
  107. platform, LLVM_EXTRA_COMPILE_OPTIONS["default"]
  108. )
  109. + LLVM_TARGETS_TO_BUILD
  110. + LLVM_PROJECTS_TO_BUILD
  111. + LLVM_INCLUDE_TOOLS_OPTION
  112. )
  113. CONFIG_CMD = f"cmake {compile_options} ../llvm"
  114. if "windows" == platform:
  115. if "mingw" in sysconfig.get_platform().lower():
  116. CONFIG_CMD += " -G'Unix Makefiles'"
  117. else:
  118. CONFIG_CMD += " -A x64"
  119. else:
  120. CONFIG_CMD += " -G'Ninja'"
  121. subprocess.check_call(shlex.split(CONFIG_CMD), cwd=build_dir)
  122. BUILD_CMD = "cmake --build . --target package" + (
  123. " --config Release" if "windows" == platform else ""
  124. )
  125. subprocess.check_call(shlex.split(BUILD_CMD), cwd=build_dir)
  126. return build_dir
  127. def repackage_llvm(llvm_dir):
  128. build_dir = llvm_dir.joinpath("./build").resolve()
  129. packs = [f for f in build_dir.glob("LLVM-*.tar.gz")]
  130. if len(packs) > 1:
  131. raise Exception("Find more than one LLVM-*.tar.gz")
  132. if not packs:
  133. return
  134. llvm_package = packs[0].name
  135. # mv build/LLVM-*.gz .
  136. shutil.move(str(build_dir.joinpath(llvm_package).resolve()), str(llvm_dir))
  137. # rm -r build
  138. shutil.rmtree(str(build_dir))
  139. # mkdir build
  140. build_dir.mkdir()
  141. # tar xf ./LLVM-*.tar.gz --strip-components=1 --directory=build
  142. CMD = f"tar xf {llvm_dir.joinpath(llvm_package).resolve()} --strip-components=1 --directory={build_dir}"
  143. subprocess.check_call(shlex.split(CMD), cwd=llvm_dir)
  144. # rm ./LLVM-1*.gz
  145. os.remove(llvm_dir.joinpath(llvm_package).resolve())
  146. def main():
  147. parser = argparse.ArgumentParser(description="build necessary LLVM libraries")
  148. parser.add_argument(
  149. "--platform",
  150. type=str,
  151. choices=["android", "arc", "darwin", "linux", "windows", "xtensa"],
  152. help="identify current platform",
  153. )
  154. parser.add_argument(
  155. "--arch",
  156. nargs="+",
  157. type=str,
  158. choices=[
  159. "AArch64",
  160. "ARC",
  161. "ARM",
  162. "Mips",
  163. "RISCV",
  164. "WebAssembly",
  165. "X86",
  166. "Xtensa",
  167. ],
  168. help="identify LLVM supported backends, separate by space, like '--arch ARM Mips X86'",
  169. )
  170. parser.add_argument(
  171. "--project",
  172. nargs="+",
  173. type=str,
  174. default="",
  175. choices=["clang", "lldb"],
  176. help="identify extra LLVM projects, separate by space, like '--project clang lldb'",
  177. )
  178. parser.add_argument(
  179. "--llvm-ver",
  180. action="store_true",
  181. help="return the version info of generated llvm libraries",
  182. )
  183. parser.add_argument(
  184. "--use-clang",
  185. action="store_true",
  186. help="use clang instead of gcc",
  187. )
  188. options = parser.parse_args()
  189. # if the "platform" is not identified in the command line option,
  190. # detect it
  191. if not options.platform:
  192. if sys.platform.startswith("win32") or sys.platform.startswith("msys"):
  193. platform = "windows"
  194. elif sys.platform.startswith("darwin"):
  195. platform = "darwin"
  196. else:
  197. platform = "linux"
  198. else:
  199. platform = options.platform
  200. llvm_repo_and_branch = {
  201. "arc": {
  202. "repo": "https://github.com/llvm/llvm-project.git",
  203. "branch": "release/15.x",
  204. },
  205. "xtensa": {
  206. "repo": "https://github.com/espressif/llvm-project.git",
  207. "branch": "xtensa_release_15.x",
  208. },
  209. "default": {
  210. "repo": "https://github.com/llvm/llvm-project.git",
  211. "branch": "release/15.x",
  212. },
  213. }
  214. # retrieve the real file
  215. current_file = pathlib.Path(__file__)
  216. if current_file.is_symlink():
  217. current_file = pathlib.Path(os.readlink(current_file))
  218. current_dir = current_file.parent.resolve()
  219. deps_dir = current_dir.joinpath("../core/deps").resolve()
  220. try:
  221. llvm_info = llvm_repo_and_branch.get(platform, llvm_repo_and_branch["default"])
  222. llvm_dir = clone_llvm(deps_dir, llvm_info["repo"], llvm_info["branch"])
  223. if options.llvm_ver:
  224. commit_hash = query_llvm_version(llvm_dir)
  225. print(commit_hash)
  226. return commit_hash is not None
  227. if (
  228. build_llvm(
  229. llvm_dir, platform, options.arch, options.project, options.use_clang
  230. )
  231. is not None
  232. ):
  233. repackage_llvm(llvm_dir)
  234. return True
  235. except subprocess.CalledProcessError:
  236. return False
  237. if __name__ == "__main__":
  238. sys.exit(0 if main() else 1)