Skip to content

Commit

Permalink
Enforce that files in the src directory are referenced from BUILD.gn (
Browse files Browse the repository at this point in the history
#31960)

* Start creating a script

* Have much more functionality

* Restyle

* Add some doc comments ... this starts being usable

* Add workflow to validate that gn knows about files

* Remove controller from known exceptions: we fixed that one

* Fix flake8

* Add more known failures

* Better error reporting for gn reachability

* Remove the platform specific orphan file listing

* Force the "not failures anymore" to be fatal

---------

Co-authored-by: Andrei Litvin <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Feb 16, 2024
1 parent 2805a99 commit 1661124
Show file tree
Hide file tree
Showing 2 changed files with 342 additions and 0 deletions.
160 changes: 160 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,166 @@ jobs:
with:
platform: linux

- name: Check for orphaned gn files
if: always()
# We should enforce that ALL new files are referenced in our build scripts.
# Several things do not have a clear fix path:
# - various platform implementations (including darwin-specific files as they
# are not using GN)
# - app/clusters (they are fetched dynamically - this should probably be fixed)
#
# All the rest of the exceptions should be driven down to 0: chip should fully
# be defined in build rules.
#
# This check enforces that for any newly added file, it must be part of some
# BUILD.gn file
run: |
./scripts/run_in_build_env.sh "./scripts/tools/not_known_to_gn.py \
src \
--skip-dir app/clusters \
--skip-dir darwin \
--skip-dir include \
--skip-dir platform/Ameba \
--skip-dir platform/android \
--skip-dir platform/ASR \
--skip-dir platform/Beken \
--skip-dir platform/bouffalolab \
--skip-dir platform/cc13xx_26xx \
--skip-dir platform/cc32xx \
--skip-dir platform/Darwin \
--skip-dir platform/ESP32 \
--skip-dir platform/fake \
--skip-dir platform/FreeRTOS \
--skip-dir platform/Infineon \
--skip-dir platform/Linux \
--skip-dir platform/mbed \
--skip-dir platform/mt793x \
--skip-dir platform/nxp \
--skip-dir platform/OpenThread \
--skip-dir platform/qpg \
--skip-dir platform/silabs \
--skip-dir platform/telink \
--skip-dir platform/webos \
--skip-dir platform/Zephyr \
--skip-dir test_driver \
--known-failure app/app-platform/ContentApp.cpp \
--known-failure app/app-platform/ContentApp.h \
--known-failure app/app-platform/ContentAppPlatform.cpp \
--known-failure app/app-platform/ContentAppPlatform.h \
--known-failure controller/ExamplePersistentStorage.cpp \
--known-failure controller/ExamplePersistentStorage.h \
--known-failure controller/java/GroupDeviceProxy.h \
--known-failure controller/java/CHIPEventTLVValueDecoder.h \
--known-failure controller/python/chip/credentials/cert.h \
--known-failure controller/python/chip/server/Options.h \
--known-failure controller/python/chip/crypto/p256keypair.h \
--known-failure controller/python/chip/commissioning/PlaceholderOperationalCredentialsIssuer.h \
--known-failure controller/python/chip/native/PyChipError.h \
--known-failure app/AttributeAccessInterface.h \
--known-failure app/AttributeAccessToken.h \
--known-failure app/att-storage.h \
--known-failure app/BufferedReadCallback.h \
--known-failure app/CommandHandler.h \
--known-failure app/CommandHandlerInterface.h \
--known-failure app/CommandPathParams.h \
--known-failure app/CommandPathRegistry.h \
--known-failure app/CommandResponseSender.h \
--known-failure app/CommandSender.h \
--known-failure app/CommandSenderLegacyCallback.h \
--known-failure app/CompatEnumNames.h \
--known-failure app/ConcreteAttributePath.h \
--known-failure app/ConcreteCommandPath.h \
--known-failure app/data-model/ListLargeSystemExtensions.h \
--known-failure app/EventHeader.h \
--known-failure app/EventLoggingDelegate.h \
--known-failure app/EventLogging.h \
--known-failure app/EventLoggingTypes.h \
--known-failure app/EventManagement.h \
--known-failure app/InteractionModelHelper.h \
--known-failure app/MessageDef/ArrayBuilder.h \
--known-failure app/MessageDef/ArrayParser.h \
--known-failure app/MessageDef/CommandDataIB.h \
--known-failure app/MessageDef/CommandPathIB.h \
--known-failure app/MessageDef/CommandStatusIB.h \
--known-failure app/MessageDef/EventFilterIB.h \
--known-failure app/MessageDef/EventFilterIBs.h \
--known-failure app/MessageDef/InvokeRequestMessage.h \
--known-failure app/MessageDef/InvokeRequests.h \
--known-failure app/MessageDef/InvokeResponseIB.h \
--known-failure app/MessageDef/InvokeResponseIBs.h \
--known-failure app/MessageDef/InvokeResponseMessage.h \
--known-failure app/MessageDef/ListBuilder.h \
--known-failure app/MessageDef/ListParser.h \
--known-failure app/MessageDef/StatusResponseMessage.h \
--known-failure app/MessageDef/StructBuilder.h \
--known-failure app/MessageDef/StructParser.h \
--known-failure app/MessageDef/SubscribeRequestMessage.h \
--known-failure app/MessageDef/SubscribeResponseMessage.h \
--known-failure app/MessageDef/TimedRequestMessage.h \
--known-failure app/MessageDef/WriteRequestMessage.h \
--known-failure app/MessageDef/WriteResponseMessage.h \
--known-failure app/ObjectList.h \
--known-failure app/ReadClient.h \
--known-failure app/ReadHandler.h \
--known-failure app/ReadPrepareParams.h \
--known-failure app/reporting/tests/MockReportScheduler.cpp \
--known-failure app/reporting/tests/MockReportScheduler.h \
--known-failure app/server/AppDelegate.h \
--known-failure app/TestEventTriggerDelegate.h \
--known-failure app/tests/integration/common.h \
--known-failure app/tests/integration/MockEvents.h \
--known-failure app/tests/suites/credentials/TestHarnessDACProvider.h \
--known-failure app/tests/TestOperationalDeviceProxy.cpp \
--known-failure app/util/af-enums.h \
--known-failure app/util/af.h \
--known-failure app/util/af-types.h \
--known-failure app/util/attribute-metadata.h \
--known-failure app/util/attribute-storage.cpp \
--known-failure app/util/attribute-storage.h \
--known-failure app/util/attribute-storage-null-handling.h \
--known-failure app/util/attribute-table.cpp \
--known-failure app/util/attribute-table.h \
--known-failure app/util/binding-table.cpp \
--known-failure app/util/binding-table.h \
--known-failure app/util/common.h \
--known-failure app/util/config.h \
--known-failure app/util/DataModelHandler.cpp \
--known-failure app/util/DataModelHandler.h \
--known-failure app/util/ember-compatibility-functions.cpp \
--known-failure app/util/endpoint-config-api.h \
--known-failure app/util/endpoint-config-defines.h \
--known-failure app/util/error-mapping.h \
--known-failure app/util/generic-callbacks.h \
--known-failure app/util/generic-callback-stubs.cpp \
--known-failure app/util/im-client-callbacks.h \
--known-failure app/util/MatterCallbacks.h \
--known-failure app/util/message.cpp \
--known-failure app/util/mock/Constants.h \
--known-failure app/util/mock/Functions.h \
--known-failure app/util/mock/MockNodeConfig.h \
--known-failure app/util/odd-sized-integers.h \
--known-failure app/util/types_stub.h \
--known-failure app/util/util.cpp \
--known-failure app/util/util.h \
--known-failure app/WriteClient.h \
--known-failure app/WriteHandler.h \
--known-failure inet/tests/TestInetLayerCommon.hpp \
--known-failure lib/core/CHIPVendorIdentifiers.hpp \
--known-failure lib/dnssd/Constants.h \
--known-failure lib/dnssd/minimal_mdns/core/FlatAllocatedQName.h \
--known-failure lib/dnssd/minimal_mdns/core/HeapQName.h \
--known-failure lib/dnssd/minimal_mdns/ListenIterator.h \
--known-failure lib/dnssd/minimal_mdns/tests/CheckOnlyServer.h \
--known-failure lib/dnssd/platform/DnssdBrowseDelegate.h \
--known-failure lib/support/CHIPArgParser.hpp \
--known-failure messaging/tests/echo/common.h \
--known-failure platform/DeviceSafeQueue.cpp \
--known-failure platform/DeviceSafeQueue.h \
--known-failure platform/GLibTypeDeleter.h \
--known-failure platform/SingletonConfigurationManager.cpp \
--known-failure transport/retransmit/tests/TestCacheDriver.cpp \
"
- name: Check for matter lint errors
if: always()
run: |
Expand Down
182 changes: 182 additions & 0 deletions scripts/tools/not_known_to_gn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
#
# Copyright (c) 2024 Project CHIP Authors
#
# 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.
#
"""
Lists files specific files from a source tree and ensures
they are covered by GN in some way.
'Covered' is very loosely and it just tries to see if the GN text
contains that word without trying to validate if this is a
comment or some actual 'source' element.
It is intended as a failsafe to not foget adding source files
to gn.
"""
import logging
import os
import sys
from pathlib import Path, PurePath
from typing import Dict, Set

import click
import coloredlogs

__LOG_LEVELS__ = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warn': logging.WARN,
'fatal': logging.FATAL,
}


