Skip to content

Commit fe7717c

Browse files
authored
gh-158080: Py_InitializeFromConfig() no longer exits Python (#158085)
If a command line option wants to exit Python, move this option processing from PyConfig_Read() to Py_Main(). So Py_InitializeFromInitConfig() and Py_InitializeFromConfig() can no longer return an exit code. Deprecate PyInitConfig_GetExitCode(), PyStatus_Exit(), and PyStatus_IsExit() functions, and PyStatus.exitcode member. Renumber getopt.c long options to start at 1, instead of 0.
1 parent b954645 commit fe7717c

14 files changed

Lines changed: 190 additions & 104 deletions

File tree

‎Doc/c-api/init_config.rst‎

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,14 @@ Error Handling
116116
117117
Get the *config* exit code.
118118
119-
* Set *\*exitcode* and return ``1`` if *config* has an exit code set.
120-
* Return ``0`` if *config* has no exit code set.
119+
Return ``0`` and leave *\*exitcode* unchanged.
121120
122-
Only the ``Py_InitializeFromInitConfig()`` function can set an exit
123-
code if the ``parse_argv`` option is non-zero.
121+
In Python 3.15, :c:func:`Py_InitializeFromInitConfig` sets an exit code if a
122+
command line option wants to exit Python. This is no longer the case in
123+
Python 3.16. Instead, the option is now processed in :c:func:`Py_RunMain`.
124+
This function became useless.
124125
125-
An exit code can be set when parsing the command line failed (exit
126-
code ``2``) or when a command line option asks to display the command
127-
line help (exit code ``0``).
126+
.. deprecated:: next
128127
129128
130129
Get Options
@@ -247,10 +246,11 @@ Initialize Python
247246
248247
* Return ``0`` on success.
249248
* Set an error in *config* and return ``-1`` on error.
250-
* Set an exit code in *config* and return ``-1`` if Python wants to
251-
exit.
252249
253-
See ``PyInitConfig_GetExitcode()`` for the exit code case.
250+
.. versionchanged:: next
251+
The function no longer sets an exit code if a command line option wants
252+
to exit Python. Instead, the option is now processed in
253+
:c:func:`Py_RunMain`.
254254
255255
256256
.. _pyinitconfig-opts:
@@ -690,9 +690,6 @@ Example of customized Python always running in isolated mode::
690690
691691
exception:
692692
PyConfig_Clear(&config);
693-
if (PyStatus_IsExit(status)) {
694-
return status.exitcode;
695-
}
696693
/* Display the error message and exit the process with
697694
non-zero exit code */
698695
Py_ExitStatusException(status);
@@ -758,6 +755,8 @@ PyStatus
758755
759756
Exit code. Argument passed to ``exit()``.
760757
758+
.. deprecated:: next
759+
761760
.. c:member:: const char *err_msg
762761
763762
Error message.
@@ -788,6 +787,11 @@ PyStatus
788787
789788
Exit Python with the specified exit code.
790789
790+
.. deprecated:: next
791+
:c:func:`Py_InitializeFromConfig` no longer sets an exit code if a
792+
command line option wants to exit Python. Instead, the option is
793+
now processed in :c:func:`Py_RunMain`.
794+
791795
Functions to handle a status:
792796
793797
.. c:function:: int PyStatus_Exception(PyStatus status)
@@ -803,6 +807,11 @@ PyStatus
803807
804808
Is the result an exit?
805809
810+
.. deprecated:: next
811+
:c:func:`Py_InitializeFromConfig` no longer sets an exit code if a
812+
command line option wants to exit Python. Instead, the option is
813+
now processed in :c:func:`Py_RunMain`.
814+
806815
.. c:function:: void Py_ExitStatusException(PyStatus status)
807816
808817
Call ``exit(exitcode)`` if *status* is an exit. Print the error

‎Doc/c-api/interp-lifecycle.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ Initializing and finalizing the interpreter
127127
interpreter, populating the runtime configuration structure, and querying
128128
the returned status structure.
129129
130+
.. versionchanged:: next
131+
The function no longer returns an exit code if a command line option
132+
wants to exit Python. Instead, the option is processed in
133+
:c:func:`Py_RunMain`.
134+
130135
131136
.. c:function:: int Py_IsInitialized()
132137

‎Doc/whatsnew/3.16.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,14 @@ Deprecated C APIs
11281128
and :c:func:`PyAsyncGen_New` are deprecated.
11291129
They are scheduled for removal in 3.18.
11301130

1131+
* Deprecate :c:func:`PyInitConfig_GetExitCode`, :c:func:`PyStatus_Exit`,
1132+
and :c:func:`PyStatus_IsExit` functions, and :c:member:`PyStatus.exitcode`
1133+
member. :c:func:`Py_InitializeFromInitConfig` and
1134+
:c:func:`Py_InitializeFromConfig` can no longer return an exit code.
1135+
Instead, if a command line option wants to exit Python, the option is
1136+
now processed in :c:func:`Py_RunMain`.
1137+
(Contributed by Victor Stinner in :gh:`158080`.)
1138+
11311139
* :c:func:`PyModule_GetFilename` is no longer deprecated, but using
11321140
:c:func:`PyModule_GetFilenameObject` instead is still recommended.
11331141
(Contributed by Victor Stinner in :gh:`154757`.)

‎Include/cpython/initconfig.h‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ typedef struct {
1111
enum {
1212
_PyStatus_TYPE_OK=0,
1313
_PyStatus_TYPE_ERROR=1,
14-
_PyStatus_TYPE_EXIT=2
14+
_PyStatus_TYPE_EXIT=2 // deprecated
1515
} _type;
1616
const char *func;
1717
const char *err_msg;
18-
int exitcode;
18+
Py_DEPRECATED(3.16) int exitcode;
1919
} PyStatus;
2020

2121
PyAPI_FUNC(PyStatus) PyStatus_Ok(void);
2222
PyAPI_FUNC(PyStatus) PyStatus_Error(const char *err_msg);
2323
PyAPI_FUNC(PyStatus) PyStatus_NoMemory(void);
24-
PyAPI_FUNC(PyStatus) PyStatus_Exit(int exitcode);
24+
Py_DEPRECATED(3.16) PyAPI_FUNC(PyStatus) PyStatus_Exit(int exitcode);
2525
PyAPI_FUNC(int) PyStatus_IsError(PyStatus err);
26-
PyAPI_FUNC(int) PyStatus_IsExit(PyStatus err);
26+
Py_DEPRECATED(3.16) PyAPI_FUNC(int) PyStatus_IsExit(PyStatus err);
2727
PyAPI_FUNC(int) PyStatus_Exception(PyStatus err);
2828

2929
/* --- PyWideStringList ------------------------------------------------ */
@@ -242,6 +242,11 @@ typedef struct PyConfig {
242242
// PYTHON_PRESITE=package.module or -X presite=package.module
243243
wchar_t *run_presite;
244244
#endif
245+
246+
// If a command line option wants to exit Python, store it in this member
247+
// and only process the option in Py_RunMain() instead of PyConfig_Read().
248+
// If equals to 0, there is no option.
249+
int _deferred_cmdline_option;
245250
} PyConfig;
246251

247252
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
@@ -293,7 +298,7 @@ PyAPI_FUNC(void) PyInitConfig_Free(PyInitConfig *config);
293298

294299
PyAPI_FUNC(int) PyInitConfig_GetError(PyInitConfig* config,
295300
const char **err_msg);
296-
PyAPI_FUNC(int) PyInitConfig_GetExitCode(PyInitConfig* config,
301+
Py_DEPRECATED(3.16) PyAPI_FUNC(int) PyInitConfig_GetExitCode(PyInitConfig* config,
297302
int *exitcode);
298303

299304
PyAPI_FUNC(int) PyInitConfig_HasOption(PyInitConfig *config,

‎Include/internal/pycore_initconfig.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config);
182182

183183
extern void _Py_DumpPathConfig(PyThreadState *tstate);
184184

185+
extern int _PyConfig_ProcessDeferredCmdlineOption(PyConfig *config);
186+
185187

186188
/* --- Function used for testing ---------------------------------- */
187189

‎Lib/test/test_cmd_line.py‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,16 @@ def test_site_flag(self):
133133

134134
@support.cpython_only
135135
def test_version(self):
136-
version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
137-
for switch in '-V', '--version', '-VV':
138-
rc, out, err = assert_python_ok(switch)
139-
self.assertNotStartsWith(err, version)
140-
self.assertStartsWith(out, version)
136+
short_version = ('Python %d.%d' % sys.version_info[:2])
137+
for switch in ('-V', '--version'):
138+
with self.subTest(switch=switch):
139+
rc, out, err = assert_python_ok(switch)
140+
self.assertStartsWith(out, short_version.encode())
141+
self.assertEqual(err, b'')
142+
143+
rc, out, err = assert_python_ok('-VV')
144+
self.assertEqual(out.rstrip(), f"Python {sys.version}".encode())
145+
self.assertEqual(err, b'')
141146

142147
def test_verbose(self):
143148
# -v causes imports to write to stderr. If the write to

‎Lib/test/test_embed.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
783783
'use_frozen_modules': not support.Py_DEBUG,
784784
'safe_path': False,
785785
'_is_python_build': IGNORE_CONFIG,
786+
'_deferred_cmdline_option': 0,
786787
}
787788
if Py_STATS:
788789
CONFIG_COMPAT['_pystats'] = False
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Deprecate :c:func:`PyInitConfig_GetExitCode`, :c:func:`PyStatus_Exit`, and
2+
:c:func:`PyStatus_IsExit` functions, and :c:member:`PyStatus.exitcode`
3+
member. :c:func:`Py_InitializeFromInitConfig` and
4+
:c:func:`Py_InitializeFromConfig` can no longer return an exit code.
5+
Instead, if a command line option wants to exit Python, the option is
6+
now processed in :c:func:`Py_RunMain`. Patch by Victor Stinner.

‎Modules/main.c‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -749,13 +749,21 @@ pymain_set_path0(PyObject *main_importer_path)
749749
static void
750750
pymain_run_python(int *exitcode)
751751
{
752-
int set_running_main = 0;
753-
754-
PyObject *main_importer_path = NULL;
755752
PyInterpreterState *interp = _PyInterpreterState_GET();
756753
/* pymain_repl() and pymain_run_stdin() modify the config */
757754
PyConfig *config = (PyConfig*)_PyInterpreterState_GetConfig(interp);
758755

756+
// Process command line options which want to exit Python
757+
int cmdline_exitcode = _PyConfig_ProcessDeferredCmdlineOption(config);
758+
if (cmdline_exitcode >= 0) {
759+
*exitcode = cmdline_exitcode;
760+
return;
761+
}
762+
763+
int set_running_main = 0;
764+
765+
PyObject *main_importer_path = NULL;
766+
759767
/* ensure path config is written into global variables */
760768
PyStatus status = _PyPathConfig_UpdateGlobal(config);
761769
if (_PyStatus_EXCEPTION(status)) {
@@ -910,10 +918,7 @@ static int
910918
pymain_main(_PyArgv *args)
911919
{
912920
PyStatus status = pymain_init(args);
913-
if (_PyStatus_IS_EXIT(status)) {
914-
pymain_free();
915-
return status.exitcode;
916-
}
921+
assert(!_PyStatus_IS_EXIT(status));
917922
if (_PyStatus_EXCEPTION(status)) {
918923
pymain_exit_error(status);
919924
}

‎Programs/_bootstrap_python.c‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ main(int argc, char **argv)
104104

105105
error:
106106
PyConfig_Clear(&config);
107-
if (PyStatus_IsExit(status)) {
108-
return status.exitcode;
109-
}
110107
Py_ExitStatusException(status);
111108
}
112109

0 commit comments

Comments
 (0)