Pre-PEP: use a header file that can undefine the macros defined by <Python.h>

This is the crash I met yesterday:

When compile the C externsion module, the macros defined in “<Python.h>” break the syntax in “<v8.h>”:

It is a syntax that defined a series of variables. However, “<pyconfig.h>” defined the marcos “COMPILER” and broke it. Finally I had to changing the turn of including to fix it. Luckily, it worked. But it is not always effected (In this time it worked just because the “node” project didn’t define the marco which will break the syntax in “<Python.h>”).

Maybe it needs to list the macros that doesn’t for users’ using. When facing the problem caused by marco defination, users can undefine them before including the other header files.

Here may be a solution:

// Python_undef.h
#ifndef PYTHON_UNDEF_H
#define PYTHON_UNDEF_H

#ifndef DONOTUNDEF_COMPILER
    #ifdef COMPILER
        #undef COMPILER
    #endif
#endif

// the other macros that don't for users' using

#endif //PYTHON_UNDEF_H

And users can safely include “<Python.h>” without the worry of those macros:

#include <Python.h>
#include <Python_undef.h> // for undefine the marco "COMPILE"
#include <v8.h>

These macros won’t be cancel defination in “Python_undef.h”:

  • The macros which make sure that the header file will only be include once (many are ends with “_H”)
  • The macros which are in the document and declared that they can be used by users (many are start with “Py_”)

Which version of Python defines COMPILER?

Names exported in Python.h should start with Py, PY, _Py or _PY. There used to be exceptions, and possibly there still are, but those are fixed over time especially when there are conflicts with other headers.

That said, pyconfig.h uses the regular autoconf naming, which doesn’t use Python’s prefix on names.

IMHO a mechanism to undefine exported names is not necessary, we “just” have to clean up the exported names to ensure they all follow our naming convention.

1 Like

The “pyconfig.h” doesn’t appear in Python storage because it needs the environment of the device. For example, this is the part of “pyconfig.h” in the release of version 3.14.0 on win_amd64:

#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 */

I wondered whether we can undefined it just in “pyconfig.h” because the other header files in python may use them. So I think that the undefining of these macros should be explicit.

Using g++ -dM -E -include Python.h - < /dev/null can also find the macro “COMPILER”.

That explains why I couldn’t find the issue, I’m not on a windows box and COMPILER is only defined in pyconfig.h on Windows (in PC/pyconfig.h in the repository). For other platforms it seems to be defined in Python/getcompiler.c. According to a quick search that file is also the only user of the COMPILER macro.

For this particular issue it might be possible to just move the relevant bits from PC/pyconfig.h to Python/getcompiler.c in 3.15. But Chesterton’s fence applies: I don’t know why Windows and unix use different ways to define the COMPILER macro.

In fact, maybe there are the other macros that have comflict with the other C projects. So I will try to write a script to analyse the header file (mainly “pyconfig.h”) and finally write the “Python_undef.h”.

I don’t think it’s good to allow to undef macros. It can break the build in an unpredictable way. Instead, we should change this macro name to `_Py_COMPILER_NAME`. It’s not really meant to be public either as we have a function that returns it, namely `Py_GetCompiler`. I don’t know why we use `COMPILER`.

I don’t know why Windows and unix use different ways to define the COMPILER macro.

This is because we want to include MSVC information as well. PC/pyconfig.h is manually maintained but we might want to clean it up. It could also be because we need additional info from this file to construct the `COMPILER` macro but I’m not sure about it.

Now I make this script to make “Python_undef.h”:

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")

And then got:

/*
 * Python_undef.h - Automatically generated macro undefinition header
 * 
 * This file is automatically generated from pyconfig.h
 * 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: C:\Users\hh180\AppData\Local\Programs\Python\Python314\include\pyconfig.h
 * Generated at: 2025-11-09 21:57:34
 * Total valid macros found: 174
 * Macros to undef: 98
 * Invalid macro names skipped: 0
 */

#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
 */

#ifndef DONOTUNDEF_ALIGNOF_LONG
#ifdef ALIGNOF_LONG
#undef ALIGNOF_LONG
#endif
#endif

#ifndef DONOTUNDEF_ALIGNOF_MAX_ALIGN_T
#ifdef ALIGNOF_MAX_ALIGN_T
#undef ALIGNOF_MAX_ALIGN_T
#endif
#endif

#ifndef DONOTUNDEF_ALIGNOF_SIZE_T
#ifdef ALIGNOF_SIZE_T
#undef ALIGNOF_SIZE_T
#endif
#endif

