blob: 4b05ad3a7caaa38b1936da0d5976baf2047f5c1d [file] [log] [blame]
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tools for annotation test scripts."""
from __future__ import print_function
import json
import os
import re
import subprocess
import sys
script_dir = os.path.dirname(os.path.realpath(__file__))
tool_dir = os.path.abspath(os.path.join(script_dir, '../../clang/pylib'))
sys.path.insert(0, tool_dir)
from clang import compile_db # type: ignore
# Valid return values for GetCurrentPlatform().
SUPPORTED_PLATFORMS = ['android', 'linux', 'windows']
class NetworkTrafficAnnotationTools():
def __init__(self, build_path=None):
"""Initializes a NetworkTrafficAnnotationTools object.
Args:
build_path: str Absolute or relative path to a fully compiled build
directory. If not specified, the script tries to find it based on
relative position of this file (src/tools/traffic_annotation).
"""
self.this_dir = os.path.dirname(os.path.abspath(__file__))
if not build_path:
build_path = self._FindPossibleBuildPath()
if build_path:
self.build_path = os.path.abspath(build_path)
self.auditor_path = None
self.python_auditor_path = None
# For each platform, map the returned platform name from python sys, to
# directory name of traffic_annotation_auditor executable.
host_platform = {
'linux': 'linux64',
'linux2': 'linux64',
'darwin': 'mac',
'win32': 'win32',
}[sys.platform]
path = os.path.join(self.this_dir, '..', 'bin', host_platform,
'traffic_annotation_auditor')
if sys.platform == 'win32':
path += '.exe'
if os.path.exists(path):
self.auditor_path = path
# Of course, the Python script doesn't need fancy logic per platform.
python_auditor_path = os.path.join(self.this_dir, "auditor/auditor.py")
if os.path.exists(python_auditor_path):
self.python_auditor_path = python_auditor_path
def _FindPossibleBuildPath(self):
"""Returns the first folder in //out that looks like a build dir."""
# Assuming this file is in 'tools/traffic_annotation/scripts', three
# directories deeper is 'src' and hopefully there is an 'out' in it.
out = os.path.abspath(os.path.join(self.this_dir, '..', '..', '..', 'out'))
if os.path.exists(out):
for folder in os.listdir(out):
candidate = os.path.join(out, folder)
if (os.path.isdir(candidate) and
self._CheckIfDirectorySeemsAsBuild(candidate)):
return candidate
return None
def _CheckIfDirectorySeemsAsBuild(self, path):
"""Checks to see if a directory seems to be a compiled build directory by
searching for 'gen' folder and 'build.ninja' file in it.
"""
return all(os.path.exists(
os.path.join(path, item)) for item in ('gen', 'build.ninja'))
def GetCompDBFiles(self, generate_compdb):
"""Gets the list of files.
Args:
generate_compdb: if true, generate a new compdb and write it to
compile_commands.json.
Returns:
A set of absolute filepaths, with all compile-able C++ files (based on the
compilation database).
"""
if generate_compdb:
compile_commands = compile_db.GenerateWithNinja(self.build_path)
compdb_path = os.path.join(self.build_path, 'compile_commands.json')
with open(compdb_path, 'w') as f:
f.write(json.dumps(compile_commands, indent=2))
compdb = compile_db.Read(self.build_path)
return set(
os.path.abspath(os.path.join(self.build_path, e['file']))
for e in compdb)
def GetModifiedFiles(self):
"""Gets the list of modified files from git. Returns None if any error
happens.
Returns:
list of str List of modified files. Returns None on errors.
"""
# List of files is extracted almost the same way as the following test
# recipe: https://cs.chromium.org/chromium/tools/depot_tools/recipes/
# recipe_modules/tryserver/api.py
# '--no-renames' switch is added so that if a file is renamed, both old and
# new name would be given. Old name is needed to discard its data in
# annotations.xml and new name is needed for updating the XML and checking
# its content for possible changes.
args = ["git.bat"] if sys.platform == "win32" else ["git"]
args += ["diff", "--cached", "--name-only", "--no-renames"]
original_path = os.getcwd()
# Change directory to src (two levels upper than build path).
os.chdir(os.path.join(self.build_path, "..", ".."))
command = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8")
stdout_text, stderr_text = command.communicate()
if stderr_text:
print("Could not run '%s' to get the list of changed files "
"because: %s" % (" ".join(args), stderr_text))
os.chdir(original_path)
return None
os.chdir(original_path)
return stdout_text.splitlines()
def CanRunAuditor(self, use_python_auditor=False):
"""Returns true if all required paths to run auditor are known."""
if use_python_auditor:
return self.build_path and self.python_auditor_path
else:
return self.build_path and self.auditor_path
def RunAuditor(self, args, use_python_auditor=False):
"""Runs traffic annotation auditor and returns the results.
Args:
args: list of str Arguments to be passed to traffic annotation auditor.
use_python_auditor: If True, use auditor.py instead of
traffic_annotation_auditor.exe.
Returns:
stdout_text: str Auditor's runtime outputs.
stderr_text: str Auditor's returned errors.
return_code: int Auditor's exit code.
"""
if use_python_auditor:
command_line = [
"vpython3", self.python_auditor_path,
"--build-path=" + self.build_path
] + args
else:
command_line = [self.auditor_path, "--build-path=" + self.build_path
] + args
command = subprocess.Popen(command_line,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8")
stdout_text, stderr_text = command.communicate()
return_code = command.returncode
return stdout_text, stderr_text, return_code