nodejs/node · error · GypError

Xcode environment variables are cyclically dependent: ${node

Error message

Xcode environment variables are cyclically dependent: ${nodes}

What it means

Raised by _TopologicallySortedEnvVarKeys when Xcode environment variables form a reference cycle. The function builds a dependency graph of environment variables that reference each other via ${VAR} interpolation and attempts a topological sort; a CycleError from gyp.common is caught and re-raised as a GypError naming the offending nodes. This means two or more xcode_settings variables reference each other in a loop.

Source

Thrown at tools/gyp/pylib/gyp/xcode_emulation.py:1877

        # Use a definition of edges such that user_of_variable -> used_variable.
        # This happens to be easier in this case, since a variable's
        # definition contains all variables it references in a single string.
        # We can then reverse the result of the topological sort at the end.
        # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
        matches = {v for v in regex.findall(env[node]) if v in env}
        for dependee in matches:
            assert "${" not in dependee, "Nested variables not supported: " + dependee
        return matches

    try:
        # Topologically sort, and then reverse, because we used an edge definition
        # that's inverted from the expected result of this function (see comment
        # above).
        order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
        order.reverse()
        return order
    except gyp.common.CycleError as e:
        raise GypError(
            "Xcode environment variables are cyclically dependent: " + str(e.nodes)
        )


def GetSortedXcodeEnv(
    xcode_settings, built_products_dir, srcroot, configuration, additional_settings=None
):
    env = _GetXcodeEnv(
        xcode_settings, built_products_dir, srcroot, configuration, additional_settings
    )
    return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)]


def GetSpecPostbuildCommands(spec, quiet=False):
    """Returns the list of postbuilds explicitly defined on |spec|, in a form
    executable by a shell."""
    postbuilds = []
    for postbuild in spec.get("postbuilds", []):

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Examine the cycle nodes reported in the error message (the 'nodes' list) and find the variables that reference each other.
  2. Break the cycle by removing or rewriting one of the ${VAR} references so the dependency graph becomes acyclic.
  3. Replace circular references with literal values or computed values set once.
  4. Use only forward references: ensure each variable's ${...} expansions point to already-defined or built-in variables.

Example fix

# before
'xcode_settings': {
  'FOO': '${BAR}',
  'BAR': '${FOO}',
}
# after
'xcode_settings': {
  'FOO': '/abs/path',
  'BAR': '${FOO}/sub',
}
Defensive patterns

Strategy: validation

Validate before calling

# Detect cyclic variable references before gyp generation
def find_cycles(env):
    import re
    graph = {k: set(re.findall(r'\$\{(\w+)\}', v)) for k, v in env.items()}
    visited, stack = set(), []
    def dfs(node):
        if node in stack:
            return stack[stack.index(node):]
        if node in visited:
            return None
        visited.add(node); stack.append(node)
        for dep in graph.get(node, []):
            c = dfs(dep)
            if c: return c
        stack.pop()
        return None
    for n in graph:
        c = dfs(n)
        if c: return c
    return None

Try / catch

from gyp.common import GypError
try:
    sorted_keys = _TopologicallySortedEnvVarKeys(env)
except GypError as e:
    if 'cyclically dependent' in str(e):
        print('fix circular xcode_settings variable references:', e)
    raise

Prevention

When it happens

Trigger: An xcode_settings (or inherited environment) dict defines variables like A=${B} and B=${A}, or a longer chain A=${B}, B=${C}, C=${A}. TopologicallySorted detects the cycle and the nodes involved are reported.

Common situations: Hand-written gyp config with cross-referencing environment variables. Copy-pasting xcode_settings from another project that introduced an indirect circular reference. A variable accidentally set to reference itself.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/2ba82477d609c708. Report an issue: GitHub.