Merge pull request #72 from bazelbuild/friends
Friends support
diff --git a/README.md b/README.md
index 735467c..2c7b8a0 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,9 @@
[Skydoc documentation](https://bazelbuild.github.io/rules_kotlin)
# Announcements
-* <b>February 15, 2018.</b>. Toolchains for the JVM rules. Currently this allow tweaking:
+* <b>May 25, 2018.<b> Test "friend" support. A single friend dep can be provided to `kt_jvm_test` which allows the test
+ to access internal members of the module under test.
+* <b>February 15, 2018.</b> Toolchains for the JVM rules. Currently this allow tweaking:
* The JVM target (bytecode level).
* API and Language levels.
* Coroutines, enabled by default.
diff --git a/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar b/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
index 97f045d..3ffaa7c 100755
--- a/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
+++ b/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
Binary files differ
diff --git a/kotlin/builder/proto/kotlin_model.proto b/kotlin/builder/proto/kotlin_model.proto
index 7fdfe16..30ddb01 100644
--- a/kotlin/builder/proto/kotlin_model.proto
+++ b/kotlin/builder/proto/kotlin_model.proto
@@ -69,6 +69,9 @@
// derived from plugins
repeated string encoded_plugin_descriptors=9;
+
+ // friend jars -- kotlin compiler allows internal visibility access to these jars. Used for tests.
+ repeated string friend_paths = 10;
}
message Outputs {
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt b/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt
index b31b31d..58a46d0 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt
@@ -114,8 +114,11 @@
with(root.infoBuilder) {
label = argMap.mandatorySingle(JavaBuilderFlags.TARGET_LABEL.flag)
ruleKind = argMap.mandatorySingle(JavaBuilderFlags.RULE_KIND.flag)
- kotlinModuleName = argMap.optionalSingle("--kotlin_module_name")
+ kotlinModuleName = argMap.mandatorySingle("--kotlin_module_name").also {
+ check(it.isNotBlank()) { "--kotlin_module_name should not be blank" }
+ }
passthroughFlags = argMap.optionalSingle("--kotlin_passthrough_flags")
+ addAllFriendPaths(argMap.mandatory("--kotlin_friend_paths"))
toolchainInfoBuilder.commonBuilder.apiVersion = argMap.mandatorySingle("--kotlin_api_version")
toolchainInfoBuilder.commonBuilder.languageVersion = argMap.mandatorySingle("--kotlin_language_version")
toolchainInfoBuilder.jvmBuilder.jvmTarget = argMap.mandatorySingle("--kotlin_jvm_target")
@@ -136,10 +139,6 @@
`package` = it[0]
target = it[1]
}
-
- kotlinModuleName = kotlinModuleName.supplyIfNullOrBlank {
- "${`package`.trimStart { it == '/' }.replace('/', '_')}-$target"
- }
}
root.build()
}
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinCompiler.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinCompiler.kt
index d8b9042..dbff055 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinCompiler.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinCompiler.kt
@@ -59,11 +59,14 @@
private fun setupCompileContext(command: KotlinModel.BuilderCommand): MutableList<String> {
val args = mutableListOf<String>()
+ // use -- for flags not meant for the kotlin compiler
args.addAll(
"-cp", command.inputs.joinedClasspath,
"-api-version", command.info.toolchainInfo.common.apiVersion,
"-language-version", command.info.toolchainInfo.common.languageVersion,
- "-jvm-target", command.info.toolchainInfo.jvm.jvmTarget
+ "-jvm-target", command.info.toolchainInfo.jvm.jvmTarget,
+ // https://github.com/bazelbuild/rules_kotlin/issues/69: remove once jetbrains adds a flag for it.
+ "--friend-paths", command.info.friendPathsList.joinToString(":")
)
args
diff --git a/kotlin/builder/src/io/bazel/kotlin/compiler/BazelK2JVMCompiler.kt b/kotlin/builder/src/io/bazel/kotlin/compiler/BazelK2JVMCompiler.kt
index b42af31..e4d104d 100644
--- a/kotlin/builder/src/io/bazel/kotlin/compiler/BazelK2JVMCompiler.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/compiler/BazelK2JVMCompiler.kt
@@ -16,10 +16,40 @@
package io.bazel.kotlin.compiler
import org.jetbrains.kotlin.cli.common.ExitCode
+import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
+import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
+import org.jetbrains.kotlin.config.Services
+@Suppress("unused")
class BazelK2JVMCompiler(private val delegate: K2JVMCompiler = K2JVMCompiler()) {
+ private lateinit var friendsPaths: Array<String>
+
+ private fun preprocessArgs(args: Array<out String>): Array<out String> {
+ val tally = mutableListOf<String>()
+ var i =0
+ do {
+ when {
+ // https://github.com/bazelbuild/rules_kotlin/issues/69: remove once jetbrains adds a flag for it.
+ args[i].startsWith("--friend-paths") -> {
+ i++
+ friendsPaths = args[i].split(":").toTypedArray()
+ }
+ else -> tally += args[i]
+ }
+ i++
+ } while(i < args.size)
+ return tally.toTypedArray()
+ }
+
fun exec(errStream: java.io.PrintStream, vararg args: kotlin.String): ExitCode {
- return delegate.exec(errStream, *args)
+ val arguments = delegate.createArguments().also {
+ delegate.parseArguments(preprocessArgs(args), it)
+ if(::friendsPaths.isInitialized) {
+ it.friendPaths = friendsPaths
+ }
+ }
+ val collector = PrintingMessageCollector(errStream, MessageRenderer.PLAIN_RELATIVE_PATHS, arguments.verbose)
+ return delegate.exec(collector, Services.EMPTY, arguments)
}
}
\ No newline at end of file
diff --git a/kotlin/internal/compile.bzl b/kotlin/internal/compile.bzl
index 1fa2ee9..6cabade 100644
--- a/kotlin/internal/compile.bzl
+++ b/kotlin/internal/compile.bzl
@@ -18,7 +18,7 @@
_src_file_types = FileType([".java", ".kt"])
_srcjar_file_type = FileType([".srcjar"])
-def _kotlin_do_compile_action(ctx, rule_kind, output_jar, compile_jars):
+def _kotlin_do_compile_action(ctx, rule_kind, output_jar, compile_jars, module_name, friend_paths):
"""Internal macro that sets up a Kotlin compile action.
This macro only supports a single Kotlin compile operation for a rule.
@@ -47,10 +47,11 @@
"--output", output_jar.path,
"--output_jdeps", ctx.outputs.jdeps.path,
"--classpath", "\n".join([f.path for f in compile_jars.to_list()]),
+ "--kotlin_friend_paths", "\n".join(friend_paths.to_list()),
"--kotlin_jvm_target", tc.jvm_target,
"--kotlin_api_version", tc.api_version,
"--kotlin_language_version", tc.language_version,
- "--kotlin_module_name", getattr(ctx.attr, "module_name", ""),
+ "--kotlin_module_name", module_name,
"--kotlin_passthrough_flags", "-Xcoroutines=%s" % tc.coroutines
]
@@ -98,7 +99,7 @@
def _select_std_libs(ctx):
return ctx.files._kotlin_std
-def _make_java_provider(ctx, auto_deps=[]):
+def _make_java_provider(ctx, input_deps=[], auto_deps=[]):
"""Creates the java_provider for a Kotlin target.
This macro is distinct from the kotlin_make_providers as collecting the java_info is useful before the DefaultInfo is
@@ -114,7 +115,7 @@
Returns:
A JavaInfo provider.
"""
- deps=utils.collect_all_jars(ctx.attr.deps)
+ deps=utils.collect_all_jars(input_deps)
exported_deps=utils.collect_all_jars(getattr(ctx.attr, "exports", []))
my_compile_jars = exported_deps.compile_jars + [ctx.outputs.jar]
@@ -140,9 +141,10 @@
transitive_runtime_jars=my_transitive_runtime_jars
)
-def _make_providers(ctx, java_info, transitive_files=depset(order="default")):
+def _make_providers(ctx, java_info, module_name, transitive_files=depset(order="default")):
kotlin_info=kt.info.KtInfo(
srcs=ctx.files.srcs,
+ module_name = module_name,
# intelij aspect needs this.
outputs = struct(
jdeps = ctx.outputs.jdeps,
@@ -167,7 +169,7 @@
providers=[java_info,default_info,kotlin_info],
)
-def _compile_action (ctx, rule_kind):
+def _compile_action(ctx, rule_kind, module_name, friend_paths=depset()):
"""Setup a kotlin compile action.
Args:
@@ -196,12 +198,16 @@
kotlin_auto_deps=_select_std_libs(ctx)
+ deps = ctx.attr.deps + getattr(ctx.attr, "friends", [])
+
# setup the compile action.
_kotlin_do_compile_action(
ctx,
rule_kind = rule_kind,
output_jar = kt_compile_output_jar,
- compile_jars = utils.collect_jars_for_compile(ctx.attr.deps) + kotlin_auto_deps
+ compile_jars = utils.collect_jars_for_compile(deps) + kotlin_auto_deps,
+ module_name = module_name,
+ friend_paths = friend_paths
)
# setup the merge action if needed.
@@ -209,7 +215,7 @@
utils.actions.fold_jars(ctx, output_jar, output_merge_list)
# create the java provider but the kotlin and default provider cannot be created here.
- return _make_java_provider(ctx, kotlin_auto_deps)
+ return _make_java_provider(ctx, deps, kotlin_auto_deps)
compile = struct(
compile_action = _compile_action,
diff --git a/kotlin/internal/kt.bzl b/kotlin/internal/kt.bzl
index 187e077..c660e70 100644
--- a/kotlin/internal/kt.bzl
+++ b/kotlin/internal/kt.bzl
@@ -26,6 +26,7 @@
_KtInfo = provider(
fields = {
"srcs": "the source files. [intelij-aspect]",
+ "module_name": "the module name",
"outputs": "output jars produced by this rule. [intelij-aspect]",
},
)
diff --git a/kotlin/internal/rules.bzl b/kotlin/internal/rules.bzl
index 2647ff0..79ee8b1 100644
--- a/kotlin/internal/rules.bzl
+++ b/kotlin/internal/rules.bzl
@@ -76,10 +76,16 @@
return struct(kt = kotlin_info, providers= [default_info, java_info, kotlin_info])
def kt_jvm_library_impl(ctx):
- return compile.make_providers(ctx, compile.compile_action(ctx, "kt_jvm_library"))
+ module_name=utils.derive_module_name(ctx)
+ return compile.make_providers(
+ ctx,
+ compile.compile_action(ctx, "kt_jvm_library", module_name),
+ module_name,
+ )
def kt_jvm_binary_impl(ctx):
- java_info = compile.compile_action(ctx, "kt_jvm_binary")
+ module_name=utils.derive_module_name(ctx)
+ java_info = compile.compile_action(ctx, "kt_jvm_binary", module_name)
utils.actions.write_launcher(
ctx,
java_info.transitive_runtime_jars,
@@ -89,15 +95,29 @@
return compile.make_providers(
ctx,
java_info,
+ module_name,
depset(
order = "default",
transitive=[java_info.transitive_runtime_jars],
direct=[ctx.executable._java]
- )
+ ),
)
def kt_jvm_junit_test_impl(ctx):
- java_info = compile.compile_action(ctx, "kt_jvm_test")
+ module_name=utils.derive_module_name(ctx)
+ friend_paths=depset()
+
+ friends=getattr(ctx.attr, "friends", [])
+ if len(friends) > 1:
+ fail("only one friend is possible")
+ elif len(friends) == 1:
+ if friends[0][kt.info.KtInfo] == None:
+ fail("only kotlin dependencies can be friends")
+ else:
+ friend_paths += [j.path for j in friends[0][JavaInfo].compile_jars]
+ module_name = friends[0][kt.info.KtInfo].module_name
+
+ java_info = compile.compile_action(ctx, "kt_jvm_test", module_name,friend_paths)
transitive_runtime_jars = java_info.transitive_runtime_jars + ctx.files._bazel_test_runner
launcherJvmFlags = ["-ea", "-Dbazel.test_suite=%s"% ctx.attr.test_class]
@@ -111,9 +131,10 @@
return compile.make_providers(
ctx,
java_info,
+ module_name,
depset(
order = "default",
transitive=[transitive_runtime_jars],
direct=[ctx.executable._java]
- )
+ ),
)
\ No newline at end of file
diff --git a/kotlin/internal/utils.bzl b/kotlin/internal/utils.bzl
index 7b6ce28..d7890a4 100644
--- a/kotlin/internal/utils.bzl
+++ b/kotlin/internal/utils.bzl
@@ -24,6 +24,12 @@
lbl = lbl.replace("external/", "@")
return lbl + "//" + l.package + ":" + l.name
+def _derive_module_name(ctx):
+ module_name=getattr(ctx.attr, "module_name", "")
+ if module_name == "":
+ module_name = (ctx.label.package.lstrip("/").replace("/","_") + "-" + ctx.label.name.replace("/", "_"))
+ return module_name
+
# DEPSET UTILS #################################################################################################################################################
def _select_compile_jars(dep):
"""selects the correct compile time jar from a java provider"""
@@ -207,4 +213,5 @@
collect_all_jars = _collect_all_jars,
collect_jars_for_compile = _collect_jars_for_compile,
restore_label = _restore_label,
+ derive_module_name = _derive_module_name
)
diff --git a/kotlin/kotlin.bzl b/kotlin/kotlin.bzl
index e8b3c5d..86248a8 100644
--- a/kotlin/kotlin.bzl
+++ b/kotlin/kotlin.bzl
@@ -301,7 +301,9 @@
"""
kt_jvm_binary = rule(
- attrs = dict(_runnable_common_attr.items() + {"main_class": attr.string(mandatory = True)}.items()),
+ attrs = dict(_runnable_common_attr.items() + {
+ "main_class": attr.string(mandatory = True)
+ }.items()),
executable = True,
outputs = _binary_outputs,
toolchains = [_kt.defs.TOOLCHAIN_TYPE],
@@ -325,6 +327,9 @@
default = Label("@bazel_tools//tools/jdk:TestRunner_deploy.jar"),
allow_files = True,
),
+ "friends": attr.label_list(
+ default = [],
+ ),
"test_class": attr.string(),
"main_class": attr.string(default="com.google.testing.junit.runner.BazelTestRunner"),
}.items()),
@@ -342,6 +347,8 @@
Args:
test_class: The Java class to be loaded by the test runner.
+ friends: A single Kotlin dep which allows the test code access to internal members. Currently uses the output jar of
+ the module -- i.e., exported deps won't be included.
"""
kt_jvm_import = rule(
diff --git a/tests/integrationtests/BUILD b/tests/integrationtests/BUILD
index c0356b1..e985ee0 100644
--- a/tests/integrationtests/BUILD
+++ b/tests/integrationtests/BUILD
@@ -18,5 +18,6 @@
tests=[
"//tests/integrationtests/jvm:basic_tests",
"//tests/integrationtests/jvm:annoation_processing_tests",
+ "//tests/integrationtests/jvm/basic:test_friends_tests"
]
)
\ No newline at end of file
diff --git a/tests/integrationtests/jvm/basic/BUILD b/tests/integrationtests/jvm/basic/BUILD
index 6b200b9..29baf44 100644
--- a/tests/integrationtests/jvm/basic/BUILD
+++ b/tests/integrationtests/jvm/basic/BUILD
@@ -98,6 +98,22 @@
deps = [":propagation_test_runtime_lib"]
)
+kt_jvm_library(
+ name = "test_friends_library",
+ srcs = ["test_friends/Service.kt"]
+)
+
+# This test should be explicetly executed as module name mangling handling could regress otherwise.
+kt_jvm_test(
+ name = "test_friends_tests",
+ srcs = ["test_friends/TestFriendsTest.kt"],
+ test_class = "test.TestFriendsTest",
+ deps = [
+ "//third_party/jvm/junit:junit"
+ ],
+ friends = [":test_friends_library"]
+)
+
filegroup(
name="cases",
srcs = [
@@ -115,3 +131,4 @@
],
visibility=["//tests/integrationtests:__subpackages__"]
)
+
diff --git a/tests/integrationtests/jvm/basic/test_friends/Service.kt b/tests/integrationtests/jvm/basic/test_friends/Service.kt
new file mode 100644
index 0000000..276c2f0
--- /dev/null
+++ b/tests/integrationtests/jvm/basic/test_friends/Service.kt
@@ -0,0 +1,11 @@
+package test
+
+internal const val DEFAULT_FRIEND = "muchacho"
+
+class Service internal constructor(
+ internal val value: String = "hello world"
+) {
+ internal fun iSayHolla(friend: String) {
+ println("holla $friend")
+ }
+}
\ No newline at end of file
diff --git a/tests/integrationtests/jvm/basic/test_friends/TestFriendsTest.kt b/tests/integrationtests/jvm/basic/test_friends/TestFriendsTest.kt
new file mode 100644
index 0000000..ad25e2c
--- /dev/null
+++ b/tests/integrationtests/jvm/basic/test_friends/TestFriendsTest.kt
@@ -0,0 +1,13 @@
+package test
+
+import org.junit.Test
+
+class TestFriendsTest {
+ val service: Service = Service()
+
+ @Test
+ fun testCanAccessFriendMembers() {
+ println(service.value)
+ println(service.iSayHolla(DEFAULT_FRIEND))
+ }
+}
\ No newline at end of file