1#!/usr/bin/env python 2 3from __future__ import print_function 4 5import ast 6import errno 7import os 8import shutil 9import sys 10import re 11 12# set at init time 13node_prefix = '/usr/local' # PREFIX variable from Makefile 14install_path = '' # base target directory (DESTDIR + PREFIX from Makefile) 15target_defaults = None 16variables = None 17 18def abspath(*args): 19 path = os.path.join(*args) 20 return os.path.abspath(path) 21 22def load_config(): 23 with open('config.gypi') as f: 24 return ast.literal_eval(f.read()) 25 26def try_unlink(path): 27 try: 28 os.unlink(path) 29 except OSError as e: 30 if e.errno != errno.ENOENT: raise 31 32def try_symlink(source_path, link_path): 33 print('symlinking %s -> %s' % (source_path, link_path)) 34 try_unlink(link_path) 35 try_mkdir_r(os.path.dirname(link_path)) 36 os.symlink(source_path, link_path) 37 38def try_mkdir_r(path): 39 try: 40 os.makedirs(path) 41 except OSError as e: 42 if e.errno != errno.EEXIST: raise 43 44def try_rmdir_r(path): 45 path = abspath(path) 46 while path.startswith(install_path): 47 try: 48 os.rmdir(path) 49 except OSError as e: 50 if e.errno == errno.ENOTEMPTY: return 51 if e.errno == errno.ENOENT: return 52 raise 53 path = abspath(path, '..') 54 55def mkpaths(path, dst): 56 if dst.endswith('/'): 57 target_path = abspath(install_path, dst, os.path.basename(path)) 58 else: 59 target_path = abspath(install_path, dst) 60 return path, target_path 61 62def try_copy(path, dst): 63 source_path, target_path = mkpaths(path, dst) 64 print('installing %s' % target_path) 65 try_mkdir_r(os.path.dirname(target_path)) 66 try_unlink(target_path) # prevent ETXTBSY errors 67 return shutil.copy2(source_path, target_path) 68 69def try_remove(path, dst): 70 source_path, target_path = mkpaths(path, dst) 71 print('removing %s' % target_path) 72 try_unlink(target_path) 73 try_rmdir_r(os.path.dirname(target_path)) 74 75def install(paths, dst): 76 for path in paths: 77 try_copy(path, dst) 78 79def uninstall(paths, dst): 80 for path in paths: 81 try_remove(path, dst) 82 83def package_files(action, name, bins): 84 target_path = 'lib/node_modules/' + name + '/' 85 86 # don't install npm if the target path is a symlink, it probably means 87 # that a dev version of npm is installed there 88 if os.path.islink(abspath(install_path, target_path)): return 89 90 # npm has a *lot* of files and it'd be a pain to maintain a fixed list here 91 # so we walk its source directory instead... 92 root = 'deps/' + name 93 for dirname, subdirs, basenames in os.walk(root, topdown=True): 94 subdirs[:] = [subdir for subdir in subdirs if subdir != 'test'] 95 paths = [os.path.join(dirname, basename) for basename in basenames] 96 action(paths, target_path + dirname[len(root) + 1:] + '/') 97 98 # create/remove symlinks 99 for bin_name, bin_target in bins.items(): 100 link_path = abspath(install_path, 'bin/' + bin_name) 101 if action == uninstall: 102 action([link_path], 'bin/' + bin_name) 103 elif action == install: 104 try_symlink('../lib/node_modules/' + name + '/' + bin_target, link_path) 105 else: 106 assert 0 # unhandled action type 107 108def npm_files(action): 109 package_files(action, 'npm', { 110 'npm': 'bin/npm-cli.js', 111 'npx': 'bin/npx-cli.js', 112 }) 113 114def corepack_files(action): 115 package_files(action, 'corepack', { 116 'corepack': 'dist/corepack.js', 117# Not the default just yet: 118# 'yarn': 'dist/yarn.js', 119# 'yarnpkg': 'dist/yarn.js', 120# 'pnpm': 'dist/pnpm.js', 121# 'pnpx': 'dist/pnpx.js', 122 }) 123 124 # On z/OS, we install node-gyp for convenience, as some vendors don't have 125 # external access and may want to build native addons. 126 if sys.platform == 'zos': 127 link_path = abspath(install_path, 'bin/node-gyp') 128 if action == uninstall: 129 action([link_path], 'bin/node-gyp') 130 elif action == install: 131 try_symlink('../lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js', link_path) 132 else: 133 assert 0 # unhandled action type 134 135def subdir_files(path, dest, action): 136 ret = {} 137 for dirpath, dirnames, filenames in os.walk(path): 138 files_in_path = [dirpath + '/' + f for f in filenames if f.endswith('.h')] 139 ret[dest + dirpath.replace(path, '')] = files_in_path 140 for subdir, files_in_path in ret.items(): 141 action(files_in_path, subdir + '/') 142 143def files(action): 144 is_windows = sys.platform == 'win32' 145 output_file = 'node' 146 output_prefix = 'out/Release/' 147 148 if is_windows: 149 output_file += '.exe' 150 action([output_prefix + output_file], 'bin/' + output_file) 151 152 if 'true' == variables.get('node_shared'): 153 if is_windows: 154 action([output_prefix + 'libnode.dll'], 'bin/libnode.dll') 155 action([output_prefix + 'libnode.lib'], 'lib/libnode.lib') 156 elif sys.platform == 'zos': 157 # GYP will output to lib.target; see _InstallableTargetInstallPath 158 # function in tools/gyp/pylib/gyp/generator/make.py 159 output_prefix += 'lib.target/' 160 161 output_lib = 'libnode.' + variables.get('shlib_suffix') 162 action([output_prefix + output_lib], 'lib/' + output_lib) 163 164 # create libnode.x that references libnode.so (C++ addons compat) 165 os.system(os.path.dirname(os.path.realpath(__file__)) + 166 '/zos/modifysidedeck.sh ' + 167 abspath(install_path, 'lib/' + output_lib) + ' ' + 168 abspath(install_path, 'lib/libnode.x') + ' libnode.so') 169 170 # install libnode.version.so 171 so_name = 'libnode.' + re.sub(r'\.x$', '.so', variables.get('shlib_suffix')) 172 action([output_prefix + so_name], variables.get('libdir') + '/' + so_name) 173 174 # create symlink of libnode.so -> libnode.version.so (C++ addons compat) 175 link_path = abspath(install_path, 'lib/libnode.so') 176 try_symlink(so_name, link_path) 177 else: 178 output_lib = 'libnode.' + variables.get('shlib_suffix') 179 action([output_prefix + output_lib], variables.get('libdir') + '/' + output_lib) 180 if 'true' == variables.get('node_use_dtrace'): 181 action(['out/Release/node.d'], 'lib/dtrace/node.d') 182 183 # behave similarly for systemtap 184 action(['src/node.stp'], 'share/systemtap/tapset/') 185 186 action(['deps/v8/tools/gdbinit'], 'share/doc/node/') 187 action(['deps/v8/tools/lldb_commands.py'], 'share/doc/node/') 188 189 if 'freebsd' in sys.platform or 'openbsd' in sys.platform: 190 action(['doc/node.1'], 'man/man1/') 191 else: 192 action(['doc/node.1'], 'share/man/man1/') 193 194 if 'true' == variables.get('node_install_npm'): 195 npm_files(action) 196 197 if 'true' == variables.get('node_install_corepack'): 198 corepack_files(action) 199 200 headers(action) 201 202def headers(action): 203 def wanted_v8_headers(files_arg, dest): 204 v8_headers = [ 205 'deps/v8/include/cppgc/common.h', 206 'deps/v8/include/libplatform/libplatform.h', 207 'deps/v8/include/libplatform/libplatform-export.h', 208 'deps/v8/include/libplatform/v8-tracing.h', 209 'deps/v8/include/v8.h', 210 'deps/v8/include/v8-array-buffer.h', 211 'deps/v8/include/v8-callbacks.h', 212 'deps/v8/include/v8-container.h', 213 'deps/v8/include/v8-context.h', 214 'deps/v8/include/v8-data.h', 215 'deps/v8/include/v8-date.h', 216 'deps/v8/include/v8-debug.h', 217 'deps/v8/include/v8-embedder-heap.h', 218 'deps/v8/include/v8-embedder-state-scope.h', 219 'deps/v8/include/v8-exception.h', 220 'deps/v8/include/v8-extension.h', 221 'deps/v8/include/v8-external.h', 222 'deps/v8/include/v8-forward.h', 223 'deps/v8/include/v8-function-callback.h', 224 'deps/v8/include/v8-function.h', 225 'deps/v8/include/v8-initialization.h', 226 'deps/v8/include/v8-internal.h', 227 'deps/v8/include/v8-isolate.h', 228 'deps/v8/include/v8-json.h', 229 'deps/v8/include/v8-local-handle.h', 230 'deps/v8/include/v8-locker.h', 231 'deps/v8/include/v8-maybe.h', 232 'deps/v8/include/v8-memory-span.h', 233 'deps/v8/include/v8-message.h', 234 'deps/v8/include/v8-microtask-queue.h', 235 'deps/v8/include/v8-microtask.h', 236 'deps/v8/include/v8-object.h', 237 'deps/v8/include/v8-persistent-handle.h', 238 'deps/v8/include/v8-platform.h', 239 'deps/v8/include/v8-primitive-object.h', 240 'deps/v8/include/v8-primitive.h', 241 'deps/v8/include/v8-profiler.h', 242 'deps/v8/include/v8-promise.h', 243 'deps/v8/include/v8-proxy.h', 244 'deps/v8/include/v8-regexp.h', 245 'deps/v8/include/v8-script.h', 246 'deps/v8/include/v8-snapshot.h', 247 'deps/v8/include/v8-statistics.h', 248 'deps/v8/include/v8-template.h', 249 'deps/v8/include/v8-traced-handle.h', 250 'deps/v8/include/v8-typed-array.h', 251 'deps/v8/include/v8-unwinder.h', 252 'deps/v8/include/v8-value-serializer.h', 253 'deps/v8/include/v8-value.h', 254 'deps/v8/include/v8-version.h', 255 'deps/v8/include/v8-wasm.h', 256 'deps/v8/include/v8-weak-callback-info.h', 257 'deps/v8/include/v8config.h', 258 ] 259 files_arg = [name for name in files_arg if name in v8_headers] 260 action(files_arg, dest) 261 262 def wanted_zoslib_headers(files_arg, dest): 263 import glob 264 zoslib_headers = glob.glob(zoslibinc + '/*.h') 265 files_arg = [name for name in files_arg if name in zoslib_headers] 266 action(files_arg, dest) 267 268 action([ 269 'common.gypi', 270 'config.gypi', 271 'src/node.h', 272 'src/node_api.h', 273 'src/js_native_api.h', 274 'src/js_native_api_types.h', 275 'src/node_api_types.h', 276 'src/node_buffer.h', 277 'src/node_object_wrap.h', 278 'src/node_version.h', 279 ], 'include/node/') 280 281 # Add the expfile that is created on AIX 282 if sys.platform.startswith('aix') or sys.platform == "os400": 283 action(['out/Release/node.exp'], 'include/node/') 284 285 subdir_files('deps/v8/include', 'include/node/', wanted_v8_headers) 286 287 if 'false' == variables.get('node_shared_libuv'): 288 subdir_files('deps/uv/include', 'include/node/', action) 289 290 if 'true' == variables.get('node_use_openssl') and \ 291 'false' == variables.get('node_shared_openssl'): 292 subdir_files('deps/openssl/openssl/include/openssl', 'include/node/openssl/', action) 293 subdir_files('deps/openssl/config/archs', 'include/node/openssl/archs', action) 294 subdir_files('deps/openssl/config', 'include/node/openssl', action) 295 296 if 'false' == variables.get('node_shared_zlib'): 297 action([ 298 'deps/zlib/zconf.h', 299 'deps/zlib/zlib.h', 300 ], 'include/node/') 301 302 if sys.platform == 'zos': 303 zoslibinc = os.environ.get('ZOSLIB_INCLUDES') 304 if not zoslibinc: 305 raise RuntimeError('Environment variable ZOSLIB_INCLUDES is not set\n') 306 if not os.path.isfile(zoslibinc + '/zos-base.h'): 307 raise RuntimeError('ZOSLIB_INCLUDES is not set to a valid location\n') 308 subdir_files(zoslibinc, 'include/node/zoslib/', wanted_zoslib_headers) 309 310def run(args): 311 global node_prefix, install_path, target_defaults, variables 312 313 # chdir to the project's top-level directory 314 os.chdir(abspath(os.path.dirname(__file__), '..')) 315 316 conf = load_config() 317 variables = conf['variables'] 318 target_defaults = conf['target_defaults'] 319 320 # argv[2] is a custom install prefix for packagers (think DESTDIR) 321 # argv[3] is a custom install prefix (think PREFIX) 322 # Difference is that dst_dir won't be included in shebang lines etc. 323 dst_dir = args[2] if len(args) > 2 else '' 324 325 if len(args) > 3: 326 node_prefix = args[3] 327 328 # install_path thus becomes the base target directory. 329 install_path = dst_dir + node_prefix + '/' 330 331 cmd = args[1] if len(args) > 1 else 'install' 332 333 if os.environ.get('HEADERS_ONLY'): 334 if cmd == 'install': 335 headers(install) 336 return 337 if cmd == 'uninstall': 338 headers(uninstall) 339 return 340 else: 341 if cmd == 'install': 342 files(install) 343 return 344 if cmd == 'uninstall': 345 files(uninstall) 346 return 347 348 raise RuntimeError('Bad command: %s\n' % cmd) 349 350if __name__ == '__main__': 351 run(sys.argv[:]) 352