You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
abi3t migration howto: Windows link fails with LNK2001 "unresolved external symbol PyInit_<name>" — export-symbol step is missing #158098
The abi3t migration howto instructs authors to remove PyInit_ and define PyModExport_ instead (PEP 793). For build tools without native abi3t support, the "Setting up the build" note says the extension "will later need to name and tag the resulting extension manually," and "Tagging and distribution" covers those two manual steps: the filename and the wheel tag.
On Windows there is a third manual step that isn't documented: the linker export list. setuptools (no abi3t support yet, see pypa/setuptools#5205) passes /EXPORT:PyInit_ to the MSVC linker. Since the abi3t module no longer defines PyInit_, following the guide leads to a guaranteed link failure:
code
LINK : error LNK2001: unresolved external symbol PyInit__awscrt
_awscrt.cp315t-win_amd64.lib : fatal error LNK1120: 1 unresolved externals
This doesn't reproduce on Linux/macOS (no export lists there), so it only surfaces at the end of an otherwise-successful port. We hit it migrating awscrt (awslabs/aws-crt-python#768) after the Linux and macOS abi3t builds were already green.
Workaround (setuptools):
python
class my_build_ext(setuptools.command.build_ext.build_ext):
def get_export_symbols(self, ext):
symbols = super().get_export_symbols(ext)
if BUILDING_ABI3T:
symbols = [s.replace('PyInit_', 'PyModExport_') for s in symbols]
return symbols
Suggested fix: add a sentence to "Tagging and distribution", e.g.:
On Windows, also ensure your build exports PyModExport_ rather than PyInit_; build tools without abi3t support pass /EXPORT:PyInit_ to the linker, which fails because that function no longer exists. With setuptools, override build_ext.get_export_symbols().
I think this is a bug in setuptools/distutils: pypa/distutils#387
Looks like that won't be solved any time soon, which wasn't obvious when the guide was written.
It is not necessary to pass an /EXPORT flag to the linker; the PyMODINIT_FUNC & PyMODEXPORT_FUNC macros now contain the necessary declaration. Could you test if your workaround can be simplified to the following?
defget_export_symbols(self, ext):
return []
But for the guide, I think a better workaround would be adding a dummy PyInit_ function to your extension. This function would only be there to satisfy the linker, it should never be called, so it can always fail. Something like this:
// Workaround for https://github.com/pypa/distutils/issues/387PyMODINIT_FUNCPyInit_<modname>(void)
{
PyErr_SetString(PyExc_SystemError, "PyInit_* called for module with PyModExport_*");
returnNULL;
}
Confirmed the simplified workaround.
Tested on our Windows CI (windows-2022, MSVC 14.44, CPython 3.15.0rc2 free-threaded x64):
With the export list suppressed, the extension links cleanly and PyModExport__awscrt appears in the DLL's export table via PyMODEXPORT_FUNC's dllexport alone, no /EXPORT flag needed. The wheel builds, imports, and passes our test suite.
We went with the get_export_symbols() override rather than the dummy PyInit_ function since we already subclass build_ext for the abi3t wheel tag and .abi3t.so filename handling. The dummy-function approach is probably the better guide recommendation as it's build-tool-agnostic and keeps working for people who don't customize their build.
I recommend you have the dummy set an exception rather than silently returning NULL so any unexpected PyInit_ call surfaces loudly.
Thanks for the pointer to pypa/distutils#387,we've updated our comments to reference it as the root cause.
Activity
picnixz commentedon Sep 24, 2026
cc @encukou
encukou commentedon Sep 24, 2026
Thank you for bringing this up!
I think this is a bug in setuptools/distutils: pypa/distutils#387
Looks like that won't be solved any time soon, which wasn't obvious when the guide was written.
It is not necessary to pass an
/EXPORTflag to the linker; thePyMODINIT_FUNC&PyMODEXPORT_FUNCmacros now contain the necessary declaration. Could you test if your workaround can be simplified to the following?But for the guide, I think a better workaround would be adding a dummy
PyInit_function to your extension. This function would only be there to satisfy the linker, it should never be called, so it can always fail. Something like this:sbSteveK commentedon Sep 24, 2026
Confirmed the simplified workaround.
Tested on our Windows CI (windows-2022, MSVC 14.44, CPython 3.15.0rc2 free-threaded x64):
With the export list suppressed, the extension links cleanly and PyModExport__awscrt appears in the DLL's export table via PyMODEXPORT_FUNC's dllexport alone, no /EXPORT flag needed. The wheel builds, imports, and passes our test suite.
Change: awslabs/aws-crt-python@[commit] (PR awslabs/aws-crt-python#768).
We went with the get_export_symbols() override rather than the dummy PyInit_ function since we already subclass build_ext for the abi3t wheel tag and .abi3t.so filename handling. The dummy-function approach is probably the better guide recommendation as it's build-tool-agnostic and keeps working for people who don't customize their build.
I recommend you have the dummy set an exception rather than silently returning NULL so any unexpected PyInit_ call surfaces loudly.
Thanks for the pointer to pypa/distutils#387,we've updated our comments to reference it as the root cause.
pythongh-158098: Add Setuptools workaround to abi3t migration guide
encukou commentedon Sep 29, 2026
Thanks.
I'd still like to add this to the guide, to help others.
gh-158098: Add Setuptools workaround to abi3t migration guide (#158405)
[3.15] gh-158098: Add Setuptools workaround to abi3t migration guide (G…