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