• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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