Files
esp-idf/tools/ci/check_examples_documented.py
T
igor.masar c30851292f docs(usb): Update links and example checks
Replace the peripherals index USB entries with direct links to
ESP-USB so the generated pages are no longer redirect-only stubs.

Keep legacy USB URLs working via redirects, including the
individual USB host maintainer notes pages.

Remove the obsolete placeholder documents and stale CODEOWNERS
pattern.

Mark the moved USB examples as KNOWN_MISSING in the example
documentation checker until it can also read the ESP-USB guide.
2026-03-03 17:01:08 +01:00

270 lines
11 KiB
Python

#!/usr/bin/env python3
#
# Checks that every IDF example is referenced in the programming guide (docs/en/).
# Each example must be referenced exactly by its own path; parent-directory references do not count.
#
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
import re
import sys
from pathlib import Path
# Examples that are not yet referenced in the programming guide.
# These must be fixed: add a :example:`<path>` reference in docs/en/ and remove from here.
KNOWN_MISSING = {
# TODO IDF-15374: add :example: references for ble_get_started examples
'bluetooth/ble_get_started/bluedroid/Bluedroid_Beacon',
'bluetooth/ble_get_started/bluedroid/Bluedroid_Connection',
'bluetooth/ble_get_started/bluedroid/Bluedroid_GATT_Server',
'bluetooth/ble_get_started/nimble/NimBLE_Security',
# TODO IDF-15375: add :example: references for zigbee and ieee802154 examples
'ieee802154/ieee802154_cli',
'zigbee/esp_zigbee_gateway',
'zigbee/light_sample/HA_on_off_light',
'zigbee/light_sample/HA_on_off_switch',
# TODO IDF-15376: add :example: reference for security_features_app
'security/security_features_app',
# TODO IDF-15380: add :example: references for bluetooth examples
'bluetooth/bluedroid/ble/ble_acl_latency/cent',
'bluetooth/bluedroid/ble/ble_acl_latency/periph',
'bluetooth/bluedroid/ble/ble_ancs',
'bluetooth/bluedroid/ble/ble_compatibility_test',
'bluetooth/bluedroid/ble/ble_eddystone_receiver',
'bluetooth/bluedroid/ble/ble_eddystone_sender',
'bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent',
'bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph',
'bluetooth/bluedroid/ble/ble_hid_device_demo',
'bluetooth/bluedroid/ble/ble_ibeacon',
'bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent',
'bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph',
'bluetooth/bluedroid/ble/ble_spp_client',
'bluetooth/bluedroid/ble/ble_spp_server',
'bluetooth/bluedroid/ble/ble_throughput/throughput_client',
'bluetooth/bluedroid/ble/ble_throughput/throughput_server',
'bluetooth/bluedroid/ble_50/ble50_security_client',
'bluetooth/bluedroid/ble_50/ble50_security_server',
'bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client',
'bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server',
'bluetooth/bluedroid/ble_50/ble_conn_subrating_central',
'bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral',
'bluetooth/bluedroid/ble_50/ble_connection_central_with_cte',
'bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte',
'bluetooth/bluedroid/ble_50/ble_pawr_advertiser',
'bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn',
'bluetooth/bluedroid/ble_50/ble_pawr_synchronizer',
'bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte',
'bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte',
'bluetooth/bluedroid/ble_50/ble_power_control_central',
'bluetooth/bluedroid/ble_50/ble_power_control_peripheral',
'bluetooth/bluedroid/ble_50/multi-adv',
'bluetooth/bluedroid/ble_50/periodic_adv',
'bluetooth/bluedroid/ble_50/periodic_sync',
'bluetooth/bluedroid/bluedroid_host_only/bluedroid_host_only_uart',
'bluetooth/esp_ble_mesh/aligenie_demo',
'bluetooth/esp_ble_mesh/directed_forwarding/df_client',
'bluetooth/esp_ble_mesh/directed_forwarding/df_server',
'bluetooth/nimble/ble_ancs',
'bluetooth/nimble/ble_chan_sound_initiator',
'bluetooth/nimble/ble_chan_sound_reflector',
'bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte',
'bluetooth/nimble/ble_cte/ble_periodic_sync_with_cte',
'bluetooth/nimble/ble_cts/cts_cent',
'bluetooth/nimble/ble_cts/cts_prph',
'bluetooth/nimble/ble_dynamic_service',
'bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent',
'bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph',
'bluetooth/nimble/ble_gattc_gatts_coex',
'bluetooth/nimble/ble_htp/htp_cent',
'bluetooth/nimble/ble_htp/htp_prph',
'bluetooth/nimble/ble_l2cap_coc/coc_blecent',
'bluetooth/nimble/ble_l2cap_coc/coc_bleprph',
'bluetooth/nimble/ble_multi_adv',
'bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent',
'bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph',
'bluetooth/nimble/ble_pawr_adv/ble_pawr_adv',
'bluetooth/nimble/ble_pawr_adv/ble_pawr_sync',
'bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn',
'bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn',
'bluetooth/nimble/ble_periodic_adv',
'bluetooth/nimble/ble_periodic_sync',
'bluetooth/nimble/ble_phy/phy_cent',
'bluetooth/nimble/ble_phy/phy_prph',
'bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent',
'bluetooth/nimble/ble_proximity_sensor/proximity_sensor_prph',
'bluetooth/nimble/ble_spp/spp_client',
'bluetooth/nimble/ble_spp/spp_server',
'bluetooth/nimble/blecent',
'bluetooth/nimble/blecsc',
'bluetooth/nimble/blehr',
'bluetooth/nimble/blemesh',
'bluetooth/nimble/bleprph',
'bluetooth/nimble/bleprph_host_only',
'bluetooth/nimble/bleprph_wifi_coex',
'bluetooth/nimble/hci',
'bluetooth/nimble/throughput_app/blecent_throughput',
'bluetooth/nimble/throughput_app/bleprph_throughput',
# TODO IDF-15385: add :example: references for build_system examples
'build_system/cmake/import_prebuilt/prebuilt',
'build_system/cmakev2/features/component_manager',
'build_system/cmakev2/features/conditional_component',
'build_system/cmakev2/features/idf_as_lib',
'build_system/cmakev2/features/import_lib',
'build_system/cmakev2/features/import_lib_direct',
'build_system/cmakev2/features/import_prebuilt',
'build_system/cmakev2/features/import_prebuilt/prebuilt',
'build_system/cmakev2/features/multi_binary',
'build_system/cmakev2/features/multi_config',
'build_system/cmakev2/features/plugins',
'build_system/cmakev2/get-started/hello_world',
# TODO IDF-15381: add :example: references for system and custom_bootloader examples
'custom_bootloader/bootloader_multiboot',
'system/gcov',
'system/gdbstub',
'system/rt_mqueue',
'system/sysview_tracing',
'system/sysview_tracing_heap_log',
'system/ulp/lp_core/build_system',
'system/ulp/lp_core/build_system/main/ulp',
'system/ulp/lp_core/debugging/main/ulp',
'system/ulp/lp_core/gpio_wakeup',
'system/ulp/lp_core/inter_cpu_critical_section',
'system/ulp/lp_core/lp_adc',
'system/ulp/lp_core/lp_spi',
'system/ulp/lp_core/lp_timer_interrupt',
'system/ulp/lp_core/lp_touch',
'system/ulp/ulp_riscv/gpio_pulse_counter',
'system/unit_test/test',
# TODO IDF-15382: add :example: references for peripherals examples
'peripherals/camera/dvp_dsi',
'peripherals/camera/dvp_spi_lcd',
'peripherals/h264',
'peripherals/i2s/i2s_advance/i2s_usb',
'peripherals/spi_slave/receiver',
'peripherals/spi_slave/sender',
'peripherals/spi_slave_hd/append_mode/master',
'peripherals/spi_slave_hd/append_mode/slave',
'peripherals/spi_slave_hd/segment_mode/seg_master',
'peripherals/spi_slave_hd/segment_mode/seg_slave',
'peripherals/twai/twai_network/twai_listen_only',
'peripherals/twai/twai_network/twai_sender',
# TODO: remove once the checker can also account for the ESP-USB programming guide.
'peripherals/usb/device/cherryusb_serial_device',
'peripherals/usb/device/tusb_composite_msc_serialdevice',
'peripherals/usb/device/tusb_console',
'peripherals/usb/device/tusb_hid',
'peripherals/usb/device/tusb_midi',
'peripherals/usb/device/tusb_msc',
'peripherals/usb/device/tusb_ncm',
'peripherals/usb/device/tusb_serial_device',
'peripherals/usb/host/cdc',
'peripherals/usb/host/cherryusb_host',
'peripherals/usb/host/hid',
'peripherals/usb/host/msc',
'peripherals/usb/host/usb_host_lib',
'peripherals/usb/host/uvc',
# TODO IDF-15383: add :example: references for protocols examples
'protocols/dns_over_https',
'protocols/http_request',
'protocols/icmp/pmtu_probe',
'protocols/mqtt',
'protocols/mqtt5',
'protocols/smtp_client',
'protocols/sockets/tcp_transport_client',
# TODO IDF-15384: add :example: references for wifi examples
'wifi/ftm',
'wifi/wifi_nvs_config',
}
# Matches :example:`path` and :example:`label <path>`
_EXAMPLE_ROLE_RE = re.compile(r':example:`(?:[^<`]*<([^>]+)>|([^`]+))`')
def find_leaf_examples(examples_dir: Path) -> list[str]:
"""Return relative paths of all leaf examples (those containing a project() call)."""
results = []
for cmake_file in examples_dir.rglob('CMakeLists.txt'):
# Skip managed_components and build dirs
parts = cmake_file.parts
if 'managed_components' in parts or 'build' in parts:
continue
if cmake_file.read_text().startswith('project(') or '\nproject(' in cmake_file.read_text():
rel = cmake_file.parent.relative_to(examples_dir)
results.append(str(rel))
return sorted(results)
def collect_doc_example_refs(docs_dir: Path) -> set[str]:
"""Return the set of example paths referenced in .rst files under docs_dir."""
refs: set[str] = set()
for rst_file in docs_dir.rglob('*.rst'):
text = rst_file.read_text(errors='replace')
for m in _EXAMPLE_ROLE_RE.finditer(text):
path = (m.group(1) or m.group(2)).strip()
refs.add(path)
return refs
def is_covered(example: str, refs: set[str]) -> bool:
"""An example is covered only if it is referenced exactly by its own path."""
return example in refs
def main() -> int:
parser = argparse.ArgumentParser(description='Check that all IDF examples are referenced in the programming guide.')
parser.add_argument(
'--idf-path',
default=os.environ.get('IDF_PATH', '.'),
help='Path to the IDF repository root (default: $IDF_PATH or .)',
)
args = parser.parse_args()
idf_path = Path(args.idf_path)
examples_dir = idf_path / 'examples'
docs_dir = idf_path / 'docs' / 'en'
if not examples_dir.is_dir():
print(f'ERROR: examples directory not found: {examples_dir}', file=sys.stderr)
return 1
if not docs_dir.is_dir():
print(f'ERROR: docs/en directory not found: {docs_dir}', file=sys.stderr)
return 1
examples = find_leaf_examples(examples_dir)
refs = collect_doc_example_refs(docs_dir)
newly_missing = []
fixed = []
for ex in examples:
covered = is_covered(ex, refs)
if not covered and ex not in KNOWN_MISSING:
newly_missing.append(ex)
elif covered and ex in KNOWN_MISSING:
fixed.append(ex)
if fixed:
print('The following examples were in KNOWN_MISSING but are now referenced in docs.')
print('Please remove them from KNOWN_MISSING in tools/ci/check_examples_documented.py:')
for ex in fixed:
print(f' {ex}')
print()
if newly_missing:
print('ERROR: The following examples are not referenced anywhere in the programming guide (docs/en/).')
print('Add a :example:`<path>` role to an appropriate .rst file in docs/en/, then run this check again.')
print()
for ex in newly_missing:
print(f' {ex}')
return 1
if not fixed:
print(f'OK: all {len(examples)} examples are referenced in the programming guide.')
return 1 if fixed else 0
if __name__ == '__main__':
sys.exit(main())