• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
2#
3# Use of this source code is governed by a BSD-style license
4# that can be found in the LICENSE file in the root of the source
5# tree. An additional intellectual property rights grant can be found
6# in the file PATENTS.  All contributing project authors may
7# be found in the AUTHORS file in the root of the source tree.
8
9"""This script helps to invoke gn and ninja
10which lie in depot_tools repository."""
11
12import json
13import os
14import re
15import shutil
16import subprocess
17import sys
18import tempfile
19
20
21def FindSrcDirPath():
22  """Returns the abs path to the src/ dir of the project."""
23  src_dir = os.path.dirname(os.path.abspath(__file__))
24  while os.path.basename(src_dir) != 'src':
25    src_dir = os.path.normpath(os.path.join(src_dir, os.pardir))
26  return src_dir
27
28
29SRC_DIR = FindSrcDirPath()
30sys.path.append(os.path.join(SRC_DIR, 'build'))
31import find_depot_tools
32
33
34def RunGnCommand(args, root_dir=None):
35  """Runs `gn` with provided args and return error if any."""
36  try:
37    command = [
38      sys.executable,
39      os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gn.py')
40    ] + args
41    subprocess.check_output(command, cwd=root_dir)
42  except subprocess.CalledProcessError as err:
43    return err.output
44  return None
45
46
47# GN_ERROR_RE matches the summary of an error output by `gn check`.
48# Matches "ERROR" and following lines until it sees an empty line or a line
49# containing just underscores.
50GN_ERROR_RE = re.compile(r'^ERROR .+(?:\n.*[^_\n].*$)+', re.MULTILINE)
51
52
53def RunGnCheck(root_dir=None):
54  """Runs `gn gen --check` with default args to detect mismatches between
55  #includes and dependencies in the BUILD.gn files, as well as general build
56  errors.
57
58  Returns a list of error summary strings.
59  """
60  out_dir = tempfile.mkdtemp('gn')
61  try:
62    error = RunGnCommand(['gen', '--check', out_dir], root_dir)
63  finally:
64    shutil.rmtree(out_dir, ignore_errors=True)
65  return GN_ERROR_RE.findall(error) if error else []
66
67
68def RunNinjaCommand(args, root_dir=None):
69  """Runs ninja quietly. Any failure (e.g. clang not found) is
70     silently discarded, since this is unlikely an error in submitted CL."""
71  command = [
72              os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'ninja')
73            ] + args
74  p = subprocess.Popen(command, cwd=root_dir,
75                       stdout=subprocess.PIPE, stderr=subprocess.PIPE)
76  out, _ = p.communicate()
77  return out
78
79
80def GetClangTidyPath():
81  """POC/WIP! Use the one we have, even it doesn't match clang's version."""
82  tidy = ('third_party/android_ndk/toolchains/'
83          'llvm/prebuilt/linux-x86_64/bin/clang-tidy')
84  return os.path.join(SRC_DIR, tidy)
85
86
87def GetCompilationDb(root_dir=None):
88  """Run ninja compdb tool to get proper flags, defines and include paths."""
89  # The compdb tool expect a rule.
90  commands = json.loads(RunNinjaCommand(['-t', 'compdb', 'cxx'], root_dir))
91  # Turns 'file' field into a key.
92  return {v['file']: v for v in commands}
93
94
95def GetCompilationCommand(filepath, gn_args, work_dir):
96  """Get the whole command used to compile one cc file.
97  Typically, clang++ with flags, defines and include paths.
98
99  Args:
100      filepath: path to .cc file.
101      gen_args: build configuration for gn.
102      work_dir: build dir.
103
104  Returns:
105    Command as a list, ready to be consumed by subprocess.Popen.
106  """
107  gn_errors = RunGnCommand(['gen'] + gn_args + [work_dir])
108  if gn_errors:
109    raise(RuntimeError(
110      'FYI, cannot complete check due to gn error:\n%s\n'
111      'Please open a bug.' % gn_errors))
112
113  # Needed for single file compilation.
114  commands = GetCompilationDb(work_dir)
115
116  # Path as referenced by ninja.
117  rel_path = os.path.relpath(os.path.abspath(filepath), work_dir)
118
119  # Gather defines, include path and flags (such as -std=c++11).
120  try:
121    compilation_entry = commands[rel_path]
122  except KeyError:
123    raise ValueError('%s: Not found in compilation database.\n'
124                     'Please check the path.' % filepath)
125  command = compilation_entry['command'].split()
126
127  # Remove troublesome flags. May trigger an error otherwise.
128  if '-MMD' in command:
129    command.remove('-MMD')
130  if '-MF' in command:
131    index = command.index('-MF')
132    del command[index:index+2]  # Remove filename as well.
133
134  return command
135