{"record":{"id":"61899d9d050b036c","repo":"nodejs/node","slug":"error-importing-pymod-do-mainmodule-s-s","errorCode":null,"errorMessage":"Error importing pymod_do_mainmodule (%s): %s","messagePattern":"Error importing pymod_do_mainmodule \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"GypError","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":941,"sourceCode":"\n                replacement = \"\"\n\n                if command_string == \"pymod_do_main\":\n                    # <!pymod_do_main(modulename param eters) loads |modulename| as a\n                    # python module and then calls that module's DoMain() function,\n                    # passing [\"param\", \"eters\"] as a single list argument. For modules\n                    # that don't load quickly, this can be faster than\n                    # <!(python modulename param eters). Do this in |build_file_dir|.\n                    oldwd = os.getcwd()  # Python doesn't like os.open('.'): no fchdir.\n                    if build_file_dir:  # build_file_dir may be None (see above).\n                        os.chdir(build_file_dir)\n                    sys.path.append(os.getcwd())\n                    try:\n                        parsed_contents = shlex.split(contents)\n                        try:\n                            py_module = __import__(parsed_contents[0])\n                        except ImportError as e:\n                            raise GypError(\n                                \"Error importing pymod_do_main\"\n                                \"module (%s): %s\" % (parsed_contents[0], e)\n                            )\n                        replacement = str(\n                            py_module.DoMain(parsed_contents[1:])\n                        ).rstrip()\n                    finally:\n                        sys.path.pop()\n                        os.chdir(oldwd)\n                    assert replacement is not None\n                elif command_string:\n                    raise GypError(\n                        \"Unknown command string '%s' in '%s'.\"\n                        % (command_string, contents)\n                    )\n                else:\n                    # Fix up command with platform specific workarounds.\n                    contents = FixupPlatformCommand(contents)","sourceCodeStart":923,"sourceCodeEnd":959,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L923-L959","documentation":"The <!pymod_do_main(modulename args) expansion tells GYP to import a Python module and call its DoMain(args) function to produce the replacement text. When __import__(modulename) raises ImportError, GYP wraps it in this GypError naming the offending module and the underlying import error. The module is imported from build_file_dir appended to sys.path, so it must be importable from there.","triggerScenarios":"A .gyp file uses '<!(pymod_do_main mymodule arg1)' (or the <! shorthand) but 'mymodule' is not on sys.path, has a typo in its name, has a syntax error, or itself imports an unavailable dependency. Also triggers when the module's name parses differently via shlex.split (e.g. stray quotes).","commonSituations":"Missing PYTHONPATH entry for a helper script shipped alongside the .gyp file; a helper module that was renamed across a refactor; a Python 2/3 incompatibility in the helper; a helper that imports a third-party package not installed in the gyp-running environment (e.g. a v8/node build helper).","solutions":["Confirm the module file exists in the build_file_dir (or on sys.path) and the name matches parsed_contents[0] exactly.","Run 'python -c \"import modulename\"' from the build_file_dir to reproduce the ImportError and read the underlying message in the GypError text.","Install any missing third-party dependency the helper imports, or vendor it next to the helper.","Fix typos/shlex artifacts in the <!pymod_do_main(...) expression (the first whitespace-delimited token is the module name)."],"exampleFix":"// before\n'value': '<!pymod_do_main(build_helper get_version) main',\n// after — ensure build_helper.py lives next to the .gyp file:\n//   <gyp dir>/build_helper.py  exporting  def DoMain(args): ...\n'value': '<!pymod_do_main(build_helper get_version) main',","handlingStrategy":"validation","validationCode":"# From the build_file_dir, confirm the helper module imports cleanly before running gyp.\nimport sys, os\nsys.path.insert(0, build_file_dir)\ntry:\n    __import__(module_name)\nexcept ImportError as e:\n    raise SystemExit(f\"pymod_do_main helper {module_name!r} is not importable: {e}\")","typeGuard":"def is_pymod_importable(module_name: str, build_file_dir: str) -> bool:\n    import importlib.util\n    spec = importlib.util.find_spec(module_name)\n    return spec is not None","tryCatchPattern":null,"preventionTips":["Run 'python -c \"import helper\"' from the .gyp file's directory as a pre-build sanity check.","Keep pymod_do_main helpers next to the .gyp file or vendor them on a known sys.path entry.","Pin the helper's own dependencies in the same environment gyp runs in."],"tags":["gyp","python-import","pymod-do-main","build-config"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}