#ifndef DONOTUNDEF_COMPILER
#ifdef COMPILER
#undef COMPILER
#endif
#endif

#ifndef DONOTUNDEF_DONT_HAVE_SIG_ALARM
#ifdef DONT_HAVE_SIG_ALARM
#undef DONT_HAVE_SIG_ALARM
#endif
#endif

#ifndef DONOTUNDEF_DONT_HAVE_SIG_PAUSE
#ifdef DONT_HAVE_SIG_PAUSE
#undef DONT_HAVE_SIG_PAUSE
#endif
#endif

#ifndef DONOTUNDEF_DOUBLE_IS_LITTLE_ENDIAN_IEEE754
#ifdef DOUBLE_IS_LITTLE_ENDIAN_IEEE754
#undef DOUBLE_IS_LITTLE_ENDIAN_IEEE754
#endif
#endif

#ifndef DONOTUNDEF_HAVE_ACCEPT
#ifdef HAVE_ACCEPT
#undef HAVE_ACCEPT
#endif
#endif

#ifndef DONOTUNDEF_HAVE_BIND
#ifdef HAVE_BIND
#undef HAVE_BIND
#endif
#endif

#ifndef DONOTUNDEF_HAVE_CLOCK
#ifdef HAVE_CLOCK
#undef HAVE_CLOCK
#endif
#endif

#ifndef DONOTUNDEF_HAVE_CONNECT
#ifdef HAVE_CONNECT
#undef HAVE_CONNECT
#endif
#endif

#ifndef DONOTUNDEF_HAVE_DECLSPEC_DLL
#ifdef HAVE_DECLSPEC_DLL
#undef HAVE_DECLSPEC_DLL
#endif
#endif

#ifndef DONOTUNDEF_HAVE_DECL_TZNAME
#ifdef HAVE_DECL_TZNAME
#undef HAVE_DECL_TZNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_DUP
#ifdef HAVE_DUP
#undef HAVE_DUP
#endif
#endif

#ifndef DONOTUNDEF_HAVE_DYNAMIC_LOADING
#ifdef HAVE_DYNAMIC_LOADING
#undef HAVE_DYNAMIC_LOADING
#endif
#endif

#ifndef DONOTUNDEF_HAVE_ERF
#ifdef HAVE_ERF
#undef HAVE_ERF
#endif
#endif

#ifndef DONOTUNDEF_HAVE_ERFC
#ifdef HAVE_ERFC
#undef HAVE_ERFC
#endif
#endif

#ifndef DONOTUNDEF_HAVE_FTIME
#ifdef HAVE_FTIME
#undef HAVE_FTIME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETHOSTBYADDR
#ifdef HAVE_GETHOSTBYADDR
#undef HAVE_GETHOSTBYADDR
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETHOSTBYNAME
#ifdef HAVE_GETHOSTBYNAME
#undef HAVE_GETHOSTBYNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETHOSTNAME
#ifdef HAVE_GETHOSTNAME
#undef HAVE_GETHOSTNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETPEERNAME
#ifdef HAVE_GETPEERNAME
#undef HAVE_GETPEERNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETPID
#ifdef HAVE_GETPID
#undef HAVE_GETPID
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETPROTOBYNAME
#ifdef HAVE_GETPROTOBYNAME
#undef HAVE_GETPROTOBYNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETSERVBYNAME
#ifdef HAVE_GETSERVBYNAME
#undef HAVE_GETSERVBYNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETSERVBYPORT
#ifdef HAVE_GETSERVBYPORT
#undef HAVE_GETSERVBYPORT
#endif
#endif

#ifndef DONOTUNDEF_HAVE_GETSOCKNAME
#ifdef HAVE_GETSOCKNAME
#undef HAVE_GETSOCKNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_INET_NTOA
#ifdef HAVE_INET_NTOA
#undef HAVE_INET_NTOA
#endif
#endif

#ifndef DONOTUNDEF_HAVE_INET_PTON
#ifdef HAVE_INET_PTON
#undef HAVE_INET_PTON
#endif
#endif

#ifndef DONOTUNDEF_HAVE_INTPTR_T
#ifdef HAVE_INTPTR_T
#undef HAVE_INTPTR_T
#endif
#endif

#ifndef DONOTUNDEF_HAVE_LARGEFILE_SUPPORT
#ifdef HAVE_LARGEFILE_SUPPORT
#undef HAVE_LARGEFILE_SUPPORT
#endif
#endif

#ifndef DONOTUNDEF_HAVE_LIBSOCKET
#ifdef HAVE_LIBSOCKET
#undef HAVE_LIBSOCKET
#endif
#endif

