nodejs/node · error · GypError

extension %s associated with multiple rules, target %s rules

Error message

extension %s associated with multiple rules, target %s rules %s and %s

What it means

ValidateRulesInTarget also enforces that each file extension maps to at most one rule per target. After stripping an optional leading dot it records the extension; a second rule claiming the same extension is rejected because GYP would not know which rule to invoke on matching source files.

Source

Thrown at tools/gyp/pylib/gyp/input.py:2733

    # Dicts to map between values found in rules' 'rule_name' and 'extension'
    # keys and the rule dicts themselves.
    rule_names = {}
    rule_extensions = {}

    rules = target_dict.get("rules", [])
    for rule in rules:
        # Make sure that there's no conflict among rule names and extensions.
        rule_name = rule["rule_name"]
        if rule_name in rule_names:
            raise GypError(f"rule {rule_name} exists in duplicate, target {target}")
        rule_names[rule_name] = rule

        rule_extension = rule["extension"]
        if rule_extension.startswith("."):
            rule_extension = rule_extension[1:]
        if rule_extension in rule_extensions:
            raise GypError(
                (
                    "extension %s associated with multiple rules, "
                    + "target %s rules %s and %s"
                )
                % (
                    rule_extension,
                    target,
                    rule_extensions[rule_extension]["rule_name"],
                    rule_name,
                )
            )
        rule_extensions[rule_extension] = rule

        # Make sure rule_sources isn't already there.  It's going to be
        # created below if needed.
        if "rule_sources" in rule:
            raise GypError(
                "rule_sources must not exist in input, target %s rule %s"

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Find the two rules whose extension matches (remember the leading dot is optional on both sides).
  2. Decide which single rule should own that extension for this target, or split the source types across separate targets.
  3. Delete or repoint the losing rule's extension to a distinct value, then re-run gyp.

Example fix

// before
{ 'rule_name': 'rule_a', 'extension': 'foo', ... },
{ 'rule_name': 'rule_b', 'extension': '.foo', ... },
// after
{ 'rule_name': 'rule_a', 'extension': 'foo', ... },
{ 'rule_name': 'rule_b', 'extension': 'bar', ... },
Defensive patterns

Strategy: validation

Validate before calling

# Check one extension per rule per target, ignoring leading dots.
exts = set()
for r in target_dict.get('rules', []):
    ext = r.get('extension', '').lstrip('.')
    if ext in exts:
        raise ValueError(f'extension {ext!r} claimed by multiple rules')
    exts.add(ext)

Type guard

def has_unique_rule_extensions(target_dict: dict) -> bool:
    exts = [r.get('extension', '').lstrip('.') for r in target_dict.get('rules', [])]
    return len(exts) == len(set(exts))

Prevention

When it happens

Trigger: Two entries in one target's 'rules' list whose 'extension' keys (after a leading '.' is removed) compare equal, e.g. 'foo' and '.foo'.

Common situations: Adding a second code-generation rule for the same file type; merging gypi files that each define a rule for the same extension; changing a rule's extension to one already covered by another rule in the same target.

Related errors


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