mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
219 lines
5.3 KiB
Python
Executable File
219 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright 2019 Google
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Lints source files for conformance with the style guide that applies.
|
|
|
|
Currently supports linting Objective-C, Objective-C++, C++, and Python source.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
|
|
from lib import checker
|
|
from lib import command_trace
|
|
from lib import git
|
|
from lib import source
|
|
|
|
_logger = logging.getLogger('lint')
|
|
|
|
|
|
_dry_run = False
|
|
|
|
|
|
_CPPLINT_OBJC_FILTERS = [
|
|
# Objective-C uses #import and does not use header guards
|
|
'-build/header_guard',
|
|
|
|
# Inline definitions of Objective-C blocks confuse
|
|
'-readability/braces',
|
|
|
|
# C-style casts are acceptable in Objective-C++
|
|
'-readability/casting',
|
|
|
|
# Objective-C needs use type 'long' for interop between types like NSInteger
|
|
# and printf-style functions.
|
|
'-runtime/int',
|
|
|
|
# cpplint is generally confused by Objective-C mixing with C++.
|
|
# * Objective-C method invocations in a for loop make it think its a
|
|
# range-for
|
|
# * Objective-C dictionary literals confuse brace spacing
|
|
# * Empty category declarations ("@interface Foo ()") look like function
|
|
# invocations
|
|
'-whitespace',
|
|
]
|
|
|
|
_CPPLINT_OBJC_OPTIONS = [
|
|
# cpplint normally excludes Objective-C++
|
|
'--extensions=h,m,mm',
|
|
|
|
# Objective-C style allows longer lines
|
|
'--linelength=100',
|
|
|
|
'--filter=' + ','.join(_CPPLINT_OBJC_FILTERS),
|
|
]
|
|
|
|
|
|
def main():
|
|
global _dry_run
|
|
|
|
parser = argparse.ArgumentParser(description='Lint source files.')
|
|
parser.add_argument('--dry-run', '-n', action='store_true',
|
|
help='Show what the linter would do without doing it')
|
|
parser.add_argument('--all', action='store_true',
|
|
help='run the linter over all known sources')
|
|
parser.add_argument('rev_or_files', nargs='*',
|
|
help='A single revision that specifies a point in time '
|
|
'from which to look for changes. Defaults to '
|
|
'origin/master. Alternatively, a list of specific '
|
|
'files or git pathspecs to lint.')
|
|
args = command_trace.parse_args(parser)
|
|
|
|
if args.dry_run:
|
|
_dry_run = True
|
|
command_trace.enable_tracing()
|
|
|
|
pool = checker.Pool()
|
|
|
|
sources = _unique(source.CC_DIRS + source.OBJC_DIRS + source.PYTHON_DIRS)
|
|
patterns = git.make_patterns(sources)
|
|
|
|
files = git.find_changed_or_files(args.all, args.rev_or_files, patterns)
|
|
check(pool, files)
|
|
|
|
pool.exit()
|
|
|
|
|
|
def check(pool, files):
|
|
group = source.categorize_files(files)
|
|
|
|
for kind, files in group.kinds.items():
|
|
for chunk in checker.shard(files):
|
|
if not chunk:
|
|
continue
|
|
|
|
linter = _linters[kind]
|
|
pool.submit(linter, chunk)
|
|
|
|
|
|
def lint_cc(files):
|
|
return _run_cpplint([], files)
|
|
|
|
|
|
def lint_objc(files):
|
|
return _run_cpplint(_CPPLINT_OBJC_OPTIONS, files)
|
|
|
|
|
|
def _run_cpplint(options, files):
|
|
scripts_dir = os.path.dirname(os.path.abspath(__file__))
|
|
cpplint = os.path.join(scripts_dir, 'cpplint.py')
|
|
|
|
command = [sys.executable, cpplint, '--quiet']
|
|
command.extend(options)
|
|
command.extend(files)
|
|
|
|
return _read_output(command)
|
|
|
|
|
|
_flake8_warned = False
|
|
|
|
|
|
def lint_py(files):
|
|
flake8 = which('flake8')
|
|
if flake8 is None:
|
|
global _flake8_warned
|
|
if not _flake8_warned:
|
|
_flake8_warned = True
|
|
_logger.warn(textwrap.dedent(
|
|
"""
|
|
Could not find flake8 on $PATH; skipping python lint.
|
|
Install with:
|
|
|
|
pip install --user flake8
|
|
"""))
|
|
return
|
|
|
|
command = [flake8]
|
|
command.extend(files)
|
|
|
|
return _read_output(command)
|
|
|
|
|
|
def _read_output(command):
|
|
command_trace.log(command)
|
|
|
|
if _dry_run:
|
|
return checker.Result(0, '')
|
|
|
|
proc = subprocess.Popen(
|
|
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
output = proc.communicate('')[0]
|
|
sc = proc.wait()
|
|
|
|
return checker.Result(sc, output)
|
|
|
|
|
|
_linters = {
|
|
'cc': lint_cc,
|
|
'objc': lint_objc,
|
|
'py': lint_py,
|
|
}
|
|
|
|
|
|
def _unique(items):
|
|
return list(set(items))
|
|
|
|
|
|
def which(executable):
|
|
"""Finds the executable with the given name.
|
|
|
|
Returns:
|
|
The fully qualified path to the executable or None if the executable isn't
|
|
found.
|
|
"""
|
|
if executable.startswith('/'):
|
|
return executable
|
|
|
|
path = os.environ['PATH'].split(os.pathsep)
|
|
|
|
for executable_with_ext in _executable_names(executable):
|
|
for entry in path:
|
|
joined = os.path.join(entry, executable_with_ext)
|
|
if os.path.isfile(joined) and os.access(joined, os.X_OK):
|
|
return joined
|
|
|
|
return None
|
|
|
|
|
|
def _executable_names(executable):
|
|
"""Yields a sequence of all possible executable names."""
|
|
|
|
if os.name == 'nt':
|
|
pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
|
|
for ext in pathext:
|
|
yield executable + ext
|
|
|
|
else:
|
|
yield executable
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|