diff --git a/ci-targets.yaml b/ci-targets.yaml index 0ecc662d..ec08bd9a 100644 --- a/ci-targets.yaml +++ b/ci-targets.yaml @@ -320,6 +320,7 @@ windows: - "3.11" - "3.12" - "3.13" + - "3.14" build_options: - pgo build_options_conditional: @@ -336,6 +337,7 @@ windows: - "3.11" - "3.12" - "3.13" + - "3.14" build_options: - pgo build_options_conditional: diff --git a/cpython-windows/build.py b/cpython-windows/build.py index 4f833a4a..638a2c78 100644 --- a/cpython-windows/build.py +++ b/cpython-windows/build.py @@ -345,6 +345,7 @@ def hack_props( td: pathlib.Path, pcbuild_path: pathlib.Path, arch: str, + python_version: str, ): # TODO can we pass props into msbuild.exe? @@ -355,9 +356,14 @@ def hack_props( sqlite_version = DOWNLOADS["sqlite"]["version"] xz_version = DOWNLOADS["xz"]["version"] zlib_version = DOWNLOADS["zlib"]["version"] - tcltk_commit = DOWNLOADS["tk-windows-bin-8612"]["git_commit"] + mpdecimal_version = DOWNLOADS["mpdecimal"]["version"] + if meets_python_minimum_version(python_version, "3.14"): + tcltk_commit = DOWNLOADS["tk-windows-bin"]["git_commit"] + else: + tcltk_commit = DOWNLOADS["tk-windows-bin-8612"]["git_commit"] + sqlite_path = td / ("sqlite-autoconf-%s" % sqlite_version) bzip2_path = td / ("bzip2-%s" % bzip2_version) libffi_path = td / "libffi" @@ -487,6 +493,7 @@ def hack_project_files( td, pcbuild_path, build_directory, + python_version, ) # Our SQLite directory is named weirdly. This throws off version detection @@ -566,9 +573,13 @@ def hack_project_files( rb'', ) - # We're still on the pre-built tk-windows-bin 8.6.12 which doesn't have a - # standalone zlib DLL. So remove references to it from 3.12+. - if meets_python_minimum_version(python_version, "3.12"): + # Python 3.12+ uses the the pre-built tk-windows-bin 8.6.12 which doesn't + # have a standalone zlib DLL, so we remove references to it. For Python + # 3.14+, we're using tk-windows-bin 8.6.14 which includes a prebuilt zlib + # DLL, so we skip this patch there. + if meets_python_minimum_version( + python_version, "3.12" + ) and meets_python_maximum_version(python_version, "3.13"): static_replace_in_file( pcbuild_path / "_tkinter.vcxproj", rb'<_TclTkDLL Include="$(tcltkdir)\bin\$(tclZlibDllName)" />', @@ -1127,6 +1138,10 @@ def find_additional_dependencies(project: pathlib.Path): if name == "openssl": name = openssl_entry + # On 3.14+, we use the latest tcl/tk version + if ext == "_tkinter" and python_majmin == "314": + name = name.replace("-8612", "") + download_entry = DOWNLOADS[name] # This will raise if no license metadata defined. This is @@ -1196,9 +1211,6 @@ def build_cpython( bzip2_archive = download_entry("bzip2", BUILD) sqlite_archive = download_entry("sqlite", BUILD) - tk_bin_archive = download_entry( - "tk-windows-bin-8612", BUILD, local_name="tk-windows-bin.tar.gz" - ) xz_archive = download_entry("xz", BUILD) zlib_archive = download_entry("zlib", BUILD) @@ -1210,6 +1222,17 @@ def build_cpython( setuptools_wheel = download_entry("setuptools", BUILD) pip_wheel = download_entry("pip", BUILD) + # On CPython 3.14+, we use the latest tcl/tk version which has additional runtime + # dependencies, so we are conservative and use the old version elsewhere. + if meets_python_minimum_version(python_version, "3.14"): + tk_bin_archive = download_entry( + "tk-windows-bin", BUILD, local_name="tk-windows-bin.tar.gz" + ) + else: + tk_bin_archive = download_entry( + "tk-windows-bin-8612", BUILD, local_name="tk-windows-bin.tar.gz" + ) + # CPython 3.13+ no longer uses a bundled `mpdecimal` version so we build it if meets_python_minimum_version(python_version, "3.13"): mpdecimal_archive = download_entry("mpdecimal", BUILD) @@ -1690,6 +1713,7 @@ def main() -> None: "cpython-3.11", "cpython-3.12", "cpython-3.13", + "cpython-3.14", }, default="cpython-3.11", help="Python distribution to build", diff --git a/src/validation.rs b/src/validation.rs index fa19b4d3..1beea181 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -137,6 +137,9 @@ const PE_ALLOWED_LIBRARIES: &[&str] = &[ "tk86t.dll", ]; +// CPython 3.14 uses tcl/tk 8.6.14+ which includes a bundled zlib and dynamically links to msvcrt. +const PE_ALLOWED_LIBRARIES_314: &[&str] = &["msvcrt.dll", "zlib1.dll"]; + static GLIBC_MAX_VERSION_BY_TRIPLE: Lazy>> = Lazy::new(|| { let mut versions = HashMap::new(); @@ -795,6 +798,8 @@ const GLOBAL_EXTENSIONS_WINDOWS: &[&str] = &[ "winsound", ]; +const GLOBAL_EXTENSIONS_WINDOWS_3_14: &[&str] = &["_wmi"]; + const GLOBAL_EXTENSIONS_WINDOWS_PRE_3_13: &[&str] = &["_msi"]; /// Extension modules not present in Windows static builds. @@ -1331,6 +1336,7 @@ fn validate_macho>( fn validate_pe<'data, Pe: ImageNtHeaders>( context: &mut ValidationContext, + python_major_minor: &str, path: &Path, pe: &PeFile<'data, Pe, &'data [u8]>, ) -> Result<()> { @@ -1346,6 +1352,18 @@ fn validate_pe<'data, Pe: ImageNtHeaders>( let lib = import_table.name(descriptor.name.get(object::LittleEndian))?; let lib = String::from_utf8(lib.to_vec())?; + match python_major_minor { + "3.9" | "3.10" | "3.11" | "3.12" | "3.13" => {} + "3.14" => { + if PE_ALLOWED_LIBRARIES_314.contains(&lib.as_str()) { + continue; + } + } + _ => { + panic!("unhandled Python version: {}", python_major_minor); + } + } + if !PE_ALLOWED_LIBRARIES.contains(&lib.as_str()) { context .errors @@ -1451,11 +1469,11 @@ fn validate_possible_object_file( } FileKind::Pe32 => { let file = PeFile32::parse(data)?; - validate_pe(&mut context, path, &file)?; + validate_pe(&mut context, python_major_minor, path, &file)?; } FileKind::Pe64 => { let file = PeFile64::parse(data)?; - validate_pe(&mut context, path, &file)?; + validate_pe(&mut context, python_major_minor, path, &file)?; } _ => {} } @@ -1526,6 +1544,10 @@ fn validate_extension_modules( wanted.extend(GLOBAL_EXTENSIONS_WINDOWS_PRE_3_13); } + if matches!(python_major_minor, "3.14") { + wanted.extend(GLOBAL_EXTENSIONS_WINDOWS_3_14); + } + if static_crt { for x in GLOBAL_EXTENSIONS_WINDOWS_NO_STATIC { wanted.remove(*x);