1622. gopy lifecycle
What we are porting
Five C files, ~11k lines total, that together drive the
runtime from cold start to Py_Initialize() returning, and
back down through Py_Finalize():
Python/pylifecycle.c(~3.6k lines). ThePy_InitializeandPy_Finalizeentry points plus the staged init phases: preinit, core, main. Sets up the runtime, builtins, sys, the initial thread state, and the import system.Python/preconfig.c(~1k lines). Pre-init: locale, encoding detection,-Xflags read before site.py runs.Python/initconfig.c(~4.7k lines). The fullPyConfigschema, env-var and CLI parsing, defaults, validation.Python/pathconfig.c(~600 lines).sys.pathdefaults, prefix/exec_prefix discovery,PYTHONHOMEhandling.Python/interpconfig.c(~300 lines). Per-sub-interpreter config knobs (mostly relevant in v0.13).
The companion Python/pythonrun.c lives in spec 1624 because
its surface (REPL, PyRun_*) is consumer-facing rather than
init-time.
Why this lands in v0.7
In v0.6 the cmd/gopy entry point hand-rolled a tiny boot
sequence: state.NewThread, builtins.Init, then
vm.EvalCode. v0.7 replaces that with the real CPython
lifecycle so gopy -c and gopy script.py both flow through
Py_Initialize / pythonrun / Py_Finalize. Once 1622 is
in place, every later subsystem (import in v0.8, contextvars
in v0.9, monitor in v0.11) can attach via the standard init
hooks instead of bespoke wiring.
Package layout
gopy/
initconfig/ # 1622: preconfig + initconfig
preconfig.go
config.go
config_env.go # PYTHON* env-var parsing
config_cli.go # CLI -X / -c / -m parsing
config_validate.go
pathconfig/ # 1622: path discovery
pathconfig.go
pathconfig_unix.go
pathconfig_win.go
lifecycle/ # 1622: Py_Initialize / Py_Finalize
init.go
init_phases.go
finalize.go
interp.go
main.go # the Py_Main equivalent that cmd/gopy calls
pythonrun/ # 1624: separate spec, listed for context
runstring.go
runfile.go
repl.go
pyc.go
Existing v0.6 packages it touches:
gopy/state: Runtime / Interpreter / Thread already exist but were minimal. Lifecycle wires them through the real init.gopy/builtins:builtins.Initkeeps its signature but becomes one of the init phases instead of the entry point.gopy/cmd/gopy:main.goshrinks to a thinlifecycle.Main(os.Args)call.
Init staging (the porting unit)
CPython splits init into ordered phases. We mirror them one-for-one because import, signals, codecs, and warnings each attach at a specific phase and changing the order would change when their callbacks fire.
| Phase | CPython call | gopy entry |
|---|---|---|
| pre-init: locale + UTF-8 | _PyPreConfig_Read | initconfig.PreConfig.Read |
| pre-init: allocators | _PyPreConfig_InitFromPreConfig | initconfig.PreConfig.Apply |
| core init: runtime alloc | pyinit_core step 1 | lifecycle.initRuntime |
| core init: types | _PyTypes_InitTypes | (handled in objects, called here) |
| core init: builtins | _PyBuiltin_Init | builtins.Init (already exists) |
| core init: sys | _PySys_Create | sysmod.Init (1651) |
| core init: import system | init_importlib_external | (deferred to v0.8 spec 1623) |
| main init: site/userpath | init_set_builtins_open, pymain_run_* | pythonrun.* (1624) |
| finalize: shutdown phases | Py_FinalizeEx | lifecycle.Finalize |
Each row maps to one Go function with a CPython citation. The spec 1622 work order is top-to-bottom: a phase only lands when the phase above it lands.
v0.7 release blockers
Tracked as individual tasks; each one must close before tagging v0.7.0.
1622-APortPyPreConfigstruct + defaults +PyPreConfig_InitPythonConfig. CPython:Python/preconfig.c:33 PyPreConfig_InitPythonConfig.1622-BPort thePYTHON*env-var reader subset gopy honours (PYTHONHOME,PYTHONPATH,PYTHONHASHSEED,PYTHONDONTWRITEBYTECODE,PYTHONUNBUFFERED,PYTHONUTF8,PYTHONDEBUG,PYTHONVERBOSE,PYTHONOPTIMIZE,PYTHONNOUSERSITE). CPython:Python/initconfig.c:_Py_str_to_intpanel.1622-CPort thePyConfigstruct (the v0.7 subset; full schema lands incrementally with later phases).1622-DPortPyConfig_Read(env then CLI then defaults). CPython:Python/initconfig.c:_PyConfig_Read.1622-EPortpyinit_coreandpyinit_mainskeletons. CPython:Python/pylifecycle.c:pyinit_core,Python/pylifecycle.c:pyinit_main.1622-FPort the path-config defaults fordarwinandlinux(Windows arrives later). CPython:Modules/getpath.pyis the modern source: gopy reimplements the resolved logic in Go.1622-GPortPy_Finalizeshutdown order (modules, sys, threads, types, runtime). CPython:Python/pylifecycle.c:Py_FinalizeEx.1622-Hlifecycle.Mainentry: parses args, runs init, hands off topythonrun, runs finalize. CPython:Modules/main.c:Py_Main.
Test gates
lifecycle/init_test.go: Initialize, ask forstate.GetThreadState(), Finalize, Initialize again. Must not panic and must not leak goroutines (counted viaruntime.NumGoroutine).lifecycle/main_test.go: feed[]string{"-c", "print(1+2)"}throughlifecycle.Mainwith a captured stdout. Output is"3\n"and exit code 0.initconfig/config_test.go: env-var precedence: a flag set in env is overridden by a CLI value; CLI is overridden by an explicitPyConfigfield set afterRead.pathconfig/pathconfig_unix_test.go: given a synthetic prefix layout (a temp dir withlib/python3.14/), the resolvedsys.pathmatches the documented order fromDoc/using/cmdline.rst.
Out of scope for v0.7
Py_NewInterpreterand the sub-interpreter init flow stays stubbed; spec 1622 lands a single-interpreter shape.- The
pymain_run_moduleimport-driven case (gopy -m foo) is gated on import (v0.8); v0.7 returnsErrNotImplemented. Py_BytesMain(the wide-char Windows entry) is deferred.
CPython divergences worth pinning here
- gopy never reads C-level allocator hooks (
PyMemAllocatorEx) because Go owns allocation. The fields exist onPyPreConfigfor shape parity but have no effect. sys.implementation.cache_tagis"gopy-3140"(see 1690_quirks). The init code that buildssys.implementationmust hard-code this string and pin it in tests.