mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
* Fix field initialization order * Reduce dependencies in objc_compatibility_apple_test This makes it possible to build and test util without having to build everything * Add a lint check for CMake files * Find XCTest, if available * Fix CMakeLists.txt to include all sources * Fix test flake in local store tests This doesn't affect production because we always set the additional references pointer there, but it's technically optional and some tests don't do this.
306 lines
8.5 KiB
Python
Executable File
306 lines
8.5 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.
|
|
|
|
"""Checks that source files are mentioned in CMakeLists.txt.
|
|
|
|
Also checks that files mentioned in CMakeLists.txt exist on the filesystem.
|
|
|
|
Note that this check needs to be able to run before anything has been built, so
|
|
generated files must be excluded from the check. Add a "# NOLINT(generated)"
|
|
comment to any line mentioning such a file to ensure they don't falsely trigger
|
|
errors. This only needs to be done once within a file.
|
|
"""
|
|
|
|
import argparse
|
|
import collections
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
# Directories relative to the repo root that will be scanned by default if no
|
|
# arguments are passed.
|
|
_DEFAULT_DIRS = [
|
|
'Firestore/core',
|
|
'Firestore/Source'
|
|
]
|
|
|
|
# When scanning the filesystem, look for specific files or files with these
|
|
# extensions.
|
|
_INCLUDE_FILES = {'CMakeLists.txt'}
|
|
_INCLUDE_EXTENSIONS = {'.cc', '.h', '.mm'}
|
|
|
|
# When scanning the filesystem, exclude any files or directories with these
|
|
# names.
|
|
_EXCLUDE_DIRS = {'third_party', 'Pods', 'Protos'}
|
|
|
|
_verbose = False
|
|
|
|
|
|
def main(args):
|
|
global _verbose
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Check CMakeLists.txt file membership.')
|
|
parser.add_argument('--verbose', '-v', action='store_true',
|
|
help='Run verbosely')
|
|
parser.add_argument('filenames', nargs='*', metavar='file_or_dir',
|
|
help='Files and directories to scan')
|
|
args = parser.parse_args(args)
|
|
if args.verbose:
|
|
_verbose = True
|
|
|
|
scan_filenames = args.filenames
|
|
if not scan_filenames:
|
|
scan_filenames = default_args()
|
|
|
|
filenames = find_source_files(scan_filenames)
|
|
groups = group_by_cmakelists(filenames)
|
|
errors = find_all_errors(groups)
|
|
trace('checked %d files' % len(filenames))
|
|
sys.exit(1 if errors else 0)
|
|
|
|
|
|
def default_args():
|
|
"""Returns a default list of directories to scan.
|
|
"""
|
|
command = ['git', 'rev-parse', '--show-toplevel']
|
|
toplevel = subprocess.check_output(command).rstrip()
|
|
|
|
return [os.path.join(toplevel, dirname) for dirname in _DEFAULT_DIRS]
|
|
|
|
|
|
def find_source_files(roots):
|
|
"""Finds source files on the filesystem.
|
|
|
|
Args:
|
|
roots: A list of files or directories
|
|
|
|
Returns:
|
|
A list of filenames found in the roots, excluding those that are
|
|
uninteresting.
|
|
|
|
"""
|
|
result = []
|
|
|
|
for root in roots:
|
|
for parent, dirs, files in os.walk(root, topdown=True):
|
|
# Prune directories known to be uninteresting
|
|
dirs[:] = [d for d in dirs if d not in _EXCLUDE_DIRS]
|
|
|
|
for filename in files:
|
|
if filename in _INCLUDE_FILES or is_source_file(filename):
|
|
result.append(os.path.join(parent, filename))
|
|
|
|
return result
|
|
|
|
|
|
_filename_pattern = re.compile(r'\b([A-Za-z0-9_/+]+\.)+(?:c|cc|h|m|mm)\b')
|
|
_comment_pattern = re.compile(r'^(\s*)#')
|
|
_check_pattern = re.compile(r'^\s*check_[A-Za-z0-9_]+\(.*\)$')
|
|
_nolint_pattern = re.compile(r'NOLINT')
|
|
|
|
|
|
def read_listed_source_files(filename):
|
|
"""Reads the contents of the given filename and finds all the filenames it
|
|
finds in the file.
|
|
|
|
Args:
|
|
filename: A filename to read, typically some path to a CMakeLists.txt file.
|
|
|
|
Returns:
|
|
A pair of lists. The first list contains filenames mentioned in the file.
|
|
The second contains files that have been ignored (by marking them NOLINT)
|
|
in the file. Elements from the second list might also be present in the
|
|
first list.
|
|
"""
|
|
found = []
|
|
ignored = []
|
|
parent = os.path.dirname(filename)
|
|
|
|
with open(filename, 'r') as fd:
|
|
for line in fd.readlines():
|
|
# Simple hack to exclude files mentioned in CMake checks
|
|
if _check_pattern.match(line):
|
|
continue
|
|
|
|
ignore = bool(_nolint_pattern.search(line))
|
|
|
|
if not ignore:
|
|
# Exclude comments, but only on regular lines. This allows files to
|
|
# include files in comments that mark them NOLINT.
|
|
m = _comment_pattern.match(line)
|
|
if m:
|
|
line = m.group(1)
|
|
|
|
for m in _filename_pattern.finditer(line):
|
|
listed_filename = os.path.join(parent, m.group(0))
|
|
if ignore:
|
|
trace('ignoring %s' % listed_filename)
|
|
ignored.append(listed_filename)
|
|
else:
|
|
found.append(listed_filename)
|
|
|
|
found.sort()
|
|
ignored.sort()
|
|
return found, ignored
|
|
|
|
|
|
class Group(object):
|
|
"""A comparison group.
|
|
|
|
Groups include the location of a CMakeLists.txt file along with files that
|
|
were found on the filesystem that should be mentioned in the file, files that
|
|
were found in the CMakeLists.txt file, and any files that should be ignored.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.list_file = None
|
|
self.fs_files = []
|
|
self.list_files = []
|
|
self.ignored_files = []
|
|
|
|
def shorten(self):
|
|
"""Shorten filenames to make them relative to the directory containing the
|
|
CMakeLists.txt file and make the file lists into sets.
|
|
"""
|
|
prefix = os.path.dirname(self.list_file) + '/'
|
|
|
|
self.fs_files = self._remove_prefix(prefix, self.fs_files)
|
|
self.list_files = self._remove_prefix(prefix, self.list_files)
|
|
self.ignored_files = self._remove_prefix(prefix, self.ignored_files)
|
|
|
|
def _remove_prefix(self, prefix, filenames):
|
|
result = []
|
|
|
|
for filename in filenames:
|
|
if not filename.startswith(prefix):
|
|
raise Exception('Filename %s not in prefix %s' % (filename, prefix))
|
|
|
|
result.append(filename[len(prefix):])
|
|
|
|
return set(result)
|
|
|
|
def __repr__(self):
|
|
def files(items):
|
|
return repr(sorted(list(items)))
|
|
|
|
return """<Group %s
|
|
fs=%s
|
|
listed=%s
|
|
ignored=%s>""" % (self.list_file,
|
|
files(self.fs_files),
|
|
files(self.list_files),
|
|
files(self.ignored_files))
|
|
|
|
|
|
def group_by_cmakelists(filenames):
|
|
"""Groups the given filenames by the nearest CMakeLists.txt
|
|
|
|
Args:
|
|
filenames: A list of filenames found on the filesystem.
|
|
|
|
Returns:
|
|
A list of filled-out Groups for evaluation.
|
|
"""
|
|
filename_set = set(filenames)
|
|
|
|
groups = collections.defaultdict(Group)
|
|
|
|
for filename in filenames:
|
|
if is_source_file(filename):
|
|
for cmake_list in possible_cmake_lists_files(filename):
|
|
if cmake_list in filename_set:
|
|
groups[cmake_list].fs_files.append(filename)
|
|
break
|
|
|
|
elif os.path.basename(filename) == 'CMakeLists.txt':
|
|
g = groups[filename]
|
|
g.list_file = filename
|
|
g.list_files, g.ignored_files = read_listed_source_files(filename)
|
|
|
|
return sorted(list(groups.values()))
|
|
|
|
|
|
def find_all_errors(groups):
|
|
"""Finds errors in the given groups.
|
|
|
|
Args:
|
|
groups: A list of groups. Each group is shortened.
|
|
|
|
Returns:
|
|
A count of errors encountered; errors information is printed for the user.
|
|
"""
|
|
errors = 0
|
|
for group in groups:
|
|
group.shorten()
|
|
errors += find_errors(group)
|
|
|
|
return errors
|
|
|
|
|
|
def find_errors(group):
|
|
"""Evaluates whether or not a group has any errors.
|
|
"""
|
|
in_both = group.fs_files.intersection(group.list_files)
|
|
in_both = in_both | group.ignored_files
|
|
|
|
just_fs = group.fs_files - in_both
|
|
just_list = group.list_files - in_both
|
|
|
|
if just_fs or just_list:
|
|
sys.stderr.write('%s had errors:\n' % group.list_file)
|
|
|
|
for filename in sorted(just_fs):
|
|
sys.stderr.write(' %s: missing from CMakeLists.txt\n' % filename)
|
|
|
|
for filename in sorted(just_list):
|
|
sys.stderr.write(' %s: missing from filesystem\n' % filename)
|
|
|
|
return len(just_fs) + len(just_list)
|
|
|
|
|
|
def is_source_file(filename):
|
|
ext = os.path.splitext(filename)[1]
|
|
return ext in _INCLUDE_EXTENSIONS
|
|
|
|
|
|
def possible_cmake_lists_files(filename):
|
|
"""Finds CMakeLists.txt files that might apply to the given filename.
|
|
|
|
Args:
|
|
filename: A source file
|
|
|
|
Yields:
|
|
A sequence of CMakeLists.txt filenames that might govern the source file,
|
|
starting in the directory containing the given filename and working up
|
|
toward the filesystem root. The filenames may point to files that don't
|
|
exist.
|
|
"""
|
|
while filename:
|
|
filename = os.path.dirname(filename)
|
|
yield os.path.join(filename, 'CMakeLists.txt')
|
|
|
|
|
|
def trace(line):
|
|
if _verbose:
|
|
sys.stderr.write('%s\n' % line)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:])
|