rules_intellij/build_defs/restrictions.bzl (203 lines of code) (raw):

# # This file is based on Bazel plugin for IntelliJ by The Bazel Authors, licensed under Apache-2.0; # It was modified by JetBrains s.r.o. and contributors # """ Restriction rules for plugin development This file implements an aspect that restricts the transitive dependencies of intellij_plugins that decide to do so. This prevents large transitive google3 dependencies from making it into the plugin that runs on a different context (IntelliJ and not google3) """ # intellij_plugin will validate that all dependencies from these pacakages are self contained _project = [ ] _tests = [ ] # Targets from the project scope that should be reported as external targets. _not_project_for_tests = [ ] # A set of external dependencies that can be built outside of google3 _valid = [ ] EXTERNAL_DEPENDENCIES = { } # List of targets that use internal only Guava APIs that need to be cleaned up. # Targets in this list are java_library's that do not have the line: # plugins = ["//java/com/google/devtools/build/buildjar/plugin/annotations:google_internal_checker"], EXISTING_UNCHECKED = [ ] # A temporary list of external targets that plugins are depending on. DO NOT ADD TO THIS ALLOWED_EXTERNAL_TEST_DEPENDENCIES = [ ] # A list of targets currently with not allowed dependencies EXISTING_EXTERNAL_TEST_VIOLATIONS = [ ] RestrictedInfo = provider( doc = "The dependencies, per target, outside the project", fields = { "roots": "A depset of roots of all target trees that don't have external dependencies", "dependencies": "A map from target to external dependencies", "unchecked": "A list of targets that are still unchecked for guava internal APIs", }, ) def _in_set(target, set): pkg = target.label.package for p in set: if pkg == p or pkg.startswith(p + "/"): return p if str(target.label) == p: return p return None def _in_project(target): return _in_set(target, _project) def _in_tests(target): lbl = str(target.label) return (lbl.endswith("_test") or lbl.endswith("_tests") or lbl.endswith(":tests")) and _in_project(target) or _in_set(target, _tests) def _get_deps(ctx): deps = [] if hasattr(ctx.rule.attr, "deps"): deps.extend(ctx.rule.attr.deps) if hasattr(ctx.rule.attr, "exports"): deps.extend(ctx.rule.attr.exports) if hasattr(ctx.rule.attr, "runtime_deps"): deps.extend(ctx.rule.attr.runtime_deps) if hasattr(ctx.rule.attr, "data"): deps.extend(ctx.rule.attr.data) if hasattr(ctx.rule.attr, "tests"): deps.extend(ctx.rule.attr.tests) return deps def _restricted_deps_aspect_impl(target, ctx): if not _in_project(target): return [] unchecked = [] if ctx.rule.kind == "java_library": if ctx.rule.attr.plugins: labels = [t.label for t in ctx.rule.attr.plugins] if (Label("//java/com/google/devtools/build/buildjar/plugin/annotations:google_internal_checker") not in labels): unchecked.append(target) else: unchecked.append(target) nested_roots = [] dependencies = {} nested_unchecked = [] outside_project = [] for d in _get_deps(ctx): if not _in_project(d) and not _in_set(d, _valid): outside_project.append(d) if RestrictedInfo in d: dependencies.update(d[RestrictedInfo].dependencies) nested_unchecked.append(d[RestrictedInfo].unchecked) nested_roots.append(d[RestrictedInfo].roots) if outside_project: dependencies[target] = outside_project if dependencies: # This target cannot be a root as either itself or its dependencies depend on out of project targets roots = depset(direct = [], transitive = nested_roots) else: # No external dependencies on the entire subtree, we are a root roots = depset(direct = [target]) return [RestrictedInfo( dependencies = dependencies, unchecked = depset(direct = unchecked, transitive = nested_unchecked), roots = roots, )] # buildifier: disable=function-docstring def validate_unchecked_internal(unchecked): not_allowed_to_be_unchecked = [t for t in unchecked if t not in EXISTING_UNCHECKED] checked_still_in_list = [t for t in EXISTING_UNCHECKED if t not in unchecked] error = "" if not_allowed_to_be_unchecked: error += "The following targets do not have either google_internal_checker or beta_checker on:\n " + "\n ".join(not_allowed_to_be_unchecked) + "\n" if checked_still_in_list: error += "The following targets are checked but still in the EXISTING_UNCHECKED list:\n " + "\n ".join(checked_still_in_list) + "\n" if error: fail(error) def _restricted_test_deps_aspect_impl(target, ctx): if not _in_tests(target): return [] dependencies = {} outside_project = [] for d in _get_deps(ctx): if not _in_tests(d) and not _in_set(d, _valid) and (not _in_project(d) or _in_set(d, _not_project_for_tests)): outside_project.append(d) if RestrictedInfo in d: dependencies.update(d[RestrictedInfo].dependencies) if outside_project: dependencies[target] = outside_project return [ RestrictedInfo(dependencies = dependencies), ] def validate_restrictions(dependencies): external_dependencies = {str(k.label): [str(vt.label) for vt in v] for (k, v) in dependencies.items()} if external_dependencies != EXTERNAL_DEPENDENCIES: error = ( ) fail(error) # buildifier: disable=function-docstring def _validate_test_restrictions(dependencies, allowed_external, existing_violations): violations = sorted([str(d.label) for d in dependencies.keys()]) error = "" if violations != sorted(existing_violations): new_violations = [t for t in violations if t not in existing_violations] no_longer_violations = [t for t in existing_violations if t not in violations] if new_violations: error += ( "These targets now depend on external targets:\n " + "\n ".join( [ str(t.label) + " =>\n " + "\n ".join([str(vt.label) for vt in v]) for (t, v) in dependencies.items() if str(t.label) in new_violations ], ) + "\n" ) if no_longer_violations: error += "The following targets no longer depend on external targets, please remove from restrictions.bzl:\n " + "\n ".join(no_longer_violations) + "\n" for target, outside_project in dependencies.items(): invalid = [dep for dep in outside_project if not _in_set(dep, allowed_external)] if invalid: tgts = [str(t.label) for t in invalid] error += "Invalid dependencies for target " + str(target.label) + "\n " + "\n ".join(tgts) + "\n" if error != "": error += "For more information see restrictions.bzl" fail(error) # Check allowed_external does not contain unnecessary targets current_allowed_external = {} for target, outside_project in dependencies.items(): for out in outside_project: item = _in_set(out, allowed_external) if item: current_allowed_external[item] = item if sorted(current_allowed_external.keys()) != sorted(allowed_external): no_longer_needed = [e for e in allowed_external if e not in current_allowed_external] if no_longer_needed: tgts = [str(t) for t in no_longer_needed] fail("The following external dependencies are no longer needed: " + "\n " + "\n ".join(tgts) + "\n") restricted_deps_aspect = aspect( implementation = _restricted_deps_aspect_impl, attr_aspects = ["*"], ) restricted_test_deps_aspect = aspect( implementation = _restricted_test_deps_aspect_impl, attr_aspects = ["*"], ) def _validate_test_dependencies_impl(ctx): dependencies = {} for k in ctx.attr.deps: if RestrictedInfo in k: dependencies.update(k[RestrictedInfo].dependencies) _validate_test_restrictions(dependencies, ctx.attr.allowed_external_dependencies, ctx.attr.existing_external_violations) return [DefaultInfo(files = depset())] _validate_test_dependencies = rule( implementation = _validate_test_dependencies_impl, attrs = { "allowed_external_dependencies": attr.string_list(), "existing_external_violations": attr.string_list(), "deps": attr.label_list(aspects = [restricted_test_deps_aspect]), "data": attr.label(), }, )