Skip to content

Commit

Permalink
Add support for friend declarations of a library or test on another c…
Browse files Browse the repository at this point in the history
…ompilation unit, causing it to (a) share a module definition, and (b) have access to the internal members of that library. This expands the current feature which exposes this to test only, and generalizes it.

Also, the dagger example is partitioned into smaller pieces, to illustrate and validate the use of friend declarations, directly and transitively.
  • Loading branch information
cgruber committed May 6, 2020
1 parent 556b3b7 commit 0c86951
Show file tree
Hide file tree
Showing 22 changed files with 318 additions and 52 deletions.
12 changes: 10 additions & 2 deletions .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ tasks:
- "//:rules_kotlin_release"
test_targets:
- "//:all_tests"
- "//examples/dagger/..."
- "//examples/plugin/..."
ubuntu1804:
test_targets:
- "//:all_tests"
build_targets:
- "//:rules_kotlin_release"
test_targets:
- "//:all_tests"
- "//examples/dagger/..."
- "//examples/plugin/..."
rbe_ubuntu1604:
test_targets:
- "--"
Expand All @@ -20,13 +24,17 @@ tasks:
# execution compatible, do not run them for now.
- "//src/test/kotlin/io/bazel/kotlin:KotlinJvmFriendsVisibilityTest"
- "//src/test/kotlin/io/bazel/kotlin:KotlinJvmBasicAssertionTest"
- "//examples/dagger/..."
- "//examples/plugin/..."
test_flags:
# Override the default worker strategy for remote builds (worker strategy
# cannot be used with remote builds)
- "--strategy=KotlinCompile=remote"
macos:
test_targets:
- "//:all_tests"
- "//examples/dagger/..."
- "//examples/plugin/..."
example-android:
name: "Example - Android"
platform: ubuntu1804
Expand Down
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ in your `WORKSPACE` file (or import from a `.bzl` file:
```python
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories")

KOTLIN_VERSION = "1.3.31"
KOTLINC_RELEASE_SHA = "107325d56315af4f59ff28db6837d03c2660088e3efeb7d4e41f3e01bb848d6a"
KOTLIN_VERSION = "1.3.50"
KOTLINC_RELEASE_SHA = "69424091a6b7f52d93eed8bba2ace921b02b113dbb71388d704f8180a6bdc6ec"

KOTLINC_RELEASE = {
"urls": [
Expand Down Expand Up @@ -161,6 +161,43 @@ kotlin_repositories() # if you want the default. Otherwise see custom kotlinc di
kt_register_toolchains() # to use the default toolchain, otherwise see toolchains below
```

## Friends/Internal support

The rules support kotlin `internal` visibility (usually accessible only to the compilation unit)
by allowing a library or test to specify that it is friends with another library like so:

```
kt_jvm_library(
name = "foo",
srcs = glob(["*.kt"]),
friend = "//some/other:target",
# ...
)
```

> Note: declaring friends of a kt_android_library requires adding `_kt` to the target, e.g.
> `//some/other:target_kt`. This is because the `kt_android_library` rule is a macro with a
> `kt_jvm_library` under the hood, and the surfaced rule is an android rule which doesn't have
> kotlin aware bazel providers. This will be fixed in the future.
This grants access to `internal` members of `//some/other:target`. A library can only be friends
with one other library, which must be a kotlin-aware target. It inherits the module name from that
library. Only one friend can be declared, because of the assumptions made here about module
membership. Since friendship can be transitive (see below), this constrains the visibility so it
does not become an arbitrarily growing lattice of trust, defeating the purpose.

Very common use-cases for this are:

* tests and test-libraries depending on internals of the system under test.
* clusters of targets that represent one logical unit, with public, private,
fake, testing-utilities, configuration, or other targets that comprise the
whole unit.

Friendship has limited transitivity. Consider projects `C`, `B`, and `A` with a dependency
relationship `C->B->A`. If `C` declares friendship in `B`, and `B` declares friendship with `A`,
then they are all treated as logically one module for `internal` purposes. However it doesn't
skip. Once the line of friendship is broken, a separate module is presumed by kotlinc.

# Kotlin compiler plugins

The `kt_compiler_plugin` rule allows running Kotlin compiler plugins, such as no-arg, sam-with-receiver and allopen.
Expand Down
21 changes: 21 additions & 0 deletions examples/dagger/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2018 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.
package(default_visibility = ["//examples/dagger:__subpackages__"])

java_binary(
name = "coffee_app",
main_class = "coffee.CoffeeApp",
visibility = ["//visibility:public"],
runtime_deps = ["//examples/dagger/src/coffee"],
)
16 changes: 7 additions & 9 deletions examples/dagger/BUILD → examples/dagger/src/coffee/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,18 @@ rm TeaPot.kt
)

