Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ jobs:
- name: Check for unsupported C global variables
if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME
run: make check-c-globals
- name: Check C API macros
run: make check-capi-macros
Comment thread
vstinner marked this conversation as resolved.
Resolved

check-c-api-docs:
name: C API Docs
Expand Down
5 changes: 5 additions & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -3367,6 +3367,11 @@ distclean: clobber docclean
smelly: all
$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/smelly.py

# Check that macros defined by the Python C API start with "Py"
.PHONY: check-capi-macros
check-capi-macros: all
$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/check_capi_macros.py

Comment thread
vstinner marked this conversation as resolved.
Resolved
# Check if any unsupported C global variables have been added.
.PHONY: check-c-globals
check-c-globals:
Expand Down
106 changes: 106 additions & 0 deletions Tools/build/check_capi_macros.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Check that all macros defined by the Python C API have a name starting with
"Py". Ignore names listed by check_capi_macros_ignored.txt: macros with an
invalid name, added before this script was created.

Python C API:

* Include/*.h
* Include/cpython/*.h
* pyconfig.h.in
"""

import glob
import os.path
import re
import sys

TOOLS_BUILD_DIR = os.path.abspath(os.path.dirname(__file__))
SRC_DIR = os.path.dirname(os.path.dirname(TOOLS_BUILD_DIR))
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved

DEFINE_REGEX = re.compile(r'^\s*# *define\s+(.*)')
PYTHON_PREFIX = re.compile(r'^(Py|PY|_Py|_PY)')
DEFINE_NAME_REGEX = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*)\b')
UNDEF_REGEX = re.compile(r'#undef (.*)$')
Comment thread
vstinner marked this conversation as resolved.
Resolved


def parse_file(filename, names, ignored):
with open(filename, encoding='utf8') as fp:
for line in fp:
match = DEFINE_REGEX.match(line)
Comment thread
vstinner marked this conversation as resolved.
Resolved
if not match:
continue
macro = match.group(1)

if PYTHON_PREFIX.match(macro):
Comment thread
vstinner marked this conversation as resolved.
Resolved
continue

match = DEFINE_NAME_REGEX.search(macro)
if not match:
print(f"ERROR: {filename}: Unable to parse {line!r}")
sys.exit(1)
name = match.group(1)
if name in ignored:
continue

names.append((name, filename))


def parse_pyconfig_in(filename, names, ignored):
with open(filename, encoding='utf8') as fp:
for line in fp:
match = UNDEF_REGEX.match(line)
if not match:
continue
name = match.group(1)
if name in ignored:
continue
names.append((name, filename))


def get_ignored_names():
filename = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt')
ignored = set()
with open(filename, encoding='utf8') as fp:
for line in fp:
name = line.strip()
if name.startswith('#'):
# Ignore comment
continue
if name:
ignored.add(name)
return ignored


def main():
ignored = get_ignored_names()

include_dir = os.path.join(SRC_DIR, 'Include')
files = glob.glob(os.path.join(include_dir, '*.h'))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Forgot to post this yesterday)

Include/opcode_ids.h contains generated opcodes, we could exclude these:

Suggested change
files = glob.glob(os.path.join(include_dir, '*.h'))
files = glob.glob(os.path.join(include_dir, '*.h'))
files.remove(os.path.join(include_dir, 'opcode_ids.h'))

And then remove the 245 generated names from check_capi_macros_ignored.txt. This will make maintenance easier so we don't need to add/remove future ones.

files.extend(glob.glob(os.path.join(include_dir, 'cpython', '*.h')))
names = []
for filename in files:
parse_file(filename, names, ignored)
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved

filename = os.path.join(SRC_DIR, 'pyconfig.h.in')
parse_file(filename, names, ignored)
parse_pyconfig_in(filename, names, ignored)
names.sort()

if not names:
print("OK: the Python C API only defines macros with names "
f"starting with Py (ignoring {len(ignored)} macros)")
sys.exit(0)

print('ERROR: the Python C API defines the following macros '
'with a name not starting with "Py":')
print()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, but it'd be nice to have colorful output with _colorize (the C API docs check script does this).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that it's needed to add colors to this script.

for name, filename in names:
print(f"- {name} defined by {filename}")
print()
print(f"Total: {len(names)} macros")
sys.exit(1)


if __name__ == "__main__":
main()
Loading
Loading