Skip to content

Commit

Permalink
tools: update gyp-next to 0.17.0
Browse files Browse the repository at this point in the history
PR-URL: #52835
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Christian Clauss <cclauss@me.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
  • Loading branch information
nodejs-github-bot authored and targos committed May 11, 2024
1 parent e895f7c commit c5f832a
Show file tree
Hide file tree
Showing 15 changed files with 328 additions and 73 deletions.
15 changes: 15 additions & 0 deletions tools/gyp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## [0.17.0](https://github.com/nodejs/gyp-next/compare/v0.16.2...v0.17.0) (2024-04-29)


### Features

* generate compile_commands.json with ninja ([#228](https://github.com/nodejs/gyp-next/issues/228)) ([7b20b46](https://github.com/nodejs/gyp-next/commit/7b20b4673d8cf46ff61898eb19569007d55c854a))


### Bug Fixes

* 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))
* 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))
* support empty dictionary keys in input ([#245](https://github.com/nodejs/gyp-next/issues/245)) ([178459f](https://github.com/nodejs/gyp-next/commit/178459ff343a2771d5f30f04467d2f032d6b3565))
* update Ruff to 0.3.1 ([876ccaf](https://github.com/nodejs/gyp-next/commit/876ccaf5629e1b95e13aaa2b0eb6cbd08fa80593))

## [0.16.2](https://github.com/nodejs/gyp-next/compare/v0.16.1...v0.16.2) (2024-03-07)


Expand Down
4 changes: 4 additions & 0 deletions tools/gyp/data/ninja/build.ninja
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
rule cc
command = cc $in $out

build my.out: cc my.in
63 changes: 60 additions & 3 deletions tools/gyp/pylib/gyp/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import tempfile
import sys
import subprocess
import shlex

from collections.abc import MutableSet

Expand Down Expand Up @@ -422,17 +423,61 @@ def EnsureDirExists(path):
except OSError:
pass

def GetCrossCompilerPredefines(): # -> dict
cmd = []

# shlex.split() will eat '\' in posix mode, but
# setting posix=False will preserve extra '"' cause CreateProcess fail on Windows
# this makes '\' in %CC_target% and %CFLAGS% work
def replace_sep(s):
return s.replace(os.sep, "/") if os.sep != "/" else s

if CC := os.environ.get("CC_target") or os.environ.get("CC"):
cmd += shlex.split(replace_sep(CC))
if CFLAGS := os.environ.get("CFLAGS"):
cmd += shlex.split(replace_sep(CFLAGS))
elif CXX := os.environ.get("CXX_target") or os.environ.get("CXX"):
cmd += shlex.split(replace_sep(CXX))
if CXXFLAGS := os.environ.get("CXXFLAGS"):
cmd += shlex.split(replace_sep(CXXFLAGS))
else:
return {}

def GetFlavor(params):
if sys.platform == "win32":
fd, input = tempfile.mkstemp(suffix=".c")
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
try:
os.close(fd)
stdout = subprocess.run(
real_cmd, shell=True,
capture_output=True, check=True
).stdout
finally:
os.unlink(input)
else:
input = "/dev/null"
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
stdout = subprocess.run(
real_cmd, shell=False,
capture_output=True, check=True
).stdout

defines = {}
lines = stdout.decode("utf-8").replace("\r\n", "\n").split("\n")
for line in lines:
if (line or "").startswith("#define "):
_, key, *value = line.split(" ")
defines[key] = " ".join(value)
return defines

def GetFlavorByPlatform():
"""Returns |params.flavor| if it's set, the system's default flavor else."""
flavors = {
"cygwin": "win",
"win32": "win",
"darwin": "mac",
}

if "flavor" in params:
return params["flavor"]
if sys.platform in flavors:
return flavors[sys.platform]
if sys.platform.startswith("sunos"):
Expand All @@ -452,6 +497,18 @@ def GetFlavor(params):

return "linux"

def GetFlavor(params):
if "flavor" in params:
return params["flavor"]

defines = GetCrossCompilerPredefines()
if "__EMSCRIPTEN__" in defines:
return "emscripten"
if "__wasm__" in defines:
return "wasi" if "__wasi__" in defines else "wasm"

return GetFlavorByPlatform()


def CopyTool(flavor, out_path, generator_flags={}):
"""Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it
Expand Down
103 changes: 98 additions & 5 deletions tools/gyp/pylib/gyp/common_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import gyp.common
import unittest
import sys

import os
from unittest.mock import patch, MagicMock

class TestTopologicallySorted(unittest.TestCase):
def test_Valid(self):
Expand All @@ -24,9 +25,8 @@ def test_Valid(self):
def GetEdge(node):
return tuple(graph[node])

self.assertEqual(
gyp.common.TopologicallySorted(graph.keys(), GetEdge), ["a", "c", "d", "b"]
)
assert gyp.common.TopologicallySorted(
graph.keys(), GetEdge) == ["a", "c", "d", "b"]

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

def assertFlavor(self, expected, argument, param):
sys.platform = argument
self.assertEqual(expected, gyp.common.GetFlavor(param))
assert expected == gyp.common.GetFlavor(param)

def test_platform_default(self):
self.assertFlavor("freebsd", "freebsd9", {})
Expand All @@ -73,6 +73,99 @@ def test_platform_default(self):
def test_param(self):
self.assertFlavor("foobar", "linux2", {"flavor": "foobar"})

class MockCommunicate:
def __init__(self, stdout):
self.stdout = stdout

def decode(self, encoding):
return self.stdout

@patch("os.close")
@patch("os.unlink")
@patch("tempfile.mkstemp")
def test_GetCrossCompilerPredefines(self, mock_mkstemp, mock_unlink, mock_close):
mock_close.return_value = None
mock_unlink.return_value = None
mock_mkstemp.return_value = (0, "temp.c")

def mock_run(env, defines_stdout, expected_cmd):
with patch("subprocess.run") as mock_run:
mock_process = MagicMock()
mock_process.returncode = 0
mock_process.stdout = TestGetFlavor.MockCommunicate(defines_stdout)
mock_run.return_value = mock_process
expected_input = "temp.c" if sys.platform == "win32" else "/dev/null"
with patch.dict(os.environ, env):
defines = gyp.common.GetCrossCompilerPredefines()
flavor = gyp.common.GetFlavor({})
if env.get("CC_target"):
mock_run.assert_called_with(
[
*expected_cmd,
"-dM", "-E", "-x", "c", expected_input
],
shell=sys.platform == "win32",
capture_output=True, check=True)
return [defines, flavor]

[defines1, _] = mock_run({}, "", [])
assert {} == defines1

[defines2, flavor2] = mock_run(
{ "CC_target": "/opt/wasi-sdk/bin/clang" },
"#define __wasm__ 1\n#define __wasi__ 1\n",
["/opt/wasi-sdk/bin/clang"]
)
assert { "__wasm__": "1", "__wasi__": "1" } == defines2
assert flavor2 == "wasi"

[defines3, flavor3] = mock_run(
{ "CC_target": "/opt/wasi-sdk/bin/clang --target=wasm32" },
"#define __wasm__ 1\n",
["/opt/wasi-sdk/bin/clang", "--target=wasm32"]
)
assert { "__wasm__": "1" } == defines3
assert flavor3 == "wasm"

[defines4, flavor4] = mock_run(
{ "CC_target": "/emsdk/upstream/emscripten/emcc" },
"#define __EMSCRIPTEN__ 1\n",
["/emsdk/upstream/emscripten/emcc"]
)
assert { "__EMSCRIPTEN__": "1" } == defines4
assert flavor4 == "emscripten"

# Test path which include white space
[defines5, flavor5] = mock_run(
{
"CC_target": "\"/Users/Toyo Li/wasi-sdk/bin/clang\" -O3",
"CFLAGS": "--target=wasm32-wasi-threads -pthread"
},
"#define __wasm__ 1\n#define __wasi__ 1\n#define _REENTRANT 1\n",
[
"/Users/Toyo Li/wasi-sdk/bin/clang",
"-O3",
"--target=wasm32-wasi-threads",
"-pthread"
]
)
assert {
"__wasm__": "1",
"__wasi__": "1",
"_REENTRANT": "1"
} == defines5
assert flavor5 == "wasi"

original_sep = os.sep
os.sep = "\\"
[defines6, flavor6] = mock_run(
{ "CC_target": "\"C:\\Program Files\\wasi-sdk\\clang.exe\"" },
"#define __wasm__ 1\n#define __wasi__ 1\n",
["C:/Program Files/wasi-sdk/clang.exe"]
)
os.sep = original_sep
assert { "__wasm__": "1", "__wasi__": "1" } == defines6
assert flavor6 == "wasi"

if __name__ == "__main__":
unittest.main()
6 changes: 3 additions & 3 deletions tools/gyp/pylib/gyp/generator/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,9 +739,9 @@ def ComputeOutput(self, spec):
% (self.android_class, self.android_module)
)
else:
path = "$(call intermediates-dir-for,{},{},,,$(GYP_VAR_PREFIX))".format(
self.android_class,
self.android_module,
path = (
f"$(call intermediates-dir-for,{self.android_class},"
f"{self.android_module},,,$(GYP_VAR_PREFIX))"
)

assert spec.get("product_dir") is None # TODO: not supported?
Expand Down
10 changes: 7 additions & 3 deletions tools/gyp/pylib/gyp/generator/compile_commands_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,14 @@ def GenerateOutput(target_list, target_dicts, data, params):
cwd = os.path.dirname(build_file)
AddCommandsForTarget(cwd, target, params, per_config_commands)

output_dir = None
try:
output_dir = params["options"].generator_output
except (AttributeError, KeyError):
output_dir = params["generator_flags"].get("output_dir", "out")
# generator_output can be `None` on Windows machines, or even not
# defined in other cases
output_dir = params.get("options").generator_output
except AttributeError:
pass
output_dir = output_dir or params["generator_flags"].get("output_dir", "out")
for configuration_name, commands in per_config_commands.items():
filename = os.path.join(output_dir, configuration_name, "compile_commands.json")
gyp.common.EnsureDirExists(filename)
Expand Down
7 changes: 3 additions & 4 deletions tools/gyp/pylib/gyp/generator/gypsh.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
# Use a banner that looks like the stock Python one and like what
# code.interact uses by default, but tack on something to indicate what
# locals are available, and identify gypsh.
banner = "Python {} on {}\nlocals.keys() = {}\ngypsh".format(
sys.version,
sys.platform,
repr(sorted(locals.keys())),
banner = (
f"Python {sys.version} on {sys.platform}\nlocals.keys() = "
f"{sorted(locals.keys())!r}\ngypsh"
)

code.interact(banner, local=locals)

0 comments on commit c5f832a

Please sign in to comment.