Update extern c ignore (#3025)
* Add a --include-list argument
This allows us to check the exclude list is still valid.
* Add some missing __cplusplus guards
* Update .extern_c_ignore
See #2865
* Add a github workflow to check extern c guards
diff --git a/.extern_c_ignore b/.extern_c_ignore
index 3a7464a..306b1db 100644
--- a/.extern_c_ignore
+++ b/.extern_c_ignore
@@ -25,8 +25,6 @@
src/common/pico_sync/include/pico/lock_core.h
src/common/pico_sync/include/pico/sync.h
src/common/pico_usb_reset_interface_headers/include/pico/usb_reset_interface.h
-src/host/pico_stdio/include/pico/stdio.h
-src/host/pico_time_adapter/include/pico/time_adapter.h
src/rp2040/pico_platform/include/pico/platform/cpu_regs.h
src/rp2350/pico_platform/include/pico/platform/cpu_regs.h
src/rp2_common/pico_bootrom/include/pico/bootrom/lock.h
@@ -36,8 +34,6 @@
src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h
src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h
src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h
-src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h
-src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/pico/fix/rp2040_usb_device_enumeration.h
src/rp2_common/pico_int64_ops/include/pico/int64_ops.h
src/rp2_common/pico_malloc/include/pico/malloc.h
src/rp2_common/pico_mem_ops/include/pico/mem_ops.h
@@ -45,9 +41,7 @@
src/rp2_common/pico_stdio/include/pico/stdio/driver.h
src/rp2_common/pico_stdio_usb/include/pico/stdio_usb/reset_interface.h
src/rp2_common/pico_thread_local/include/pico/thread_local.h
-src/rp2_common/pico_time_adapter/include/pico/time_adapter.h
src/rp2_common/pico_usb_reset/include/pico/usb_reset.h
src/rp2_common/pico_usb_reset/include/pico/usb_reset_config.h
-src/rp2_common/pico_usb_reset/include/pico/usb_reset_tusb.h
test/pico_test/include/pico/test.h
test/pico_test/include/pico/test/xrand.h
diff --git a/.github/workflows/check-extern-c-guards.yml b/.github/workflows/check-extern-c-guards.yml
new file mode 100644
index 0000000..f0a090e
--- /dev/null
+++ b/.github/workflows/check-extern-c-guards.yml
@@ -0,0 +1,39 @@
+name: Check extern C guards
+
+on:
+ push:
+ branches-ignore:
+ - 'iar/**'
+ paths:
+ - '**.h'
+ - '.extern_c_ignore'
+ - 'tools/check_extern_c_guards.py'
+ - '.github/workflows/check-extern-c-guards.yml'
+ pull_request:
+ paths:
+ - '**.h'
+ - '.extern_c_ignore'
+ - 'tools/check_extern_c_guards.py'
+ - '.github/workflows/check-extern-c-guards.yml'
+
+jobs:
+ check-guards:
+ if: github.repository_owner == 'raspberrypi'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Check extern C guards
+ run: |
+ python3 tools/check_extern_c_guards.py \
+ --match "**/include/pico/**" \
+ --ignore-list .extern_c_ignore \
+ .
+
diff --git a/src/host/pico_stdio/include/pico/stdio.h b/src/host/pico_stdio/include/pico/stdio.h
index b2b5807..9d6c60a 100644
--- a/src/host/pico_stdio/include/pico/stdio.h
+++ b/src/host/pico_stdio/include/pico/stdio.h
@@ -6,6 +6,10 @@
#ifndef _PICO_STDIO_H
#define _PICO_STDIO_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
typedef struct stdio_driver stdio_driver_t;
#define STDIO_ERROR -1
@@ -22,4 +26,8 @@
#define puts_raw puts
#define putchar_raw putchar
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/host/pico_time_adapter/include/pico/time_adapter.h b/src/host/pico_time_adapter/include/pico/time_adapter.h
index 74f8de2..94a6e14 100644
--- a/src/host/pico_time_adapter/include/pico/time_adapter.h
+++ b/src/host/pico_time_adapter/include/pico/time_adapter.h
@@ -7,6 +7,10 @@
#ifndef _PICO_TIME_ADAPTER_H
#define _PICO_TIME_ADAPTER_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
#ifndef TA_NUM_TIMERS
#define TA_NUM_TIMERS 1
#endif
@@ -32,4 +36,8 @@
alarm_pool_timer_t *ta_timer_instance(uint instance_num);
alarm_pool_timer_t *ta_default_timer_instance(void);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h b/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h
index 037b68d..cf40923 100644
--- a/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h
+++ b/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h
@@ -9,10 +9,18 @@
#include "btstack_chipset.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/**
* \brief Return the singleton BTstack chipset CY43 API instance
* \ingroup pico_btstack
*/
const btstack_chipset_t * btstack_chipset_cyw43_instance(void);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/pico/fix/rp2040_usb_device_enumeration.h b/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/pico/fix/rp2040_usb_device_enumeration.h
index 49f115e..62a9dd1 100644
--- a/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/pico/fix/rp2040_usb_device_enumeration.h
+++ b/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/pico/fix/rp2040_usb_device_enumeration.h
@@ -7,6 +7,10 @@
#ifndef _PICO_FIX_RP2040_USB_DEVICE_ENUMERATION_H
#define _PICO_FIX_RP2040_USB_DEVICE_ENUMERATION_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/*! \brief Perform a brute force workaround for USB device enumeration issue
* \ingroup pico_fix
*
@@ -14,4 +18,8 @@
*/
void rp2040_usb_device_enumeration_fix(void);
+#ifdef __cplusplus
+}
+#endif
+
#endif
\ No newline at end of file
diff --git a/src/rp2_common/pico_time_adapter/include/pico/time_adapter.h b/src/rp2_common/pico_time_adapter/include/pico/time_adapter.h
index 9392fe7..5b898c4 100644
--- a/src/rp2_common/pico_time_adapter/include/pico/time_adapter.h
+++ b/src/rp2_common/pico_time_adapter/include/pico/time_adapter.h
@@ -11,6 +11,10 @@
#include "hardware/timer.h"
#include "pico/assert.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
#define TA_NUM_TIMERS NUM_GENERIC_TIMERS
#define TA_NUM_TIMER_ALARMS NUM_ALARMS
@@ -102,4 +106,9 @@
static inline alarm_pool_timer_t *ta_default_timer_instance(void) {
return PICO_DEFAULT_TIMER_INSTANCE();
}
+
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/src/rp2_common/pico_usb_reset/include/pico/usb_reset_tusb.h b/src/rp2_common/pico_usb_reset/include/pico/usb_reset_tusb.h
index 1ef73d7..1a980bd 100644
--- a/src/rp2_common/pico_usb_reset/include/pico/usb_reset_tusb.h
+++ b/src/rp2_common/pico_usb_reset/include/pico/usb_reset_tusb.h
@@ -7,6 +7,10 @@
#ifndef _PICO_USB_RESET_TUSB_H
#define _PICO_USB_RESET_TUSB_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
// Interface descriptor
#define TUD_RPI_RESET_DESC_LEN 9
#define TUD_RPI_RESET_DESCRIPTOR(_itfnum, _stridx) \
@@ -61,4 +65,8 @@
.sof = NULL
};
+#ifdef __cplusplus
+}
+#endif
+
#endif
\ No newline at end of file
diff --git a/tools/check_extern_c_guards.py b/tools/check_extern_c_guards.py
old mode 100644
new mode 100755
index f463cdf..e6e495f
--- a/tools/check_extern_c_guards.py
+++ b/tools/check_extern_c_guards.py
@@ -32,6 +32,9 @@
# Skip files listed in an ignore list (reviewed, don't need guards)
python check_extern_c_guards.py --ignore-list .extern_c_ignore
+ # Check only specific files from a list (e.g. to verify your ignore list)
+ python check_extern_c_guards.py /path/to/pico-sdk --include-list .extern_c_ignore
+
# Exclude specific files or directories
python check_extern_c_guards.py --exclude "test/**" --exclude "host/**"
@@ -114,28 +117,26 @@
return any(fnmatch.fnmatch(filepath, pat) for pat in matches)
-def load_ignore_list(filepath: str) -> set[str]:
- """Load an ignore list file and return a set of normalised paths.
+def load_path_list(filepath: str) -> list[str]:
+ """Load a file containing one path per line.
- The file format is one path per line, relative to the repo / scan root.
- Blank lines and lines starting with ``#`` are ignored. Leading and
- trailing whitespace is stripped, and paths are normalised with forward
- slashes so that the file works cross-platform.
+ Blank lines and lines starting with ``#`` are treated as comments.
+ Leading/trailing whitespace is stripped and paths are normalised to
+ forward slashes so the file works cross-platform.
"""
- entries: set[str] = set()
+ entries: list[str] = []
try:
with open(filepath, encoding="utf-8") as fh:
for raw_line in fh:
line = raw_line.strip()
if not line or line.startswith("#"):
continue
- # Normalise separators and remove trailing slashes
normalised = line.replace("\\", "/").rstrip("/")
- entries.add(normalised)
+ entries.append(normalised)
except FileNotFoundError:
- print(f"Warning: ignore list not found: {filepath}", file=sys.stderr)
+ print(f"Warning: path list not found: {filepath}", file=sys.stderr)
except Exception as exc:
- print(f"Warning: could not read ignore list {filepath}: {exc}", file=sys.stderr)
+ print(f"Warning: could not read path list {filepath}: {exc}", file=sys.stderr)
return entries
@@ -418,6 +419,17 @@
),
)
parser.add_argument(
+ "--include-list",
+ metavar="FILE",
+ help=(
+ "Path to a file listing headers to check (one per line). "
+ "When given, ONLY these files are checked — the normal directory "
+ "walk or git diff is skipped. Paths are resolved relative to the "
+ "scan root. Useful for re-verifying your ignore list. "
+ "Uses the same file format as --ignore-list (# comments, blanks ok)."
+ ),
+ )
+ parser.add_argument(
"-v", "--verbose",
action="store_true",
help="Show passing files as well",
@@ -434,10 +446,23 @@
excludes = DEFAULT_EXCLUDES + args.exclude
matches = args.match # empty list means "match everything"
- ignore_set = load_ignore_list(args.ignore_list) if args.ignore_list else set()
+ ignore_set = set(load_path_list(args.ignore_list)) if args.ignore_list else set()
# Collect files based on mode
- if args.staged:
+ if args.include_list:
+ # --include-list: check exactly these files, resolved against root
+ root = os.path.abspath(args.path) if not (args.staged or args.commit or args.range) else os.path.abspath(".")
+ raw_paths = load_path_list(args.include_list)
+ files = []
+ for p in raw_paths:
+ full = os.path.join(root, p)
+ rel = os.path.relpath(full, root)
+ if is_header_file(rel) and not is_excluded(rel, excludes) and is_matched(rel, matches or []):
+ files.append(full)
+ files.sort()
+ label = f"headers from {args.include_list}"
+ rel_root = root
+ elif args.staged:
files = collect_staged(excludes, matches)
label = "staged headers"
rel_root = None