"""Inline-source (``python -c …``) command-line parsing for gateway process identity. Everything after ``-c`` is data for the inline program, not the process's own identity -- except Hermes' own bootstraps, which run an entry point in-process and are mapped back to the equivalent ``python -m `` (see #107002). """ from __future__ import annotations import re def inline_source_flag_index(tokens: list[str]) -> int | None: """Index of the ``-c`` token when *tokens* is an interpreter running INLINE SOURCE, else None. Everything after ``-c`` is data the inline program receives, not this process's own identity. The detached gateway restart watcher (``gateway._spawn_gateway_restart_watcher``) is spawned as ``python -c -m hermes_cli.main gateway run``: its trailing argv is the command the watcher will LATER spawn, so every argv matcher used to read it as a live gateway. See #107002 and the "never infer process identity from argv substrings" rule. Only interpreter options may precede ``-c``; the first non-option token ends the option block (``python -m hermes_cli.main …`` therefore never matches). The walk is VALUE-AWARE: ``-X``/``-W``/``-Q`` and ``--check-hash-based-pycs``/``--jit`` take a SEPARATE operand, so a naive "first non-option token ends the block" walk mistakes that operand for the end of the block and never reaches the ``-c`` behind it (``python -X utf8 -c …`` was still read as a live gateway). The operand sets are the canonical ones in ``hermes_state_holders``, not a second hand-rolled copy. *tokens* must be CASE-PRESERVING: the operand-taking ``-Q``/``-W``/``-X`` differ from the operand-less ``-q``/``-b``, so a lowercased argv would skip the token after a plain ``-q``. """ from hermes_state_holders import ( _PYTHON_LONG_OPTIONS_WITH_OPERANDS, _PYTHON_SHORT_OPTIONS_WITH_OPERANDS, ) index = 1 while index < len(tokens): token = tokens[index] if token == "--": return None if token in _PYTHON_LONG_OPTIONS_WITH_OPERANDS: index += 2 # the next token is this option's operand, not the end of the option block continue if token.startswith("--"): index += 1 # ``--opt=value`` and operand-less long options continue if not token.startswith("-") or token == "-": return None # Clustered short options (``-uc``, ``-IsB``). An operand-taking letter consumes the rest of # the cluster as its attached value, or the following token when the cluster ends there -- # so ``-Xc`` is ``-X c``, NOT an inline-source ``-c``. cluster = token[1:] for position, letter in enumerate(cluster): if letter == "c": return index if letter in _PYTHON_SHORT_OPTIONS_WITH_OPERANDS: index += 1 if cluster[position + 1 :] else 2 break else: index += 1 return None def command_line_runs_inline_source(tokens: list[str]) -> bool: """True when *tokens* is an interpreter running INLINE SOURCE (``python -c [args]``).""" return inline_source_flag_index(tokens) is not None # Hermes' own inline bootstraps hand control to a Hermes entry point IN this process, so the argv # they run with is this process's own identity; every other ``-c`` program keeps its trailing argv # as data (#107002). Each pattern is one emitted source shape, anchored at both ends so a program # merely CARRYING a bootstrap command line (the restart watcher's respawn argv) never matches. _Q = r"""['"]?""" _MAIN = rf"{_Q}__main__{_Q}" _RUN_MODULE = rf"runpy\.run_module\(\s*{_Q}(?P[\w.]+){_Q}\s*,\s*run_name\s*=\s*{_MAIN}\s*,\s*alter_sys\s*=\s*True\s*\)" _BOOTSTRAPS = ( # hermes_cli._launchers.runtime_command (store launcher, the Windows updater's relaunch) ("module", re.compile(rf"import os, sys, runpy;.*\b{_RUN_MODULE}", re.S)), # hermes_cli.venv_sync.relaunch_command: argv is assigned inside the source ("module", re.compile(rf"import sys, runpy; sys\.path\.insert\(.*\b{_RUN_MODULE}", re.S)), ("path", re.compile( rf"import sys, runpy; sys\.path\.insert\(.*\brunpy\.run_path\(\s*{_Q}(?P[^'\"]+?){_Q}\s*,\s*run_name\s*=\s*{_MAIN}\s*\)", re.S)), # hermes_cli._launchers._launcher_script (the published POSIX shell / Windows .cmd launcher) ("entry", re.compile(r"import os, re, sys\s.*\bfrom\s+(?P[\w.]+)\s+import\s+(?P\w+)\b.*\bsys\.exit\(\s*(?P=func)\(\)\s*\)", re.S)), # hermes_cli._launchers._write_cmd_launcher: the launcher script, base64-encoded ("base64", re.compile(rf"import base64; exec\(base64\.b64decode\({_Q}(?P[A-Za-z0-9+/=]+){_Q}\)\)")), ) _ASSIGNED_ARGV = re.compile(r"\bsys\.argv\s*=\s*\[(.*?)\]\s*;") def _bootstrap_entry(source: str, argv: list[str]) -> list[str] | None: """``[-m, , *argv]`` (or ``[, *argv]``) the inline *source* runs in-process, else None.""" source = source.strip() kind, match = next(((k, m) for k, p in _BOOTSTRAPS if (m := p.fullmatch(source))), (None, None)) if match is None: return None target = match["target"] if kind == "base64": import base64 import binascii try: return _bootstrap_entry(base64.b64decode(target, validate=True).decode("utf-8"), argv) except (binascii.Error, UnicodeDecodeError): return None if kind == "entry": # the launcher script's own ``--run-module `` switch return ["-m", argv[1], *argv[2:]] if argv[:1] == ["--run-module"] and len(argv) > 1 else ["-m", target, *argv] if assigned := _ASSIGNED_ARGV.search(source): argv = [item.strip().strip("'\"") for item in assigned.group(1).split(",")][1:] return [target, *argv] if kind == "path" else ["-m", target, *argv] def inline_bootstrap_argv(tokens: list[str]) -> list[str] | None: """*tokens* as the equivalent ``python -m `` when this interpreter's ``-c`` source is a Hermes bootstrap running an entry point in-process; None for any other inline source. Command lines usually arrive space-joined (``/proc``, psutil, ``ps``), which splits the source across tokens; the shortest token run that ends in a recognised tail is the source, whatever joined it, and the tokens after it are the entry point's argv. """ index = inline_source_flag_index(tokens) if index is None: return None for end in range(index + 1, len(tokens)): if tokens[end].rstrip().endswith(")"): entry = _bootstrap_entry(" ".join(tokens[index + 1 : end + 1]), tokens[end + 1 :]) if entry is not None: return [tokens[0], *entry] return None