{"record":{"id":"ae5ddb378be8ae9b","repo":"nodejs/node","slug":"rule-sources-must-not-exist-in-input-target-s-ru","errorCode":null,"errorMessage":"rule_sources must not exist in input, target %s rule %s","messagePattern":"rule_sources must not exist in input, target (.+?) rule (.+?)","errorType":"exception","errorClass":"GypError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":2750,"sourceCode":"        if rule_extension in rule_extensions:\n            raise GypError(\n                (\n                    \"extension %s associated with multiple rules, \"\n                    + \"target %s rules %s and %s\"\n                )\n                % (\n                    rule_extension,\n                    target,\n                    rule_extensions[rule_extension][\"rule_name\"],\n                    rule_name,\n                )\n            )\n        rule_extensions[rule_extension] = rule\n\n        # Make sure rule_sources isn't already there.  It's going to be\n        # created below if needed.\n        if \"rule_sources\" in rule:\n            raise GypError(\n                \"rule_sources must not exist in input, target %s rule %s\"\n                % (target, rule_name)\n            )\n\n        rule_sources = []\n        source_keys = [\"sources\"]\n        source_keys.extend(extra_sources_for_rules)\n        for source_key in source_keys:\n            for source in target_dict.get(source_key, []):\n                (_source_root, source_extension) = os.path.splitext(source)\n                if source_extension.startswith(\".\"):\n                    source_extension = source_extension[1:]\n                if source_extension == rule_extension:\n                    rule_sources.append(source)\n\n        if len(rule_sources) > 0:\n            rule[\"rule_sources\"] = rule_sources\n","sourceCodeStart":2732,"sourceCodeEnd":2768,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L2732-L2768","documentation":"GYP itself synthesizes 'rule_sources' for each rule by scanning the target's sources for files matching the rule's extension. Hand-written 'rule_sources' in the input would be overwritten and is therefore rejected outright so authors do not rely on a value gyp will replace.","triggerScenarios":"A rule dict inside a target's 'rules' list already contains a 'rule_sources' key before validation runs.","commonSituations":"Migrating from an older gyp layout that exposed rule_sources; manually editing a rule block generated by a tool and leaving the generated key in place; misunderstanding that rule_sources is an output, not an input.","solutions":["Remove the 'rule_sources' key from the offending rule in the .gyp/.gypi file.","If you need to control which sources a rule processes, instead curate the target's 'sources' list or the rule's extension; gyp derives rule_sources from those.","Re-run gyp to confirm the rule now validates."],"exampleFix":"// before\n{ 'rule_name': 'gen', 'extension': 'idl', 'rule_sources': ['a.idl'], ... },\n// after\n{ 'rule_name': 'gen', 'extension': 'idl', ... },","handlingStrategy":"validation","validationCode":"for r in target_dict.get('rules', []):\n    if 'rule_sources' in r:\n        raise ValueError(f\"rule {r.get('rule_name')} must not preset 'rule_sources'\")","typeGuard":"def rules_have_no_rule_sources(target_dict: dict) -> bool:\n    return all('rule_sources' not in r for r in target_dict.get('rules', []))","tryCatchPattern":null,"preventionTips":["Remember rule_sources is an OUTPUT field generated by gyp, never an input.","Keep hand-authored rule blocks minimal: rule_name, extension, action/process, outputs, message.","Avoid diffing against generated gyp output."],"tags":["gyp","rules","reserved-key","validation"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}