This commit is contained in:
lz_db
2026-10-16 22:48:30 +08:00
parent 190de72571
commit 0b36fd3383
3161 changed files with 12787 additions and 77274 deletions
@@ -2,30 +2,21 @@
Build .egg distributions"""
from __future__ import annotations
import marshal
from distutils.dir_util import remove_tree, mkpath
from distutils import log
from types import CodeType
import sys
import os
import re
import sys
import textwrap
from sysconfig import get_path, get_python_version
from types import CodeType
from typing import TYPE_CHECKING, Literal
import marshal
from setuptools import Command
from pkg_resources import get_build_platform, Distribution
from setuptools.extension import Library
from setuptools import Command
from .._path import ensure_directory
from .._path import StrPathT, ensure_directory
from distutils import log
from distutils.dir_util import mkpath, remove_tree
if TYPE_CHECKING:
from typing_extensions import TypeAlias
# Same as zipfile._ZipFileMode from typeshed
_ZipFileMode: TypeAlias = Literal["r", "w", "x", "a"]
from sysconfig import get_path, get_python_version
def _get_purelib():
@@ -50,9 +41,8 @@ def sorted_walk(dir):
yield base, dirs, files
def write_stub(resource, pyfile) -> None:
_stub_template = textwrap.dedent(
"""
def write_stub(resource, pyfile):
_stub_template = textwrap.dedent("""
def __bootstrap__():
global __bootstrap__, __loader__, __file__
import sys, pkg_resources, importlib.util
@@ -62,46 +52,44 @@ def write_stub(resource, pyfile) -> None:
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
__bootstrap__()
"""
).lstrip()
with open(pyfile, 'w', encoding="utf-8") as f:
""").lstrip()
with open(pyfile, 'w') as f:
f.write(_stub_template % resource)
class bdist_egg(Command):
description = 'create an "egg" distribution'
description = "create an \"egg\" distribution"
user_options = [
('bdist-dir=', 'b', "temporary directory for creating the distribution"),
(
'plat-name=',
'p',
"platform name to embed in generated filenames "
"(by default uses `pkg_resources.get_build_platform()`)",
),
('exclude-source-files', None, "remove all .py files from the generated egg"),
(
'keep-temp',
'k',
"keep the pseudo-installation tree around after "
"creating the distribution archive",
),
('dist-dir=', 'd', "directory to put final built distributions in"),
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
('bdist-dir=', 'b',
"temporary directory for creating the distribution"),
('plat-name=', 'p', "platform name to embed in generated filenames "
"(default: %s)" % get_build_platform()),
('exclude-source-files', None,
"remove all .py files from the generated egg"),
('keep-temp', 'k',
"keep the pseudo-installation tree around after " +
"creating the distribution archive"),
('dist-dir=', 'd',
"directory to put final built distributions in"),
('skip-build', None,
"skip rebuilding everything (for testing/debugging)"),
]
boolean_options = ['keep-temp', 'skip-build', 'exclude-source-files']
boolean_options = [
'keep-temp', 'skip-build', 'exclude-source-files'
]
def initialize_options(self):
self.bdist_dir = None
self.plat_name = None
self.keep_temp = False
self.keep_temp = 0
self.dist_dir = None
self.skip_build = False
self.skip_build = 0
self.egg_output = None
self.exclude_source_files = None
def finalize_options(self) -> None:
def finalize_options(self):
ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info")
self.egg_info = ei_cmd.egg_info
@@ -110,22 +98,22 @@ class bdist_egg(Command):
self.bdist_dir = os.path.join(bdist_base, 'egg')
if self.plat_name is None:
from pkg_resources import get_build_platform
self.plat_name = get_build_platform()
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
if self.egg_output is None:
# Compute filename of the output egg
basename = ei_cmd._get_egg_basename(
py_version=get_python_version(),
platform=self.distribution.has_ext_modules() and self.plat_name,
)
basename = Distribution(
None, None, ei_cmd.egg_name, ei_cmd.egg_version,
get_python_version(),
self.distribution.has_ext_modules() and self.plat_name
).egg_name()
self.egg_output = os.path.join(self.dist_dir, basename + '.egg')
def do_install_data(self) -> None:
def do_install_data(self):
# Hack for packages that install data to install's --install-lib
self.get_finalized_command('install').install_lib = self.bdist_dir
@@ -140,13 +128,13 @@ class bdist_egg(Command):
if normalized == site_packages or normalized.startswith(
site_packages + os.sep
):
item = realpath[len(site_packages) + 1 :], item[1]
item = realpath[len(site_packages) + 1:], item[1]
# XXX else: raise ???
self.distribution.data_files.append(item)
try:
log.info("installing package data to %s", self.bdist_dir)
self.call_command('install_data', force=False, root=None)
self.call_command('install_data', force=0, root=None)
finally:
self.distribution.data_files = old
@@ -174,15 +162,16 @@ class bdist_egg(Command):
instcmd.root = None
if self.distribution.has_c_libraries() and not self.skip_build:
self.run_command('build_clib')
cmd = self.call_command('install_lib', warn_dir=False)
cmd = self.call_command('install_lib', warn_dir=0)
instcmd.root = old_root
all_outputs, ext_outputs = self.get_ext_outputs()
self.stubs = []
to_compile = []
for p, ext_name in enumerate(ext_outputs):
filename, _ext = os.path.splitext(ext_name)
pyfile = os.path.join(self.bdist_dir, strip_module(filename) + '.py')
for (p, ext_name) in enumerate(ext_outputs):
filename, ext = os.path.splitext(ext_name)
pyfile = os.path.join(self.bdist_dir, strip_module(filename) +
'.py')
self.stubs.append(pyfile)
log.info("creating stub loader for %s", ext_name)
if not self.dry_run:
@@ -202,7 +191,8 @@ class bdist_egg(Command):
if self.distribution.scripts:
script_dir = os.path.join(egg_info, 'scripts')
log.info("installing scripts to %s", script_dir)
self.call_command('install_scripts', install_dir=script_dir, no_ep=True)
self.call_command('install_scripts', install_dir=script_dir,
no_ep=1)
self.copy_metadata_to(egg_info)
native_libs = os.path.join(egg_info, "native_libs.txt")
@@ -210,15 +200,18 @@ class bdist_egg(Command):
log.info("writing %s", native_libs)
if not self.dry_run:
ensure_directory(native_libs)
with open(native_libs, 'wt', encoding="utf-8") as libs_file:
libs_file.write('\n'.join(all_outputs))
libs_file.write('\n')
libs_file = open(native_libs, 'wt')
libs_file.write('\n'.join(all_outputs))
libs_file.write('\n')
libs_file.close()
elif os.path.isfile(native_libs):
log.info("removing %s", native_libs)
if not self.dry_run:
os.unlink(native_libs)
write_safety_flag(os.path.join(archive_root, 'EGG-INFO'), self.zip_safe())
write_safety_flag(
os.path.join(archive_root, 'EGG-INFO'), self.zip_safe()
)
if os.path.exists(os.path.join(self.egg_info, 'depends.txt')):
log.warn(
@@ -230,22 +223,14 @@ class bdist_egg(Command):
self.zap_pyfiles()
# Make the archive
make_zipfile(
self.egg_output,
archive_root,
verbose=self.verbose,
dry_run=self.dry_run,
mode=self.gen_header(),
)
make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
dry_run=self.dry_run, mode=self.gen_header())
if not self.keep_temp:
remove_tree(self.bdist_dir, dry_run=self.dry_run)
# Add to 'Distribution.dist_files' so that the "upload" command works
getattr(self.distribution, 'dist_files', []).append((
'bdist_egg',
get_python_version(),
self.egg_output,
))
getattr(self.distribution, 'dist_files', []).append(
('bdist_egg', get_python_version(), self.egg_output))
def zap_pyfiles(self):
log.info("Removing .py files from temporary directory")
@@ -262,8 +247,11 @@ class bdist_egg(Command):
pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc'
m = re.match(pattern, name)
path_new = os.path.join(base, os.pardir, m.group('name') + '.pyc')
log.info(f"Renaming file from [{path_old}] to [{path_new}]")
path_new = os.path.join(
base, os.pardir, m.group('name') + '.pyc')
log.info(
"Renaming file from [%s] to [%s]"
% (path_old, path_new))
try:
os.remove(path_new)
except OSError:
@@ -277,10 +265,10 @@ class bdist_egg(Command):
log.warn("zip_safe flag not set; analyzing archive contents...")
return analyze_egg(self.bdist_dir, self.stubs)
def gen_header(self) -> Literal["w"]:
def gen_header(self):
return 'w'
def copy_metadata_to(self, target_dir) -> None:
def copy_metadata_to(self, target_dir):
"Copy metadata (egg info) to the target_dir"
# normalize the path (so that a forward-slash in egg_info will
# match using startswith below)
@@ -288,7 +276,7 @@ class bdist_egg(Command):
prefix = os.path.join(norm_egg_info, '')
for path in self.ei_cmd.filelist.files:
if path.startswith(prefix):
target = os.path.join(target_dir, path[len(prefix) :])
target = os.path.join(target_dir, path[len(prefix):])
ensure_directory(target)
self.copy_file(path, target)
@@ -300,13 +288,12 @@ class bdist_egg(Command):
paths = {self.bdist_dir: ''}
for base, dirs, files in sorted_walk(self.bdist_dir):
all_outputs.extend(
paths[base] + filename
for filename in files
if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS
)
for filename in files:
if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:
all_outputs.append(paths[base] + filename)
for filename in dirs:
paths[os.path.join(base, filename)] = paths[base] + filename + '/'
paths[os.path.join(base, filename)] = (paths[base] +
filename + '/')
if self.distribution.has_ext_modules():
build_cmd = self.get_finalized_command('build_ext')
@@ -322,7 +309,7 @@ class bdist_egg(Command):
return all_outputs, ext_outputs
NATIVE_EXTENSIONS: dict[str, None] = dict.fromkeys('.dll .so .dylib .pyd'.split())
NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split())
def walk_egg(egg_dir):
@@ -332,7 +319,8 @@ def walk_egg(egg_dir):
if 'EGG-INFO' in dirs:
dirs.remove('EGG-INFO')
yield base, dirs, files
yield from walker
for bdf in walker:
yield bdf
def analyze_egg(egg_dir, stubs):
@@ -353,7 +341,7 @@ def analyze_egg(egg_dir, stubs):
return safe
def write_safety_flag(egg_dir, safe) -> None:
def write_safety_flag(egg_dir, safe):
# Write or remove zip safety flag file(s)
for flag, fn in safety_flags.items():
fn = os.path.join(egg_dir, fn)
@@ -361,8 +349,9 @@ def write_safety_flag(egg_dir, safe) -> None:
if safe is None or bool(safe) != flag:
os.unlink(fn)
elif safe is not None and bool(safe) == flag:
with open(fn, 'wt', encoding="utf-8") as f:
f.write('\n')
f = open(fn, 'wt')
f.write('\n')
f.close()
safety_flags = {
@@ -377,9 +366,12 @@ def scan_module(egg_dir, base, name, stubs):
filename = os.path.join(base, name)
if filename[:-1] in stubs:
return True # Extension module
pkg = base[len(egg_dir) + 1 :].replace(os.sep, '.')
pkg = base[len(egg_dir) + 1:].replace(os.sep, '.')
module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]
skip = 16 # skip magic & reserved? & date & file size
if sys.version_info < (3, 7):
skip = 12 # skip magic & date & file size
else:
skip = 16 # skip magic & reserved? & date & file size
f = open(filename, 'rb')
f.read(skip)
code = marshal.load(f)
@@ -392,18 +384,9 @@ def scan_module(egg_dir, base, name, stubs):
safe = False
if 'inspect' in symbols:
for bad in [
'getsource',
'getabsfile',
'getfile',
'getsourcefile',
'getsourcelines',
'findsource',
'getcomments',
'getframeinfo',
'getinnerframes',
'getouterframes',
'stack',
'trace',
'getsource', 'getabsfile', 'getsourcefile', 'getfile'
'getsourcelines', 'findsource', 'getcomments', 'getframeinfo',
'getinnerframes', 'getouterframes', 'stack', 'trace'
]:
if bad in symbols:
log.warn("%s: module MAY be using inspect.%s", module, bad)
@@ -413,40 +396,35 @@ def scan_module(egg_dir, base, name, stubs):
def iter_symbols(code):
"""Yield names and strings used by `code` and its nested code objects"""
yield from code.co_names
for name in code.co_names:
yield name
for const in code.co_consts:
if isinstance(const, str):
yield const
elif isinstance(const, CodeType):
yield from iter_symbols(const)
for name in iter_symbols(const):
yield name
def can_scan() -> bool:
def can_scan():
if not sys.platform.startswith('java') and sys.platform != 'cli':
# CPython, PyPy, etc.
return True
log.warn("Unable to analyze compiled code on this platform.")
log.warn(
"Please ask the author to include a 'zip_safe'"
" setting (either True or False) in the package's setup.py"
)
return False
log.warn("Please ask the author to include a 'zip_safe'"
" setting (either True or False) in the package's setup.py")
# Attribute names of options for commands that might need to be convinced to
# install to the egg build directory
INSTALL_DIRECTORY_ATTRS = ['install_lib', 'install_dir', 'install_data', 'install_base']
INSTALL_DIRECTORY_ATTRS = [
'install_lib', 'install_dir', 'install_data', 'install_base'
]
def make_zipfile(
zip_filename: StrPathT,
base_dir,
verbose: bool = False,
dry_run: bool = False,
compress=True,
mode: _ZipFileMode = 'w',
) -> StrPathT:
def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True,
mode='w'):
"""Create a zip file from all the files under 'base_dir'. The output
zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
Python module (if available) or the InfoZIP "zip" utility (if installed
@@ -455,14 +433,14 @@ def make_zipfile(
"""
import zipfile
mkpath(os.path.dirname(zip_filename), dry_run=dry_run) # type: ignore[arg-type] # python/mypy#18075
mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
def visit(z, dirname, names):
for name in names:
path = os.path.normpath(os.path.join(dirname, name))
if os.path.isfile(path):
p = path[len(base_dir) + 1 :]
p = path[len(base_dir) + 1:]
if not dry_run:
z.write(path, p)
log.debug("adding '%s'", p)