node/tools/build_addons.py

152 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
from concurrent.futures import ThreadPoolExecutor
ROOT_DIR = os.path.abspath(os.path.join(__file__, '..', '..'))
# Run install.py to install headers.
def generate_headers(headers_dir, install_args):
print('Generating headers')
subprocess.check_call([
sys.executable,
os.path.join(ROOT_DIR, 'tools/install.py'),
'install',
'--silent',
'--headers-only',
'--prefix', '/',
'--dest-dir', headers_dir,
] + install_args)
# Rebuild addons in parallel.
def rebuild_addons(args):
headers_dir = os.path.abspath(args.headers_dir)
out_dir = os.path.abspath(args.out_dir)
node_bin = os.path.join(out_dir, 'node')
if args.is_win:
node_bin += '.exe'
if os.path.isabs(args.node_gyp):
node_gyp = args.node_gyp
else:
node_gyp = os.path.join(ROOT_DIR, args.node_gyp)
# Copy node.lib.
if args.is_win:
node_lib_dir = os.path.join(headers_dir, args.config)
os.makedirs(node_lib_dir)
shutil.copy2(os.path.join(args.out_dir, 'node.lib'),
os.path.join(node_lib_dir, 'node.lib'))
def node_gyp_rebuild(test_dir):
print('Building addon in', test_dir)
try:
process = subprocess.Popen([
node_bin,
node_gyp,
'rebuild',
'--directory', test_dir,
'--nodedir', headers_dir,
'--python', sys.executable,
'--loglevel', args.loglevel,
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# We buffer the output and print it out once the process is done in order
# to avoid interleaved output from multiple builds running at once.
return_code = process.wait()
stdout, stderr = process.communicate()
if return_code != 0:
print(f'Failed to build addon in {test_dir}:')
if stdout:
print(stdout.decode())
if stderr:
print(stderr.decode())
return return_code
except Exception as e:
print(f'Unexpected error when building addon in {test_dir}. Error: {e}')
test_dirs = []
skip_tests = args.skip_tests.split(',')
only_tests = args.only_tests.split(',') if args.only_tests else None
for child_dir in os.listdir(args.target):
full_path = os.path.join(args.target, child_dir)
if not os.path.isdir(full_path):
continue
if 'binding.gyp' not in os.listdir(full_path):
continue
if child_dir in skip_tests:
continue
if only_tests and child_dir not in only_tests:
continue
test_dirs.append(full_path)
with ThreadPoolExecutor() as executor:
codes = executor.map(node_gyp_rebuild, test_dirs)
return 0 if all(code == 0 for code in codes) else 1
def get_default_out_dir(args):
default_out_dir = os.path.join('out', args.config)
if not args.is_win:
# POSIX platforms only have one out dir.
return default_out_dir
# On Windows depending on the args of GYP and configure script, the out dir
# could be 'out/Release', 'out/Debug' or just 'Release' or 'Debug'.
if os.path.exists(default_out_dir):
return default_out_dir
if os.path.exists(args.config):
return args.config
raise RuntimeError('Cannot find out dir, did you run build?')
def main():
if sys.platform == 'cygwin':
raise RuntimeError('This script does not support running with cygwin python.')
parser = argparse.ArgumentParser(
description='Install headers and rebuild child directories')
parser.add_argument('target', help='target directory to build addons')
parser.add_argument('--headers-dir',
help='path to node headers directory, if not specified '
'new headers will be generated for building',
default=None)
parser.add_argument('--out-dir', help='path to the output directory',
default=None)
parser.add_argument('--loglevel', help='loglevel of node-gyp',
default='silent')
parser.add_argument('--skip-tests', help='skip building tests',
default='')
parser.add_argument('--only-tests', help='only build tests in the list',
default='')
parser.add_argument('--node-gyp', help='path to node-gyp script',
default='deps/npm/node_modules/node-gyp/bin/node-gyp.js')
parser.add_argument('--is-win', help='build for Windows target',
action='store_true', default=(sys.platform == 'win32'))
parser.add_argument('--config', help='build config (Release or Debug)',
default='Release')
args, unknown_args = parser.parse_known_args()
if not args.out_dir:
args.out_dir = get_default_out_dir(args)
exit_code = 1
if args.headers_dir:
exit_code = rebuild_addons(args)
else:
# When --headers-dir is not specified, generate headers into a temp dir and
# build with the new headers.
try:
args.headers_dir = tempfile.mkdtemp()
generate_headers(args.headers_dir, unknown_args)
exit_code = rebuild_addons(args)
finally:
shutil.rmtree(args.headers_dir)
return exit_code
if __name__ == '__main__':
sys.exit(main())