From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cheng Zhao <zcbenz@gmail.com>
Date: Fri, 12 Jan 2024 08:46:57 +0900
Subject: build: do not rely on gn_helpers in GN build

Backport https://github.com/nodejs/node/pull/51439

diff --git a/deps/ngtcp2/unofficial.gni b/deps/ngtcp2/unofficial.gni
index a304cf4aded42d27a1656be36f2d9e1e2b1818c9..838f5d2402cfd6265f913fa1afa57c1c14ed6eca 100644
--- a/deps/ngtcp2/unofficial.gni
+++ b/deps/ngtcp2/unofficial.gni
@@ -7,7 +7,7 @@
 # building official binaries.
 # Please edit the gyp files if you are making changes to build system.
 
-import("//node/node.gni")
+import("../../node.gni")
 
 # The actual configurations are put inside a template in unofficial.gni to
 # prevent accidental edits from contributors.
diff --git a/tools/gypi_to_gn.py b/tools/gypi_to_gn.py
index 47182d8017bfc2e4c156d43a868eaa3df15ed3e2..8a094bd7cf844f880d20a9015e76eefe350af592 100755
--- a/tools/gypi_to_gn.py
+++ b/tools/gypi_to_gn.py
@@ -77,29 +77,119 @@ the input will be replaced with "bar":
 from __future__ import absolute_import
 from __future__ import print_function
 from optparse import OptionParser
-import os
 import sys
 
 
-# Look for standalone GN distribution.
-def FindGNPath():
-  for i in os.environ['PATH'].split(os.pathsep):
-    if i.rstrip(os.sep).endswith('gn'):
-      return i
-  return None
+# This function is copied from build/gn_helpers.py in Chromium.
+def ToGNString(value, pretty=False):
+  """Returns a stringified GN equivalent of a Python value.
 
+  Args:
+    value: The Python value to convert.
+    pretty: Whether to pretty print. If true, then non-empty lists are rendered
+        recursively with one item per line, with indents. Otherwise lists are
+        rendered without new line.
+  Returns:
+    The stringified GN equivalent to |value|.
 
-try:
-  # May already be in the import path.
-  import gn_helpers
-except ImportError:
-  # Add src/build to import path.
-  src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
-                            os.pardir, os.pardir))
-  sys.path.append(os.path.join(src_dir, 'build'))
-  if FindGNPath():
-    sys.path.append(os.path.join(FindGNPath(), 'build'))
-  import gn_helpers
+  Raises:
+    ValueError: |value| cannot be printed to GN.
+  """
+
+  # Emits all output tokens without intervening whitespaces.
+  def GenerateTokens(v, level):
+    if isinstance(v, str):
+      yield '"' + ''.join(TranslateToGnChars(v)) + '"'
+
+    elif isinstance(v, bool):
+      yield 'true' if v else 'false'
+
+    elif isinstance(v, int):
+      yield str(v)
+
+    elif isinstance(v, list):
+      yield '['
+      for i, item in enumerate(v):
+        if i > 0:
+          yield ','
+        for tok in GenerateTokens(item, level + 1):
+          yield tok
+      yield ']'
+
+    elif isinstance(v, dict):
+      if level > 0:
+        yield '{'
+      for key in sorted(v):
+        if not isinstance(key, str):
+          raise ValueError('Dictionary key is not a string.')
+        if not key or key[0].isdigit() or not key.replace('_', '').isalnum():
+          raise ValueError('Dictionary key is not a valid GN identifier.')
+        yield key  # No quotations.
+        yield '='
+        for tok in GenerateTokens(v[key], level + 1):
+          yield tok
+      if level > 0:
+        yield '}'
+
+    else:  # Not supporting float: Add only when needed.
+      raise ValueError('Unsupported type when printing to GN.')
+
+  can_start = lambda tok: tok and tok not in ',}]='
+  can_end = lambda tok: tok and tok not in ',{[='
+
+  # Adds whitespaces, trying to keep everything (except dicts) in 1 line.
+  def PlainGlue(gen):
+    prev_tok = None
+    for i, tok in enumerate(gen):
+      if i > 0:
+        if can_end(prev_tok) and can_start(tok):
+          yield '\n'  # New dict item.
+        elif prev_tok == '[' and tok == ']':
+          yield '  '  # Special case for [].
+        elif tok != ',':
+          yield ' '
+      yield tok
+      prev_tok = tok
+
+  # Adds whitespaces so non-empty lists can span multiple lines, with indent.
+  def PrettyGlue(gen):
+    prev_tok = None
+    level = 0
+    for i, tok in enumerate(gen):
+      if i > 0:
+        if can_end(prev_tok) and can_start(tok):
+          yield '\n' + '  ' * level  # New dict item.
+        elif tok == '=' or prev_tok in '=':
+          yield ' '  # Separator before and after '=', on same line.
+      if tok in ']}':
+        level -= 1
+      # Exclude '[]' and '{}' cases.
+      if int(prev_tok == '[') + int(tok == ']') == 1 or \
+         int(prev_tok == '{') + int(tok == '}') == 1:
+        yield '\n' + '  ' * level
+      yield tok
+      if tok in '[{':
+        level += 1
+      if tok == ',':
+        yield '\n' + '  ' * level
+      prev_tok = tok
+
+  token_gen = GenerateTokens(value, 0)
+  ret = ''.join((PrettyGlue if pretty else PlainGlue)(token_gen))
+  # Add terminating '\n' for dict |value| or multi-line output.
+  if isinstance(value, dict) or '\n' in ret:
+    return ret + '\n'
+  return ret
+
+
+def TranslateToGnChars(s):
+  for code in s.encode('utf-8'):
+    if code in (34, 36, 92):  # For '"', '$', or '\\'.
+      yield '\\' + chr(code)
+    elif 32 <= code < 127:
+      yield chr(code)
+    else:
+      yield '$0x%02X' % code
 
 
 def LoadPythonDictionary(path):
@@ -209,7 +299,7 @@ def main():
     else:
       gn_dict[gn_key] = data[key]
 
-  print(gn_helpers.ToGNString(DeduplicateLists(gn_dict)))
+  print(ToGNString(DeduplicateLists(gn_dict)))
 
 if __name__ == '__main__':
   try:
