Skip to content

Commit 353afc9

Browse files
committed
build,tools: make addons tests work with GN
1 parent dfbca33 commit 353afc9

11 files changed

+489
-328
lines changed

Makefile

+6-14
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ config.gypi: configure configure.py src/node_version.h
187187

188188
.PHONY: install
189189
install: all ## Installs node into $PREFIX (default=/usr/local).
190-
$(PYTHON) tools/install.py $@ '$(DESTDIR)' '$(PREFIX)'
190+
$(PYTHON) tools/install.py $@ --dest-dir '$(DESTDIR)' --prefix '$(PREFIX)'
191191

192192
.PHONY: uninstall
193193
uninstall: ## Uninstalls node from $PREFIX (default=/usr/local).
194-
$(PYTHON) tools/install.py $@ '$(DESTDIR)' '$(PREFIX)'
194+
$(PYTHON) tools/install.py $@ --dest-dir '$(DESTDIR)' --prefix '$(PREFIX)'
195195

196196
.PHONY: clean
197197
.NOTPARALLEL: clean
@@ -388,15 +388,12 @@ ADDONS_BINDING_SOURCES := \
388388
$(filter-out test/addons/??_*/*.h, $(wildcard test/addons/*/*.h))
389389

390390
ADDONS_PREREQS := config.gypi \
391-
deps/npm/node_modules/node-gyp/package.json tools/build-addons.mjs \
391+
deps/npm/node_modules/node-gyp/package.json tools/build_addons.py \
392392
deps/uv/include/*.h deps/v8/include/*.h \
393393
src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h
394394

395395
define run_build_addons
396-
env npm_config_loglevel=$(LOGLEVEL) npm_config_nodedir="$$PWD" \
397-
npm_config_python="$(PYTHON)" $(NODE) "$$PWD/tools/build-addons.mjs" \
398-
"$$PWD/deps/npm/node_modules/node-gyp/bin/node-gyp.js" \
399-
$1
396+
env $(PYTHON) "$$PWD/tools/build_addons.py" --loglevel=$(LOGLEVEL) $1
400397
touch $2
401398
endef
402399

@@ -1216,15 +1213,10 @@ $(TARBALL)-headers: release-only
12161213
--tag=$(TAG) \
12171214
--release-urlbase=$(RELEASE_URLBASE) \
12181215
$(CONFIG_FLAGS) $(BUILD_RELEASE_FLAGS)
1219-
HEADERS_ONLY=1 $(PYTHON) tools/install.py install '$(TARNAME)' '/'
1220-
find $(TARNAME)/ -type l | xargs $(RM)
1221-
tar -cf $(TARNAME)-headers.tar $(TARNAME)
1222-
$(RM) -r $(TARNAME)
1223-
gzip -c -f -9 $(TARNAME)-headers.tar > $(TARNAME)-headers.tar.gz
1216+
$(PYTHON) tools/create_headers_tarball.py --target '$(TARNAME)-headers.tar.gz' --prefix '/'
12241217
ifeq ($(XZ), 1)
1225-
xz -c -f -$(XZ_COMPRESSION) $(TARNAME)-headers.tar > $(TARNAME)-headers.tar.xz
1218+
$(PYTHON) tools/create_headers_tarball.py --target '$(TARNAME)-headers.tar.xz' --compression-level $(XZ_COMPRESSION) --prefix '/'
12261219
endif
1227-
$(RM) $(TARNAME)-headers.tar
12281220

12291221
.PHONY: tar-headers
12301222
tar-headers: $(TARBALL)-headers ## Build the node header tarball.

deps/openssl/unofficial.gni

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ template("openssl_gn_build") {
9393
configs += [ ":openssl_internal_config" ]
9494
public_configs = [ ":openssl_external_config" ]
9595

96+
if (is_posix) {
97+
configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
98+
configs += [ "//build/config/gcc:symbol_visibility_default" ]
99+
}
100+
96101
config_path_name = ""
97102
if (is_win) {
98103
if (target_cpu == "x86") {

deps/uv/unofficial.gni

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ template("uv_gn_build") {
6969
configs += [ ":uv_internal_config" ]
7070
public_configs = [ ":uv_external_config" ]
7171

72+
if (is_posix) {
73+
configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
74+
configs += [ "//build/config/gcc:symbol_visibility_default" ]
75+
}
76+
7277
if (is_win) {
7378
libs = [
7479
"advapi32.lib",

doc/contributing/collaborator-guide.md

-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ There are some other files that touch the build chain. Changes in the following
232232
files also qualify as affecting the `node` binary:
233233

234234
* `tools/*.py`
235-
* `tools/build-addons.mjs`
236235
* `*.gyp`
237236
* `*.gypi`
238237
* `configure`

tools/build-addons.mjs

-63
This file was deleted.

tools/build_addons.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import shutil
6+
import subprocess
7+
import sys
8+
import tempfile
9+
from concurrent.futures import ThreadPoolExecutor
10+
11+
ROOT_DIR = os.path.abspath(os.path.join(__file__, '..', '..'))
12+
13+
def install_and_rebuild(args, install_args):
14+
out_dir = os.path.abspath(args.out_dir)
15+
node_bin = os.path.join(out_dir, 'node')
16+
if args.is_win:
17+
node_bin += '.exe'
18+
19+
if os.path.isabs(args.node_gyp):
20+
node_gyp = args.node_gyp
21+
else:
22+
node_gyp = os.path.join(ROOT_DIR, args.node_gyp)
23+
24+
# Create a temporary directory for node headers.
25+
with tempfile.TemporaryDirectory() as headers_dir:
26+
# Run install.py to install headers.
27+
print('Generating headers')
28+
subprocess.check_call([
29+
sys.executable,
30+
os.path.join(ROOT_DIR, 'tools/install.py'),
31+
'install',
32+
'--silent',
33+
'--headers-only',
34+
'--prefix', '/',
35+
'--dest-dir', headers_dir,
36+
] + install_args)
37+
38+
# Copy node.lib.
39+
if args.is_win:
40+
node_lib_dir = os.path.join(headers_dir, 'Release')
41+
os.makedirs(node_lib_dir)
42+
shutil.copy2(os.path.join(args.out_dir, 'node.lib'),
43+
os.path.join(node_lib_dir, 'node.lib'))
44+
45+
def rebuild_addon(test_dir):
46+
print('Building addon in', test_dir)
47+
try:
48+
process = subprocess.Popen([
49+
node_bin,
50+
node_gyp,
51+
'rebuild',
52+
'--directory', test_dir,
53+
'--nodedir', headers_dir,
54+
'--python', sys.executable,
55+
'--loglevel', args.loglevel,
56+
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
57+
58+
# We buffer the output and print it out once the process is done in order
59+
# to avoid interleaved output from multiple builds running at once.
60+
return_code = process.wait()
61+
stdout, stderr = process.communicate()
62+
if return_code != 0:
63+
print(f'Failed to build addon in {test_dir}:')
64+
if stdout:
65+
print(stdout.decode())
66+
if stderr:
67+
print(stderr.decode())
68+
69+
except Exception as e:
70+
print(f'Unexpected error when building addon in {test_dir}. Error: {e}')
71+
72+
test_dirs = []
73+
skip_tests = args.skip_tests.split(',')
74+
only_tests = args.only_tests.split(',') if args.only_tests else None
75+
for child_dir in os.listdir(args.target):
76+
full_path = os.path.join(args.target, child_dir)
77+
if not os.path.isdir(full_path):
78+
continue
79+
if 'binding.gyp' not in os.listdir(full_path):
80+
continue
81+
if child_dir in skip_tests:
82+
continue
83+
if only_tests and child_dir not in only_tests:
84+
continue
85+
test_dirs.append(full_path)
86+
87+
with ThreadPoolExecutor() as executor:
88+
executor.map(rebuild_addon, test_dirs)
89+
90+
def main():
91+
if sys.platform == 'cygwin':
92+
raise RuntimeError('This script does not support running with cygwin python.')
93+
94+
parser = argparse.ArgumentParser(
95+
description='Install headers and rebuild child directories')
96+
parser.add_argument('target', help='target directory to build addons')
97+
parser.add_argument('--out-dir', help='path to the output directory',
98+
default='out/Release')
99+
parser.add_argument('--loglevel', help='loglevel of node-gyp',
100+
default='silent')
101+
parser.add_argument('--skip-tests', help='skip building tests',
102+
default='')
103+
parser.add_argument('--only-tests', help='only build tests in the list',
104+
default='')
105+
parser.add_argument('--node-gyp', help='path to node-gyp script',
106+
default='deps/npm/node_modules/node-gyp/bin/node-gyp.js')
107+
parser.add_argument('--is-win', help='build for Windows target',
108+
action='store_true', default=(sys.platform == 'win32'))
109+
args, unknown_args = parser.parse_known_args()
110+
111+
install_and_rebuild(args, unknown_args)
112+
113+
if __name__ == '__main__':
114+
main()

tools/create_headers_tarball.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import lzma
5+
import os
6+
import shutil
7+
import sys
8+
import subprocess
9+
import tarfile
10+
import tempfile
11+
12+
def strip_tarball_suffix(filename):
13+
suffixs = ['-headers.tar.gz', '-headers.tar.xz']
14+
for suffix in suffixs:
15+
if filename.endswith(suffix):
16+
return filename[:-len(suffix)]
17+
raise ValueError('The tarball name must ends with -headers.tar.gz/xz.')
18+
19+
def run_install_py(flags, temp_dir):
20+
install_py = os.path.join(os.path.dirname(__file__), 'install.py')
21+
subprocess.check_call([
22+
sys.executable,
23+
install_py,
24+
'install',
25+
'--silent',
26+
'--headers-only',
27+
'--dest-dir', temp_dir,
28+
'--prefix', '/',
29+
] + flags)
30+
31+
def create_tarball(temp_dir, tarball, compression_level):
32+
if tarball.endswith('.gz'):
33+
tar = tarfile.open(tarball, 'w:gz', compresslevel=int(compression_level))
34+
elif tarball.endswith('.xz'):
35+
if compression_level.endswith('e'):
36+
preset = int(compression_level[:-1]) | lzma.PRESET_EXTREME
37+
else:
38+
preset = int(compression_level)
39+
tar = tarfile.open(tarball, 'w:xz', preset=preset)
40+
else:
41+
raise ValueError('Invalid tarball extension. Only .tar.gz and .tar.xz are supported.')
42+
with tar:
43+
tar.add(temp_dir, arcname=os.path.basename(temp_dir))
44+
45+
def main():
46+
parser = argparse.ArgumentParser(
47+
description='Install headers and create tarball')
48+
parser.add_argument('--target',
49+
required=True,
50+
help='Target path of the generated tarball')
51+
parser.add_argument('--compression-level',
52+
default='9',
53+
help='Compression level for gzip or xz (default: 9)')
54+
args, unknown_args = parser.parse_known_args()
55+
56+
temp_dir = tempfile.mkdtemp()
57+
try:
58+
workspace = os.path.join(temp_dir, strip_tarball_suffix(args.target))
59+
run_install_py(unknown_args, workspace)
60+
create_tarball(workspace, args.target, args.compression_level)
61+
finally:
62+
shutil.rmtree(temp_dir)
63+
64+
if __name__ == '__main__':
65+
main()

tools/generate_config_gypi.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ def translate_config(config):
3030
},
3131
'variables': {
3232
'asan': bool_string_to_number(config['is_asan']),
33+
'enable_lto': config['use_thin_lto'],
3334
'llvm_version': 13,
3435
'napi_build_version': config['napi_build_version'],
3536
'node_builtin_shareable_builtins':
3637
eval(config['node_builtin_shareable_builtins']),
3738
'node_module_version': int(config['node_module_version']),
38-
'node_shared': bool_string_to_number(config['is_component_build']),
3939
'node_use_openssl': config['node_use_openssl'],
4040
'node_use_node_code_cache': config['node_use_node_code_cache'],
4141
'node_use_node_snapshot': config['node_use_node_snapshot'],
@@ -48,6 +48,17 @@ def translate_config(config):
4848
'v8_enable_inspector': # this is actually a node misnomer
4949
bool_string_to_number(config['node_enable_inspector']),
5050
'shlib_suffix': 'dylib' if sys.platform == 'darwin' else 'so',
51+
# TODO(zcbenz): Shared components are not supported in GN config yet.
52+
'node_shared': 'false',
53+
'node_shared_brotli': 'false',
54+
'node_shared_cares': 'false',
55+
'node_shared_http_parser': 'false',
56+
'node_shared_libuv': 'false',
57+
'node_shared_nghttp2': 'false',
58+
'node_shared_nghttp3': 'false',
59+
'node_shared_ngtcp2': 'false',
60+
'node_shared_openssl': 'false',
61+
'node_shared_zlib': 'false',
5162
}
5263
}
5364

0 commit comments

Comments
 (0)