]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-45020: Default to using frozen modules unless running from source tree. (gh-28940)
authorEric Snow <ericsnowcurrently@gmail.com>
Sat, 16 Oct 2021 19:16:08 +0000 (13:16 -0600)
committerGitHub <noreply@github.com>
Sat, 16 Oct 2021 19:16:08 +0000 (13:16 -0600)
The default was "off".  Switching it to "on" means users get the benefit of frozen stdlib modules without having to do anything.  There's a special-case for running-in-source-tree, so contributors don't get surprised when their stdlib changes don't get used.

https://bugs.python.org/issue45020

Doc/using/cmdline.rst
Include/internal/pycore_fileutils.h
Lib/site.py
Lib/test/test_embed.py
Misc/NEWS.d/3.11.0a1.rst
Python/fileutils.c
Python/initconfig.c

index 23a645a036aedf31405e92b901370103094c597f..d341ea8bb43c88309cef7a20e1f8ed4c5f93132c 100644 (file)
@@ -483,7 +483,8 @@ Miscellaneous options
    * ``-X frozen_modules`` determines whether or not frozen modules are
      ignored by the import machinery.  A value of "on" means they get
      imported and "off" means they are ignored.  The default is "on"
-     for non-debug builds (the normal case) and "off" for debug builds.
+     if this is an installed Python (the normal case).  If it's under
+     development (running from the source tree) then the default is "off".
      Note that the "importlib_bootstrap" and "importlib_bootstrap_external"
      frozen modules are always used, even if this flag is set to "off".
 
