-
Notifications
You must be signed in to change notification settings - Fork 122
/
Copy pathutils.py
269 lines (216 loc) · 10.2 KB
/
utils.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env python
# Copyright 2020 Google
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Helper functions that are shared amongst prereqs and build scripts across various
platforms.
"""
import distutils.spawn
import glob
import platform
import shutil
import subprocess
import os
import urllib.request
def run_command(cmd, capture_output=False, cwd=None, check=False, as_root=False,
print_cmd=True):
"""Run a command.
Args:
cmd (list(str)): Command to run as a list object.
Eg: ['ls', '-l'].
capture_output (bool): Capture the output of this command.
Output can be accessed as <return_object>.stdout
cwd (str): Directory to execute the command from.
check (bool): Raises a CalledProcessError if True and the command errored out
as_root (bool): Run command as root user with admin priveleges (supported on mac and linux).
print_cmd (bool): Print the command we are running to stdout.
Raises:
(subprocess.CalledProcessError): If command errored out and `text=True`
Returns:
(`subprocess.CompletedProcess`): object containing information from
command execution
"""
if as_root and (is_mac_os() or is_linux_os()):
cmd.insert(0, 'sudo')
cmd_string = ' '.join(cmd)
if print_cmd:
print('Running cmd: {0}\n'.format(cmd_string))
# If capture_output is requested, we also set text=True to store the returned value of the
# command as a string instead of bytes object
return subprocess.run(cmd, capture_output=capture_output, cwd=cwd,
check=check, text=capture_output)
def is_command_installed(tool):
"""Check if a command is installed on the system."""
return distutils.spawn.find_executable(tool)
def glob_exists(glob_path):
"""Check if any file/directory exists at a given path glob."""
return len(glob.glob(glob_path)) > 0
def delete_directory(dir_path):
"""Recursively delete a valid directory"""
if os.path.exists(dir_path):
shutil.rmtree(dir_path)
def download_file(url, file_path):
"""Download from url and save to specified file path."""
with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
def unpack_files(archive_file_path, output_dir=None):
"""Unpack/extract an archive to specified output_directory"""
shutil.unpack_archive(archive_file_path, output_dir)
def is_windows_os():
return platform.system() == 'Windows'
def is_mac_os():
return platform.system() == 'Darwin'
def is_linux_os():
return platform.system() == 'Linux'
def copy_vcpkg_custom_data():
"""Copy custom files for vcpkg to vcpkg directory."""
# Since vcpkg is a submodule in the cpp sdk repo, we cannot just keep our custom
# files in the external/vcpkg directory. That would require committing to the
# vcpkg submodule. Instead we keep the data in a separate directory and copy it
# over after vcpkg submodule is initialized.
custom_data_root_path = os.path.join(os.getcwd(), 'external', 'vcpkg_custom_data')
destination_dirs_map = {
'triplets': os.path.join(get_vcpkg_root_path(), 'triplets'),
'toolchains': os.path.join(get_vcpkg_root_path(), 'scripts', 'buildsystems')
}
for custom_data_subdir in destination_dirs_map:
custom_data_subdir_abspath = os.path.join(custom_data_root_path, custom_data_subdir)
destination_dir = destination_dirs_map[custom_data_subdir]
for custom_file in os.listdir(custom_data_subdir_abspath):
abspath = os.path.join(custom_data_subdir_abspath, custom_file)
shutil.copy(abspath, destination_dir)
def get_vcpkg_triplet(arch, msvc_runtime_library='static'):
""" Get vcpkg target triplet (platform definition).
Args:
arch (str): Architecture (eg: 'x86', 'x64', 'arm64').
msvc_runtime_library (str): Runtime library for MSVC (eg: 'static', 'dynamic').
Raises:
ValueError: If current OS is not win,mac or linux.
Returns:
(str): Triplet name.
Eg: "x64-windows-static".
"""
triplet_name = [arch]
if is_windows_os():
triplet_name.append('windows')
triplet_name.append('static')
if msvc_runtime_library == 'dynamic':
triplet_name.append('md')
elif is_mac_os():
triplet_name.append('osx')
elif is_linux_os():
triplet_name.append('linux')
triplet_name = '-'.join(triplet_name)
print("Using vcpkg triplet: {0}".format(triplet_name))
return triplet_name
def get_vcpkg_root_path():
"""Get absolute path to vcpkg root directory in repo."""
return os.path.join(os.getcwd(), 'external', 'vcpkg')
def get_vcpkg_executable_file_path():
"""Get absolute path to vcpkg executable."""
vcpkg_root_dir = get_vcpkg_root_path()
if is_windows_os():
vcpkg_executable_file_path = os.path.join(vcpkg_root_dir, 'vcpkg.exe')
elif is_linux_os() or is_mac_os():
vcpkg_executable_file_path = os.path.join(vcpkg_root_dir, 'vcpkg')
return vcpkg_executable_file_path
def get_vcpkg_installation_script_path():
"""Get absolute path to the script used to build and install vcpkg."""
vcpkg_root_dir = get_vcpkg_root_path()
if is_windows_os():
script_absolute_path = os.path.join(vcpkg_root_dir, 'bootstrap-vcpkg.bat')
elif is_linux_os() or is_mac_os():
script_absolute_path = os.path.join(vcpkg_root_dir, 'bootstrap-vcpkg.sh')
return script_absolute_path
def verify_vcpkg_build(vcpkg_triplet, attempt_auto_fix=False):
"""Check if vcpkg installation finished successfully.
Args:
vcpkg_triplet (str): Triplet name for vcpkg. Eg: 'x64-linux'
attempt_auto_fix (bool): If installation failed, try fixing some errors.
Returns:
(bool) True if everything looks good
False if installation failed but auto fix was attempted.
Caller should retry installation in this case.
Raises:
(ValueError) Installation failed and auto fix was not attempted
"""
# At the very least, we should have an "installed" directory under vcpkg triplet.
vcpkg_root_dir_path = get_vcpkg_root_path()
installed_triplets_dir_path = os.path.join(vcpkg_root_dir_path, 'installed', vcpkg_triplet)
if not os.path.exists(installed_triplets_dir_path):
if is_windows_os() and attempt_auto_fix:
# On some Windows machines with NFS drives, we have seen errors
# installing vcpkg due to permission issues while renaming temp directories.
# Manually renaming and re-running script makes it go through.
tools_dir_path = os.path.join(vcpkg_root_dir_path, 'downloads', 'tools')
for name in os.listdir(tools_dir_path):
# In the specific windows error that we noticed, the error occurs while
# trying to rename intermediate directories for donwloaded tools
# like "powershell.partial.<pid>" to "powershell". Renaming via python
# also runs into the same error. Workaround is to copy instead of rename.
if '.partial.' in name and os.path.isdir(os.path.join(tools_dir_path, name)):
expected_name = name.split('.partial.')[0]
shutil.copytree(os.path.join(tools_dir_path, name),
os.path.join(tools_dir_path, expected_name))
return False
raise ValueError("Could not find directory containing installed packages by vcpkg: {0}\n"
"Please check if there were errors during "
"vcpkg installation.".format(installed_triplets_dir_path))
return True
def clean_vcpkg_temp_data():
"""Delete files/directories that vcpkg uses during its build"""
# Clear temporary directories and files created by vcpkg buildtrees
# could be several GBs and cause github runners to run out of space
directories_to_remove = ['buildtrees', 'downloads', 'packages']
vcpkg_root_dir_path = get_vcpkg_root_path()
for directory_to_remove in directories_to_remove:
abspath = os.path.join(vcpkg_root_dir_path, directory_to_remove)
delete_directory(abspath)
def install_x86_support_libraries(gha_build=False):
"""Install support libraries needed to build x86 on x86_64 hosts.
Args:
gha_build: Pass in True if running on a GitHub runner; this will activate
workarounds that might be undesirable on a personal system (e.g.
downgrading Ubuntu packages).
"""
if is_linux_os():
packages = ['gcc-10-multilib', 'g++-10-multilib', 'libglib2.0-dev:i386',
'libsecret-1-dev:i386', 'libpthread-stubs0-dev:i386',
'libssl-dev:i386', 'libsecret-1-0:i386', 'libgcc-s1:i386',
'gcc-10-base:i386']
remove_packages = []
# First check if these packages exist on the machine already
with open(os.devnull, "w") as devnull:
process = subprocess.run(["dpkg", "-s"] + packages, stdout=devnull,
stderr=subprocess.STDOUT)
if process.returncode != 0:
# This implies not all of the required packages are already installed on
# user's machine. Install them.
run_command(['dpkg', '--add-architecture', 'i386'], as_root=True,
check=True)
run_command(['apt', 'update'], as_root=True, check=True)
run_command(['apt', 'install', 'aptitude'], as_root=True, check=True)
if gha_build:
# Remove libpcre to prevent package conflicts.
# Only remove packages on GitHub runners.
remove_packages = ['libpcre2-16-0:amd64', 'libpcre2-32-0:amd64', 'libpcre2-8-0:amd64',
'libpcre2-posix3:amd64', 'libpcre2-dev:amd64', 'pcre2-utils:amd64']
# Note: With aptitude, you can remove package 'xyz' by specifying 'xyz-'
# in the package list.
run_command(['aptitude', 'install', '-V', '-y'] + packages +
['%s-' % pkg for pkg in remove_packages], as_root=True, check=True)
# Check if the packages were installed
with open(os.devnull, "w") as devnull:
subprocess.run(["dpkg", "-s"] + packages, stdout=devnull, stderr=subprocess.STDOUT,
check=True)