class OrphanChecker:
def __init__(self):
self.gn_data: Dict[str, str] = {}
self.known_failures: Set[str] = set()
self.fatal_failures = 0
self.failures = 0
self.found_failures: Set[str] = set()

def AppendGnData(self, gn: PurePath):
"""Adds a GN file to the list of internally known GN data.
Will read the entire content of the GN file in memory for future reference.
"""
logging.debug(f'Adding GN {gn!s} for {gn.parent!s}')
self.gn_data[str(gn.parent)] = gn.read_text('utf-8')

def AddKnownFailure(self, k: str):
self.known_failures.add(k)

def _IsKnownFailure(self, path: str) -> bool:
"""check if failing on the given path is a known/acceptable failure"""
for k in self.known_failures:
if path == k or path.endswith(os.path.sep + k):
# mark some found failures to report if something is supposed
# to be known but it is not
self.found_failures.add(k)
return True
return False

def Check(self, top_dir: str, file: PurePath):
"""
Validates that the given path is somehow referenced in GN files in any
of the parent sub-directories of the file.
`file` must be relative to `top_dir`. Top_dir is used to resolve relative
paths in error reports and known failure checks.
"""
# Check logic:
# - ensure the file name is included in some GN file inside this or
# upper directory (although upper directory is not ideal)
for p in file.parents:
data = self.gn_data.get(str(p), None)
if not data:
continue

