def loadUserPlugin(plugin_filename): """Load of a user plugins and store them in list of active plugins. Notes: A plugin is accepted only if it has a non-empty variable plugin_name, which does not equal that of a disabled (standard) plugin. Supports plugin option specifications. Returns: None """ if not os.path.exists(plugin_filename): plugins_logger.sysexit("Error, cannot find '%s'." % plugin_filename) user_plugin_module = importFileAsModule(plugin_filename) valid_file = False plugin_class = None for key in dir(user_plugin_module): obj = getattr(user_plugin_module, key) if not isObjectAUserPluginBaseClass(obj): continue plugin_name = getattr(obj, "plugin_name", None) if plugin_name and plugin_name not in Options.getPluginsDisabled(): plugin_class = obj valid_file = True break # do not look for more in that module if not valid_file: # this is not a plugin file ... plugins_logger.sysexit("Error, '%s' is not a plugin file." % plugin_filename) return plugin_class
def getPluginOptions(plugin_name): """Return the options values for the specified plugin. Args: plugin_name: plugin identifier Returns: dict with key, value of options given, potentially from default values. """ result = plugin_values.get(plugin_name, {}) for option in plugin_options.get(plugin_name, {}): option_name = option._long_opts[0] # pylint: disable=protected-access arg_value = getattr(Options.options, option.dest) if "[REQUIRED]" in option.help: if not arg_value: plugins_logger.sysexit( "Error, required plugin argument %r of Nuitka plugin %s not given." % (option_name, plugin_name) ) result[option.dest] = arg_value return result
def getPluginClass(plugin_name): # First, load plugin classes, to know what we are talking about. loadPlugins() if plugin_name not in plugin_name2plugin_classes: plugins_logger.sysexit("Error, unknown plug-in '%s' referenced." % plugin_name) return plugin_name2plugin_classes[plugin_name][0]
def _loadPluginClassesFromPath(scan_path): for loader, name, is_pkg in pkgutil.iter_modules(scan_path): if is_pkg: continue module_loader = loader.find_module(name) # Ignore bytecode only left overs. try: if module_loader.get_filename().endswith(".pyc"): continue except AttributeError: # Not a bytecode loader, but e.g. extension module, which is OK in case # it was compiled with Nuitka. pass try: plugin_module = module_loader.load_module(name) except Exception: if Options.is_nondebug: plugins_logger.warning( "Problem loading plugin %r (%s), ignored. Use --debug to make it visible." % (name, module_loader.get_filename())) continue raise plugin_classes = set(obj for obj in plugin_module.__dict__.values() if isObjectAUserPluginBaseClass(obj)) detectors = [ plugin_class for plugin_class in plugin_classes if hasattr(plugin_class, "detector_for") ] for detector in detectors: plugin_class = detector.detector_for assert detector.plugin_name is None, detector detector.plugin_name = plugin_class.plugin_name if plugin_class not in plugin_classes: plugins_logger.sysexit( "Plugin detector %r references unknown plugin %r" % (detector, plugin_class)) plugin_classes.remove(detector) plugin_classes.remove(plugin_class) plugin_name2plugin_classes[plugin_class.plugin_name] = ( plugin_class, detector, ) for plugin_class in plugin_classes: plugin_name2plugin_classes[ plugin_class.plugin_name] = plugin_class, None
def activatePlugins(): """Activate selected plugin classes Args: None Notes: This creates actual plugin instances, before only class objects were used. User plugins are activated as a first step, because they themselves may enable standard plugins. Returns: None """ loadPlugins() # ensure plugin is known and not both, enabled and disabled for plugin_name in Options.getPluginsEnabled() + Options.getPluginsDisabled(): if plugin_name not in plugin_name2plugin_classes: plugins_logger.sysexit( "Error, unknown plug-in '%s' referenced." % plugin_name ) if ( plugin_name in Options.getPluginsEnabled() and plugin_name in Options.getPluginsDisabled() ): plugins_logger.sysexit( "Error, conflicting enable/disable of plug-in '%s'." % plugin_name ) for (plugin_name, (plugin_class, plugin_detector)) in sorted( plugin_name2plugin_classes.items() ): if plugin_name in Options.getPluginsEnabled(): if plugin_class.isRelevant(): _addActivePlugin(plugin_class, args=True) else: plugin_class.warning( "Not relevant with this OS, or Nuitka arguments given, not activated." ) elif plugin_name in Options.getPluginsDisabled(): pass elif plugin_class.isAlwaysEnabled() and plugin_class.isRelevant(): _addActivePlugin(plugin_class, args=True) elif ( plugin_detector is not None and Options.shallDetectMissingPlugins() and plugin_detector.isRelevant() ): _addActivePlugin(plugin_detector, args=False) for plugin_class in user_plugins: _addActivePlugin(plugin_class, args=True)
def _addPluginClass(plugin_class, detector): plugin_name = plugin_class.plugin_name if plugin_name in plugin_name2plugin_classes: plugins_logger.sysexit("Error, plugins collide by name %s: %s <-> %s" % (plugin_name, plugin_class, plugin_name2plugin_classes[plugin_name])) plugin_name2plugin_classes[plugin_name] = ( plugin_class, detector, )
def getPluginClass(plugin_name): # First, load plugin classes, to know what we are talking about. loadPlugins() # Backward compatibility. plugin_name = Options.getPluginNameConsideringRenames(plugin_name) if plugin_name not in plugin_name2plugin_classes: plugins_logger.sysexit("Error, unknown plug-in '%s' referenced." % plugin_name) return plugin_name2plugin_classes[plugin_name][0]
def onModuleDiscovered(self, module): """Called with a module to be loaded. Notes: We may specify code to be prepended and/or appended to this module. This code is stored in the appropriate dict. For every imported module and each of these two options, only one plugin may do this. We check this condition here. Args: module: the module object Returns: None """ full_name = module.getFullName() pre_code, reason = self.createPreModuleLoadCode(module) if pre_code: # Note: We could find a way to handle this if needed. if full_name in pre_modules: plugins_logger.sysexit( "Error, conflicting pre module code from plug-ins for %s" % full_name ) self.info("Injecting pre-module load code for module '%s':" % full_name) for line in reason.split("\n"): self.info(" " + line) pre_modules[full_name] = self._createTriggerLoadedModule( module=module, trigger_name="-preLoad", code=pre_code ) post_code, reason = self.createPostModuleLoadCode(module) if post_code: # Note: We could find a way to handle this if needed. if full_name is post_modules: plugins_logger.sysexit( "Error, conflicting post module code from plug-ins for %s" % full_name ) self.info("Injecting post-module load code for module '%s':" % full_name) for line in reason.split("\n"): self.info(" " + line) post_modules[full_name] = self._createTriggerLoadedModule( module=module, trigger_name="-postLoad", code=post_code )
def _createTriggerLoadedModule(cls, module, trigger_name, code, flags): """Create a "trigger" for a module to be imported. Notes: The trigger will incorporate the code to be prepended / appended. Called by @onModuleDiscovered. Args: module: the module object (serves as dict key) trigger_name: string ("preload"/"postload") code: the code string Returns trigger_module """ from nuitka.tree.Building import buildModule module_name = makeTriggerModuleName(module.getFullName(), trigger_name) # In debug mode, put the files in the build folder, so they can be looked up easily. if Options.is_debug and "HIDE_SOURCE" not in flags: source_path = os.path.join( OutputDirectories.getSourceDirectoryPath(), module_name + ".py") putTextFileContents(filename=source_path, contents=code) try: trigger_module, _added = buildModule( module_filename=os.path.join( os.path.dirname(module.getCompileTimeFilename()), module_name.asPath() + ".py", ), module_name=module_name, source_code=code, is_top=False, is_main=False, is_extension=False, is_fake=module_name, hide_syntax_error=False, ) except SyntaxError: plugins_logger.sysexit( "SyntaxError in plugin provided source code for '%s'." % module_name) if trigger_module.getCompilationMode() == "bytecode": trigger_module.setSourceCode(code) return trigger_module
def _addPluginCommandLineOptions(parser, plugin_class, data_files_tags): plugin_name = plugin_class.plugin_name if plugin_name not in plugin_options: option_group = OptionGroup(parser, "Plugin %s" % plugin_name) try: plugin_class.addPluginCommandLineOptions(option_group) except OptionConflictError as e: for other_plugin_name, other_plugin_option_list in plugin_options.items( ): for other_plugin_option in other_plugin_option_list: # no public interface for that, pylint: disable=protected-access if (e.option_id in other_plugin_option._long_opts or other_plugin_option._short_opts): plugins_logger.sysexit( "Plugin '%s' failed to add options due to conflict with '%s' from plugin '%s." % (plugin_name, e.option_id, other_plugin_name)) if option_group.option_list: parser.add_option_group(option_group) plugin_options[plugin_name] = option_group.option_list else: plugin_options[plugin_name] = () plugin_data_files_tags = plugin_class.getTagDataFileTagOptions() if plugin_data_files_tags: for tag_name, tag_desc in plugin_data_files_tags: if tag_name in (tag for tag, _desc in data_files_tags): plugins_logger.sysexit( "Plugin '%s' provides data files tag handling '%s' already provided." % (plugin_name, tag_name)) data_files_tags.append((tag_name, tag_desc)) plugin_datatag2pluginclasses[tag_name] = plugin_class
def sysexit(cls, message): plugins_logger.sysexit(cls.plugin_name + ": " + message)
def _loadPluginClassesFromPackage(scan_package): scan_path = scan_package.__path__ for item in iter_modules(scan_path): if item.ispkg: continue module_loader = item.module_finder.find_module(item.name) # Ignore bytecode only left overs. try: if module_loader.get_filename().endswith(".pyc"): continue except AttributeError: # Not a bytecode loader, but e.g. extension module, which is OK in case # it was compiled with Nuitka. pass try: plugin_module = module_loader.load_module(item.name) except Exception: if Options.is_nondebug: plugins_logger.warning( "Problem loading plugin %r (%s), ignored. Use --debug to make it visible." % (item.name, module_loader.get_filename())) continue raise # At least for Python2, this is not set properly, but we use it for package # data loading. plugin_module.__package__ = scan_package.__name__ plugin_classes = set(obj for obj in plugin_module.__dict__.values() if isObjectAUserPluginBaseClass(obj)) detectors = [ plugin_class for plugin_class in plugin_classes if hasattr(plugin_class, "detector_for") ] # First the ones with detectors. for detector in detectors: plugin_class = detector.detector_for if detector.__name__.replace("NuitkaPluginDetector", "") != plugin_class.__name__.replace( "NuitkaPlugin", ""): plugins_logger.warning( "Class names %r and %r do not match NuitkaPlugin* and NuitkaPluginDetector* naming convention." % (plugin_class.__name__, detector.__name__)) assert detector.plugin_name is None, detector detector.plugin_name = plugin_class.plugin_name if plugin_class not in plugin_classes: plugins_logger.sysexit( "Plugin detector %r references unknown plugin %r" % (detector, plugin_class)) plugin_classes.remove(detector) plugin_classes.remove(plugin_class) _addPluginClass( plugin_class=plugin_class, detector=detector, ) # Remaining ones have no detector. for plugin_class in plugin_classes: _addPluginClass(plugin_class=plugin_class, detector=None)