-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathinstall.py.in
244 lines (211 loc) · 8.03 KB
/
install.py.in
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
#!/usr/bin/env python
import argparse
import filecmp
import os
import re
import shutil
import stat
import sys
from subprocess import check_output, check_call, CalledProcessError
subdirs = set()
prefix = None
libs = {}
list_only = False
# On linux, dynamic libraries may have their version number
# as a suffix (e.g. my_lib.so.x.y.z).
dylib_match = "(.*\.so)(\.\\d+)*$|(.*\.dylib)$"
def needs_install(src, dst):
# Get canonical destination.
dst_full = os.path.join(prefix, dst)
# Check if destination exists.
if not os.path.exists(dst_full):
# Destination doesn't exist -> installation needed.
return True
# Check if files are different.
if filecmp.cmp(src, dst_full, shallow=False):
# Files are the same -> no installation needed.
return False
# File needs to be installed.
return True
def install(src, dst):
global subdirs
# In list-only mode, just display the filename, don't do any real work.
if list_only:
print(dst)
return
# Ensure destination subdirectory exists, creating it if necessary.
subdir = os.path.dirname(dst)
if subdir not in subdirs:
subdir_full = os.path.join(prefix, subdir)
if not os.path.exists(subdir_full):
os.makedirs(subdir_full)
subdirs.add(subdir)
installed = False
dst_full = os.path.join(prefix, dst)
# Install file, if not up to date.
if needs_install(src, dst):
print("[Installing] %s" % dst)
if os.path.exists(dst_full):
os.remove(dst_full)
shutil.copy2(src, dst_full)
installed = True
else:
print("[Up to date] %s" % dst)
re_result = re.match(dylib_match, dst)
if re_result is not None:
basename = os.path.basename(re_result.group(0))
# Check that dependency is only referenced once
# in the library dictionary. If it is referenced multiple times,
# we do not know which one to use, and fail fast.
if basename in libs:
sys.stderr.write(
"Multiple installation rules found for %s." % (basename))
sys.exit(1)
libs[basename] = (dst_full, installed)
def fix_libraries_rpaths():
# Only fix libraries that are installed now.
fix_libs = [(k, libs[k][0]) for k in libs.keys() if libs[k][1]]
for basename, dst_full in fix_libs:
# Enable write permissions to allow modification.
os.chmod(dst_full, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
if sys.platform == "darwin":
macos_fix_libraries_rpaths(basename, dst_full)
else:
linux_fix_libraries_rpaths(dst_full)
def macos_fix_libraries_rpaths(basename, dst_full):
# Update library ID (remove relative path).
check_call(
['install_name_tool', "-id", "@rpath/" + basename, dst_full]
)
# Check if library dependencies are specified with relative paths.
file_output = check_output(["otool", "-L", dst_full])
for line in file_output.splitlines():
# keep only file path, remove version information.
relative_path = line.split(' (')[0].strip()
# If path is relative, it needs to be replaced by absolute path.
if "@loader_path" not in relative_path:
continue
dep_basename = os.path.basename(relative_path)
# Look for the absolute path in the dictionary of libraries.
if dep_basename not in libs.keys():
continue
lib_dirname = os.path.dirname(dst_full)
diff_path = os.path.relpath(libs[dep_basename][0], lib_dirname)
check_call(
['install_name_tool',
"-change", relative_path,
os.path.join('@loader_path', diff_path),
dst_full]
)
# Remove rpath values that contain $ORIGIN. These are from the build
# tree and are irrelevant in the install tree.
file_output = check_output(["otool", "-l", dst_full])
for line in file_output.splitlines():
split_line = line.strip().split(' ')
if len(split_line) >= 2 \
and split_line[0] == 'path' \
and split_line[1].startswith('$ORIGIN'):
check_call(
['install_name_tool', "-delete_rpath", split_line[1], dst_full]
)
def linux_fix_libraries_rpaths(dst_full):
# Check that library has an rpath or a runpath tag
try:
check_output(["chrpath", "-l", dst_full])
except OSError as ex:
if ex.errno == 2 and ex.strerror == "No such file or directory":
print("`chrpath` not found. Please run install_prereqs.sh.")
sys.exit(1)
except CalledProcessError as ex:
if ex.returncode == 2:
if ex.output.strip().endswith('no rpath or runpath tag found.'):
# Cannot be modified with `chrpath`, so we skip it.
return
else:
raise ex
# Check if library dependencies are found.
file_output = check_output(["ldd", dst_full])
rpath = set()
for line in file_output.splitlines():
ldd_result = line.strip().split(' => ')
# If library found, then skip.
if len(ldd_result) < 2 or ldd_result[1] != "not found":
continue
re_result = re.match(dylib_match, ldd_result[0])
# Look for the absolute path in the dictionary of libraries using the
# library name without its possible version number.
soname, sover, _ = re_result.groups()
if soname not in libs.keys():
continue
lib_dirname = os.path.dirname(dst_full)
diff_path = os.path.dirname(
os.path.relpath(libs[soname][0], lib_dirname)
)
rpath.add('$ORIGIN' + '/' + diff_path)
if rpath:
str_rpath = ":".join(x for x in rpath)
check_output(
['chrpath',
"-r", str_rpath,
dst_full]
)
def create_java_launcher(filename, classpath, main_class):
# In list-only mode, just display the filename, don't do any real work.
if list_only:
print(filename)
return
# Make sure install directory exists.
filename = os.path.join(prefix, filename)
filepath = os.path.dirname(filename)
if not os.path.exists(filepath):
os.makedirs(filepath)
# Converting classpath to string
strclasspath = '"{}"'.format('" "'.join(classpath))
# Write launcher.
if os.path.exists(filename):
os.chmod(filename, stat.S_IWUSR)
with open(filename, 'w') as launcher_file:
content = """#!/bin/bash
# autogenerated - do not edit.
set -euo pipefail
# This file is installed to <prefix>/bin.
readonly prefix=$(python -c 'import os;\
print(os.path.realpath(os.path.join(\"'\"$0\"'\", os.pardir, os.pardir)))')
for jar_file in %s; do
if [ -f "$jar_file" ]; then
export CLASSPATH="${CLASSPATH:+$CLASSPATH:}$jar_file"
fi
done
java %s
""" % (strclasspath, main_class)
launcher_file.write(content)
os.chmod(filename, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH |
stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
def main(args):
global prefix
global list_only
# Set up options.
parser = argparse.ArgumentParser()
parser.add_argument('prefix', type=str, help='Install prefix')
parser.add_argument(
'--list', action='store_true', default=False,
help='print the list of installed files; do not install anything')
args = parser.parse_args(args)
# Get install prefix.
prefix = args.prefix
list_only = args.list
# Because Bazel executes us in a strange working directory and not the
# working directory of the user's shell, enforce that the install
# location is an absolute path so that the user is not surprised.
if not os.path.isabs(prefix):
sys.stderr.write(
"Install prefix must be an absolute path (got %r)\n"
% prefix)
sys.exit(1)
# Execute the install actions.
<<actions>>
# Libraries paths may need to be updated.
fix_libraries_rpaths()
if __name__ == "__main__":
main(sys.argv[1:])