Skip to content

Commit 658bff8

Browse files
authored
Add Python 3.14 support on Windows (#494)
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
1 parent 6f3ab98 commit 658bff8

File tree

3 files changed

+57
-9
lines changed

3 files changed

+57
-9
lines changed

ci-targets.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ windows:
320320
- "3.11"
321321
- "3.12"
322322
- "3.13"
323+
- "3.14"
323324
build_options:
324325
- pgo
325326
build_options_conditional:
@@ -336,6 +337,7 @@ windows:
336337
- "3.11"
337338
- "3.12"
338339
- "3.13"
340+
- "3.14"
339341
build_options:
340342
- pgo
341343
build_options_conditional:

cpython-windows/build.py

+31-7
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ def hack_props(
345345
td: pathlib.Path,
346346
pcbuild_path: pathlib.Path,
347347
arch: str,
348+
python_version: str,
348349
):
349350
# TODO can we pass props into msbuild.exe?
350351

@@ -355,9 +356,14 @@ def hack_props(
355356
sqlite_version = DOWNLOADS["sqlite"]["version"]
356357
xz_version = DOWNLOADS["xz"]["version"]
357358
zlib_version = DOWNLOADS["zlib"]["version"]
358-
tcltk_commit = DOWNLOADS["tk-windows-bin-8612"]["git_commit"]
359+
359360
mpdecimal_version = DOWNLOADS["mpdecimal"]["version"]
360361

362+
if meets_python_minimum_version(python_version, "3.14"):
363+
tcltk_commit = DOWNLOADS["tk-windows-bin"]["git_commit"]
364+
else:
365+
tcltk_commit = DOWNLOADS["tk-windows-bin-8612"]["git_commit"]
366+
361367
sqlite_path = td / ("sqlite-autoconf-%s" % sqlite_version)
362368
bzip2_path = td / ("bzip2-%s" % bzip2_version)
363369
libffi_path = td / "libffi"
@@ -487,6 +493,7 @@ def hack_project_files(
487493
td,
488494
pcbuild_path,
489495
build_directory,
496+
python_version,
490497
)
491498

492499
# Our SQLite directory is named weirdly. This throws off version detection
@@ -566,9 +573,13 @@ def hack_project_files(
566573
rb'<ClCompile Include="$(opensslIncludeDir)\openssl\applink.c">',
567574
)
568575

569-
# We're still on the pre-built tk-windows-bin 8.6.12 which doesn't have a
570-
# standalone zlib DLL. So remove references to it from 3.12+.
571-
if meets_python_minimum_version(python_version, "3.12"):
576+
# Python 3.12+ uses the the pre-built tk-windows-bin 8.6.12 which doesn't
577+
# have a standalone zlib DLL, so we remove references to it. For Python
578+
# 3.14+, we're using tk-windows-bin 8.6.14 which includes a prebuilt zlib
579+
# DLL, so we skip this patch there.
580+
if meets_python_minimum_version(
581+
python_version, "3.12"
582+
) and meets_python_maximum_version(python_version, "3.13"):
572583
static_replace_in_file(
573584
pcbuild_path / "_tkinter.vcxproj",
574585
rb'<_TclTkDLL Include="$(tcltkdir)\bin\$(tclZlibDllName)" />',
@@ -1127,6 +1138,10 @@ def find_additional_dependencies(project: pathlib.Path):
11271138
if name == "openssl":
11281139
name = openssl_entry
11291140

1141+
# On 3.14+, we use the latest tcl/tk version
1142+
if ext == "_tkinter" and python_majmin == "314":
1143+
name = name.replace("-8612", "")
1144+
11301145
download_entry = DOWNLOADS[name]
11311146

11321147
# This will raise if no license metadata defined. This is
@@ -1196,9 +1211,6 @@ def build_cpython(
11961211

11971212
bzip2_archive = download_entry("bzip2", BUILD)
11981213
sqlite_archive = download_entry("sqlite", BUILD)
1199-
tk_bin_archive = download_entry(
1200-
"tk-windows-bin-8612", BUILD, local_name="tk-windows-bin.tar.gz"
1201-
)
12021214
xz_archive = download_entry("xz", BUILD)
12031215
zlib_archive = download_entry("zlib", BUILD)
12041216

@@ -1210,6 +1222,17 @@ def build_cpython(
12101222
setuptools_wheel = download_entry("setuptools", BUILD)
12111223
pip_wheel = download_entry("pip", BUILD)
12121224

1225+
# On CPython 3.14+, we use the latest tcl/tk version which has additional runtime
1226+
# dependencies, so we are conservative and use the old version elsewhere.
1227+
if meets_python_minimum_version(python_version, "3.14"):
1228+
tk_bin_archive = download_entry(
1229+
"tk-windows-bin", BUILD, local_name="tk-windows-bin.tar.gz"
1230+
)
1231+
else:
1232+
tk_bin_archive = download_entry(
1233+
"tk-windows-bin-8612", BUILD, local_name="tk-windows-bin.tar.gz"
1234+
)
1235+
12131236
# CPython 3.13+ no longer uses a bundled `mpdecimal` version so we build it
12141237
if meets_python_minimum_version(python_version, "3.13"):
12151238
mpdecimal_archive = download_entry("mpdecimal", BUILD)
@@ -1690,6 +1713,7 @@ def main() -> None:
16901713
"cpython-3.11",
16911714
"cpython-3.12",
16921715
"cpython-3.13",
1716+
"cpython-3.14",
16931717
},
16941718
default="cpython-3.11",
16951719
help="Python distribution to build",

src/validation.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ const PE_ALLOWED_LIBRARIES: &[&str] = &[
137137
"tk86t.dll",
138138
];
139139

140+
// CPython 3.14 uses tcl/tk 8.6.14+ which includes a bundled zlib and dynamically links to msvcrt.
141+
const PE_ALLOWED_LIBRARIES_314: &[&str] = &["msvcrt.dll", "zlib1.dll"];
142+
140143
static GLIBC_MAX_VERSION_BY_TRIPLE: Lazy<HashMap<&'static str, version_compare::Version<'static>>> =
141144
Lazy::new(|| {
142145
let mut versions = HashMap::new();
@@ -795,6 +798,8 @@ const GLOBAL_EXTENSIONS_WINDOWS: &[&str] = &[
795798
"winsound",
796799
];
797800

801+
const GLOBAL_EXTENSIONS_WINDOWS_3_14: &[&str] = &["_wmi"];
802+
798803
const GLOBAL_EXTENSIONS_WINDOWS_PRE_3_13: &[&str] = &["_msi"];
799804

800805
/// Extension modules not present in Windows static builds.
@@ -1331,6 +1336,7 @@ fn validate_macho<Mach: MachHeader<Endian = Endianness>>(
13311336

13321337
fn validate_pe<'data, Pe: ImageNtHeaders>(
13331338
context: &mut ValidationContext,
1339+
python_major_minor: &str,
13341340
path: &Path,
13351341
pe: &PeFile<'data, Pe, &'data [u8]>,
13361342
) -> Result<()> {
@@ -1346,6 +1352,18 @@ fn validate_pe<'data, Pe: ImageNtHeaders>(
13461352
let lib = import_table.name(descriptor.name.get(object::LittleEndian))?;
13471353
let lib = String::from_utf8(lib.to_vec())?;
13481354

1355+
match python_major_minor {
1356+
"3.9" | "3.10" | "3.11" | "3.12" | "3.13" => {}
1357+
"3.14" => {
1358+
if PE_ALLOWED_LIBRARIES_314.contains(&lib.as_str()) {
1359+
continue;
1360+
}
1361+
}
1362+
_ => {
1363+
panic!("unhandled Python version: {}", python_major_minor);
1364+
}
1365+
}
1366+
13491367
if !PE_ALLOWED_LIBRARIES.contains(&lib.as_str()) {
13501368
context
13511369
.errors
@@ -1451,11 +1469,11 @@ fn validate_possible_object_file(
14511469
}
14521470
FileKind::Pe32 => {
14531471
let file = PeFile32::parse(data)?;
1454-
validate_pe(&mut context, path, &file)?;
1472+
validate_pe(&mut context, python_major_minor, path, &file)?;
14551473
}
14561474
FileKind::Pe64 => {
14571475
let file = PeFile64::parse(data)?;
1458-
validate_pe(&mut context, path, &file)?;
1476+
validate_pe(&mut context, python_major_minor, path, &file)?;
14591477
}
14601478
_ => {}
14611479
}
@@ -1526,6 +1544,10 @@ fn validate_extension_modules(
15261544
wanted.extend(GLOBAL_EXTENSIONS_WINDOWS_PRE_3_13);
15271545
}
15281546

1547+
if matches!(python_major_minor, "3.14") {
1548+
wanted.extend(GLOBAL_EXTENSIONS_WINDOWS_3_14);
1549+
}
1550+
15291551
if static_crt {
15301552
for x in GLOBAL_EXTENSIONS_WINDOWS_NO_STATIC {
15311553
wanted.remove(*x);

0 commit comments

Comments
 (0)