build_llvm.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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 requests
  10. import shlex
  11. import shutil
  12. import subprocess
  13. import sysconfig
  14. import sys
  15. def clone_llvm(dst_dir, llvm_repo, llvm_branch):
  16. """
  17. any error will raise CallProcessError
  18. """
  19. llvm_dir = dst_dir.joinpath("llvm").resolve()
  20. if not llvm_dir.exists():
  21. GIT_CLONE_CMD = f"git clone --depth 1 --branch {llvm_branch} {llvm_repo} llvm"
  22. print(GIT_CLONE_CMD)
  23. subprocess.check_output(shlex.split(GIT_CLONE_CMD), cwd=dst_dir)
  24. return llvm_dir
  25. def query_llvm_version(llvm_info):
  26. github_token = os.environ["GH_TOKEN"]
  27. owner_project = (
  28. llvm_info["repo"].replace("https://github.com/", "").replace(".git", "")
  29. )
  30. url = f"https://api.github.com/repos/{owner_project}/commits/{llvm_info['branch']}"
  31. headers = {"Authorization": f"Bearer {github_token}"}
  32. try:
  33. response = requests.request("GET", url, headers=headers, data={})
  34. response.raise_for_status()
  35. except requests.exceptions.HTTPError as error:
  36. print(error) # for debugging purpose
  37. return None
  38. response = response.json()
  39. return response["sha"]
  40. def build_llvm(llvm_dir, platform, backends, projects, use_clang=False, extra_flags=""):
  41. LLVM_COMPILE_OPTIONS = [
  42. '-DCMAKE_BUILD_TYPE:STRING="Release"',
  43. "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
  44. "-DLLVM_APPEND_VC_REV:BOOL=ON",
  45. "-DLLVM_BUILD_EXAMPLES:BOOL=OFF",
  46. "-DLLVM_BUILD_LLVM_DYLIB:BOOL=OFF",
  47. "-DLLVM_ENABLE_BINDINGS:BOOL=OFF",
  48. "-DLLVM_ENABLE_IDE:BOOL=OFF",
  49. "-DLLVM_ENABLE_LIBEDIT=OFF",
  50. "-DLLVM_ENABLE_TERMINFO:BOOL=OFF",
  51. "-DLLVM_ENABLE_ZLIB:BOOL=ON",
  52. "-DLLVM_INCLUDE_BENCHMARKS:BOOL=OFF",
  53. "-DLLVM_INCLUDE_DOCS:BOOL=OFF",
  54. "-DLLVM_INCLUDE_EXAMPLES:BOOL=OFF",
  55. "-DLLVM_INCLUDE_UTILS:BOOL=OFF",
  56. "-DLLVM_INCLUDE_TESTS:BOOL=OFF",
  57. "-DLLVM_OPTIMIZED_TABLEGEN:BOOL=ON",
  58. ]
  59. # ccache is not available on Windows
  60. if not "windows" == platform:
  61. LLVM_COMPILE_OPTIONS.append("-DLLVM_CCACHE_BUILD:BOOL=ON")
  62. # perf support is available on Linux only
  63. if "linux" == platform:
  64. LLVM_COMPILE_OPTIONS.append("-DLLVM_USE_PERF:BOOL=ON")
  65. # use clang/clang++/lld. but macos doesn't support lld
  66. if not sys.platform.startswith("darwin") and use_clang:
  67. if shutil.which("clang") and shutil.which("clang++") and shutil.which("lld"):
  68. os.environ["CC"] = "clang"
  69. os.environ["CXX"] = "clang++"
  70. LLVM_COMPILE_OPTIONS.append('-DLLVM_USE_LINKER:STRING="lld"')
  71. print("Use the clang toolchain")
  72. else:
  73. print("Can not find clang, clang++ and lld, keep using the gcc toolchain")
  74. else:
  75. print("Use the gcc toolchain")
  76. LLVM_EXTRA_COMPILE_OPTIONS = {
  77. "arc": [
  78. '-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD:STRING="ARC"',
  79. "-DLLVM_ENABLE_LIBICUUC:BOOL=OFF",
  80. "-DLLVM_ENABLE_LIBICUDATA:BOOL=OFF",
  81. ],
  82. "xtensa": [
  83. '-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD:STRING="Xtensa"',
  84. ],
  85. "windows": [
  86. "-DCMAKE_INSTALL_PREFIX=LLVM-install",
  87. ],
  88. "default": [],
  89. }
  90. experimental_backends = ["ARC", "Xtensa"]
  91. normal_backends = [s for s in backends if s not in experimental_backends]
  92. LLVM_TARGETS_TO_BUILD = [
  93. (
  94. '-DLLVM_TARGETS_TO_BUILD:STRING="' + ";".join(normal_backends) + '"'
  95. if normal_backends
  96. else '-DLLVM_TARGETS_TO_BUILD:STRING="AArch64;ARM;Mips;RISCV;X86"'
  97. )
  98. ]
  99. # if not on ARC platform, but want to add expeirmental backend ARC as target
  100. if platform != "ARC" and "ARC" in backends:
  101. LLVM_TARGETS_TO_BUILD.extend(LLVM_EXTRA_COMPILE_OPTIONS["arc"])
  102. if platform != "Xtensa" and "Xtensa" in backends:
  103. print(
  104. "Currently it's not supported to build Xtensa backend on non-Xtensa platform"
  105. )
  106. return None
  107. LLVM_PROJECTS_TO_BUILD = [
  108. '-DLLVM_ENABLE_PROJECTS:STRING="' + ";".join(projects) + '"' if projects else ""
  109. ]
  110. # lldb project requires libxml2
  111. LLVM_LIBXML2_OPTION = [
  112. "-DLLVM_ENABLE_LIBXML2:BOOL=" + ("ON" if "lldb" in projects else "OFF")
  113. ]
  114. # enabling LLVM_INCLUDE_TOOLS will increase ~300M to the final package
  115. LLVM_INCLUDE_TOOLS_OPTION = [
  116. "-DLLVM_INCLUDE_TOOLS:BOOL=ON" if projects else "-DLLVM_INCLUDE_TOOLS:BOOL=OFF"
  117. ]
  118. if not llvm_dir.exists():
  119. raise Exception(f"{llvm_dir} doesn't exist")
  120. build_dir = llvm_dir.joinpath("build").resolve()
  121. build_dir.mkdir(exist_ok=True)
  122. lib_llvm_core_library = build_dir.joinpath("lib/libLLVMCore.a").resolve()
  123. if lib_llvm_core_library.exists():
  124. print(
  125. f"It has already been fully compiled. If want to a re-build, please remove {build_dir} manually and try again"
  126. )
  127. return None
  128. compile_options = " ".join(
  129. LLVM_COMPILE_OPTIONS
  130. + LLVM_LIBXML2_OPTION
  131. + LLVM_EXTRA_COMPILE_OPTIONS.get(
  132. platform, LLVM_EXTRA_COMPILE_OPTIONS["default"]
  133. )
  134. + LLVM_TARGETS_TO_BUILD
  135. + LLVM_PROJECTS_TO_BUILD
  136. + LLVM_INCLUDE_TOOLS_OPTION
  137. )
  138. CONFIG_CMD = f"cmake {compile_options} {extra_flags} ../llvm"
  139. if "windows" == platform:
  140. if "mingw" in sysconfig.get_platform().lower():
  141. CONFIG_CMD += " -G'Unix Makefiles'"
  142. else:
  143. CONFIG_CMD += " -A x64"
  144. else:
  145. CONFIG_CMD += " -G'Ninja'"
  146. print(f"Config command: {CONFIG_CMD}")
  147. subprocess.check_call(shlex.split(CONFIG_CMD), cwd=build_dir)
  148. BUILD_CMD = "cmake --build . --target package" + (
  149. " --config Release" if "windows" == platform else ""
  150. )
  151. if "windows" == platform:
  152. BUILD_CMD += " --parallel " + str(os.cpu_count())
  153. print(f"Build command: {BUILD_CMD}")
  154. subprocess.check_call(shlex.split(BUILD_CMD), cwd=build_dir)
  155. return build_dir
  156. def repackage_llvm(llvm_dir):
  157. build_dir = llvm_dir.joinpath("./build").resolve()
  158. packs = [f for f in build_dir.glob("LLVM-*.tar.gz")]
  159. if len(packs) > 1:
  160. raise Exception("Find more than one LLVM-*.tar.gz")
  161. if not packs:
  162. raise Exception("Didn't find any LLVM-* package")
  163. return
  164. llvm_package = packs[0].name
  165. # mv build/LLVM-*.gz .
  166. shutil.move(str(build_dir.joinpath(llvm_package).resolve()), str(llvm_dir))
  167. # rm -r build
  168. shutil.rmtree(str(build_dir))
  169. # mkdir build
  170. build_dir.mkdir()
  171. # tar xf ./LLVM-*.tar.gz --strip-components=1 --directory=build
  172. CMD = f"tar xf {llvm_dir.joinpath(llvm_package).resolve()} --strip-components=1 --directory={build_dir}"
  173. subprocess.check_call(shlex.split(CMD), cwd=llvm_dir)
  174. # rm ./LLVM-1*.gz
  175. os.remove(llvm_dir.joinpath(llvm_package).resolve())
  176. def repackage_llvm_windows(llvm_dir):
  177. build_dir = llvm_dir.joinpath("./build").resolve()
  178. packs_path = [
  179. f for f in build_dir.glob("./_CPack_Packages/win64/NSIS/LLVM-*-win64")
  180. ]
  181. if len(packs_path) > 1:
  182. raise Exception("Find more than one LLVM-* package")
  183. if not packs_path:
  184. raise Exception("Didn't find any LLVM-* package")
  185. return
  186. llvm_package_path = f"_CPack_Packages/win64/NSIS/{packs_path[0].name}"
  187. windows_package_dir = build_dir.joinpath(llvm_package_path).resolve()
  188. # mv package dir outside of build
  189. shutil.move(str(windows_package_dir), str(llvm_dir))
  190. # rm -r build
  191. shutil.rmtree(str(build_dir))
  192. # mkdir build
  193. build_dir.mkdir()
  194. # move back all the subdiretories under cpack directory(bin/include/lib) to build dir
  195. moved_package_dir = llvm_dir.joinpath(packs_path[0].name)
  196. for sub_dir in moved_package_dir.iterdir():
  197. shutil.move(str(sub_dir), str(build_dir))
  198. moved_package_dir.rmdir()
  199. def main():
  200. parser = argparse.ArgumentParser(description="build necessary LLVM libraries")
  201. parser.add_argument(
  202. "--platform",
  203. type=str,
  204. choices=["android", "arc", "darwin", "linux", "windows", "xtensa"],
  205. help="identify current platform",
  206. )
  207. parser.add_argument(
  208. "--arch",
  209. nargs="+",
  210. type=str,
  211. choices=[
  212. "AArch64",
  213. "ARC",
  214. "ARM",
  215. "Mips",
  216. "RISCV",
  217. "WebAssembly",
  218. "X86",
  219. "Xtensa",
  220. ],
  221. default=[],
  222. help="identify LLVM supported backends, separate by space, like '--arch ARM Mips X86'",
  223. )
  224. parser.add_argument(
  225. "--project",
  226. nargs="+",
  227. type=str,
  228. default="",
  229. choices=["clang", "lldb"],
  230. help="identify extra LLVM projects, separate by space, like '--project clang lldb'",
  231. )
  232. parser.add_argument(
  233. "--llvm-ver",
  234. action="store_true",
  235. help="return the version info of generated llvm libraries",
  236. )
  237. parser.add_argument(
  238. "--use-clang",
  239. action="store_true",
  240. help="use clang instead of gcc",
  241. )
  242. parser.add_argument(
  243. "--extra-cmake-flags",
  244. type=str,
  245. default="",
  246. help="custom extra cmake flags",
  247. )
  248. options = parser.parse_args()
  249. # if the "platform" is not identified in the command line option,
  250. # detect it
  251. if not options.platform:
  252. if sys.platform.startswith("win32") or sys.platform.startswith("msys"):
  253. platform = "windows"
  254. elif sys.platform.startswith("darwin"):
  255. platform = "darwin"
  256. else:
  257. platform = "linux"
  258. else:
  259. platform = options.platform
  260. llvm_repo_and_branch = {
  261. "arc": {
  262. "repo": "https://github.com/llvm/llvm-project.git",
  263. "repo_ssh": "git@github.com:llvm/llvm-project.git",
  264. "branch": "release/15.x",
  265. },
  266. "xtensa": {
  267. "repo": "https://github.com/espressif/llvm-project.git",
  268. "repo_ssh": "git@github.com:espressif/llvm-project.git",
  269. "branch": "xtensa_release_17.0.1",
  270. },
  271. "default": {
  272. "repo": "https://github.com/llvm/llvm-project.git",
  273. "repo_ssh": "git@github.com:llvm/llvm-project.git",
  274. "branch": "release/15.x",
  275. },
  276. }
  277. # retrieve the real file
  278. current_file = pathlib.Path(__file__)
  279. if current_file.is_symlink():
  280. current_file = pathlib.Path(os.readlink(current_file))
  281. current_dir = current_file.parent.resolve()
  282. deps_dir = current_dir.joinpath("../core/deps").resolve()
  283. try:
  284. llvm_info = llvm_repo_and_branch.get(platform, llvm_repo_and_branch["default"])
  285. if options.llvm_ver:
  286. commit_hash = query_llvm_version(llvm_info)
  287. print(commit_hash)
  288. return commit_hash is not None
  289. repo_addr = llvm_info["repo"]
  290. if os.environ.get("USE_GIT_SSH") == "true":
  291. repo_addr = llvm_info["repo_ssh"]
  292. else:
  293. print("To use ssh for git clone, run: export USE_GIT_SSH=true")
  294. llvm_dir = clone_llvm(deps_dir, repo_addr, llvm_info["branch"])
  295. if (
  296. build_llvm(
  297. llvm_dir,
  298. platform,
  299. options.arch,
  300. options.project,
  301. options.use_clang,
  302. options.extra_cmake_flags,
  303. )
  304. is not None
  305. ):
  306. # TODO: repackage process may change in the future, this work for LLVM 15.x
  307. if "windows" == platform:
  308. repackage_llvm_windows(llvm_dir)
  309. else:
  310. repackage_llvm(llvm_dir)
  311. return True
  312. except subprocess.CalledProcessError:
  313. return False
  314. if __name__ == "__main__":
  315. sys.exit(0 if main() else 1)