From 15dd1de6dd1431c5d2f7d2d7f1179a951d63a328 Mon Sep 17 00:00:00 2001 From: Christian Edward Gruber Date: Fri, 29 Jan 2021 09:13:41 -0800 Subject: [PATCH] Expose kotlinc's -Xfriend-paths to all jvm/android kt rules under the attribute 'assocates=' (#465) Associates lets a library associate it self to other libraries, making them part of the same module. This is constrained such that while multiple libraries may be associated, they must all shard the same module, and so cannot associate to anything that is part of a different module. These module relationships are in the bazel build graph, not the contents of the jars as such. This module membership is transitive (within the above-mentioned constraint), though strict-deps would stop that. Also, only kotlin targets can be associated. Per discussions across several media, the name "associates" was chosen over "friends" (despite the kotlinc flag being -Xfriend-paths) as that is the terminology used in the gradle kotlin plugin, which is kotlin's primary delivery vehicle, and to avoid confusion with the C++ friends concepts. The pre-existing "friends" attribute is preserved for backward compatibility with a warning. Future PR will add a flag to turn off that support, and then we'll delete it. kt_jvm_import does not include this facility, but these can just set their module_name in common to participate. Android should work, but because kt_android_* is a macro not a rule, the implicit target //my/android/library:mytarget_kt should be friended, since it has a KtJvmInfo. The //my/android/library will macro-resolve into an android_library. Until the android rules get the right kind of love, such that we can make a rule that has KtJvmInfo AND android-whatever providers, this simply is a known limitation we'll have to live with. Also, the prior implementation shoved the full transitive closure (all jars, kotlin or no) of the friend= into the -Xfriends-paths flag, which is awful. This PR does break that, in case people were relying on that by some oddity. The fix is to just add the targets directly. Fixes #211 --- .bazelci/presubmit.yml | 12 ++- .bazelignore | 1 + README.md | 4 +- examples/associates/.bazelversion | 1 + examples/associates/WORKSPACE | 44 +++++++++ .../associates/projects/core/api/BUILD.bazel | 9 ++ .../core/api/src/main/kotlin/core/api/api.kt | 16 ++++ .../api/src/test/kotlin/core/api/BUILD.bazel | 10 +++ .../src/test/kotlin/core/api/CoreApiTest.kt | 15 ++++ .../associates/projects/core/impl/BUILD.bazel | 13 +++ .../impl/src/main/kotlin/core/impl/impl.kt | 13 +++ .../src/test/kotlin/core/impl/BUILD.bazel | 13 +++ .../src/test/kotlin/core/impl/CoreImplTest.kt | 17 ++++ kotlin/internal/defs.bzl | 1 + kotlin/internal/jvm/android.bzl | 13 ++- kotlin/internal/jvm/associates.bzl | 83 +++++++++++++++++ kotlin/internal/jvm/compile.bzl | 90 ++++++++++--------- kotlin/internal/jvm/impl.bzl | 2 +- kotlin/internal/jvm/jvm.bzl | 12 ++- kotlin/internal/utils/sets.bzl | 79 ++++++++++++++++ src/test/data/jvm/basic/BUILD | 2 +- src/test/kotlin/io/bazel/kotlin/BUILD | 8 +- ...KotlinJvmAssociatesBasicVisibilityTest.kt} | 6 +- 23 files changed, 406 insertions(+), 58 deletions(-) create mode 120000 examples/associates/.bazelversion create mode 100644 examples/associates/WORKSPACE create mode 100644 examples/associates/projects/core/api/BUILD.bazel create mode 100644 examples/associates/projects/core/api/src/main/kotlin/core/api/api.kt create mode 100644 examples/associates/projects/core/api/src/test/kotlin/core/api/BUILD.bazel create mode 100644 examples/associates/projects/core/api/src/test/kotlin/core/api/CoreApiTest.kt create mode 100644 examples/associates/projects/core/impl/BUILD.bazel create mode 100644 examples/associates/projects/core/impl/src/main/kotlin/core/impl/impl.kt create mode 100644 examples/associates/projects/core/impl/src/test/kotlin/core/impl/BUILD.bazel create mode 100644 examples/associates/projects/core/impl/src/test/kotlin/core/impl/CoreImplTest.kt create mode 100644 kotlin/internal/jvm/associates.bzl create mode 100644 kotlin/internal/utils/sets.bzl rename src/test/kotlin/io/bazel/kotlin/{KotlinJvmFriendsVisibilityTest.kt => KotlinJvmAssociatesBasicVisibilityTest.kt} (58%) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 7370f85a7..10947647f 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -18,7 +18,7 @@ tasks: - "//src/test/kotlin/io/bazel/kotlin/builder:builder_tests" # KotlinJvmDaggerExampleTest and KotlinJvmKaptAssertionTest are not remote # execution compatible, do not run them for now. - - "//src/test/kotlin/io/bazel/kotlin:KotlinJvmFriendsVisibilityTest" + - "//src/test/kotlin/io/bazel/kotlin:KotlinJvmAssociatesBasicVisibilityTest" - "//src/test/kotlin/io/bazel/kotlin:KotlinJvmBasicAssertionTest" test_flags: # Override the default worker strategy for remote builds (worker strategy @@ -37,6 +37,16 @@ tasks: - "--override_repository=io_bazel_rules_kotlin=/tmp/rules_kotlin_release" test_targets: - //... + example-associates: + name: "Example - Associates" + platform: ubuntu1804 + shell_commands: + - "cd ../.. && bazel build //:rules_kotlin_release && rm -rf /tmp/rules_kotlin_release && mkdir -p /tmp/rules_kotlin_release && tar -C /tmp/rules_kotlin_release -xvf bazel-bin/rules_kotlin_release.tgz" + working_directory: examples/associates + test_flags: + - "--override_repository=io_bazel_rules_kotlin=/tmp/rules_kotlin_release" + test_targets: + - //... example-anvil: name: "Example - Anvil" platform: ubuntu1804 diff --git a/.bazelignore b/.bazelignore index 26962a89d..6e5bc4a5d 100644 --- a/.bazelignore +++ b/.bazelignore @@ -2,6 +2,7 @@ # we don't break trying to build separate workspaces using wildcards like //... # examples/dagger doesn't have its own workspace, so don't do all of examples. examples/android +examples/associates examples/jetpack_compose examples/node examples/trivial diff --git a/README.md b/README.md index 4eb971771..a813994c7 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ and `kt_js`, and `kt_android` typically applied to the rules (the exception bein `kt_android_local_test`, which doesn't exist. Use an `android_local_test` that takes a `kt_android_library` as a dependency). -Limited "friend" support is available, in the form of tests being friends of their library for the -system under test, allowing `internal` access to types and functions. +Support for kotlin's -Xfriend-paths via the `associates=` attribute in the jvm allow access to +`internal` members. Also, `kt_jvm_*` rules support the following standard `java_*` rules attributes: * `data` diff --git a/examples/associates/.bazelversion b/examples/associates/.bazelversion new file mode 120000 index 000000000..96cf94962 --- /dev/null +++ b/examples/associates/.bazelversion @@ -0,0 +1 @@ +../../.bazelversion \ No newline at end of file diff --git a/examples/associates/WORKSPACE b/examples/associates/WORKSPACE new file mode 100644 index 000000000..307e04d4c --- /dev/null +++ b/examples/associates/WORKSPACE @@ -0,0 +1,44 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +local_repository( + name = "io_bazel_rules_kotlin", + path = "../..", +) + +load("@io_bazel_rules_kotlin//kotlin:dependencies.bzl", "kt_download_local_dev_dependencies") + +kt_download_local_dev_dependencies() + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains") + +kotlin_repositories() + +kt_register_toolchains() + +RULES_JVM_EXTERNAL_TAG = "2.7" + +RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + artifacts = [ + "junit:junit:4.13", + ], + repositories = [ + "https://repo1.maven.org/maven2", + ], +) + +http_archive( + name = "rules_pkg", + sha256 = "4ba8f4ab0ff85f2484287ab06c0d871dcb31cc54d439457d28fd4ae14b18450a", + url = "https://github.com/bazelbuild/rules_pkg/releases/download/0.2.4/rules_pkg-0.2.4.tar.gz", +) diff --git a/examples/associates/projects/core/api/BUILD.bazel b/examples/associates/projects/core/api/BUILD.bazel new file mode 100644 index 000000000..54b5f68b2 --- /dev/null +++ b/examples/associates/projects/core/api/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") + +package(default_visibility = ["//visibility:public"]) + +kt_jvm_library( + name = "api", + srcs = glob(["src/main/kotlin/**/*.kt"]), + deps = [], +) diff --git a/examples/associates/projects/core/api/src/main/kotlin/core/api/api.kt b/examples/associates/projects/core/api/src/main/kotlin/core/api/api.kt new file mode 100644 index 000000000..aa5de6c96 --- /dev/null +++ b/examples/associates/projects/core/api/src/main/kotlin/core/api/api.kt @@ -0,0 +1,16 @@ +package core.api + +interface SomeInterface { + val name: String + val camelName: String +} + +data class MyType( + override val name: String +) : SomeInterface { + override val camelName: String = name.camelCase() +} + +internal fun String.camelCase() = this.split("_").joinToString("") { + "${it[0].toUpperCase()}${it.substring(1)}" +} diff --git a/examples/associates/projects/core/api/src/test/kotlin/core/api/BUILD.bazel b/examples/associates/projects/core/api/src/test/kotlin/core/api/BUILD.bazel new file mode 100644 index 000000000..970fc1fb8 --- /dev/null +++ b/examples/associates/projects/core/api/src/test/kotlin/core/api/BUILD.bazel @@ -0,0 +1,10 @@ +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_test") + +kt_jvm_test( + name = "CoreApiTest", + srcs = ["CoreApiTest.kt"], + friends = ["//projects/core/api"], # Deprecated - here to ensure it still works. + deps = [ + "@maven//:junit_junit", + ], +) diff --git a/examples/associates/projects/core/api/src/test/kotlin/core/api/CoreApiTest.kt b/examples/associates/projects/core/api/src/test/kotlin/core/api/CoreApiTest.kt new file mode 100644 index 000000000..f7874f586 --- /dev/null +++ b/examples/associates/projects/core/api/src/test/kotlin/core/api/CoreApiTest.kt @@ -0,0 +1,15 @@ +package core.api + +import org.junit.Assert.assertEquals +import org.junit.Test + +class CoreApiTest { + @Test fun testCamelCaseVar() { + val foo = MyType("foo_bar") + assertEquals("FooBar", foo.camelName) + } + + @Test fun testCamelCaseFun() { + assertEquals("FooBar", "foo_bar".camelCase()) + } +} diff --git a/examples/associates/projects/core/impl/BUILD.bazel b/examples/associates/projects/core/impl/BUILD.bazel new file mode 100644 index 000000000..c1055d289 --- /dev/null +++ b/examples/associates/projects/core/impl/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") + +kt_jvm_library( + name = "impl", + srcs = glob(["src/main/kotlin/**/*.kt"]), + associates = ["//projects/core/api"], + visibility = [ + "//projects/core:__subpackages__", + ], + deps = [ + # All my deps are associates. + ], +) diff --git a/examples/associates/projects/core/impl/src/main/kotlin/core/impl/impl.kt b/examples/associates/projects/core/impl/src/main/kotlin/core/impl/impl.kt new file mode 100644 index 000000000..3ae75945d --- /dev/null +++ b/examples/associates/projects/core/impl/src/main/kotlin/core/impl/impl.kt @@ -0,0 +1,13 @@ +package core.impl + +import core.api.SomeInterface +import core.api.camelCase + +internal data class ImplType( + override val name: String +) : SomeInterface { + override val camelName: String = name.camelCase() + val customName = name.customStuff() +} + +internal fun String.customStuff() = "${hashCode()}" diff --git a/examples/associates/projects/core/impl/src/test/kotlin/core/impl/BUILD.bazel b/examples/associates/projects/core/impl/src/test/kotlin/core/impl/BUILD.bazel new file mode 100644 index 000000000..a0525d3f4 --- /dev/null +++ b/examples/associates/projects/core/impl/src/test/kotlin/core/impl/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_test") + +kt_jvm_test( + name = "CoreImplTest", + srcs = ["CoreImplTest.kt"], + associates = [ + "//projects/core/api", + "//projects/core/impl", + ], + deps = [ + "@maven//:junit_junit", + ], +) diff --git a/examples/associates/projects/core/impl/src/test/kotlin/core/impl/CoreImplTest.kt b/examples/associates/projects/core/impl/src/test/kotlin/core/impl/CoreImplTest.kt new file mode 100644 index 000000000..5cea5f049 --- /dev/null +++ b/examples/associates/projects/core/impl/src/test/kotlin/core/impl/CoreImplTest.kt @@ -0,0 +1,17 @@ +package core.impl + +import core.api.camelCase +import org.junit.Assert.assertEquals +import org.junit.Test + +class CoreImplTest { + @Test fun testCamelCaseVar() { + val foo = ImplType("foo_bar") + assertEquals("FooBar", foo.camelName) + } + + @Test fun testCamelCaseFun() { + // Testing transitivity here. TODO: Once strict deps are in place, delete this case. + assertEquals("FooBar", "foo_bar".camelCase()) + } +} diff --git a/kotlin/internal/defs.bzl b/kotlin/internal/defs.bzl index 06f49e5a8..8f5702bad 100644 --- a/kotlin/internal/defs.bzl +++ b/kotlin/internal/defs.bzl @@ -25,6 +25,7 @@ KT_COMPILER_REPO = "com_github_jetbrains_kotlin" KtJvmInfo = provider( fields = { "module_name": "the module name", + "module_jars": "Jars comprising the module (logical compilation unit), a.k.a. associates", "exported_compiler_plugins": "compiler plugins to be invoked by targets depending on this.", "srcs": "the source files. [intelij-aspect]", "outputs": "output jars produced by this rule. [intelij-aspect]", diff --git a/kotlin/internal/jvm/android.bzl b/kotlin/internal/jvm/android.bzl index b7dd9161c..59d9083d7 100644 --- a/kotlin/internal/jvm/android.bzl +++ b/kotlin/internal/jvm/android.bzl @@ -18,7 +18,17 @@ load( _ANDROID_SDK_JAR = "%s" % Label("//third_party:android_sdk") -def _kt_android_artifact(name, srcs = [], deps = [], plugins = [], friends = [], kotlinc_opts = None, javac_opts = None, enable_data_binding = False, **kwargs): +def _kt_android_artifact( + name, + srcs = [], + deps = [], + plugins = [], + friends = None, + associates = [], + kotlinc_opts = None, + javac_opts = None, + enable_data_binding = False, + **kwargs): """Delegates Android related build attributes to the native rules but uses the Kotlin builder to compile Java and Kotlin srcs. Returns a sequence of labels that a wrapping macro should export. """ @@ -42,6 +52,7 @@ def _kt_android_artifact(name, srcs = [], deps = [], plugins = [], friends = [], deps = base_deps + [base_name], plugins = plugins, friends = friends, + associates = associates, testonly = kwargs.get("testonly", default = False), visibility = ["//visibility:private"], kotlinc_opts = kotlinc_opts, diff --git a/kotlin/internal/jvm/associates.bzl b/kotlin/internal/jvm/associates.bzl new file mode 100644 index 000000000..74661024a --- /dev/null +++ b/kotlin/internal/jvm/associates.bzl @@ -0,0 +1,83 @@ +# Copyright 2020 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +load( + "//kotlin/internal:defs.bzl", + _KtJvmInfo = "KtJvmInfo", +) +load( + "//kotlin/internal/utils:sets.bzl", + _sets = "sets", +) +load( + "//kotlin/internal/utils:utils.bzl", + _utils = "utils", +) + +def _get_associates(ctx): + """Creates a struct of associates meta data""" + + friends_legacy = getattr(ctx.attr, "friends", []) + associates = getattr(ctx.attr, "associates", []) + + if friends_legacy: + print("WARNING: friends=[...] is deprecated, please prefer associates=[...] instead.") + if associates: + fail("friends= may not be used together with associates=. Use one or the other.") + elif ctx.attr.testonly == False: + fail("Only testonly targets can use the friends attribute. ") + else: + associates = friends_legacy + + if not bool(associates): + return struct( + targets = [], + module_name = _utils.derive_module_name(ctx), + jars = [], + ) + elif ctx.attr.module_name: + fail("if associates have been set then module_name cannot be provided") + else: + jars = [depset([a], transitive = a[_KtJvmInfo].module_jars) for a in associates] + module_names = _sets.copy_of([x[_KtJvmInfo].module_name for x in associates]) + if len(module_names) > 1: + fail("Dependencies from several different kotlin modules cannot be associated. " + + "Associates can see each other's \"internal\" members, and so must only be " + + "used with other targets in the same module: \n%s" % module_names) + if len(module_names) < 1: + # This should be impossible + fail("Error in rules - a KtJvmInfo was found which did not have a module_name") + return struct( + targets = associates, + jars = jars, + module_name = list(module_names)[0], + ) + +def _flatten_jars(nested_jars_depset): + """Returns a list of strings containing the compile_jars for depset of targets. + + This ends up unwinding the nesting of depsets, since compile_jars contains depsets inside + the nested_jars targets, which themselves are depsets. This function is intended to be called + lazily form within Args.add_all(map_each) as it collapses depsets. + """ + compile_jars_depsets = [ + target[JavaInfo].compile_jars + for target in nested_jars_depset.to_list() + if target[JavaInfo].compile_jars + ] + return [file.path for file in depset(transitive = compile_jars_depsets).to_list()] + +associate_utils = struct( + get_associates = _get_associates, + flatten_jars = _flatten_jars, +) diff --git a/kotlin/internal/jvm/compile.bzl b/kotlin/internal/jvm/compile.bzl index eb61cc028..0542014f9 100644 --- a/kotlin/internal/jvm/compile.bzl +++ b/kotlin/internal/jvm/compile.bzl @@ -34,10 +34,18 @@ load( _plugins_to_classpaths = "plugins_to_classpaths", _plugins_to_options = "plugins_to_options", ) +load( + "//kotlin/internal/jvm:associates.bzl", + _associate_utils = "associate_utils", +) load( "//kotlin/internal/utils:utils.bzl", _utils = "utils", ) +load( + "//kotlin/internal/utils:sets.bzl", + _sets = "sets", +) load( "@bazel_tools//tools/jdk:toolchain_utils.bzl", "find_java_runtime_toolchain", @@ -87,36 +95,18 @@ def _compiler_toolchains(ctx): java_runtime = find_java_runtime_toolchain(ctx, ctx.attr._host_javabase), ) -def _compiler_friends(ctx, friends): - """Creates a struct of friends meta data""" - - if len(friends) > 0 and ctx.attr.testonly == False: - fail("only testonly targets can have friends associated with them") - - # TODO extract and move this into common. Need to make it generic first. - if len(friends) == 0: - return struct( - targets = [], - module_name = _utils.derive_module_name(ctx), - paths = [], - ) - elif len(friends) == 1: - if friends[0][_KtJvmInfo] == None: - fail("only kotlin dependencies can be friends") - elif ctx.attr.module_name: - fail("if friends has been set then module_name cannot be provided") - else: - return struct( - targets = friends, - paths = _java_info(friends[0]).compile_jars, - module_name = friends[0][_KtJvmInfo].module_name, - ) - else: - fail("only one friend is possible") - -def _jvm_deps(toolchains, friend, deps, runtime_deps = []): +def _jvm_deps(toolchains, associated_targets, deps, runtime_deps = []): """Encapsulates jvm dependency metadata.""" - dep_infos = [_java_info(d) for d in friend.targets + deps] + [toolchains.kt.jvm_stdlibs] + diff = _sets.intersection( + _sets.copy_of([x.label for x in associated_targets]), + _sets.copy_of([x.label for x in deps]), + ) + if diff: + fail( + "\n------\nTargets should only be put in associates= or deps=, not both:\n%s" % + ",\n ".join([" %s" % x for x in list(diff)]), + ) + dep_infos = [_java_info(d) for d in associated_targets + deps] + [toolchains.kt.jvm_stdlibs] return struct( deps = dep_infos, compile_jars = depset( @@ -179,13 +169,14 @@ def _adjust_resources_path(path, resource_strip_prefix): else: return _adjust_resources_path_by_default_prefixes(path) +# TODO: Figure this out and delete if really unused. def _merge_kt_jvm_info(module_name, providers): language_versions = {p.language_version: True for p in providers if p.language_version} if len(language_versions) != 1: fail("Conflicting kt language versions: %s" % language_versions) return _KtJvmInfo( language_versions.keys()[0], - modules_jar = [p.module_jars for p in providers], + module_jars = [p.module_jars for p in providers], exported_compiler_plugins = depset(transitive = [ p.exported_compiler_plugins for p in providers @@ -367,7 +358,7 @@ def _run_kt_builder_action( toolchains, srcs, generated_src_jars, - friend, + associates, compile_deps, deps_artifacts, annotation_processors, @@ -378,7 +369,7 @@ def _run_kt_builder_action( build_kotlin = True, mnemonic = "KotlinCompile"): """Creates a KotlinBuilder action invocation.""" - args = _utils.init_args(ctx, rule_kind, friend.module_name) + args = _utils.init_args(ctx, rule_kind, associates.module_name) for f, path in outputs.items(): args.add("--" + f, path) @@ -398,8 +389,7 @@ def _run_kt_builder_action( args.add_all("--sources", srcs.all_srcs, omit_if_empty = True) args.add_all("--source_jars", srcs.src_jars + generated_src_jars, omit_if_empty = True) args.add_all("--deps_artifacts", deps_artifacts, omit_if_empty = True) - - args.add_joined("--kotlin_friend_paths", friend.paths, join_with = "\n") + args.add_all("--kotlin_friend_paths", associates.jars, map_each = _associate_utils.flatten_jars) # Collect and prepare plugin descriptor for the worker. args.add_all( @@ -526,17 +516,17 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind): """ toolchains = _compiler_toolchains(ctx) srcs = _partitioned_srcs(ctx.files.srcs) - friend = _compiler_friends(ctx, friends = getattr(ctx.attr, "friends", [])) + associates = _associate_utils.get_associates(ctx) compile_deps = _jvm_deps( toolchains, - friend, + associates.targets, deps = ctx.attr.deps, runtime_deps = ctx.attr.runtime_deps, ) annotation_processors = _plugin_mappers.targets_to_annotation_processors(ctx.attr.plugins + ctx.attr.deps) transitive_runtime_jars = _plugin_mappers.targets_to_transitive_runtime_jars(ctx.attr.plugins + ctx.attr.deps) plugins = ctx.attr.plugins + _exported_plugins(deps = ctx.attr.deps) - deps_artifacts = _deps_artifacts(toolchains, ctx.attr.deps + friend.targets) + deps_artifacts = _deps_artifacts(toolchains, ctx.attr.deps + associates.targets) generated_src_jars = [] annotation_processing = None @@ -548,7 +538,7 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind): toolchains = toolchains, srcs = srcs, generated_src_jars = [], - friend = friend, + associates = associates, compile_deps = compile_deps, deps_artifacts = deps_artifacts, annotation_processors = annotation_processors, @@ -572,7 +562,7 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind): toolchains = toolchains, srcs = srcs, generated_src_jars = [], - friend = friend, + associates = associates, compile_deps = compile_deps, deps_artifacts = deps_artifacts, annotation_processors = annotation_processors, @@ -643,7 +633,8 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind): java = java_info, kt = _KtJvmInfo( srcs = ctx.files.srcs, - module_name = _utils.derive_module_name(ctx), + module_name = associates.module_name, + module_jars = associates.jars, language_version = toolchains.kt.api_version, exported_compiler_plugins = _collect_plugins_for_export( getattr(ctx.attr, "exported_compiler_plugins", []), @@ -665,7 +656,19 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind): ) -def _run_kt_java_builder_actions(ctx, rule_kind, toolchains, srcs, generated_src_jars, friend, compile_deps, deps_artifacts, annotation_processors, transitive_runtime_jars, plugins, compile_jar): +def _run_kt_java_builder_actions( + ctx, + rule_kind, + toolchains, + srcs, + generated_src_jars, + associates, + compile_deps, + deps_artifacts, + annotation_processors, + transitive_runtime_jars, + plugins, + compile_jar): """Runs the necessary KotlinBuilder and JavaBuilder actions to compile a jar Returns: @@ -686,7 +689,7 @@ def _run_kt_java_builder_actions(ctx, rule_kind, toolchains, srcs, generated_src toolchains = toolchains, srcs = srcs, generated_src_jars = [], - friend = friend, + associates = associates, compile_deps = compile_deps, deps_artifacts = deps_artifacts, annotation_processors = annotation_processors, @@ -737,7 +740,7 @@ def _run_kt_java_builder_actions(ctx, rule_kind, toolchains, srcs, generated_src toolchains = toolchains, srcs = srcs, generated_src_jars = generated_src_jars, - friend = friend, + associates = associates, compile_deps = compile_deps, deps_artifacts = deps_artifacts, annotation_processors = [], @@ -894,6 +897,7 @@ def export_only_providers(ctx, actions, attr, outputs): java = java, kt = _KtJvmInfo( module_name = _utils.derive_module_name(ctx), + module_jars = [], language_version = toolchains.kt.api_version, exported_compiler_plugins = _collect_plugins_for_export( getattr(attr, "exported_compiler_plugins", []), diff --git a/kotlin/internal/jvm/impl.bzl b/kotlin/internal/jvm/impl.bzl index cad524c2e..df3c33a5a 100644 --- a/kotlin/internal/jvm/impl.bzl +++ b/kotlin/internal/jvm/impl.bzl @@ -123,6 +123,7 @@ def kt_jvm_import_impl(ctx): artifact = _unify_jars(ctx) kt_info = _KtJvmInfo( module_name = _utils.derive_module_name(ctx), + module_jars = [], exported_compiler_plugins = depset(getattr(ctx.attr, "exported_compiler_plugins", [])), outputs = struct( jars = [artifact], @@ -159,7 +160,6 @@ def kt_jvm_library_impl(ctx): "\nTo export libraries use exports.", attr = "deps", ) - return _make_providers( ctx, _kt_jvm_produce_jar_actions(ctx, "kt_jvm_library") if ctx.attr.srcs else export_only_providers( diff --git a/kotlin/internal/jvm/jvm.bzl b/kotlin/internal/jvm/jvm.bzl index 6e8888f64..d8f169698 100644 --- a/kotlin/internal/jvm/jvm.bzl +++ b/kotlin/internal/jvm/jvm.bzl @@ -198,9 +198,17 @@ _common_attr = utils.add_dicts( [Attributes common to all build rules](https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes).""", allow_files = True, ), + "associates": attr.label_list( + doc = """Kotlin deps who should be considered part of the same module/compilation-unit + for the purposes of "internal" access. Such deps must all share the same module space + and so a target cannot associate to two deps from two different modules.""", + default = [], + providers = [JavaInfo, _KtJvmInfo], + ), "friends": attr.label_list( - doc = """A single Kotlin dep which allows Kotlin code in other modules access to internal members. Currently uses the output - jar of the module -- i.e., exported deps won't be included.""", + doc = """A single Kotlin dep which allows Kotlin code in other modules access to + internal members. Currently uses the output jar of the module -- i.e., exported + deps won't be included. [DEPRECATED, use "associates" instead]""", default = [], providers = [JavaInfo, _KtJvmInfo], ), diff --git a/kotlin/internal/utils/sets.bzl b/kotlin/internal/utils/sets.bzl new file mode 100644 index 000000000..78de26963 --- /dev/null +++ b/kotlin/internal/utils/sets.bzl @@ -0,0 +1,79 @@ +# +# Copyright 2018 The Bazel Authors. All rights reserved. +# Copyright 2018 Square, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Originally written for bazel_maven_repository, inspired by skylib +# +""" +A set of utilities that provide set-like behavior, using a dict (specifically its keys) as the underlying +implementation. Generally only use dictionaries created by sets.new() because values are normalized. Using +dictionaries from other sources may result in equality failing, and other odd behavior. +""" + +_UNDEFINED = "__UNDEFINED__" +_EMPTY = "__EMPTY__" # Check when changing this to keep in sync with sets.bzl +_SET_DICTIONARY_KEY = "_____SET_DICTIONARY_KEY______" + +def _contains(set, item): + """Returns true if the set contains the supplied item""" + return not (set.get(item, _UNDEFINED) == _UNDEFINED) + +def _add(set, item): + """Adds an item to the set and returns the set""" + set[item] = _EMPTY + return set + +def _add_all_as_list(set, items): + "Implementation for the add_* family of functions." + for item in items: + sets.add(set, item) + return set + +def _add_all(set, items): + """Adds all items in the list or all keys in the dictionary to the set and returns the set""" + item_type = type(items) + if item_type == type({}): + _add_all_as_list(set, list(items)) + elif item_type == type([]): + _add_all_as_list(set, items) + else: + fail("Error, invalid %s argument passed to set operation." % item_type) + return set + +def _new(*items): + """Creates a new set, from a variable array of parameters. """ + return {} if not bool(items) else sets.add_all({}, list(items)) + +def _copy_of(items): + """Creates a new set from a given list. """ + return {} if not bool(items) else sets.add_all({}, list(items)) + +def _difference(a, b): + """Returns the elements that reflect the set difference (items in a that are not in b)""" + return sets.copy_of([x for x in list(a) if not sets.contains(b, x)]) + +def _intersection(a, b): + """Returns the elements that exist in both A and B""" + return sets.difference(a, sets.difference(a, b)) + +sets = struct( + difference = _difference, + intersection = _intersection, + contains = _contains, + add = _add, + add_all = _add_all, + new = _new, + copy_of = _copy_of, +) diff --git a/src/test/data/jvm/basic/BUILD b/src/test/data/jvm/basic/BUILD index 74bee8d40..927aa50d4 100644 --- a/src/test/data/jvm/basic/BUILD +++ b/src/test/data/jvm/basic/BUILD @@ -104,7 +104,7 @@ java_binary( ) kt_jvm_library( - name = "test_friends_library", + name = "test_associates_library", srcs = ["test_friends/Service.kt"], visibility = ["//src/test/kotlin:__subpackages__"], ) diff --git a/src/test/kotlin/io/bazel/kotlin/BUILD b/src/test/kotlin/io/bazel/kotlin/BUILD index b291c8f8c..ca9d83544 100644 --- a/src/test/kotlin/io/bazel/kotlin/BUILD +++ b/src/test/kotlin/io/bazel/kotlin/BUILD @@ -58,9 +58,9 @@ kt_rules_e2e_test( ) kt_rules_e2e_test( - name = "KotlinJvmFriendsVisibilityTest", - srcs = ["KotlinJvmFriendsVisibilityTest.kt"], - friends = ["//src/test/data/jvm/basic:test_friends_library"], + name = "KotlinJvmAssociatesBasicVisibilityTest", + srcs = ["KotlinJvmAssociatesBasicVisibilityTest.kt"], + associates = ["//src/test/data/jvm/basic:test_associates_library"], ) kt_rules_e2e_test( @@ -72,9 +72,9 @@ test_suite( name = "assertion_tests", tests = [ "KotlinJvm13Test", + "KotlinJvmAssociatesBasicVisibilityTest", "KotlinJvmBasicAssertionTest", "KotlinJvmDaggerExampleTest", - "KotlinJvmFriendsVisibilityTest", "KotlinJvmKaptAssertionTest", ], ) diff --git a/src/test/kotlin/io/bazel/kotlin/KotlinJvmFriendsVisibilityTest.kt b/src/test/kotlin/io/bazel/kotlin/KotlinJvmAssociatesBasicVisibilityTest.kt similarity index 58% rename from src/test/kotlin/io/bazel/kotlin/KotlinJvmFriendsVisibilityTest.kt rename to src/test/kotlin/io/bazel/kotlin/KotlinJvmAssociatesBasicVisibilityTest.kt index 32d71a936..84edcc504 100644 --- a/src/test/kotlin/io/bazel/kotlin/KotlinJvmFriendsVisibilityTest.kt +++ b/src/test/kotlin/io/bazel/kotlin/KotlinJvmAssociatesBasicVisibilityTest.kt @@ -4,10 +4,10 @@ import test.DEFAULT_FRIEND import test.Service /** - * This test validates that friend visibility is working. Services and DEFAULT_FRIEND are internal another compilation - * unit. + * This test validates that internal visibility is working. Services and DEFAULT_FRIEND are + * internal another compilation unit. */ -class KotlinJvmFriendsVisibilityTest { +class KotlinJvmAssociatesBasicVisibilityTest { val service: Service = Service() @org.junit.Test