kt_jvm_library(
name = "coffee_lib",
srcs = glob(["src/**"]) + [
name = "coffee",
srcs = glob(["*.kt"]) + [
# Adding a file ending with .srcjar is how code generation patterns are implemented.
":tea_lib_src",
],
friend = "//examples/dagger/src/pumping",
visibility = ["//examples/dagger:__subpackages__"],
deps = [
"//examples/dagger/src/heating",
"//examples/dagger/src/pumping",
"//examples/dagger/src/time",
"//third_party:dagger",
"@kotlin_rules_maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core",
],
)

java_binary(
name = "coffee_app",
main_class = "coffee.CoffeeApp",
visibility = ["//visibility:public"],
runtime_deps = [":coffee_lib"],
)
7 changes: 6 additions & 1 deletion examples/dagger/src/coffee/CoffeeMaker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
package coffee

import dagger.Lazy
import heating.Heater
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
import pumping.Pump

class CoffeeMaker @Inject internal constructor(
// Create a possibly costly heater only when we use it.
Expand All @@ -34,5 +36,8 @@ class CoffeeMaker @Inject internal constructor(
println(" [_]P coffee! [_]P ")
heater.get().off()
}
withContext(Dispatchers.Default) {
if (heater.get().isOn) throw IllegalStateException("Heater should be off")
}
}
}
21 changes: 18 additions & 3 deletions examples/dagger/src/coffee/DripCoffeeModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,30 @@
*/
package coffee

import dagger.Binds
import dagger.Module
import dagger.Provides
import heating.ElectricHeater
import heating.Heater
import javax.inject.Singleton
import pumping.PumpModule
import time.Delayer

@Module(includes = arrayOf(PumpModule::class))
@Module(includes = arrayOf(PumpModule::class, Bindings::class))
internal class DripCoffeeModule {
@Provides
@Singleton
fun provideHeater(): Heater {
return ElectricHeater()
fun provideDelayer(): Delayer {
return object : Delayer {
override fun delay() {
Thread.sleep(1000)
}
}
}
}

@Module
internal abstract class Bindings {
@Binds @Singleton
internal abstract fun bindHeater(heater: ElectricHeater): Heater
}
24 changes: 24 additions & 0 deletions examples/dagger/src/heating/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2019 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:kotlin.bzl", "kt_jvm_library")

kt_jvm_library(
name = "heating",
srcs = glob(["*.kt"]),
visibility = ["//examples/dagger:__subpackages__"],
deps = [
"//examples/dagger/src/time",
"//third_party:dagger",
],
)
40 changes: 40 additions & 0 deletions examples/dagger/src/heating/ElectricHeater.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2018 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.
*/
package heating

import javax.inject.Inject
import time.Delayer

class ElectricHeater
@Inject constructor(private val delayer: Delayer) : Heater() {

override var isHot: Boolean = false
override var isOn: Boolean = false

override fun on() {
isOn = true
println("~ ~ ~ heating ~ ~ ~")
delayer.delay()
this.isHot = true
}

override fun off() {
this.isOn = false
println("~ ~ ~ cooling ~ ~ ~")
delayer.delay()
this.isHot = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee
package heating

internal interface Heater {
val isHot: Boolean
fun on()
fun off()
abstract class Heater {
abstract val isHot: Boolean

internal abstract val isOn: Boolean

abstract fun on()
abstract fun off()
}
26 changes: 26 additions & 0 deletions examples/dagger/src/pumping/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2019 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:kotlin.bzl", "kt_jvm_library")

kt_jvm_library(
name = "pumping",
srcs = glob(["*.kt"]),
visibility = ["//examples/dagger:__subpackages__"],
friend = "//examples/dagger/src/heating",
deps = [
"//examples/dagger/src/heating",
"//examples/dagger/src/time",
"//third_party:dagger",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee
package pumping

internal interface Pump {
fun pump()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee
package pumping

import dagger.Binds
import dagger.Module

@Module
internal abstract class PumpModule {
abstract class PumpModule {
@Binds
internal abstract fun providePump(pump: Thermosiphon): Pump
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee
package pumping

import heating.Heater
import javax.inject.Inject
import time.Delayer

internal class Thermosiphon @Inject
constructor(private val heater: Heater) : Pump {
internal class Thermosiphon
@Inject constructor(private val heater: Heater, private val delayer: Delayer) :
Pump {

override fun pump() {
if (heater.isHot) {
println("=> => pumping => =>")
delayer.delay()
}
}
}
20 changes: 20 additions & 0 deletions examples/dagger/src/time/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2019 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:kotlin.bzl", "kt_jvm_library")

kt_jvm_library(
name = "time",
srcs = glob(["*.kt"]),
visibility = ["//examples/dagger:__subpackages__"],
)
Loading

0 comments on commit 0c86951

Please sign in to comment.