if file.name in data:
logging.debug("%s found in BUILD.gn for %s", file, p)
return

path = str(file.relative_to(top_dir))
if not self._IsKnownFailure(path):
logging.error("UNKNOWN to gn: %s", path)
self.fatal_failures += 1
else:
logging.warning("UNKNOWN to gn: %s (known error)", path)

self.failures += 1


@click.command()
@click.option(
'--log-level',
default='INFO',
type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False),
help='Determines the verbosity of script output',
)
@click.option(
'--extensions',
default=["cpp", "cc", "c", "h", "hpp"],
type=str, multiple=True,
help='What file extensions to consider',
)
@click.option(
'--known-failure',
type=str, multiple=True,
help='What paths are known to fail',
)
@click.option(
'--skip-dir',
type=str,
multiple=True,
help='Skip a specific sub-directory from checks',
)
@click.argument('dirs',
type=click.Path(exists=True, file_okay=False, resolve_path=True), nargs=-1)
def main(log_level, extensions, dirs, known_failure, skip_dir):
coloredlogs.install(level=__LOG_LEVELS__[log_level],
fmt='%(asctime)s %(levelname)-7s %(message)s')

if not dirs:
logging.error("Please provide at least one directory to scan")
sys.exit(1)

if not extensions:
logging.error("Need at least one extension")
sys.exit(1)

checker = OrphanChecker()
for k in known_failure:
checker.AddKnownFailure(k)

# ensure all GN data is loaded
for directory in dirs:
for name in Path(directory).rglob("BUILD.gn"):
checker.AppendGnData(name)

skip_dir = set(skip_dir)

# Go through all files and check for orphaned (if any)
extensions = set(extensions)
for directory in dirs:
for path, dirnames, filenames in os.walk(directory):
if any([s in path for s in skip_dir]):
continue
for f in filenames:
full_path = Path(os.path.join(path, f))
if not full_path.suffix or full_path.suffix[1:] not in extensions:
continue
checker.Check(directory, full_path)

if checker.failures:
logging.warning("%d files not known to GN (%d fatal)", checker.failures, checker.fatal_failures)

if checker.known_failures != checker.found_failures:
not_failing = checker.known_failures - checker.found_failures
logging.warning("NOTE: %d failures are not found anymore:", len(not_failing))
for name in not_failing:
logging.warning(" - %s", name)
# Assume this is fatal - remove some of the "known-failing" should be easy.
# This forces scripts to always be correct and not accumulate bad input.
sys.exit(1)

if checker.fatal_failures > 0:
sys.exit(1)


if __name__ == '__main__':
main(auto_envvar_prefix='CHIP')

0 comments on commit 1661124

Please sign in to comment.