Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python 3.14 support on Windows #494

Merged
merged 3 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading