Skip to content

Commit 43a1a4d

Browse files
committed
windows: build statically linked libffi
Up until this point, static builds for Python 3.8 have been broken because of issues linking libffi. This commit unbusts things. We teach libffi to build a static library. This entailed changing some defines so declspec(dllexport) isn't used. We also had to opt in to a statically linked CRT. And disable a shared library. With the static libffi.lib being produced, we had to teach our hacks to the static build configuration to link it. With all that in place, we're able to build a statically linked Python 3.8!
1 parent 766fca9 commit 43a1a4d

File tree

1 file changed

+81
-6
lines changed

1 file changed

+81
-6
lines changed

cpython-windows/build.py

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,13 @@ def copy_link_to_lib(p: pathlib.Path):
622622
"""
623623

624624

625-
def hack_props(td: pathlib.Path, pcbuild_path: pathlib.Path, arch: str, static: bool):
625+
def hack_props(
626+
td: pathlib.Path,
627+
pcbuild_path: pathlib.Path,
628+
arch: str,
629+
static: bool,
630+
building_libffi: bool,
631+
):
626632
# TODO can we pass props into msbuild.exe?
627633

628634
# Our dependencies are in different directories from what CPython's
@@ -724,18 +730,37 @@ def hack_props(td: pathlib.Path, pcbuild_path: pathlib.Path, arch: str, static:
724730
b"<_DLLSuffix>-1_1-%s</_DLLSuffix>" % suffix,
725731
)
726732

733+
libffi_props = pcbuild_path / "libffi.props"
734+
735+
if static and building_libffi:
736+
# For some reason the built .lib doesn't have the -7 suffix in
737+
# static build mode. This is possibly a side-effect of CPython's
738+
# libffi build script not officially supporting static-only builds.
739+
static_replace_in_file(
740+
libffi_props,
741+
b"<AdditionalDependencies>libffi-7.lib;%(AdditionalDependencies)</AdditionalDependencies>",
742+
b"<AdditionalDependencies>libffi.lib;%(AdditionalDependencies)</AdditionalDependencies>",
743+
)
744+
727745

728746
def hack_project_files(
729747
td: pathlib.Path,
730748
cpython_source_path: pathlib.Path,
731749
build_directory: str,
732750
static: bool,
751+
building_libffi: bool,
733752
):
734753
"""Hacks Visual Studio project files to work with our build."""
735754

736755
pcbuild_path = cpython_source_path / "PCbuild"
737756

738-
hack_props(td, pcbuild_path, build_directory, static=static)
757+
hack_props(
758+
td,
759+
pcbuild_path,
760+
build_directory,
761+
static=static,
762+
building_libffi=building_libffi,
763+
)
739764

740765
# Our SQLite directory is named weirdly. This throws off version detection
741766
# in the project file. Replace the parsing logic with a static string.
@@ -797,6 +822,24 @@ def hack_project_files(
797822
br'<ClCompile Include="$(opensslIncludeDir)\openssl\applink.c">',
798823
)
799824

825+
pythoncore_proj = pcbuild_path / "pythoncore.vcxproj"
826+
827+
# normally the _ctypes extension/project pulls in libffi via
828+
# <Link><AdditionalDependencies>. However, as part of converting this
829+
# extension to static, we lose the transitive dependency. Here, we
830+
# hack pythoncore as a one-off to add the dependency. Ideally we would
831+
# handle this when hacking the extension's project. But it is easier to
832+
# do here.
833+
if static and building_libffi:
834+
libffi_path = td / "libffi" / "libffi.lib"
835+
static_replace_in_file(
836+
pythoncore_proj,
837+
b"<AdditionalDependencies>version.lib;shlwapi.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>",
838+
b"<AdditionalDependencies>version.lib;shlwapi.lib;ws2_32.lib;"
839+
+ bytes(libffi_path)
840+
+ b";%(AdditionalDependencies)</AdditionalDependencies>",
841+
)
842+
800843
if static:
801844
for extension, entry in sorted(CONVERT_TO_BUILTIN_EXTENSIONS.items()):
802845
if entry.get("ignore_static"):
@@ -810,7 +853,6 @@ def hack_project_files(
810853

811854
# pythoncore.vcxproj produces libpython. Typically pythonXY.dll. We change
812855
# it to produce a static library.
813-
pythoncore_proj = pcbuild_path / "pythoncore.vcxproj"
814856
pyproject_props = pcbuild_path / "pyproject.props"
815857

816858
# Need to replace Py_ENABLE_SHARED with Py_NO_ENABLE_SHARED so symbol
@@ -1314,7 +1356,11 @@ def build_openssl(
13141356

13151357

13161358
def build_libffi(
1317-
python: str, arch: str, sh_exe: pathlib.Path, dest_archive: pathlib.Path
1359+
python: str,
1360+
arch: str,
1361+
sh_exe: pathlib.Path,
1362+
dest_archive: pathlib.Path,
1363+
static: bool,
13181364
):
13191365
with tempfile.TemporaryDirectory(prefix="libffi-build-") as td:
13201366
td = pathlib.Path(td)
@@ -1355,6 +1401,23 @@ def build_libffi(
13551401
/ "prepare_libffi.bat"
13561402
)
13571403

1404+
if static:
1405+
# We replace FFI_BUILDING_DLL with FFI_BUILDING so
1406+
# declspec(dllexport) isn't used.
1407+
# We add USE_STATIC_RTL to force static linking of the crt.
1408+
static_replace_in_file(
1409+
prepare_libffi,
1410+
b"CPPFLAGS='-DFFI_BUILDING_DLL'",
1411+
b"CPPFLAGS='-DFFI_BUILDING -DUSE_STATIC_RTL'",
1412+
)
1413+
1414+
# We also need to tell configure to only build a static library.
1415+
static_replace_in_file(
1416+
prepare_libffi,
1417+
b"--build=$BUILD --host=$HOST;",
1418+
b"--build=$BUILD --host=$HOST --disable-shared;",
1419+
)
1420+
13581421
env = dict(os.environ)
13591422
env["LIBFFI_SOURCE"] = str(ffi_source_path)
13601423
env["VCVARSALL"] = str(find_vcvarsall_path())
@@ -1776,7 +1839,13 @@ def build_cpython(
17761839

17771840
builtin_extensions = parse_config_c(config_c)
17781841

1779-
hack_project_files(td, cpython_source_path, build_directory, static=static)
1842+
hack_project_files(
1843+
td,
1844+
cpython_source_path,
1845+
build_directory,
1846+
static=static,
1847+
building_libffi=libffi_archive is not None,
1848+
)
17801849
hack_source_files(cpython_source_path, static=static)
17811850

17821851
if pgo:
@@ -2114,7 +2183,13 @@ def main():
21142183
"libffi-%s-%s.tar" % (target_triple, args.profile)
21152184
)
21162185
if not libffi_archive.exists():
2117-
build_libffi(args.python, arch, pathlib.Path(args.sh), libffi_archive)
2186+
build_libffi(
2187+
args.python,
2188+
arch,
2189+
pathlib.Path(args.sh),
2190+
libffi_archive,
2191+
"static" in args.profile,
2192+
)
21182193
else:
21192194
libffi_archive = None
21202195

0 commit comments

Comments
 (0)