Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Include/pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ PyAPI_FUNC(int) Py_SetStandardStreamEncoding(const char *encoding,
PyAPI_FUNC(_PyInitError) _Py_InitializeCore(const _PyCoreConfig *);
PyAPI_FUNC(int) _Py_IsCoreInitialized(void);

PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *, int);
PyAPI_FUNC(void) _PyCoreConfig_Clear(_PyCoreConfig *);
PyAPI_FUNC(void) _PyCoreConfig_UpdateGlobals(_PyCoreConfig *);
PyAPI_FUNC(int) _PyCoreConfig_Copy(
_PyCoreConfig *config,
const _PyCoreConfig *config2);
Expand Down
6 changes: 6 additions & 0 deletions Include/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ typedef struct {
wchar_t *exec_prefix; /* sys.exec_prefix */
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */

int optimization_level; /* Py_OptimizeFlag, -O, PYTHONOPTIMIZE */
int inspect; /* Py_InspectFlag, -i, PYTHONINSPECT */
int no_user_site_directory; /* Py_NoUserSiteDirectory, -I, -s, PYTHONNOUSERSITE */
int dont_write_bytecode; /* Py_DontWriteBytecodeFlag, -B, PYTHONDONTWRITEBYTECODE */
int use_unbuffered_io; /* Py_UnbufferedStdioFlag, -u, PYTHONUNBUFFERED */

/* Private fields */
int _disable_importlib; /* Needed by freeze_importlib */
} _PyCoreConfig;
Expand Down
41 changes: 41 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,47 @@ def test_pre_initialization_sys_options(self):
self.assertIn(expected_output, out)
self.assertEqual(err, '')

def test_initialize_sys_flags_from_environment(self):
"""
Validate that settings from environment variables affect
interpreter settings
"""
env = { k: os.environ[k] for k in os.environ
if not k.startswith('PYTHON') }

out, err = self.run_embedded_interpreter(
"initialize_sys_flags", env=env)

expected_output = (
"dont_write_bytecode=0\n"
"hash_randomization=1\n"
"inspect=0\n"
"no_user_site=0\n"
"optimize=0\n"
)
self.assertIn(expected_output, out)
self.assertEqual(err, '')

env['PYTHONDONTWRITEBYTECODE'] = '1'
env['PYTHONHASHSEED'] = '0'
env['PYTHONINSPECT'] = '3'
env['PYTHONNOUSERSITE'] = '4'
env['PYTHONOPTIMIZE'] = '2'

out, err = self.run_embedded_interpreter(
"initialize_sys_flags", env=env)

expected_output = (
"dont_write_bytecode=1\n"
"hash_randomization=0\n"
"inspect=3\n"
"no_user_site=4\n"
"optimize=2\n"
)
self.assertEqual(expected_output, out)
self.assertEqual(err, '')