#ifndef DONOTUNDEF_HAVE_LISTEN
#ifdef HAVE_LISTEN
#undef HAVE_LISTEN
#endif
#endif

#ifndef DONOTUNDEF_HAVE_MKTIME
#ifdef HAVE_MKTIME
#undef HAVE_MKTIME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_PROTOTYPES
#ifdef HAVE_PROTOTYPES
#undef HAVE_PROTOTYPES
#endif
#endif

#ifndef DONOTUNDEF_HAVE_PUTENV
#ifdef HAVE_PUTENV
#undef HAVE_PUTENV
#endif
#endif

#ifndef DONOTUNDEF_HAVE_PY_SSIZE_T
#ifdef HAVE_PY_SSIZE_T
#undef HAVE_PY_SSIZE_T
#endif
#endif

#ifndef DONOTUNDEF_HAVE_RECVFROM
#ifdef HAVE_RECVFROM
#undef HAVE_RECVFROM
#endif
#endif

#ifndef DONOTUNDEF_HAVE_SENDTO
#ifdef HAVE_SENDTO
#undef HAVE_SENDTO
#endif
#endif

#ifndef DONOTUNDEF_HAVE_SETSOCKOPT
#ifdef HAVE_SETSOCKOPT
#undef HAVE_SETSOCKOPT
#endif
#endif

#ifndef DONOTUNDEF_HAVE_SETVBUF
#ifdef HAVE_SETVBUF
#undef HAVE_SETVBUF
#endif
#endif

#ifndef DONOTUNDEF_HAVE_SHUTDOWN
#ifdef HAVE_SHUTDOWN
#undef HAVE_SHUTDOWN
#endif
#endif

#ifndef DONOTUNDEF_HAVE_SOCKET
#ifdef HAVE_SOCKET
#undef HAVE_SOCKET
#endif
#endif

#ifndef DONOTUNDEF_HAVE_STRERROR
#ifdef HAVE_STRERROR
#undef HAVE_STRERROR
#endif
#endif

#ifndef DONOTUNDEF_HAVE_STRFTIME
#ifdef HAVE_STRFTIME
#undef HAVE_STRFTIME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_TEMPNAM
#ifdef HAVE_TEMPNAM
#undef HAVE_TEMPNAM
#endif
#endif

#ifndef DONOTUNDEF_HAVE_TMPFILE
#ifdef HAVE_TMPFILE
#undef HAVE_TMPFILE
#endif
#endif

#ifndef DONOTUNDEF_HAVE_TMPNAM
#ifdef HAVE_TMPNAM
#undef HAVE_TMPNAM
#endif
#endif

#ifndef DONOTUNDEF_HAVE_TZNAME
#ifdef HAVE_TZNAME
#undef HAVE_TZNAME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_UINTPTR_T
#ifdef HAVE_UINTPTR_T
#undef HAVE_UINTPTR_T
#endif
#endif

#ifndef DONOTUNDEF_HAVE_UMASK
#ifdef HAVE_UMASK
#undef HAVE_UMASK
#endif
#endif

#ifndef DONOTUNDEF_HAVE_WCSCOLL
#ifdef HAVE_WCSCOLL
#undef HAVE_WCSCOLL
#endif
#endif

#ifndef DONOTUNDEF_HAVE_WCSFTIME
#ifdef HAVE_WCSFTIME
#undef HAVE_WCSFTIME
#endif
#endif

#ifndef DONOTUNDEF_HAVE_WCSXFRM
#ifdef HAVE_WCSXFRM
#undef HAVE_WCSXFRM
#endif
#endif

#ifndef DONOTUNDEF_HAVE_WINDOWS_CONSOLE_IO
#ifdef HAVE_WINDOWS_CONSOLE_IO
#undef HAVE_WINDOWS_CONSOLE_IO
#endif
#endif

#ifndef DONOTUNDEF_HAVE_X509_VERIFY_PARAM_SET1_HOST
#ifdef HAVE_X509_VERIFY_PARAM_SET1_HOST
#undef HAVE_X509_VERIFY_PARAM_SET1_HOST
#endif
#endif

#ifndef DONOTUNDEF_HAVE_ZLIB_COPY
#ifdef HAVE_ZLIB_COPY
#undef HAVE_ZLIB_COPY
#endif
#endif

#ifndef DONOTUNDEF_LONG_BIT
#ifdef LONG_BIT
#undef LONG_BIT
#endif
#endif

#ifndef DONOTUNDEF_MS_COREDLL
#ifdef MS_COREDLL
#undef MS_COREDLL
#endif
#endif

