| # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
| # |
| # Use of this source code is governed by a BSD-style license |
| # that can be found in the LICENSE file in the root of the source |
| # tree. An additional intellectual property rights grant can be found |
| # in the file PATENTS. All contributing project authors may |
| # be found in the AUTHORS file in the root of the source tree. |
| """This script helps to invoke gn and ninja |
| which lie in depot_tools repository.""" |
| |
| import json |
| import os |
| import re |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
| |
| |
| def FindSrcDirPath(): |
| """Returns the abs path to the src/ dir of the project.""" |
| src_dir = os.path.dirname(os.path.abspath(__file__)) |
| while os.path.basename(src_dir) != 'src': |
| src_dir = os.path.normpath(os.path.join(src_dir, os.pardir)) |
| return src_dir |
| |
| |
| SRC_DIR = FindSrcDirPath() |
| sys.path.append(os.path.join(SRC_DIR, 'build')) |
| import find_depot_tools |
| |
| |
| def RunGnCommand(args, root_dir=None): |
| """Runs `gn` with provided args and return error if any.""" |
| try: |
| command = [ |
| sys.executable, |
| os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gn.py') |
| ] + args |
| subprocess.check_output(command, cwd=root_dir) |
| except subprocess.CalledProcessError as err: |
| return err.output |
| return None |
| |
| |
| # GN_ERROR_RE matches the summary of an error output by `gn check`. |
| # Matches "ERROR" and following lines until it sees an empty line or a line |
| # containing just underscores. |
| GN_ERROR_RE = re.compile(r'^ERROR .+(?:\n.*[^_\n].*$)+', re.MULTILINE) |
| |
| |
| def RunGnCheck(root_dir=None): |
| """Runs `gn gen --check` with default args to detect mismatches between |
| #includes and dependencies in the BUILD.gn files, as well as general build |
| errors. |
| |
| Returns a list of error summary strings. |
| """ |
| out_dir = tempfile.mkdtemp('gn') |
| try: |
| error = RunGnCommand(['gen', '--check', out_dir], root_dir) |
| finally: |
| shutil.rmtree(out_dir, ignore_errors=True) |
| return GN_ERROR_RE.findall(error) if error else [] |
| |
| |
| def RunNinjaCommand(args, root_dir=None): |
| """Runs ninja quietly. Any failure (e.g. clang not found) is |
| silently discarded, since this is unlikely an error in submitted CL.""" |
| command = [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'ninja')] + args |
| p = subprocess.Popen(command, |
| cwd=root_dir, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| out, _ = p.communicate() |
| return out |
| |
| |
| def GetClangTidyPath(): |
| """POC/WIP! Use the one we have, even it doesn't match clang's version.""" |
| tidy = ('third_party/android_ndk/toolchains/' |
| 'llvm/prebuilt/linux-x86_64/bin/clang-tidy') |
| return os.path.join(SRC_DIR, tidy) |
| |
| |
| def GetCompilationDb(root_dir=None): |
| """Run ninja compdb tool to get proper flags, defines and include paths.""" |
| # The compdb tool expect a rule. |
| commands = json.loads(RunNinjaCommand(['-t', 'compdb', 'cxx'], root_dir)) |
| # Turns 'file' field into a key. |
| return {v['file']: v for v in commands} |
| |
| |
| def GetCompilationCommand(filepath, gn_args, work_dir): |
| """Get the whole command used to compile one cc file. |
| Typically, clang++ with flags, defines and include paths. |
| |
| Args: |
| filepath: path to .cc file. |
| gen_args: build configuration for gn. |
| work_dir: build dir. |
| |
| Returns: |
| Command as a list, ready to be consumed by subprocess.Popen. |
| """ |
| gn_errors = RunGnCommand(['gen'] + gn_args + [work_dir]) |
| if gn_errors: |
| raise (RuntimeError('FYI, cannot complete check due to gn error:\n%s\n' |
| 'Please open a bug.' % gn_errors)) |
| |
| # Needed for single file compilation. |
| commands = GetCompilationDb(work_dir) |
| |
| # Path as referenced by ninja. |
| rel_path = os.path.relpath(os.path.abspath(filepath), work_dir) |
| |
| # Gather defines, include path and flags (such as -std=c++11). |
| try: |
| compilation_entry = commands[rel_path] |
| except KeyError: |
| raise ValueError('%s: Not found in compilation database.\n' |
| 'Please check the path.' % filepath) |
| command = compilation_entry['command'].split() |
| |
| # Remove troublesome flags. May trigger an error otherwise. |
| if '-MMD' in command: |
| command.remove('-MMD') |
| if '-MF' in command: |
| index = command.index('-MF') |
| del command[index:index + 2] # Remove filename as well. |
| |
| return command |