Skip to content

Commit d60a6df

Browse files
committed
dev-python/setuptools: Prevent object file collisions in parallel extension builds
Parallel builds of extensions that share source files may write to the same object file paths under a common build directory, resulting in race conditions and non-deterministic build outputs. Use a per-extension subdirectory within build_temp to isolate object files and ensure deterministic, parallel-safe builds. Bug: https://bugs.gentoo.org/967476 Bug: https://bugs.gentoo.org/945376 See-also: pypa/setuptools#5132 Signed-off-by: Lukas Schmelting <[email protected]>
1 parent c80fb4d commit d60a6df

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
From dcfabbcbab03eaf03a31b75de610088973f9473d Mon Sep 17 00:00:00 2001
2+
From: Lukas Schmelting <[email protected]>
3+
Date: Sat, 20 Dec 2025 00:34:29 +0100
4+
Subject: [PATCH] Prevent object file collisions in parallel extension builds
5+
6+
Parallel builds of extensions that share source files may write to the same
7+
object file paths under a common build directory, resulting in race conditions
8+
and non-deterministic build outputs.
9+
10+
Use a per-extension subdirectory within build_temp to isolate object files and
11+
ensure deterministic, parallel-safe builds.
12+
---
13+
setuptools/_distutils/command/build_ext.py | 7 +++++-- 1 file changed, 5
14+
insertions(+), 2 deletions(-)
15+
16+
diff --git a/setuptools/_distutils/command/build_ext.py b/setuptools/_distutils/command/build_ext.py
17+
index ec45b44..f82fb5f 100644
18+
--- a/setuptools/_distutils/command/build_ext.py
19+
+++ b/setuptools/_distutils/command/build_ext.py
20+
@@ -562,9 +562,12 @@ class build_ext(Command):
21+
for undef in ext.undef_macros:
22+
macros.append((undef,))
23+
24+
+ # Per-extension build dir to avoid conflicts in parallel builds.
25+
+ ext_build_temp = os.path.join(self.build_temp, ext.name.replace(".", "_"))
26+
+
27+
objects = self.compiler.compile(
28+
sources,
29+
- output_dir=self.build_temp,
30+
+ output_dir=ext_build_temp,
31+
macros=macros,
32+
include_dirs=ext.include_dirs,
33+
debug=self.debug,
34+
@@ -595,7 +598,7 @@ class build_ext(Command):
35+
extra_postargs=extra_args,
36+
export_symbols=self.get_export_symbols(ext),
37+
debug=self.debug,
38+
- build_temp=self.build_temp,
39+
+ build_temp=ext_build_temp,
40+
target_lang=language,
41+
)
42+
43+
--
44+
2.52.0
45+
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 1999-2025 Gentoo Authors
2+
# Distributed under the terms of the GNU General Public License v2
3+
4+
# please keep this ebuild at EAPI 8 -- sys-apps/portage dep
5+
EAPI=8
6+
7+
# please bump dev-python/ensurepip-setuptools along with this package!
8+
9+
DISTUTILS_USE_PEP517=standalone
10+
PYTHON_TESTED=( python3_{11..14} pypy3_11 )
11+
PYTHON_COMPAT=( "${PYTHON_TESTED[@]}" python3_{13,14}t )
12+
PYTHON_REQ_USE="xml(+)"
13+
14+
inherit distutils-r1 pypi
15+
16+
DESCRIPTION="Collection of extensions to Distutils"
17+
HOMEPAGE="
18+
https://github.com/pypa/setuptools/
19+
https://pypi.org/project/setuptools/
20+
"
21+
22+
LICENSE="MIT"
23+
SLOT="0"
24+
KEYWORDS="~alpha ~amd64 ~arm ~arm64 ~hppa ~loong ~m68k ~mips ~ppc ~ppc64 ~riscv ~s390 ~sparc ~x86 ~arm64-macos ~x64-macos ~x64-solaris"
25+
IUSE="test"
26+
RESTRICT="!test? ( test )"
27+
28+
RDEPEND="
29+
dev-python/jaraco-collections[${PYTHON_USEDEP}]
30+
>=dev-python/jaraco-functools-4[${PYTHON_USEDEP}]
31+
>=dev-python/jaraco-text-3.7.0-r1[${PYTHON_USEDEP}]
32+
>=dev-python/more-itertools-8.12.0-r1[${PYTHON_USEDEP}]
33+
>=dev-python/packaging-24.2[${PYTHON_USEDEP}]
34+
>=dev-python/platformdirs-4.2.2[${PYTHON_USEDEP}]
35+
>=dev-python/wheel-0.44.0[${PYTHON_USEDEP}]
36+
"
37+
BDEPEND="
38+
${RDEPEND}
39+
test? (
40+
$(python_gen_cond_dep '
41+
>=dev-python/build-1.0.3[${PYTHON_USEDEP}]
42+
>=dev-python/ini2toml-0.14[${PYTHON_USEDEP}]
43+
>=dev-python/filelock-3.4.0[${PYTHON_USEDEP}]
44+
>=dev-python/jaraco-envs-2.2[${PYTHON_USEDEP}]
45+
>=dev-python/jaraco-path-3.7.2[${PYTHON_USEDEP}]
46+
>=dev-python/jaraco-test-5.5[${PYTHON_USEDEP}]
47+
dev-python/pip[${PYTHON_USEDEP}]
48+
dev-python/pyproject-hooks[${PYTHON_USEDEP}]
49+
dev-python/pytest[${PYTHON_USEDEP}]
50+
>=dev-python/pytest-home-0.5[${PYTHON_USEDEP}]
51+
dev-python/pytest-subprocess[${PYTHON_USEDEP}]
52+
dev-python/pytest-timeout[${PYTHON_USEDEP}]
53+
dev-python/pytest-xdist[${PYTHON_USEDEP}]
54+
>=dev-python/tomli-w-1.0.0[${PYTHON_USEDEP}]
55+
>=dev-python/virtualenv-20[${PYTHON_USEDEP}]
56+
' "${PYTHON_TESTED[@]}")
57+
)
58+
"
59+
# setuptools-scm is here because installing plugins apparently breaks stuff at
60+
# runtime, so let's pull it early. See bug #663324.
61+
#
62+
# trove-classifiers are optionally used in validation, if they are
63+
# installed. Since we really oughtn't block them, let's always enforce
64+
# the newest version for the time being to avoid errors.
65+
# https://github.com/pypa/setuptools/issues/4459
66+
PDEPEND="
67+
dev-python/setuptools-scm[${PYTHON_USEDEP}]
68+
>=dev-python/trove-classifiers-2024.10.16[${PYTHON_USEDEP}]
69+
"
70+
71+
src_prepare() {
72+
local PATCHES=(
73+
# https://github.com/abravalheri/validate-pyproject/pull/221
74+
"${FILESDIR}/setuptools-75.6.0-disable-trove-classifiers.patch"
75+
"${FILESDIR}/fix-parallel-extension-build-race-condition.patch"
76+
)
77+
78+
distutils-r1_src_prepare
79+
80+
# remove bundled dependencies
81+
rm -r */_vendor || die
82+
}
83+
84+
python_test() {
85+
if ! has "${EPYTHON}" "${PYTHON_TESTED[@]/_/.}"; then
86+
return
87+
fi
88+
89+
local EPYTEST_DESELECT=(
90+
# broken by unbundling (e.g. installs self-wheel into venv)
91+
setuptools/tests/config/test_apply_pyprojecttoml.py::TestMeta
92+
setuptools/tests/test_distutils_adoption.py
93+
setuptools/tests/test_editable_install.py
94+
setuptools/tests/test_sdist.py::test_sanity_check_setuptools_own_sdist
95+
setuptools/tests/test_setuptools.py::test_wheel_includes_vendored_metadata
96+
setuptools/tests/test_virtualenv.py::test_no_missing_dependencies
97+
# TODO
98+
setuptools/tests/config/test_setupcfg.py::TestConfigurationReader::test_basic
99+
setuptools/tests/config/test_setupcfg.py::TestConfigurationReader::test_ignore_errors
100+
# TODO, probably some random package
101+
setuptools/tests/config/test_setupcfg.py::TestOptions::test_cmdclass
102+
# relies on -Werror
103+
setuptools/_static.py::setuptools._static.Dict
104+
setuptools/_static.py::setuptools._static.List
105+
)
106+
107+
local EPYTEST_XDIST=1
108+
local -x PRE_BUILT_SETUPTOOLS_WHEEL=${DISTUTILS_WHEEL_PATH}
109+
local EPYTEST_PLUGINS=( pytest-{home,subprocess,timeout} )
110+
epytest -o tmp_path_retention_policy=all \
111+
-m "not uses_network" setuptools
112+
}

0 commit comments

Comments
 (0)