Skip to content

Commit

Permalink
Add Python 3.14 support on Windows (#494)
Browse files Browse the repository at this point in the history
The upstream build system now requires the latest tcl/tk and we want to
upgrade anyway, so that happens here as well. There are some concerns
about the new zlib/msvcrt dependencies, so I'm limiting these to the
3.14a to see how it goes.

See #495
  • Loading branch information
zanieb authored Jan 14, 2025
1 parent 6f3ab98 commit 658bff8
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 9 deletions.
2 changes: 2 additions & 0 deletions ci-targets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ windows:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
build_options:
- pgo
build_options_conditional:
Expand All @@ -336,6 +337,7 @@ windows:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
build_options:
- pgo
build_options_conditional:
Expand Down
38 changes: 31 additions & 7 deletions cpython-windows/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -566,9 +573,13 @@ def hack_project_files(
rb'<ClCompile Include="$(opensslIncludeDir)\openssl\applink.c">',
)

# 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)" />',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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",
Expand Down
26 changes: 24 additions & 2 deletions src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HashMap<&'static str, version_compare::Version<'static>>> =
Lazy::new(|| {
let mut versions = HashMap::new();
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1331,6 +1336,7 @@ fn validate_macho<Mach: MachHeader<Endian = Endianness>>(

fn validate_pe<'data, Pe: ImageNtHeaders>(
context: &mut ValidationContext,
python_major_minor: &str,
path: &Path,
pe: &PeFile<'data, Pe, &'data [u8]>,
) -> Result<()> {
Expand All @@ -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
Expand Down Expand Up @@ -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)?;
}
_ => {}
}
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 658bff8

Please sign in to comment.