Skip to content

Commit 85bbad4

Browse files
authored
Merge branch 'main' into debt/remove-easy-install
2 parents b7c2e03 + 9653305 commit 85bbad4

File tree

11 files changed

+120
-317
lines changed

11 files changed

+120
-317
lines changed

.bumpversion.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 79.0.0
2+
current_version = 79.0.1
33
commit = True
44
tag = True
55

NEWS.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
v79.0.1
2+
=======
3+
4+
Bugfixes
5+
--------
6+
7+
- Merge with pypa/distutils@24bd3179b including fix for pypa/distutils#355.
8+
9+
110
v79.0.0
211
=======
312

newsfragments/4955.removal.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Develop command no longer uses easy_install, but instead defers execution to pip (which then will re-invoke Setuptools via PEP 517 to build the editable wheel). Most of the options to develop are dropped. This is the final warning before the command is dropped completely in a few months. Use-cases relying on 'setup.py develop' should pin to older Setuptools version or migrate to modern build tooling.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ backend-path = ["."]
55

66
[project]
77
name = "setuptools"
8-
version = "79.0.0"
8+
version = "79.0.1"
99
authors = [
1010
{ name = "Python Packaging Authority", email = "[email protected]" },
1111
]

pytest.ini

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ filterwarnings=
5252
# https://github.com/pypa/setuptools/issues/917
5353
ignore:setup.py install is deprecated.
5454
ignore:easy_install command is deprecated.
55+
ignore:develop command is deprecated.
5556

5657
# https://github.com/pypa/setuptools/issues/2497
5758
ignore:.* is an invalid version and will not be supported::pkg_resources

setuptools/_distutils/command/config.py

