|
1
|
|
|
""" |
|
2
|
|
|
AppVeyor will at least have few Pythons around so there's no point of implementing a bootstrapper in PowerShell. |
|
3
|
|
|
|
|
4
|
|
|
This is a port of https://github.com/pypa/python-packaging-user-guide/blob/master/source/code/install.ps1 |
|
5
|
|
|
with various fixes and improvements that just weren't feasible to implement in PowerShell. |
|
6
|
|
|
""" |
|
7
|
|
|
from __future__ import print_function |
|
8
|
|
|
|
|
9
|
|
|
from os import environ |
|
10
|
|
|
from os.path import exists |
|
11
|
|
|
from subprocess import check_call |
|
12
|
|
|
|
|
13
|
|
|
try: |
|
14
|
|
|
from urllib.request import urlretrieve |
|
15
|
|
|
except ImportError: |
|
16
|
|
|
from urllib import urlretrieve |
|
17
|
|
|
|
|
18
|
|
|
BASE_URL = "https://www.python.org/ftp/python/" |
|
19
|
|
|
GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" |
|
20
|
|
|
GET_PIP_PATH = "C:\get-pip.py" |
|
21
|
|
|
URLS = { |
|
22
|
|
|
("2.7", "64"): BASE_URL + "2.7.13/python-2.7.13.amd64.msi", |
|
23
|
|
|
("2.7", "32"): BASE_URL + "2.7.13/python-2.7.13.msi", |
|
24
|
|
|
("3.4", "64"): BASE_URL + "3.4.4/python-3.4.4.amd64.msi", |
|
25
|
|
|
("3.4", "32"): BASE_URL + "3.4.4/python-3.4.4.msi", |
|
26
|
|
|
("3.5", "64"): BASE_URL + "3.5.4/python-3.5.4-amd64.exe", |
|
27
|
|
|
("3.5", "32"): BASE_URL + "3.5.4/python-3.5.4.exe", |
|
28
|
|
|
("3.6", "64"): BASE_URL + "3.6.2/python-3.6.2-amd64.exe", |
|
29
|
|
|
("3.6", "32"): BASE_URL + "3.6.2/python-3.6.2.exe", |
|
30
|
|
|
("3.7", "32"): BASE_URL + "3.7.8/python-3.7.8-amd64.exe", |
|
31
|
|
|
("3.7", "64"): BASE_URL + "3.7.8/python-3.7.8.exe", |
|
32
|
|
|
("3.8", "32"): BASE_URL + "3.8.4/python-3.8.4-amd64.exe", |
|
33
|
|
|
("3.8", "64"): BASE_URL + "3.8.4/python-3.8.4.exe", |
|
34
|
|
|
} |
|
35
|
|
|
INSTALL_CMD = { |
|
36
|
|
|
# Commands are allowed to fail only if they are not the last command. Eg: uninstall (/x) allowed to fail. |
|
37
|
|
|
"2.7": [["msiexec.exe", "/L*+!", "install.log", "/qn", "/x", "{path}"], |
|
38
|
|
|
["msiexec.exe", "/L*+!", "install.log", "/qn", "/i", "{path}", "TARGETDIR={home}"]], |
|
39
|
|
|
"3.4": [["msiexec.exe", "/L*+!", "install.log", "/qn", "/x", "{path}"], |
|
40
|
|
|
["msiexec.exe", "/L*+!", "install.log", "/qn", "/i", "{path}", "TARGETDIR={home}"]], |
|
41
|
|
|
"3.5": [["{path}", "/quiet", "TargetDir={home}"]], |
|
42
|
|
|
"3.6": [["{path}", "/quiet", "TargetDir={home}"]], |
|
43
|
|
|
"3.7": [["{path}", "/quiet", "TargetDir={home}"]], |
|
44
|
|
|
"3.8": [["{path}", "/quiet", "TargetDir={home}"]], |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
def command_iterator(version, **kwargs): |
|
48
|
|
|
"""Call to iterate through the defined commands, given a python version. Each command is a list of words""" |
|
49
|
|
|
return iter([part.format(home=kwargs['home'], path=kwargs['path']) for part in cmd] for cmd in INSTALL_CMD[version]) |
|
50
|
|
|
|
|
51
|
|
|
|
|
52
|
|
|
def exec_command(cmd): |
|
53
|
|
|
print("Running:", " ".join(cmd)) |
|
54
|
|
|
try: |
|
55
|
|
|
check_call(cmd) |
|
56
|
|
|
return True |
|
57
|
|
|
except Exception as e: |
|
58
|
|
|
print("Failed command", cmd, "with:", e) |
|
59
|
|
|
if exists("install.log"): |
|
60
|
|
|
with open("install.log") as f: |
|
61
|
|
|
print(f.read()) |
|
62
|
|
|
return False |
|
63
|
|
|
|
|
64
|
|
|
def download_file(url, path): |
|
65
|
|
|
print("Downloading: {} (into {})".format(url, path)) |
|
66
|
|
|
progress = [0, 0] |
|
67
|
|
|
|
|
68
|
|
|
def report(count, size, total): |
|
69
|
|
|
progress[0] = count * size |
|
70
|
|
|
if progress[0] - progress[1] > 1000000: |
|
|
|
|
|
|
71
|
|
|
progress[1] = progress[0] |
|
72
|
|
|
print("Downloaded {:,}/{:,} ...".format(progress[1], total)) |
|
73
|
|
|
|
|
74
|
|
|
dest, _ = urlretrieve(url, path, reporthook=report) |
|
75
|
|
|
return dest |
|
76
|
|
|
|
|
77
|
|
|
|
|
78
|
|
|
def install_python(python_specs): |
|
79
|
|
|
print("Installing Python", python_specs.version, "for", python_specs.arch, "bit architecture to", python_specs.home) |
|
80
|
|
|
if exists(python_specs.home): |
|
81
|
|
|
return |
|
82
|
|
|
path = download_python(python_specs.version, python_specs.arch) |
|
83
|
|
|
print("Installing", python_specs.path, "to", python_specs.home) |
|
84
|
|
|
success = any(x == True for x in [exec_command(cmd) for cmd in command_iterator(version, home=python_specs.home, path=python_specs.path)]) |
|
|
|
|
|
|
85
|
|
|
if success: |
|
86
|
|
|
print("Installation complete!") |
|
87
|
|
|
else: |
|
88
|
|
|
print("Installation failed") |
|
89
|
|
|
|
|
90
|
|
|
|
|
91
|
|
|
def download_python(version, arch): |
|
92
|
|
|
for _ in range(3): |
|
93
|
|
|
try: |
|
94
|
|
|
return download_file(URLS[version, arch], "installer.exe") |
|
95
|
|
|
except Exception as exc: |
|
96
|
|
|
print("Failed to download:", exc) |
|
97
|
|
|
print("Retrying ...") |
|
98
|
|
|
|
|
99
|
|
|
|
|
100
|
|
|
def install_pip(home): |
|
101
|
|
|
pip_path = home + "/Scripts/pip.exe" |
|
102
|
|
|
python_path = home + "/python.exe" |
|
103
|
|
|
if exists(pip_path): |
|
104
|
|
|
print("pip already installed.") |
|
105
|
|
|
else: |
|
106
|
|
|
print("Installing pip...") |
|
107
|
|
|
download_file(GET_PIP_URL, GET_PIP_PATH) |
|
108
|
|
|
print("Executing:", python_path, GET_PIP_PATH) |
|
109
|
|
|
check_call([python_path, GET_PIP_PATH]) |
|
110
|
|
|
|
|
111
|
|
|
def upgrade_pip(python_home_folder): |
|
112
|
|
|
print('Upgrading pip ..') |
|
113
|
|
|
try: |
|
114
|
|
|
check_call([python_home_folder + '/python.exe', '-m', 'pip', 'install', '--upgrade', 'pip']) |
|
115
|
|
|
except Exception as e: |
|
116
|
|
|
print("Failed to upgade pip: {}".format(e)) |
|
117
|
|
|
|
|
118
|
|
|
|
|
119
|
|
|
def install_packages(home, *packages): |
|
120
|
|
|
print('Installing packages [{}]'.format(', '.join(str(_) for _ in packages))) |
|
121
|
|
|
# cmd = [home + "/Scripts/pip.exe", "install"] |
|
122
|
|
|
cmd = [home + "/python.exe", "-m", "pip", "install"] |
|
123
|
|
|
cmd.extend(packages) |
|
124
|
|
|
check_call(cmd) |
|
125
|
|
|
|
|
126
|
|
|
|
|
127
|
|
|
if __name__ == "__main__": |
|
128
|
|
|
python_specs = type('PythonSpecs', (object,), {'version': environ['PYTHON_VERSION'], |
|
129
|
|
|
'arch': environ['PYTHON_ARCH'], |
|
130
|
|
|
'home': environ['PYTHON_HOME']}) |
|
131
|
|
|
install_python(python_specs) |
|
132
|
|
|
install_pip(environ['PYTHON_HOME']) |
|
133
|
|
|
upgrade_pip(environ['PYTHON_HOME']) |
|
134
|
|
|
install_packages(environ['PYTHON_HOME'], "setuptools>=40.0.0", "wheel", "tox", "virtualenv>=20.0.0") |
|
135
|
|
|
|