llvm-compilers-check 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. #!/usr/bin/python3
  2. ##===- utils/llvmbuild - Build the LLVM project ----------------*-python-*-===##
  3. #
  4. # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  5. # See https://llvm.org/LICENSE.txt for license information.
  6. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  7. #
  8. ##===----------------------------------------------------------------------===##
  9. #
  10. # This script builds many different flavors of the LLVM ecosystem. It
  11. # will build LLVM, Clang and dragonegg as well as run tests on them.
  12. # This script is convenient to use to check builds and tests before
  13. # committing changes to the upstream repository
  14. #
  15. # A typical source setup uses three trees and looks like this:
  16. #
  17. # official
  18. # dragonegg
  19. # llvm
  20. # tools
  21. # clang
  22. # staging
  23. # dragonegg
  24. # llvm
  25. # tools
  26. # clang
  27. # commit
  28. # dragonegg
  29. # llvm
  30. # tools
  31. # clang
  32. #
  33. # In a typical workflow, the "official" tree always contains unchanged
  34. # sources from the main LLVM project repositories. The "staging" tree
  35. # is where local work is done. A set of changes resides there waiting
  36. # to be moved upstream. The "commit" tree is where changes from
  37. # "staging" make their way upstream. Individual incremental changes
  38. # from "staging" are applied to "commit" and committed upstream after
  39. # a successful build and test run. A successful build is one in which
  40. # testing results in no more failures than seen in the testing of the
  41. # "official" tree.
  42. #
  43. # A build may be invoked as such:
  44. #
  45. # llvmbuild --src=~/llvm/commit --src=~/llvm/staging --src=~/llvm/official
  46. # --build=debug --build=release --build=paranoid
  47. # --prefix=/home/greened/install --builddir=/home/greened/build
  48. #
  49. # This will build the LLVM ecosystem, including LLVM, Clangand
  50. # dragonegg, putting build results in ~/build and installing tools in
  51. # ~/install. llvm-compilers-check creates separate build and install
  52. # directories for each source/build flavor. In the above example,
  53. # llvmbuild will build debug, release and paranoid (debug+checks)
  54. # flavors from each source tree (official, staging and commit) for a
  55. # total of nine builds. All builds will be run in parallel.
  56. #
  57. # The user may control parallelism via the --jobs and --threads
  58. # switches. --jobs tells llvm-compilers-checl the maximum total
  59. # number of builds to activate in parallel. The user may think of it
  60. # as equivalent to the GNU make -j switch. --threads tells
  61. # llvm-compilers-check how many worker threads to use to accomplish
  62. # those builds. If --threads is less than --jobs, --threads workers
  63. # will be launched and each one will pick a source/flavor combination
  64. # to build. Then llvm-compilers-check will invoke GNU make with -j
  65. # (--jobs / --threads) to use up the remaining job capacity. Once a
  66. # worker is finished with a build, it will pick another combination
  67. # off the list and start building it.
  68. #
  69. ##===----------------------------------------------------------------------===##
  70. import optparse
  71. import os
  72. import sys
  73. import threading
  74. import queue
  75. import logging
  76. import traceback
  77. import subprocess
  78. import re
  79. # TODO: Use shutil.which when it is available (3.2 or later)
  80. def find_executable(executable, path=None):
  81. """Try to find 'executable' in the directories listed in 'path' (a
  82. string listing directories separated by 'os.pathsep'; defaults to
  83. os.environ['PATH']). Returns the complete filename or None if not
  84. found
  85. """
  86. if path is None:
  87. path = os.environ['PATH']
  88. paths = path.split(os.pathsep)
  89. extlist = ['']
  90. if os.name == 'os2':
  91. (base, ext) = os.path.splitext(executable)
  92. # executable files on OS/2 can have an arbitrary extension, but
  93. # .exe is automatically appended if no dot is present in the name
  94. if not ext:
  95. executable = executable + ".exe"
  96. elif sys.platform == 'win32':
  97. pathext = os.environ['PATHEXT'].lower().split(os.pathsep)
  98. (base, ext) = os.path.splitext(executable)
  99. if ext.lower() not in pathext:
  100. extlist = pathext
  101. for ext in extlist:
  102. execname = executable + ext
  103. if os.path.isfile(execname):
  104. return execname
  105. else:
  106. for p in paths:
  107. f = os.path.join(p, execname)
  108. if os.path.isfile(f):
  109. return f
  110. else:
  111. return None
  112. def is_executable(fpath):
  113. return os.path.exists(fpath) and os.access(fpath, os.X_OK)
  114. def add_options(parser):
  115. parser.add_option("-v", "--verbose", action="store_true",
  116. default=False,
  117. help=("Output informational messages"
  118. " [default: %default]"))
  119. parser.add_option("--src", action="append",
  120. help=("Top-level source directory [default: %default]"))
  121. parser.add_option("--build", action="append",
  122. help=("Build types to run [default: %default]"))
  123. parser.add_option("--cc", default=find_executable("cc"),
  124. help=("The C compiler to use [default: %default]"))
  125. parser.add_option("--cxx", default=find_executable("c++"),
  126. help=("The C++ compiler to use [default: %default]"))
  127. parser.add_option("--threads", default=4, type="int",
  128. help=("The number of worker threads to use "
  129. "[default: %default]"))
  130. parser.add_option("--jobs", "-j", default=8, type="int",
  131. help=("The number of simultaneous build jobs "
  132. "[default: %default]"))
  133. parser.add_option("--prefix",
  134. help=("Root install directory [default: %default]"))
  135. parser.add_option("--builddir",
  136. help=("Root build directory [default: %default]"))
  137. parser.add_option("--extra-llvm-config-flags", default="",
  138. help=("Extra flags to pass to llvm configure [default: %default]"))
  139. parser.add_option("--force-configure", default=False, action="store_true",
  140. help=("Force reconfigure of all components"))
  141. parser.add_option("--no-dragonegg", default=False, action="store_true",
  142. help=("Do not build dragonegg"))
  143. parser.add_option("--no-install", default=False, action="store_true",
  144. help=("Do not do installs"))
  145. parser.add_option("--keep-going", default=False, action="store_true",
  146. help=("Keep going after failures"))
  147. parser.add_option("--no-flavor-prefix", default=False, action="store_true",
  148. help=("Do not append the build flavor to the install path"))
  149. parser.add_option("--enable-werror", default=False, action="store_true",
  150. help=("Build with -Werror"))
  151. return
  152. def check_options(parser, options, valid_builds):
  153. # See if we're building valid flavors.
  154. for build in options.build:
  155. if (build not in valid_builds):
  156. parser.error("'" + build + "' is not a valid build flavor "
  157. + str(valid_builds))
  158. # See if we can find source directories.
  159. for src in options.src:
  160. for component in components:
  161. component = component.rstrip("2")
  162. compsrc = src + "/" + component
  163. if (not os.path.isdir(compsrc)):
  164. parser.error("'" + compsrc + "' does not exist")
  165. # See if we can find the compilers
  166. options.cc = find_executable(options.cc)
  167. options.cxx = find_executable(options.cxx)
  168. return
  169. # Find a unique short name for the given set of paths. This searches
  170. # back through path components until it finds unique component names
  171. # among all given paths.
  172. def get_path_abbrevs(paths):
  173. # Find the number of common starting characters in the last component
  174. # of the paths.
  175. unique_paths = list(paths)
  176. class NotFoundException(Exception): pass
  177. # Find a unique component of each path.
  178. unique_bases = unique_paths[:]
  179. found = 0
  180. while len(unique_paths) > 0:
  181. bases = [os.path.basename(src) for src in unique_paths]
  182. components = { c for c in bases }
  183. # Account for single entry in paths.
  184. if len(components) > 1 or len(components) == len(bases):
  185. # We found something unique.
  186. for c in components:
  187. if bases.count(c) == 1:
  188. index = bases.index(c)
  189. unique_bases[index] = c
  190. # Remove the corresponding path from the set under
  191. # consideration.
  192. unique_paths[index] = None
  193. unique_paths = [ p for p in unique_paths if p is not None ]
  194. unique_paths = [os.path.dirname(src) for src in unique_paths]
  195. if len(unique_paths) > 0:
  196. raise NotFoundException()
  197. abbrevs = dict(zip(paths, [base for base in unique_bases]))
  198. return abbrevs
  199. # Given a set of unique names, find a short character sequence that
  200. # uniquely identifies them.
  201. def get_short_abbrevs(unique_bases):
  202. # Find a unique start character for each path base.
  203. my_unique_bases = unique_bases[:]
  204. unique_char_starts = unique_bases[:]
  205. while len(my_unique_bases) > 0:
  206. for start, char_tuple in enumerate(zip(*[base
  207. for base in my_unique_bases])):
  208. chars = { c for c in char_tuple }
  209. # Account for single path.
  210. if len(chars) > 1 or len(chars) == len(char_tuple):
  211. # We found something unique.
  212. for c in chars:
  213. if char_tuple.count(c) == 1:
  214. index = char_tuple.index(c)
  215. unique_char_starts[index] = start
  216. # Remove the corresponding path from the set under
  217. # consideration.
  218. my_unique_bases[index] = None
  219. my_unique_bases = [ b for b in my_unique_bases
  220. if b is not None ]
  221. break
  222. if len(my_unique_bases) > 0:
  223. raise NotFoundException()
  224. abbrevs = [abbrev[start_index:start_index+3]
  225. for abbrev, start_index
  226. in zip([base for base in unique_bases],
  227. [index for index in unique_char_starts])]
  228. abbrevs = dict(zip(unique_bases, abbrevs))
  229. return abbrevs
  230. class Builder(threading.Thread):
  231. class ExecutableNotFound(Exception): pass
  232. class FileNotExecutable(Exception): pass
  233. def __init__(self, work_queue, jobs,
  234. build_abbrev, source_abbrev,
  235. options):
  236. super().__init__()
  237. self.work_queue = work_queue
  238. self.jobs = jobs
  239. self.cc = options.cc
  240. self.cxx = options.cxx
  241. self.build_abbrev = build_abbrev
  242. self.source_abbrev = source_abbrev
  243. self.build_prefix = options.builddir
  244. self.install_prefix = options.prefix
  245. self.options = options
  246. self.component_abbrev = dict(
  247. llvm="llvm",
  248. dragonegg="degg")
  249. def run(self):
  250. while True:
  251. try:
  252. source, build = self.work_queue.get()
  253. self.dobuild(source, build)
  254. except:
  255. traceback.print_exc()
  256. finally:
  257. self.work_queue.task_done()
  258. def execute(self, command, execdir, env, component):
  259. prefix = self.component_abbrev[component.replace("-", "_")]
  260. pwd = os.getcwd()
  261. if not os.path.exists(execdir):
  262. os.makedirs(execdir)
  263. execenv = os.environ.copy()
  264. for key, value in env.items():
  265. execenv[key] = value
  266. self.logger.debug("[" + prefix + "] " + "env " + str(env) + " "
  267. + " ".join(command));
  268. try:
  269. proc = subprocess.Popen(command,
  270. cwd=execdir,
  271. env=execenv,
  272. stdout=subprocess.PIPE,
  273. stderr=subprocess.STDOUT)
  274. line = proc.stdout.readline()
  275. while line:
  276. self.logger.info("[" + prefix + "] "
  277. + str(line, "utf-8").rstrip())
  278. line = proc.stdout.readline()
  279. (stdoutdata, stderrdata) = proc.communicate()
  280. retcode = proc.wait()
  281. return retcode
  282. except:
  283. traceback.print_exc()
  284. # Get a list of C++ include directories to pass to clang.
  285. def get_includes(self):
  286. # Assume we're building with g++ for now.
  287. command = [self.cxx]
  288. command += ["-v", "-x", "c++", "/dev/null", "-fsyntax-only"]
  289. includes = []
  290. self.logger.debug(command)
  291. try:
  292. proc = subprocess.Popen(command,
  293. stdout=subprocess.PIPE,
  294. stderr=subprocess.STDOUT)
  295. gather = False
  296. line = proc.stdout.readline()
  297. while line:
  298. self.logger.debug(line)
  299. if re.search("End of search list", str(line)) is not None:
  300. self.logger.debug("Stop Gather")
  301. gather = False
  302. if gather:
  303. includes.append(str(line, "utf-8").strip())
  304. if re.search("#include <...> search starts", str(line)) is not None:
  305. self.logger.debug("Start Gather")
  306. gather = True
  307. line = proc.stdout.readline()
  308. except:
  309. traceback.print_exc()
  310. self.logger.debug(includes)
  311. return includes
  312. def dobuild(self, source, build):
  313. build_suffix = ""
  314. ssabbrev = get_short_abbrevs([ab for ab in self.source_abbrev.values()])
  315. prefix = "[" + ssabbrev[self.source_abbrev[source]] + "-" + self.build_abbrev[build] + "]"
  316. if (not self.options.no_flavor_prefix):
  317. self.install_prefix += "/" + self.source_abbrev[source] + "/" + build
  318. build_suffix += "/" + self.source_abbrev[source] + "/" + build
  319. self.logger = logging.getLogger(prefix)
  320. self.logger.debug(self.install_prefix)
  321. # Assume we're building with gcc for now.
  322. cxxincludes = self.get_includes()
  323. cxxroot = os.path.dirname(cxxincludes[0]) # Remove the version
  324. cxxroot = os.path.dirname(cxxroot) # Remove the c++
  325. cxxroot = os.path.dirname(cxxroot) # Remove the include
  326. configure_flags = dict(
  327. llvm=dict(debug=["--prefix=" + self.install_prefix,
  328. "--enable-assertions",
  329. "--disable-optimized",
  330. "--with-gcc-toolchain=" + cxxroot],
  331. release=["--prefix=" + self.install_prefix,
  332. "--enable-optimized",
  333. "--with-gcc-toolchain=" + cxxroot],
  334. paranoid=["--prefix=" + self.install_prefix,
  335. "--enable-assertions",
  336. "--enable-expensive-checks",
  337. "--disable-optimized",
  338. "--with-gcc-toolchain=" + cxxroot]),
  339. dragonegg=dict(debug=[],
  340. release=[],
  341. paranoid=[]))
  342. if (self.options.enable_werror):
  343. configure_flags["llvm"]["debug"].append("--enable-werror")
  344. configure_flags["llvm"]["release"].append("--enable-werror")
  345. configure_flags["llvm"]["paranoid"].append("--enable-werror")
  346. configure_env = dict(
  347. llvm=dict(debug=dict(CC=self.cc,
  348. CXX=self.cxx),
  349. release=dict(CC=self.cc,
  350. CXX=self.cxx),
  351. paranoid=dict(CC=self.cc,
  352. CXX=self.cxx)),
  353. dragonegg=dict(debug=dict(CC=self.cc,
  354. CXX=self.cxx),
  355. release=dict(CC=self.cc,
  356. CXX=self.cxx),
  357. paranoid=dict(CC=self.cc,
  358. CXX=self.cxx)))
  359. make_flags = dict(
  360. llvm=dict(debug=["-j" + str(self.jobs)],
  361. release=["-j" + str(self.jobs)],
  362. paranoid=["-j" + str(self.jobs)]),
  363. dragonegg=dict(debug=["-j" + str(self.jobs)],
  364. release=["-j" + str(self.jobs)],
  365. paranoid=["-j" + str(self.jobs)]))
  366. make_env = dict(
  367. llvm=dict(debug=dict(),
  368. release=dict(),
  369. paranoid=dict()),
  370. dragonegg=dict(debug=dict(GCC=self.cc,
  371. LLVM_CONFIG=self.install_prefix + "/bin/llvm-config"),
  372. release=dict(GCC=self.cc,
  373. LLVM_CONFIG=self.install_prefix + "/bin/llvm-config"),
  374. paranoid=dict(GCC=self.cc,
  375. LLVM_CONFIG=self.install_prefix + "/bin/llvm-config")))
  376. make_install_flags = dict(
  377. llvm=dict(debug=["install"],
  378. release=["install"],
  379. paranoid=["install"]),
  380. dragonegg=dict(debug=["install"],
  381. release=["install"],
  382. paranoid=["install"]))
  383. make_install_env = dict(
  384. llvm=dict(debug=dict(),
  385. release=dict(),
  386. paranoid=dict()),
  387. dragonegg=dict(debug=dict(),
  388. release=dict(),
  389. paranoid=dict()))
  390. make_check_flags = dict(
  391. llvm=dict(debug=["check"],
  392. release=["check"],
  393. paranoid=["check"]),
  394. dragonegg=dict(debug=["check"],
  395. release=["check"],
  396. paranoid=["check"]))
  397. make_check_env = dict(
  398. llvm=dict(debug=dict(),
  399. release=dict(),
  400. paranoid=dict()),
  401. dragonegg=dict(debug=dict(),
  402. release=dict(),
  403. paranoid=dict()))
  404. for component in components:
  405. comp = component[:]
  406. if (self.options.no_dragonegg):
  407. if (comp == 'dragonegg'):
  408. self.logger.info("Skipping " + component + " in "
  409. + builddir)
  410. continue
  411. srcdir = source + "/" + comp.rstrip("2")
  412. builddir = self.build_prefix + "/" + comp + "/" + build_suffix
  413. installdir = self.install_prefix
  414. comp_key = comp.replace("-", "_")
  415. config_args = configure_flags[comp_key][build][:]
  416. config_args.extend(getattr(self.options,
  417. "extra_" + comp_key.rstrip("2")
  418. + "_config_flags",
  419. "").split())
  420. self.logger.info("Configuring " + component + " in " + builddir)
  421. configrc = self.configure(component, srcdir, builddir,
  422. config_args,
  423. configure_env[comp_key][build])
  424. if (configrc == None) :
  425. self.logger.info("[None] Failed to configure " + component + " in " + installdir)
  426. if (configrc == 0 or self.options.keep_going) :
  427. self.logger.info("Building " + component + " in " + builddir)
  428. self.logger.info("Build: make " + str(make_flags[comp_key][build]))
  429. buildrc = self.make(component, srcdir, builddir,
  430. make_flags[comp_key][build],
  431. make_env[comp_key][build])
  432. if (buildrc == None) :
  433. self.logger.info("[None] Failed to build " + component + " in " + installdir)
  434. if (buildrc == 0 or self.options.keep_going) :
  435. self.logger.info("Testing " + component + " in " + builddir)
  436. self.logger.info("Test: make "
  437. + str(make_check_flags[comp_key][build]))
  438. testrc = self.make(component, srcdir, builddir,
  439. make_check_flags[comp_key][build],
  440. make_check_env[comp_key][build])
  441. if (testrc == None) :
  442. self.logger.info("[None] Failed to test " + component + " in " + installdir)
  443. if ((testrc == 0 or self.options.keep_going)
  444. and not self.options.no_install):
  445. self.logger.info("Installing " + component + " in " + installdir)
  446. self.make(component, srcdir, builddir,
  447. make_install_flags[comp_key][build],
  448. make_install_env[comp_key][build])
  449. else :
  450. self.logger.info("Failed testing " + component + " in " + installdir)
  451. else :
  452. self.logger.info("Failed to build " + component + " in " + installdir)
  453. else :
  454. self.logger.info("Failed to configure " + component + " in " + installdir)
  455. def configure(self, component, srcdir, builddir, flags, env):
  456. prefix = self.component_abbrev[component.replace("-", "_")]
  457. self.logger.debug("Configure " + str(flags) + " " + str(srcdir) + " -> "
  458. + str(builddir))
  459. configure_files = dict(
  460. llvm=[(srcdir + "/configure", builddir + "/Makefile")],
  461. dragonegg=[(None,None)])
  462. doconfig = False
  463. for conf, mf in configure_files[component.replace("-", "_")]:
  464. if conf is None:
  465. # No configure necessary
  466. return 0
  467. if not os.path.exists(conf):
  468. self.logger.info("[" + prefix + "] Configure failed, no configure script " + conf)
  469. return -1
  470. if os.path.exists(conf) and os.path.exists(mf):
  471. confstat = os.stat(conf)
  472. makestat = os.stat(mf)
  473. if confstat.st_mtime > makestat.st_mtime:
  474. doconfig = True
  475. break
  476. else:
  477. doconfig = True
  478. break
  479. if not doconfig and not self.options.force_configure:
  480. return 0
  481. program = srcdir + "/configure"
  482. if not is_executable(program):
  483. self.logger.info("[" + prefix + "] Configure failed, cannot execute " + program)
  484. return -1
  485. args = [program]
  486. args += ["--verbose"]
  487. args += flags
  488. return self.execute(args, builddir, env, component)
  489. def make(self, component, srcdir, builddir, flags, env):
  490. program = find_executable("make")
  491. if program is None:
  492. raise ExecutableNotFound
  493. if not is_executable(program):
  494. raise FileNotExecutable
  495. args = [program]
  496. args += flags
  497. return self.execute(args, builddir, env, component)
  498. # Global constants
  499. build_abbrev = dict(debug="dbg", release="opt", paranoid="par")
  500. components = ["llvm", "dragonegg"]
  501. # Parse options
  502. parser = optparse.OptionParser(version="%prog 1.0")
  503. add_options(parser)
  504. (options, args) = parser.parse_args()
  505. check_options(parser, options, build_abbrev.keys());
  506. if options.verbose:
  507. logging.basicConfig(level=logging.DEBUG,
  508. format='%(name)-13s: %(message)s')
  509. else:
  510. logging.basicConfig(level=logging.INFO,
  511. format='%(name)-13s: %(message)s')
  512. source_abbrev = get_path_abbrevs(set(options.src))
  513. work_queue = queue.Queue()
  514. jobs = options.jobs // options.threads
  515. if jobs == 0:
  516. jobs = 1
  517. numthreads = options.threads
  518. logging.getLogger().info("Building with " + str(options.jobs) + " jobs and "
  519. + str(numthreads) + " threads using " + str(jobs)
  520. + " make jobs")
  521. logging.getLogger().info("CC = " + str(options.cc))
  522. logging.getLogger().info("CXX = " + str(options.cxx))
  523. for t in range(numthreads):
  524. builder = Builder(work_queue, jobs,
  525. build_abbrev, source_abbrev,
  526. options)
  527. builder.daemon = True
  528. builder.start()
  529. for build in set(options.build):
  530. for source in set(options.src):
  531. work_queue.put((source, build))
  532. work_queue.join()