-2
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ def _check_compiler(self):
8989
"""Check that 'self.compiler' really is a CCompiler object;
9090
if not, make it one.
9191
"""
92-
# We do this late, and only on-demand, because this is an expensive
93-
# import.
9492
if not isinstance(self.compiler, CCompiler):
9593
self.compiler = new_compiler(
9694
compiler=self.compiler, dry_run=self.dry_run, force=True

setuptools/_distutils/compilers/C/tests/test_unix.py

+63
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,69 @@ def gcvs(*args, _orig=sysconfig.get_config_vars):
244244
sysconfig.customize_compiler(self.cc)
245245
assert self.cc.linker_so[0] == 'my_cc'
246246

247+
@pytest.mark.skipif('platform.system == "Windows"')
248+
def test_cxx_commands_used_are_correct(self):
249+
def gcv(v):
250+
if v == 'LDSHARED':
251+
return 'ccache gcc-4.2 -bundle -undefined dynamic_lookup'
252+
elif v == 'LDCXXSHARED':
253+
return 'ccache g++-4.2 -bundle -undefined dynamic_lookup'
254+
elif v == 'CXX':
255+
return 'ccache g++-4.2'
256+
elif v == 'CC':
257+
return 'ccache gcc-4.2'
258+
return ''
259+
260+
def gcvs(*args, _orig=sysconfig.get_config_vars):
261+
if args:
262+
return list(map(sysconfig.get_config_var, args))
263+
return _orig() # pragma: no cover
264+
265+
sysconfig.get_config_var = gcv
266+
sysconfig.get_config_vars = gcvs
267+
with (
268+
mock.patch.object(self.cc, 'spawn', return_value=None) as mock_spawn,
269+
mock.patch.object(self.cc, '_need_link', return_value=True),
270+
mock.patch.object(self.cc, 'mkpath', return_value=None),
271+
EnvironmentVarGuard() as env,
272+
):
273+
# override environment overrides in case they're specified by CI
274+
del env['CXX']
275+
del env['LDCXXSHARED']
276+
277+
sysconfig.customize_compiler(self.cc)
278+
assert self.cc.linker_so_cxx[0:2] == ['ccache', 'g++-4.2']
279+
assert self.cc.linker_exe_cxx[0:2] == ['ccache', 'g++-4.2']
280+
self.cc.link(None, [], 'a.out', target_lang='c++')
281+
call_args = mock_spawn.call_args[0][0]
282+
expected = ['ccache', 'g++-4.2', '-bundle', '-undefined', 'dynamic_lookup']
283+
assert call_args[:5] == expected
284+
285+
self.cc.link_executable([], 'a.out', target_lang='c++')
286+
call_args = mock_spawn.call_args[0][0]
287+
expected = ['ccache', 'g++-4.2', '-o', self.cc.executable_filename('a.out')]
288+
assert call_args[:4] == expected
289+
290+
env['LDCXXSHARED'] = 'wrapper g++-4.2 -bundle -undefined dynamic_lookup'
291+
env['CXX'] = 'wrapper g++-4.2'
292+
sysconfig.customize_compiler(self.cc)
293+
assert self.cc.linker_so_cxx[0:2] == ['wrapper', 'g++-4.2']
294+
assert self.cc.linker_exe_cxx[0:2] == ['wrapper', 'g++-4.2']
295+
self.cc.link(None, [], 'a.out', target_lang='c++')
296+
call_args = mock_spawn.call_args[0][0]
297+
expected = ['wrapper', 'g++-4.2', '-bundle', '-undefined', 'dynamic_lookup']
298+
assert call_args[:5] == expected
299+
300+
self.cc.link_executable([], 'a.out', target_lang='c++')
301+
call_args = mock_spawn.call_args[0][0]
302+
expected = [
303+
'wrapper',
304+
'g++-4.2',
305+
'-o',
306+
self.cc.executable_filename('a.out'),
307+
]
308+
assert call_args[:4] == expected
309+
247310
@pytest.mark.skipif('platform.system == "Windows"')
248311
@pytest.mark.usefixtures('disable_macos_customization')
249312
def test_cc_overrides_ldshared_for_cxx_correctly(self):

setuptools/_distutils/compilers/C/unix.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -286,19 +286,18 @@ def link(
286286
# building an executable or linker_so (with shared options)
287287
# when building a shared library.
288288
building_exe = target_desc == base.Compiler.EXECUTABLE
289+
target_cxx = target_lang == "c++"
289290
linker = (
290-
self.linker_exe
291+
(self.linker_exe_cxx if target_cxx else self.linker_exe)
291292
if building_exe
292-
else (
293-
self.linker_so_cxx if target_lang == "c++" else self.linker_so
294-
)
293+
else (self.linker_so_cxx if target_cxx else self.linker_so)
295294
)[:]
296295

297-
if target_lang == "c++" and self.compiler_cxx:
296+
if target_cxx and self.compiler_cxx:
298297
env, linker_ne = _split_env(linker)
299298
aix, linker_na = _split_aix(linker_ne)
300299
_, compiler_cxx_ne = _split_env(self.compiler_cxx)
301-
_, linker_exe_ne = _split_env(self.linker_exe)
300+
_, linker_exe_ne = _split_env(self.linker_exe_cxx)
302301

303302
params = _linker_params(linker_na, linker_exe_ne)
304303
linker = env + aix + compiler_cxx_ne + params

setuptools/command/develop.py

+39-179
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,55 @@
1-
import glob
2-
import os
1+
import site
2+
import subprocess
3+
import sys
34

4-
import setuptools
5-
from setuptools import _normalization, _path, namespaces
6-
from setuptools.command.easy_install import easy_install
5+
from setuptools import Command
6+
from setuptools.warnings import SetuptoolsDeprecationWarning
77

8-
from ..unicode_utils import _read_utf8_with_fallback
98

10-
from distutils import log
11-
from distutils.errors import DistutilsOptionError
12-
from distutils.util import convert_path
13-
14-
15-
class develop(namespaces.DevelopInstaller, easy_install):
9+
class develop(Command):
1610
"""Set up package for development"""
1711

18-
description = "install package in 'development mode'"
19-
20-
user_options = easy_install.user_options + [
21-
("uninstall", "u", "Uninstall this source package"),
22-
("egg-path=", None, "Set the path to be used in the .egg-link file"),
12+
user_options = [
13+
("install-dir=", "d", "install package to DIR"),
14+
('no-deps', 'N', "don't install dependencies"),
15+
('user', None, f"install in user site-package '{site.USER_SITE}'"),
16+
('prefix=', None, "installation prefix"),
17+
("index-url=", "i", "base URL of Python Package Index"),
18+
]
19+
boolean_options = [
20+
'no-deps',
21+
'user',
2322
]
2423

25-
boolean_options = easy_install.boolean_options + ['uninstall']
26-
27-
command_consumes_arguments = False # override base
24+
install_dir = None
25+
no_deps = False
26+
user = False
27+
prefix = None
28+
index_url = None
2829

2930
def run(self):
30-
if self.uninstall:
31-
self.multi_version = True
32-
self.uninstall_link()
33-
self.uninstall_namespaces()
34-
else:
35-
self.install_for_development()
36-
self.warn_deprecated_options()
31+
cmd = (
32+
[sys.executable, '-m', 'pip', 'install', '-e', '.', '--use-pep517']
33+
+ ['--target', self.install_dir] * bool(self.install_dir)
34+
+ ['--no-deps'] * self.no_deps
35+
+ ['--user'] * self.user
36+
+ ['--prefix', self.prefix] * bool(self.prefix)
37+
+ ['--index-url', self.index_url] * bool(self.prefix)
38+
)
39+
subprocess.check_call(cmd)
3740

3841
def initialize_options(self):
39-
self.uninstall = None
40-
self.egg_path = None
41-
easy_install.initialize_options(self)
42-
self.setup_path = None
43-
self.always_copy_from = '.' # always copy eggs installed in curdir
42+
DevelopDeprecationWarning.emit()
4443

4544
def finalize_options(self) -> None:
46-
import pkg_resources
47-
48-
ei = self.get_finalized_command("egg_info")
49-
self.args = [ei.egg_name]
50-
51-
easy_install.finalize_options(self)
52-
self.expand_basedirs()
53-
self.expand_dirs()
54-
# pick up setup-dir .egg files only: no .egg-info
55-
self.package_index.scan(glob.glob('*.egg'))
56-
57-
egg_link_fn = (
58-
_normalization.filename_component_broken(ei.egg_name) + '.egg-link'
59-
)
60-
self.egg_link = os.path.join(self.install_dir, egg_link_fn)
61-
self.egg_base = ei.egg_base
62-
if self.egg_path is None:
63-
self.egg_path = os.path.abspath(ei.egg_base)
64-
65-
target = _path.normpath(self.egg_base)
66-
egg_path = _path.normpath(os.path.join(self.install_dir, self.egg_path))
67-
if egg_path != target:
68-
raise DistutilsOptionError(
69-
"--egg-path must be a relative path from the install"
70-
" directory to " + target
71-
)
72-
73-
# Make a distribution for the package's source
74-
self.dist = pkg_resources.Distribution(
75-
target,
76-
pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)),
77-
project_name=ei.egg_name,
78-
)
79-
80-
self.setup_path = self._resolve_setup_path(
81-
self.egg_base,
82-
self.install_dir,
83-
self.egg_path,
84-
)
85-
86-
@staticmethod
87-
def _resolve_setup_path(egg_base, install_dir, egg_path):
88-
"""
89-
Generate a path from egg_base back to '.' where the
90-
setup script resides and ensure that path points to the
91-
setup path from $install_dir/$egg_path.
92-
"""
93-
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
94-
if path_to_setup != os.curdir:
95-
path_to_setup = '../' * (path_to_setup.count('/') + 1)
96-
resolved = _path.normpath(os.path.join(install_dir, egg_path, path_to_setup))
97-
curdir = _path.normpath(os.curdir)
98-
if resolved != curdir:
99-
raise DistutilsOptionError(
100-
"Can't get a consistent path to setup script from"
101-
" installation directory",
102-
resolved,
103-
curdir,
104-
)
105-
return path_to_setup
106-
107-
def install_for_development(self) -> None:
108-
self.run_command('egg_info')
109-
110-
# Build extensions in-place
111-
self.reinitialize_command('build_ext', inplace=True)
112-
self.run_command('build_ext')
45+
pass
11346

114-
if setuptools.bootstrap_install_from:
115-
self.easy_install(setuptools.bootstrap_install_from)
116-
setuptools.bootstrap_install_from = None
11747

118-
self.install_namespaces()
119-
120-
# create an .egg-link in the installation dir, pointing to our egg
121-
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
122-
if not self.dry_run:
123-
with open(self.egg_link, "w", encoding="utf-8") as f:
124-
f.write(self.egg_path + "\n" + self.setup_path)
125-
# postprocess the installed distro, fixing up .pth, installing scripts,
126-
# and handling requirements
127-
self.process_distribution(None, self.dist, not self.no_deps)
128-
129-
def uninstall_link(self) -> None:
130-
if os.path.exists(self.egg_link):
131-
log.info("Removing %s (link to %s)", self.egg_link, self.egg_base)
132-
133-
contents = [
134-
line.rstrip()
135-
for line in _read_utf8_with_fallback(self.egg_link).splitlines()
136-
]
137-
138-
if contents not in ([self.egg_path], [self.egg_path, self.setup_path]):
139-
log.warn("Link points to %s: uninstall aborted", contents)
140-
return
141-
if not self.dry_run:
142-
os.unlink(self.egg_link)
143-
if not self.dry_run:
144-
self.update_pth(self.dist) # remove any .pth link to us
145-
if self.distribution.scripts:
146-
# XXX should also check for entry point scripts!
147-
log.warn("Note: you must uninstall or replace scripts manually!")
148-
149-
def install_egg_scripts(self, dist):
150-
if dist is not self.dist:
151-
# Installing a dependency, so fall back to normal behavior
152-
return easy_install.install_egg_scripts(self, dist)
153-
154-
# create wrapper scripts in the script dir, pointing to dist.scripts
155-
156-
# new-style...
157-
self.install_wrapper_scripts(dist)
158-
159-
# ...and old-style
160-
for script_name in self.distribution.scripts or []:
161-
script_path = os.path.abspath(convert_path(script_name))
162-
script_name = os.path.basename(script_path)
163-
script_text = _read_utf8_with_fallback(script_path)
164-
self.install_script(dist, script_name, script_text, script_path)
165-
166-
return None
167-
168-
def install_wrapper_scripts(self, dist):
169-
dist = VersionlessRequirement(dist)
170-
return easy_install.install_wrapper_scripts(self, dist)
171-
172-
173-
class VersionlessRequirement:
174-
"""
175-
Adapt a pkg_resources.Distribution to simply return the project
176-
name as the 'requirement' so that scripts will work across
177-
multiple versions.
178-
179-
>>> from pkg_resources import Distribution
180-
>>> dist = Distribution(project_name='foo', version='1.0')
181-
>>> str(dist.as_requirement())
182-
'foo==1.0'
183-
>>> adapted_dist = VersionlessRequirement(dist)
184-
>>> str(adapted_dist.as_requirement())
185-
'foo'
48+
class DevelopDeprecationWarning(SetuptoolsDeprecationWarning):
49+
_SUMMARY = "develop command is deprecated."
50+
_DETAILS = """
51+
Please avoid running ``setup.py`` and ``develop``.
52+
Instead, use standards-based tools like pip or uv.
18653
"""
187-
188-
def __init__(self, dist) -> None:
189-
self.__dist = dist
190-
191-
def __getattr__(self, name: str):
192-
return getattr(self.__dist, name)
193-
194-
def as_requirement(self):
195-
return self.project_name
54+
_SEE_URL = "https://github.com/pypa/setuptools/issues/917"
55+
_DUE_DATE = 2025, 10, 31

0 commit comments

Comments
 (0)