diff --git a/Doc/c-api/config.rst b/Doc/c-api/config.rst new file mode 100644 index 000000000000000..3bfc42a5178db71 --- /dev/null +++ b/Doc/c-api/config.rst @@ -0,0 +1,201 @@ +.. highlight:: c + +.. _config-c-api: + +******************** +Python Configuration +******************** + +.. versionadded:: 3.13 + +API part of the limited C API version 3.13 to configure the Python +initialization and get the Python runtime configuration. + +See also :ref:`Python Initialization Configuration `. + + +Initialize Python +================= + +.. c:struct:: PyInitConfig + + Opaque structure to configure the Python initialization. + + +.. c:function:: PyInitConfig* PyInitConfig_Python_New(void) + + Create a new initialization configuration using :ref:`Python Configuration + ` default values. + + It must be freed by c:func:`PyInitConfig_Free`. + + Return ``NULL`` on memory allocation failure. + + +.. c:function:: PyInitConfig* PyInitConfig_Isolated_New(void) + + Similar to :c:func:`PyInitConfig_Python_New`, but use :ref:`Isolated + Configuration ` default values. + + +.. c:function:: void PyInitConfig_Free(PyInitConfig *config) + + Free memory of a initialization configuration. + + +.. c:function:: int PyInitConfig_SetInt(PyInitConfig *config, const char *key, int64_t value) + + Set an integer configuration option. + + Return 0 on success, or return -1 on error. + + +.. c:function:: int PyInitConfig_SetStr(PyInitConfig *config, const char *key, const char *value) + + Set a string configuration option from a bytes string. + + The bytes string is decoded by Py_DecodeLocale(). Preinitialize Python if + needed to ensure that encodings are properly configured. + + Return 0 on success, or return -1 on error. + +.. c:function:: int PyInitConfig_SetWStr(PyInitConfig *config, const char *key, const wchar_t *value) + + Set a string configuration option from a wide string. + + Preinitialize Python if needed. + + Return ``0`` on success, or return ``-1`` on error. + + +.. c:function:: int PyInitConfig_SetStrList(PyInitConfig *config, const char *key, size_t length, char * const *items) + + Set a string list configuration option from bytes strings. + + The bytes strings are decoded by Py_DecodeLocale(). Preinitialize Python if + needed to ensure that encodings are properly configured. + + Return ``0`` on success, or return ``-1`` on error. + + +.. c:function:: int PyInitConfig_SetWStrList(PyInitConfig *config, const char *key, size_t length, wchar_t * const *items) + + Set a string list configuration option from a wide strings. + Preinitialize Python if needed. + Return 0 on success, or return -1 on error. + + +.. c:function:: int Py_InitializeFromInitConfig(PyInitConfig *config) + + Initialize Python from the initialization configuration. + + Return ``0`` on success. + + Return ``-1`` if Python wants to exit and on error. Call + :c:func:`Py_ExitWithInitConfig` in this case. + + +.. c:function:: char* PyInitConfig_GetErrorMsg(PyInitConfig* config) + + Get the current error message. + + Return a UTF-8 string allocated by ``malloc()``. It must be released by + ``free()``. + + Return ``NULL`` on memory allocation failure. + + +.. c:function:: void Py_ExitWithInitConfig(PyInitConfig *config) + + Exit Python and free memory of a initialization configuration. + + The function does not return. + +Example +------- + +Code:: + + void init_python(void) + { + PyInitConfig *config = PyInitConfig_Python_New(); + if (config == NULL) { + printf("Init allocation error\n"); + return; + } + + if (PyInitConfig_SetInt(config, "dev_mode", 1) < 0) { + goto error; + } + + // Set a list of wide strings (argv) + wchar_t *argv[] = {L"my_program"", L"-c", L"pass"}; + if (PyInitConfig_SetWStrList(config, "argv", + Py_ARRAY_LENGTH(argv), argv) < 0) { + goto error; + } + + // Set a wide string (program_name) + if (PyInitConfig_SetWStr(config, "program_name", L"my_program") < 0) { + goto error; + } + + // Set a list of bytes strings (xoptions) + char* xoptions[] = {"faulthandler"}; + if (PyInitConfig_SetStrList(config, "xoptions", + Py_ARRAY_LENGTH(xoptions), xoptions) < 0) { + goto error; + } + + if (Py_InitializeFromInitConfig(config) < 0) { + Py_ExitWithInitConfig(config); + } + PyInitConfig_Free(config); + } + + +Get Python runtime configuration +================================ + +.. c:function:: int PyConfig_GetInt(const char *key, int64_t *value) + + Get an integer configuration option. + + Return ``0`` and set *\*value* on success. + + Raise an exception return ``-1`` on error. + + +.. c:function:: int PyConfig_GetStr(const char *key, PyObject **value) + + Get a string configuration option. + + Return ``0`` and set *\*value* on success. *\*value* can be set to a Python + :class:`str` object or to ``None``. + + Raise an exception return ``-1`` on error. + + +.. c:function:: int PyConfig_GetStrList(const char *key, PyObject **value) + + Get a string configuration option. + + Return ``0`` and set *\*value* to a Python list on success. + + Raise an exception return ``-1`` on error. + +Example +------- + +Code:: + + static int get_bytes_warning(void) + { + int64_t bytes_warning; + if (PyConfig_GetInt("bytes_warning", &bytes_warning) < 0) { + // Silently ignore the error + PyErr_Clear(); + return -1; + } + return (int)bytes_warning; + } diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index e89641f74c7491e..7a1abb9f19bcb5b 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -7,7 +7,8 @@ Initialization, Finalization, and Threads ***************************************** -See also :ref:`Python Initialization Configuration `. +See also the :ref:`Python Initialization Configuration ` +and the :ref:`Python Configuration ` .. _pre-init-safe: diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 47a8fbb2cd9c97e..a1abe5353f16359 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -27,7 +27,8 @@ There are two kinds of configuration: The :c:func:`Py_RunMain` function can be used to write a customized Python program. -See also :ref:`Initialization, Finalization, and Threads `. +See also :ref:`Initialization, Finalization, and Threads ` +and the :ref:`Python Configuration `. .. seealso:: :pep:`587` "Python Initialization Configuration". diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 811b1bd84d24174..da64e50d54169c7 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -320,6 +320,15 @@ function,PyImport_ImportModuleLevelObject,3.7,, function,PyImport_ImportModuleNoBlock,3.2,, function,PyImport_ReloadModule,3.2,, function,PyIndex_Check,3.8,, +function,PyInitConfig_Free,3.13,, +function,PyInitConfig_GetErrorMsg,3.13,, +function,PyInitConfig_Isolated_New,3.13,, +function,PyInitConfig_Python_New,3.13,, +function,PyInitConfig_SetInt,3.13,, +function,PyInitConfig_SetStr,3.13,, +function,PyInitConfig_SetStrList,3.13,, +function,PyInitConfig_SetWStr,3.13,, +function,PyInitConfig_SetWStrList,3.13,, type,PyInterpreterState,3.2,,opaque function,PyInterpreterState_Clear,3.2,, function,PyInterpreterState_Delete,3.2,, @@ -823,6 +832,7 @@ function,Py_EncodeLocale,3.7,, function,Py_EndInterpreter,3.2,, function,Py_EnterRecursiveCall,3.9,, function,Py_Exit,3.2,, +function,Py_ExitWithInitConfig,3.13,, function,Py_FatalError,3.2,, var,Py_FileSystemDefaultEncodeErrors,3.10,, var,Py_FileSystemDefaultEncoding,3.2,, @@ -846,6 +856,7 @@ var,Py_HasFileSystemDefaultEncoding,3.2,, function,Py_IncRef,3.2,, function,Py_Initialize,3.2,, function,Py_InitializeEx,3.2,, +function,Py_InitializeFromInitConfig,3.13,, function,Py_Is,3.10,, function,Py_IsFalse,3.10,, function,Py_IsFinalizing,3.13,, diff --git a/Include/Python.h b/Include/Python.h index 196751c3201e620..deb01e50c9fa681 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -88,7 +88,7 @@ #include "sliceobject.h" #include "cpython/cellobject.h" #include "iterobject.h" -#include "cpython/initconfig.h" +#include "initconfig.h" #include "pystate.h" #include "cpython/genobject.h" #include "descrobject.h" diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 87c059c521cbc92..7276e2a7eda3e25 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -1,8 +1,5 @@ -#ifndef Py_PYCORECONFIG_H -#define Py_PYCORECONFIG_H -#ifndef Py_LIMITED_API -#ifdef __cplusplus -extern "C" { +#ifndef Py_CPYTHON_INITCONFIG_H +# error "this header file must not be included directly" #endif /* --- PyStatus ----------------------------------------------- */ @@ -263,9 +260,3 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config, See also PyConfig.orig_argv. */ PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv); - -#ifdef __cplusplus -} -#endif -#endif /* !Py_LIMITED_API */ -#endif /* !Py_PYCORECONFIG_H */ diff --git a/Include/initconfig.h b/Include/initconfig.h new file mode 100644 index 000000000000000..0e7d3dd6c2b393a --- /dev/null +++ b/Include/initconfig.h @@ -0,0 +1,119 @@ +#ifndef Py_INITCONFIG_H +#define Py_INITCONFIG_H +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000 + +typedef struct PyInitConfig PyInitConfig; + +// Create a new initialization configuration. +// It must be freed by PyInitConfig_Free(). +// Return NULL on memory allocation failure. +// +// PyInitConfig_Python_New() has a Python configuration by default. +// PyInitConfig_Isolated_New() has an Isolated configuration by default. +PyAPI_FUNC(PyInitConfig*) PyInitConfig_Python_New(void); +PyAPI_FUNC(PyInitConfig*) PyInitConfig_Isolated_New(void); + +// Free memory of a initialization configuration. +PyAPI_FUNC(void) PyInitConfig_Free(PyInitConfig *config); + +// Set an integer configuration option. +// Return 0 on success, or return -1 on error. +PyAPI_FUNC(int) PyInitConfig_SetInt( + PyInitConfig *config, + const char *key, + int64_t value); + +// Set a string configuration option from a bytes string. +// +// The bytes string is decoded by Py_DecodeLocale(). Preinitialize Python if +// needed to ensure that encodings are properly configured. +// +// Return 0 on success, or return -1 on error. +PyAPI_FUNC(int) PyInitConfig_SetStr( + PyInitConfig *config, + const char *key, + const char *value); + +// Set a string configuration option from a wide string. +// Preinitialize Python if needed. +// Return 0 on success, or return -1 on error. +PyAPI_FUNC(int) PyInitConfig_SetWStr( + PyInitConfig *config, + const char *key, + const wchar_t *value); + +// Set a string list configuration option from bytes strings. +// +// The bytes strings are decoded by Py_DecodeLocale(). Preinitialize Python if +// needed to ensure that encodings are properly configured. +// +// Return 0 on success, or return -1 on error. +PyAPI_FUNC(int) PyInitConfig_SetStrList( + PyInitConfig *config, + const char *key, + size_t length, + char * const *items); + +// Set a string list configuration option from a wide strings. +// Preinitialize Python if needed. +// Return 0 on success, or return -1 on error. +PyAPI_FUNC(int) PyInitConfig_SetWStrList( + PyInitConfig *config, + const char *key, + size_t length, + wchar_t * const *items); + +// Initialize Python from the initialization configuration. +// Return 0 on success. +// Return -1 if Python wants to exit and on error +PyAPI_FUNC(int) Py_InitializeFromInitConfig(PyInitConfig *config); + +// Get the current error message. +// Return a UTF-8 string allocated by malloc(). It must be released by free(). +// Return NULL on memory allocation failure. +PyAPI_FUNC(char*) PyInitConfig_GetErrorMsg(PyInitConfig* config); + +// Exit Python and free memory of a initialization configuration. +// The function does not return. +PyAPI_FUNC(void) _Py_NO_RETURN Py_ExitWithInitConfig(PyInitConfig *config); + + +// Get an integer configuration option. +// Return 0 and set '*value' on success. +// Raise an exception return -1 on error. +PyAPI_FUNC(int) PyConfig_GetInt( + const char *key, + int64_t *value); + +// Get a string configuration option. +// Return 0 and set '*value' on success. '*value' can be set to a Python str +// object or to None. +// Raise an exception return -1 on error. +PyAPI_FUNC(int) PyConfig_GetStr( + const char *key, + PyObject **value); + +// Get a string configuration option. +// Return 0 and set '*value' to a Python list on success. +// Raise an exception return -1 on error. +PyAPI_FUNC(int) PyConfig_GetStrList( + const char *key, + PyObject **value); + +#endif // !Py_LIMITED_API + + +#ifndef Py_LIMITED_API +# define Py_CPYTHON_INITCONFIG_H +# include "cpython/initconfig.h" +# undef Py_CPYTHON_INITCONFIG_H +#endif + +#ifdef __cplusplus +} +#endif +#endif // !Py_INITCONFIG_H diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index c86988234f6a050..6aaafb69f9f8a13 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -62,6 +62,10 @@ extern int _PyWideStringList_Copy(PyWideStringList *list, extern PyStatus _PyWideStringList_Extend(PyWideStringList *list, const PyWideStringList *list2); extern PyObject* _PyWideStringList_AsList(const PyWideStringList *list); +extern PyStatus _PyWideStringList_FromBytes( + PyWideStringList *list, + Py_ssize_t length, + char * const *items); /* --- _PyArgv ---------------------------------------------------- */ diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py new file mode 100644 index 000000000000000..abcad03776ef9c8 --- /dev/null +++ b/Lib/test/test_capi/test_config.py @@ -0,0 +1,59 @@ +""" +Tests on PyConfig API (PEP 587). +""" +import os +import sys +import unittest +try: + import _testcapi +except ImportError: + _testcapi = None + + +@unittest.skipIf(_testcapi is None, 'need _testcapi') +class CAPITests(unittest.TestCase): + def check_get_func(self, get_func, wrong_type_key: str): + with self.assertRaises(TypeError): + get_func(wrong_type_key) + + with self.assertRaisesRegex(ValueError, + 'unknown config option: NONEXISTENT_KEY'): + get_func('NONEXISTENT_KEY') + + def test_config_getint(self): + config_getint = _testcapi.config_getint + # PyConfig_MEMBER_INT type + self.assertEqual(config_getint('verbose'), sys.flags.verbose) + + # PyConfig_MEMBER_UINT type + self.assertEqual(config_getint('isolated'), sys.flags.isolated) + + # PyConfig_MEMBER_ULONG type + hash_seed = config_getint('hash_seed') + self.assertIsInstance(hash_seed, int) + self.assertGreaterEqual(hash_seed, 0) + + # platlibdir is a str + self.check_get_func(config_getint, 'platlibdir') + + def test_config_getstr(self): + config_getstr = _testcapi.config_getstr + # PyConfig_MEMBER_WSTR type + self.assertEqual(config_getstr('platlibdir'), sys.platlibdir) + if 'PYTHONDUMPREFSFILE' not in os.environ: + # PyConfig_MEMBER_WSTR_OPT type + self.assertIsNone(config_getstr('dump_refs_file')) + + # verbose is an int + self.check_get_func(config_getstr, 'verbose') + + def test_config_getstrlist(self): + config_getstrlist = _testcapi.config_getstrlist + self.assertEqual(config_getstrlist('orig_argv'), sys.orig_argv) + + # verbose is an int + self.check_get_func(config_getstrlist, 'verbose') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index d2d6c1b61e46f06..aca5ad6db59dc99 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -390,6 +390,14 @@ def test_ucnhash_capi_reset(self): out, err = self.run_embedded_interpreter("test_repeated_init_exec", code) self.assertEqual(out, '9\n' * INIT_LOOPS) + +def config_dev_mode(preconfig, config): + preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG + config['dev_mode'] = 1 + config['warnoptions'] = ['default'] + config['faulthandler'] = 1 + + class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): maxDiff = 4096 UTF8_MODE_ERRORS = ('surrogatepass' if MS_WINDOWS else 'surrogateescape') @@ -509,7 +517,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'check_hash_pycs_mode': 'default', 'pathconfig_warnings': 1, '_init_main': 1, - 'use_frozen_modules': not support.Py_DEBUG, + 'use_frozen_modules': int(not support.Py_DEBUG), 'safe_path': 0, '_is_python_build': IGNORE_CONFIG, } @@ -992,33 +1000,26 @@ def test_init_env_dev_mode_alloc(self): api=API_COMPAT) def test_init_dev_mode(self): - preconfig = { - 'allocator': PYMEM_ALLOCATOR_DEBUG, - } + preconfig = {} config = { - 'faulthandler': 1, 'dev_mode': 1, - 'warnoptions': ['default'], } + config_dev_mode(preconfig, config) self.check_all_configs("test_init_dev_mode", config, preconfig, api=API_PYTHON) def test_preinit_parse_argv(self): # Pre-initialize implicitly using argv: make sure that -X dev # is used to configure the allocation in preinitialization - preconfig = { - 'allocator': PYMEM_ALLOCATOR_DEBUG, - } + preconfig = {} config = { 'argv': ['script.py'], 'orig_argv': ['python3', '-X', 'dev', '-P', 'script.py'], 'run_filename': os.path.abspath('script.py'), - 'dev_mode': 1, - 'faulthandler': 1, - 'warnoptions': ['default'], 'xoptions': ['dev'], 'safe_path': 1, } + config_dev_mode(preconfig, config) self.check_all_configs("test_preinit_parse_argv", config, preconfig, api=API_PYTHON) @@ -1619,16 +1620,15 @@ def test_init_warnoptions(self): 'ignore:::PySys_AddWarnOption2', # PySys_AddWarnOption() 'ignore:::PyConfig_BeforeRead', # PyConfig.warnoptions 'ignore:::PyConfig_AfterRead'] # PyWideStringList_Append() - preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG) + preconfig = {} config = { - 'dev_mode': 1, - 'faulthandler': 1, 'bytes_warning': 1, - 'warnoptions': warnoptions, 'orig_argv': ['python3', '-Wignore:::cmdline1', '-Wignore:::cmdline2'], } + config_dev_mode(preconfig, config) + config['warnoptions'] = warnoptions self.check_all_configs("test_init_warnoptions", config, preconfig, api=API_PYTHON) @@ -1641,6 +1641,21 @@ def test_init_set_config(self): self.check_all_configs("test_init_set_config", config, api=API_ISOLATED) + def test_initconfig_api(self): + preconfig = {} + config = { + 'dev_mode': 1, + 'pycache_prefix': 'conf_pycache_prefix', + 'argv': ['-c'], + 'orig_argv': ['./_testembed', '-c', 'pass'], + 'run_command': 'pass\n', + 'xoptions': ['faulthandler'], + 'faulthandler': 1, + } + config_dev_mode(preconfig, config) + self.check_all_configs("test_initconfig_api", config, preconfig, + api=API_PYTHON) + def test_get_argc_argv(self): self.run_embedded_interpreter("test_get_argc_argv") # ignore output diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 4976ac3642bbe46..755cc326e1e7dbd 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -350,6 +350,15 @@ def test_windows_feature_macros(self): "PyImport_ImportModuleNoBlock", "PyImport_ReloadModule", "PyIndex_Check", + "PyInitConfig_Free", + "PyInitConfig_GetErrorMsg", + "PyInitConfig_Isolated_New", + "PyInitConfig_Python_New", + "PyInitConfig_SetInt", + "PyInitConfig_SetStr", + "PyInitConfig_SetStrList", + "PyInitConfig_SetWStr", + "PyInitConfig_SetWStrList", "PyInterpreterState_Clear", "PyInterpreterState_Delete", "PyInterpreterState_Get", @@ -833,6 +842,7 @@ def test_windows_feature_macros(self): "Py_EndInterpreter", "Py_EnterRecursiveCall", "Py_Exit", + "Py_ExitWithInitConfig", "Py_FatalError", "Py_FileSystemDefaultEncodeErrors", "Py_FileSystemDefaultEncoding", @@ -857,6 +867,7 @@ def test_windows_feature_macros(self): "Py_IncRef", "Py_Initialize", "Py_InitializeEx", + "Py_InitializeFromInitConfig", "Py_Is", "Py_IsFalse", "Py_IsFinalizing", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 22b25dd0ec141fd..b5bcfa58b9ad3ca 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2481,3 +2481,25 @@ [function._Py_SetRefcnt] added = '3.13' abi_only = true +[function.PyInitConfig_Python_New] + added = '3.13' +[function.PyInitConfig_Isolated_New] + added = '3.13' +[function.PyInitConfig_Free] + added = '3.13' +[function.PyInitConfig_SetInt] + added = '3.13' +[function.PyInitConfig_SetStr] + added = '3.13' +[function.PyInitConfig_SetStrList] + added = '3.13' +[function.PyInitConfig_SetWStr] + added = '3.13' +[function.PyInitConfig_SetWStrList] + added = '3.13' +[function.PyInitConfig_GetErrorMsg] + added = '3.13' +[function.Py_InitializeFromInitConfig] + added = '3.13' +[function.Py_ExitWithInitConfig] + added = '3.13' diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 54650ea9c1d4ac1..504192754134f2a 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -159,7 +159,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/config.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/config.c b/Modules/_testcapi/config.c new file mode 100644 index 000000000000000..3dbc02d10e76f2c --- /dev/null +++ b/Modules/_testcapi/config.c @@ -0,0 +1,67 @@ +#define Py_LIMITED_API 0x030d0000 // 3.13 +#include "parts.h" + +static PyObject * +_testcapi_config_getint(PyObject *module, PyObject *name_obj) +{ + const char *name; + if (PyArg_Parse(name_obj, "s", &name) < 0) { + return NULL; + } + + int64_t value; + if (PyConfig_GetInt(name, &value) < 0) { + return NULL; + } + Py_BUILD_ASSERT(sizeof(long long) >= sizeof(int64_t)); + return PyLong_FromLongLong(value); +} + + +static PyObject * +_testcapi_config_getstr(PyObject *module, PyObject *name_obj) +{ + const char *name; + if (PyArg_Parse(name_obj, "s", &name) < 0) { + return NULL; + } + + PyObject *value; + if (PyConfig_GetStr(name, &value) < 0) { + return NULL; + } + return value; +} + + +static PyObject * +_testcapi_config_getstrlist(PyObject *module, PyObject *name_obj) +{ + const char *name; + if (PyArg_Parse(name_obj, "s", &name) < 0) { + return NULL; + } + + PyObject *value; + if (PyConfig_GetStrList(name, &value) < 0) { + return NULL; + } + return value; +} + + +static PyMethodDef test_methods[] = { + {"config_getint", _testcapi_config_getint, METH_O}, + {"config_getstr", _testcapi_config_getstr, METH_O}, + {"config_getstrlist", _testcapi_config_getstrlist, METH_O}, + {NULL} +}; + +int _PyTestCapi_Init_Config(PyObject *mod) +{ + if (PyModule_AddFunctions(mod, test_methods) < 0) { + return -1; + } + + return 0; +} diff --git a/Modules/_testcapi/gc.c b/Modules/_testcapi/gc.c index 829200ad12cd3ce..1687a67dbcf368f 100644 --- a/Modules/_testcapi/gc.c +++ b/Modules/_testcapi/gc.c @@ -325,9 +325,6 @@ int _PyTestCapi_Init_GC(PyObject *mod) if (PyModule_AddFunctions(mod, test_methods) < 0) { return -1; } - if (PyModule_AddFunctions(mod, test_methods) < 0) { - return -1; - } PyObject *ObjExtraData_Type = PyType_FromModuleAndSpec( mod, &ObjExtraData_TypeSpec, NULL); diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 29817edd69b1348..83d93ae565d4fa9 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -62,5 +62,6 @@ int _PyTestCapi_Init_Hash(PyObject *module); int _PyTestCapi_Init_VectorcallLimited(PyObject *module); int _PyTestCapi_Init_HeaptypeRelative(PyObject *module); +int _PyTestCapi_Init_Config(PyObject *mod); #endif // Py_TESTCAPI_PARTS_H diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9fdd67093338e45..2bfb54b81a8d522 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4005,6 +4005,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Hash(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Config(m) < 0) { + return NULL; + } PyState_AddModule(m, &_testcapimodule); return m; diff --git a/PC/python3dll.c b/PC/python3dll.c index 07aa84c91f9fc7f..43a8439c28c4bee 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -48,6 +48,7 @@ EXPORT_FUNC(Py_EncodeLocale) EXPORT_FUNC(Py_EndInterpreter) EXPORT_FUNC(Py_EnterRecursiveCall) EXPORT_FUNC(Py_Exit) +EXPORT_FUNC(Py_ExitWithInitConfig) EXPORT_FUNC(Py_FatalError) EXPORT_FUNC(Py_Finalize) EXPORT_FUNC(Py_FinalizeEx) @@ -68,6 +69,7 @@ EXPORT_FUNC(Py_GetVersion) EXPORT_FUNC(Py_IncRef) EXPORT_FUNC(Py_Initialize) EXPORT_FUNC(Py_InitializeEx) +EXPORT_FUNC(Py_InitializeFromInitConfig) EXPORT_FUNC(Py_Is) EXPORT_FUNC(Py_IsFalse) EXPORT_FUNC(Py_IsFinalizing) @@ -312,6 +314,15 @@ EXPORT_FUNC(PyImport_ImportModuleLevelObject) EXPORT_FUNC(PyImport_ImportModuleNoBlock) EXPORT_FUNC(PyImport_ReloadModule) EXPORT_FUNC(PyIndex_Check) +EXPORT_FUNC(PyInitConfig_Free) +EXPORT_FUNC(PyInitConfig_GetErrorMsg) +EXPORT_FUNC(PyInitConfig_Isolated_New) +EXPORT_FUNC(PyInitConfig_Python_New) +EXPORT_FUNC(PyInitConfig_SetInt) +EXPORT_FUNC(PyInitConfig_SetStr) +EXPORT_FUNC(PyInitConfig_SetStrList) +EXPORT_FUNC(PyInitConfig_SetWStr) +EXPORT_FUNC(PyInitConfig_SetWStrList) EXPORT_FUNC(PyInterpreterState_Clear) EXPORT_FUNC(PyInterpreterState_Delete) EXPORT_FUNC(PyInterpreterState_Get) diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 1c15541d3ec735f..51c4ca74ac83c45 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -127,6 +127,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 6059959bb9a040f..1f43a6864778535 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -108,6 +108,9 @@ Source Files + + Source Files + diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 1f9aa4b3d449a10..88eb0165e0b1cb1 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1786,6 +1786,63 @@ static int test_init_set_config(void) } +static int test_initconfig_api(void) +{ + PyInitConfig *config = PyInitConfig_Python_New(); + if (config == NULL) { + printf("Init allocation error\n"); + return 1; + } + + if (PyInitConfig_SetInt(config, "dev_mode", 1) < 0) { + goto error; + } + + // Set a list of wide strings (argv) + wchar_t *argv[] = {PROGRAM_NAME, L"-c", L"pass"}; + if (PyInitConfig_SetWStrList(config, "argv", + Py_ARRAY_LENGTH(argv), argv) < 0) { + goto error; + } + + if (PyInitConfig_SetInt(config, "hash_seed", 10) < 0) { + goto error; + } + + // Set a wide string (program_name) + if (PyInitConfig_SetWStr(config, "program_name", PROGRAM_NAME) < 0) { + goto error; + } + + // Set a bytes string (pycache_prefix) + if (PyInitConfig_SetStr(config, "pycache_prefix", + "conf_pycache_prefix") < 0) { + goto error; + } + + // Set a list of bytes strings (xoptions) + char* xoptions[] = {"faulthandler"}; + if (PyInitConfig_SetStrList(config, "xoptions", + Py_ARRAY_LENGTH(xoptions), xoptions) < 0) { + goto error; + } + + + if (Py_InitializeFromInitConfig(config) < 0) { + Py_ExitWithInitConfig(config); + } + PyInitConfig_Free(config); + + dump_config(); + Py_Finalize(); + return 0; + +error: + printf("Init failed:\n"); + Py_ExitWithInitConfig(config); +} + + static void configure_init_main(PyConfig *config) { wchar_t* argv[] = { @@ -2188,6 +2245,7 @@ static struct TestCase TestCases[] = { {"test_init_is_python_build", test_init_is_python_build}, {"test_init_warnoptions", test_init_warnoptions}, {"test_init_set_config", test_init_set_config}, + {"test_initconfig_api", test_initconfig_api}, {"test_run_main", test_run_main}, {"test_run_main_loop", test_run_main_loop}, {"test_get_argc_argv", test_get_argc_argv}, diff --git a/Python/initconfig.c b/Python/initconfig.c index d7f3195ed5fcf0f..4510dae9bacc1a7 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -24,6 +24,16 @@ # endif #endif +// Forward declarations +typedef struct PyConfigSpec PyConfigSpec; + +static int +config_get_str(const PyConfig *config, const PyConfigSpec *spec, + PyObject **value, int use_sys); +static int +config_get_str_list(const PyConfig *config, const PyConfigSpec *spec, + PyObject **value, int use_sys); + /* --- PyConfig spec ---------------------------------------------- */ typedef enum { @@ -36,11 +46,11 @@ typedef enum { PyConfig_MEMBER_WSTR_LIST = 12, } PyConfigMemberType; -typedef struct { +struct PyConfigSpec { const char *name; size_t offset; PyConfigMemberType type; -} PyConfigSpec; +}; #define SPEC(MEMBER, TYPE) \ {#MEMBER, offsetof(PyConfig, MEMBER), PyConfig_MEMBER_##TYPE} @@ -983,7 +993,7 @@ config_set_bytes_string(PyConfig *config, wchar_t **config_str, /* Decode str using Py_DecodeLocale() and set the result into *config_str. - Pre-initialize Python if needed to ensure that encodings are properly + Preinitialize Python if needed to ensure that encodings are properly configured. */ PyStatus PyConfig_SetBytesString(PyConfig *config, wchar_t **config_str, @@ -993,6 +1003,13 @@ PyConfig_SetBytesString(PyConfig *config, wchar_t **config_str, } +static inline void* +config_spec_get_member(const PyConfigSpec *spec, const PyConfig *config) +{ + return (char *)config + spec->offset; +} + + PyStatus _PyConfig_Copy(PyConfig *config, const PyConfig *config2) { @@ -1001,18 +1018,18 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) PyStatus status; const PyConfigSpec *spec = PYCONFIG_SPEC; for (; spec->name != NULL; spec++) { - char *member = (char *)config + spec->offset; - char *member2 = (char *)config2 + spec->offset; + char *member = config_spec_get_member(spec, config); + const char *member2 = config_spec_get_member(spec, (PyConfig*)config2); switch (spec->type) { case PyConfig_MEMBER_INT: case PyConfig_MEMBER_UINT: { - *(int*)member = *(int*)member2; + *(int*)member = *(const int*)member2; break; } case PyConfig_MEMBER_ULONG: { - *(unsigned long*)member = *(unsigned long*)member2; + *(unsigned long*)member = *(const unsigned long*)member2; break; } case PyConfig_MEMBER_WSTR: @@ -1041,6 +1058,47 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) } +static PyObject* +_PyConfig_Get(const PyConfig *config, const PyConfigSpec *spec) +{ + switch (spec->type) { + case PyConfig_MEMBER_INT: + case PyConfig_MEMBER_UINT: + { + const char *member = config_spec_get_member(spec, config); + int value = *(const int*)member; + return PyLong_FromLong(value); + } + case PyConfig_MEMBER_ULONG: + { + const char *member = config_spec_get_member(spec, config); + unsigned long value = *(const unsigned long*)member; + return PyLong_FromUnsignedLong(value); + } + case PyConfig_MEMBER_WSTR: + case PyConfig_MEMBER_WSTR_OPT: + { + PyObject *obj; + if (config_get_str(config, spec, &obj, 0) < 0) { + return NULL; + } + return obj; + } + case PyConfig_MEMBER_WSTR_LIST: + { + PyObject *obj; + if (config_get_str_list(config, spec, &obj, 0) < 0) { + return NULL; + } + return obj; + } + default: + break; + } + Py_UNREACHABLE(); +} + + PyObject * _PyConfig_AsDict(const PyConfig *config) { @@ -1051,44 +1109,7 @@ _PyConfig_AsDict(const PyConfig *config) const PyConfigSpec *spec = PYCONFIG_SPEC; for (; spec->name != NULL; spec++) { - char *member = (char *)config + spec->offset; - PyObject *obj; - switch (spec->type) { - case PyConfig_MEMBER_INT: - case PyConfig_MEMBER_UINT: - { - int value = *(int*)member; - obj = PyLong_FromLong(value); - break; - } - case PyConfig_MEMBER_ULONG: - { - unsigned long value = *(unsigned long*)member; - obj = PyLong_FromUnsignedLong(value); - break; - } - case PyConfig_MEMBER_WSTR: - case PyConfig_MEMBER_WSTR_OPT: - { - const wchar_t *wstr = *(const wchar_t**)member; - if (wstr != NULL) { - obj = PyUnicode_FromWideChar(wstr, -1); - } - else { - obj = Py_NewRef(Py_None); - } - break; - } - case PyConfig_MEMBER_WSTR_LIST: - { - const PyWideStringList *list = (const PyWideStringList*)member; - obj = _PyWideStringList_AsList(list); - break; - } - default: - Py_UNREACHABLE(); - } - + PyObject *obj = _PyConfig_Get(config, spec); if (obj == NULL) { Py_DECREF(dict); return NULL; @@ -1281,7 +1302,7 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) const PyConfigSpec *spec = PYCONFIG_SPEC; for (; spec->name != NULL; spec++) { - char *member = (char *)config + spec->offset; + char *member = config_spec_get_member(spec, config); switch (spec->type) { case PyConfig_MEMBER_INT: if (config_dict_get_int(dict, spec->name, (int*)member) < 0) { @@ -3218,3 +3239,549 @@ _Py_DumpPathConfig(PyThreadState *tstate) _PyErr_SetRaisedException(tstate, exc); } + + +// --- PyInitConfig API --------------------------------------------------- + +struct PyInitConfig { + PyConfig config; + PyStatus status; + const char *err_msg; +}; + +static PyInitConfig* +initconfig_alloc(void) +{ + const size_t size = sizeof(PyInitConfig); + PyInitConfig *config = malloc(size); + if (config == NULL) { + memset(config, 0, size); + return NULL; + } + return config; +} + + +PyInitConfig* +PyInitConfig_Python_New(void) +{ + PyInitConfig *config = initconfig_alloc(); + if (config == NULL) { + return NULL; + } + PyConfig_InitPythonConfig(&config->config); + return config; +} + + +PyInitConfig* +PyInitConfig_Isolated_New(void) +{ + PyInitConfig *config = initconfig_alloc(); + if (config == NULL) { + return NULL; + } + PyConfig_InitIsolatedConfig(&config->config); + return config; +} + + +void +PyInitConfig_Free(PyInitConfig *config) +{ + free(config); +} + + +static void +initconfig_error(PyInitConfig *config, const char *err_msg) +{ + assert(err_msg != NULL); + config->err_msg = err_msg; +} + + +static void +initconfig_status_error(PyInitConfig *config) +{ + // FIXME: include status.func + if (PyStatus_IsExit(config->status)) { + // FIXME: include status.exitcode + config->err_msg = "exit"; + } + else { + assert(PyStatus_IsError(config->status)); + config->err_msg = config->status.err_msg; + } +} + + +static const PyConfigSpec* +config_get_spec(const char *key) +{ + const PyConfigSpec *spec = PYCONFIG_SPEC; + for (; spec->name != NULL; spec++) { + if (strcmp(key, spec->name) == 0) { + return spec; + } + } + return NULL; +} + + +static const PyConfigSpec* +initconfig_prepare_set(PyInitConfig *config, const char *key, int preconfig) +{ + if (config == NULL) { + initconfig_error(config, "config argument is NULL"); + return NULL; + } + if (key == NULL) { + initconfig_error(config, "key argument is NULL"); + return NULL; + } + + const PyConfigSpec *spec = config_get_spec(key); + if (spec == NULL) { + initconfig_error(config, "unknown config key"); + return NULL; + } + + if (preconfig) { + config->status = _Py_PreInitializeFromConfig(&config->config, NULL); + if (_PyStatus_EXCEPTION(config->status)) { + initconfig_status_error(config); + return NULL; + } + } + + return spec; +} + + +int +PyInitConfig_SetInt(PyInitConfig *config, const char *key, int64_t value) +{ + const PyConfigSpec *spec = initconfig_prepare_set(config, key, 0); + if (spec == NULL) { + return -1; + } + + if (spec->type == PyConfig_MEMBER_INT) { + if (value < (int64_t)INT_MIN || INT_MAX < (int64_t)value) { + initconfig_error(config, + "config option value is out of int range"); + return -1; + } + int int_value = (int)value; + + int *member = config_spec_get_member(spec, &config->config); + *member = int_value; + } + else if (spec->type == PyConfig_MEMBER_UINT) { + if (value < 0 || UINT_MAX < (int64_t)value) { + initconfig_error(config, + "config option value is out of unsigned int range"); + return -1; + } + int int_value = (int)value; + + int *member = config_spec_get_member(spec, &config->config); + *member = int_value; + } + else if (spec->type == PyConfig_MEMBER_ULONG) { + if (value < 0 || ULONG_MAX < (int64_t)value) { + initconfig_error(config, + "config option value is out of unsigned long range"); + return -1; + } + unsigned long ulong_value = (unsigned long)value; + + unsigned long *member = config_spec_get_member(spec, &config->config); + *member = ulong_value; + } + else { + initconfig_error(config, "config option type is not int"); + return -1; + } + return 0; +} + + +int +PyInitConfig_SetWStr(PyInitConfig *config, const char *key, + const wchar_t *value) +{ + const PyConfigSpec *spec = initconfig_prepare_set(config, key, 1); + if (spec == NULL) { + return -1; + } + + if (spec->type != PyConfig_MEMBER_WSTR + && spec->type != PyConfig_MEMBER_WSTR_OPT) { + initconfig_error(config, "config option type is not string"); + return -1; + } + + if (value == NULL && spec->type != PyConfig_MEMBER_WSTR_OPT) { + initconfig_error(config, "config option string cannot be NULL"); + } + + wchar_t **member = config_spec_get_member(spec, &config->config); + config->status = PyConfig_SetString(&config->config, member, value); + + if (_PyStatus_EXCEPTION(config->status)) { + initconfig_status_error(config); + return -1; + } + return 0; +} + + +int +PyInitConfig_SetStr(PyInitConfig *config, const char *key, + const char* value) +{ + const PyConfigSpec *spec = initconfig_prepare_set(config, key, 1); + if (spec == NULL) { + return -1; + } + + if (spec->type != PyConfig_MEMBER_WSTR + && spec->type != PyConfig_MEMBER_WSTR_OPT) { + initconfig_error(config, "config option type is not string"); + return -1; + } + + if (value == NULL && spec->type != PyConfig_MEMBER_WSTR_OPT) { + initconfig_error(config, "config option string cannot be NULL"); + } + + wchar_t **member = config_spec_get_member(spec, &config->config); + config->status = PyConfig_SetBytesString(&config->config, member, value); + + if (_PyStatus_EXCEPTION(config->status)) { + initconfig_status_error(config); + return -1; + } + return 0; +} + + +int +PyInitConfig_SetWStrList(PyInitConfig *config, const char *key, + size_t length, wchar_t * const *items) +{ + const PyConfigSpec *spec = initconfig_prepare_set(config, key, 1); + if (spec == NULL) { + return -1; + } + + if (spec->type != PyConfig_MEMBER_WSTR_LIST) { + initconfig_error(config, "config option type is not strings list"); + return -1; + } + + PyWideStringList *member = config_spec_get_member(spec, &config->config); + config->status = PyConfig_SetWideStringList(&config->config, member, + length, (wchar_t**)items); + if (_PyStatus_EXCEPTION(config->status)) { + initconfig_status_error(config); + return -1; + } + return 0; +} + + +int +PyInitConfig_SetStrList(PyInitConfig *config, const char *key, + size_t length, char * const *items) +{ + const PyConfigSpec *spec = initconfig_prepare_set(config, key, 1); + if (spec == NULL) { + return -1; + } + + if (spec->type != PyConfig_MEMBER_WSTR_LIST) { + initconfig_error(config, "config option type is not strings list"); + return -1; + } + PyWideStringList *list = config_spec_get_member(spec, &config->config); + + config->status = _PyWideStringList_FromBytes(list, length, items); + if (_PyStatus_EXCEPTION(config->status)) { + initconfig_status_error(config); + return -1; + } + return 0; +} + + +int +Py_InitializeFromInitConfig(PyInitConfig *config) +{ + config->status = Py_InitializeFromConfig(&config->config); + if (_PyStatus_EXCEPTION(config->status)) { + initconfig_status_error(config); + return -1; + } + return 0; +} + + +static void +initconfig_print_error(PyInitConfig *config) +{ + const char *err_msg = config->err_msg; + if (!err_msg) { + err_msg = "success"; + } + printf("Initialization configuration error: %s\n", err_msg); +} + + +void +Py_ExitWithInitConfig(PyInitConfig *config) +{ + initconfig_print_error(config); + + // FIXME: handle initconfig_error() case + PyStatus status = config->status; + PyInitConfig_Free(config); + + if (_PyStatus_EXCEPTION(status)) { + Py_ExitStatusException(status); + } + else { + exit(1); + } +} + + +char* +PyInitConfig_GetErrorMsg(PyInitConfig* config) +{ + const char *err_msg = config->err_msg; + if (!err_msg) { + err_msg = "success"; + } + + size_t size = strlen(err_msg) + 1; + char *copy = malloc(size); + if (copy == NULL) { + return NULL; + } + memcpy(copy, err_msg, size); + return copy; +} + + +static inline const PyConfigSpec* +config_prepare_get(const char *key) +{ + const PyConfigSpec *spec = config_get_spec(key); + if (spec == NULL) { + PyErr_Format(PyExc_ValueError, "unknown config option: %s", key); + return NULL; + } + return spec; +} + + +int +PyConfig_GetInt(const char *key, int64_t *value) +{ + const PyConfigSpec *spec = config_prepare_get(key); + if (spec == NULL) { + return -1; + } + const PyConfig *config = _Py_GetConfig(); + int *int_member = config_spec_get_member(spec, config); + + if (spec->type == PyConfig_MEMBER_INT) { + *value = *int_member; + } + else if (spec->type == PyConfig_MEMBER_UINT) { + *value = *int_member; + } + else if (spec->type == PyConfig_MEMBER_ULONG) { + unsigned long *ulong_member = (unsigned long *)int_member; + unsigned long ulong_value = *ulong_member; +#if ULONG_MAX > INT64_MAX + if (ulong_value > (unsigned long)INT64_MAX) { + PyErr_Format(PyExc_OverflowError, + "config option %s value doesn't fit into int64_t", + key); + return -1; + } +#endif + *value = (int64_t)ulong_value; + } + else { + PyErr_Format(PyExc_TypeError, "config option %s is not an int", key); + return -1; + } + return 0; +} + + +static int +config_get_str(const PyConfig *config, const PyConfigSpec *spec, + PyObject **value, int use_sys) +{ + if (use_sys && !_PyRuntime.initialized) { + use_sys = 0; + } + if (use_sys) { + const char* sys_attrs[] = { + "_base_executable", + "_stdlib_dir", + "base_exec_prefix", + "base_prefix", + "exec_prefix", + "executable", + "platlibdir", + "prefix", + "pycache_prefix", + NULL, + }; + const char *name = spec->name; + for (const char **attr = sys_attrs; *attr != NULL; attr++) { + if (strcmp(name, *attr) == 0) { + *value = Py_XNewRef(PySys_GetObject(name)); + if (*value == NULL) { + return -1; + } + return 0; + } + } + } + + wchar_t **member = config_spec_get_member(spec, config); + if (spec->type != PyConfig_MEMBER_WSTR + && spec->type != PyConfig_MEMBER_WSTR_OPT) + { + PyErr_Format(PyExc_TypeError, "config option %s is not a string", + spec->name); + return -1; + } + + if (*member != NULL) { + *value = PyUnicode_FromWideChar(*member, -1); + if (*value == NULL) { + return -1; + } + } + else { + *value = Py_NewRef(Py_None); + } + return 0; +} + +int +PyConfig_GetStr(const char *key, PyObject **value) +{ + const PyConfigSpec *spec = config_prepare_get(key); + if (spec == NULL) { + return -1; + } + const PyConfig *config = _Py_GetConfig(); + return config_get_str(config, spec, value, 1); +} + +static PyObject* +config_dict_as_str_list(PyObject *dict) +{ + PyObject *list = PyList_New(0); + if (list == NULL) { + return NULL; + } + + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(dict, &pos, &key, &value)) { + PyObject *item; + if (value != Py_True) { + item = PyUnicode_FromFormat("%S=%S", key, value); + } + else { + item = Py_NewRef(key); + } + if (item == NULL) { + goto error; + } + int res = PyList_Append(list, item); + Py_DECREF(item); + if (res < 0) { + goto error; + } + } + + return list; + +error: + Py_DECREF(list); + return NULL; +} + + +static int +config_get_str_list(const PyConfig *config, const PyConfigSpec *spec, + PyObject **value, int use_sys) +{ + if (use_sys && !_PyRuntime.initialized) { + use_sys = 0; + } + if (use_sys) { + const char* sys_attrs[] = { + "argv", + "module_search_paths", + "orig_argv", + "warnoptions", + "xoptions", + NULL, + }; + const char *name = spec->name; + for (const char **attr = sys_attrs; *attr != NULL; attr++) { + if (strcmp(name, *attr) == 0) { + if (strcmp(name, "module_search_paths") == 0) { + *value = Py_XNewRef(PySys_GetObject("path")); + } + else if (strcmp(name, "xoptions") == 0) { + *value = config_dict_as_str_list(PySys_GetObject("_xoptions")); + } + else { + *value = Py_XNewRef(PySys_GetObject(name)); + } + if (*value == NULL) { + return -1; + } + return 0; + } + } + } + + if (spec->type != PyConfig_MEMBER_WSTR_LIST) { + PyErr_Format(PyExc_TypeError, + "config option %s is not a strings list", spec->name); + return -1; + } + + const PyWideStringList *list = config_spec_get_member(spec, config); + *value = _PyWideStringList_AsList(list); + if (*value == NULL) { + return -1; + } + return 0; +} + +int +PyConfig_GetStrList(const char *key, PyObject **value) +{ + const PyConfigSpec *spec = config_prepare_get(key); + if (spec == NULL) { + return -1; + } + const PyConfig *config = _Py_GetConfig(); + return config_get_str_list(config, spec, value, 1); +} diff --git a/Python/preconfig.c b/Python/preconfig.c index 5b26c75de8b3a00..ebcfa28323f74b4 100644 --- a/Python/preconfig.c +++ b/Python/preconfig.c @@ -74,33 +74,47 @@ _Py_COMP_DIAG_POP /* --- _PyArgv ---------------------------------------------------- */ -/* Decode bytes_argv using Py_DecodeLocale() */ PyStatus -_PyArgv_AsWstrList(const _PyArgv *args, PyWideStringList *list) +_PyWideStringList_FromBytes(PyWideStringList *list, + Py_ssize_t length, char * const *items) { PyWideStringList wargv = _PyWideStringList_INIT; - if (args->use_bytes_argv) { - size_t size = sizeof(wchar_t*) * args->argc; - wargv.items = (wchar_t **)PyMem_RawMalloc(size); - if (wargv.items == NULL) { - return _PyStatus_NO_MEMORY(); - } + size_t size = sizeof(wchar_t*) * length; + wargv.items = (wchar_t **)PyMem_RawMalloc(size); + if (wargv.items == NULL) { + return _PyStatus_NO_MEMORY(); + } - for (Py_ssize_t i = 0; i < args->argc; i++) { - size_t len; - wchar_t *arg = Py_DecodeLocale(args->bytes_argv[i], &len); - if (arg == NULL) { - _PyWideStringList_Clear(&wargv); - return DECODE_LOCALE_ERR("command line arguments", len); - } - wargv.items[i] = arg; - wargv.length++; + for (Py_ssize_t i = 0; i < length; i++) { + size_t len; + wchar_t *arg = Py_DecodeLocale(items[i], &len); + if (arg == NULL) { + _PyWideStringList_Clear(&wargv); + return DECODE_LOCALE_ERR("command line arguments", len); } + wargv.items[i] = arg; + wargv.length++; + } - _PyWideStringList_Clear(list); - *list = wargv; + _PyWideStringList_Clear(list); + *list = wargv; + return _PyStatus_OK(); +} + +/* Decode bytes_argv using Py_DecodeLocale() */ +PyStatus +_PyArgv_AsWstrList(const _PyArgv *args, PyWideStringList *list) +{ + if (args->use_bytes_argv) { + PyStatus status; + status = _PyWideStringList_FromBytes(list, + args->argc, args->bytes_argv); + if (_PyStatus_EXCEPTION(status)) { + return status; + } } else { + PyWideStringList wargv = _PyWideStringList_INIT; wargv.length = args->argc; wargv.items = (wchar_t **)args->wchar_argv; if (_PyWideStringList_Copy(list, &wargv) < 0) {