index 3464477bce57558a42c37d4b8c28c71ec1b1e71d..ab436ae9b007acc39e665096effe88128b5d4975 100644 (file)
@@ -79,6 +79,7 @@ extern wchar_t * _Py_join_relfile(const wchar_t *dirname,
 extern int _Py_add_relfile(wchar_t *dirname,
                            const wchar_t *relfile,
                            size_t bufsize);
+extern size_t _Py_find_basename(const wchar_t *filename);
 
 // Macros to protect CRT calls against instant termination when passed an
 // invalid parameter (bpo-23524). IPH stands for Invalid Parameter Handler.
index 939893eb5ee93bc8a3c717d02bdf2f4f69636549..e129f3b4851f3dd802715d4743157212263f71fc 100644 (file)
@@ -418,8 +418,10 @@ def setcopyright():
     files, dirs = [], []
     # Not all modules are required to have a __file__ attribute.  See
     # PEP 420 for more details.
-    if hasattr(os, '__file__'):
+    here = getattr(sys, '_stdlib_dir', None)
+    if not here and hasattr(os, '__file__'):
         here = os.path.dirname(os.__file__)
+    if here:
         files.extend(["LICENSE.txt", "LICENSE"])
         dirs.extend([os.path.join(here, os.pardir), here, os.curdir])
     builtins.license = _sitebuiltins._Printer(
index 4cbb4c2c1ce3668dcfd4d5957fd84c3a2047edfc..4b4396efb5cadb5e993148966681a1bebf9dfe98 100644 (file)
@@ -53,12 +53,13 @@ def remove_python_envvars():
 class EmbeddingTestsMixin:
     def setUp(self):
         exename = "_testembed"
+        builddir = os.path.dirname(sys.executable)
         if MS_WINDOWS:
             ext = ("_d" if debug_build(sys.executable) else "") + ".exe"
             exename += ext
-            exepath = os.path.dirname(sys.executable)
+            exepath = builddir
         else:
-            exepath = os.path.join(support.REPO_ROOT, "Programs")
+            exepath = os.path.join(builddir, 'Programs')
         self.test_exe = exe = os.path.join(exepath, exename)
         if not os.path.exists(exe):
             self.skipTest("%r doesn't exist" % exe)
@@ -434,7 +435,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
         'pathconfig_warnings': 1,
         '_init_main': 1,
         '_isolated_interpreter': 0,
-        'use_frozen_modules': 0,
+        'use_frozen_modules': 1,
     }
     if MS_WINDOWS:
         CONFIG_COMPAT.update({
@@ -1146,6 +1147,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
              # The current getpath.c doesn't determine the stdlib dir
              # in this case.
             'stdlib_dir': '',
+            'use_frozen_modules': -1,
         }
         self.default_program_name(config)
         env = {'TESTPATH': os.path.pathsep.join(paths)}
@@ -1169,6 +1171,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
              # The current getpath.c doesn't determine the stdlib dir
              # in this case.
             'stdlib_dir': '',
+            'use_frozen_modules': -1,
             # overridden by PyConfig
             'program_name': 'conf_program_name',
             'base_executable': 'conf_executable',
@@ -1265,6 +1268,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
             'stdlib_dir': stdlib,
         }
         self.default_program_name(config)
+        if not config['executable']:
+            config['use_frozen_modules'] = -1
         env = {'TESTHOME': home, 'PYTHONPATH': paths_str}
         self.check_all_configs("test_init_setpythonhome", config,
                                api=API_COMPAT, env=env)
@@ -1303,6 +1308,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                 # The current getpath.c doesn't determine the stdlib dir
                 # in this case.
                 'stdlib_dir': None,
+                'use_frozen_modules': -1,
             }
             env = self.copy_paths_by_env(config)
             self.check_all_configs("test_init_compat_config", config,
@@ -1361,6 +1367,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                 config['base_prefix'] = pyvenv_home
                 config['prefix'] = pyvenv_home
                 config['stdlib_dir'] = os.path.join(pyvenv_home, 'lib')
+                config['use_frozen_modules'] = 1
 
                 ver = sys.version_info
                 dll = f'python{ver.major}'
@@ -1373,6 +1380,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
                 # The current getpath.c doesn't determine the stdlib dir
                 # in this case.
                 config['stdlib_dir'] = None
+                config['use_frozen_modules'] = -1
 
             env = self.copy_paths_by_env(config)
             self.check_all_configs("test_init_compat_config", config,
index e3d2acc4999689db590d5df92802a8cc873373e9..a64a3e74ccb410068fc40da40b7a86e2c28d8884 100644 (file)
@@ -262,7 +262,7 @@ Compiler now removes trailing unused constants from co_consts.
 
 Add a new command line option, "-X frozen_modules=[on|off]" to opt out of
 (or into) using optional frozen modules.  This defaults to "on" (or "off" if
-it's a debug build).
+it's running out of the source tree).
 
 ..
 
index 173d34dd23f18be8f31bbcb7ba171869c70ecdb2..3d8f3a4f16326c02787e20444655b325afa3ef16 100644 (file)
@@ -2169,6 +2169,18 @@ _Py_add_relfile(wchar_t *dirname, const wchar_t *relfile, size_t bufsize)
 }
 
 
+size_t
+_Py_find_basename(const wchar_t *filename)
+{
+    for (size_t i = wcslen(filename); i > 0; --i) {
+        if (filename[i] == SEP) {
+            return i + 1;
+        }
+    }
+    return 0;
+}
+
+
 /* Get the current directory. buflen is the buffer size in wide characters
    including the null character. Decode the path from the locale encoding.
 
index b0d54b0472fb094a50e07023ba48fa836ed4af7d..c916e2f7c0714f15dbffafc2dccc6d0fda5ce2a3 100644 (file)
@@ -739,6 +739,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
 #ifdef MS_WINDOWS
     config->legacy_windows_stdio = -1;
 #endif
+    config->use_frozen_modules = -1;
 }
 
 
@@ -2090,6 +2091,44 @@ config_init_fs_encoding(PyConfig *config, const PyPreConfig *preconfig)
 }
 
 
+/* Determine if the current build is a "development" build (e.g. running
+   out of the source tree) or not.
+
+   A return value of -1 indicates that we do not know.
+  */
+static int
+is_dev_env(PyConfig *config)
+{
+    // This should only ever get called early in runtime initialization,
+    // before the global path config is written.  Otherwise we would
+    // use Py_GetProgramFullPath() and _Py_GetStdlibDir().
+    assert(config != NULL);
+
+    const wchar_t *executable = config->executable;
+    const wchar_t *stdlib = config->stdlib_dir;
+    if (executable == NULL || *executable == L'\0' ||
+            stdlib == NULL || *stdlib == L'\0') {
+        // _PyPathConfig_Calculate() hasn't run yet.
+        return -1;
+    }
+    size_t len = _Py_find_basename(executable);
+    if (wcscmp(executable + len, L"python") != 0 &&
+            wcscmp(executable + len, L"python.exe") != 0) {
+        return 0;
+    }
+    /* If dirname() is the same for both then it is a dev build. */
+    if (len != _Py_find_basename(stdlib)) {
+        return 0;
+    }
+    // We do not bother normalizing the two filenames first since
+    // for config_init_import() is does the right thing as-is.
+    if (wcsncmp(stdlib, executable, len) != 0) {
+        return 0;
+    }
+    return 1;
+}
+
+
 static PyStatus
 config_init_import(PyConfig *config, int compute_path_config)
 {
@@ -2101,25 +2140,28 @@ config_init_import(PyConfig *config, int compute_path_config)
     }
 
     /* -X frozen_modules=[on|off] */
-    const wchar_t *value = config_get_xoption_value(config, L"frozen_modules");
-    if (value == NULL) {
-        // For now we always default to "off".
-        // In the near future we will be factoring in PGO and in-development.
-        config->use_frozen_modules = 0;
-    }
-    else if (wcscmp(value, L"on") == 0) {
-        config->use_frozen_modules = 1;
-    }
-    else if (wcscmp(value, L"off") == 0) {
-        config->use_frozen_modules = 0;
-    }
-    else if (wcslen(value) == 0) {
-        // "-X frozen_modules" and "-X frozen_modules=" both imply "on".
-        config->use_frozen_modules = 1;
-    }
-    else {
-        return PyStatus_Error("bad value for option -X frozen_modules "
-                              "(expected \"on\" or \"off\")");
+    if (config->use_frozen_modules < 0) {
+        const wchar_t *value = config_get_xoption_value(config, L"frozen_modules");
+        if (value == NULL) {
+            int isdev = is_dev_env(config);
+            if (isdev >= 0) {
+                config->use_frozen_modules = !isdev;
+            }
+        }
+        else if (wcscmp(value, L"on") == 0) {
+            config->use_frozen_modules = 1;
+        }
+        else if (wcscmp(value, L"off") == 0) {
+            config->use_frozen_modules = 0;
+        }
+        else if (wcslen(value) == 0) {
+            // "-X frozen_modules" and "-X frozen_modules=" both imply "on".
+            config->use_frozen_modules = 1;
+        }
+        else {
+            return PyStatus_Error("bad value for option -X frozen_modules "
+                                  "(expected \"on\" or \"off\")");
+        }
     }
 
     return _PyStatus_OK();