File: //opt/saltstack/salt/lib/python3.10/site-packages/relenv/build/darwin.py
# Copyright 2025-2026 Broadcom.
# SPDX-License-Identifier: Apache-2
# mypy: ignore-errors
"""
The darwin build process.
"""
from __future__ import annotations
import glob
import io
import os
import pathlib
import shutil
import tarfile
import time
import urllib.request
from typing import IO, TYPE_CHECKING
from ..common import DARWIN, MACOS_DEVELOPMENT_TARGET, arches, runcmd
from .common import (
Dirs,
build_openssl,
build_sqlite,
builds,
finalize,
get_dependency_version,
update_sbom_checksums,
)
if TYPE_CHECKING:
from collections.abc import MutableMapping
ARCHES = arches[DARWIN]
def populate_env(env: MutableMapping[str, str], dirs: Dirs) -> None:
"""
Make sure we have the correct environment variables set.
:param env: The environment dictionary
:type env: dict
:param dirs: The working directories
:type dirs: ``relenv.build.common.Dirs``
"""
env["CC"] = "clang"
ldflags = [
"-Wl,-rpath,{prefix}/lib",
"-L{prefix}/lib",
]
env["LDFLAGS"] = " ".join(ldflags).format(prefix=dirs.prefix)
env["MACOSX_DEPLOYMENT_TARGET"] = MACOS_DEVELOPMENT_TARGET
cflags = [
"-I{prefix}/include",
"-I{prefix}/include/readline",
]
env["CFLAGS"] = " ".join(cflags).format(prefix=dirs.prefix)
def update_expat(dirs: Dirs, env: MutableMapping[str, str]) -> None:
"""
Update the bundled expat library to the latest version.
Python ships with an older bundled expat. This function updates it
to the latest version for security and bug fixes.
"""
# Get version from JSON
expat_info = get_dependency_version("expat", "darwin")
if not expat_info:
# No update needed, use bundled version
return
version = expat_info["version"]
version_tag = version.replace(".", "_")
url = f"https://github.com/libexpat/libexpat/releases/download/R_{version_tag}/expat-{version}.tar.xz"
expat_dir = pathlib.Path(dirs.source) / "Modules" / "expat"
if not expat_dir.exists():
# No expat directory, skip
return
# Download expat tarball
tmpbuild = pathlib.Path(dirs.tmpbuild)
tarball_path = tmpbuild / f"expat-{version}.tar.xz"
urllib.request.urlretrieve(url, str(tarball_path))
# Extract tarball
with tarfile.open(tarball_path) as tar:
tar.extractall(path=str(tmpbuild))
# Copy source files to Modules/expat/
expat_source_dir = tmpbuild / f"expat-{version}" / "lib"
updated_files = []
for source_file in ["*.h", "*.c"]:
for file_path in glob.glob(str(expat_source_dir / source_file)):
target_file = expat_dir / pathlib.Path(file_path).name
# Remove old file if it exists
if target_file.exists():
target_file.unlink()
shutil.copy2(file_path, str(expat_dir))
updated_files.append(target_file)
# Touch all updated files to ensure make rebuilds them
# (The tarball may contain files with newer timestamps)
now = time.time()
for target_file in updated_files:
os.utime(target_file, (now, now))
# For expat >= 2.8.0, new entropy source files are required but not compiled
# by Python's build system. Include them directly in xmlparse.c.
xmlparse_c = expat_dir / "xmlparse.c"
if xmlparse_c.exists():
with open(str(xmlparse_c), "a") as f:
f.write("\n/* Relenv: include new entropy sources for expat >= 2.8.0 */\n")
f.write('#if defined(_WIN32)\n#include "random_rand_s.c"\n#endif\n')
f.write('#if defined(HAVE_GETENTROPY)\n#include "random_getentropy.c"\n#endif\n')
f.write("#if defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM)\n")
f.write('#include "random_getrandom.c"\n#endif\n')
f.write('#if defined(HAVE_ARC4RANDOM_BUF)\n#include "random_arc4random_buf.c"\n#endif\n')
f.write('#if defined(HAVE_ARC4RANDOM)\n#include "random_arc4random.c"\n#endif\n')
f.write('#if !defined(_WIN32) && defined(XML_DEV_URANDOM)\n#include "random_dev_urandom.c"\n#endif\n')
# Update SBOM with correct checksums for updated expat files
files_to_update = {}
for target_file in updated_files:
# SBOM uses relative paths from Python source root
relative_path = f"Modules/expat/{target_file.name}"
files_to_update[relative_path] = target_file
update_sbom_checksums(dirs.source, files_to_update)
def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> None:
"""
Run the commands to build Python.
:param env: The environment dictionary
:type env: dict
:param dirs: The working directories
:type dirs: ``relenv.build.common.Dirs``
:param logfp: A handle for the log file
:type logfp: file
"""
# Update bundled expat to latest version
update_expat(dirs, env)
env["LDFLAGS"] = "-Wl,-rpath,{prefix}/lib {ldflags}".format(prefix=dirs.prefix, ldflags=env["LDFLAGS"])
runcmd(
[
"./configure",
"-v",
f"--prefix={dirs.prefix}",
f"--with-openssl={dirs.prefix}",
"--enable-optimizations",
"--disable-test-modules",
],
env=env,
stderr=logfp,
stdout=logfp,
)
with open("Modules/Setup", "a+") as fp:
fp.seek(0, io.SEEK_END)
fp.write("*disabled*\n_tkinter\nnsl\nncurses\nnis\n")
runcmd(["sed", "s/#zlib/zlib/g", "Modules/Setup"], env=env, stderr=logfp, stdout=logfp)
runcmd(["make", "-j8"], env=env, stderr=logfp, stdout=logfp)
runcmd(["make", "install"], env=env, stderr=logfp, stdout=logfp)
build = builds.add("darwin", populate_env=populate_env)
# Get dependency versions from JSON (with fallback to hardcoded values)
openssl_info = get_dependency_version("openssl", "darwin")
if openssl_info:
openssl_version = openssl_info["version"]
openssl_url = openssl_info["url"]
openssl_checksum = openssl_info["sha256"]
else:
openssl_version = "3.5.4"
openssl_url = "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz"
openssl_checksum = "b75daac8e10f189abe28a076ba5905d363e4801f"
build.add(
"openssl",
build_func=build_openssl,
download={
"url": openssl_url,
"version": openssl_version,
"checksum": openssl_checksum,
},
)
# Get XZ version from JSON
xz_info = get_dependency_version("xz", "darwin")
if xz_info:
xz_version = xz_info["version"]
xz_url = xz_info["url"]
xz_checksum = xz_info["sha256"]
else:
xz_version = "5.8.1"
xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz"
xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931"
build.add(
"XZ",
download={
"url": xz_url,
"version": xz_version,
"checksum": xz_checksum,
},
)
# Get SQLite version from JSON
sqlite_info = get_dependency_version("sqlite", "darwin")
if sqlite_info:
sqlite_url = sqlite_info["url"]
sqlite_checksum = sqlite_info["sha256"]
sqlite_version_num = sqlite_info.get("sqliteversion", "3500400")
else:
sqlite_version_num = "3500400"
sqlite_url = "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz"
sqlite_checksum = "145048005c777796dd8494aa1cfed304e8c34283"
build.add(
name="SQLite",
build_func=build_sqlite,
download={
"url": sqlite_url,
"fallback_url": "https://woz.io/relenv/dependencies/sqlite-autoconf-{version}.tar.gz",
"version": sqlite_version_num,
"checksum": sqlite_checksum,
},
)
build.add(
"python",
build_func=build_python,
wait_on=[
"openssl",
"XZ",
"SQLite",
],
download={
"url": "https://www.python.org/ftp/python/{version}/Python-{version}.tar.xz",
"fallback_url": "https://woz.io/relenv/dependencies/Python-{version}.tar.gz",
"version": build.version,
"checksum": None,
},
)
build.add(
"relenv-finalize",
build_func=finalize,
wait_on=[
"python",
],
)