Skip to content

Commit ae239f6

Browse files
authored
bpo-36763: Add _PyCoreConfig.parse_argv (GH-13361)
* _PyCoreConfig_Read() doesn't parse nor update argv if parse_argv is 0. * Move path configuration fields in _PyCoreConfig. * Add an unit test for parse_argv=0. * Remove unused "done": label in _Py_RunMain().
1 parent 68b34a7 commit ae239f6

File tree

5 files changed

+109
-36
lines changed

5 files changed

+109
-36
lines changed

Include/cpython/coreconfig.h

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -207,31 +207,25 @@ typedef struct {
207207
wchar_t *filesystem_errors;
208208

209209
wchar_t *pycache_prefix; /* PYTHONPYCACHEPREFIX, -X pycache_prefix=PATH */
210-
wchar_t *program_name; /* Program name, see also Py_GetProgramName() */
211-
_PyWstrList argv; /* Command line arguments */
212-
wchar_t *program; /* argv[0] or "" */
213-
_PyWstrList xoptions; /* Command line -X options */
214-
_PyWstrList warnoptions; /* Warnings options */
210+
int parse_argv; /* Parse argv command line arguments? */
215211

216-
/* Path configuration inputs */
217-
wchar_t *module_search_path_env; /* PYTHONPATH environment variable */
218-
wchar_t *home; /* PYTHONHOME environment variable,
219-
see also Py_SetPythonHome(). */
212+
/* Command line arguments (sys.argv).
220213
221-
/* Path configuration outputs */
222-
int use_module_search_paths; /* If non-zero, use module_search_paths */
223-
_PyWstrList module_search_paths; /* sys.path paths. Computed if
224-
use_module_search_paths is equal
225-
to zero. */
214+
By default, Python command line arguments are parsed and then stripped
215+
from argv. Set parse_argv to 0 to avoid that.
226216
227-
wchar_t *executable; /* sys.executable */
228-
wchar_t *prefix; /* sys.prefix */
229-
wchar_t *base_prefix; /* sys.base_prefix */
230-
wchar_t *exec_prefix; /* sys.exec_prefix */
231-
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */
232-
#ifdef MS_WINDOWS
233-
wchar_t *dll_path; /* Windows DLL path */
234-
#endif
217+
If argv is empty, an empty string is added to ensure that sys.argv
218+
always exists and is never empty. */
219+
_PyWstrList argv;
220+
221+
/* Program: argv[0] or "".
222+
Used to display Python usage if parsing command line arguments fails.
223+
Used to initialize the default value of program_name */
224+
wchar_t *program;
225+
wchar_t *program_name; /* Program name, see also Py_GetProgramName() */
226+
227+
_PyWstrList xoptions; /* Command line -X options */
228+
_PyWstrList warnoptions; /* Warnings options */
235229

236230
/* If equal to zero, disable the import of the module site and the
237231
site-dependent manipulations of sys.path that it entails. Also disable
@@ -350,6 +344,28 @@ typedef struct {
350344
int legacy_windows_stdio;
351345
#endif
352346

347+
/* --- Path configuration inputs ------------ */
348+
349+
wchar_t *module_search_path_env; /* PYTHONPATH environment variable */
350+
wchar_t *home; /* PYTHONHOME environment variable,
351+
see also Py_SetPythonHome(). */
352+
353+
/* --- Path configuration outputs ----------- */
354+
355+
int use_module_search_paths; /* If non-zero, use module_search_paths */
356+
_PyWstrList module_search_paths; /* sys.path paths. Computed if
357+
use_module_search_paths is equal
358+
to zero. */
359+
360+
wchar_t *executable; /* sys.executable */
361+
wchar_t *prefix; /* sys.prefix */
362+
wchar_t *base_prefix; /* sys.base_prefix */
363+
wchar_t *exec_prefix; /* sys.exec_prefix */
364+
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */
365+
#ifdef MS_WINDOWS
366+
wchar_t *dll_path; /* Windows DLL path */
367+
#endif
368+
353369
/* --- Parameter only used by Py_Main() ---------- */
354370

355371
/* Skip the first line of the source ('run_filename' parameter), allowing use of non-Unix forms of
@@ -408,6 +424,7 @@ typedef struct {
408424
.faulthandler = -1, \
409425
.tracemalloc = -1, \
410426
.use_module_search_paths = 0, \
427+
.parse_argv = 1, \
411428
.site_import = -1, \
412429
.bytes_warning = -1, \
413430
.inspect = -1, \

Lib/test/test_embed.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
304304

305305
'pycache_prefix': None,
306306
'program_name': GET_DEFAULT_CONFIG,
307+
'parse_argv': 1,
307308
'argv': [""],
308309
'program': '',
309310

@@ -700,6 +701,14 @@ def test_run_main_config(self):
700701
}
701702
self.check_config("run_main_config", core_config, preconfig)
702703

704+
def test_init_dont_parse_argv(self):
705+
core_config = {
706+
'argv': ['-v', '-c', 'arg1', '-W', 'arg2'],
707+
'parse_argv': 0,
708+
'program': 'program',
709+
}
710+
self.check_config("init_dont_parse_argv", core_config, {})
711+
703712

704713
if __name__ == "__main__":
705714
unittest.main()

Modules/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,13 +574,13 @@ _Py_RunMain(void)
574574
int exitcode = 0;
575575

576576
pymain_run_python(&exitcode);
577+
577578
if (Py_FinalizeEx() < 0) {
578579
/* Value unlikely to be confused with a non-error exit status or
579580
other special meaning */
580581
exitcode = 120;
581582
}
582583

