| #!/usr/bin/env python3 |
| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from __future__ import print_function |
| |
| import json |
| import os |
| import re |
| import sys |
| import subprocess |
| import shutil |
| |
| _RSP_RE = re.compile(r' (@(.+?\.rsp)) ') |
| _CMD_LINE_RE = re.compile( |
| r'^(?P<gomacc>.*gomacc(\.exe)?"?\s+)?(?P<clang>\S*clang\S*)\s+(?P<args>.*)$' |
| ) |
| _debugging = False |
| |
| |
| def _IsTargettingWindows(target_os): |
| if target_os is not None: |
| # Available choices are based on: gn help target_os |
| assert target_os in [ |
| 'android', |
| 'chromeos', |
| 'fuchsia', |
| 'ios', |
| 'linux', |
| 'mac', |
| 'nacl', |
| 'win', |
| ] |
| return target_os == 'win' |
| return sys.platform == 'win32' |
| |
| |
| def _ProcessCommand(command, target_os): |
| """Removes gomacc(.exe). On Windows inserts --driver-mode=cl as the first arg. |
| |
| Note that we deliberately don't use shlex.split here, because it doesn't work |
| predictably for Windows commands (specifically, it doesn't parse args the same |
| way that Clang does on Windows). |
| |
| Instead, we just use a regex, with the simplifying assumption that the path to |
| clang-cl.exe contains no spaces. |
| """ |
| # If the driver mode is not already set then define it. Driver mode is |
| # automatically included in the compile db by clang starting with release |
| # 9.0.0. |
| driver_mode = '' |
| # Only specify for Windows. Other platforms do fine without it. |
| if _IsTargettingWindows(target_os) and '--driver-mode' not in command: |
| driver_mode = '--driver-mode=cl' |
| |
| match = _CMD_LINE_RE.search(command) |
| if match: |
| match_dict = match.groupdict() |
| command = ' '.join([match_dict['clang'], driver_mode, match_dict['args']]) |
| elif _debugging: |
| print('Compile command didn\'t match expected regex!') |
| print('Command:', command) |
| print('Regex:', _CMD_LINE_RE.pattern) |
| |
| # Remove some blocklisted arguments. These are VisualStudio specific arguments |
| # not recognized or used by clangd. They only suppress or activate graphical |
| # output anyway. |
| blocklisted_arguments = ['/nologo', '/showIncludes'] |
| command_parts = filter(lambda arg: arg not in blocklisted_arguments, |
| command.split()) |
| |
| return " ".join(command_parts) |
| |
| |
| def _ProcessEntry(entry, target_os): |
| """Transforms one entry in a Windows compile db to be clang-tool friendly.""" |
| entry['command'] = _ProcessCommand(entry['command'], target_os) |
| |
| # Expand the contents of the response file, if any. |
| # http://llvm.org/bugs/show_bug.cgi?id=21634 |
| try: |
| match = _RSP_RE.search(entry['command']) |
| if match: |
| rsp_path = os.path.join(entry['directory'], match.group(2)) |
| rsp_contents = open(rsp_path).read() |
| entry['command'] = ''.join([ |
| entry['command'][:match.start(1)], rsp_contents, |
| entry['command'][match.end(1):] |
| ]) |
| except IOError: |
| if _debugging: |
| print('Couldn\'t read response file for %s' % entry['file']) |
| |
| return entry |
| |
| |
| def ProcessCompileDatabaseIfNeeded(compile_db, target_os=None): |
| """Make the compile db generated by ninja on Windows more clang-tool friendly. |
| |
| Args: |
| compile_db: The compile database parsed as a Python dictionary. |
| |
| Returns: |
| A postprocessed compile db that clang tooling can use. |
| """ |
| compile_db = [_ProcessEntry(e, target_os) for e in compile_db] |
| |
| if not _IsTargettingWindows(target_os): |
| return compile_db |
| |
| if _debugging: |
| print('Read in %d entries from the compile db' % len(compile_db)) |
| original_length = len(compile_db) |
| |
| # Filter out NaCl stuff. The clang tooling chokes on them. |
| # TODO(dcheng): This doesn't appear to do anything anymore, remove? |
| compile_db = [ |
| e for e in compile_db if '_nacl.cc.pdb' not in e['command'] |
| and '_nacl_win64.cc.pdb' not in e['command'] |
| ] |
| if _debugging: |
| print('Filtered out %d entries...' % (original_length - len(compile_db))) |
| |
| # TODO(dcheng): Also filter out multiple commands for the same file. Not sure |
| # how that happens, but apparently it's an issue on Windows. |
| return compile_db |
| |
| |
| def GetNinjaPath(): |
| ninja_executable = 'ninja.exe' if sys.platform == 'win32' else 'ninja' |
| return os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', |
| '..', '..', 'third_party', 'depot_tools', |
| ninja_executable) |
| |
| |
| # FIXME: This really should be a build target, rather than generated at runtime. |
| def GenerateWithNinja(path, targets=[]): |
| """Generates a compile database using ninja. |
| |
| Args: |
| path: The build directory to generate a compile database for. |
| targets: Additional targets to pass to ninja. |
| |
| Returns: |
| List of the contents of the compile database. |
| """ |
| # TODO(dcheng): Ensure that clang is enabled somehow. |
| |
| # First, generate the compile database. |
| ninja_path = GetNinjaPath() |
| if not os.path.exists(ninja_path): |
| ninja_path = shutil.which("ninja") |
| json_compile_db = subprocess.check_output( |
| [ninja_path, '-C', path] + targets + |
| ['-t', 'compdb', 'cc', 'cxx', 'objc', 'objcxx']) |
| return json.loads(json_compile_db) |
| |
| |
| def Read(path): |
| """Reads a compile database into memory. |
| |
| Args: |
| path: Directory that contains the compile database. |
| """ |
| with open(os.path.join(path, 'compile_commands.json'), 'rb') as db: |
| return json.load(db) |