1#!/usr/bin/env python 2 3from __future__ import print_function 4 5import ast 6import errno 7import os 8import shutil 9import sys 10 11# set at init time 12node_prefix = '/usr/local' # PREFIX variable from Makefile 13install_path = '' # base target directory (DESTDIR + PREFIX from Makefile) 14target_defaults = None 15variables = None 16 17def abspath(*args): 18 path = os.path.join(*args) 19 return os.path.abspath(path) 20 21def load_config(): 22 with open('config.gypi') as f: 23 return ast.literal_eval(f.read()) 24 25def try_unlink(path): 26 try: 27 os.unlink(path) 28 except OSError as e: 29 if e.errno != errno.ENOENT: raise 30 31def try_symlink(source_path, link_path): 32 print('symlinking %s -> %s' % (source_path, link_path)) 33 try_unlink(link_path) 34 try_mkdir_r(os.path.dirname(link_path)) 35 os.symlink(source_path, link_path) 36 37def try_mkdir_r(path): 38 try: 39 os.makedirs(path) 40 except OSError as e: 41 if e.errno != errno.EEXIST: raise 42 43def try_rmdir_r(path): 44 path = abspath(path) 45 while path.startswith(install_path): 46 try: 47 os.rmdir(path) 48 except OSError as e: 49 if e.errno == errno.ENOTEMPTY: return 50 if e.errno == errno.ENOENT: return 51 raise 52 path = abspath(path, '..') 53 54def mkpaths(path, dst): 55 if dst.endswith('/'): 56 target_path = abspath(install_path, dst, os.path.basename(path)) 57 else: 58 target_path = abspath(install_path, dst) 59 return path, target_path 60 61def try_copy(path, dst): 62 source_path, target_path = mkpaths(path, dst) 63 print('installing %s' % target_path) 64 try_mkdir_r(os.path.dirname(target_path)) 65 try_unlink(target_path) # prevent ETXTBSY errors 66 return shutil.copy2(source_path, target_path) 67 68def try_remove(path, dst): 69 source_path, target_path = mkpaths(path, dst) 70 print('removing %s' % target_path) 71 try_unlink(target_path) 72 try_rmdir_r(os.path.dirname(target_path)) 73 74def install(paths, dst): 75 for path in paths: 76 try_copy(path, dst) 77 78def uninstall(paths, dst): 79 for path in paths: 80 try_remove(path, dst) 81 82def package_files(action, name, bins): 83 target_path = 'lib/node_modules/' + name + '/' 84 85 # don't install npm if the target path is a symlink, it probably means 86 # that a dev version of npm is installed there 87 if os.path.islink(abspath(install_path, target_path)): return 88 89 # npm has a *lot* of files and it'd be a pain to maintain a fixed list here 90 # so we walk its source directory instead... 91 root = 'deps/' + name 92 for dirname, subdirs, basenames in os.walk(root, topdown=True): 93 subdirs[:] = [subdir for subdir in subdirs if subdir != 'test'] 94 paths = [os.path.join(dirname, basename) for basename in basenames] 95 action(paths, target_path + dirname[len(root) + 1:] + '/') 96 97 # create/remove symlinks 98 for bin_name, bin_target in bins.items(): 99 link_path = abspath(install_path, 'bin/' + bin_name) 100 if action == uninstall: 101 action([link_path], 'bin/' + bin_name) 102 elif action == install: 103 try_symlink('../lib/node_modules/' + name + '/' + bin_target, link_path) 104 else: 105 assert 0 # unhandled action type 106 107def npm_files(action): 108 package_files(action, 'npm', { 109 'npm': 'bin/npm-cli.js', 110 'npx': 'bin/npx-cli.js', 111 }) 112 113def corepack_files(action): 114 package_files(action, 'corepack', { 115 'corepack': 'dist/corepack.js', 116# Not the default just yet: 117# 'yarn': 'dist/yarn.js', 118# 'yarnpkg': 'dist/yarn.js', 119# 'pnpm': 'dist/pnpm.js', 120# 'pnpx': 'dist/pnpx.js', 121 }) 122 123def subdir_files(path, dest, action): 124 ret = {} 125 for dirpath, dirnames, filenames in os.walk(path): 126 files_in_path = [dirpath + '/' + f for f in filenames if f.endswith('.h')] 127 ret[dest + dirpath.replace(path, '')] = files_in_path 128 for subdir, files_in_path in ret.items(): 129 action(files_in_path, subdir + '/') 130 131def files(action): 132 is_windows = sys.platform == 'win32' 133 output_file = 'node' 134 output_prefix = 'out/Release/' 135 136 if 'false' == variables.get('node_shared'): 137 if is_windows: 138 output_file += '.exe' 139 else: 140 if is_windows: 141 output_file += '.dll' 142 else: 143 output_file = 'lib' + output_file + '.' + variables.get('shlib_suffix') 144 145 if 'false' == variables.get('node_shared'): 146 action([output_prefix + output_file], 'bin/' + output_file) 147 else: 148 action([output_prefix + output_file], 'lib/' + output_file) 149 150 if 'true' == variables.get('node_use_dtrace'): 151 action(['out/Release/node.d'], 'lib/dtrace/node.d') 152 153 # behave similarly for systemtap 154 action(['src/node.stp'], 'share/systemtap/tapset/') 155 156 action(['deps/v8/tools/gdbinit'], 'share/doc/node/') 157 action(['deps/v8/tools/lldb_commands.py'], 'share/doc/node/') 158 159 if 'freebsd' in sys.platform or 'openbsd' in sys.platform: 160 action(['doc/node.1'], 'man/man1/') 161 else: 162 action(['doc/node.1'], 'share/man/man1/') 163 164 if 'true' == variables.get('node_install_npm'): 165 npm_files(action) 166 167 if 'true' == variables.get('node_install_corepack'): 168 corepack_files(action) 169 170 headers(action) 171 172def headers(action): 173 def ignore_inspector_headers(files_arg, dest): 174 inspector_headers = [ 175 'deps/v8/include/v8-inspector.h', 176 'deps/v8/include/v8-inspector-protocol.h' 177 ] 178 files_arg = [name for name in files_arg if name not in inspector_headers] 179 action(files_arg, dest) 180 181 action([ 182 'common.gypi', 183 'config.gypi', 184 'src/node.h', 185 'src/node_api.h', 186 'src/js_native_api.h', 187 'src/js_native_api_types.h', 188 'src/node_api_types.h', 189 'src/node_buffer.h', 190 'src/node_object_wrap.h', 191 'src/node_version.h', 192 ], 'include/node/') 193 194 # Add the expfile that is created on AIX 195 if sys.platform.startswith('aix'): 196 action(['out/Release/node.exp'], 'include/node/') 197 198 subdir_files('deps/v8/include', 'include/node/', ignore_inspector_headers) 199 200 if 'false' == variables.get('node_shared_libuv'): 201 subdir_files('deps/uv/include', 'include/node/', action) 202 203 if 'true' == variables.get('node_use_openssl') and \ 204 'false' == variables.get('node_shared_openssl'): 205 subdir_files('deps/openssl/openssl/include/openssl', 'include/node/openssl/', action) 206 subdir_files('deps/openssl/config/archs', 'include/node/openssl/archs', action) 207 subdir_files('deps/openssl/config', 'include/node/openssl', action) 208 209 if 'false' == variables.get('node_shared_zlib'): 210 action([ 211 'deps/zlib/zconf.h', 212 'deps/zlib/zlib.h', 213 ], 'include/node/') 214 215def run(args): 216 global node_prefix, install_path, target_defaults, variables 217 218 # chdir to the project's top-level directory 219 os.chdir(abspath(os.path.dirname(__file__), '..')) 220 221 conf = load_config() 222 variables = conf['variables'] 223 target_defaults = conf['target_defaults'] 224 225 # argv[2] is a custom install prefix for packagers (think DESTDIR) 226 # argv[3] is a custom install prefix (think PREFIX) 227 # Difference is that dst_dir won't be included in shebang lines etc. 228 dst_dir = args[2] if len(args) > 2 else '' 229 230 if len(args) > 3: 231 node_prefix = args[3] 232 233 # install_path thus becomes the base target directory. 234 install_path = dst_dir + node_prefix + '/' 235 236 cmd = args[1] if len(args) > 1 else 'install' 237 238 if os.environ.get('HEADERS_ONLY'): 239 if cmd == 'install': 240 headers(install) 241 return 242 if cmd == 'uninstall': 243 headers(uninstall) 244 return 245 else: 246 if cmd == 'install': 247 files(install) 248 return 249 if cmd == 'uninstall': 250 files(uninstall) 251 return 252 253 raise RuntimeError('Bad command: %s\n' % cmd) 254 255if __name__ == '__main__': 256 run(sys.argv[:]) 257