#ifndef DONOTUNDEF_MS_WIN32
#ifdef MS_WIN32
#undef MS_WIN32
#endif
#endif

#ifndef DONOTUNDEF_MS_WIN64
#ifdef MS_WIN64
#undef MS_WIN64
#endif
#endif

#ifndef DONOTUNDEF_MS_WINDOWS
#ifdef MS_WINDOWS
#undef MS_WINDOWS
#endif
#endif

#ifndef DONOTUNDEF_MS_WINDOWS_APP
#ifdef MS_WINDOWS_APP
#undef MS_WINDOWS_APP
#endif
#endif

#ifndef DONOTUNDEF_MS_WINDOWS_DESKTOP
#ifdef MS_WINDOWS_DESKTOP
#undef MS_WINDOWS_DESKTOP
#endif
#endif

#ifndef DONOTUNDEF_MS_WINDOWS_GAMES
#ifdef MS_WINDOWS_GAMES
#undef MS_WINDOWS_GAMES
#endif
#endif

#ifndef DONOTUNDEF_MS_WINDOWS_SYSTEM
#ifdef MS_WINDOWS_SYSTEM
#undef MS_WINDOWS_SYSTEM
#endif
#endif

#ifndef DONOTUNDEF_NTDDI_VERSION
#ifdef NTDDI_VERSION
#undef NTDDI_VERSION
#endif
#endif

#ifndef DONOTUNDEF_NT_THREADS
#ifdef NT_THREADS
#undef NT_THREADS
#endif
#endif

#ifndef DONOTUNDEF_PLATFORM
#ifdef PLATFORM
#undef PLATFORM
#endif
#endif

#ifndef DONOTUNDEF_RETSIGTYPE
#ifdef RETSIGTYPE
#undef RETSIGTYPE
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_DOUBLE
#ifdef SIZEOF_DOUBLE
#undef SIZEOF_DOUBLE
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_FLOAT
#ifdef SIZEOF_FLOAT
#undef SIZEOF_FLOAT
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_FPOS_T
#ifdef SIZEOF_FPOS_T
#undef SIZEOF_FPOS_T
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_HKEY
#ifdef SIZEOF_HKEY
#undef SIZEOF_HKEY
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_INT
#ifdef SIZEOF_INT
#undef SIZEOF_INT
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_LONG
#ifdef SIZEOF_LONG
#undef SIZEOF_LONG
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_LONG_LONG
#ifdef SIZEOF_LONG_LONG
#undef SIZEOF_LONG_LONG
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_OFF_T
#ifdef SIZEOF_OFF_T
#undef SIZEOF_OFF_T
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_PID_T
#ifdef SIZEOF_PID_T
#undef SIZEOF_PID_T
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_SHORT
#ifdef SIZEOF_SHORT
#undef SIZEOF_SHORT
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_SIZE_T
#ifdef SIZEOF_SIZE_T
#undef SIZEOF_SIZE_T
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_TIME_T
#ifdef SIZEOF_TIME_T
#undef SIZEOF_TIME_T
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_VOID_P
#ifdef SIZEOF_VOID_P
#undef SIZEOF_VOID_P
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF_WCHAR_T
#ifdef SIZEOF_WCHAR_T
#undef SIZEOF_WCHAR_T
#endif
#endif

#ifndef DONOTUNDEF_SIZEOF__BOOL
#ifdef SIZEOF__BOOL
#undef SIZEOF__BOOL
#endif
#endif

#ifndef DONOTUNDEF_STDC_HEADERS
#ifdef STDC_HEADERS
#undef STDC_HEADERS
#endif
#endif

#ifndef DONOTUNDEF_USE_SOCKET
#ifdef USE_SOCKET
#undef USE_SOCKET
#endif
#endif

#ifndef DONOTUNDEF_WINVER
#ifdef WINVER
#undef WINVER
#endif
#endif

#ifndef DONOTUNDEF_WITH_DECIMAL_CONTEXTVAR
#ifdef WITH_DECIMAL_CONTEXTVAR
#undef WITH_DECIMAL_CONTEXTVAR
#endif
#endif

#ifndef DONOTUNDEF_WITH_DOC_STRINGS
#ifdef WITH_DOC_STRINGS
#undef WITH_DOC_STRINGS
#endif
#endif

#ifndef DONOTUNDEF_WITH_MIMALLOC
#ifdef WITH_MIMALLOC
#undef WITH_MIMALLOC
#endif
#endif

#ifndef DONOTUNDEF_WITH_PYMALLOC
#ifdef WITH_PYMALLOC
#undef WITH_PYMALLOC
#endif
#endif

