-
Notifications
You must be signed in to change notification settings - Fork 5
/
pip.py
143 lines (115 loc) · 4 KB
/
pip.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import os
import sys
import json
import typing
import logging
import tempfile
import itertools
import subprocess
import dataclasses
import dataclasses_json
import rez_pip.data
import rez_pip.exceptions
_LOG = logging.getLogger(__name__)
@dataclasses.dataclass
class Metadata(dataclasses_json.DataClassJsonMixin):
version: str
name: str
@dataclasses.dataclass
class ArchiveInfo(dataclasses_json.DataClassJsonMixin):
hash: str
hashes: typing.Dict[str, str]
@dataclasses.dataclass
class DownloadInfo(dataclasses_json.DataClassJsonMixin):
url: str
archive_info: ArchiveInfo
dataclass_json_config = dataclasses_json.config( # type: ignore
undefined=dataclasses_json.Undefined.EXCLUDE
)
@dataclasses.dataclass
class PackageInfo(dataclasses_json.DataClassJsonMixin):
download_info: DownloadInfo
is_direct: bool
requested: bool
metadata: Metadata
dataclass_json_config = dataclasses_json.config( # type: ignore
undefined=dataclasses_json.Undefined.EXCLUDE
)
@property
def name(self) -> str:
return self.metadata.name
@property
def version(self) -> str:
return self.metadata.version
def getBundledPip() -> str:
return os.path.join(os.path.dirname(rez_pip.data.__file__), "pip.pyz")
def getPackages(
packageNames: typing.List[str],
pip: str,
pythonVersion: str,
pythonExecutable: str,
requirements: typing.List[str],
constraints: typing.List[str],
extraArgs: typing.List[str],
) -> typing.List[PackageInfo]:
# python pip.pyz install -q requests --dry-run --ignore-installed --python-version 2.7 --only-binary=:all: --target /tmp/asd --report -
_fd, tmpFile = tempfile.mkstemp(prefix="pip-install-output", text=True)
os.close(_fd)
# We can't with "with" (context manager) because it will fail on Windows.
# Windows doesn't allow two different processes to write if the file is
# already opened.
try:
command = [
# We need to use the real interpreter because pip can't resolve
# markers correctly even if --python-version is provided.
# See https://github.com/pypa/pip/issues/11664.
pythonExecutable,
pip,
"install",
"-q",
*packageNames,
*list(itertools.chain(*zip(["-r"] * len(requirements), requirements))),
*list(itertools.chain(*zip(["-c"] * len(constraints), constraints))),
"--disable-pip-version-check",
"--dry-run",
"--ignore-installed",
f"--python-version={pythonVersion}" if pythonVersion else "",
"--only-binary=:all:",
"--target=/tmp/asd",
"--disable-pip-version-check",
"--report", # This is the "magic". Pip will generate a JSON with all the resolved URLs.
tmpFile,
*extraArgs,
]
_LOG.debug(f"Running {' '.join(command)!r}")
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
pipOutput = []
while True:
stdout = typing.cast(typing.IO[str], process.stdout).readline()
if process.poll() is not None:
break
if stdout:
pipOutput.append(stdout.rstrip())
sys.stdout.write(stdout)
if process.poll() != 0:
output = "\n".join(pipOutput)
raise rez_pip.exceptions.PipError(
f"[bold red]Failed to run pip command[/]: {' '.join(command)!r}\n\n"
"[bold]Pip reported this[/]:\n\n"
f"{output}",
)
with open(tmpFile, "r") as fd:
rawData = json.load(fd)
finally:
os.remove(tmpFile)
rawPackages = rawData["install"]
packages: typing.List[PackageInfo] = []
for rawPackage in rawPackages:
packageInfo = PackageInfo.from_dict(rawPackage)
packages.append(packageInfo)
return packages