-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
Closed as not planned
Labels
type-featureA feature request or enhancementA feature request or enhancement
Description
Feature or enhancement
Proposal:
#ifdef MS_WIN64
#if defined(_M_X64) || defined(_M_AMD64)
#if defined(__clang__)
#define COMPILER ("[Clang " __clang_version__ "] 64 bit (AMD64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
#define PY_SUPPORT_TIER 0
#elif defined(__INTEL_COMPILER)
#define COMPILER ("[ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 64 bit (amd64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
#define PY_SUPPORT_TIER 0
#else
#define COMPILER _Py_PASTE_VERSION("64 bit (AMD64)")
#define PY_SUPPORT_TIER 1
#endif /* __clang__ */
#define PYD_PLATFORM_TAG "win_amd64"
#elif defined(_M_ARM64)
#define COMPILER _Py_PASTE_VERSION("64 bit (ARM64)")
#define PY_SUPPORT_TIER 3
#define PYD_PLATFORM_TAG "win_arm64"
#else
#define COMPILER _Py_PASTE_VERSION("64 bit (Unknown)")
#define PY_SUPPORT_TIER 0
#endif
#endif /* MS_WIN64 */This is the code in "PC/pyconfig.h", it defines the macro "COMPILER" and break the syntax in v8:
enum StateTag : uint16_t {
JS,
GC,
PARSER,
BYTECODE_COMPILER,
COMPILER,
OTHER,
EXTERNAL,
ATOMICS_WAIT,
IDLE,
LOGGING,
IDLE_EXTERNAL,
};Due to that it is unsure whether there are the other macros which don't start with "Py" or "_Py" won't be undefined (mainly in "pyconfig.h"), here is the script to create the file:
Details
import os
import re
import datetime
def is_valid_macro_name(macro_name):
"""
Determine whether a macro name is valid using Python's standard library methods.
Args:
macro_name: The macro name to check.
Returns:
bool: True if it's a valid Python identifier, False otherwise.
"""
# Empty string is invalid
if not macro_name:
return False
# Use str.isidentifier() to check for valid identifier syntax
return macro_name.isidentifier()
def extract_macro_name(line):
"""Extract the macro name from a #define line (handles spaces between # and define)."""
line = line.strip()
# Match '#', optional spaces, 'define', spaces, and the macro name
match = re.match(r'^#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)', line)
if not match:
return None
candidate = match.group(1)
# Validate with standard identifier rules
if candidate and is_valid_macro_name(candidate):
return candidate
return None
def is_standard_python_macro(macro_name):
"""
Check whether a macro follows Python's standard naming conventions.
Rules: Starts with Py, PY, _Py, _PY, or ends with _H.
"""
standard_prefixes = ('Py', 'PY', '_Py', '_PY')
return macro_name.startswith(standard_prefixes) or macro_name.endswith('_H')
def generate_undef_code(macro_name):
"""Generate the code to undefine a macro."""
return f"""#ifndef DONOTUNDEF_{macro_name}
#ifdef {macro_name}
#undef {macro_name}
#endif
#endif
"""
def generate_python_undef_header(pyconfig_path, output_path=None):
"""
Generate the Python_undef.h header file.
Args:
pyconfig_path: Path to pyconfig.h
output_path: Output file path, defaults to Python_undef.h in the current directory.
"""
if output_path is None:
output_path = 'Python_undef.h'
# Read pyconfig.h
try:
with open(pyconfig_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
except FileNotFoundError:
print(f"Error: File not found {pyconfig_path}")
return False
except Exception as e:
print(f"Error reading file: {e}")
return False
# Collect macros
macros_to_undef = []
all_macros = []
invalid_macros = []
print("Analyzing pyconfig.h...")
for i, line in enumerate(lines, 1):
macro_name = extract_macro_name(line)
if macro_name:
all_macros.append(macro_name)
# New rule: any macro not starting with Py/PY/_Py/_PY and not ending with _H is considered non-standard
if not is_standard_python_macro(macro_name):
macros_to_undef.append(macro_name)
print(f"Line {i:4d}: Found non-standard macro '{macro_name}'")
else:
# Check if line looks like a define but has invalid name
line = line.strip()
if line.startswith('#'):
m = re.match(r'^#\s*define\s+(\S+)', line)
if m:
candidate = m.group(1)
if candidate and not is_valid_macro_name(candidate):
invalid_macros.append((i, candidate))
# Deduplicate and sort
macros_to_undef = sorted(set(macros_to_undef))
# Header section
header = f"""/*
* Python_undef.h - Automatically generated macro undefinition header
*
* This file is automatically generated from {os.path.basename(pyconfig_path)}
* Contains macros that may need to be undefined to avoid conflicts with other libraries.
*
* WARNING: This is an automatically generated file. Do not edit manually.
*
* Usage:
* #include <Python.h>
* #include <Python_undef.h>
* #include <other_library_headers.h>
*
* To preserve specific macros, define before including this header:
* #define DONOTUNDEF_MACRO_NAME
*
* Generation rules:
* - Macros starting with Py_, PY_, _Py, _PY are preserved (Python standard)
* - Macros ending with _H are preserved (header guards)
* - All other macros are undefined
* - Macro name validation uses Python's standard identifier checking
*
* Generated from: {os.path.abspath(pyconfig_path)}
* Generated at: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
* Total valid macros found: {len(all_macros)}
* Macros to undef: {len(macros_to_undef)}
* Invalid macro names skipped: {len(invalid_macros)}
*/
#ifndef PYTHON_UNDEF_H
#define PYTHON_UNDEF_H
#ifndef Py_PYTHON_H
# error "Python_undef.h must be included *after* Python.h"
#endif
/*
* Platform Note:
* - The COMPILER macro is primarily defined in pyconfig.h on Windows
* - Other platforms define compiler info in Python/getcompiler.c
* - This macro and others can conflict with libraries such as V8
*/
"""
# Generate undef code sections
undef_sections = []
for macro_name in macros_to_undef:
undef_sections.append(generate_undef_code(macro_name))
# Footer
footer = """#endif /* PYTHON_UNDEF_H */
"""
# Write output
try:
with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(header)
f.writelines(undef_sections)
f.write(footer)
print(f"\n{'='*60}")
print(f"Successfully generated: {output_path}")
print(f"{'='*60}")
print("Summary:")
print(f" - Total valid macro definitions: {len(all_macros)}")
print(f" - Macros to undefine: {len(macros_to_undef)}")
print(f" - Preserved standard macros: {len(all_macros) - len(macros_to_undef)}")
print(f" - Invalid macro names skipped: {len(invalid_macros)}")
if invalid_macros:
print(f"\nSkipped invalid macro names:")
for line_num, invalid_macro in invalid_macros[:10]: # show only first 10
print(f" Line {line_num:4d}: '{invalid_macro}'")
if len(invalid_macros) > 10:
print(f" ... and {len(invalid_macros) - 10} more")
if macros_to_undef:
print(f"\nMacros to undefine (first 50):")
for i, macro in enumerate(macros_to_undef[:50], 1):
print(f" {i:3d}. {macro}")
if len(macros_to_undef) > 50:
print(f" ... and {len(macros_to_undef) - 50} more")
print(f"\nUsage Notes:")
print(f" 1. Include this file before including other library headers.")
print(f" 2. Use DONOTUNDEF_XXX to protect macros that must be kept.")
print(f" 3. Regenerate this file whenever rebuilding Python.")
return True
except Exception as e:
print(f"Error writing file: {e}")
return False
def test_macro_validation():
"""Test the macro name validation function."""
test_cases = [
("COMPILER", True, "Valid identifier"),
("Py_InitModule", True, "Python standard macro"),
("PYCONFIG_H", True, "Header guard macro"),
("123MACRO", False, "Starts with a digit"),
("MACRO-TEST", False, "Contains a hyphen"),
("MACRO.TEST", False, "Contains a dot"),
("if", True, "C/C++ keyword but valid macro name"),
("for", True, "C/C++ keyword but valid macro name"),
("_Private", True, "Starts with underscore"),
("__special__", True, "Double underscore name"),
("", False, "Empty string"),
("MAX_VALUE", True, "Valid identifier"),
("SIZEOF_INT", True, "Valid identifier"),
("HAVE_STDLIB", True, "Valid identifier"),
]
print("Macro name validation tests:")
print("-" * 50)
for macro, expected, description in test_cases:
result = is_valid_macro_name(macro)
status = "✓" if result == expected else "✗"
print(f"{status} {macro:15} -> {result:5} ({description})")
if __name__ == "__main__":
test_macro_validation()
print(f"\n{'='*60}")
print("Note: Python keywords are not excluded since they are valid macro names in C/C++.")
print(f"{'='*60}")
pyconfig_path = "pyconfig.h" # modify as needed
if os.path.exists(pyconfig_path):
success = generate_python_undef_header(pyconfig_path)
if success:
print(f"\n✅ Generation complete!")
print(f"💡 Tip: Place Python_undef.h inside Python include search path.")
else:
print(f"\n❌ Generation failed!")
else:
print(f"File {pyconfig_path} not found.")
print("Please update the pyconfig_path variable to the actual pyconfig.h path.")
print("\nTypical paths on Windows:")
print(" C:\\\\Python3x\\\\include\\\\pyconfig.h")
print("\nTypical paths on Unix/Linux:")
print(" /usr/include/python3.x/pyconfig.h")
print(" /usr/local/include/python3.x/pyconfig.h")The macros which were defined in "pyconfig.h" and not standard only take part in the python itself building. Users can safely undefined them:
#include <Python.h>
#include <Python_undef.h> // To udefine the macros that may cause SyntaxError
#include <other_header.h>If users want to use the macro, they can define the macro to force preserve it:
#include <Python.h>
#define DONOTUNDEF_macro_name
#include <Python_undef.h> // If the macro "macro_name" exist, it will be preserved
#include <other_header.h>The action that include this header file to undefine the macros should be explicit.
Has this already been discussed elsewhere?
Links to previous discussion of this feature:
No response
Metadata
Metadata
Assignees
Labels
type-featureA feature request or enhancementA feature request or enhancement