-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathinstall.py.in
172 lines (146 loc) · 5.75 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
#!/usr/bin/env python
import argparse
import filecmp
import os
import shutil
import stat
import sys
from subprocess import check_output, check_call
subdirs = set()
prefix = None
libs = {}
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
# 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)
if dst.endswith('.so') or dst.endswith('.dylib'):
basename = os.path.basename(dst)
# 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_library_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
)
# 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 'path' == split_line[0] and split_line[1].startswith('$ORIGIN'):
# path is specified 2 lines below
check_call(
['install_name_tool', "-delete_rpath", split_line[1], dst_full]
)
def create_java_launcher(filename, classpath, main_class):
# 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
# Set up options.
parser = argparse.ArgumentParser()
parser.add_argument('prefix', type=str, help='Install prefix')
args = parser.parse_args(args)
# Get install prefix.
prefix = args.prefix
# 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>>
# On MacOS, library paths may need to be updated.
if sys.platform == "darwin":
fix_library_rpaths()
if __name__ == "__main__":
main(sys.argv[1:])