Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
keewenaw
GitHub Repository: keewenaw/ethereum-wallet-cracker
Path: blob/main/test/lib/python3.9/site-packages/setuptools/command/easy_install.py
4799 views
1
"""
2
Easy Install
3
------------
4
5
A tool for doing automatic download/extract/build of distutils-based Python
6
packages. For detailed documentation, see the accompanying EasyInstall.txt
7
file, or visit the `EasyInstall home page`__.
8
9
__ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
10
11
"""
12
13
from glob import glob
14
from distutils.util import get_platform
15
from distutils.util import convert_path, subst_vars
16
from distutils.errors import (
17
DistutilsArgError, DistutilsOptionError,
18
DistutilsError, DistutilsPlatformError,
19
)
20
from distutils import log, dir_util
21
from distutils.command.build_scripts import first_line_re
22
from distutils.spawn import find_executable
23
from distutils.command import install
24
import sys
25
import os
26
import zipimport
27
import shutil
28
import tempfile
29
import zipfile
30
import re
31
import stat
32
import random
33
import textwrap
34
import warnings
35
import site
36
import struct
37
import contextlib
38
import subprocess
39
import shlex
40
import io
41
import configparser
42
import sysconfig
43
44
45
from sysconfig import get_path
46
47
from setuptools import SetuptoolsDeprecationWarning
48
49
from setuptools import Command
50
from setuptools.sandbox import run_setup
51
from setuptools.command import setopt
52
from setuptools.archive_util import unpack_archive
53
from setuptools.package_index import (
54
PackageIndex, parse_requirement_arg, URL_SCHEME,
55
)
56
from setuptools.command import bdist_egg, egg_info
57
from setuptools.wheel import Wheel
58
from pkg_resources import (
59
normalize_path, resource_string,
60
get_distribution, find_distributions, Environment, Requirement,
61
Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound,
62
VersionConflict, DEVELOP_DIST,
63
)
64
import pkg_resources
65
from .._path import ensure_directory
66
from ..extern.jaraco.text import yield_lines
67
68
69
# Turn on PEP440Warnings
70
warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
71
72
__all__ = [
73
'easy_install', 'PthDistributions', 'extract_wininst_cfg',
74
'get_exe_prefixes',
75
]
76
77
78
def is_64bit():
79
return struct.calcsize("P") == 8
80
81
82
def _to_bytes(s):
83
return s.encode('utf8')
84
85
86
def isascii(s):
87
try:
88
s.encode('ascii')
89
return True
90
except UnicodeError:
91
return False
92
93
94
def _one_liner(text):
95
return textwrap.dedent(text).strip().replace('\n', '; ')
96
97
98
class easy_install(Command):
99
"""Manage a download/build/install process"""
100
description = "Find/get/install Python packages"
101
command_consumes_arguments = True
102
103
user_options = [
104
('prefix=', None, "installation prefix"),
105
("zip-ok", "z", "install package as a zipfile"),
106
("multi-version", "m", "make apps have to require() a version"),
107
("upgrade", "U", "force upgrade (searches PyPI for latest versions)"),
108
("install-dir=", "d", "install package to DIR"),
109
("script-dir=", "s", "install scripts to DIR"),
110
("exclude-scripts", "x", "Don't install scripts"),
111
("always-copy", "a", "Copy all needed packages to install dir"),
112
("index-url=", "i", "base URL of Python Package Index"),
113
("find-links=", "f", "additional URL(s) to search for packages"),
114
("build-directory=", "b",
115
"download/extract/build in DIR; keep the results"),
116
('optimize=', 'O',
117
"also compile with optimization: -O1 for \"python -O\", "
118
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
119
('record=', None,
120
"filename in which to record list of installed files"),
121
('always-unzip', 'Z', "don't install as a zipfile, no matter what"),
122
('site-dirs=', 'S', "list of directories where .pth files work"),
123
('editable', 'e', "Install specified packages in editable form"),
124
('no-deps', 'N', "don't install dependencies"),
125
('allow-hosts=', 'H', "pattern(s) that hostnames must match"),
126
('local-snapshots-ok', 'l',
127
"allow building eggs from local checkouts"),
128
('version', None, "print version information and exit"),
129
('no-find-links', None,
130
"Don't load find-links defined in packages being installed"),
131
('user', None, "install in user site-package '%s'" % site.USER_SITE)
132
]
133
boolean_options = [
134
'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
135
'editable',
136
'no-deps', 'local-snapshots-ok', 'version',
137
'user'
138
]
139
140
negative_opt = {'always-unzip': 'zip-ok'}
141
create_index = PackageIndex
142
143
def initialize_options(self):
144
warnings.warn(
145
"easy_install command is deprecated. "
146
"Use build and pip and other standards-based tools.",
147
EasyInstallDeprecationWarning,
148
)
149
150
# the --user option seems to be an opt-in one,
151
# so the default should be False.
152
self.user = 0
153
self.zip_ok = self.local_snapshots_ok = None
154
self.install_dir = self.script_dir = self.exclude_scripts = None
155
self.index_url = None
156
self.find_links = None
157
self.build_directory = None
158
self.args = None
159
self.optimize = self.record = None
160
self.upgrade = self.always_copy = self.multi_version = None
161
self.editable = self.no_deps = self.allow_hosts = None
162
self.root = self.prefix = self.no_report = None
163
self.version = None
164
self.install_purelib = None # for pure module distributions
165
self.install_platlib = None # non-pure (dists w/ extensions)
166
self.install_headers = None # for C/C++ headers
167
self.install_lib = None # set to either purelib or platlib
168
self.install_scripts = None
169
self.install_data = None
170
self.install_base = None
171
self.install_platbase = None
172
self.install_userbase = site.USER_BASE
173
self.install_usersite = site.USER_SITE
174
self.no_find_links = None
175
176
# Options not specifiable via command line
177
self.package_index = None
178
self.pth_file = self.always_copy_from = None
179
self.site_dirs = None
180
self.installed_projects = {}
181
# Always read easy_install options, even if we are subclassed, or have
182
# an independent instance created. This ensures that defaults will
183
# always come from the standard configuration file(s)' "easy_install"
184
# section, even if this is a "develop" or "install" command, or some
185
# other embedding.
186
self._dry_run = None
187
self.verbose = self.distribution.verbose
188
self.distribution._set_command_options(
189
self, self.distribution.get_option_dict('easy_install')
190
)
191
192
def delete_blockers(self, blockers):
193
extant_blockers = (
194
filename for filename in blockers
195
if os.path.exists(filename) or os.path.islink(filename)
196
)
197
list(map(self._delete_path, extant_blockers))
198
199
def _delete_path(self, path):
200
log.info("Deleting %s", path)
201
if self.dry_run:
202
return
203
204
is_tree = os.path.isdir(path) and not os.path.islink(path)
205
remover = rmtree if is_tree else os.unlink
206
remover(path)
207
208
@staticmethod
209
def _render_version():
210
"""
211
Render the Setuptools version and installation details, then exit.
212
"""
213
ver = '{}.{}'.format(*sys.version_info)
214
dist = get_distribution('setuptools')
215
tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
216
print(tmpl.format(**locals()))
217
raise SystemExit()
218
219
def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME
220
self.version and self._render_version()
221
222
py_version = sys.version.split()[0]
223
224
self.config_vars = dict(sysconfig.get_config_vars())
225
226
self.config_vars.update({
227
'dist_name': self.distribution.get_name(),
228
'dist_version': self.distribution.get_version(),
229
'dist_fullname': self.distribution.get_fullname(),
230
'py_version': py_version,
231
'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',
232
'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
233
'sys_prefix': self.config_vars['prefix'],
234
'sys_exec_prefix': self.config_vars['exec_prefix'],
235
# Only python 3.2+ has abiflags
236
'abiflags': getattr(sys, 'abiflags', ''),
237
'platlibdir': getattr(sys, 'platlibdir', 'lib'),
238
})
239
with contextlib.suppress(AttributeError):
240
# only for distutils outside stdlib
241
self.config_vars.update({
242
'implementation_lower': install._get_implementation().lower(),
243
'implementation': install._get_implementation(),
244
})
245
246
# pypa/distutils#113 Python 3.9 compat
247
self.config_vars.setdefault(
248
'py_version_nodot_plat',
249
getattr(sys, 'windir', '').replace('.', ''),
250
)
251
252
self.config_vars['userbase'] = self.install_userbase
253
self.config_vars['usersite'] = self.install_usersite
254
if self.user and not site.ENABLE_USER_SITE:
255
log.warn("WARNING: The user site-packages directory is disabled.")
256
257
self._fix_install_dir_for_user_site()
258
259
self.expand_basedirs()
260
self.expand_dirs()
261
262
self._expand(
263
'install_dir', 'script_dir', 'build_directory',
264
'site_dirs',
265
)
266
# If a non-default installation directory was specified, default the
267
# script directory to match it.
268
if self.script_dir is None:
269
self.script_dir = self.install_dir
270
271
if self.no_find_links is None:
272
self.no_find_links = False
273
274
# Let install_dir get set by install_lib command, which in turn
275
# gets its info from the install command, and takes into account
276
# --prefix and --home and all that other crud.
277
self.set_undefined_options(
278
'install_lib', ('install_dir', 'install_dir')
279
)
280
# Likewise, set default script_dir from 'install_scripts.install_dir'
281
self.set_undefined_options(
282
'install_scripts', ('install_dir', 'script_dir')
283
)
284
285
if self.user and self.install_purelib:
286
self.install_dir = self.install_purelib
287
self.script_dir = self.install_scripts
288
# default --record from the install command
289
self.set_undefined_options('install', ('record', 'record'))
290
self.all_site_dirs = get_site_dirs()
291
self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))
292
293
if not self.editable:
294
self.check_site_dir()
295
default_index = os.getenv("__EASYINSTALL_INDEX", "https://pypi.org/simple/")
296
# ^ Private API for testing purposes only
297
self.index_url = self.index_url or default_index
298
self.shadow_path = self.all_site_dirs[:]
299
for path_item in self.install_dir, normalize_path(self.script_dir):
300
if path_item not in self.shadow_path:
301
self.shadow_path.insert(0, path_item)
302
303
if self.allow_hosts is not None:
304
hosts = [s.strip() for s in self.allow_hosts.split(',')]
305
else:
306
hosts = ['*']
307
if self.package_index is None:
308
self.package_index = self.create_index(
309
self.index_url, search_path=self.shadow_path, hosts=hosts,
310
)
311
self.local_index = Environment(self.shadow_path + sys.path)
312
313
if self.find_links is not None:
314
if isinstance(self.find_links, str):
315
self.find_links = self.find_links.split()
316
else:
317
self.find_links = []
318
if self.local_snapshots_ok:
319
self.package_index.scan_egg_links(self.shadow_path + sys.path)
320
if not self.no_find_links:
321
self.package_index.add_find_links(self.find_links)
322
self.set_undefined_options('install_lib', ('optimize', 'optimize'))
323
self.optimize = self._validate_optimize(self.optimize)
324
325
if self.editable and not self.build_directory:
326
raise DistutilsArgError(
327
"Must specify a build directory (-b) when using --editable"
328
)
329
if not self.args:
330
raise DistutilsArgError(
331
"No urls, filenames, or requirements specified (see --help)")
332
333
self.outputs = []
334
335
@staticmethod
336
def _process_site_dirs(site_dirs):
337
if site_dirs is None:
338
return
339
340
normpath = map(normalize_path, sys.path)
341
site_dirs = [
342
os.path.expanduser(s.strip()) for s in
343
site_dirs.split(',')
344
]
345
for d in site_dirs:
346
if not os.path.isdir(d):
347
log.warn("%s (in --site-dirs) does not exist", d)
348
elif normalize_path(d) not in normpath:
349
raise DistutilsOptionError(
350
d + " (in --site-dirs) is not on sys.path"
351
)
352
else:
353
yield normalize_path(d)
354
355
@staticmethod
356
def _validate_optimize(value):
357
try:
358
value = int(value)
359
if value not in range(3):
360
raise ValueError
361
except ValueError as e:
362
raise DistutilsOptionError(
363
"--optimize must be 0, 1, or 2"
364
) from e
365
366
return value
367
368
def _fix_install_dir_for_user_site(self):
369
"""
370
Fix the install_dir if "--user" was used.
371
"""
372
if not self.user:
373
return
374
375
self.create_home_path()
376
if self.install_userbase is None:
377
msg = "User base directory is not specified"
378
raise DistutilsPlatformError(msg)
379
self.install_base = self.install_platbase = self.install_userbase
380
scheme_name = f'{os.name}_user'
381
self.select_scheme(scheme_name)
382
383
def _expand_attrs(self, attrs):
384
for attr in attrs:
385
val = getattr(self, attr)
386
if val is not None:
387
if os.name == 'posix' or os.name == 'nt':
388
val = os.path.expanduser(val)
389
val = subst_vars(val, self.config_vars)
390
setattr(self, attr, val)
391
392
def expand_basedirs(self):
393
"""Calls `os.path.expanduser` on install_base, install_platbase and
394
root."""
395
self._expand_attrs(['install_base', 'install_platbase', 'root'])
396
397
def expand_dirs(self):
398
"""Calls `os.path.expanduser` on install dirs."""
399
dirs = [
400
'install_purelib',
401
'install_platlib',
402
'install_lib',
403
'install_headers',
404
'install_scripts',
405
'install_data',
406
]
407
self._expand_attrs(dirs)
408
409
def run(self, show_deprecation=True):
410
if show_deprecation:
411
self.announce(
412
"WARNING: The easy_install command is deprecated "
413
"and will be removed in a future version.",
414
log.WARN,
415
)
416
if self.verbose != self.distribution.verbose:
417
log.set_verbosity(self.verbose)
418
try:
419
for spec in self.args:
420
self.easy_install(spec, not self.no_deps)
421
if self.record:
422
outputs = self.outputs
423
if self.root: # strip any package prefix
424
root_len = len(self.root)
425
for counter in range(len(outputs)):
426
outputs[counter] = outputs[counter][root_len:]
427
from distutils import file_util
428
429
self.execute(
430
file_util.write_file, (self.record, outputs),
431
"writing list of installed files to '%s'" %
432
self.record
433
)
434
self.warn_deprecated_options()
435
finally:
436
log.set_verbosity(self.distribution.verbose)
437
438
def pseudo_tempname(self):
439
"""Return a pseudo-tempname base in the install directory.
440
This code is intentionally naive; if a malicious party can write to
441
the target directory you're already in deep doodoo.
442
"""
443
try:
444
pid = os.getpid()
445
except Exception:
446
pid = random.randint(0, sys.maxsize)
447
return os.path.join(self.install_dir, "test-easy-install-%s" % pid)
448
449
def warn_deprecated_options(self):
450
pass
451
452
def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME
453
"""Verify that self.install_dir is .pth-capable dir, if needed"""
454
455
instdir = normalize_path(self.install_dir)
456
pth_file = os.path.join(instdir, 'easy-install.pth')
457
458
if not os.path.exists(instdir):
459
try:
460
os.makedirs(instdir)
461
except (OSError, IOError):
462
self.cant_write_to_target()
463
464
# Is it a configured, PYTHONPATH, implicit, or explicit site dir?
465
is_site_dir = instdir in self.all_site_dirs
466
467
if not is_site_dir and not self.multi_version:
468
# No? Then directly test whether it does .pth file processing
469
is_site_dir = self.check_pth_processing()
470
else:
471
# make sure we can write to target dir
472
testfile = self.pseudo_tempname() + '.write-test'
473
test_exists = os.path.exists(testfile)
474
try:
475
if test_exists:
476
os.unlink(testfile)
477
open(testfile, 'w').close()
478
os.unlink(testfile)
479
except (OSError, IOError):
480
self.cant_write_to_target()
481
482
if not is_site_dir and not self.multi_version:
483
# Can't install non-multi to non-site dir with easy_install
484
pythonpath = os.environ.get('PYTHONPATH', '')
485
log.warn(self.__no_default_msg, self.install_dir, pythonpath)
486
487
if is_site_dir:
488
if self.pth_file is None:
489
self.pth_file = PthDistributions(pth_file, self.all_site_dirs)
490
else:
491
self.pth_file = None
492
493
if self.multi_version and not os.path.exists(pth_file):
494
self.pth_file = None # don't create a .pth file
495
self.install_dir = instdir
496
497
__cant_write_msg = textwrap.dedent("""
498
can't create or remove files in install directory
499
500
The following error occurred while trying to add or remove files in the
501
installation directory:
502
503
%s
504
505
The installation directory you specified (via --install-dir, --prefix, or
506
the distutils default setting) was:
507
508
%s
509
""").lstrip() # noqa
510
511
__not_exists_id = textwrap.dedent("""
512
This directory does not currently exist. Please create it and try again, or
513
choose a different installation directory (using the -d or --install-dir
514
option).
515
""").lstrip() # noqa
516
517
__access_msg = textwrap.dedent("""
518
Perhaps your account does not have write access to this directory? If the
519
installation directory is a system-owned directory, you may need to sign in
520
as the administrator or "root" account. If you do not have administrative
521
access to this machine, you may wish to choose a different installation
522
directory, preferably one that is listed in your PYTHONPATH environment
523
variable.
524
525
For information on other options, you may wish to consult the
526
documentation at:
527
528
https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
529
530
Please make the appropriate changes for your system and try again.
531
""").lstrip() # noqa
532
533
def cant_write_to_target(self):
534
msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,)
535
536
if not os.path.exists(self.install_dir):
537
msg += '\n' + self.__not_exists_id
538
else:
539
msg += '\n' + self.__access_msg
540
raise DistutilsError(msg)
541
542
def check_pth_processing(self):
543
"""Empirically verify whether .pth files are supported in inst. dir"""
544
instdir = self.install_dir
545
log.info("Checking .pth file support in %s", instdir)
546
pth_file = self.pseudo_tempname() + ".pth"
547
ok_file = pth_file + '.ok'
548
ok_exists = os.path.exists(ok_file)
549
tmpl = _one_liner("""
550
import os
551
f = open({ok_file!r}, 'w')
552
f.write('OK')
553
f.close()
554
""") + '\n'
555
try:
556
if ok_exists:
557
os.unlink(ok_file)
558
dirname = os.path.dirname(ok_file)
559
os.makedirs(dirname, exist_ok=True)
560
f = open(pth_file, 'w')
561
except (OSError, IOError):
562
self.cant_write_to_target()
563
else:
564
try:
565
f.write(tmpl.format(**locals()))
566
f.close()
567
f = None
568
executable = sys.executable
569
if os.name == 'nt':
570
dirname, basename = os.path.split(executable)
571
alt = os.path.join(dirname, 'pythonw.exe')
572
use_alt = (
573
basename.lower() == 'python.exe' and
574
os.path.exists(alt)
575
)
576
if use_alt:
577
# use pythonw.exe to avoid opening a console window
578
executable = alt
579
580
from distutils.spawn import spawn
581
582
spawn([executable, '-E', '-c', 'pass'], 0)
583
584
if os.path.exists(ok_file):
585
log.info(
586
"TEST PASSED: %s appears to support .pth files",
587
instdir
588
)
589
return True
590
finally:
591
if f:
592
f.close()
593
if os.path.exists(ok_file):
594
os.unlink(ok_file)
595
if os.path.exists(pth_file):
596
os.unlink(pth_file)
597
if not self.multi_version:
598
log.warn("TEST FAILED: %s does NOT support .pth files", instdir)
599
return False
600
601
def install_egg_scripts(self, dist):
602
"""Write all the scripts for `dist`, unless scripts are excluded"""
603
if not self.exclude_scripts and dist.metadata_isdir('scripts'):
604
for script_name in dist.metadata_listdir('scripts'):
605
if dist.metadata_isdir('scripts/' + script_name):
606
# The "script" is a directory, likely a Python 3
607
# __pycache__ directory, so skip it.
608
continue
609
self.install_script(
610
dist, script_name,
611
dist.get_metadata('scripts/' + script_name)
612
)
613
self.install_wrapper_scripts(dist)
614
615
def add_output(self, path):
616
if os.path.isdir(path):
617
for base, dirs, files in os.walk(path):
618
for filename in files:
619
self.outputs.append(os.path.join(base, filename))
620
else:
621
self.outputs.append(path)
622
623
def not_editable(self, spec):
624
if self.editable:
625
raise DistutilsArgError(
626
"Invalid argument %r: you can't use filenames or URLs "
627
"with --editable (except via the --find-links option)."
628
% (spec,)
629
)
630
631
def check_editable(self, spec):
632
if not self.editable:
633
return
634
635
if os.path.exists(os.path.join(self.build_directory, spec.key)):
636
raise DistutilsArgError(
637
"%r already exists in %s; can't do a checkout there" %
638
(spec.key, self.build_directory)
639
)
640
641
@contextlib.contextmanager
642
def _tmpdir(self):
643
tmpdir = tempfile.mkdtemp(prefix=u"easy_install-")
644
try:
645
# cast to str as workaround for #709 and #710 and #712
646
yield str(tmpdir)
647
finally:
648
os.path.exists(tmpdir) and rmtree(tmpdir)
649
650
def easy_install(self, spec, deps=False):
651
with self._tmpdir() as tmpdir:
652
if not isinstance(spec, Requirement):
653
if URL_SCHEME(spec):
654
# It's a url, download it to tmpdir and process
655
self.not_editable(spec)
656
dl = self.package_index.download(spec, tmpdir)
657
return self.install_item(None, dl, tmpdir, deps, True)
658
659
elif os.path.exists(spec):
660
# Existing file or directory, just process it directly
661
self.not_editable(spec)
662
return self.install_item(None, spec, tmpdir, deps, True)
663
else:
664
spec = parse_requirement_arg(spec)
665
666
self.check_editable(spec)
667
dist = self.package_index.fetch_distribution(
668
spec, tmpdir, self.upgrade, self.editable,
669
not self.always_copy, self.local_index
670
)
671
if dist is None:
672
msg = "Could not find suitable distribution for %r" % spec
673
if self.always_copy:
674
msg += " (--always-copy skips system and development eggs)"
675
raise DistutilsError(msg)
676
elif dist.precedence == DEVELOP_DIST:
677
# .egg-info dists don't need installing, just process deps
678
self.process_distribution(spec, dist, deps, "Using")
679
return dist
680
else:
681
return self.install_item(spec, dist.location, tmpdir, deps)
682
683
def install_item(self, spec, download, tmpdir, deps, install_needed=False):
684
685
# Installation is also needed if file in tmpdir or is not an egg
686
install_needed = install_needed or self.always_copy
687
install_needed = install_needed or os.path.dirname(download) == tmpdir
688
install_needed = install_needed or not download.endswith('.egg')
689
install_needed = install_needed or (
690
self.always_copy_from is not None and
691
os.path.dirname(normalize_path(download)) ==
692
normalize_path(self.always_copy_from)
693
)
694
695
if spec and not install_needed:
696
# at this point, we know it's a local .egg, we just don't know if
697
# it's already installed.
698
for dist in self.local_index[spec.project_name]:
699
if dist.location == download:
700
break
701
else:
702
install_needed = True # it's not in the local index
703
704
log.info("Processing %s", os.path.basename(download))
705
706
if install_needed:
707
dists = self.install_eggs(spec, download, tmpdir)
708
for dist in dists:
709
self.process_distribution(spec, dist, deps)
710
else:
711
dists = [self.egg_distribution(download)]
712
self.process_distribution(spec, dists[0], deps, "Using")
713
714
if spec is not None:
715
for dist in dists:
716
if dist in spec:
717
return dist
718
719
def select_scheme(self, name):
720
try:
721
install._select_scheme(self, name)
722
except AttributeError:
723
# stdlib distutils
724
install.install.select_scheme(self, name.replace('posix', 'unix'))
725
726
# FIXME: 'easy_install.process_distribution' is too complex (12)
727
def process_distribution( # noqa: C901
728
self, requirement, dist, deps=True, *info,
729
):
730
self.update_pth(dist)
731
self.package_index.add(dist)
732
if dist in self.local_index[dist.key]:
733
self.local_index.remove(dist)
734
self.local_index.add(dist)
735
self.install_egg_scripts(dist)
736
self.installed_projects[dist.key] = dist
737
log.info(self.installation_report(requirement, dist, *info))
738
if (dist.has_metadata('dependency_links.txt') and
739
not self.no_find_links):
740
self.package_index.add_find_links(
741
dist.get_metadata_lines('dependency_links.txt')
742
)
743
if not deps and not self.always_copy:
744
return
745
elif requirement is not None and dist.key != requirement.key:
746
log.warn("Skipping dependencies for %s", dist)
747
return # XXX this is not the distribution we were looking for
748
elif requirement is None or dist not in requirement:
749
# if we wound up with a different version, resolve what we've got
750
distreq = dist.as_requirement()
751
requirement = Requirement(str(distreq))
752
log.info("Processing dependencies for %s", requirement)
753
try:
754
distros = WorkingSet([]).resolve(
755
[requirement], self.local_index, self.easy_install
756
)
757
except DistributionNotFound as e:
758
raise DistutilsError(str(e)) from e
759
except VersionConflict as e:
760
raise DistutilsError(e.report()) from e
761
if self.always_copy or self.always_copy_from:
762
# Force all the relevant distros to be copied or activated
763
for dist in distros:
764
if dist.key not in self.installed_projects:
765
self.easy_install(dist.as_requirement())
766
log.info("Finished processing dependencies for %s", requirement)
767
768
def should_unzip(self, dist):
769
if self.zip_ok is not None:
770
return not self.zip_ok
771
if dist.has_metadata('not-zip-safe'):
772
return True
773
if not dist.has_metadata('zip-safe'):
774
return True
775
return False
776
777
def maybe_move(self, spec, dist_filename, setup_base):
778
dst = os.path.join(self.build_directory, spec.key)
779
if os.path.exists(dst):
780
msg = (
781
"%r already exists in %s; build directory %s will not be kept"
782
)
783
log.warn(msg, spec.key, self.build_directory, setup_base)
784
return setup_base
785
if os.path.isdir(dist_filename):
786
setup_base = dist_filename
787
else:
788
if os.path.dirname(dist_filename) == setup_base:
789
os.unlink(dist_filename) # get it out of the tmp dir
790
contents = os.listdir(setup_base)
791
if len(contents) == 1:
792
dist_filename = os.path.join(setup_base, contents[0])
793
if os.path.isdir(dist_filename):
794
# if the only thing there is a directory, move it instead
795
setup_base = dist_filename
796
ensure_directory(dst)
797
shutil.move(setup_base, dst)
798
return dst
799
800
def install_wrapper_scripts(self, dist):
801
if self.exclude_scripts:
802
return
803
for args in ScriptWriter.best().get_args(dist):
804
self.write_script(*args)
805
806
def install_script(self, dist, script_name, script_text, dev_path=None):
807
"""Generate a legacy script wrapper and install it"""
808
spec = str(dist.as_requirement())
809
is_script = is_python_script(script_text, script_name)
810
811
if is_script:
812
body = self._load_template(dev_path) % locals()
813
script_text = ScriptWriter.get_header(script_text) + body
814
self.write_script(script_name, _to_bytes(script_text), 'b')
815
816
@staticmethod
817
def _load_template(dev_path):
818
"""
819
There are a couple of template scripts in the package. This
820
function loads one of them and prepares it for use.
821
"""
822
# See https://github.com/pypa/setuptools/issues/134 for info
823
# on script file naming and downstream issues with SVR4
824
name = 'script.tmpl'
825
if dev_path:
826
name = name.replace('.tmpl', ' (dev).tmpl')
827
828
raw_bytes = resource_string('setuptools', name)
829
return raw_bytes.decode('utf-8')
830
831
def write_script(self, script_name, contents, mode="t", blockers=()):
832
"""Write an executable file to the scripts directory"""
833
self.delete_blockers( # clean up old .py/.pyw w/o a script
834
[os.path.join(self.script_dir, x) for x in blockers]
835
)
836
log.info("Installing %s script to %s", script_name, self.script_dir)
837
target = os.path.join(self.script_dir, script_name)
838
self.add_output(target)
839
840
if self.dry_run:
841
return
842
843
mask = current_umask()
844
ensure_directory(target)
845
if os.path.exists(target):
846
os.unlink(target)
847
with open(target, "w" + mode) as f:
848
f.write(contents)
849
chmod(target, 0o777 - mask)
850
851
def install_eggs(self, spec, dist_filename, tmpdir):
852
# .egg dirs or files are already built, so just return them
853
installer_map = {
854
'.egg': self.install_egg,
855
'.exe': self.install_exe,
856
'.whl': self.install_wheel,
857
}
858
try:
859
install_dist = installer_map[
860
dist_filename.lower()[-4:]
861
]
862
except KeyError:
863
pass
864
else:
865
return [install_dist(dist_filename, tmpdir)]
866
867
# Anything else, try to extract and build
868
setup_base = tmpdir
869
if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'):
870
unpack_archive(dist_filename, tmpdir, self.unpack_progress)
871
elif os.path.isdir(dist_filename):
872
setup_base = os.path.abspath(dist_filename)
873
874
if (setup_base.startswith(tmpdir) # something we downloaded
875
and self.build_directory and spec is not None):
876
setup_base = self.maybe_move(spec, dist_filename, setup_base)
877
878
# Find the setup.py file
879
setup_script = os.path.join(setup_base, 'setup.py')
880
881
if not os.path.exists(setup_script):
882
setups = glob(os.path.join(setup_base, '*', 'setup.py'))
883
if not setups:
884
raise DistutilsError(
885
"Couldn't find a setup script in %s" %
886
os.path.abspath(dist_filename)
887
)
888
if len(setups) > 1:
889
raise DistutilsError(
890
"Multiple setup scripts in %s" %
891
os.path.abspath(dist_filename)
892
)
893
setup_script = setups[0]
894
895
# Now run it, and return the result
896
if self.editable:
897
log.info(self.report_editable(spec, setup_script))
898
return []
899
else:
900
return self.build_and_install(setup_script, setup_base)
901
902
def egg_distribution(self, egg_path):
903
if os.path.isdir(egg_path):
904
metadata = PathMetadata(egg_path, os.path.join(egg_path,
905
'EGG-INFO'))
906
else:
907
metadata = EggMetadata(zipimport.zipimporter(egg_path))
908
return Distribution.from_filename(egg_path, metadata=metadata)
909
910
# FIXME: 'easy_install.install_egg' is too complex (11)
911
def install_egg(self, egg_path, tmpdir): # noqa: C901
912
destination = os.path.join(
913
self.install_dir,
914
os.path.basename(egg_path),
915
)
916
destination = os.path.abspath(destination)
917
if not self.dry_run:
918
ensure_directory(destination)
919
920
dist = self.egg_distribution(egg_path)
921
if not (
922
os.path.exists(destination) and os.path.samefile(egg_path, destination)
923
):
924
if os.path.isdir(destination) and not os.path.islink(destination):
925
dir_util.remove_tree(destination, dry_run=self.dry_run)
926
elif os.path.exists(destination):
927
self.execute(
928
os.unlink,
929
(destination,),
930
"Removing " + destination,
931
)
932
try:
933
new_dist_is_zipped = False
934
if os.path.isdir(egg_path):
935
if egg_path.startswith(tmpdir):
936
f, m = shutil.move, "Moving"
937
else:
938
f, m = shutil.copytree, "Copying"
939
elif self.should_unzip(dist):
940
self.mkpath(destination)
941
f, m = self.unpack_and_compile, "Extracting"
942
else:
943
new_dist_is_zipped = True
944
if egg_path.startswith(tmpdir):
945
f, m = shutil.move, "Moving"
946
else:
947
f, m = shutil.copy2, "Copying"
948
self.execute(
949
f,
950
(egg_path, destination),
951
(m + " %s to %s") % (
952
os.path.basename(egg_path),
953
os.path.dirname(destination)
954
),
955
)
956
update_dist_caches(
957
destination,
958
fix_zipimporter_caches=new_dist_is_zipped,
959
)
960
except Exception:
961
update_dist_caches(destination, fix_zipimporter_caches=False)
962
raise
963
964
self.add_output(destination)
965
return self.egg_distribution(destination)
966
967
def install_exe(self, dist_filename, tmpdir):
968
# See if it's valid, get data
969
cfg = extract_wininst_cfg(dist_filename)
970
if cfg is None:
971
raise DistutilsError(
972
"%s is not a valid distutils Windows .exe" % dist_filename
973
)
974
# Create a dummy distribution object until we build the real distro
975
dist = Distribution(
976
None,
977
project_name=cfg.get('metadata', 'name'),
978
version=cfg.get('metadata', 'version'), platform=get_platform(),
979
)
980
981
# Convert the .exe to an unpacked egg
982
egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg')
983
dist.location = egg_path
984
egg_tmp = egg_path + '.tmp'
985
_egg_info = os.path.join(egg_tmp, 'EGG-INFO')
986
pkg_inf = os.path.join(_egg_info, 'PKG-INFO')
987
ensure_directory(pkg_inf) # make sure EGG-INFO dir exists
988
dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX
989
self.exe_to_egg(dist_filename, egg_tmp)
990
991
# Write EGG-INFO/PKG-INFO
992
if not os.path.exists(pkg_inf):
993
f = open(pkg_inf, 'w')
994
f.write('Metadata-Version: 1.0\n')
995
for k, v in cfg.items('metadata'):
996
if k != 'target_version':
997
f.write('%s: %s\n' % (k.replace('_', '-').title(), v))
998
f.close()
999
script_dir = os.path.join(_egg_info, 'scripts')
1000
# delete entry-point scripts to avoid duping
1001
self.delete_blockers([
1002
os.path.join(script_dir, args[0])
1003
for args in ScriptWriter.get_args(dist)
1004
])
1005
# Build .egg file from tmpdir
1006
bdist_egg.make_zipfile(
1007
egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run,
1008
)
1009
# install the .egg
1010
return self.install_egg(egg_path, tmpdir)
1011
1012
# FIXME: 'easy_install.exe_to_egg' is too complex (12)
1013
def exe_to_egg(self, dist_filename, egg_tmp): # noqa: C901
1014
"""Extract a bdist_wininst to the directories an egg would use"""
1015
# Check for .pth file and set up prefix translations
1016
prefixes = get_exe_prefixes(dist_filename)
1017
to_compile = []
1018
native_libs = []
1019
top_level = {}
1020
1021
def process(src, dst):
1022
s = src.lower()
1023
for old, new in prefixes:
1024
if s.startswith(old):
1025
src = new + src[len(old):]
1026
parts = src.split('/')
1027
dst = os.path.join(egg_tmp, *parts)
1028
dl = dst.lower()
1029
if dl.endswith('.pyd') or dl.endswith('.dll'):
1030
parts[-1] = bdist_egg.strip_module(parts[-1])
1031
top_level[os.path.splitext(parts[0])[0]] = 1
1032
native_libs.append(src)
1033
elif dl.endswith('.py') and old != 'SCRIPTS/':
1034
top_level[os.path.splitext(parts[0])[0]] = 1
1035
to_compile.append(dst)
1036
return dst
1037
if not src.endswith('.pth'):
1038
log.warn("WARNING: can't process %s", src)
1039
return None
1040
1041
# extract, tracking .pyd/.dll->native_libs and .py -> to_compile
1042
unpack_archive(dist_filename, egg_tmp, process)
1043
stubs = []
1044
for res in native_libs:
1045
if res.lower().endswith('.pyd'): # create stubs for .pyd's
1046
parts = res.split('/')
1047
resource = parts[-1]
1048
parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py'
1049
pyfile = os.path.join(egg_tmp, *parts)
1050
to_compile.append(pyfile)
1051
stubs.append(pyfile)
1052
bdist_egg.write_stub(resource, pyfile)
1053
self.byte_compile(to_compile) # compile .py's
1054
bdist_egg.write_safety_flag(
1055
os.path.join(egg_tmp, 'EGG-INFO'),
1056
bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag
1057
1058
for name in 'top_level', 'native_libs':
1059
if locals()[name]:
1060
txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')
1061
if not os.path.exists(txt):
1062
f = open(txt, 'w')
1063
f.write('\n'.join(locals()[name]) + '\n')
1064
f.close()
1065
1066
def install_wheel(self, wheel_path, tmpdir):
1067
wheel = Wheel(wheel_path)
1068
assert wheel.is_compatible()
1069
destination = os.path.join(self.install_dir, wheel.egg_name())
1070
destination = os.path.abspath(destination)
1071
if not self.dry_run:
1072
ensure_directory(destination)
1073
if os.path.isdir(destination) and not os.path.islink(destination):
1074
dir_util.remove_tree(destination, dry_run=self.dry_run)
1075
elif os.path.exists(destination):
1076
self.execute(
1077
os.unlink,
1078
(destination,),
1079
"Removing " + destination,
1080
)
1081
try:
1082
self.execute(
1083
wheel.install_as_egg,
1084
(destination,),
1085
("Installing %s to %s") % (
1086
os.path.basename(wheel_path),
1087
os.path.dirname(destination)
1088
),
1089
)
1090
finally:
1091
update_dist_caches(destination, fix_zipimporter_caches=False)
1092
self.add_output(destination)
1093
return self.egg_distribution(destination)
1094
1095
__mv_warning = textwrap.dedent("""
1096
Because this distribution was installed --multi-version, before you can
1097
import modules from this package in an application, you will need to
1098
'import pkg_resources' and then use a 'require()' call similar to one of
1099
these examples, in order to select the desired version:
1100
1101
pkg_resources.require("%(name)s") # latest installed version
1102
pkg_resources.require("%(name)s==%(version)s") # this exact version
1103
pkg_resources.require("%(name)s>=%(version)s") # this version or higher
1104
""").lstrip() # noqa
1105
1106
__id_warning = textwrap.dedent("""
1107
Note also that the installation directory must be on sys.path at runtime for
1108
this to work. (e.g. by being the application's script directory, by being on
1109
PYTHONPATH, or by being added to sys.path by your code.)
1110
""") # noqa
1111
1112
def installation_report(self, req, dist, what="Installed"):
1113
"""Helpful installation message for display to package users"""
1114
msg = "\n%(what)s %(eggloc)s%(extras)s"
1115
if self.multi_version and not self.no_report:
1116
msg += '\n' + self.__mv_warning
1117
if self.install_dir not in map(normalize_path, sys.path):
1118
msg += '\n' + self.__id_warning
1119
1120
eggloc = dist.location
1121
name = dist.project_name
1122
version = dist.version
1123
extras = '' # TODO: self.report_extras(req, dist)
1124
return msg % locals()
1125
1126
__editable_msg = textwrap.dedent("""
1127
Extracted editable version of %(spec)s to %(dirname)s
1128
1129
If it uses setuptools in its setup script, you can activate it in
1130
"development" mode by going to that directory and running::
1131
1132
%(python)s setup.py develop
1133
1134
See the setuptools documentation for the "develop" command for more info.
1135
""").lstrip() # noqa
1136
1137
def report_editable(self, spec, setup_script):
1138
dirname = os.path.dirname(setup_script)
1139
python = sys.executable
1140
return '\n' + self.__editable_msg % locals()
1141
1142
def run_setup(self, setup_script, setup_base, args):
1143
sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
1144
sys.modules.setdefault('distutils.command.egg_info', egg_info)
1145
1146
args = list(args)
1147
if self.verbose > 2:
1148
v = 'v' * (self.verbose - 1)
1149
args.insert(0, '-' + v)
1150
elif self.verbose < 2:
1151
args.insert(0, '-q')
1152
if self.dry_run:
1153
args.insert(0, '-n')
1154
log.info(
1155
"Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args)
1156
)
1157
try:
1158
run_setup(setup_script, args)
1159
except SystemExit as v:
1160
raise DistutilsError(
1161
"Setup script exited with %s" % (v.args[0],)
1162
) from v
1163
1164
def build_and_install(self, setup_script, setup_base):
1165
args = ['bdist_egg', '--dist-dir']
1166
1167
dist_dir = tempfile.mkdtemp(
1168
prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script)
1169
)
1170
try:
1171
self._set_fetcher_options(os.path.dirname(setup_script))
1172
args.append(dist_dir)
1173
1174
self.run_setup(setup_script, setup_base, args)
1175
all_eggs = Environment([dist_dir])
1176
eggs = []
1177
for key in all_eggs:
1178
for dist in all_eggs[key]:
1179
eggs.append(self.install_egg(dist.location, setup_base))
1180
if not eggs and not self.dry_run:
1181
log.warn("No eggs found in %s (setup script problem?)",
1182
dist_dir)
1183
return eggs
1184
finally:
1185
rmtree(dist_dir)
1186
log.set_verbosity(self.verbose) # restore our log verbosity
1187
1188
def _set_fetcher_options(self, base):
1189
"""
1190
When easy_install is about to run bdist_egg on a source dist, that
1191
source dist might have 'setup_requires' directives, requiring
1192
additional fetching. Ensure the fetcher options given to easy_install
1193
are available to that command as well.
1194
"""
1195
# find the fetch options from easy_install and write them out
1196
# to the setup.cfg file.
1197
ei_opts = self.distribution.get_option_dict('easy_install').copy()
1198
fetch_directives = (
1199
'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',
1200
)
1201
fetch_options = {}
1202
for key, val in ei_opts.items():
1203
if key not in fetch_directives:
1204
continue
1205
fetch_options[key] = val[1]
1206
# create a settings dictionary suitable for `edit_config`
1207
settings = dict(easy_install=fetch_options)
1208
cfg_filename = os.path.join(base, 'setup.cfg')
1209
setopt.edit_config(cfg_filename, settings)
1210
1211
def update_pth(self, dist): # noqa: C901 # is too complex (11) # FIXME
1212
if self.pth_file is None:
1213
return
1214
1215
for d in self.pth_file[dist.key]: # drop old entries
1216
if not self.multi_version and d.location == dist.location:
1217
continue
1218
1219
log.info("Removing %s from easy-install.pth file", d)
1220
self.pth_file.remove(d)
1221
if d.location in self.shadow_path:
1222
self.shadow_path.remove(d.location)
1223
1224
if not self.multi_version:
1225
if dist.location in self.pth_file.paths:
1226
log.info(
1227
"%s is already the active version in easy-install.pth",
1228
dist,
1229
)
1230
else:
1231
log.info("Adding %s to easy-install.pth file", dist)
1232
self.pth_file.add(dist) # add new entry
1233
if dist.location not in self.shadow_path:
1234
self.shadow_path.append(dist.location)
1235
1236
if self.dry_run:
1237
return
1238
1239
self.pth_file.save()
1240
1241
if dist.key != 'setuptools':
1242
return
1243
1244
# Ensure that setuptools itself never becomes unavailable!
1245
# XXX should this check for latest version?
1246
filename = os.path.join(self.install_dir, 'setuptools.pth')
1247
if os.path.islink(filename):
1248
os.unlink(filename)
1249
with open(filename, 'wt') as f:
1250
f.write(self.pth_file.make_relative(dist.location) + '\n')
1251
1252
def unpack_progress(self, src, dst):
1253
# Progress filter for unpacking
1254
log.debug("Unpacking %s to %s", src, dst)
1255
return dst # only unpack-and-compile skips files for dry run
1256
1257
def unpack_and_compile(self, egg_path, destination):
1258
to_compile = []
1259
to_chmod = []
1260
1261
def pf(src, dst):
1262
if dst.endswith('.py') and not src.startswith('EGG-INFO/'):
1263
to_compile.append(dst)
1264
elif dst.endswith('.dll') or dst.endswith('.so'):
1265
to_chmod.append(dst)
1266
self.unpack_progress(src, dst)
1267
return not self.dry_run and dst or None
1268
1269
unpack_archive(egg_path, destination, pf)
1270
self.byte_compile(to_compile)
1271
if not self.dry_run:
1272
for f in to_chmod:
1273
mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755
1274
chmod(f, mode)
1275
1276
def byte_compile(self, to_compile):
1277
if sys.dont_write_bytecode:
1278
return
1279
1280
from distutils.util import byte_compile
1281
1282
try:
1283
# try to make the byte compile messages quieter
1284
log.set_verbosity(self.verbose - 1)
1285
1286
byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)
1287
if self.optimize:
1288
byte_compile(
1289
to_compile, optimize=self.optimize, force=1,
1290
dry_run=self.dry_run,
1291
)
1292
finally:
1293
log.set_verbosity(self.verbose) # restore original verbosity
1294
1295
__no_default_msg = textwrap.dedent("""
1296
bad install directory or PYTHONPATH
1297
1298
You are attempting to install a package to a directory that is not
1299
on PYTHONPATH and which Python does not read ".pth" files from. The
1300
installation directory you specified (via --install-dir, --prefix, or
1301
the distutils default setting) was:
1302
1303
%s
1304
1305
and your PYTHONPATH environment variable currently contains:
1306
1307
%r
1308
1309
Here are some of your options for correcting the problem:
1310
1311
* You can choose a different installation directory, i.e., one that is
1312
on PYTHONPATH or supports .pth files
1313
1314
* You can add the installation directory to the PYTHONPATH environment
1315
variable. (It must then also be on PYTHONPATH whenever you run
1316
Python and want to use the package(s) you are installing.)
1317
1318
* You can set up the installation directory to support ".pth" files by
1319
using one of the approaches described here:
1320
1321
https://setuptools.pypa.io/en/latest/deprecated/easy_install.html#custom-installation-locations
1322
1323
1324
Please make the appropriate changes for your system and try again.
1325
""").strip()
1326
1327
def create_home_path(self):
1328
"""Create directories under ~."""
1329
if not self.user:
1330
return
1331
home = convert_path(os.path.expanduser("~"))
1332
for path in only_strs(self.config_vars.values()):
1333
if path.startswith(home) and not os.path.isdir(path):
1334
self.debug_print("os.makedirs('%s', 0o700)" % path)
1335
os.makedirs(path, 0o700)
1336
1337
INSTALL_SCHEMES = dict(
1338
posix=dict(
1339
install_dir='$base/lib/python$py_version_short/site-packages',
1340
script_dir='$base/bin',
1341
),
1342
)
1343
1344
DEFAULT_SCHEME = dict(
1345
install_dir='$base/Lib/site-packages',
1346
script_dir='$base/Scripts',
1347
)
1348
1349
def _expand(self, *attrs):
1350
config_vars = self.get_finalized_command('install').config_vars
1351
1352
if self.prefix:
1353
# Set default install_dir/scripts from --prefix
1354
config_vars = dict(config_vars)
1355
config_vars['base'] = self.prefix
1356
scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME)
1357
for attr, val in scheme.items():
1358
if getattr(self, attr, None) is None:
1359
setattr(self, attr, val)
1360
1361
from distutils.util import subst_vars
1362
1363
for attr in attrs:
1364
val = getattr(self, attr)
1365
if val is not None:
1366
val = subst_vars(val, config_vars)
1367
if os.name == 'posix':
1368
val = os.path.expanduser(val)
1369
setattr(self, attr, val)
1370
1371
1372
def _pythonpath():
1373
items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
1374
return filter(None, items)
1375
1376
1377
def get_site_dirs():
1378
"""
1379
Return a list of 'site' dirs
1380
"""
1381
1382
sitedirs = []
1383
1384
# start with PYTHONPATH
1385
sitedirs.extend(_pythonpath())
1386
1387
prefixes = [sys.prefix]
1388
if sys.exec_prefix != sys.prefix:
1389
prefixes.append(sys.exec_prefix)
1390
for prefix in prefixes:
1391
if not prefix:
1392
continue
1393
1394
if sys.platform in ('os2emx', 'riscos'):
1395
sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
1396
elif os.sep == '/':
1397
sitedirs.extend([
1398
os.path.join(
1399
prefix,
1400
"lib",
1401
"python{}.{}".format(*sys.version_info),
1402
"site-packages",
1403
),
1404
os.path.join(prefix, "lib", "site-python"),
1405
])
1406
else:
1407
sitedirs.extend([
1408
prefix,
1409
os.path.join(prefix, "lib", "site-packages"),
1410
])
1411
if sys.platform != 'darwin':
1412
continue
1413
1414
# for framework builds *only* we add the standard Apple
1415
# locations. Currently only per-user, but /Library and
1416
# /Network/Library could be added too
1417
if 'Python.framework' not in prefix:
1418
continue
1419
1420
home = os.environ.get('HOME')
1421
if not home:
1422
continue
1423
1424
home_sp = os.path.join(
1425
home,
1426
'Library',
1427
'Python',
1428
'{}.{}'.format(*sys.version_info),
1429
'site-packages',
1430
)
1431
sitedirs.append(home_sp)
1432
lib_paths = get_path('purelib'), get_path('platlib')
1433
1434
sitedirs.extend(s for s in lib_paths if s not in sitedirs)
1435
1436
if site.ENABLE_USER_SITE:
1437
sitedirs.append(site.USER_SITE)
1438
1439
with contextlib.suppress(AttributeError):
1440
sitedirs.extend(site.getsitepackages())
1441
1442
sitedirs = list(map(normalize_path, sitedirs))
1443
1444
return sitedirs
1445
1446
1447
def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME
1448
"""Yield sys.path directories that might contain "old-style" packages"""
1449
1450
seen = {}
1451
1452
for dirname in inputs:
1453
dirname = normalize_path(dirname)
1454
if dirname in seen:
1455
continue
1456
1457
seen[dirname] = 1
1458
if not os.path.isdir(dirname):
1459
continue
1460
1461
files = os.listdir(dirname)
1462
yield dirname, files
1463
1464
for name in files:
1465
if not name.endswith('.pth'):
1466
# We only care about the .pth files
1467
continue
1468
if name in ('easy-install.pth', 'setuptools.pth'):
1469
# Ignore .pth files that we control
1470
continue
1471
1472
# Read the .pth file
1473
f = open(os.path.join(dirname, name))
1474
lines = list(yield_lines(f))
1475
f.close()
1476
1477
# Yield existing non-dupe, non-import directory lines from it
1478
for line in lines:
1479
if line.startswith("import"):
1480
continue
1481
1482
line = normalize_path(line.rstrip())
1483
if line in seen:
1484
continue
1485
1486
seen[line] = 1
1487
if not os.path.isdir(line):
1488
continue
1489
1490
yield line, os.listdir(line)
1491
1492
1493
def extract_wininst_cfg(dist_filename):
1494
"""Extract configuration data from a bdist_wininst .exe
1495
1496
Returns a configparser.RawConfigParser, or None
1497
"""
1498
f = open(dist_filename, 'rb')
1499
try:
1500
endrec = zipfile._EndRecData(f)
1501
if endrec is None:
1502
return None
1503
1504
prepended = (endrec[9] - endrec[5]) - endrec[6]
1505
if prepended < 12: # no wininst data here
1506
return None
1507
f.seek(prepended - 12)
1508
1509
tag, cfglen, bmlen = struct.unpack("<iii", f.read(12))
1510
if tag not in (0x1234567A, 0x1234567B):
1511
return None # not a valid tag
1512
1513
f.seek(prepended - (12 + cfglen))
1514
init = {'version': '', 'target_version': ''}
1515
cfg = configparser.RawConfigParser(init)
1516
try:
1517
part = f.read(cfglen)
1518
# Read up to the first null byte.
1519
config = part.split(b'\0', 1)[0]
1520
# Now the config is in bytes, but for RawConfigParser, it should
1521
# be text, so decode it.
1522
config = config.decode(sys.getfilesystemencoding())
1523
cfg.read_file(io.StringIO(config))
1524
except configparser.Error:
1525
return None
1526
if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
1527
return None
1528
return cfg
1529
1530
finally:
1531
f.close()
1532
1533
1534
def get_exe_prefixes(exe_filename):
1535
"""Get exe->egg path translations for a given .exe file"""
1536
1537
prefixes = [
1538
('PURELIB/', ''),
1539
('PLATLIB/pywin32_system32', ''),
1540
('PLATLIB/', ''),
1541
('SCRIPTS/', 'EGG-INFO/scripts/'),
1542
('DATA/lib/site-packages', ''),
1543
]
1544
z = zipfile.ZipFile(exe_filename)
1545
try:
1546
for info in z.infolist():
1547
name = info.filename
1548
parts = name.split('/')
1549
if len(parts) == 3 and parts[2] == 'PKG-INFO':
1550
if parts[1].endswith('.egg-info'):
1551
prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/'))
1552
break
1553
if len(parts) != 2 or not name.endswith('.pth'):
1554
continue
1555
if name.endswith('-nspkg.pth'):
1556
continue
1557
if parts[0].upper() in ('PURELIB', 'PLATLIB'):
1558
contents = z.read(name).decode()
1559
for pth in yield_lines(contents):
1560
pth = pth.strip().replace('\\', '/')
1561
if not pth.startswith('import'):
1562
prefixes.append((('%s/%s/' % (parts[0], pth)), ''))
1563
finally:
1564
z.close()
1565
prefixes = [(x.lower(), y) for x, y in prefixes]
1566
prefixes.sort()
1567
prefixes.reverse()
1568
return prefixes
1569
1570
1571
class PthDistributions(Environment):
1572
"""A .pth file with Distribution paths in it"""
1573
1574
dirty = False
1575
1576
def __init__(self, filename, sitedirs=()):
1577
self.filename = filename
1578
self.sitedirs = list(map(normalize_path, sitedirs))
1579
self.basedir = normalize_path(os.path.dirname(self.filename))
1580
self._load()
1581
super().__init__([], None, None)
1582
for path in yield_lines(self.paths):
1583
list(map(self.add, find_distributions(path, True)))
1584
1585
def _load(self):
1586
self.paths = []
1587
saw_import = False
1588
seen = dict.fromkeys(self.sitedirs)
1589
if os.path.isfile(self.filename):
1590
f = open(self.filename, 'rt')
1591
for line in f:
1592
if line.startswith('import'):
1593
saw_import = True
1594
continue
1595
path = line.rstrip()
1596
self.paths.append(path)
1597
if not path.strip() or path.strip().startswith('#'):
1598
continue
1599
# skip non-existent paths, in case somebody deleted a package
1600
# manually, and duplicate paths as well
1601
path = self.paths[-1] = normalize_path(
1602
os.path.join(self.basedir, path)
1603
)
1604
if not os.path.exists(path) or path in seen:
1605
self.paths.pop() # skip it
1606
self.dirty = True # we cleaned up, so we're dirty now :)
1607
continue
1608
seen[path] = 1
1609
f.close()
1610
1611
if self.paths and not saw_import:
1612
self.dirty = True # ensure anything we touch has import wrappers
1613
while self.paths and not self.paths[-1].strip():
1614
self.paths.pop()
1615
1616
def save(self):
1617
"""Write changed .pth file back to disk"""
1618
if not self.dirty:
1619
return
1620
1621
rel_paths = list(map(self.make_relative, self.paths))
1622
if rel_paths:
1623
log.debug("Saving %s", self.filename)
1624
lines = self._wrap_lines(rel_paths)
1625
data = '\n'.join(lines) + '\n'
1626
1627
if os.path.islink(self.filename):
1628
os.unlink(self.filename)
1629
with open(self.filename, 'wt') as f:
1630
f.write(data)
1631
1632
elif os.path.exists(self.filename):
1633
log.debug("Deleting empty %s", self.filename)
1634
os.unlink(self.filename)
1635
1636
self.dirty = False
1637
1638
@staticmethod
1639
def _wrap_lines(lines):
1640
return lines
1641
1642
def add(self, dist):
1643
"""Add `dist` to the distribution map"""
1644
new_path = (
1645
dist.location not in self.paths and (
1646
dist.location not in self.sitedirs or
1647
# account for '.' being in PYTHONPATH
1648
dist.location == os.getcwd()
1649
)
1650
)
1651
if new_path:
1652
self.paths.append(dist.location)
1653
self.dirty = True
1654
super().add(dist)
1655
1656
def remove(self, dist):
1657
"""Remove `dist` from the distribution map"""
1658
while dist.location in self.paths:
1659
self.paths.remove(dist.location)
1660
self.dirty = True
1661
super().remove(dist)
1662
1663
def make_relative(self, path):
1664
npath, last = os.path.split(normalize_path(path))
1665
baselen = len(self.basedir)
1666
parts = [last]
1667
sep = os.altsep == '/' and '/' or os.sep
1668
while len(npath) >= baselen:
1669
if npath == self.basedir:
1670
parts.append(os.curdir)
1671
parts.reverse()
1672
return sep.join(parts)
1673
npath, last = os.path.split(npath)
1674
parts.append(last)
1675
else:
1676
return path
1677
1678
1679
class RewritePthDistributions(PthDistributions):
1680
@classmethod
1681
def _wrap_lines(cls, lines):
1682
yield cls.prelude
1683
for line in lines:
1684
yield line
1685
yield cls.postlude
1686
1687
prelude = _one_liner("""
1688
import sys
1689
sys.__plen = len(sys.path)
1690
""")
1691
postlude = _one_liner("""
1692
import sys
1693
new = sys.path[sys.__plen:]
1694
del sys.path[sys.__plen:]
1695
p = getattr(sys, '__egginsert', 0)
1696
sys.path[p:p] = new
1697
sys.__egginsert = p + len(new)
1698
""")
1699
1700
1701
if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite':
1702
PthDistributions = RewritePthDistributions
1703
1704
1705
def _first_line_re():
1706
"""
1707
Return a regular expression based on first_line_re suitable for matching
1708
strings.
1709
"""
1710
if isinstance(first_line_re.pattern, str):
1711
return first_line_re
1712
1713
# first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.
1714
return re.compile(first_line_re.pattern.decode())
1715
1716
1717
def auto_chmod(func, arg, exc):
1718
if func in [os.unlink, os.remove] and os.name == 'nt':
1719
chmod(arg, stat.S_IWRITE)
1720
return func(arg)
1721
et, ev, _ = sys.exc_info()
1722
# TODO: This code doesn't make sense. What is it trying to do?
1723
raise (ev[0], ev[1] + (" %s %s" % (func, arg)))
1724
1725
1726
def update_dist_caches(dist_path, fix_zipimporter_caches):
1727
"""
1728
Fix any globally cached `dist_path` related data
1729
1730
`dist_path` should be a path of a newly installed egg distribution (zipped
1731
or unzipped).
1732
1733
sys.path_importer_cache contains finder objects that have been cached when
1734
importing data from the original distribution. Any such finders need to be
1735
cleared since the replacement distribution might be packaged differently,
1736
e.g. a zipped egg distribution might get replaced with an unzipped egg
1737
folder or vice versa. Having the old finders cached may then cause Python
1738
to attempt loading modules from the replacement distribution using an
1739
incorrect loader.
1740
1741
zipimport.zipimporter objects are Python loaders charged with importing
1742
data packaged inside zip archives. If stale loaders referencing the
1743
original distribution, are left behind, they can fail to load modules from
1744
the replacement distribution. E.g. if an old zipimport.zipimporter instance
1745
is used to load data from a new zipped egg archive, it may cause the
1746
operation to attempt to locate the requested data in the wrong location -
1747
one indicated by the original distribution's zip archive directory
1748
information. Such an operation may then fail outright, e.g. report having
1749
read a 'bad local file header', or even worse, it may fail silently &
1750
return invalid data.
1751
1752
zipimport._zip_directory_cache contains cached zip archive directory
1753
information for all existing zipimport.zipimporter instances and all such
1754
instances connected to the same archive share the same cached directory
1755
information.
1756
1757
If asked, and the underlying Python implementation allows it, we can fix
1758
all existing zipimport.zipimporter instances instead of having to track
1759
them down and remove them one by one, by updating their shared cached zip
1760
archive directory information. This, of course, assumes that the
1761
replacement distribution is packaged as a zipped egg.
1762
1763
If not asked to fix existing zipimport.zipimporter instances, we still do
1764
our best to clear any remaining zipimport.zipimporter related cached data
1765
that might somehow later get used when attempting to load data from the new
1766
distribution and thus cause such load operations to fail. Note that when
1767
tracking down such remaining stale data, we can not catch every conceivable
1768
usage from here, and we clear only those that we know of and have found to
1769
cause problems if left alive. Any remaining caches should be updated by
1770
whomever is in charge of maintaining them, i.e. they should be ready to
1771
handle us replacing their zip archives with new distributions at runtime.
1772
1773
"""
1774
# There are several other known sources of stale zipimport.zipimporter
1775
# instances that we do not clear here, but might if ever given a reason to
1776
# do so:
1777
# * Global setuptools pkg_resources.working_set (a.k.a. 'master working
1778
# set') may contain distributions which may in turn contain their
1779
# zipimport.zipimporter loaders.
1780
# * Several zipimport.zipimporter loaders held by local variables further
1781
# up the function call stack when running the setuptools installation.
1782
# * Already loaded modules may have their __loader__ attribute set to the
1783
# exact loader instance used when importing them. Python 3.4 docs state
1784
# that this information is intended mostly for introspection and so is
1785
# not expected to cause us problems.
1786
normalized_path = normalize_path(dist_path)
1787
_uncache(normalized_path, sys.path_importer_cache)
1788
if fix_zipimporter_caches:
1789
_replace_zip_directory_cache_data(normalized_path)
1790
else:
1791
# Here, even though we do not want to fix existing and now stale
1792
# zipimporter cache information, we still want to remove it. Related to
1793
# Python's zip archive directory information cache, we clear each of
1794
# its stale entries in two phases:
1795
# 1. Clear the entry so attempting to access zip archive information
1796
# via any existing stale zipimport.zipimporter instances fails.
1797
# 2. Remove the entry from the cache so any newly constructed
1798
# zipimport.zipimporter instances do not end up using old stale
1799
# zip archive directory information.
1800
# This whole stale data removal step does not seem strictly necessary,
1801
# but has been left in because it was done before we started replacing
1802
# the zip archive directory information cache content if possible, and
1803
# there are no relevant unit tests that we can depend on to tell us if
1804
# this is really needed.
1805
_remove_and_clear_zip_directory_cache_data(normalized_path)
1806
1807
1808
def _collect_zipimporter_cache_entries(normalized_path, cache):
1809
"""
1810
Return zipimporter cache entry keys related to a given normalized path.
1811
1812
Alternative path spellings (e.g. those using different character case or
1813
those using alternative path separators) related to the same path are
1814
included. Any sub-path entries are included as well, i.e. those
1815
corresponding to zip archives embedded in other zip archives.
1816
1817
"""
1818
result = []
1819
prefix_len = len(normalized_path)
1820
for p in cache:
1821
np = normalize_path(p)
1822
if (np.startswith(normalized_path) and
1823
np[prefix_len:prefix_len + 1] in (os.sep, '')):
1824
result.append(p)
1825
return result
1826
1827
1828
def _update_zipimporter_cache(normalized_path, cache, updater=None):
1829
"""
1830
Update zipimporter cache data for a given normalized path.
1831
1832
Any sub-path entries are processed as well, i.e. those corresponding to zip
1833
archives embedded in other zip archives.
1834
1835
Given updater is a callable taking a cache entry key and the original entry
1836
(after already removing the entry from the cache), and expected to update
1837
the entry and possibly return a new one to be inserted in its place.
1838
Returning None indicates that the entry should not be replaced with a new
1839
one. If no updater is given, the cache entries are simply removed without
1840
any additional processing, the same as if the updater simply returned None.
1841
1842
"""
1843
for p in _collect_zipimporter_cache_entries(normalized_path, cache):
1844
# N.B. pypy's custom zipimport._zip_directory_cache implementation does
1845
# not support the complete dict interface:
1846
# * Does not support item assignment, thus not allowing this function
1847
# to be used only for removing existing cache entries.
1848
# * Does not support the dict.pop() method, forcing us to use the
1849
# get/del patterns instead. For more detailed information see the
1850
# following links:
1851
# https://github.com/pypa/setuptools/issues/202#issuecomment-202913420
1852
# http://bit.ly/2h9itJX
1853
old_entry = cache[p]
1854
del cache[p]
1855
new_entry = updater and updater(p, old_entry)
1856
if new_entry is not None:
1857
cache[p] = new_entry
1858
1859
1860
def _uncache(normalized_path, cache):
1861
_update_zipimporter_cache(normalized_path, cache)
1862
1863
1864
def _remove_and_clear_zip_directory_cache_data(normalized_path):
1865
def clear_and_remove_cached_zip_archive_directory_data(path, old_entry):
1866
old_entry.clear()
1867
1868
_update_zipimporter_cache(
1869
normalized_path, zipimport._zip_directory_cache,
1870
updater=clear_and_remove_cached_zip_archive_directory_data)
1871
1872
1873
# PyPy Python implementation does not allow directly writing to the
1874
# zipimport._zip_directory_cache and so prevents us from attempting to correct
1875
# its content. The best we can do there is clear the problematic cache content
1876
# and have PyPy repopulate it as needed. The downside is that if there are any
1877
# stale zipimport.zipimporter instances laying around, attempting to use them
1878
# will fail due to not having its zip archive directory information available
1879
# instead of being automatically corrected to use the new correct zip archive
1880
# directory information.
1881
if '__pypy__' in sys.builtin_module_names:
1882
_replace_zip_directory_cache_data = \
1883
_remove_and_clear_zip_directory_cache_data
1884
else:
1885
1886
def _replace_zip_directory_cache_data(normalized_path):
1887
def replace_cached_zip_archive_directory_data(path, old_entry):
1888
# N.B. In theory, we could load the zip directory information just
1889
# once for all updated path spellings, and then copy it locally and
1890
# update its contained path strings to contain the correct
1891
# spelling, but that seems like a way too invasive move (this cache
1892
# structure is not officially documented anywhere and could in
1893
# theory change with new Python releases) for no significant
1894
# benefit.
1895
old_entry.clear()
1896
zipimport.zipimporter(path)
1897
old_entry.update(zipimport._zip_directory_cache[path])
1898
return old_entry
1899
1900
_update_zipimporter_cache(
1901
normalized_path, zipimport._zip_directory_cache,
1902
updater=replace_cached_zip_archive_directory_data)
1903
1904
1905
def is_python(text, filename='<string>'):
1906
"Is this string a valid Python script?"
1907
try:
1908
compile(text, filename, 'exec')
1909
except (SyntaxError, TypeError):
1910
return False
1911
else:
1912
return True
1913
1914
1915
def is_sh(executable):
1916
"""Determine if the specified executable is a .sh (contains a #! line)"""
1917
try:
1918
with io.open(executable, encoding='latin-1') as fp:
1919
magic = fp.read(2)
1920
except (OSError, IOError):
1921
return executable
1922
return magic == '#!'
1923
1924
1925
def nt_quote_arg(arg):
1926
"""Quote a command line argument according to Windows parsing rules"""
1927
return subprocess.list2cmdline([arg])
1928
1929
1930
def is_python_script(script_text, filename):
1931
"""Is this text, as a whole, a Python script? (as opposed to shell/bat/etc.
1932
"""
1933
if filename.endswith('.py') or filename.endswith('.pyw'):
1934
return True # extension says it's Python
1935
if is_python(script_text, filename):
1936
return True # it's syntactically valid Python
1937
if script_text.startswith('#!'):
1938
# It begins with a '#!' line, so check if 'python' is in it somewhere
1939
return 'python' in script_text.splitlines()[0].lower()
1940
1941
return False # Not any Python I can recognize
1942
1943
1944
try:
1945
from os import chmod as _chmod
1946
except ImportError:
1947
# Jython compatibility
1948
def _chmod(*args):
1949
pass
1950
1951
1952
def chmod(path, mode):
1953
log.debug("changing mode of %s to %o", path, mode)
1954
try:
1955
_chmod(path, mode)
1956
except os.error as e:
1957
log.debug("chmod failed: %s", e)
1958
1959
1960
class CommandSpec(list):
1961
"""
1962
A command spec for a #! header, specified as a list of arguments akin to
1963
those passed to Popen.
1964
"""
1965
1966
options = []
1967
split_args = dict()
1968
1969
@classmethod
1970
def best(cls):
1971
"""
1972
Choose the best CommandSpec class based on environmental conditions.
1973
"""
1974
return cls
1975
1976
@classmethod
1977
def _sys_executable(cls):
1978
_default = os.path.normpath(sys.executable)
1979
return os.environ.get('__PYVENV_LAUNCHER__', _default)
1980
1981
@classmethod
1982
def from_param(cls, param):
1983
"""
1984
Construct a CommandSpec from a parameter to build_scripts, which may
1985
be None.
1986
"""
1987
if isinstance(param, cls):
1988
return param
1989
if isinstance(param, list):
1990
return cls(param)
1991
if param is None:
1992
return cls.from_environment()
1993
# otherwise, assume it's a string.
1994
return cls.from_string(param)
1995
1996
@classmethod
1997
def from_environment(cls):
1998
return cls([cls._sys_executable()])
1999
2000
@classmethod
2001
def from_string(cls, string):
2002
"""
2003
Construct a command spec from a simple string representing a command
2004
line parseable by shlex.split.
2005
"""
2006
items = shlex.split(string, **cls.split_args)
2007
return cls(items)
2008
2009
def install_options(self, script_text):
2010
self.options = shlex.split(self._extract_options(script_text))
2011
cmdline = subprocess.list2cmdline(self)
2012
if not isascii(cmdline):
2013
self.options[:0] = ['-x']
2014
2015
@staticmethod
2016
def _extract_options(orig_script):
2017
"""
2018
Extract any options from the first line of the script.
2019
"""
2020
first = (orig_script + '\n').splitlines()[0]
2021
match = _first_line_re().match(first)
2022
options = match.group(1) or '' if match else ''
2023
return options.strip()
2024
2025
def as_header(self):
2026
return self._render(self + list(self.options))
2027
2028
@staticmethod
2029
def _strip_quotes(item):
2030
_QUOTES = '"\''
2031
for q in _QUOTES:
2032
if item.startswith(q) and item.endswith(q):
2033
return item[1:-1]
2034
return item
2035
2036
@staticmethod
2037
def _render(items):
2038
cmdline = subprocess.list2cmdline(
2039
CommandSpec._strip_quotes(item.strip()) for item in items)
2040
return '#!' + cmdline + '\n'
2041
2042
2043
# For pbr compat; will be removed in a future version.
2044
sys_executable = CommandSpec._sys_executable()
2045
2046
2047
class WindowsCommandSpec(CommandSpec):
2048
split_args = dict(posix=False)
2049
2050
2051
class ScriptWriter:
2052
"""
2053
Encapsulates behavior around writing entry point scripts for console and
2054
gui apps.
2055
"""
2056
2057
template = textwrap.dedent(r"""
2058
# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
2059
import re
2060
import sys
2061
2062
# for compatibility with easy_install; see #2198
2063
__requires__ = %(spec)r
2064
2065
try:
2066
from importlib.metadata import distribution
2067
except ImportError:
2068
try:
2069
from importlib_metadata import distribution
2070
except ImportError:
2071
from pkg_resources import load_entry_point
2072
2073
2074
def importlib_load_entry_point(spec, group, name):
2075
dist_name, _, _ = spec.partition('==')
2076
matches = (
2077
entry_point
2078
for entry_point in distribution(dist_name).entry_points
2079
if entry_point.group == group and entry_point.name == name
2080
)
2081
return next(matches).load()
2082
2083
2084
globals().setdefault('load_entry_point', importlib_load_entry_point)
2085
2086
2087
if __name__ == '__main__':
2088
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
2089
sys.exit(load_entry_point(%(spec)r, %(group)r, %(name)r)())
2090
""").lstrip()
2091
2092
command_spec_class = CommandSpec
2093
2094
@classmethod
2095
def get_script_args(cls, dist, executable=None, wininst=False):
2096
# for backward compatibility
2097
warnings.warn("Use get_args", EasyInstallDeprecationWarning)
2098
writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
2099
header = cls.get_script_header("", executable, wininst)
2100
return writer.get_args(dist, header)
2101
2102
@classmethod
2103
def get_script_header(cls, script_text, executable=None, wininst=False):
2104
# for backward compatibility
2105
warnings.warn(
2106
"Use get_header", EasyInstallDeprecationWarning, stacklevel=2)
2107
if wininst:
2108
executable = "python.exe"
2109
return cls.get_header(script_text, executable)
2110
2111
@classmethod
2112
def get_args(cls, dist, header=None):
2113
"""
2114
Yield write_script() argument tuples for a distribution's
2115
console_scripts and gui_scripts entry points.
2116
"""
2117
if header is None:
2118
header = cls.get_header()
2119
spec = str(dist.as_requirement())
2120
for type_ in 'console', 'gui':
2121
group = type_ + '_scripts'
2122
for name, ep in dist.get_entry_map(group).items():
2123
cls._ensure_safe_name(name)
2124
script_text = cls.template % locals()
2125
args = cls._get_script_args(type_, name, header, script_text)
2126
for res in args:
2127
yield res
2128
2129
@staticmethod
2130
def _ensure_safe_name(name):
2131
"""
2132
Prevent paths in *_scripts entry point names.
2133
"""
2134
has_path_sep = re.search(r'[\\/]', name)
2135
if has_path_sep:
2136
raise ValueError("Path separators not allowed in script names")
2137
2138
@classmethod
2139
def get_writer(cls, force_windows):
2140
# for backward compatibility
2141
warnings.warn("Use best", EasyInstallDeprecationWarning)
2142
return WindowsScriptWriter.best() if force_windows else cls.best()
2143
2144
@classmethod
2145
def best(cls):
2146
"""
2147
Select the best ScriptWriter for this environment.
2148
"""
2149
if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'):
2150
return WindowsScriptWriter.best()
2151
else:
2152
return cls
2153
2154
@classmethod
2155
def _get_script_args(cls, type_, name, header, script_text):
2156
# Simply write the stub with no extension.
2157
yield (name, header + script_text)
2158
2159
@classmethod
2160
def get_header(cls, script_text="", executable=None):
2161
"""Create a #! line, getting options (if any) from script_text"""
2162
cmd = cls.command_spec_class.best().from_param(executable)
2163
cmd.install_options(script_text)
2164
return cmd.as_header()
2165
2166
2167
class WindowsScriptWriter(ScriptWriter):
2168
command_spec_class = WindowsCommandSpec
2169
2170
@classmethod
2171
def get_writer(cls):
2172
# for backward compatibility
2173
warnings.warn("Use best", EasyInstallDeprecationWarning)
2174
return cls.best()
2175
2176
@classmethod
2177
def best(cls):
2178
"""
2179
Select the best ScriptWriter suitable for Windows
2180
"""
2181
writer_lookup = dict(
2182
executable=WindowsExecutableLauncherWriter,
2183
natural=cls,
2184
)
2185
# for compatibility, use the executable launcher by default
2186
launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')
2187
return writer_lookup[launcher]
2188
2189
@classmethod
2190
def _get_script_args(cls, type_, name, header, script_text):
2191
"For Windows, add a .py extension"
2192
ext = dict(console='.pya', gui='.pyw')[type_]
2193
if ext not in os.environ['PATHEXT'].lower().split(';'):
2194
msg = (
2195
"{ext} not listed in PATHEXT; scripts will not be "
2196
"recognized as executables."
2197
).format(**locals())
2198
warnings.warn(msg, UserWarning)
2199
old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
2200
old.remove(ext)
2201
header = cls._adjust_header(type_, header)
2202
blockers = [name + x for x in old]
2203
yield name + ext, header + script_text, 't', blockers
2204
2205
@classmethod
2206
def _adjust_header(cls, type_, orig_header):
2207
"""
2208
Make sure 'pythonw' is used for gui and 'python' is used for
2209
console (regardless of what sys.executable is).
2210
"""
2211
pattern = 'pythonw.exe'
2212
repl = 'python.exe'
2213
if type_ == 'gui':
2214
pattern, repl = repl, pattern
2215
pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)
2216
new_header = pattern_ob.sub(string=orig_header, repl=repl)
2217
return new_header if cls._use_header(new_header) else orig_header
2218
2219
@staticmethod
2220
def _use_header(new_header):
2221
"""
2222
Should _adjust_header use the replaced header?
2223
2224
On non-windows systems, always use. On
2225
Windows systems, only use the replaced header if it resolves
2226
to an executable on the system.
2227
"""
2228
clean_header = new_header[2:-1].strip('"')
2229
return sys.platform != 'win32' or find_executable(clean_header)
2230
2231
2232
class WindowsExecutableLauncherWriter(WindowsScriptWriter):
2233
@classmethod
2234
def _get_script_args(cls, type_, name, header, script_text):
2235
"""
2236
For Windows, add a .py extension and an .exe launcher
2237
"""
2238
if type_ == 'gui':
2239
launcher_type = 'gui'
2240
ext = '-script.pyw'
2241
old = ['.pyw']
2242
else:
2243
launcher_type = 'cli'
2244
ext = '-script.py'
2245
old = ['.py', '.pyc', '.pyo']
2246
hdr = cls._adjust_header(type_, header)
2247
blockers = [name + x for x in old]
2248
yield (name + ext, hdr + script_text, 't', blockers)
2249
yield (
2250
name + '.exe', get_win_launcher(launcher_type),
2251
'b' # write in binary mode
2252
)
2253
if not is_64bit():
2254
# install a manifest for the launcher to prevent Windows
2255
# from detecting it as an installer (which it will for
2256
# launchers like easy_install.exe). Consider only
2257
# adding a manifest for launchers detected as installers.
2258
# See Distribute #143 for details.
2259
m_name = name + '.exe.manifest'
2260
yield (m_name, load_launcher_manifest(name), 't')
2261
2262
2263
# for backward-compatibility
2264
get_script_args = ScriptWriter.get_script_args
2265
get_script_header = ScriptWriter.get_script_header
2266
2267
2268
def get_win_launcher(type):
2269
"""
2270
Load the Windows launcher (executable) suitable for launching a script.
2271
2272
`type` should be either 'cli' or 'gui'
2273
2274
Returns the executable as a byte string.
2275
"""
2276
launcher_fn = '%s.exe' % type
2277
if is_64bit():
2278
if get_platform() == "win-arm64":
2279
launcher_fn = launcher_fn.replace(".", "-arm64.")
2280
else:
2281
launcher_fn = launcher_fn.replace(".", "-64.")
2282
else:
2283
launcher_fn = launcher_fn.replace(".", "-32.")
2284
return resource_string('setuptools', launcher_fn)
2285
2286
2287
def load_launcher_manifest(name):
2288
manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml')
2289
return manifest.decode('utf-8') % vars()
2290
2291
2292
def rmtree(path, ignore_errors=False, onerror=auto_chmod):
2293
return shutil.rmtree(path, ignore_errors, onerror)
2294
2295
2296
def current_umask():
2297
tmp = os.umask(0o022)
2298
os.umask(tmp)
2299
return tmp
2300
2301
2302
def only_strs(values):
2303
"""
2304
Exclude non-str values. Ref #3063.
2305
"""
2306
return filter(lambda val: isinstance(val, str), values)
2307
2308
2309
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
2310
"""
2311
Warning for EasyInstall deprecations, bypassing suppression.
2312
"""
2313
2314