bpo-36142: Add _PyPreConfig structure (GH-12172)

* Add _PyPreConfig structure
* Move 'ignored' and 'use_environment' fields from _PyCoreConfig
  to _PyPreConfig
* Add a new "_PyPreConfig preconfig;" field to _PyCoreConfig
diff --git a/Include/cpython/coreconfig.h b/Include/cpython/coreconfig.h
index 8ad3b23..7997d59 100644
--- a/Include/cpython/coreconfig.h
+++ b/Include/cpython/coreconfig.h
@@ -46,16 +46,35 @@
 #define _Py_INIT_FAILED(err) \
     (err.msg != NULL || err.exitcode != -1)
 
-/* --- _PyCoreConfig ---------------------------------------------- */
+/* --- _PyPreConfig ----------------------------------------------- */
 
 typedef struct {
-    /* Install signal handlers? Yes by default. */
-    int install_signal_handlers;
+    /* If greater than 0, enable isolated mode: sys.path contains
+       neither the script's directory nor the user's site-packages directory.
+
+       Set to 1 by the -I command line option. If set to -1 (default), inherit
+       Py_IsolatedFlag value. */
+    int isolated;
 
     /* If greater than 0: use environment variables.
        Set to 0 by -E command line option. If set to -1 (default), it is
        set to !Py_IgnoreEnvironmentFlag. */
     int use_environment;
+} _PyPreConfig;
+
+#define _PyPreConfig_INIT \
+    (_PyPreConfig){ \
+        .isolated = -1, \
+        .use_environment = -1}
+
+
+/* --- _PyCoreConfig ---------------------------------------------- */
+
+typedef struct {
+    _PyPreConfig preconfig;
+
+    /* Install signal handlers? Yes by default. */
+    int install_signal_handlers;
 
     int use_hash_seed;      /* PYTHONHASHSEED=x */
     unsigned long hash_seed;
@@ -152,13 +171,6 @@
     wchar_t *dll_path;      /* Windows DLL path */
 #endif
 
-    /* If greater than 0, enable isolated mode: sys.path contains
-       neither the script's directory nor the user's site-packages directory.
-
-       Set to 1 by the -I command line option. If set to -1 (default), inherit
-       Py_IsolatedFlag value. */
-    int isolated;
-
     /* If equal to zero, disable the import of the module site and the
        site-dependent manipulations of sys.path that it entails. Also disable
        these manipulations if site is explicitly imported later (call
@@ -336,8 +348,8 @@
 
 #define _PyCoreConfig_INIT \
     (_PyCoreConfig){ \
+        .preconfig = _PyPreConfig_INIT, \
         .install_signal_handlers = 1, \
-        .use_environment = -1, \
         .use_hash_seed = -1, \
         .faulthandler = -1, \
         .tracemalloc = -1, \
@@ -345,7 +357,6 @@
         .utf8_mode = -1, \
         .argc = -1, \
         .nmodule_search_path = -1, \
-        .isolated = -1, \
         .site_import = -1, \
         .bytes_warning = -1, \
         .inspect = -1, \
diff --git a/Include/internal/pycore_coreconfig.h b/Include/internal/pycore_coreconfig.h
index 9f3a4c7..6469fca 100644
--- a/Include/internal/pycore_coreconfig.h
+++ b/Include/internal/pycore_coreconfig.h
@@ -34,6 +34,19 @@
 PyAPI_FUNC(void) _Py_ClearArgcArgv(void);
 PyAPI_FUNC(int) _Py_SetArgcArgv(int argc, wchar_t * const *argv);
 
+/* --- _PyPreConfig ----------------------------------------------- */
+
+PyAPI_FUNC(void) _PyPreConfig_Clear(_PyPreConfig *config);
+PyAPI_FUNC(int) _PyPreConfig_Copy(_PyPreConfig *config,
+    const _PyPreConfig *config2);
+PyAPI_FUNC(void) _PyPreConfig_GetGlobalConfig(_PyPreConfig *config);
+PyAPI_FUNC(void) _PyPreConfig_SetGlobalConfig(const _PyPreConfig *config);
+PyAPI_FUNC(_PyInitError) _PyPreConfig_Read(_PyPreConfig *config);
+PyAPI_FUNC(int) _PyPreConfig_AsDict(const _PyPreConfig *config,
+    PyObject *dict);
+
+
+
 /* --- _PyCoreConfig ---------------------------------------------- */
 
 PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *config);
