diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS new file mode 100644 index 0000000000..aa7317a6de --- /dev/null +++ b/.gitlab/CODEOWNERS @@ -0,0 +1,107 @@ +* @esp-idf-codeowners/other + +components/app_trace @esp-idf-codeowners/tools +components/app_update @esp-idf-codeowners/system @esp-idf-codeowners/app-utilities +components/asio @esp-idf-codeowners/network +components/bootloader*/** @esp-idf-codeowners/system @esp-idf-codeowners/security +components/bt @esp-idf-codeowners/bluetooth +components/cbor @esp-idf-codeowners/app-utilities +components/coap @esp-idf-codeowners/app-utilities +components/console @esp-idf-codeowners/system @esp-idf-codeowners/app-utilities +components/cxx @esp-idf-codeowners/system +components/driver @esp-idf-codeowners/peripherals +components/efuse @esp-idf-codeowners/system +components/esp32* @esp-idf-codeowners/system +components/esp_adc_cal @esp-idf-codeowners/peripherals +components/esp_common @esp-idf-codeowners/system +components/esp_eth @esp-idf-codeowners/network +components/esp_event @esp-idf-codeowners/system +components/esp_gdbstub @esp-idf-codeowners/tools +components/esp_hid @esp-idf-codeowners/bluetooth +components/esp_http_client @esp-idf-codeowners/app-utilities +components/esp_http_server @esp-idf-codeowners/app-utilities +components/esp_https_ota @esp-idf-codeowners/app-utilities +components/esp_https_server @esp-idf-codeowners/app-utilities +components/esp_ipc @esp-idf-codeowners/system +components/esp_local_ctrl @esp-idf-codeowners/app-utilities +components/esp_netif @esp-idf-codeowners/network +components/esp_ringbuf @esp-idf-codeowners/system +components/esp_rom @esp-idf-codeowners/system +components/esp_serial_slave_link @esp-idf-codeowners/peripherals +components/esp_system @esp-idf-codeowners/system +components/esp_timer @esp-idf-codeowners/system +components/esp-tls @esp-idf-codeowners/app-utilities +components/esp_websocket_client @esp-idf-codeowners/network +components/esp_wifi @esp-idf-codeowners/wifi +components/espcoredump @esp-idf-codeowners/tools +components/esptool_py @esp-idf-codeowners/tools +components/expat @esp-idf-codeowners/app-utilities +components/fatfs @esp-idf-codeowners/storage +components/freemodbus @esp-idf-codeowners/peripherals +components/freertos @esp-idf-codeowners/system +components/heap @esp-idf-codeowners/system +components/idf_test @esp-idf-codeowners/ci +components/jsmn @esp-idf-codeowners/app-utilities +components/json @esp-idf-codeowners/app-utilities +components/libsodium @esp-idf-codeowners/security +components/log @esp-idf-codeowners/system +components/lwip @esp-idf-codeowners/lwip +components/mbedtls @esp-idf-codeowners/app-utilities @esp-idf-codeowners/security +components/mdns @esp-idf-codeowners/network +components/mqtt @esp-idf-codeowners/network +components/newlib @esp-idf-codeowners/system @esp-idf-codeowners/tools +components/nghttp @esp-idf-codeowners/app-utilities +components/nvs_flash @esp-idf-codeowners/storage +components/openssl @esp-idf-codeowners/network +components/partition_table @esp-idf-codeowners/system +components/perfmon @esp-idf-codeowners/tools +components/protobuf-c @esp-idf-codeowners/app-utilities +components/protocomm @esp-idf-codeowners/app-utilities +components/pthread @esp-idf-codeowners/system +components/sdmmc @esp-idf-codeowners/storage +components/soc @esp-idf-codeowners/peripherals +components/spi_flash @esp-idf-codeowners/peripherals +components/spiffs @esp-idf-codeowners/storage +components/tcp_transport @esp-idf-codeowners/network +components/tcpip_adapter @esp-idf-codeowners/network +components/tinyusb @esp-idf-codeowners/peripherals +components/ulp @esp-idf-codeowners/system +components/unity @esp-idf-codeowners/ci +components/vfs @esp-idf-codeowners/storage +components/wear_levelling @esp-idf-codeowners/storage +components/wifi_provisioning @esp-idf-codeowners/app-utilities +components/wpa_supplicant @esp-idf-codeowners/wifi +components/xtensa @esp-idf-codeowners/system + +docs @esp-idf-codeowners/docs + +examples/**/*.py @esp-idf-codeowners/ci @esp-idf-codeowners/tools + +examples/bluetooth @esp-idf-codeowners/bluetooth +examples/build_system @esp-idf-codeowners/build-config +examples/common_components @esp-idf-codeowners/system +examples/cxx @esp-idf-codeowners/system +examples/ethernet @esp-idf-codeowners/network +examples/get-started @esp-idf-codeowners/system +examples/mesh @esp-idf-codeowners/wifi +examples/peripherals @esp-idf-codeowners/peripherals +examples/protocols @esp-idf-codeowners/network @esp-idf-codeowners/app-utilities +examples/provisioning @esp-idf-codeowners/app-utilities +examples/security @esp-idf-codeowners/security +examples/storage @esp-idf-codeowners/storage +examples/system @esp-idf-codeowners/system +examples/wifi @esp-idf-codeowners/wifi + +make @esp-idf-codeowners/build-config + +tools @esp-idf-codeowners/tools +tools/*_apps.py @esp-idf-codeowners/ci +tools/ble @esp-idf-codeowners/app-utilities +tools/catch @esp-idf-codeowners/ci +tools/ci @esp-idf-codeowners/ci +tools/cmake @esp-idf-codeowners/build-config +tools/esp_prov @esp-idf-codeowners/app-utilities +tools/find_build_apps @esp-idf-codeowners/ci +tools/kconfig* @esp-idf-codeowners/build-config +tools/ldgen @esp-idf-codeowners/build-config +tools/mass_mfg @esp-idf-codeowners/app-utilities diff --git a/tools/ci/config/pre_check.yml b/tools/ci/config/pre_check.yml index 986efaba90..c51b6c722c 100644 --- a/tools/ci/config/pre_check.yml +++ b/tools/ci/config/pre_check.yml @@ -202,3 +202,8 @@ clang_tidy_check_all: variables: BOT_NEEDS_TRIGGER_BY_NAME: 1 BOT_LABEL_STATIC_ANALYSIS_ALL: 1 + +check_codeowners: + extends: .check_job_template + script: + - tools/codeowners.py ci-check diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index db95d2d205..7008bab8ef 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -63,6 +63,7 @@ tools/ci/test_build_system_cmake.sh tools/ci/test_configure_ci_environment.sh tools/cmake/convert_to_cmake.py tools/cmake/run_cmake_lint.sh +tools/codeowners.py tools/docker/entrypoint.sh tools/docker/hooks/build tools/esp_app_trace/logtrace_proc.py diff --git a/tools/codeowners.py b/tools/codeowners.py new file mode 100755 index 0000000000..7cf1ab4944 --- /dev/null +++ b/tools/codeowners.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# Utility script for ESP-IDF developers to work with the CODEOWNERS file. +# +# Copyright 2020 Espressif Systems (Shanghai) PTE LTD +# +# 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. + +import argparse +from collections import defaultdict +import os +import subprocess +import sys + +CODEOWNERS_PATH = os.path.join(os.path.dirname(__file__), "..", ".gitlab", "CODEOWNERS") +CODEOWNER_GROUP_PREFIX = "@esp-idf-codeowners/" + + +def action_identify(args): + best_match = [] + with open(CODEOWNERS_PATH) as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + tokens = line.split() + path_pattern = tokens[0] + owners = tokens[1:] + files = files_by_pattern(path_pattern) + if args.path in files: + best_match = owners + for owner in best_match: + print(owner) + + +def files_by_pattern(pattern=None): + args = ["git", "ls-files"] + if pattern: + args.append(pattern) + idf_root = os.path.join(os.path.dirname(__file__), "..") + return subprocess.check_output(args, cwd=idf_root).decode("utf-8").split() + + +def action_ci_check(args): + errors = [] + + def add_error(msg): + errors.append("Error at CODEOWNERS:{}: {}".format(line_no, msg)) + + files_by_owner = defaultdict(int) + prev_path_pattern = "" + with open(CODEOWNERS_PATH) as f: + for line_no, line in enumerate(f, start=1): + # Skip empty lines and comments + line = line.strip() + if not line or line.startswith("#"): + continue + + # Each line has a form of " +" + tokens = line.split() + path_pattern = tokens[0] + owners = tokens[1:] + if not owners: + add_error("no owners specified for {}".format(path_pattern)) + + # Check that the file is sorted by path patterns + path_pattern_for_cmp = path_pattern.replace("-", "_") # ignore difference between _ and - for ordering + if path_pattern_for_cmp < prev_path_pattern: + add_error("file is not sorted: {} < {}".format(path_pattern_for_cmp, prev_path_pattern)) + prev_path_pattern = path_pattern_for_cmp + + # Check that the pattern matches at least one file + files = files_by_pattern(path_pattern) + if not files: + add_error("no files matched by pattern {}".format(path_pattern)) + + # Count the number of files per owner + for o in owners: + # Sanity-check the owner group name + if not o.startswith(CODEOWNER_GROUP_PREFIX): + add_error("owner {} doesn't start with {}".format(o, CODEOWNER_GROUP_PREFIX)) + files_by_owner[o] += len(files) + + owners_sorted = sorted([(owner, cnt) for owner, cnt in files_by_owner.items()], key=lambda p: p[0]) + print("File count per owner (not including submodules):") + for owner, cnt in owners_sorted: + print("{}: {} files".format(owner, cnt)) + + if not errors: + print("No errors found.") + else: + print("Errors found!") + for e in errors: + print(e) + raise SystemExit(1) + + +def main(): + parser = argparse.ArgumentParser( + sys.argv[0], description="Internal helper script for working with the CODEOWNERS file." + ) + subparsers = parser.add_subparsers(dest="action") + identify = subparsers.add_parser( + "identify", + help="Lists the owners of the specified path within IDF." + "This command doesn't support files inside submodules, or files not added to git repository.", + ) + identify.add_argument("path", help="Path of the file relative to the root of the repository") + subparsers.add_parser( + "ci-check", + help="Check CODEOWNERS file: every line should match at least one file, sanity-check group names, " + "check that the file is sorted by paths", + ) + args = parser.parse_args() + + if args.action is None: + parser.print_help() + parser.exit(1) + + action_func_name = "action_" + args.action.replace("-", "_") + action_func = globals()[action_func_name] + action_func(args) + + +if __name__ == "__main__": + main()