mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
Add File class to represent a file to install
Hiding the file-specific implementation we currently use will let us trade out the implementation for a zip-backed one later. We can also use this interface to represent the other kinds of files that we have to generate as part of wheel installation. We use a Protocol instead of a base class because there's no need for shared behavior right now, and using Protocol is less verbose.
This commit is contained in:
parent
6b26ac911a
commit
aa8dd9cecc
|
@ -54,6 +54,7 @@ else:
|
||||||
List,
|
List,
|
||||||
NewType,
|
NewType,
|
||||||
Optional,
|
Optional,
|
||||||
|
Protocol,
|
||||||
Sequence,
|
Sequence,
|
||||||
Set,
|
Set,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
@ -69,6 +70,14 @@ else:
|
||||||
RecordPath = NewType('RecordPath', text_type)
|
RecordPath = NewType('RecordPath', text_type)
|
||||||
InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
|
InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
|
||||||
|
|
||||||
|
class File(Protocol):
|
||||||
|
src_path = None # type: text_type
|
||||||
|
dest_path = None # type: text_type
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
# type: () -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -388,6 +397,54 @@ def get_console_script_specs(console):
|
||||||
return scripts_to_generate
|
return scripts_to_generate
|
||||||
|
|
||||||
|
|
||||||
|
class DiskFile(object):
|
||||||
|
def __init__(self, src_path, dest_path):
|
||||||
|
# type: (text_type, text_type) -> None
|
||||||
|
self.src_path = src_path
|
||||||
|
self.dest_path = dest_path
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
# type: () -> None
|
||||||
|
# directory creation is lazy and after file filtering
|
||||||
|
# to ensure we don't install empty dirs; empty dirs can't be
|
||||||
|
# uninstalled.
|
||||||
|
parent_dir = os.path.dirname(self.dest_path)
|
||||||
|
ensure_dir(parent_dir)
|
||||||
|
|
||||||
|
# copyfile (called below) truncates the destination if it
|
||||||
|
# exists and then writes the new contents. This is fine in most
|
||||||
|
# cases, but can cause a segfault if pip has loaded a shared
|
||||||
|
# object (e.g. from pyopenssl through its vendored urllib3)
|
||||||
|
# Since the shared object is mmap'd an attempt to call a
|
||||||
|
# symbol in it will then cause a segfault. Unlinking the file
|
||||||
|
# allows writing of new contents while allowing the process to
|
||||||
|
# continue to use the old copy.
|
||||||
|
if os.path.exists(self.dest_path):
|
||||||
|
os.unlink(self.dest_path)
|
||||||
|
|
||||||
|
# We use copyfile (not move, copy, or copy2) to be extra sure
|
||||||
|
# that we are not moving directories over (copyfile fails for
|
||||||
|
# directories) as well as to ensure that we are not copying
|
||||||
|
# over any metadata because we want more control over what
|
||||||
|
# metadata we actually copy over.
|
||||||
|
shutil.copyfile(self.src_path, self.dest_path)
|
||||||
|
|
||||||
|
# Copy over the metadata for the file, currently this only
|
||||||
|
# includes the atime and mtime.
|
||||||
|
st = os.stat(self.src_path)
|
||||||
|
if hasattr(os, "utime"):
|
||||||
|
os.utime(self.dest_path, (st.st_atime, st.st_mtime))
|
||||||
|
|
||||||
|
# If our file is executable, then make our destination file
|
||||||
|
# executable.
|
||||||
|
if os.access(self.src_path, os.X_OK):
|
||||||
|
st = os.stat(self.src_path)
|
||||||
|
permissions = (
|
||||||
|
st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
||||||
|
)
|
||||||
|
os.chmod(self.dest_path, permissions)
|
||||||
|
|
||||||
|
|
||||||
class MissingCallableSuffix(Exception):
|
class MissingCallableSuffix(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -479,49 +536,13 @@ def install_unpacked_wheel(
|
||||||
continue
|
continue
|
||||||
srcfile = os.path.join(dir, f)
|
srcfile = os.path.join(dir, f)
|
||||||
destfile = os.path.join(dest, basedir, f)
|
destfile = os.path.join(dest, basedir, f)
|
||||||
# directory creation is lazy and after the file filtering above
|
file = DiskFile(srcfile, destfile)
|
||||||
# to ensure we don't install empty dirs; empty dirs can't be
|
|
||||||
# uninstalled.
|
|
||||||
parent_dir = os.path.dirname(destfile)
|
|
||||||
ensure_dir(parent_dir)
|
|
||||||
|
|
||||||
# copyfile (called below) truncates the destination if it
|
|
||||||
# exists and then writes the new contents. This is fine in most
|
|
||||||
# cases, but can cause a segfault if pip has loaded a shared
|
|
||||||
# object (e.g. from pyopenssl through its vendored urllib3)
|
|
||||||
# Since the shared object is mmap'd an attempt to call a
|
|
||||||
# symbol in it will then cause a segfault. Unlinking the file
|
|
||||||
# allows writing of new contents while allowing the process to
|
|
||||||
# continue to use the old copy.
|
|
||||||
if os.path.exists(destfile):
|
|
||||||
os.unlink(destfile)
|
|
||||||
|
|
||||||
# We use copyfile (not move, copy, or copy2) to be extra sure
|
|
||||||
# that we are not moving directories over (copyfile fails for
|
|
||||||
# directories) as well as to ensure that we are not copying
|
|
||||||
# over any metadata because we want more control over what
|
|
||||||
# metadata we actually copy over.
|
|
||||||
shutil.copyfile(srcfile, destfile)
|
|
||||||
|
|
||||||
# Copy over the metadata for the file, currently this only
|
|
||||||
# includes the atime and mtime.
|
|
||||||
st = os.stat(srcfile)
|
|
||||||
if hasattr(os, "utime"):
|
|
||||||
os.utime(destfile, (st.st_atime, st.st_mtime))
|
|
||||||
|
|
||||||
# If our file is executable, then make our destination file
|
|
||||||
# executable.
|
|
||||||
if os.access(srcfile, os.X_OK):
|
|
||||||
st = os.stat(srcfile)
|
|
||||||
permissions = (
|
|
||||||
st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
|
||||||
)
|
|
||||||
os.chmod(destfile, permissions)
|
|
||||||
|
|
||||||
|
file.save()
|
||||||
changed = False
|
changed = False
|
||||||
if fixer:
|
if fixer:
|
||||||
changed = fixer(destfile)
|
changed = fixer(file.dest_path)
|
||||||
record_installed(srcfile, destfile, changed)
|
record_installed(file.src_path, file.dest_path, changed)
|
||||||
|
|
||||||
clobber(
|
clobber(
|
||||||
ensure_text(source, encoding=sys.getfilesystemencoding()),
|
ensure_text(source, encoding=sys.getfilesystemencoding()),
|
||||||
|
|
Loading…
Reference in a new issue