Skip to content

Commit 6aefbd1

Browse files
committed
[mypyc] Fix separate=True: cached-SCC second pass returns empty Extensions
When mypycify is called twice in the same source tree (typical of pip's wheel build, which runs setup.py for "Preparing metadata" and then again for "Building wheel"), the second call hits the mypy metadata cache and ends up with every state's `tree` still None. The SCC loop in compile_modules_to_ir then sees `trees == []` for every SCC and silently skips them, so compile_modules_to_ir returns an empty `modules` dict. Downstream, compile_ir_to_c sets `ctext[group_name] = []` for every group, mypyc_build's loop produces empty cfilenames, and the resulting Extensions ship with `sources=[]`. setuptools then runs the linker with no input and produces a stub .so, so the installed module is missing PyInit_<name>__mypyc and explodes on import. Fix: when ctext is empty for a group but the .c file from the previous mypycify pass is still on disk (which it always is for a fully-cached build), reuse it. The path mirrors the one that GroupGenerator.generate_c_for_modules emits. Reproducer: `pip wheel ./sqlglotc` from sqlglot's repo with separate=True; first build succeeds, second build (or pip's two-phase metadata-then-wheel flow on a fresh checkout) yields a wheel with all shared libs missing PyInit_*.
1 parent 812a3c8 commit 6aefbd1

1 file changed

Lines changed: 18 additions & 1 deletion

File tree

mypyc/build.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ def mypyc_build(
564564
# Write out the generated C and collect the files for each group
565565
# Should this be here??
566566
group_cfilenames: list[tuple[list[str], list[str]]] = []
567-
for cfiles in group_cfiles:
567+
for (group_sources, group_name), cfiles in zip(groups, group_cfiles):
568568
cfilenames = []
569569
for cfile, ctext in cfiles:
570570
cfile = os.path.join(compiler_options.target_dir, cfile)
@@ -573,6 +573,23 @@ def mypyc_build(
573573
if os.path.splitext(cfile)[1] == ".c":
574574
cfilenames.append(cfile)
575575

576+
# Fully-cached mypy build (typical of pip's second setup.py invocation
577+
# for the wheel-build phase): mypyc returns an empty ctext for the
578+
# group, but the .c file from the previous run is still on disk.
579+
# Reuse it so the resulting Extension isn't built with sources=[].
580+
# Mirrors the path that GroupGenerator.generate_c_for_modules emits.
581+
if not cfilenames and group_name is not None:
582+
from mypyc.codegen.emitmodule import group_dir as _group_dir
583+
584+
short_suffix = "_" + exported_name(group_name.split(".")[-1])
585+
existing = os.path.join(
586+
compiler_options.target_dir,
587+
_group_dir(group_name),
588+
f"__native{short_suffix}.c",
589+
)
590+
if os.path.exists(existing):
591+
cfilenames.append(existing)
592+
576593
deps = [os.path.join(compiler_options.target_dir, dep) for dep in get_header_deps(cfiles)]
577594
group_cfilenames.append((cfilenames, deps))
578595

0 commit comments

Comments
 (0)