Skip to content

Commit 5911731

Browse files
zcbenzmarco-ippolito
authored andcommitted
build,tools: make addons tests work with GN
PR-URL: #50737 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
1 parent 9be6b7c commit 5911731

9 files changed

+469
-331
lines changed

Makefile

+32-16
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
@@ -379,6 +379,28 @@ test/addons/.docbuildstamp: $(DOCBUILDSTAMP_PREREQS) tools/doc/node_modules
379379
[ $$? -eq 0 ] && touch $@; \
380380
fi
381381

382+
# All files that will be included in headers tarball should be listed as deps
383+
# for generating headers. The list is manually synchronized with install.py.
384+
ADDONS_HEADERS_PREREQS := tools/install.py \
385+
config.gypi common.gypi \
386+
$(wildcard deps/openssl/config/*.h) \
387+
$(wildcard deps/openssl/openssl/include/openssl/*.h) \
388+
$(wildcard deps/uv/include/*.h) \
389+
$(wildcard deps/uv/include/*/*.h) \
390+
$(wildcard deps/v8/include/*.h) \
391+
$(wildcard deps/v8/include/*/*.h) \
392+
deps/zlib/zconf.h deps/zlib/zlib.h \
393+
src/node.h src/node_api.h src/js_native_api.h src/js_native_api_types.h \
394+
src/node_api_types.h src/node_buffer.h src/node_object_wrap.h \
395+
src/node_version.h
396+
397+
ADDONS_HEADERS_DIR = out/$(BUILDTYPE)/addons_headers
398+
399+
# Generate node headers which will be used for building addons.
400+
test/addons/.headersbuildstamp: $(ADDONS_HEADERS_PREREQS)
401+
$(PYTHON) tools/install.py install --headers-only --dest-dir '$(ADDONS_HEADERS_DIR)' --prefix '/'
402+
@touch $@
403+
382404
ADDONS_BINDING_GYPS := \
383405
$(filter-out test/addons/??_*/binding.gyp, \
384406
$(wildcard test/addons/*/binding.gyp))
@@ -387,16 +409,11 @@ ADDONS_BINDING_SOURCES := \
387409
$(filter-out test/addons/??_*/*.cc, $(wildcard test/addons/*/*.cc)) \
388410
$(filter-out test/addons/??_*/*.h, $(wildcard test/addons/*/*.h))
389411

390-
ADDONS_PREREQS := config.gypi \
391-
deps/npm/node_modules/node-gyp/package.json tools/build-addons.mjs \
392-
deps/uv/include/*.h deps/v8/include/*.h \
393-
src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h
412+
ADDONS_PREREQS := test/addons/.headersbuildstamp \
413+
deps/npm/node_modules/node-gyp/package.json tools/build_addons.py
394414

395415
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
416+
env $(PYTHON) "$$PWD/tools/build_addons.py" --loglevel=$(LOGLEVEL) --headers-dir "$(ADDONS_HEADERS_DIR)" $1
400417
touch $2
401418
endef
402419

@@ -429,8 +446,7 @@ JS_NATIVE_API_BINDING_SOURCES := \
429446
# Implicitly depends on $(NODE_EXE), see the build-js-native-api-tests rule for rationale.
430447
test/js-native-api/.buildstamp: $(ADDONS_PREREQS) \
431448
$(JS_NATIVE_API_BINDING_GYPS) $(JS_NATIVE_API_BINDING_SOURCES) \
432-
src/node_api.h src/node_api_types.h src/js_native_api.h \
433-
src/js_native_api_types.h src/js_native_api_v8.h src/js_native_api_v8_internals.h
449+
src/js_native_api_v8.h src/js_native_api_v8_internals.h
434450
@$(call run_build_addons,"$$PWD/test/js-native-api",$@)
435451

436452
.PHONY: build-js-native-api-tests
@@ -454,8 +470,7 @@ NODE_API_BINDING_SOURCES := \
454470
# Implicitly depends on $(NODE_EXE), see the build-node-api-tests rule for rationale.
455471
test/node-api/.buildstamp: $(ADDONS_PREREQS) \
456472
$(NODE_API_BINDING_GYPS) $(NODE_API_BINDING_SOURCES) \
457-
src/node_api.h src/node_api_types.h src/js_native_api.h \
458-
src/js_native_api_types.h src/js_native_api_v8.h src/js_native_api_v8_internals.h
473+
src/js_native_api_v8.h src/js_native_api_v8_internals.h
459474
@$(call run_build_addons,"$$PWD/test/node-api",$@)
460475

461476
.PHONY: build-node-api-tests
@@ -660,9 +675,10 @@ test-addons: test-build test-js-native-api test-node-api
660675
.PHONY: test-addons-clean
661676
.NOTPARALLEL: test-addons-clean
662677
test-addons-clean:
678+
$(RM) -r "$(ADDONS_HEADERS_DIR)"
663679
$(RM) -r test/addons/??_*/
664680
$(RM) -r test/addons/*/build
665-
$(RM) test/addons/.buildstamp test/addons/.docbuildstamp
681+
$(RM) test/addons/.buildstamp test/addons/.docbuildstamp test/addons/.headersbuildstamp
666682
$(MAKE) test-js-native-api-clean
667683
$(MAKE) test-node-api-clean
668684

@@ -1216,7 +1232,7 @@ $(TARBALL)-headers: release-only
12161232
--tag=$(TAG) \
12171233
--release-urlbase=$(RELEASE_URLBASE) \
12181234
$(CONFIG_FLAGS) $(BUILD_RELEASE_FLAGS)
1219-
HEADERS_ONLY=1 $(PYTHON) tools/install.py install '$(TARNAME)' '/'
1235+
$(PYTHON) tools/install.py install --headers-only --dest-dir '$(TARNAME)' --prefix '/'
12201236
find $(TARNAME)/ -type l | xargs $(RM)
12211237
tar -cf $(TARNAME)-headers.tar $(TARNAME)
12221238
$(RM) -r $(TARNAME)

deps/openssl/unofficial.gni

+5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ template("openssl_gn_build") {
8888
configs += [ ":openssl_internal_config" ]
8989
public_configs = [ ":openssl_external_config" ]
9090

91+
if (is_posix) {
92+
configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
93+
configs += [ "//build/config/gcc:symbol_visibility_default" ]
94+
}
95+
9196
config_path_name = ""
9297
if (is_win) {
9398
if (target_cpu == "x86") {

deps/uv/unofficial.gni

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ template("uv_gn_build") {
6464
configs += [ ":uv_internal_config" ]
6565
public_configs = [ ":uv_external_config" ]
6666

67+
if (is_posix) {
68+
configs -= [ "//build/config/gcc:symbol_visibility_hidden" ]
69+
configs += [ "//build/config/gcc:symbol_visibility_default" ]
70+
}
71+
6772
if (is_win) {
6873
libs = [
6974
"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

-65
This file was deleted.

tools/build_addons.py

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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+
# Run install.py to install headers.
14+
def generate_headers(headers_dir, install_args):
15+
print('Generating headers')
16+
subprocess.check_call([
17+
sys.executable,
18+
os.path.join(ROOT_DIR, 'tools/install.py'),
19+
'install',
20+
'--silent',
21+
'--headers-only',
22+
'--prefix', '/',
23+
'--dest-dir', headers_dir,
24+
] + install_args)
25+
26+
# Rebuild addons in parallel.
27+
def rebuild_addons(args):
28+
headers_dir = os.path.abspath(args.headers_dir)
29+
out_dir = os.path.abspath(args.out_dir)
30+
node_bin = os.path.join(out_dir, 'node')
31+
if args.is_win:
32+
node_bin += '.exe'
33+
34+
if os.path.isabs(args.node_gyp):
35+
node_gyp = args.node_gyp
36+
else:
37+
node_gyp = os.path.join(ROOT_DIR, args.node_gyp)
38+
39+
# Copy node.lib.
40+
if args.is_win:
41+
node_lib_dir = os.path.join(headers_dir, 'Release')
42+
os.makedirs(node_lib_dir)
43+
shutil.copy2(os.path.join(args.out_dir, 'node.lib'),
44+
os.path.join(node_lib_dir, 'node.lib'))
45+
46+
def node_gyp_rebuild(test_dir):
47+
print('Building addon in', test_dir)
48+
try:
49+
process = subprocess.Popen([
50+
node_bin,
51+
node_gyp,
52+
'rebuild',
53+
'--directory', test_dir,
54+
'--nodedir', headers_dir,
55+
'--python', sys.executable,
56+
'--loglevel', args.loglevel,
57+
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
58+
59+
# We buffer the output and print it out once the process is done in order
60+
# to avoid interleaved output from multiple builds running at once.
61+
return_code = process.wait()
62+
stdout, stderr = process.communicate()
63+
if return_code != 0:
64+
print(f'Failed to build addon in {test_dir}:')
65+
if stdout:
66+
print(stdout.decode())
67+
if stderr:
68+
print(stderr.decode())
69+
70+
except Exception as e:
71+
print(f'Unexpected error when building addon in {test_dir}. Error: {e}')
72+
73+
test_dirs = []
74+
skip_tests = args.skip_tests.split(',')
75+
only_tests = args.only_tests.split(',') if args.only_tests else None
76+
for child_dir in os.listdir(args.target):
77+
full_path = os.path.join(args.target, child_dir)
78+
if not os.path.isdir(full_path):
79+
continue
80+
if 'binding.gyp' not in os.listdir(full_path):
81+
continue
82+
if child_dir in skip_tests:
83+
continue
84+
if only_tests and child_dir not in only_tests:
85+
continue
86+
test_dirs.append(full_path)
87+
88+
with ThreadPoolExecutor() as executor:
89+
executor.map(node_gyp_rebuild, test_dirs)
90+
91+
def get_default_out_dir(args):
92+
default_out_dir = os.path.join('out', 'Release')
93+
if not args.is_win:
94+
# POSIX platforms only have one out dir.
95+
return default_out_dir
96+
# On Windows depending on the args of GYP and configure script, the out dir
97+
# could be 'out/Release' or just 'Release'.
98+
if os.path.exists(default_out_dir):
99+
return default_out_dir
100+
if os.path.exists('Release'):
101+
return 'Release'
102+
raise RuntimeError('Can not find out dir, did you run configure?')
103+
104+
def main():
105+
if sys.platform == 'cygwin':
106+
raise RuntimeError('This script does not support running with cygwin python.')
107+
108+
parser = argparse.ArgumentParser(
109+
description='Install headers and rebuild child directories')
110+
parser.add_argument('target', help='target directory to build addons')
111+
parser.add_argument('--headers-dir',
112+
help='path to node headers directory, if not specified '
113+
'new headers will be generated for building',
114+
default=None)
115+
parser.add_argument('--out-dir', help='path to the output directory',
116+
default=None)
117+
parser.add_argument('--loglevel', help='loglevel of node-gyp',
118+
default='silent')
119+
parser.add_argument('--skip-tests', help='skip building tests',
120+
default='')
121+
parser.add_argument('--only-tests', help='only build tests in the list',
122+
default='')
123+
parser.add_argument('--node-gyp', help='path to node-gyp script',
124+
default='deps/npm/node_modules/node-gyp/bin/node-gyp.js')
125+
parser.add_argument('--is-win', help='build for Windows target',
126+
action='store_true', default=(sys.platform == 'win32'))
127+
args, unknown_args = parser.parse_known_args()
128+
129+
if not args.out_dir:
130+
args.out_dir = get_default_out_dir(args)
131+
132+
if args.headers_dir:
133+
rebuild_addons(args)
134+
else:
135+
# When --headers-dir is not specified, generate headers into a temp dir and
136+
# build with the new headers.
137+
try:
138+
args.headers_dir = tempfile.mkdtemp()
139+
generate_headers(args.headers_dir, unknown_args)
140+
rebuild_addons(args)
141+
finally:
142+
shutil.rmtree(args.headers_dir)
143+
144+
if __name__ == '__main__':
145+
main()

0 commit comments

Comments
 (0)