{"record":{"id":"2e9338918fbedf78","repo":"nodejs/node","slug":"need-one-argument-which-is-the-gypi-file-to-read","errorCode":null,"errorMessage":"Need one argument which is the .gypi file to read.","messagePattern":"Need one argument which is the \\.gypi file to read\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"tools/gypi_to_gn.py","lineNumber":302,"sourceCode":"def DeduplicateLists(values):\n  \"\"\"Recursively remove duplicate values in lists.\"\"\"\n  if isinstance(values, list):\n    return sorted(list(set(values)))\n\n  if isinstance(values, dict):\n    for key in values:\n      values[key] = DeduplicateLists(values[key])\n  return values\n\n\ndef main():\n  parser = OptionParser()\n  parser.add_option(\"-r\", \"--replace\", action=\"append\",\n    help=\"Replaces substrings. If passed a=b, replaces all substrs a with b.\")\n  (options, args) = parser.parse_args()\n\n  if len(args) != 1:\n    raise Exception(\"Need one argument which is the .gypi file to read.\")\n\n  data = LoadPythonDictionary(args[0])\n  if options.replace:\n    # Do replacements for all specified patterns.\n    for replace in options.replace:\n      split = replace.split('=')\n      # Allow \"foo=\" to replace with nothing.\n      if len(split) == 1:\n        split.append('')\n      assert len(split) == 2, \"Replacement must be of the form 'key=value'.\"\n      data = ReplaceSubstrings(data, split[0], split[1])\n\n  gn_dict = {}\n  for key in data:\n    gn_key = key.replace('-', '_')\n    # Sometimes .gypi files use the GYP syntax with percents at the end of the\n    # variable name (to indicate not to overwrite a previously-defined value):\n    #   'foo%': 'bar',","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gypi_to_gn.py#L284-L320","documentation":"Raised by main() in gypi_to_gn.py when the parsed command-line positional arguments are not exactly one. The script expects a single positional argument: the path to the .gypi file to convert. Both zero arguments and two-or-more arguments trip this guard, which runs after OptionParser has consumed the -r/--replace options.","triggerScenarios":"Running `python gypi_to_gn.py` with no positional file; running it with two or more positional files (`gypi_to_gn.py a.gypi b.gypi`); accidentally passing flags after the file such that OptionParser swallows the file as an option value and leaves args empty.","commonSituations":"Copy-pasting an invocation from docs that omitted the filename; batching several .gypi files in one call (unsupported); a wrapper script that conditionally appends the filename and skipped it on a given branch.","solutions":["Invoke with exactly one positional path: `python tools/gypi_to_gn.py path/to/file.gypi`.","Put any -r/--replace options before or after the single file, never in place of it.","Loop over multiple files in a shell loop instead of passing them all at once."],"exampleFix":"# before\npython tools/gypi_to_gn.py a.gypi b.gypi\n# after\npython tools/gypi_to_gn.py a.gypi\npython tools/gypi_to_gn.py b.gypi","handlingStrategy":"validation","validationCode":"if len(args) != 1:\n    raise SystemExit('usage: gypi_to_gn.py [-r a=b] <file.gypi>')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Wrap the script in a shell helper that enforces the single-argument contract.","Document the exact invocation in your build README.","Loop over multiple files externally; never pass more than one to a single invocation."],"tags":["cli","usage","gyp"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}