blob: 1ad59bfd49aaca48ea2f83943bfe504fcaeaf9f1 [file] [log] [blame]
Yves Gerey546ee612019-02-26 17:04:16 +01001# 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