def test_bpo20891(self):
"""
bpo-20891: Calling PyGILState_Ensure in a non-Python thread before
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Don't ignore environment variables with command-line equivalents in
Py_Initialize.
85 changes: 47 additions & 38 deletions Modules/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,9 @@ typedef struct {
int print_version; /* -V option */
int bytes_warning; /* Py_BytesWarningFlag, -b */
int debug; /* Py_DebugFlag, -b, PYTHONDEBUG */
int inspect; /* Py_InspectFlag, -i, PYTHONINSPECT */
int interactive; /* Py_InteractiveFlag, -i */
int isolated; /* Py_IsolatedFlag, -I */
int optimization_level; /* Py_OptimizeFlag, -O, PYTHONOPTIMIZE */
int dont_write_bytecode; /* Py_DontWriteBytecodeFlag, -B, PYTHONDONTWRITEBYTECODE */
int no_user_site_directory; /* Py_NoUserSiteDirectory, -I, -s, PYTHONNOUSERSITE */
int no_site_import; /* Py_NoSiteFlag, -S */
int use_unbuffered_io; /* Py_UnbufferedStdioFlag, -u, PYTHONUNBUFFERED */
int verbosity; /* Py_VerboseFlag, -v, PYTHONVERBOSE */
int quiet_flag; /* Py_QuietFlag, -q */
const char *check_hash_pycs_mode; /* --check-hash-based-pycs */
Expand Down Expand Up @@ -805,28 +800,28 @@ pymain_parse_cmdline_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
break;

case 'i':
cmdline->inspect++;
config->inspect++;
cmdline->interactive++;
break;

case 'I':
config->ignore_environment++;
cmdline->isolated++;
cmdline->no_user_site_directory++;
config->no_user_site_directory++;
break;

/* case 'J': reserved for Jython */

case 'O':
cmdline->optimization_level++;
config->optimization_level++;
break;

case 'B':
cmdline->dont_write_bytecode++;
config->dont_write_bytecode++;
break;

case 's':
cmdline->no_user_site_directory++;
config->no_user_site_directory++;
break;

case 'S':
Expand All @@ -842,7 +837,7 @@ pymain_parse_cmdline_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
break;

case 'u':
cmdline->use_unbuffered_io = 1;
config->use_unbuffered_io = 1;
break;

case 'v':
Expand Down Expand Up @@ -1368,14 +1363,14 @@ pymain_get_global_config(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
{
cmdline->bytes_warning = Py_BytesWarningFlag;
cmdline->debug = Py_DebugFlag;
cmdline->inspect = Py_InspectFlag;
pymain->config.inspect = Py_InspectFlag;
cmdline->interactive = Py_InteractiveFlag;
cmdline->isolated = Py_IsolatedFlag;
cmdline->optimization_level = Py_OptimizeFlag;
cmdline->dont_write_bytecode = Py_DontWriteBytecodeFlag;
cmdline->no_user_site_directory = Py_NoUserSiteDirectory;
pymain->config.optimization_level = Py_OptimizeFlag;
pymain->config.dont_write_bytecode = Py_DontWriteBytecodeFlag;
pymain->config.no_user_site_directory = Py_NoUserSiteDirectory;
cmdline->no_site_import = Py_NoSiteFlag;
cmdline->use_unbuffered_io = Py_UnbufferedStdioFlag;
pymain->config.use_unbuffered_io = Py_UnbufferedStdioFlag;
cmdline->verbosity = Py_VerboseFlag;
cmdline->quiet_flag = Py_QuietFlag;
#ifdef MS_WINDOWS
Expand All @@ -1395,14 +1390,9 @@ pymain_set_global_config(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
{
Py_BytesWarningFlag = cmdline->bytes_warning;
Py_DebugFlag = cmdline->debug;
Py_InspectFlag = cmdline->inspect;
Py_InteractiveFlag = cmdline->interactive;
Py_IsolatedFlag = cmdline->isolated;
Py_OptimizeFlag = cmdline->optimization_level;
Py_DontWriteBytecodeFlag = cmdline->dont_write_bytecode;
Py_NoUserSiteDirectory = cmdline->no_user_site_directory;
Py_NoSiteFlag = cmdline->no_site_import;
Py_UnbufferedStdioFlag = cmdline->use_unbuffered_io;
Py_VerboseFlag = cmdline->verbosity;
Py_QuietFlag = cmdline->quiet_flag;
_Py_CheckHashBasedPycsMode = cmdline->check_hash_pycs_mode;
Expand All @@ -1411,15 +1401,11 @@ pymain_set_global_config(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
Py_LegacyWindowsStdioFlag = cmdline->legacy_windows_stdio;
#endif

Py_IgnoreEnvironmentFlag = pymain->config.ignore_environment;
Py_UTF8Mode = pymain->config.utf8_mode;

/* Random or non-zero hash seed */
Py_HashRandomizationFlag = (pymain->config.use_hash_seed == 0 ||
pymain->config.hash_seed != 0);
_PyCoreConfig_UpdateGlobals(&(pymain->config));
}



static void
pymain_import_readline(_PyMain *pymain)
{
Expand Down Expand Up @@ -1694,15 +1680,15 @@ get_env_flag(int *flag, const char *name)


static void
cmdline_get_env_flags(_Py_CommandLineDetails *cmdline)
cmdline_get_env_flags(_PyCoreConfig* config, _Py_CommandLineDetails *cmdline)
{
get_env_flag(&cmdline->debug, "PYTHONDEBUG");
get_env_flag(&cmdline->verbosity, "PYTHONVERBOSE");
get_env_flag(&cmdline->optimization_level, "PYTHONOPTIMIZE");
get_env_flag(&cmdline->inspect, "PYTHONINSPECT");
get_env_flag(&cmdline->dont_write_bytecode, "PYTHONDONTWRITEBYTECODE");
get_env_flag(&cmdline->no_user_site_directory, "PYTHONNOUSERSITE");
get_env_flag(&cmdline->use_unbuffered_io, "PYTHONUNBUFFERED");
get_env_flag(&config->optimization_level, "PYTHONOPTIMIZE");
get_env_flag(&config->inspect, "PYTHONINSPECT");
get_env_flag(&config->dont_write_bytecode, "PYTHONDONTWRITEBYTECODE");
get_env_flag(&config->no_user_site_directory, "PYTHONNOUSERSITE");
get_env_flag(&config->use_unbuffered_io, "PYTHONUNBUFFERED");
#ifdef MS_WINDOWS
get_env_flag(&cmdline->legacy_windows_fs_encoding,
"PYTHONLEGACYWINDOWSFSENCODING");
Expand Down Expand Up @@ -1804,8 +1790,15 @@ config_init_utf8_mode(_PyCoreConfig *config)


static _PyInitError
config_read_env_vars(_PyCoreConfig *config)
config_read_env_vars(_PyCoreConfig *config, int ignore_cmdline)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the ignore_cmdline to be 100% sure that I don't change the behaviour of Py_Main with this patch, and haven't checked yet if this is really necessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided we are handling the -I option correctly and are also not looking at argv unless using Py_Main, it shouldn't be necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've talked with Victor about this issue (without looking at this code in detail), the master branch contains a much better implementation of this included much improved unit tests.

I won't update this pull request until he has had time to think about this issue.

{
if (!ignore_cmdline) {
get_env_flag(&config->optimization_level, "PYTHONOPTIMIZE");
get_env_flag(&config->inspect, "PYTHONINSPECT");
get_env_flag(&config->no_user_site_directory, "PYTHONNOUSERSITE");
get_env_flag(&config->dont_write_bytecode, "PYTHONDONTWRITEBYTECODE");
}

config->allocator = config_get_env_var("PYTHONMALLOC");

if (config_get_env_var("PYTHONDUMPREFS")) {
Expand Down Expand Up @@ -1893,7 +1886,7 @@ pymain_read_conf_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
Py_IgnoreEnvironmentFlag = config->ignore_environment;

/* Get environment variables */
cmdline_get_env_flags(cmdline);
cmdline_get_env_flags(config, cmdline);

err = cmdline_init_env_warnoptions(cmdline);
if (_Py_INIT_FAILED(err)) {
Expand All @@ -1918,7 +1911,7 @@ pymain_read_conf_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
Py_IsolatedFlag = cmdline->isolated;
Py_NoSiteFlag = cmdline->no_site_import;

err = _PyCoreConfig_Read(config);
err = _PyCoreConfig_Read(config, 1);

cmdline->isolated = Py_IsolatedFlag;
cmdline->no_site_import = Py_NoSiteFlag;
Expand Down Expand Up @@ -2168,12 +2161,28 @@ config_init_path_config(_PyCoreConfig *config)
* this function multiple times with various preconfigured settings.
*/

void
_PyCoreConfig_UpdateGlobals(_PyCoreConfig* config)
{
Py_InspectFlag = config->inspect;
Py_OptimizeFlag = config->optimization_level;
Py_DontWriteBytecodeFlag = config->dont_write_bytecode;
Py_NoUserSiteDirectory = config->no_user_site_directory;
Py_UnbufferedStdioFlag = config->use_unbuffered_io;

Py_IgnoreEnvironmentFlag = config->ignore_environment;
Py_UTF8Mode = config->utf8_mode;

Py_HashRandomizationFlag = (config->use_hash_seed == 0 ||
config->hash_seed != 0);
}

_PyInitError
_PyCoreConfig_Read(_PyCoreConfig *config)
_PyCoreConfig_Read(_PyCoreConfig *config, int ignore_cmdline)
{
_PyInitError err;

err = config_read_env_vars(config);
err = config_read_env_vars(config, ignore_cmdline);
if (_Py_INIT_FAILED(err)) {
return err;
}
Expand Down
19 changes: 19 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,24 @@ static int test_initialize_pymain(void)
return 0;
}

static int test_initialize_sys_flags(void)
{
_testembed_Py_Initialize();

PyRun_SimpleString(
"import sys;"
"print(f\"dont_write_bytecode={sys.flags.dont_write_bytecode}\");"
"print(f\"hash_randomization={sys.flags.hash_randomization}\");"
"print(f\"inspect={sys.flags.inspect}\");"
"print(f\"no_user_site={sys.flags.no_user_site}\");"
"print(f\"optimize={sys.flags.optimize}\");"
"sys.stdout.flush()"
);

Py_Finalize();
return 0;
}


/* *********************************************************
* List of test cases and the function that implements it.
Expand Down Expand Up @@ -318,6 +336,7 @@ static struct TestCase TestCases[] = {
{ "bpo20891", test_bpo20891 },
{ "initialize_twice", test_initialize_twice },
{ "initialize_pymain", test_initialize_pymain },
{ "initialize_sys_flags", test_initialize_sys_flags },
{ NULL, NULL }
};

Expand Down
2 changes: 1 addition & 1 deletion Python/pathconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pathconfig_global_init(void)
_PyInitError err;
_PyCoreConfig config = _PyCoreConfig_INIT;

err = _PyCoreConfig_Read(&config);
err = _PyCoreConfig_Read(&config, 1);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this one, this is the minimal change to make this code compile with my other changes and without any changes to its behaviour. I haven't check the code yet to see if the changed behaviour you get with "0" as the second argument would be more useful here.

if (_Py_INIT_FAILED(err)) {
goto error;
}
Expand Down
5 changes: 4 additions & 1 deletion Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -924,11 +924,13 @@ _Py_InitializeEx_Private(int install_sigs, int install_importlib)
config._disable_importlib = !install_importlib;
config.install_signal_handlers = install_sigs;

err = _PyCoreConfig_Read(&config);
err = _PyCoreConfig_Read(&config, 0);
if (_Py_INIT_FAILED(err)) {
goto done;
}

_PyCoreConfig_UpdateGlobals(&config);

err = _Py_InitializeCore(&config);
if (_Py_INIT_FAILED(err)) {
goto done;
Expand All @@ -944,6 +946,7 @@ _Py_InitializeEx_Private(int install_sigs, int install_importlib)
goto done;
}


err = _Py_INIT_OK();

done:
Expand Down