• 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
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