Skip to content

Commit 3cb79cf

Browse files
authored
Redesign API for better usage in projects (#49)
### 1. Single library entrypoint Migrate library API from a few top-level variables and functions to a single entrypoint interface called `GradleEnterpriseApi`. The current setup isn't great because one has to remember symbol names without much help from autocomplete. A single interface results in a better experience with autocomplete, especially considering the limited Jupyter and Jupyter Lab environments. This also improves use in projects by removing broadly-named symbols such as `options`, which could be easily confused with anything else the project does: ```kotlin class MyProjectCliClass { fun startup() { // "Options of what? Which library? Our CLI is caching something?" options.cache.cacheEnabled = true } } ``` Faking `shutdown` and other library methods is now supported. ### 2. Redesign library options Replace the cumbersome global, var-based `Options` with a (mostly) immutable version. Previously, one had to ensure changing the vars before the first time the `gradleEnterpriseApi` global instance is accessed. Now, each `GradleEnterpriseApi` instance contains an immutable set of options and one can obtain a copy with different settings at any time. ### 3. Split API service interface to match Gradle's docs Replace the current Retrofit service interface with four others, also generated, to match Gradle's docs. The docs group endpoints in four: - Builds - Build cache - Meta - Test Distribution Splitting it in smaller interfaces can also make it simpler to fake the libraries APIs for testing.
1 parent 1380430 commit 3cb79cf

File tree

32 files changed

+470
-577
lines changed

32 files changed

+470
-577
lines changed

.github/workflows/pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: gradle check
1818
uses: ./.github/actions/build
1919
with:
20-
args: 'check'
20+
args: 'check compileIntegrationTestKotlin'
2121
# artifact-name: 'Test reports (${{matrix.runner}})'
2222
# path-to-upload: '**/build/reports/tests/**'
2323

examples/example-notebooks/MostFrequentBuilds.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@
153153
"\n",
154154
"val builds: List<GradleAttributes> = runBlocking {\n",
155155
" val startMilli = startDate.atStartOfDay(ZoneId.of(\"UTC\")).toInstant().toEpochMilli()\n",
156-
" gradleEnterpriseApi.getGradleAttributesFlow(since = startMilli)\n",
156+
" GradleEnterprise.api.getGradleAttributesFlow(since = startMilli)\n",
157157
" .filter(buildFilter)\n",
158158
" .printProgress { i, build ->\n",
159159
" val buildDate = Instant.ofEpochMilli(build.buildStartTime).atOffset(ZoneOffset.UTC)\n",
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.gabrielfeo.gradle.enterprise.api.example
22

3+
import com.gabrielfeo.gradle.enterprise.api.GradleEnterpriseApi
34
import com.gabrielfeo.gradle.enterprise.api.example.analysis.mostFrequentBuilds
4-
import com.gabrielfeo.gradle.enterprise.api.gradleEnterpriseApi
5-
import com.gabrielfeo.gradle.enterprise.api.options
6-
import com.gabrielfeo.gradle.enterprise.api.shutdown
75
import okhttp3.OkHttpClient
86

97
/*
@@ -15,11 +13,14 @@ import okhttp3.OkHttpClient
1513
val clientBuilder = OkHttpClient.Builder()
1614

1715
suspend fun main() {
18-
options.httpClient.clientBuilder = { clientBuilder }
19-
runAllAnalysis()
20-
shutdown()
16+
val newOptions = GradleEnterpriseApi.options.copy(
17+
clientBuilder = clientBuilder,
18+
)
19+
val gradleEnterpriseApi = GradleEnterpriseApi.withOptions(newOptions)
20+
runAllAnalysis(gradleEnterpriseApi)
21+
gradleEnterpriseApi.shutdown()
2122
}
2223

23-
private suspend fun runAllAnalysis() {
24-
mostFrequentBuilds(api = gradleEnterpriseApi)
24+
private suspend fun runAllAnalysis(gradleEnterpriseApi: GradleEnterpriseApi) {
25+
mostFrequentBuilds(api = gradleEnterpriseApi.buildsApi)
2526
}

examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import java.util.LinkedList
2222
* legacy tests. We should suggest they run test instead, leaving check for CI to run."
2323
*/
2424
suspend fun mostFrequentBuilds(
25-
api: GradleEnterpriseApi,
25+
api: BuildsApi,
2626
startDate: LocalDate = LocalDate.now().minusWeeks(1),
2727
buildFilter: (GradleAttributes) -> Boolean = { build ->
2828
"LOCAL" in build.tags
@@ -63,6 +63,7 @@ suspend fun mostFrequentBuilds(
6363

6464

6565
// A utility to print progress as builds are fetched. You may ignore this.
66+
@OptIn(DelicateCoroutinesApi::class)
6667
fun <T> Flow<T>.printProgress(produceMsg: (i: Int, current: T) -> String): Flow<T> {
6768
var i = -1
6869
var current: T? = null

examples/example-script.main.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fun <T> Flow<T>.printProgress(produceMsg: (i: Int, current: T) -> String): Flow<
5858
// Fetch builds from the API
5959
val builds: List<GradleAttributes> = runBlocking {
6060
val startMilli = startDate.atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli()
61-
gradleEnterpriseApi.getGradleAttributesFlow(since = startMilli)
61+
GradleEnterprise.api.getGradleAttributesFlow(since = startMilli)
6262
.filter(buildFilter)
6363
.printProgress { i, build ->
6464
val buildDate = Instant.ofEpochMilli(build.buildStartTime).atOffset(ZoneOffset.UTC)

library/.openapi-generator-ignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,5 @@ build/generated/openapi-generator/gradle/**/*
55
build/generated/openapi-generator/docs/*
66
build/generated/openapi-generator/src/main/AndroidManifest.xml
77
build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/infrastructure/ApiClient.kt
8-
build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/BuildsApi.kt
9-
build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/BuildCacheApi.kt
10-
build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/MetaApi.kt
11-
build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/TestDistributionApi.kt
8+
build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt
129
build/generated/openapi-generator/src/test/**/*

library/build.gradle.kts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,45 +56,45 @@ openApiGenerate {
5656
}
5757

5858
tasks.openApiGenerate.configure {
59+
val srcDir = File(outputDir.get(), "src/main/kotlin")
5960
doFirst {
6061
logger.info("Using API spec ${inputSpec.get()}")
6162
}
6263
// Replace Response<X> with X in every method return type of GradleEnterpriseApi.kt
6364
doLast {
64-
val apiFile = File(
65-
outputDir.get(),
66-
"src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt",
67-
)
6865
ant.withGroovyBuilder {
6966
"replaceregexp"(
70-
"file" to apiFile,
7167
"match" to ": Response<(.*?)>$",
7268
"replace" to """: \1""",
7369
"flags" to "gm",
74-
)
70+
) {
71+
"fileset"(
72+
"dir" to srcDir,
73+
"includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt",
74+
)
75+
}
7576
}
7677
}
7778
// Add @JvmSuppressWildcards to avoid square/retrofit#3275
7879
doLast {
79-
val apiFile = File(
80-
outputDir.get(),
81-
"src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt",
82-
)
8380
ant.withGroovyBuilder {
8481
"replaceregexp"(
85-
"file" to apiFile,
86-
"match" to "interface GradleEnterpriseApi",
82+
"match" to "interface",
8783
"replace" to """
8884
@JvmSuppressWildcards
89-
interface GradleEnterpriseApi
85+
interface
9086
""".trimIndent(),
9187
"flags" to "m",
92-
)
88+
) {
89+
"fileset"(
90+
"dir" to srcDir,
91+
"includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt",
92+
)
93+
}
9394
}
9495
}
9596
// Workaround for properties generated with `arrayListOf(null,null)` as default value
9697
doLast {
97-
val srcDir = File(outputDir.get(), "src/main/kotlin")
9898
ant.withGroovyBuilder {
9999
"replaceregexp"(
100100
"match" to """arrayListOf\(null,null\)""",
@@ -109,7 +109,6 @@ tasks.openApiGenerate.configure {
109109
}
110110
// Workaround for missing imports of exploded queries
111111
doLast {
112-
val srcDir = File(outputDir.get(), "src/main/kotlin")
113112
val modelPackage = openApiGenerate.modelPackage.get()
114113
val modelPackagePattern = modelPackage.replace(".", "\\.")
115114
ant.withGroovyBuilder {
@@ -193,7 +192,7 @@ testing {
193192
}
194193
register<JvmTestSuite>("integrationTest") {
195194
dependencies {
196-
implementation(project())
195+
// implementation(project())
197196
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1")
198197
}
199198
}
@@ -203,6 +202,14 @@ testing {
203202
}
204203
}
205204

205+
kotlin {
206+
target {
207+
val main by compilations.getting
208+
val integrationTest by compilations.getting
209+
integrationTest.associateWith(main)
210+
}
211+
}
212+
206213
java {
207214
consistentResolution {
208215
useRuntimeClasspathVersions()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.gabrielfeo.gradle.enterprise.api.internal
2+
3+
import com.gabrielfeo.gradle.enterprise.api.GradleEnterpriseApi
4+
import kotlinx.coroutines.test.runTest
5+
import kotlin.test.Test
6+
import kotlin.test.assertEquals
7+
8+
class GradleEnterpriseApiIntegrationTest {
9+
10+
@Test
11+
fun canFetchBuilds() = runTest {
12+
val builds = GradleEnterpriseApi.buildsApi.getBuilds(since = 0, maxBuilds = 1)
13+
assertEquals(1, builds.size)
14+
GradleEnterpriseApi.shutdown()
15+
}
16+
}

library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/GradleEnterpriseIntegrationTest.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package com.gabrielfeo.gradle.enterprise.api.internal
22

3+
import com.gabrielfeo.gradle.enterprise.api.internal.keychain
34
import kotlin.test.Test
45
import kotlin.test.assertFalse
6+
import kotlin.test.assertIs
57

6-
class KeychainIntegrationTest {
7-
8-
val keychain = RealKeychain(RealSystemProperties)
8+
internal class KeychainIntegrationTest {
99

1010
@Test
1111
fun getApiToken() {
12-
assertFalse(
13-
keychain["gradle-enterprise-api-token"].isNullOrEmpty(),
14-
"Keychain returned null or empty",
15-
)
12+
val result = keychain.get("gradle-enterprise-api-token")
13+
assertIs<KeychainResult.Success>(result)
14+
assertFalse(result.token.isNullOrBlank(), "Keychain returned null or blank")
1615
}
1716
}

0 commit comments

Comments
 (0)