Skip to content

Commit c5f832a

Browse files
nodejs-github-bottargos
authored andcommitted
tools: update gyp-next to 0.17.0
PR-URL: #52835 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Christian Clauss <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent e895f7c commit c5f832a

15 files changed

+328
-73
lines changed

tools/gyp/CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## [0.17.0](https://github.com/nodejs/gyp-next/compare/v0.16.2...v0.17.0) (2024-04-29)
4+
5+
6+
### Features
7+
8+
* generate compile_commands.json with ninja ([#228](https://github.com/nodejs/gyp-next/issues/228)) ([7b20b46](https://github.com/nodejs/gyp-next/commit/7b20b4673d8cf46ff61898eb19569007d55c854a))
9+
10+
11+
### Bug Fixes
12+
13+
* failed to detect flavor if compiler path include white spaces ([#240](https://github.com/nodejs/gyp-next/issues/240)) ([f3b9753](https://github.com/nodejs/gyp-next/commit/f3b9753e7526377020e7d40e66b624db771cf84a))
14+
* support cross compiling for wasm with make generator ([#222](https://github.com/nodejs/gyp-next/issues/222)) ([de0e1c9](https://github.com/nodejs/gyp-next/commit/de0e1c9a5791d1bf4bc3103f878ab74814864ab4))
15+
* support empty dictionary keys in input ([#245](https://github.com/nodejs/gyp-next/issues/245)) ([178459f](https://github.com/nodejs/gyp-next/commit/178459ff343a2771d5f30f04467d2f032d6b3565))
16+
* update Ruff to 0.3.1 ([876ccaf](https://github.com/nodejs/gyp-next/commit/876ccaf5629e1b95e13aaa2b0eb6cbd08fa80593))
17+
318
## [0.16.2](https://github.com/nodejs/gyp-next/compare/v0.16.1...v0.16.2) (2024-03-07)
419

520

tools/gyp/data/ninja/build.ninja

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
rule cc
2+
command = cc $in $out
3+
4+
build my.out: cc my.in

tools/gyp/pylib/gyp/common.py

+60-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import tempfile
1010
import sys
1111
import subprocess
12+
import shlex
1213

1314
from collections.abc import MutableSet
1415

@@ -422,17 +423,61 @@ def EnsureDirExists(path):
422423
except OSError:
423424
pass
424425

426+
def GetCrossCompilerPredefines(): # -> dict
427+
cmd = []
428+
429+
# shlex.split() will eat '\' in posix mode, but
430+
# setting posix=False will preserve extra '"' cause CreateProcess fail on Windows
431+
# this makes '\' in %CC_target% and %CFLAGS% work
432+
def replace_sep(s):
433+
return s.replace(os.sep, "/") if os.sep != "/" else s
434+
435+
if CC := os.environ.get("CC_target") or os.environ.get("CC"):
436+
cmd += shlex.split(replace_sep(CC))
437+
if CFLAGS := os.environ.get("CFLAGS"):
438+
cmd += shlex.split(replace_sep(CFLAGS))
439+
elif CXX := os.environ.get("CXX_target") or os.environ.get("CXX"):
440+
cmd += shlex.split(replace_sep(CXX))
441+
if CXXFLAGS := os.environ.get("CXXFLAGS"):
442+
cmd += shlex.split(replace_sep(CXXFLAGS))
443+
else:
444+
return {}
425445

426-
def GetFlavor(params):
446+
if sys.platform == "win32":
447+
fd, input = tempfile.mkstemp(suffix=".c")
448+
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
449+
try:
450+
os.close(fd)
451+
stdout = subprocess.run(
452+
real_cmd, shell=True,
453+
capture_output=True, check=True
454+
).stdout
455+
finally:
456+
os.unlink(input)
457+
else:
458+
input = "/dev/null"
459+
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
460+
stdout = subprocess.run(
461+
real_cmd, shell=False,
462+
capture_output=True, check=True
463+
).stdout
464+
465+
defines = {}
466+
lines = stdout.decode("utf-8").replace("\r\n", "\n").split("\n")
467+
for line in lines:
468+
if (line or "").startswith("#define "):
469+
_, key, *value = line.split(" ")
470+
defines[key] = " ".join(value)
471+
return defines
472+
473+
def GetFlavorByPlatform():
427474
"""Returns |params.flavor| if it's set, the system's default flavor else."""
428475
flavors = {
429476
"cygwin": "win",
430477
"win32": "win",
431478
"darwin": "mac",
432479
}
433480

434-
if "flavor" in params:
435-
return params["flavor"]
436481
if sys.platform in flavors:
437482
return flavors[sys.platform]
438483
if sys.platform.startswith("sunos"):
@@ -452,6 +497,18 @@ def GetFlavor(params):
452497

453498
return "linux"
454499

500+
def GetFlavor(params):
501+
if "flavor" in params:
502+
return params["flavor"]
503+
504+
defines = GetCrossCompilerPredefines()
505+
if "__EMSCRIPTEN__" in defines:
506+
return "emscripten"
507+
if "__wasm__" in defines:
508+
return "wasi" if "__wasi__" in defines else "wasm"
509+
510+
return GetFlavorByPlatform()
511+
455512

456513
def CopyTool(flavor, out_path, generator_flags={}):
457514
"""Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it

tools/gyp/pylib/gyp/common_test.py

+98-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import gyp.common
1010
import unittest
1111
import sys
12-
12+
import os
13+
from unittest.mock import patch, MagicMock
1314

1415
class TestTopologicallySorted(unittest.TestCase):
1516
def test_Valid(self):
@@ -24,9 +25,8 @@ def test_Valid(self):
2425
def GetEdge(node):
2526
return tuple(graph[node])
2627

27-
self.assertEqual(
28-
gyp.common.TopologicallySorted(graph.keys(), GetEdge), ["a", "c", "d", "b"]
29-
)
28+
assert gyp.common.TopologicallySorted(
29+
graph.keys(), GetEdge) == ["a", "c", "d", "b"]
3030

3131
def test_Cycle(self):
3232
"""Test that an exception is thrown on a cyclic graph."""
@@ -58,7 +58,7 @@ def tearDown(self):
5858

5959
def assertFlavor(self, expected, argument, param):
6060
sys.platform = argument
61-
self.assertEqual(expected, gyp.common.GetFlavor(param))
61+
assert expected == gyp.common.GetFlavor(param)
6262

6363
def test_platform_default(self):
6464
self.assertFlavor("freebsd", "freebsd9", {})
@@ -73,6 +73,99 @@ def test_platform_default(self):
7373
def test_param(self):
7474
self.assertFlavor("foobar", "linux2", {"flavor": "foobar"})
7575

76+
class MockCommunicate:
77+
def __init__(self, stdout):
78+
self.stdout = stdout
79+
80+
def decode(self, encoding):
81+
return self.stdout
82+
83+
@patch("os.close")
84+
@patch("os.unlink")
85+
@patch("tempfile.mkstemp")
86+
def test_GetCrossCompilerPredefines(self, mock_mkstemp, mock_unlink, mock_close):
87+
mock_close.return_value = None
88+
mock_unlink.return_value = None
89+
mock_mkstemp.return_value = (0, "temp.c")
90+
91+
def mock_run(env, defines_stdout, expected_cmd):
92+
with patch("subprocess.run") as mock_run:
93+
mock_process = MagicMock()
94+
mock_process.returncode = 0
95+
mock_process.stdout = TestGetFlavor.MockCommunicate(defines_stdout)
96+
mock_run.return_value = mock_process
97+
expected_input = "temp.c" if sys.platform == "win32" else "/dev/null"
98+
with patch.dict(os.environ, env):
99+
defines = gyp.common.GetCrossCompilerPredefines()
100+
flavor = gyp.common.GetFlavor({})
101+
if env.get("CC_target"):
102+
mock_run.assert_called_with(
103+
[
104+
*expected_cmd,
105+
"-dM", "-E", "-x", "c", expected_input
106+
],
107+
shell=sys.platform == "win32",
108+
capture_output=True, check=True)
109+
return [defines, flavor]
110+
111+
[defines1, _] = mock_run({}, "", [])
112+
assert {} == defines1
113+
114+
[defines2, flavor2] = mock_run(
115+
{ "CC_target": "/opt/wasi-sdk/bin/clang" },
116+
"#define __wasm__ 1\n#define __wasi__ 1\n",
117+
["/opt/wasi-sdk/bin/clang"]
118+
)
119+
assert { "__wasm__": "1", "__wasi__": "1" } == defines2
120+
assert flavor2 == "wasi"
121+
122+
[defines3, flavor3] = mock_run(
123+
{ "CC_target": "/opt/wasi-sdk/bin/clang --target=wasm32" },
124+
"#define __wasm__ 1\n",
125+
["/opt/wasi-sdk/bin/clang", "--target=wasm32"]
126+
)
127+
assert { "__wasm__": "1" } == defines3
128+
assert flavor3 == "wasm"
129+
130+
[defines4, flavor4] = mock_run(
131+
{ "CC_target": "/emsdk/upstream/emscripten/emcc" },
132+
"#define __EMSCRIPTEN__ 1\n",
133+
["/emsdk/upstream/emscripten/emcc"]
134+
)
135+
assert { "__EMSCRIPTEN__": "1" } == defines4
136+
assert flavor4 == "emscripten"
137+
138+
# Test path which include white space
139+
[defines5, flavor5] = mock_run(
140+
{
141+
"CC_target": "\"/Users/Toyo Li/wasi-sdk/bin/clang\" -O3",
142+
"CFLAGS": "--target=wasm32-wasi-threads -pthread"
143+
},
144+
"#define __wasm__ 1\n#define __wasi__ 1\n#define _REENTRANT 1\n",
145+
[
146+
"/Users/Toyo Li/wasi-sdk/bin/clang",
147+
"-O3",
148+
"--target=wasm32-wasi-threads",
149+
"-pthread"
150+
]
151+
)
152+
assert {
153+
"__wasm__": "1",
154+
"__wasi__": "1",
155+
"_REENTRANT": "1"
156+
} == defines5
157+
assert flavor5 == "wasi"
158+
159+
original_sep = os.sep
160+
os.sep = "\\"
161+
[defines6, flavor6] = mock_run(
162+
{ "CC_target": "\"C:\\Program Files\\wasi-sdk\\clang.exe\"" },
163+
"#define __wasm__ 1\n#define __wasi__ 1\n",
164+
["C:/Program Files/wasi-sdk/clang.exe"]
165+
)
166+
os.sep = original_sep
167+
assert { "__wasm__": "1", "__wasi__": "1" } == defines6
168+
assert flavor6 == "wasi"
76169

77170
if __name__ == "__main__":
78171
unittest.main()

tools/gyp/pylib/gyp/generator/android.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -739,9 +739,9 @@ def ComputeOutput(self, spec):
739739
% (self.android_class, self.android_module)
740740
)
741741
else:
742-
path = "$(call intermediates-dir-for,{},{},,,$(GYP_VAR_PREFIX))".format(
743-
self.android_class,
744-
self.android_module,
742+
path = (
743+
f"$(call intermediates-dir-for,{self.android_class},"
744+
f"{self.android_module},,,$(GYP_VAR_PREFIX))"
745745
)
746746

747747
assert spec.get("product_dir") is None # TODO: not supported?

tools/gyp/pylib/gyp/generator/compile_commands_json.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,14 @@ def GenerateOutput(target_list, target_dicts, data, params):
108108
cwd = os.path.dirname(build_file)
109109
AddCommandsForTarget(cwd, target, params, per_config_commands)
110110

111+
output_dir = None
111112
try:
112-
output_dir = params["options"].generator_output
113-
except (AttributeError, KeyError):
114-
output_dir = params["generator_flags"].get("output_dir", "out")
113+
# generator_output can be `None` on Windows machines, or even not
114+
# defined in other cases
115+
output_dir = params.get("options").generator_output
116+
except AttributeError:
117+
pass
118+
output_dir = output_dir or params["generator_flags"].get("output_dir", "out")
115119
for configuration_name, commands in per_config_commands.items():
116120
filename = os.path.join(output_dir, configuration_name, "compile_commands.json")
117121
gyp.common.EnsureDirExists(filename)

tools/gyp/pylib/gyp/generator/gypsh.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
4949
# Use a banner that looks like the stock Python one and like what
5050
# code.interact uses by default, but tack on something to indicate what
5151
# locals are available, and identify gypsh.
52-
banner = "Python {} on {}\nlocals.keys() = {}\ngypsh".format(
53-
sys.version,
54-
sys.platform,
55-
repr(sorted(locals.keys())),
52+
banner = (
53+
f"Python {sys.version} on {sys.platform}\nlocals.keys() = "
54+
f"{sorted(locals.keys())!r}\ngypsh"
5655
)
5756

5857
code.interact(banner, local=locals)

0 commit comments

Comments
 (0)