diff mbox series

[RFC,v3,05/20] mkvenv: generate console entry shims from inside the venv

Message ID 20230424200248.1183394-6-jsnow@redhat.com (mailing list archive)
State New, archived
Headers show
Series configure: create a python venv and ensure meson, sphinx | expand

Commit Message

John Snow April 24, 2023, 8:02 p.m. UTC
This patch is meant to ensure that console entry point scripts will
always generate on Python 3.7 installations where we may not have access
to importlib.metadata. By running it from a separate process *inside*
the venv, we can be assured to have access to setuptools and by
extension pkg_resources as a fallback.

It isn't strictly necessary to do this for Python 3.8+, which will
always have importlib.metadata available.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 python/scripts/mkvenv.py | 99 ++++++++++++++++++++++++++++++++++------
 1 file changed, 85 insertions(+), 14 deletions(-)
diff mbox series

Patch

diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
index 2172774403..4daa652f12 100644
--- a/python/scripts/mkvenv.py
+++ b/python/scripts/mkvenv.py
@@ -11,6 +11,8 @@ 
 Commands:
   command     Description
     create    create a venv
+    post_init
+              post-venv initialization
 
 --------------------------------------------------
 
@@ -23,6 +25,15 @@ 
   -h, --help  show this help message and exit
   --gen GEN   Regenerate console_scripts for given packages, if found.
 
+--------------------------------------------------
+
+usage: mkvenv post_init [-h] [--gen GEN] [--binpath BINPATH]
+
+options:
+  -h, --help         show this help message and exit
+  --gen GEN          Regenerate console_scripts for given packages, if found.
+  --binpath BINPATH  Path where console script shims should be generated
+
 """
 
 # Copyright (C) 2022-2023 Red Hat, Inc.
@@ -59,6 +70,7 @@ 
 # Do not add any mandatory dependencies from outside the stdlib:
 # This script *must* be usable standalone!
 
+DirType = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
 logger = logging.getLogger("mkvenv")
 
 
@@ -89,23 +101,42 @@  def __init__(self, *args: Any, **kwargs: Any) -> None:
         self.script_packages = kwargs.pop("script_packages", ())
         super().__init__(*args, **kwargs)
 
-        # The EnvBuilder class is cute and toggles this setting off
-        # before post_setup, but we need it to decide if we want to
-        # generate shims or not.
-        self._system_site_packages = self.system_site_packages
+        # Make the context available post-creation:
+        self._context: Optional[SimpleNamespace] = None
+
+    def ensure_directories(self, env_dir: DirType) -> SimpleNamespace:
+        logger.debug("ensure_directories(env_dir=%s)", env_dir)
+        self._context = super().ensure_directories(env_dir)
+        return self._context
+
+    def create(self, env_dir: DirType) -> None:
+        logger.debug("create(env_dir=%s)", env_dir)
+        super().create(env_dir)
+        assert self._context is not None
+        self.post_post_setup(self._context)
 
     def post_setup(self, context: SimpleNamespace) -> None:
         logger.debug("post_setup(...)")
-
-        # Generate console_script entry points for system packages:
-        if self._system_site_packages:
-            generate_console_scripts(
-                context.env_exe, context.bin_path, self.script_packages
-            )
-
         # print the python executable to stdout for configure.
         print(context.env_exe)
 
+    def post_post_setup(self, context: SimpleNamespace) -> None:
+        """
+        The final, final hook. Enter the venv and run commands inside of it.
+        """
+        args = [
+            context.env_exe,
+            __file__,
+            "post_init",
+            "--binpath",
+            context.bin_path,
+        ]
+        if self.system_site_packages:
+            scripts = ",".join(self.script_packages)
+            if scripts:
+                args += ["--gen", scripts]
+        subprocess.run(args, check=True)
+
 
 def need_ensurepip() -> bool:
     """
@@ -359,6 +390,13 @@  def generate_console_scripts(
     """
     Generate script shims for console_script entry points in @packages.
     """
+    logger.debug(
+        "generate_console_scripts(python_path=%s, bin_path=%s, packages=%s)",
+        python_path,
+        bin_path,
+        packages,
+    )
+
     if not packages:
         return
 
@@ -392,6 +430,17 @@  def _get_entry_points() -> Iterator[Dict[str, str]]:
         logger.debug("wrote '%s'", script_path)
 
 
+def post_venv_setup(bin_path: str, packages: Sequence[str] = ()) -> None:
+    """
+    This is intended to be run *inside the venv* after it is created.
+    """
+    python_path = sys.executable
+    logger.debug(
+        "post_venv_setup(bin_path=%s, packages=%s)", bin_path, packages
+    )
+    generate_console_scripts(python_path, bin_path, packages)
+
+
 def _add_create_subcommand(subparsers: Any) -> None:
     subparser = subparsers.add_parser("create", help="create a venv")
     subparser.add_argument(
@@ -408,6 +457,24 @@  def _add_create_subcommand(subparsers: Any) -> None:
     )
 
 
+def _add_post_init_subcommand(subparsers: Any) -> None:
+    subparser = subparsers.add_parser(
+        "post_init", help="post-venv initialization"
+    )
+    subparser.add_argument(
+        "--gen",
+        type=str,
+        action="append",
+        help="Regenerate console_scripts for given packages, if found.",
+    )
+    subparser.add_argument(
+        "--binpath",
+        type=str,
+        action="store",
+        help="Path where console script shims should be generated",
+    )
+
+
 def main() -> int:
     """CLI interface to make_qemu_venv. See module docstring."""
     if os.environ.get("DEBUG") or os.environ.get("GITLAB_CI"):
@@ -429,19 +496,23 @@  def main() -> int:
     )
 
     _add_create_subcommand(subparsers)
+    _add_post_init_subcommand(subparsers)
 
     args = parser.parse_args()
+    script_packages = []
+    for element in args.gen or ():
+        script_packages.extend(element.split(","))
+
     try:
         if args.command == "create":
-            script_packages = []
-            for element in args.gen or ():
-                script_packages.extend(element.split(","))
             make_venv(
                 args.target,
                 system_site_packages=True,
                 clear=True,
                 script_packages=script_packages,
             )
+        if args.command == "post_init":
+            post_venv_setup(args.binpath, script_packages)
         logger.debug("mkvenv.py %s: exiting", args.command)
     except Ouch as exc:
         print("\n*** Ouch! ***\n", file=sys.stderr)