1#!/usr/bin/env python
2# Copyright 2019 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""A script for fetching LLVM monorepo and building clang-tools-extra binaries.
6
7Example: build clangd and clangd-indexer
8
9   tools/clang/scripts/build_clang_tools_extra.py --fetch out/Release clangd \
10       clangd-indexer
11"""
12
13from __future__ import print_function
14
15import argparse
16import errno
17import os
18import subprocess
19import sys
20import update
21
22
23def GetCheckoutDir(out_dir):
24  """Returns absolute path to the checked-out llvm repo."""
25  return os.path.join(out_dir, 'tools', 'clang', 'third_party', 'llvm')
26
27
28def GetBuildDir(out_dir):
29  return os.path.join(GetCheckoutDir(out_dir), 'build')
30
31
32def CreateDirIfNotExists(dir):
33  if not os.path.exists(dir):
34    os.makedirs(dir)
35
36
37def FetchLLVM(checkout_dir, revision):
38  """Clone llvm repo into |out_dir| or update if it already exists."""
39  CreateDirIfNotExists(os.path.dirname(checkout_dir))
40
41  try:
42    # First, try to clone the repo.
43    args = [
44        'git',
45        'clone',
46        'https://github.com/llvm/llvm-project.git',
47        checkout_dir,
48    ]
49    subprocess.check_call(args, shell=sys.platform == 'win32')
50  except subprocess.CalledProcessError:
51    # Otherwise, try to update it.
52    print('-- Attempting to update existing repo')
53    args = ['git', 'pull', '--rebase', 'origin', 'main']
54    subprocess.check_call(args, cwd=checkout_dir, shell=sys.platform == 'win32')
55  if revision:
56    args = ['git', 'checkout', revision]
57    subprocess.check_call(args, cwd=checkout_dir, shell=sys.platform == 'win32')
58
59
60def BuildTargets(build_dir, targets):
61  """Build targets from llvm repo at |build_dir|."""
62  CreateDirIfNotExists(build_dir)
63
64  # From that dir, run cmake
65  cmake_args = [
66      'cmake',
67      '-GNinja',
68      '-DLLVM_ENABLE_PROJECTS=clang;clang-tools-extra',
69      '-DCMAKE_BUILD_TYPE=Release',
70      '-DLLVM_ENABLE_ASSERTIONS=On',
71      '../llvm',
72  ]
73  subprocess.check_call(cmake_args, cwd=build_dir)
74
75  ninja_commands = ['ninja'] + targets
76  subprocess.check_call(ninja_commands, cwd=build_dir)
77
78
79def main():
80  parser = argparse.ArgumentParser(description='Build clang_tools_extra.')
81  parser.add_argument('--fetch', action='store_true', help='fetch LLVM source')
82  parser.add_argument(
83      '--revision', help='LLVM revision to use', default=update.CLANG_REVISION)
84  parser.add_argument('OUT_DIR', help='where we put the LLVM source repository')
85  parser.add_argument('TARGETS', nargs='+', help='targets being built')
86  args = parser.parse_args()
87
88  if args.fetch:
89    print('Fetching LLVM source')
90    FetchLLVM(GetCheckoutDir(args.OUT_DIR), args.revision)
91
92  print('Building targets: %s' % ', '.join(args.TARGETS))
93  BuildTargets(GetBuildDir(args.OUT_DIR), args.TARGETS)
94
95
96if __name__ == '__main__':
97  sys.exit(main())
98