{"record":{"id":"2ba82477d609c708","repo":"nodejs/node","slug":"xcode-environment-variables-are-cyclically-depende","errorCode":null,"errorMessage":"Xcode environment variables are cyclically dependent: ${nodes}","messagePattern":"Xcode environment variables are cyclically dependent: (.+?)","errorType":"exception","errorClass":"GypError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/xcode_emulation.py","lineNumber":1877,"sourceCode":"        # Use a definition of edges such that user_of_variable -> used_variable.\n        # This happens to be easier in this case, since a variable's\n        # definition contains all variables it references in a single string.\n        # We can then reverse the result of the topological sort at the end.\n        # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))\n        matches = {v for v in regex.findall(env[node]) if v in env}\n        for dependee in matches:\n            assert \"${\" not in dependee, \"Nested variables not supported: \" + dependee\n        return matches\n\n    try:\n        # Topologically sort, and then reverse, because we used an edge definition\n        # that's inverted from the expected result of this function (see comment\n        # above).\n        order = gyp.common.TopologicallySorted(env.keys(), GetEdges)\n        order.reverse()\n        return order\n    except gyp.common.CycleError as e:\n        raise GypError(\n            \"Xcode environment variables are cyclically dependent: \" + str(e.nodes)\n        )\n\n\ndef GetSortedXcodeEnv(\n    xcode_settings, built_products_dir, srcroot, configuration, additional_settings=None\n):\n    env = _GetXcodeEnv(\n        xcode_settings, built_products_dir, srcroot, configuration, additional_settings\n    )\n    return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)]\n\n\ndef GetSpecPostbuildCommands(spec, quiet=False):\n    \"\"\"Returns the list of postbuilds explicitly defined on |spec|, in a form\n    executable by a shell.\"\"\"\n    postbuilds = []\n    for postbuild in spec.get(\"postbuilds\", []):","sourceCodeStart":1859,"sourceCodeEnd":1895,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/xcode_emulation.py#L1859-L1895","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Examine the cycle nodes reported in the error message (the 'nodes' list) and find the variables that reference each other.","Break the cycle by removing or rewriting one of the ${VAR} references so the dependency graph becomes acyclic.","Replace circular references with literal values or computed values set once.","Use only forward references: ensure each variable's ${...} expansions point to already-defined or built-in variables."],"exampleFix":"# before\n'xcode_settings': {\n  'FOO': '${BAR}',\n  'BAR': '${FOO}',\n}\n# after\n'xcode_settings': {\n  'FOO': '/abs/path',\n  'BAR': '${FOO}/sub',\n}","handlingStrategy":"validation","validationCode":"# Detect cyclic variable references before gyp generation\ndef find_cycles(env):\n    import re\n    graph = {k: set(re.findall(r'\\$\\{(\\w+)\\}', v)) for k, v in env.items()}\n    visited, stack = set(), []\n    def dfs(node):\n        if node in stack:\n            return stack[stack.index(node):]\n        if node in visited:\n            return None\n        visited.add(node); stack.append(node)\n        for dep in graph.get(node, []):\n            c = dfs(dep)\n            if c: return c\n        stack.pop()\n        return None\n    for n in graph:\n        c = dfs(n)\n        if c: return c\n    return None","typeGuard":null,"tryCatchPattern":"from gyp.common import GypError\ntry:\n    sorted_keys = _TopologicallySortedEnvVarKeys(env)\nexcept GypError as e:\n    if 'cyclically dependent' in str(e):\n        print('fix circular xcode_settings variable references:', e)\n    raise","preventionTips":["Audit xcode_settings for ${VAR} references and ensure they form a DAG.","Avoid bidirectional variable references like A=${B} and B=${A}.","Prefer computed literals over chained variable interpolation."],"tags":["xcode","gyp","macos","configuration","environment-variables"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}