def test_register_union_rules(bc_builder: BuildConfiguration.Builder) -> None: @union class Base: pass class A: pass class B: pass union_a = UnionRule(Base, A) union_b = UnionRule(Base, B) bc_builder.register_rules([union_a]) bc_builder.register_rules([union_b]) assert bc_builder.create().union_rules == FrozenOrderedSet( [union_a, union_b])
def load_backend(build_configuration: BuildConfiguration.Builder, backend_package: str) -> None: """Installs the given backend package into the build configuration. :param build_configuration: the BuildConfiguration to install the backend plugin into. :param backend_package: the package name containing the backend plugin register module that provides the plugin entrypoints. :raises: :class:``pants.base.exceptions.BuildConfigurationError`` if there is a problem loading the build configuration. """ backend_module = backend_package + ".register" try: module = importlib.import_module(backend_module) except ImportError as ex: traceback.print_exc() raise BackendConfigurationError(f"Failed to load the {backend_module} backend: {ex!r}") def invoke_entrypoint(name): entrypoint = getattr(module, name, lambda: None) try: return entrypoint() except TypeError as e: traceback.print_exc() raise BackendConfigurationError( f"Entrypoint {name} in {backend_module} must be a zero-arg callable: {e!r}" ) target_types = invoke_entrypoint("target_types") if target_types: build_configuration.register_target_types(backend_package, target_types) build_file_aliases = invoke_entrypoint("build_file_aliases") if build_file_aliases: build_configuration.register_aliases(build_file_aliases) rules = invoke_entrypoint("rules") if rules: build_configuration.register_rules(backend_package, rules)
def test_validation(caplog, bc_builder: BuildConfiguration.Builder) -> None: def mk_dummy_opt(_options_scope: str, goal: bool = False) -> Type[Optionable]: class DummyOptionable(GoalSubsystem if goal else Subsystem ): # type: ignore[misc] options_scope = _options_scope return DummyOptionable def mk_dummy_tgt(_alias: str) -> Type[Target]: class DummyTarget(Target): alias = _alias core_fields = tuple() # type: ignore[var-annotated] return DummyTarget bc_builder.register_optionables(( mk_dummy_opt("foo"), mk_dummy_opt("Bar-bar"), mk_dummy_opt("baz"), mk_dummy_opt("qux", goal=True), mk_dummy_opt("global"), )) bc_builder.register_target_types( (mk_dummy_tgt("bar_bar"), mk_dummy_tgt("qux"), mk_dummy_tgt("global"))) with pytest.raises(TypeError) as e: bc_builder.create() assert ( "Naming collision: `Bar-bar`/`bar_bar` is registered as a subsystem and a " "target type." in caplog.text) assert "Naming collision: `qux` is registered as a goal and a target type." in caplog.text assert ( "Naming collision: `global` is registered as a reserved name, a subsystem " "and a target type." in caplog.text) assert "Found naming collisions" in str(e)
def test_register_subsystems(bc_builder: BuildConfiguration.Builder) -> None: def mk_dummy_subsys(_options_scope: str) -> Type[Subsystem]: class DummySubsystem(Subsystem): options_scope = _options_scope return DummySubsystem foo = mk_dummy_subsys("foo") bar = mk_dummy_subsys("bar") baz = mk_dummy_subsys("baz") bc_builder.register_subsystems("backend1", [foo, bar]) bc_builder.register_subsystems("backend2", [bar, baz]) bc_builder.register_subsystems("backend3", [baz]) bc = bc_builder.create() assert bc.subsystem_to_providers == FrozenDict({ foo: ("backend1", ), bar: ("backend1", "backend2"), baz: ( "backend2", "backend3", ), })
def test_register_target_types(bc_builder: BuildConfiguration.Builder) -> None: def mk_dummy_tgt(_alias: str) -> Type[Target]: class DummyTarget(Target): alias = _alias core_fields = tuple() # type: ignore[var-annotated] return DummyTarget foo = mk_dummy_tgt("foo") bar = mk_dummy_tgt("bar") baz = mk_dummy_tgt("baz") bc_builder.register_target_types("backend1", [foo, bar]) bc_builder.register_target_types("backend2", [bar, baz]) bc_builder.register_target_types("backend3", [baz]) bc = bc_builder.create() assert bc.target_type_to_providers == FrozenDict({ foo: ("backend1", ), bar: ("backend1", "backend2"), baz: ( "backend2", "backend3", ), })
def load_plugins( build_configuration: BuildConfiguration.Builder, plugins: List[str], working_set: WorkingSet, ) -> None: """Load named plugins from the current working_set into the supplied build_configuration. "Loading" a plugin here refers to calling registration methods -- it is assumed each plugin is already on the path and an error will be thrown if it is not. Plugins should define their entrypoints in the `pantsbuild.plugin` group when configuring their distribution. Like source backends, the `build_file_aliases`, and `register_goals` methods are called if those entry points are defined. * Plugins are loaded in the order they are provided. * This is important as loading can add, remove or replace existing tasks installed by other plugins. If a plugin needs to assert that another plugin is registered before it, it can define an entrypoint "load_after" which can return a list of plugins which must have been loaded before it can be loaded. This does not change the order or what plugins are loaded in any way -- it is purely an assertion to guard against misconfiguration. :param build_configuration: The BuildConfiguration (for adding aliases). :param plugins: A list of plugin names optionally with versions, in requirement format. eg ['widgetpublish', 'widgetgen==1.2']. :param working_set: A pkg_resources.WorkingSet to load plugins from. """ loaded: Dict = {} for plugin in plugins or []: req = Requirement.parse(plugin) dist = working_set.find(req) if not dist: raise PluginNotFound(f"Could not find plugin: {req}") entries = dist.get_entry_map().get("pantsbuild.plugin", {}) if "load_after" in entries: deps = entries["load_after"].load()() for dep_name in deps: dep = Requirement.parse(dep_name) if dep.key not in loaded: raise PluginLoadOrderError( f"Plugin {plugin} must be loaded after {dep}") if "target_types" in entries: target_types = entries["target_types"].load()() build_configuration.register_target_types(target_types) if "build_file_aliases" in entries: aliases = entries["build_file_aliases"].load()() build_configuration.register_aliases(aliases) if "rules" in entries: rules = entries["rules"].load()() build_configuration.register_rules(rules) loaded[dist.as_requirement().key] = dist
def test_register_exposed_context_aware_object_factory( bc_builder: BuildConfiguration.Builder, ) -> None: def caof_function(parse_context): return parse_context.rel_path class CaofClass: def __init__(self, parse_context): self._parse_context = parse_context def __call__(self): return self._parse_context.rel_path _register_aliases(bc_builder, context_aware_object_factories={ "func": caof_function, "cls": CaofClass }) aliases = bc_builder.create().registered_aliases assert FrozenDict() == aliases.objects assert (FrozenDict({ "func": caof_function, "cls": CaofClass }) == aliases.context_aware_object_factories)
def register_builtin_goals( build_configuration: BuildConfiguration.Builder) -> None: build_configuration.register_subsystems("pants.goal", builtin_goals())
def test_register_exposed_object( bc_builder: BuildConfiguration.Builder) -> None: _register_aliases(bc_builder, objects={"jane": 42}) aliases = bc_builder.create().registered_aliases assert FrozenDict() == aliases.context_aware_object_factories assert FrozenDict(jane=42) == aliases.objects
def test_register_bad(bc_builder: BuildConfiguration.Builder) -> None: with pytest.raises(TypeError): bc_builder.register_aliases(42)