diff --git a/Modules/main.c b/Modules/main.c
index fdeaf1d..ff2e2f0 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -487,7 +487,7 @@
 static void
 pymain_import_readline(const _PyCoreConfig *config)
 {
-    if (config->isolated) {
+    if (config->preconfig.isolated) {
         return;
     }
     if (!config->inspect && RUN_CODE(config)) {
@@ -779,7 +779,7 @@
             goto done;
         }
     }
-    else if (!config->isolated) {
+    else if (!config->preconfig.isolated) {
         PyObject *path0 = _PyPathConfig_ComputeArgv0(config->argc,
                                                      config->argv);
         if (path0 == NULL) {
diff --git a/Programs/_freeze_importlib.c b/Programs/_freeze_importlib.c
index d0e40c9..e6c51a4 100644
--- a/Programs/_freeze_importlib.c
+++ b/Programs/_freeze_importlib.c
@@ -77,9 +77,9 @@
     text[text_size] = '\0';
 
     _PyCoreConfig config = _PyCoreConfig_INIT;
+    config.preconfig.use_environment = 0;
     config.user_site_directory = 0;
     config.site_import = 0;
-    config.use_environment = 0;
     config.program_name = L"./_freeze_importlib";
     /* Don't install importlib, since it could execute outdated bytecode. */
     config._install_importlib = 0;
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 6b5311b..7b4d8c2 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -606,15 +606,15 @@
     /* Test _PyCoreConfig.isolated=1 */
     _PyCoreConfig config = _PyCoreConfig_INIT;
 
+    Py_IsolatedFlag = 0;
+    config.preconfig.isolated = 1;
+
     /* Set coerce_c_locale and utf8_mode to not depend on the locale */
     config.coerce_c_locale = 0;
     config.utf8_mode = 0;
     /* Use path starting with "./" avoids a search along the PATH */
     config.program_name = L"./_testembed";
 
-    Py_IsolatedFlag = 0;
-    config.isolated = 1;
-
     test_init_env_putenvs();
     _PyInitError err = _Py_InitializeFromConfig(&config);
     if (_Py_INIT_FAILED(err)) {
diff --git a/Python/coreconfig.c b/Python/coreconfig.c
index c3eccb3..3486da4 100644
--- a/Python/coreconfig.c
+++ b/Python/coreconfig.c
@@ -439,6 +439,8 @@
 void
 _PyCoreConfig_Clear(_PyCoreConfig *config)
 {
+    _PyPreConfig_Clear(&config->preconfig);
+
 #define CLEAR(ATTR) \
     do { \
         PyMem_RawFree(ATTR); \
@@ -488,6 +490,10 @@
 {
     _PyCoreConfig_Clear(config);
 
+    if (_PyPreConfig_Copy(&config->preconfig, &config2->preconfig) < 0) {
+        return -1;
+    }
+
 #define COPY_ATTR(ATTR) config->ATTR = config2->ATTR
 #define COPY_STR_ATTR(ATTR) \
     do { \
@@ -519,7 +525,6 @@
     } while (0)
 
     COPY_ATTR(install_signal_handlers);
-    COPY_ATTR(use_environment);
     COPY_ATTR(use_hash_seed);
     COPY_ATTR(hash_seed);
     COPY_ATTR(_install_importlib);
@@ -557,7 +562,6 @@
 #endif
     COPY_WSTR_ATTR(base_exec_prefix);
 
-    COPY_ATTR(isolated);
     COPY_ATTR(site_import);
     COPY_ATTR(bytes_warning);
     COPY_ATTR(inspect);
@@ -595,9 +599,9 @@
 const char*
 _PyCoreConfig_GetEnv(const _PyCoreConfig *config, const char *name)
 {
-    assert(config->use_environment >= 0);
+    assert(config->preconfig.use_environment >= 0);
 
-    if (!config->use_environment) {
+    if (!config->preconfig.use_environment) {
         return NULL;
     }
 
@@ -616,9 +620,9 @@
                         wchar_t **dest,
                         wchar_t *wname, char *name)
 {
-    assert(config->use_environment >= 0);
+    assert(config->preconfig.use_environment >= 0);
 
-    if (!config->use_environment) {
+    if (!config->preconfig.use_environment) {
         *dest = NULL;
         return 0;
     }
@@ -662,6 +666,8 @@
 void
 _PyCoreConfig_GetGlobalConfig(_PyCoreConfig *config)
 {
+    _PyPreConfig_GetGlobalConfig(&config->preconfig);
+
 #define COPY_FLAG(ATTR, VALUE) \
         if (config->ATTR == -1) { \
             config->ATTR = VALUE; \
@@ -672,7 +678,6 @@
         }
 
     COPY_FLAG(utf8_mode, Py_UTF8Mode);
-    COPY_FLAG(isolated, Py_IsolatedFlag);
     COPY_FLAG(bytes_warning, Py_BytesWarningFlag);
     COPY_FLAG(inspect, Py_InspectFlag);
     COPY_FLAG(interactive, Py_InteractiveFlag);
@@ -686,7 +691,6 @@
 #endif
     COPY_FLAG(_frozen, Py_FrozenFlag);
 
-    COPY_NOT_FLAG(use_environment, Py_IgnoreEnvironmentFlag);
     COPY_NOT_FLAG(buffered_stdio, Py_UnbufferedStdioFlag);
     COPY_NOT_FLAG(site_import, Py_NoSiteFlag);
     COPY_NOT_FLAG(write_bytecode, Py_DontWriteBytecodeFlag);
@@ -701,6 +705,8 @@
 void
 _PyCoreConfig_SetGlobalConfig(const _PyCoreConfig *config)
 {
+    _PyPreConfig_SetGlobalConfig(&config->preconfig);
+
 #define COPY_FLAG(ATTR, VAR) \
         if (config->ATTR != -1) { \
             VAR = config->ATTR; \
@@ -711,7 +717,6 @@
         }
 
     COPY_FLAG(utf8_mode, Py_UTF8Mode);
-    COPY_FLAG(isolated, Py_IsolatedFlag);
     COPY_FLAG(bytes_warning, Py_BytesWarningFlag);
     COPY_FLAG(inspect, Py_InspectFlag);
     COPY_FLAG(interactive, Py_InteractiveFlag);
@@ -725,7 +730,6 @@
 #endif
     COPY_FLAG(_frozen, Py_FrozenFlag);
 
-    COPY_NOT_FLAG(use_environment, Py_IgnoreEnvironmentFlag);
     COPY_NOT_FLAG(buffered_stdio, Py_UnbufferedStdioFlag);
     COPY_NOT_FLAG(site_import, Py_NoSiteFlag);
     COPY_NOT_FLAG(write_bytecode, Py_DontWriteBytecodeFlag);
@@ -1497,10 +1501,15 @@
     _PyInitError err;
 
     _PyCoreConfig_GetGlobalConfig(config);
-    assert(config->use_environment >= 0);
 
-    if (config->isolated > 0) {
-        config->use_environment = 0;
+    err = _PyPreConfig_Read(&config->preconfig);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
+    assert(config->preconfig.use_environment >= 0);
+
+    if (config->preconfig.isolated > 0) {
         config->user_site_directory = 0;
     }
 
@@ -1510,7 +1519,7 @@
     }
 #endif
 
-    if (config->use_environment) {
+    if (config->preconfig.use_environment) {
         err = config_read_env_vars(config);
         if (_Py_INIT_FAILED(err)) {
             return err;
@@ -1611,7 +1620,7 @@
     }
 
     assert(config->coerce_c_locale >= 0);
-    assert(config->use_environment >= 0);
+    assert(config->preconfig.use_environment >= 0);
     assert(config->filesystem_encoding != NULL);
     assert(config->filesystem_errors != NULL);
     assert(config->stdio_encoding != NULL);
@@ -1679,18 +1688,23 @@
 PyObject *
 _PyCoreConfig_AsDict(const _PyCoreConfig *config)
 {
-    PyObject *dict, *obj;
+    PyObject *dict;
 
     dict = PyDict_New();
     if (dict == NULL) {
         return NULL;
     }
 
+    if (_PyPreConfig_AsDict(&config->preconfig, dict) < 0) {
+        Py_DECREF(dict);
+        return NULL;
+    }
+
 #define SET_ITEM(KEY, EXPR) \
         do { \
-            obj = (EXPR); \
+            PyObject *obj = (EXPR); \
             if (obj == NULL) { \
-                return NULL; \
+                goto fail; \
             } \
             int res = PyDict_SetItemString(dict, (KEY), obj); \
             Py_DECREF(obj); \
@@ -1718,7 +1732,6 @@
     SET_ITEM(#OPTIONS, _Py_wstrlist_as_pylist(config->NOPTION, config->OPTIONS))
 
     SET_ITEM_INT(install_signal_handlers);
-    SET_ITEM_INT(use_environment);
     SET_ITEM_INT(use_hash_seed);
     SET_ITEM_UINT(hash_seed);
     SET_ITEM_STR(allocator);
@@ -1752,7 +1765,6 @@
 #ifdef MS_WINDOWS
     SET_ITEM_WSTR(dll_path);
 #endif
-    SET_ITEM_INT(isolated);
     SET_ITEM_INT(site_import);
     SET_ITEM_INT(bytes_warning);
     SET_ITEM_INT(inspect);
@@ -1828,14 +1840,6 @@
 }
 
 
-static _PyInitError
-cmdline_decode_argv(_PyCmdline *cmdline)
-{
-    assert(cmdline->argv == NULL);
-    return _PyArgv_Decode(cmdline->args, &cmdline->argv);
-}
-
-
 /* --- _PyCoreConfig command line parser -------------------------- */
 
 /* Parse the command line arguments */
@@ -1912,7 +1916,7 @@
             break;
 
         case 'I':
-            config->isolated++;
+            config->preconfig.isolated++;
             break;
 
         /* case 'J': reserved for Jython */
@@ -1934,7 +1938,7 @@
             break;
 
         case 'E':
-            config->use_environment = 0;
+            config->preconfig.use_environment = 0;
             break;
 
         case 't':
@@ -2272,7 +2276,7 @@
         return err;
     }
 
-    if (config->use_environment) {
+    if (config->preconfig.use_environment) {
         err = cmdline_init_env_warnoptions(cmdline, config);
         if (_Py_INIT_FAILED(err)) {
             return err;
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index f96f015..41fc9e2 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -330,7 +330,7 @@
 #endif
 
     if (path_config.isolated != -1) {
-        config->isolated = path_config.isolated;
+        config->preconfig.isolated = path_config.isolated;
     }
     if (path_config.site_import != -1) {
         config->site_import = path_config.site_import;
diff --git a/Python/preconfig.c b/Python/preconfig.c
index 8102924..bb1e830 100644
--- a/Python/preconfig.c
+++ b/Python/preconfig.c
@@ -90,3 +90,116 @@
     *argv_p = argv;
     return _Py_INIT_OK();
 }
+
+
+/* --- _PyPreConfig ----------------------------------------------- */
+
+void
+_PyPreConfig_Clear(_PyPreConfig *config)
+{
+}
+
+
+int
+_PyPreConfig_Copy(_PyPreConfig *config, const _PyPreConfig *config2)
+{
+    _PyPreConfig_Clear(config);
+
+#define COPY_ATTR(ATTR) config->ATTR = config2->ATTR
+
+    COPY_ATTR(isolated);
+    COPY_ATTR(use_environment);
+
+#undef COPY_ATTR
+    return 0;
+}
+
+
+void
+_PyPreConfig_GetGlobalConfig(_PyPreConfig *config)
+{
+#define COPY_FLAG(ATTR, VALUE) \
+        if (config->ATTR == -1) { \
+            config->ATTR = VALUE; \
+        }
+#define COPY_NOT_FLAG(ATTR, VALUE) \
+        if (config->ATTR == -1) { \
+            config->ATTR = !(VALUE); \
+        }
+
+    COPY_FLAG(isolated, Py_IsolatedFlag);
+    COPY_NOT_FLAG(use_environment, Py_IgnoreEnvironmentFlag);
+
+#undef COPY_FLAG
+#undef COPY_NOT_FLAG
+}
+
+
+void
+_PyPreConfig_SetGlobalConfig(const _PyPreConfig *config)
+{
+#define COPY_FLAG(ATTR, VAR) \
+        if (config->ATTR != -1) { \
+            VAR = config->ATTR; \
+        }
+#define COPY_NOT_FLAG(ATTR, VAR) \
+        if (config->ATTR != -1) { \
+            VAR = !config->ATTR; \
+        }
+
+    COPY_FLAG(isolated, Py_IsolatedFlag);
+    COPY_NOT_FLAG(use_environment, Py_IgnoreEnvironmentFlag);
+
+#undef COPY_FLAG
+#undef COPY_NOT_FLAG
+}
+
+
+_PyInitError
+_PyPreConfig_Read(_PyPreConfig *config)
+{
+    _PyPreConfig_GetGlobalConfig(config);
+
+    if (config->isolated > 0) {
+        config->use_environment = 0;
+    }
+
+    /* Default values */
+    if (config->use_environment < 0) {
+        config->use_environment = 0;
+    }
+
+    assert(config->use_environment >= 0);
+
+    return _Py_INIT_OK();
+}
+
+
+int
+_PyPreConfig_AsDict(const _PyPreConfig *config, PyObject *dict)
+{
+#define SET_ITEM(KEY, EXPR) \
+        do { \
+            PyObject *obj = (EXPR); \
+            if (obj == NULL) { \
+                goto fail; \
+            } \
+            int res = PyDict_SetItemString(dict, (KEY), obj); \
+            Py_DECREF(obj); \
+            if (res < 0) { \
+                goto fail; \
+            } \
+        } while (0)
+#define SET_ITEM_INT(ATTR) \
+    SET_ITEM(#ATTR, PyLong_FromLong(config->ATTR))
+
+    SET_ITEM_INT(isolated);
+    SET_ITEM_INT(use_environment);
+    return 0;
+
+fail:
+    return -1;
+
+#undef SET_ITEM
+#undef SET_ITEM_INT
+}
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index dd39305..4b12280 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2172,14 +2172,14 @@
     SetFlag(!config->write_bytecode);
     SetFlag(!config->user_site_directory);
     SetFlag(!config->site_import);
-    SetFlag(!config->use_environment);
+    SetFlag(!config->preconfig.use_environment);
     SetFlag(config->verbose);
     /* SetFlag(saw_unbuffered_flag); */
     /* SetFlag(skipfirstline); */
     SetFlag(config->bytes_warning);
     SetFlag(config->quiet);
     SetFlag(config->use_hash_seed == 0 || config->hash_seed != 0);
-    SetFlag(config->isolated);
+    SetFlag(config->preconfig.isolated);
     PyStructSequence_SET_ITEM(seq, pos++, PyBool_FromLong(config->dev_mode));
     SetFlag(config->utf8_mode);
 #undef SetFlag