#!/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())