#ifndef DONOTUNDEF_WITH_THREAD
#ifdef WITH_THREAD
#undef WITH_THREAD
#endif
#endif

#ifndef DONOTUNDEF_WORD_BIT
#ifdef WORD_BIT
#undef WORD_BIT
#endif
#endif

#ifndef DONOTUNDEF__CRT_NONSTDC_NO_DEPRECATE
#ifdef _CRT_NONSTDC_NO_DEPRECATE
#undef _CRT_NONSTDC_NO_DEPRECATE
#endif
#endif

#ifndef DONOTUNDEF__CRT_SECURE_NO_DEPRECATE
#ifdef _CRT_SECURE_NO_DEPRECATE
#undef _CRT_SECURE_NO_DEPRECATE
#endif
#endif

#ifndef DONOTUNDEF__W64
#ifdef _W64
#undef _W64
#endif
#endif

#ifndef DONOTUNDEF__WIN32_WINNT
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#endif

#endif /* PYTHON_UNDEF_H */

I tested for it and there is no problem:
Both the exe and dll include python and this file are successfully to be compiled and run. It is because that the “pyconfig.h” is only for python itself build, the macros in it can be removed safely if users write the code just include “<Python.h>”.

Now the third party package python_undef is avaliable. It is used to build it.

I created an issue and a pull request to do that.

2 Likes

See also draft PEP 743 – Add Py_OMIT_LEGACY_API to the Python C API which “adds namespaced alternatives for API without the Py_ prefix, and soft-deprecate the original names”.

For macros, I tkink that we can reverse migration as the third party package because undefine macros is easy. However, for the functions it will be difficult to do that.

Now “python_undef” only considered “pyconfig.h”, if it is needed, it will contain all of the header file of python.

Do you have examples of functions which are exported without Py or _Py prefix?

It needs much time to find. I wish that there aren’t.

Just for fun, I have applied readelf to my python3.15.a that I have built a few weeks ago.

I found many symbols from mimalloc that are prefixed with _mi or mi.

Additionally, I found the following symbols (mostly for global objects) that are not prefixed with Py or _Py:

bufferedrandom_spec
bufferedreader_spec
bufferedrwpair_spec
bufferedwriter_spec
bytesiobuf_spec
bytesio_spec
constevaluator_spec
fileio_spec
generic_spec
iobase_spec
nldecoder_spec
paramspecargs_spec
paramspeckwargs_spec
paramspec_spec
pkgcontext
PY_TIMEOUT_MAX     # I did not filter for PY with capital Y
rawiobase_spec
stringio_spec
textiobase_spec
textiowrapper_slots
textiowrapper_spec
typevar_spec
typevartuple_slots
typevartuple_spec
2 Likes

That PEP has a long list of affected APIs, but I cannot find the macro COMPILER, which the OP has mentioned. Would this macro (and others from the PC/pyconfig.h as well) be covered as well by PEP 743?

The COMPILER macro has been removed (renamed to _Py_COMPILER).

I mentioned PEP 743 to show other examples of names which can be removed if the Py_OMIT_LEGACY_API macro is defined (if the PEP is accepted :wink: ).

2 Likes

But couldn’t this be done in the Python/getcompiler.c as well? That file already contains some preprocessor logic to calculate the compiler name for other compilers. The logic in PC/pyconfig.h should work there as well (and would nicely centralize the calculation of the value)

According to @Locked-chess-official this would just leave the “configure” macros from pyconfig.h as defines that don’t follow our naming convention, and those are de facto part of public API.

1 Like

Oh, not good. I created an issue with a pull request to fix these exported symbols: either add _Py prefix or no longer export them.

Which readelf command did you use to discover these symbols? Python has make smelly which runs Tools/build/smelly.py which uses the nm command.

Ah, you have already made a PR. I didn’t think that these exported symbols were that severe.

The code was rather crude, I wasn’t actually aware of the make smelly target:

readelf -Ws --dyn-syms libpython3.15.a  | grep "GLOBAL" | grep "_Py" -v | grep " Py" -v | grep "UND" -v | cut -c 60- | sort -u

I use GNU readelf (Gentoo 2.45 p3) 2.45.0.

1 Like

In PEP743, these macros will be omitted.

Considering that as a third party package, “python_undef” is unable to make its header file that can be auto included by macro Py_OMIT_LEGACY_API. But the way that create the “Python_undef.h” may be a way that the PEP743 can take.

After the compiling of python itself finished, the “pyconfig.h” is frozen, but the environment may change, I don’t want these macros disturb the other projects’ configure.