583-
done:
584584
pymain_free();
585585

586586
if (_Py_UnhandledKeyboardInterrupt) {

Programs/_testembed.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,37 @@ static int test_init_from_config(void)
511511
}
512512

513513

514+
static int test_init_dont_parse_argv(void)
515+
{
516+
_PyInitError err;
517+
518+
_PyCoreConfig config = _PyCoreConfig_INIT;
519+
520+
static wchar_t* argv[] = {
521+
L"-v",
522+
L"-c",
523+
L"arg1",
524+
L"-W",
525+
L"arg2",
526+
};
527+
528+
config.program = L"program";
529+
config.program_name = L"./_testembed";
530+
531+
config.argv.length = Py_ARRAY_LENGTH(argv);
532+
config.argv.items = argv;
533+
config.parse_argv = 0;
534+
535+
err = _Py_InitializeFromConfig(&config);
536+
if (_Py_INIT_FAILED(err)) {
537+
_Py_ExitInitError(err);
538+
}
539+
dump_config();
540+
Py_Finalize();
541+
return 0;
542+
}
543+
544+
514545
static void test_init_env_putenvs(void)
515546
{
516547
putenv("PYTHONHASHSEED=42");
@@ -797,6 +828,7 @@ static struct TestCase TestCases[] = {
797828
{ "init_default_config", test_init_default_config },
798829
{ "init_global_config", test_init_global_config },
799830
{ "init_from_config", test_init_from_config },
831+
{ "init_dont_parse_argv", test_init_dont_parse_argv },
800832
{ "init_env", test_init_env },
801833
{ "init_env_dev_mode", test_init_env_dev_mode },
802834
{ "init_env_dev_mode_alloc", test_init_env_dev_mode_alloc },

Python/coreconfig.c

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,7 @@ _PyCoreConfig_Copy(_PyCoreConfig *config, const _PyCoreConfig *config2)
628628
COPY_WSTR_ATTR(program_name);
629629
COPY_WSTR_ATTR(program);
630630

631+
COPY_ATTR(parse_argv);
631632
COPY_WSTRLIST(argv);
632633
COPY_WSTRLIST(warnoptions);
633634
COPY_WSTRLIST(xoptions);
@@ -727,6 +728,7 @@ _PyCoreConfig_AsDict(const _PyCoreConfig *config)
727728
SET_ITEM_WSTR(filesystem_errors);
728729
SET_ITEM_WSTR(pycache_prefix);
729730
SET_ITEM_WSTR(program_name);
731+
SET_ITEM_INT(parse_argv);
730732
SET_ITEM_WSTRLIST(argv);
731733
SET_ITEM_WSTR(program);
732734
SET_ITEM_WSTRLIST(xoptions);
@@ -1490,6 +1492,8 @@ config_read(_PyCoreConfig *config, _PyPreCmdline *cmdline)
14901492
}
14911493

14921494
if (config->isolated > 0) {
1495+
/* _PyPreCmdline_Read() sets use_environment to 0 if isolated is set,
1496+
_PyPreCmdline_SetCoreConfig() overrides config->use_environment. */
14931497
config->user_site_directory = 0;
14941498
}
14951499

@@ -1660,7 +1664,7 @@ config_usage(int error, const wchar_t* program)
16601664
/* Parse the command line arguments */
16611665
static _PyInitError
16621666
config_parse_cmdline(_PyCoreConfig *config, _PyPreCmdline *precmdline,
1663-
_PyWstrList *warnoptions)
1667+
_PyWstrList *warnoptions, int *opt_index)
16641668
{
16651669
_PyInitError err;
16661670
const _PyWstrList *argv = &precmdline->argv;
@@ -1833,8 +1837,7 @@ config_parse_cmdline(_PyCoreConfig *config, _PyPreCmdline *precmdline,
18331837
_PyOS_optind--;
18341838
}
18351839

1836-
/* -c and -m options are exclusive */
1837-
assert(!(config->run_command != NULL && config->run_module != NULL));
1840+
*opt_index = _PyOS_optind;
18381841

18391842
return _Py_INIT_OK();
18401843
}
@@ -1978,22 +1981,23 @@ config_init_warnoptions(_PyCoreConfig *config,
19781981

19791982

19801983
static _PyInitError
1981-
config_init_argv(_PyCoreConfig *config, const _PyPreCmdline *cmdline)
1984+
config_update_argv(_PyCoreConfig *config, const _PyPreCmdline *cmdline,
1985+
int opt_index)
19821986
{
19831987
const _PyWstrList *cmdline_argv = &cmdline->argv;
19841988
_PyWstrList config_argv = _PyWstrList_INIT;
19851989

19861990
/* Copy argv to be able to modify it (to force -c/-m) */
1987-
if (cmdline_argv->length <= _PyOS_optind) {
1991+
if (cmdline_argv->length <= opt_index) {
19881992
/* Ensure at least one (empty) argument is seen */
19891993
if (_PyWstrList_Append(&config_argv, L"") < 0) {
19901994
return _Py_INIT_NO_MEMORY();
19911995
}
19921996
}
19931997
else {
19941998
_PyWstrList slice;
1995-
slice.length = cmdline_argv->length - _PyOS_optind;
1996-
slice.items = &cmdline_argv->items[_PyOS_optind];
1999+
slice.length = cmdline_argv->length - opt_index;
2000+
slice.items = &cmdline_argv->items[opt_index];
19972001
if (_PyWstrList_Copy(&config_argv, &slice) < 0) {
19982002
return _Py_INIT_NO_MEMORY();
19992003
}
@@ -2058,14 +2062,22 @@ config_read_cmdline(_PyCoreConfig *config, _PyPreCmdline *precmdline)
20582062
_PyWstrList cmdline_warnoptions = _PyWstrList_INIT;
20592063
_PyWstrList env_warnoptions = _PyWstrList_INIT;
20602064

2061-
err = config_parse_cmdline(config, precmdline, &cmdline_warnoptions);
2062-
if (_Py_INIT_FAILED(err)) {
2063-
goto done;
2065+
if (config->parse_argv < 0) {
2066+
config->parse_argv = 1;
20642067
}
20652068

2066-
err = config_init_argv(config, precmdline);
2067-
if (_Py_INIT_FAILED(err)) {
2068-
goto done;
2069+
if (config->parse_argv) {
2070+
int opt_index;
2071+
err = config_parse_cmdline(config, precmdline, &cmdline_warnoptions,
2072+
&opt_index);
2073+
if (_Py_INIT_FAILED(err)) {
2074+
goto done;
2075+
}
2076+
2077+
err = config_update_argv(config, precmdline, opt_index);
2078+
if (_Py_INIT_FAILED(err)) {
2079+
goto done;
2080+
}
20692081
}
20702082

20712083
err = config_read(config, precmdline);
@@ -2212,6 +2224,7 @@ _PyCoreConfig_Read(_PyCoreConfig *config)
22122224
assert(config->verbose >= 0);
22132225
assert(config->quiet >= 0);
22142226
assert(config->user_site_directory >= 0);
2227+
assert(config->parse_argv >= 0);
22152228
assert(config->buffered_stdio >= 0);
22162229
assert(config->program_name != NULL);
22172230
assert(config->program != NULL);
@@ -2236,6 +2249,8 @@ _PyCoreConfig_Read(_PyCoreConfig *config)
22362249
#ifdef MS_WINDOWS
22372250
assert(config->legacy_windows_stdio >= 0);
22382251
#endif
2252+
/* -c and -m options are exclusive */
2253+
assert(!(config->run_command != NULL && config->run_module != NULL));
22392254
assert(config->check_hash_pycs_mode != NULL);
22402255
assert(config->_install_importlib >= 0);
22412256
assert(config->_frozen >= 0);

0 commit comments

Comments
 (0)