From 8208a93bb697e5ce23e952fc8089e712caa1fcbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:12:55 +0000 Subject: [PATCH 01/35] Bump the coroutines group with 2 updates (#154) Bumps the coroutines group with 2 updates from 1.7.3 to 1.8.0: [org.jetbrains.kotlinx:kotlinx-coroutines-test](https://github.com/Kotlin/kotlinx.coroutines) and [org.jetbrains.kotlinx:kotlinx-coroutines-core](https://github.com/Kotlin/kotlinx.coroutines). Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- library/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 76a30146..1ca8a07c 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -195,12 +195,12 @@ testing { dependencies { implementation("com.squareup.okhttp3:mockwebserver:4.12.0") implementation("com.squareup.okio:okio:3.7.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") } } register("integrationTest") { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") } } withType().configureEach { @@ -248,7 +248,7 @@ dependencies { api("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-moshi:2.9.0") implementation("com.squareup.retrofit2:converter-scalars:2.9.0") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") } publishing { From 3592bc598f2a00de6962ed364974ca8bef383124 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:16:49 +0000 Subject: [PATCH 02/35] Bump org.jetbrains.kotlin.jvm from 1.9.22 to 1.9.23 (#157) Bumps [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) from 1.9.22 to 1.9.23. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- examples/example-project/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 954800a4..81badb0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.jvm") version "1.9.22" apply false + id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false id("org.jetbrains.dokka") version "1.9.10" apply false id("org.openapi.generator") version "7.2.0" apply false } diff --git a/examples/example-project/build.gradle.kts b/examples/example-project/build.gradle.kts index 3039a50c..e3873143 100644 --- a/examples/example-project/build.gradle.kts +++ b/examples/example-project/build.gradle.kts @@ -1,3 +1,3 @@ plugins { - id("org.jetbrains.kotlin.jvm") version "1.9.22" apply false + id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false } From f53b5984107b129772a20e1951c48d83b465a21c Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Thu, 28 Mar 2024 17:04:58 +0000 Subject: [PATCH 03/35] Deprecate keychain support (#164) Keychain support can be removed to simplify the library, considering that there are numerous ways to use an environment variable without storing the key as plain-text. For example, using password manager CLIs or exporting from keychain to variable each time `TOKEN=$(security ...) my-script.main.kts`. --- .../gabrielfeo/gradle/enterprise/api/internal/Keychain.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt index 1fec3fc9..78b4ac10 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt @@ -26,9 +26,15 @@ internal class RealKeychain( if (status != 0) { return KeychainResult.Error("exit $status") } + println(KEYCHAIN_DEPRECATION_WARNING) val token = process.inputStream.bufferedReader().use { it.readText().trim() } return KeychainResult.Success(token) } } + +private const val KEYCHAIN_DEPRECATION_WARNING = + "WARNING: passing token via macOS keychain is deprecated. Please pass it as the " + + "GRADLE_ENTERPRISE_API_TOKEN environment variable instead. Keychain support will be " + + "removed in the next release. See release notes for details and alternatives." From ea396aa4be4c10f42f59c6e4293b4c7382642de7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:08:39 +0000 Subject: [PATCH 04/35] Bump org.jetbrains.dokka from 1.9.10 to 1.9.20 (#156) Bumps [org.jetbrains.dokka](https://github.com/Kotlin/dokka) from 1.9.10 to 1.9.20. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 81badb0d..f1065d0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false - id("org.jetbrains.dokka") version "1.9.10" apply false + id("org.jetbrains.dokka") version "1.9.20" apply false id("org.openapi.generator") version "7.2.0" apply false } From d5185d1c1ad6f433913be5a28e7c8989a30f017c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:00:15 +0000 Subject: [PATCH 05/35] Bump the okio group with 1 update (#160) Bumps the okio group with 1 update: [com.squareup.okio:okio](https://github.com/square/okio). Updates `com.squareup.okio:okio` from 3.7.0 to 3.9.0 Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- library/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 1ca8a07c..9d66edee 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -194,7 +194,7 @@ testing { getByName("test") { dependencies { implementation("com.squareup.okhttp3:mockwebserver:4.12.0") - implementation("com.squareup.okio:okio:3.7.0") + implementation("com.squareup.okio:okio:3.9.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") } } @@ -239,7 +239,7 @@ java { dependencies { constraints { - implementation("com.squareup.okio:okio:3.7.0") + implementation("com.squareup.okio:okio:3.9.0") } api("com.squareup.moshi:moshi:1.15.1") implementation("com.squareup.moshi:moshi-kotlin:1.15.1") From 79812fc819e734d8775f0034542e227afd653afa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:52:32 +0000 Subject: [PATCH 06/35] Bump the retrofit group with 3 updates (#165) Bumps the retrofit group with 3 updates from 2.9.0 to 2.10.0: [com.squareup.retrofit2:retrofit](https://github.com/square/retrofit), [com.squareup.retrofit2:converter-moshi](https://github.com/square/retrofit) and [com.squareup.retrofit2:converter-scalars](https://github.com/square/retrofit). Updates `com.squareup.retrofit2:retrofit` Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- library/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 9d66edee..c6c64992 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -245,9 +245,9 @@ dependencies { implementation("com.squareup.moshi:moshi-kotlin:1.15.1") api("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") - api("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-moshi:2.9.0") - implementation("com.squareup.retrofit2:converter-scalars:2.9.0") + api("com.squareup.retrofit2:retrofit:2.10.0") + implementation("com.squareup.retrofit2:converter-moshi:2.10.0") + implementation("com.squareup.retrofit2:converter-scalars:2.10.0") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") } From 77f604d4d2f80dd7847562838321310f648a2d5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:56:13 +0000 Subject: [PATCH 07/35] Update Gradle Wrapper to 8.7 (#161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update Gradle Wrapper to 8.7. Read the release notes: https://docs.gradle.org/8.7/release-notes.html --- The checksums of the Wrapper JAR and the distribution binary have been successfully verified. - Gradle release: `8.7` - Distribution (-bin) zip checksum: `544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d` - Wrapper JAR Checksum: `cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8` You can find the reference checksum values at https://gradle.org/release-checksums/ --- 🤖 This PR has been created by the [Update Gradle Wrapper](https://github.com/gradle-update/update-gradle-wrapper-action) action.
Need help? 🤔
If something doesn't look right with this PR please file an issue [here](https://github.com/gradle-update/update-gradle-wrapper-action/issues).
--------- Signed-off-by: gradle-update-robot Co-authored-by: gradle-update-robot --- .../gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example-project/gradle/wrapper/gradle-wrapper.jar b/examples/example-project/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 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 delta 34176 zcmX7vV`H6d(}mmEwr$(CZQE$vU^m*aZQE(=WXEZ2+l}qF_w)XN>&rEBu9;)4>7EB0 zo(HR^Mh47P)@z^^pH!4#b(O8!;$>N+S+v5K5f8RrQ+Qv0_oH#e!pI2>yt4ij>fI9l zW&-hsVAQg%dpn3NRy$kb_vbM2sr`>bZ48b35m{D=OqX;p8A${^Dp|W&J5mXvUl#_I zN!~GCBUzj~C%K?<7+UZ_q|L)EGG#_*2Zzko-&Kck)Qd2%CpS3{P1co1?$|Sj1?E;PO z7alI9$X(MDly9AIEZ-vDLhpAKd1x4U#w$OvBtaA{fW9)iD#|AkMrsSaNz(69;h1iM1#_ z?u?O_aKa>vk=j;AR&*V-p3SY`CI}Uo%eRO(Dr-Te<99WQhi>y&l%UiS%W2m(d#woD zW?alFl75!1NiUzVqgqY98fSQNjhX3uZ&orB08Y*DFD;sjIddWoJF;S_@{Lx#SQk+9 zvSQ-620z0D7cy8-u_7u?PqYt?R0m2k%PWj%V(L|MCO(@3%l&pzEy7ijNv(VXU9byn z@6=4zL|qk*7!@QWd9imT9i%y}1#6+%w=s%WmsHbw@{UVc^?nL*GsnACaLnTbr9A>B zK)H-$tB`>jt9LSwaY+4!F1q(YO!E7@?SX3X-Ug4r($QrmJnM8m#;#LN`kE>?<{vbCZbhKOrMpux zTU=02hy${;n&ikcP8PqufhT9nJU>s;dyl;&~|Cs+o{9pCu{cRF+0{iyuH~6=tIZXVd zR~pJBC3Hf-g%Y|bhTuGyd~3-sm}kaX5=T?p$V?48h4{h2;_u{b}8s~Jar{39PnL7DsXpxcX#3zx@f9K zkkrw9s2*>)&=fLY{=xeIYVICff2Id5cc*~l7ztSsU@xuXYdV1(lLGZ5)?mXyIDf1- zA7j3P{C5s?$Y-kg60&XML*y93zrir8CNq*EMx)Kw)XA(N({9t-XAdX;rjxk`OF%4-0x?ne@LlBQMJe5+$Ir{Oj`@#qe+_-z!g5qQ2SxKQy1ex_x^Huj%u+S@EfEPP-70KeL@7@PBfadCUBt%`huTknOCj{ z;v?wZ2&wsL@-iBa(iFd)7duJTY8z-q5^HR-R9d*ex2m^A-~uCvz9B-1C$2xXL#>ow z!O<5&jhbM&@m=l_aW3F>vjJyy27gY}!9PSU3kITbrbs#Gm0gD?~Tub8ZFFK$X?pdv-%EeopaGB#$rDQHELW!8bVt`%?&>0 zrZUQ0!yP(uzVK?jWJ8^n915hO$v1SLV_&$-2y(iDIg}GDFRo!JzQF#gJoWu^UW0#? z*OC-SPMEY!LYY*OO95!sv{#-t!3Z!CfomqgzFJld>~CTFKGcr^sUai5s-y^vI5K={ z)cmQthQuKS07e8nLfaIYQ5f}PJQqcmokx?%yzFH*`%k}RyXCt1Chfv5KAeMWbq^2MNft;@`hMyhWg50(!jdAn;Jyx4Yt)^^DVCSu?xRu^$*&&=O6#JVShU_N3?D)|$5pyP8A!f)`| z>t0k&S66T*es5(_cs>0F=twYJUrQMqYa2HQvy)d+XW&rai?m;8nW9tL9Ivp9qi2-` zOQM<}D*g`28wJ54H~1U!+)vQh)(cpuf^&8uteU$G{9BUhOL| zBX{5E1**;hlc0ZAi(r@)IK{Y*ro_UL8Ztf8n{Xnwn=s=qH;fxkK+uL zY)0pvf6-iHfX+{F8&6LzG;&d%^5g`_&GEEx0GU=cJM*}RecV-AqHSK@{TMir1jaFf&R{@?|ieOUnmb?lQxCN!GnAqcii9$ z{a!Y{Vfz)xD!m2VfPH=`bk5m6dG{LfgtA4ITT?Sckn<92rt@pG+sk>3UhTQx9ywF3 z=$|RgTN<=6-B4+UbYWxfQUOe8cmEDY3QL$;mOw&X2;q9x9qNz3J97)3^jb zdlzkDYLKm^5?3IV>t3fdWwNpq3qY;hsj=pk9;P!wVmjP|6Dw^ez7_&DH9X33$T=Q{>Nl zv*a*QMM1-2XQ)O=3n@X+RO~S`N13QM81^ZzljPJIFBh%x<~No?@z_&LAl)ap!AflS zb{yFXU(Uw(dw%NR_l7%eN2VVX;^Ln{I1G+yPQr1AY+0MapBnJ3k1>Zdrw^3aUig*! z?xQe8C0LW;EDY(qe_P!Z#Q^jP3u$Z3hQpy^w7?jI;~XTz0ju$DQNc4LUyX}+S5zh> zGkB%~XU+L?3pw&j!i|x6C+RyP+_XYNm9`rtHpqxvoCdV_MXg847oHhYJqO+{t!xxdbsw4Ugn($Cwkm^+36&goy$vkaFs zrH6F29eMPXyoBha7X^b+N*a!>VZ<&Gf3eeE+Bgz7PB-6X7 z_%2M~{sTwC^iQVjH9#fVa3IO6E4b*S%M;#WhHa^L+=DP%arD_`eW5G0<9Tk=Ci?P@ z6tJXhej{ZWF=idj32x7dp{zmQY;;D2*11&-(~wifGXLmD6C-XR=K3c>S^_+x!3OuB z%D&!EOk;V4Sq6eQcE{UEDsPMtED*;qgcJU^UwLwjE-Ww54d73fQ`9Sv%^H>juEKmxN+*aD=0Q+ZFH1_J(*$~9&JyUJ6!>(Nj zi3Z6zWC%Yz0ZjX>thi~rH+lqv<9nkI3?Ghn7@!u3Ef){G(0Pvwnxc&(YeC=Kg2-7z zr>a^@b_QClXs?Obplq@Lq-l5>W);Y^JbCYk^n8G`8PzCH^rnY5Zk-AN6|7Pn=oF(H zxE#8LkI;;}K7I^UK55Z)c=zn7OX_XVgFlEGSO}~H^y|wd7piw*b1$kA!0*X*DQ~O` z*vFvc5Jy7(fFMRq>XA8Tq`E>EF35{?(_;yAdbO8rrmrlb&LceV%;U3haVV}Koh9C| zTZnR0a(*yN^Hp9u*h+eAdn)d}vPCo3k?GCz1w>OOeme(Mbo*A7)*nEmmUt?eN_vA; z=~2}K_}BtDXJM-y5fn^v>QQo+%*FdZQFNz^j&rYhmZHgDA-TH47#Wjn_@iH4?6R{J z%+C8LYIy>{3~A@|y4kN8YZZp72F8F@dOZWp>N0-DyVb4UQd_t^`P)zsCoygL_>>x| z2Hyu7;n(4G&?wCB4YVUIVg0K!CALjRsb}&4aLS|}0t`C}orYqhFe7N~h9XQ_bIW*f zGlDCIE`&wwyFX1U>}g#P0xRRn2q9%FPRfm{-M7;}6cS(V6;kn@6!$y06lO>8AE_!O z{|W{HEAbI0eD$z9tQvWth7y>qpTKQ0$EDsJkQxAaV2+gE28Al8W%t`Pbh zPl#%_S@a^6Y;lH6BfUfZNRKwS#x_keQ`;Rjg@qj zZRwQXZd-rWngbYC}r6X)VCJ-=D54A+81%(L*8?+&r7(wOxDSNn!t(U}!;5|sjq zc5yF5$V!;%C#T+T3*AD+A({T)#p$H_<$nDd#M)KOLbd*KoW~9E19BBd-UwBX1<0h9 z8lNI&7Z_r4bx;`%5&;ky+y7PD9F^;Qk{`J@z!jJKyJ|s@lY^y!r9p^75D)_TJ6S*T zLA7AA*m}Y|5~)-`cyB+lUE9CS_`iB;MM&0fX**f;$n($fQ1_Zo=u>|n~r$HvkOUK(gv_L&@DE0b4#ya{HN)8bNQMl9hCva zi~j0v&plRsp?_zR zA}uI4n;^_Ko5`N-HCw_1BMLd#OAmmIY#ol4M^UjLL-UAat+xA+zxrFqKc@V5Zqan_ z+LoVX-Ub2mT7Dk_ z<+_3?XWBEM84@J_F}FDe-hl@}x@v-s1AR{_YD!_fMgagH6s9uyi6pW3gdhauG>+H? zi<5^{dp*5-9v`|m*ceT&`Hqv77oBQ+Da!=?dDO&9jo;=JkzrQKx^o$RqAgzL{ zjK@n)JW~lzxB>(o(21ibI}i|r3e;17zTjdEl5c`Cn-KAlR7EPp84M@!8~CywES-`mxKJ@Dsf6B18_!XMIq$Q3rTDeIgJ3X zB1)voa#V{iY^ju>*Cdg&UCbx?d3UMArPRHZauE}c@Fdk;z85OcA&Th>ZN%}=VU%3b9={Q(@M4QaeuGE(BbZ{U z?WPDG+sjJSz1OYFpdImKYHUa@ELn%n&PR9&I7B$<-c3e|{tPH*u@hs)Ci>Z@5$M?lP(#d#QIz}~()P7mt`<2PT4oHH}R&#dIx4uq943D8gVbaa2&FygrSk3*whGr~Jn zR4QnS@83UZ_BUGw;?@T zo5jA#potERcBv+dd8V$xTh)COur`TQ^^Yb&cdBcesjHlA3O8SBeKrVj!-D3+_p6%P zP@e{|^-G-C(}g+=bAuAy8)wcS{$XB?I=|r=&=TvbqeyXiuG43RR>R72Ry7d6RS;n^ zO5J-QIc@)sz_l6%Lg5zA8cgNK^GK_b-Z+M{RLYk5=O|6c%!1u6YMm3jJg{TfS*L%2 zA<*7$@wgJ(M*gyTzz8+7{iRP_e~(CCbGB}FN-#`&1ntct@`5gB-u6oUp3#QDxyF8v zOjxr}pS{5RpK1l7+l(bC)0>M;%7L?@6t}S&a zx0gP8^sXi(g2_g8+8-1~hKO;9Nn%_S%9djd*;nCLadHpVx(S0tixw2{Q}vOPCWvZg zjYc6LQ~nIZ*b0m_uN~l{&2df2*ZmBU8dv`#o+^5p>D5l%9@(Y-g%`|$%nQ|SSRm0c zLZV)45DS8d#v(z6gj&6|ay@MP23leodS8-GWIMH8_YCScX#Xr)mbuvXqSHo*)cY9g z#Ea+NvHIA)@`L+)T|f$Etx;-vrE3;Gk^O@IN@1{lpg&XzU5Eh3!w;6l=Q$k|%7nj^ z|HGu}c59-Ilzu^w<93il$cRf@C(4Cr2S!!E&7#)GgUH@py?O;Vl&joXrep=2A|3Vn zH+e$Ctmdy3B^fh%12D$nQk^j|v=>_3JAdKPt2YVusbNW&CL?M*?`K1mK*!&-9Ecp~>V1w{EK(429OT>DJAV21fG z=XP=%m+0vV4LdIi#(~XpaUY$~fQ=xA#5?V%xGRr_|5WWV=uoG_Z&{fae)`2~u{6-p zG>E>8j({w7njU-5Lai|2HhDPntQ(X@yB z9l?NGoKB5N98fWrkdN3g8ox7Vic|gfTF~jIfXkm|9Yuu-p>v3d{5&hC+ZD%mh|_=* zD5v*u(SuLxzX~owH!mJQi%Z=ALvdjyt9U6baVY<88B>{HApAJ~>`buHVGQd%KUu(d z5#{NEKk6Vy08_8*E(?hqZe2L?P2$>!0~26N(rVzB9KbF&JQOIaU{SumX!TsYzR%wB z<5EgJXDJ=1L_SNCNZcBWBNeN+Y`)B%R(wEA?}Wi@mp(jcw9&^1EMSM58?68gwnXF` zzT0_7>)ep%6hid-*DZ42eU)tFcFz7@bo=<~CrLXpNDM}tv*-B(ZF`(9^RiM9W4xC%@ZHv=>w(&~$Wta%)Z;d!{J;e@z zX1Gkw^XrHOfYHR#hAU=G`v43E$Iq}*gwqm@-mPac0HOZ0 zVtfu7>CQYS_F@n6n#CGcC5R%4{+P4m7uVlg3axX}B(_kf((>W?EhIO&rQ{iUO$16X zv{Abj3ZApUrcar7Ck}B1%RvnR%uocMlKsRxV9Qqe^Y_5C$xQW@9QdCcF%W#!zj;!xWc+0#VQ*}u&rJ7)zc+{vpw+nV?{tdd&Xs`NV zKUp|dV98WbWl*_MoyzM0xv8tTNJChwifP!9WM^GD|Mkc75$F;j$K%Y8K@7?uJjq-w zz*|>EH5jH&oTKlIzueAN2926Uo1OryC|CmkyoQZABt#FtHz)QmQvSX35o`f z<^*5XXxexj+Q-a#2h4(?_*|!5Pjph@?Na8Z>K%AAjNr3T!7RN;7c)1SqAJfHY|xAV z1f;p%lSdE8I}E4~tRH(l*rK?OZ>mB4C{3e%E-bUng2ymerg8?M$rXC!D?3O}_mka? zm*Y~JMu+_F7O4T;#nFv)?Ru6 z92r|old*4ZB$*6M40B;V&2w->#>4DEu0;#vHSgXdEzm{+VS48 z7U1tVn#AnQ3z#gP26$!dmS5&JsXsrR>~rWA}%qd{92+j zu+wYAqrJYOA%WC9nZ>BKH&;9vMSW_59z5LtzS4Q@o5vcrWjg+28#&$*8SMYP z!l5=|p@x6YnmNq>23sQ(^du5K)TB&K8t{P`@T4J5cEFL@qwtsCmn~p>>*b=37y!kB zn6x{#KjM{S9O_otGQub*K)iIjtE2NfiV~zD2x{4r)IUD(Y8%r`n;#)ujIrl8Sa+L{ z>ixGoZJ1K@;wTUbRRFgnltN_U*^EOJS zRo4Y+S`cP}e-zNtdl^S5#%oN#HLjmq$W^(Y6=5tM#RBK-M14RO7X(8Gliy3+&9fO; zXn{60%0sWh1_g1Z2r0MuGwSGUE;l4TI*M!$5dm&v9pO7@KlW@j_QboeDd1k9!7S)jIwBza-V#1)(7ht|sjY}a19sO!T z2VEW7nB0!zP=Sx17-6S$r=A)MZikCjlQHE)%_Ka|OY4+jgGOw=I3CM`3ui^=o0p7u z?xujpg#dRVZCg|{%!^DvoR*~;QBH8ia6%4pOh<#t+e_u!8gjuk_Aic=|*H24Yq~Wup1dTRQs0nlZOy+30f16;f7EYh*^*i9hTZ`h`015%{i|4 z?$7qC3&kt#(jI#<76Biz=bl=k=&qyaH>foM#zA7}N`Ji~)-f-t&tR4^do)-5t?Hz_Q+X~S2bZx{t+MEjwy3kGfbv(ij^@;=?H_^FIIu*HP_7mpV)NS{MY-Rr7&rvWo@Wd~{Lt!8|66rq`GdGu% z@<(<7bYcZKCt%_RmTpAjx=TNvdh+ZiLkMN+hT;=tC?%vQQGc7WrCPIYZwYTW`;x|N zrlEz1yf95FiloUU^(onr3A3>+96;;6aL?($@!JwiQ2hO|^i)b4pCJ7-y&a~B#J`#FO!3uBp{5GBvM2U@K85&o0q~6#LtppE&cVY z3Bv{xQ-;i}LN-60B2*1suMd=Fi%Y|7@52axZ|b=Wiwk^5eg{9X4}(q%4D5N5_Gm)` zg~VyFCwfkIKW(@@ZGAlTra6CO$RA_b*yz#){B82N7AYpQ9)sLQfhOAOMUV7$0|d$=_y&jl>va$3u-H z_+H*|UXBPLe%N2Ukwu1*)kt!$Y>(IH3`YbEt; znb1uB*{UgwG{pQnh>h@vyCE!6B~!k}NxEai#iY{$!_w54s5!6jG9%pr=S~3Km^EEA z)sCnnau+ZY)(}IK#(3jGGADw8V7#v~<&y5cF=5_Ypkrs3&7{}%(4KM7) zuSHVqo~g#1kzNwXc39%hL8atpa1Wd#V^uL=W^&E)fvGivt)B!M)?)Y#Ze&zU6O_I?1wj)*M;b*dE zqlcwgX#eVuZj2GKgBu@QB(#LHMd`qk<08i$hG1@g1;zD*#(9PHjVWl*5!;ER{Q#A9 zyQ%fu<$U?dOW=&_#~{nrq{RRyD8upRi}c-m!n)DZw9P>WGs>o1vefI}ujt_`O@l#Z z%xnOt4&e}LlM1-0*dd?|EvrAO-$fX8i{aTP^2wsmSDd!Xc9DxJB=x1}6|yM~QQPbl z0xrJcQNtWHgt*MdGmtj%x6SWYd?uGnrx4{m{6A9bYx`m z$*UAs@9?3s;@Jl19%$!3TxPlCkawEk12FADYJClt0N@O@Pxxhj+Kk(1jK~laR0*KGAc7%C4nI^v2NShTc4#?!p{0@p0T#HSIRndH;#Ts0YECtlSR}~{Uck+keoJq6iH)(Zc~C!fBe2~4(Wd> zR<4I1zMeW$<0xww(@09!l?;oDiq zk8qjS9Lxv$<5m#j(?4VLDgLz;8b$B%XO|9i7^1M;V{aGC#JT)c+L=BgCfO5k>CTlI zOlf~DzcopV29Dajzt*OcYvaUH{UJPaD$;spv%>{y8goE+bDD$~HQbON>W*~JD`;`- zZEcCPSdlCvANe z=?|+e{6AW$f(H;BND>uy1MvQ`pri>SafK5bK!YAE>0URAW9RS8#LWUHBOc&BNQ9T+ zJpg~Eky!u!9WBk)!$Z?!^3M~o_VPERYnk1NmzVYaGH;1h+;st==-;jzF~2LTn+x*k zvywHZg7~=aiJe=OhS@U>1fYGvT1+jsAaiaM;) zay2xsMKhO+FIeK?|K{G4SJOEt*eX?!>K8jpsZWW8c!X|JR#v(1+Ey5NM^TB1n|_40 z@Db2gH}PNT+3YEyqXP8U@)`E|Xat<{K5K;eK7O0yV72m|b!o43!e-!P>iW>7-9HN7 zmmc7)JX0^lPzF#>$#D~nU^3f!~Q zQWly&oZEb1847&czU;dg?=dS>z3lJkADL1innNtE(f?~OxM`%A_PBp?Lj;zDDomf$ z;|P=FTmqX|!sHO6uIfCmh4Fbgw@`DOn#`qAPEsYUiBvUlw zevH{)YWQu>FPXU$%1!h*2rtk_J}qNkkq+StX8Wc*KgG$yH#p-kcD&)%>)Yctb^JDB zJe>=!)5nc~?6hrE_3n^_BE<^;2{}&Z>Dr)bX>H{?kK{@R)`R5lnlO6yU&UmWy=d03 z*(jJIwU3l0HRW1PvReOb|MyZT^700rg8eFp#p<3Et%9msiCxR+jefK%x81+iN0=hG z;<`^RUVU+S)Iv-*5y^MqD@=cp{_cP4`s=z)Ti3!Bf@zCmfpZTwf|>|0t^E8R^s`ad z5~tA?0x7OM{*D;zb6bvPu|F5XpF11`U5;b*$p zNAq7E6c=aUnq>}$JAYsO&=L^`M|DdSSp5O4LA{|tO5^8%Hf1lqqo)sj=!aLNKn9(3 zvKk($N`p`f&u+8e^Z-?uc2GZ_6-HDQs@l%+pWh!|S9+y3!jrr3V%cr{FNe&U6(tYs zLto$0D+2}K_9kuxgFSeQ!EOXjJtZ$Pyl_|$mPQ9#fES=Sw8L% zO7Jij9cscU)@W+$jeGpx&vWP9ZN3fLDTp zaYM$gJD8ccf&g>n?a56X=y zec%nLN`(dVCpSl9&pJLf2BN;cR5F0Nn{(LjGe7RjFe7efp3R_2JmHOY#nWEc2TMhMSj5tBf-L zlxP3sV`!?@!mRnDTac{35I7h@WTfRjRiFw*Q*aD8)n)jdkJC@)jD-&mzAdK6Kqdct8P}~dqixq;n zjnX!pb^;5*Rr?5ycT7>AB9)RED^x+DVDmIbHKjcDv2lHK;apZOc=O@`4nJ;k|iikKk66v4{zN#lmSn$lh z_-Y3FC)iV$rFJH!#mNqWHF-DtSNbI)84+VLDWg$ph_tkKn_6+M1RZ!)EKaRhY={el zG-i@H!fvpH&4~$5Q+zHU(Ub=;Lzcrc3;4Cqqbr$O`c5M#UMtslK$3r+Cuz>xKl+xW?`t2o=q`1djXC=Q6`3C${*>dm~I{ z(aQH&Qd{{X+&+-4{epSL;q%n$)NOQ7kM}ea9bA++*F+t$2$%F!U!U}(&y7Sd0jQMV zkOhuJ$+g7^kb<`jqFiq(y1-~JjP13J&uB=hfjH5yAArMZx?VzW1~>tln~d5pt$uWR~TM!lIg+D)prR zocU0N2}_WTYpU`@Bsi1z{$le`dO{-pHFQr{M}%iEkX@0fv!AGCTcB90@e|slf#unz z*w4Cf>(^XI64l|MmWih1g!kwMJiifdt4C<5BHtaS%Ra>~3IFwjdu;_v*7BL|fPu+c zNp687`{}e@|%)5g4U*i=0zlSWXzz=YcZ*&Bg zr$r(SH0V5a%oHh*t&0y%R8&jDI=6VTWS_kJ!^WN!ET@XfEHYG-T1jJsDd`yEgh!^* z+!P62=v`R2=TBVjt=h}|JIg7N^RevZuyxyS+jsk>=iLA52Ak+7L?2$ZDUaWdi1PgB z_;*Uae_n&7o27ewV*y(wwK~8~tU<#Np6UUIx}zW6fR&dKiPq|$A{BwG_-wVfkm+EP zxHU@m`im3cD#fH63>_X`Il-HjZN_hqOVMG;(#7RmI13D-s_>41l|vDH1BglPsNJ+p zTniY{Hwoief+h%C^|@Syep#722=wmcTR7awIzimAcye?@F~f|n<$%=rM+Jkz9m>PF70$)AK@|h_^(zn?!;={;9Zo7{ zBI7O?6!J2Ixxk;XzS~ScO9{K1U9swGvR_d+SkromF040|Slk%$)M;9O_8h0@WPe4= z%iWM^ust8w$(NhO)7*8uq+9CycO$3m-l}O70sBi<4=j0CeE_&3iRUWJkDM$FIfrkR zHG2|hVh3?Nt$fdI$W?<|Qq@#hjDijk@7eUr1&JHYI>(_Q4^3$+Zz&R)Z`WqhBIvjo zX#EbA8P0Qla-yACvt)%oAVHa#kZi3Y8|(IOp_Z6J-t{)98*OXQ#8^>vTENsV@(M}^ z(>8BXw`{+)BfyZB!&85hT0!$>7$uLgp9hP9M7v=5@H`atsri1^{1VDxDqizj46-2^ z?&eA9udH#BD|QY2B7Zr$l;NJ-$L!u8G{MZoX)~bua5J=0p_JnM`$(D4S!uF}4smWq zVo%kQ~C~X?cWCH zo4s#FqJ)k|D{c_ok+sZ8`m2#-Uk8*o)io`B+WTD0PDA!G`DjtibftJXhPVjLZj~g& z=MM9nF$7}xvILx}BhM;J-Xnz0=^m1N2`Mhn6@ct+-!ijIcgi6FZ*oIPH(tGYJ2EQ0 z{;cjcc>_GkAlWEZ2zZLA_oa-(vYBp7XLPbHCBcGH$K9AK6nx}}ya%QB2=r$A;11*~ z_wfru1SkIQ0&QUqd)%eAY^FL!G;t@7-prQ|drDn#yDf%Uz8&kGtrPxKv?*TqkC(}g zUx10<;3Vhnx{gpWXM8H zKc0kkM~gIAts$E!X-?3DWG&^knj4h(q5(L;V81VWyC@_71oIpXfsb0S(^Js#N_0E} zJ%|XX&EeVPyu}? zz~(%slTw+tcY3ZMG$+diC8zed=CTN}1fB`RXD_v2;{evY z@MCG$l9Az+F()8*SqFyrg3jrN7k^x3?;A?L&>y{ZUi$T8!F7Dv8s}}4r9+Wo0h^m= zAob@CnJ;IR-{|_D;_w)? zcH@~&V^(}Ag}%A90);X2AhDj(-YB>$>GrW1F4C*1S5`u@N{T|;pYX1;E?gtBbPvS* zlv3r#rw2KCmLqX0kGT8&%#A6Sc(S>apOHtfn+UdYiN4qPawcL{Sb$>&I)Ie>Xs~ej z7)a=-92!sv-A{-7sqiG-ysG0k&beq6^nX1L!Fs$JU#fsV*CbsZqBQ|y z{)}zvtEwO%(&mIG|L?qs2Ou1rqTZHV@H+sm8Nth(+#dp0DW4VXG;;tCh`{BpY)THY z_10NNWpJuzCG%Q@#Aj>!v7Eq8eI6_JK3g2CsB2jz)2^bWiM{&U8clnV7<2?Qx5*k_ zl9B$P@LV7Sani>Xum{^yJ6uYxM4UHnw4zbPdM|PeppudXe}+OcX z!nr!xaUA|xYtA~jE|436iL&L={H3e}H`M1;2|pLG)Z~~Ug9X%_#D!DW>w}Es!D{=4 zxRPBf5UWm2{}D>Em;v43miQ~2{>%>O*`wA{7j;yh;*DV=C-bs;3p{AD;>VPcn>E;V zLgtw|Y{|Beo+_ABz`lofH+cdf33LjIf!RdcW~wWgmsE%2yCQGbst4TS_t%6nS8a+m zFEr<|9TQzQC@<(yNN9GR4S$H-SA?xiLIK2O2>*w-?cdzNPsG4D3&%$QOK{w)@Dk}W z|3_Z>U`XBu7j6Vc=es(tz}c7k4al1$cqDW4a~|xgE9zPX(C`IsN(QwNomzsBOHqjd zi{D|jYSv5 zC>6#uB~%#!!*?zXW`!yHWjbjwm!#eo3hm;>nJ!<`ZkJamE6i>>WqkoTpbm(~b%G_v z`t3Z#ERips;EoA_0c?r@WjEP|ulD+hue5r8946Sd0kuBD$A!=dxigTZn)u3>U;Y8l zX9j(R*(;;i&HrB&M|Xnitzf@><3#)aKy=bFCf5Hz@_);{nlL?J!U>%fL$Fk~Ocs3& zB@-Ek%W>h9#$QIYg07&lS_CG3d~LrygXclO!Ws-|PxMsn@n{?77wCaq?uj`dd7lllDCGd?ed&%5k{RqUhiN1u&?uz@Fq zNkv_4xmFcl?vs>;emR1R<$tg;*Ayp@rl=ik z=x2Hk zJqsM%++e|*+#camAiem6f;3-khtIgjYmNL0x|Mz|y{r{6<@_&a7^1XDyE>v*uo!qF zBq^I8PiF#w<-lFvFx9xKoi&0j)4LX~rWsK$%3hr@ebDv^($$T^4m4h#Q-(u*Mbt6F zE%y0Fvozv=WAaTj6EWZ)cX{|9=AZDvPQuq>2fUkU(!j1GmdgeYLX`B0BbGK(331ME zu3yZ3jQ@2)WW5!C#~y}=q5Av=_;+hNi!%gmY;}~~e!S&&^{4eJuNQ2kud%Olf8TRI zW-Dze987Il<^!hCO{AR5tLW{F1WLuZ>nhPjke@CSnN zzoW{m!+PSCb7byUf-1b;`{0GU^zg7b9c!7ueJF`>L;|akVzb&IzoLNNEfxp7b7xMN zKs9QG6v@t7X)yYN9}3d4>*ROMiK-Ig8(Do$3UI&E}z!vcH2t(VIk-cLyC-Y%`)~>Ce23A=dQsc<( ziy;8MmHki+5-(CR8$=lRt{(9B9W59Pz|z0^;`C!q<^PyE$KXt!KibFH*xcB9V%xTD zn;YlZ*tTukwr$(mWMka@|8CW-J8!zCXI{P1-&=wSvZf&%9SZ7m`1&2^nV#D z6T*)`Mz3wGUC69Fg0Xk!hwY}ykk!TE%mr57TLX*U4ygwvM^!#G`HYKLIN>gT;?mo% zAxGgzSnm{}vRG}K)8n(XjG#d+IyAFnozhk|uwiey(p@ zu>j#n4C|Mhtd=0G?Qn5OGh{{^MWR)V*geNY8d)py)@5a85G&_&OSCx4ASW8g&AEXa zC}^ET`eORgG*$$Q1L=9_8MCUO4Mr^1IA{^nsB$>#Bi(vN$l8+p(U^0dvN_{Cu-UUm zQyJc!8>RWp;C3*2dGp49QVW`CRR@no(t+D|@nl138lu@%c1VCy3|v4VoKZ4AwnnjF z__8f$usTzF)TQ$sQ^|#(M}-#0^3Ag%A0%5vA=KK$37I`RY({kF-z$(P50pf3_20YTr%G@w+bxE_V+Tt^YHgrlu$#wjp7igF!=o8e2rqCs|>XM9+M7~TqI&fcx z=pcX6_MQQ{TIR6a0*~xdgFvs<2!yaA1F*4IZgI!)xnzJCwsG&EElg_IpFbrT}nr)UQy}GiK;( zDlG$cksync34R3J^FqJ=={_y9x_pcd%$B*u&vr7^ItxqWFIAkJgaAQiA)pioK1JQ| zYB_6IUKc$UM*~f9{Xzw*tY$pUglV*?BDQuhsca*Fx!sm`9y`V&?lVTH%%1eJ74#D_ z7W+@8@7LAu{aq)sPys{MM~;`k>T%-wPA)E2QH7(Z4XEUrQ5YstG`Uf@w{n_Oc!wem z7=8z;k$N{T74B*zVyJI~4d60M09FYG`33;Wxh=^Ixhs69U_SG_deO~_OUO1s9K-8p z5{HmcXAaKqHrQ@(t?d@;63;Pnj2Kk<;Hx=kr>*Ko`F*l){%GVDj5nkohSU)B&5Vrc zo0u%|b%|VITSB)BXTRPQC=Bv=qplloSI#iKV#~z#t#q*jcS`3s&w-z^m--CYDI7n2 z%{LHFZ*(1u4DvhES|Dc*n%JL8%8?h7boNf|qxl8D)np@5t~VORwQn)TuSI07b-T=_ zo8qh+0yf|-6=x;Ra$w&WeVZhUO%3v6Ni*}i&sby3s_(?l5Er{K9%0_dE<`7^>8mLr zZ|~l#Bi@5}8{iZ$(d9)!`}@2~#sA~?uH|EbrJQcTw|ssG)MSJJIF96-_gf&* zy~I&$m6e0nnLz^M2;G|IeUk?s+afSZ){10*P~9W%RtYeSg{Nv5FG<2QaWpj?d`;}<4( z>V1i|wNTpH`jJtvTD0C3CTws410U9HS_%Ti2HaB~%^h6{+$@5`K9}T=eQL;dMZ?=Y zX^z?B3ZU_!E^OW%Z*-+t&B-(kLmDwikb9+F9bj;NFq-XHRB=+L)Rew{w|7p~7ph{#fRT}}K zWA)F7;kJBCk^aFILnkV^EMs=B~#qh*RG2&@F|x2$?7QTX_T6qL?i$c6J*-cNQC~E6dro zR)CGIoz;~V?=>;(NF4dihkz~Koqu}VNPE9^R{L@e6WkL{fK84H?C*uvKkO(!H-&y( zq|@B~juu*x#J_i3gBrS0*5U*%NDg+Ur9euL*5QaF^?-pxxieMM6k_xAP;S}sfKmIa zj(T6o{4RfARHz25YWzv=QaJ4P!O$LHE(L~6fB89$`6+olZR!#%y?_v+Cf+g)5#!ZM zkabT-y%v|ihYuV}Y%-B%pxL264?K%CXlbd_s<GY5BG*`kYQjao$QHiC_qPk5uE~AO+F=eOtTWJ1vm*cU(D5kvs3kity z$IYG{$L<8|&I>|WwpCWo5K3!On`)9PIx(uWAq>bSQTvSW`NqgprBIuV^V>C~?+d(w$ZXb39Vs`R=BX;4HISfN^qW!{4 z^amy@Nqw6oqqobiNlxzxU*z2>2Q;9$Cr{K;*&l!;Y??vi^)G|tefJG9utf|~4xh=r3UjmRlADyLC*i`r+m;$7?7*bL!oR4=yU<8<-3XVA z%sAb`xe&4RV(2vj+1*ktLs<&m~mGJ@RuJ)1c zLxZyjg~*PfOeAm8R>7e&#FXBsfU_?azU=uxBm=E6z7FSr7J>{XY z1qUT>dh`X(zHRML_H-7He^P_?148AkDqrb>;~1M-k+xHVy>;D7p!z=XBgxMGQX2{* z-xMCOwS33&K^~3%#k`eIjKWvNe1f3y#}U4;J+#-{;=Xne^6+eH@eGJK#i|`~dgV5S zdn%`RHBsC!=9Q=&=wNbV#pDv6rgl?k1wM03*mN`dQBT4K%uRoyoH{e=ZL5E*`~X|T zbKG9aWI}7NGTQtjc3BYDTY3LbkgBNSHG$5xVx8gc@dEuJqT~QPBD=Scf53#kZzZ6W zM^$vkvMx+-0$6R^{{hZ2qLju~e85Em>1nDcRN3-Mm7x;87W#@RSIW9G>TT6Q{4e~b z8DN%n83FvXWdpr|I_8TaMv~MCqq0TA{AXYO-(~l=ug42gpMUvOjG_pWSEdDJ2Bxqz z!em;9=7y3HW*XUtK+M^)fycd8A6Q@B<4biGAR)r%gQf>lWI%WmMbij;un)qhk$bff zQxb{&L;`-1uvaCE7Fm*83^0;!QA5-zeSvKY}WjbwE68)jqnOmj^CTBHaD zvK6}Mc$a39b~Y(AoS|$%ePoHgMjIIux?;*;=Y|3zyfo)^fM=1GBbn7NCuKSxp1J|z zC>n4!X_w*R8es1ofcPrD>%e=E*@^)7gc?+JC@mJAYsXP;10~gZv0!Egi~){3mjVzs z^PrgddFewu>Ax_G&tj-!L=TuRl0FAh#X0gtQE#~}(dSyPO=@7yd zNC6l_?zs_u5&x8O zQ|_JvKf!WHf43F0R%NQwGQi-Dy7~PGZ@KRKMp?kxlaLAV=X{UkKgaTu2!qzPi8aJ z-;n$}unR?%uzCkMHwb56T%IUV)h>qS(XiuRLh3fdlr!Cri|{fZf0x9GVYUOlsKgxLA7vHrkpQddcSsg4JfibzpB zwR!vYiL)7%u8JG7^x@^px(t-c_Xt|9Dm)C@_zGeW_3nMLZBA*9*!fLTV$Uf1a0rDt zJI@Z6pdB9J(a|&T_&AocM2WLNB;fpLnlOFtC9yE6cb39?*1@wy8UgruTtX?@=<6YW zF%82|(F7ANWQ`#HPyPqG6~ggFlhJW#R>%p@fzrpL^K)Kbwj(@#7s97r`)iJ{&-ToR z$7(mQI@~;lwY+8dSKP~0G|#sjL2lS0LQP3Oe=>#NZ|JKKYd6s6qwe#_6Xz_^L4PJ5TM_|#&~zy= zabr|kkr3Osj;bPz`B0s;c&kzzQ2C8|tC9tz;es~zr{hom8bT?t$c|t;M0t2F{xI;G z`0`ADc_nJSdT`#PYCWu4R0Rmbk#PARx(NBfdU>8wxzE(`jA}atMEsaG6zy8^^nCu| z9_tLj90r-&Xc~+p%1vyt>=q_hQsDYB&-hPj(-OGxFpesWm;A(Lh>UWy4SH9&+mB(A z2jkTQ2C&o(Q4wC_>|c()M8_kF?qKhNB+PW6__;U+?ZUoDp2GNr<|*j(CC*#v0{L2E zgVBw6|3c(~V4N*WgJsO(I3o>8)EO5;p7Xg8yU&%rZ3QSRB6Ig6MK7Wn5r+xo2V}fM z0QpfDB9^xJEi}W*Fv6>=p4%@eP`K5k%kCE0YF2Eu5L!DM1ZY7wh`kghC^NwxrL}90dRXjQx=H>8 zOWP@<+C!tcw8EL8aCt9{|4aT+x|70i6m*LP*lhp;kGr5f#OwRy`(60LK@rd=to5yk^%N z6MTSk)7)#!cGDV@pbQ>$N8i2rAD$f{8T{QM+|gaj^sBt%24UJGF4ufrG1_Ag$Rn?c zzICg9`ICT>9N_2vqvVG#_lf9IEd%G5gJ_!j)1X#d^KUJBkE9?|K03AEe zo>5Rql|WuUU=LhLRkd&0rH4#!!>sMg@4Wr=z2|}dpOa`4c;_DqN{3Pj`AgSnc;h%# z{ny1lK%7?@rwZO(ZACq#8mL)|vy8tO0d1^4l;^e?hU+zuH%-8Y^5YqM9}sRzr-XC0 zPzY1l($LC-yyy*1@eoEANoTLQAZ2lVto2r7$|?;PPQX`}rbxPDH-a$8ez@J#v0R5n z7P*qT3aHj02*cK)WzZmoXkw?e3XNu&DkElGZ0Nk~wBti%yLh+l2DYx&U1lD_NW_Yt zGN>yOF?u%ksMW?^+~2&p@NoPzk`T)8qifG_owD>@iwI3@u^Y;Mqaa!2DGUKi{?U3d z|Efe=CBc!_ZDoa~LzZr}%;J|I$dntN24m4|1(#&Tw0R}lP`a`?uT;>szf^0mDJx3u z6IJvpeOpS$OV!Xw21p>Xu~MZ(Nas5Iim-#QSLIYSNhYgx1V!AR>b zf5b7O`ITTvW5z%X8|7>&BeEs8~J1i47l;`7Y#MUMReQ4z!IL1rh8UauKNPG?7rV_;#Y zG*6Vrt^SsTMOpV7mkui}l_S8UNOBcYi+DzcMF>YKrs3*(q5fwVCr;_zO?gpGx*@%O zl`KOwYMSUs4e&}eM#FhB3(RIDJ9ZRn6NN{2Nf+ z2jcz%-u6IPq{n7N3wLH{9c+}4G(NyZa`UmDr5c-SPgj0Sy$VN#Vxxr;kF>-P;5k!w zuAdrP(H+v{Dybn78xM6^*Ym@UGxx?L)m}WY#R>6M2zXnPL_M9#h($ECz^+(4HmKN7 zA>E;`AEqouHJd7pegrq4zkk>kHh`TEb`^(_ea;v{?MW3Sr^FXegkqAQPM-h^)$#Jn z?bKbnXR@k~%*?q`TPL=sD8C+n^I#08(}d$H(@Y;3*{~nv4RLZLw`v=1M0-%j>CtT( zTp#U03GAv{RFAtj4vln4#E4eLOvt zs;=`m&{S@AJbcl1q^39VOtmN^Zm(*x(`(SUgF(=6#&^7oA8T_ojX>V5sJx@*cV|29 z)6_%P6}e}`58Sd;LY2cWv~w}fer&_c1&mlY0`YNNk9q=TRg@Khc5E$N`aYng=!afD z@ewAv^jl$`U5;q4OxFM4ab%X_Jv>V!98w$8ZN*`D-)0S7Y^6xW$pQ%g3_lEmW9Ef^ zGmFsQw`E!ATjDvy@%mdcqrD-uiKB}!)ZRwpZRmyu+x|RUXS+oQ*_jIZKAD~U=3B|t zz>9QQr91qJihg9j9rWHww{v@+SYBzCfc0kI=4Gr{ZLcC~mft^EkJ`CMl?8fZ z3G4ix71=2dQ`5QuTOYA0(}f`@`@U<#K?1TI(XO9c*()q!Hf}JUCaUmg#y?ffT9w1g zc)e=JcF-9J`hK{0##K#A>m^@ZFx!$g09WSBdc8O^IdP&JE@O{i0&G!Ztvt{L4q%x& zGE2s!RVi6ZN9)E*(c33HuMf7#X2*VPVThdmrVz-Fyqxcs&aI4DvP#bfW={h$9>K0HsBTUf z2&!G;( z^oOVIYJv~OM=-i`6=r4Z1*hC8Fcf3rI9?;a_rL*nr@zxwKNlxf(-#Kgn@C~4?BdKk zYvL?QcQeDwwR5_S(`sn&{PL6FYxwb-qSh_rUUo{Yi-GZz5rZotG4R<+!PfsGg`MVtomw z5kzOZJrh(#rMR_87KeP0Q=#^5~r_?y1*kN?3Fq% zvnzHw$r!w|Soxz8Nbx2d&{!#w$^Hua%fx!xUbc2SI-<{h>e2I;$rJL)4)hnT5cx^* zIq#+{3;Leun3Xo=C(XVjt_z)F#PIoAw%SqJ=~DMQeB zNWQ={d|1qtlDS3xFik}#j*8%DG0<^6fW~|NGL#P_weHnJ(cYEdJtI9#1-Pa8M}(r{ zwnPJB_qB?IqZw5h!hRwW2WIEb?&F<52Ruxpr77O2K>=t*3&Z@=5(c^Uy&JSph}{Q^ z0Tl|}gt=&vK;Rb9Tx{{jUvhtmF>;~k$8T7kp;EV`C!~FKW|r$n^d6=thh`)^uYgBd zydgnY9&mm$?B@pKK+_QreOm?wnl5l}-wA$RZCZukfC$slxbqv9uKq0o^QeSID96{Rm^084kZ)*`P zk))V~+<4-_7d6<~)PL%!+%JP`Dn23vUpH47h~xnA=B_a}rLy|7U-f0W+fH`{wnyh2 zD$JYdXuygeP5&OAqpl2)BZ|X){~G;E|7{liYf%AZFmXXyA@32qLA)tuuQz`n^iH1Y z=)pAzxK$jw0Xq?7`M`=kN2WeQFhz)p;QhjbKg#SB zP~_Vqo0SGbc5Q;v4Q7vm6_#iT+p9B>%{s`8H}r|hAL5I8Q|ceJAL*eruzD8~_m>fg26HvLpik&#{3Zd#|1C_>l&-RW2nBBzSO zQ3%G{nI*T}jBjr%3fjG*&G#ruH^ioDM>0 zb0vSM8ML?tPU*y%aoCq;V%x%~!W*HaebuDn9qeT*vk0%X>fq-4zrrQf{Uq5zI1rEy zjQ@V|Cp~$AoBu=VgnVl@Yiro>ZF{uB=5)~i1rZzmDTIzLBy`8Too!#Z4nE$Z{~uB( z_=o=gKuhVpy&`}-c&f%**M&(|;2iy+nZy2Su}GOAH_GT9z`!ogwn$+Bi&1ZhtPF zVS&LO5#Bq}cew$kvE7*t8W^{{7&7WaF{upy0mj*K&xbnXvSP9V$6m6cesHGC!&Us36ld9f*Pn8gbJb3`PPT|ZG zri2?uIu09i>6Y-0-8sREOU?WaGke0+rHPb^sp;*E{Z5P7kFJ@RiLZTO`cN2mRR#Nz zxjJ##Nk+Uy-2N-8K_@576L(kJ>$UhP+)|w!SQHkkz+e62*hpzyfmY4eQLZtZUhEdG zIZluDOoPDlt5#iw+2epC3vEATfok^?SDT`TzBwtgKjY z>ZImbO)i~T=IYAfw$3j2mF1Cj*_yqK(qw(U^r-!gcUKvWQrDG@E{lEyWDWOPtA9v{ z5($&mxw{nZWo_Ov??S#Bo1;+YwVfx%M23|o$24Hdf^&4hQeV=Cffa5MMYOu2NZLSC zQ4UxWvn+8%YVGDg(Y*1iHbUyT^=gP*COcE~QkU|&6_3h z-GOS6-@o9+Vd(D7x#NYt{Bvx2`P&ZuCx#^l0bR89Hr6Vm<||c3Waq(KO0eZ zH(|B;X}{FaZ8_4yyWLdK!G_q9AYZcoOY}Jlf3R;%oR5dwR(rk7NqyF%{r>F4s^>li z`R~-fh>YIAC1?%!O?mxLx!dq*=%IRCj;vXX628aZ;+^M0CDFUY0Rc<1P5e(OVX8n- z*1UOrX{J}b2N)6m5&_xw^WSN=Lp$I$T>f8K6|J_bj%ZsIYKNs1$TFt!RuCWF48;98`7D(XPVnk+~~i=U$} zR#;!ZRo4eVqlDxjDeE^3+8)bzG_o~VRwdxqvD^HNh#@o>1My$0*Y_`wfQ$y}az|Uz zM47oEaYNTH?J^w9EVNnvfmmbV+GHDe)Kf;$^@6?9DrSHnk@*{PuJ>ra|9KO!qQ-Fp zNNcZB4ZdAI>jEh@3Mt(E1Fy!^gH-Zx6&lr8%=duIgI^~gC{Q;4yoe;#F7B`w9daIe z{(I;y)=)anc;C;)#P`8H6~iAG_q-4rPJb(6rn4pjclGi6$_L79sFAj#CTv;t@94S6 zz`Id7?k!#3JItckcwOf?sj=Xr6oKvAyt1=jiWN@XBFoW6dw_+c9O9x2i4or?*~8f& zm<>yzc6Aw_E-gsGAa`6`cjK~k^TJt(^`E1^_h)5(8)1kzAsBxjd4+!hJ&&T!qklDN z`?j#za=(^wRCvEI75uE^K#IBe5!5g2XW}|lUqAmdmIQb7xJtP}G9^(=!V`ZS_7#RZ zjXq#Cekw>fE*YS-?Qea|7~H?)bbLK;G&(~%!B@H`o#LYAuu6;-c~jFfjY7GKZ|9~{ zE!`!d@@rhY_@5fDbuQ8gRI~R_vs4%fR5$?yot4hDPJ28k_Wzmc^0yzwMr#*(OXq@g zRUgQmJA?E>3GO=5N8iWIfBP{&QM%!Oa*iwTlbd0Fbm*QCX>oRb*2XfG-=Bz1Qz0$v zn#X!2C!LqE601LEMq;X7`P*5nurdKZAmmsI-zZ|rTH;AFxNDyZ_#hN2m4W(|YB64E z470#yh$;8QzsdA;6vbNvc95HLvZvyT4{C>F(fwy&izvNDuvfO1Z;`Ss#4a_c6pm*{0t|_i9z{@84^lffQa5zG4<{(+p5-S z^>lG-^GJR#V>;5f3~y%n=`U_jBp~WgB0cp;Lx5VZYPYCH&(evw#}AYRlGJ>vcoeVr z3%#-QUBgeH!GB>XLw;rT&oMI9ynP;leDwh4O2uM!oIWo&Qxk{^9#nX&^3GJ z(U~5{S9aw@yHH^yuQGso=~*JOC9Zdi6(TFP+IddkfK5Eu9q;+F9?PPNAe-O;;P_Aa zPJ{Dqa1gQb%dZ|0I{#B0(z|r(qq!A4CxlW92-LwXFjYfOzAT1DDK`9rm4AB~l&oVv zi6_{)M9L1%JP}i52y@`!T9RB~!CRel53wl?amNHqcuElq%hn)|#BPvW5_m51RVb|? zXQ&B*eAD}}QamG>o{?i~usG5X6IDa3+Xkb8w%7;C8|Cln70biA+ZH}fxkH^Wei$vZPnuqIT!Mmy26;mLfU z3Bbv4M^vvMlz-I+46=g>0^wWkmA!hlYj*I!%it^x9Kx(d{L|+L{rW?Y#hLHWJfd5X z>B=Swk8=;mRtIz}Hr3NE_garb5W*!7fnNM{+m2_>!cHZZlNEeof~7M#FBEQ+f&gJ3 z^zv*t?XV)jQi%0-Ra|ISiW-fx)DsK-> zI}Fv%uee$#-1PKJwr=lU89eh=M{>Nk7IlJ)U33U)lLW+OOU%A|9-Lf;`@c*+vX{W2 z{{?0QoP!#?8=5%yL=fP%iF+?n$0#iHz`P;1{Ra6iwr=V7v^8;NoLJ5)QxIyIx>ur?lMwV=mBo0BA?28kMow8SX=Ax5L%S~x4+EQi#Ig`(ht%)D(F#Pa!)SiHy&PvUp32=VtAsR|6|NZR@jkad zX^aEgojf9(-)rNOZ=NVA&a;6Cljkb=H-bY9m^_I)`pBHB16QW)sU27zF13ypefeATJc1Wzy39GrKF{UntHsIU59AdXp?j{eh2R)IbU&omd zk6(qzvE@hve1yM6dgkbz>5HDR&MD~yi$yymQ}?b;RfL$N-#l7(u?T^Wlu+Q;fo|jd zBe^jzGMHY(2=5l?bEIh+zgE$1TEQ&!p3fH;AW`P?W5Hkj3eJnT>dqg! zf~}A*SZU5HHDCbdywQ^l_PqssHRlrySYN=`hAv2sVrtcF!`kyEu%XeeRUTJU7vB%h zY0*)N$mLo6d=tJfe}IPIeiH~>AKwCpkn&WEfYgl?3anq5#-F$6$v-(G_j0*S9mdsn zg@ek_ut4(?+JP_9-n`YqoD(gAz+Ttm1#t za96D}oQR(o=e8wwes19_(p4g(A1vSGwPAp~Hh3hh!fc>u{1E^+^}AzwilFVf6^vbL zc&NnRs`u)N-P|Cu4()yTiuE{j_V&=K?iP!IUBf~ei2}~_KBvUAlXa;R#Wl`gOBtJ$Y5(L))@`riLB)v*r>9*8VfmQt<72?+fdwP{BA@?_qo>mN7yzICUCaeG(+>Rb~8wg~6U(P)NlDLuhQgjbC}=)HuZgC}0Z-qLX4lJ7^)8~!!*qP0=~`Y_(A z{@15*ZevZSI^s|OnpCeCwLXf#tgbq8y~R*GB5anmZ;_N!+-3>!wu@NBFCNJ$#y?{? zMI!?s*=_xA;V&aX)ROxzVW8*de+&P#2zucA|8mksdgCXBsZ*TM=%{L1Tk5LB_*^@&S?O=ot{h)1xRVSn27&Tk8>rF|6ruzYb;Nq) z;qvlmrP^SL$mhe4Ai)xpl6Wx&y;z8o!7-+6$qj;ZLXvfR71I@w(R|6lyuP6v-lP&r z@KK-TEmGQfMmk1c0^fd7!^si}T%b5a2%>T-Drh|^Cf z$}qxIv@zxbmJ#qjK6Q_aGDe{ciVT20V1lW52Xs!}x(4_j)sUXYdm4 zwYC9FOa;X*c*LxL;xE5ov?|?^7gWXyALy_D2GvDo-8%0-Y%9TkkO_Tcr2qIUg3(OC z%3wt?hyn*+e^z%(~2#!2dvMFa$mzgwk1I1X;naFMjXSbnmZ!zd%7u)=cgi z*0&@Scrl&BDfU(9Pks8#;!~v~r7~DN{G6WE&_;7i{{a*?oiCao(l%2ruxX0fAt69e2vLgL%Mf_)!*(Tz zNKW>sW@YB2vBfP>C&L|-pq)Uq^PsG_THu;8iEcqafO?0k$IQp1KyWyOoTxwmKvlc^ zO9$%Tt8;%qQxwy5;CsJ)V}a7I6}SvQ%0_H53Kcqx=m83fIzpLSGgfVe^SPdc*xPdciI5dg}#{Etv$e<)gGD=qm0v=!aN@*?$s zLhzD%4w{vf-g6FHQjG9XyC+4=bewb?Mz%!u8%oP{G9{UJFTLTcCi3R(=Nm&t&Sl(? zr>pj?=ECdDVa}-g%`LF^1EY@>7d}%VhYpKFSDPH)D(zB+gPe1m7E}W>TiW=8L0&(D&YG=0<&7G4Bu{;-#Ud;-1%Ta9V}U6fyK1YX z`Rq|i-X(loPZ)M$H%m@j7bGx>uj~y=0)!t#dc|c}+hT%~Sq>fefez0Ul|jOJHta~u zx7*mV6~Jpt(FkY(pQN91>aFk7VS%Sa^oLaq$*)W?fy`xuFJgH<2s=!Rz}_(qdmdF~ zlr2f=)q_vpi8X;Jq>5^$GweJ{iS`Khw2f)fsvKpgh;U~13a+9 zfaw}UuGiBy;q10pI^Avb#X3D=k_r(T{N;-xA)OM}2Py5L##<96NU*Sr7GQqhfrPej z?;B$Bt_sTxuSAPXfTSC{zr?@$$0iHxC@z*5F52j*PG87hh`0w3At8jPf*rjNE~_Gj z2)fjeUFJ(#l9uWuw&5#@13|AQ1;pdA?EL4YKq0JDR5T8I?aWGxI=J9}vdyH;gQ@iE z>+UnC2iwT0f80-VuE^bY!N@(}9?bOXyy%rTqSNDN4rO4Zt#(kZwcGgTp&3((F+nsd ze~B)%K6oP4WX_w1>|QImC;9q zy}4p+s%^Too2(gE>yo%+yY#F{)phtmNqsJPVQQ0lGR|H9q>aA&AtU4M+EZ%`xvQLb zbigBOc`dL}&j3er?EOI`!W)N#>+uwp_!h^5FspaEylq!e(FPY-6T3~WeNmZ<$?Y6y z-!bM1kD7ZF8xl+Pi6fiv1?)q%`aNxn#pK%)ct||L&Xnf8Gu&3g;Of{B8Pt=u`e+Mn zA(DmU#3cF#Nr7W;X0V4ksFHMcNDAf4G&D8VjLeZ^|5-f$>_|71>P3xuu)?4NJed*w z6GR_RB5HQLzT(h+`Y?-3esxeue{-Q%b+!&o>IJ!#=}#_&q+hwJga>fkt(*(WdoN5vSta z#$mMN6}YzYRpaBZ)j)EL91-oL1(|d(>%UclsTUOyXyWM&(hNqLwqtn`!E>HJM{ zh>M~xa1@*U^cwx-k5QjePr5=B6u*jpJ)C0{C?f7Yga+I^4$TleyX$x&jm9z@c!?cC z<2kY7)p^+W{AXd@l1C09_yB*TG|yzb96BYk z8Wpj81vB>zcR+qM4m~A44w1n7$fxB$-?MV}S?Fh}c_|2FXg`cZ?750i;Cdl-_nGK# zta)h)6!*AsQ-z8caSh)%5JY>_yCeJs~FpAzdY8 zF@SU_hN#~ip5I;UACFzx1v0yf{j97l&)e-=`d#1Kp6A(Kj&HC!%vK!wEdK3HFJ?|6 za;WwUczZ+&<$g!Td^48@lJtfW@doXL#jY6)dK_RDCQAZ}l&OdD+?Yl5-bqpsHZR^( zF{u_cR(x>u(c4i5f(^8!h6CV0#ZxRFhLlunWiGDLO6yoRb(wV<(P^8=fOU7Hp{AHE z;Yg%kg@6&tL3Z*IrbkDeQ$%rbalVP39D@LVrC2xSavnTp%PorXPf1DVzHyqjDsDnS zL=mv0a2s60bHKGQM)ue>npH0SCp;XtZFUzm?R-x7D*(PxMmuJ4J*K2eY&ebe0yQHe zVG&*qe{pot{PM^xQv`H_rn2FcYOrEN+I#uX^1`Id%J$;Hi2cNCU!0Hlc0TjxLzkss zHxmC;hQBu5U4J0XflWM;{uH`_47Sg)QyZ{8D&T0;bdc3{^^<=q7P?C_2E-}PQn>*= z2T5q^J|Q_2+x%Qt`i3m6=6V$)BxIx{2KAFkMb#q`iMCD|L>+}_dYVA$wBr1Zr}YOF z^MMGO@PHGGh>g|^yF`PvvtDwN@kxt?ClLcG<+murHMz1Asj!$l=b)4{d}SqOJ}>Y< zSeAyP@ZEcpx`ayIdp>{--UVLYC_cZZURh_!4u2(*#x@Tk(QJa}4BqqZ$6%LhF-HB~ zAcc?$I6KP}IxANcAteEBX$Ys?T=JB|Fnd3*UAO0mYAXCgWf~?7Z_G7G5`H4;S^QKK zG*2l75vI@DHQC*es>6&|r^#RHKRQ5rwv_l4`!(!I3%)Z$P1fnZ8N@27zyg}54ElO%SjQ_4uujX)4ta@Gz2)_>4b~vX|rhRIH-eqdD zL)xaEpW3K|a>daQRRR*_$W>rWOsW-IE4VQl3L$3}=-PFU)s@XG&9+DFivH-;2&w~$ES_nJZJH!?1mO!CnP)Jb{mW9=f`bDpo^PI6i4|YurK)Q1 z^Ys1oHRdr!$X4RuyR%kgp!a*Lz*_AAoJ$EVAdsNCoPA^VZE1pGO@D3UStACE+%vs6 z$io@E>DmB|3VV~GbOt2oc+K;t zdn3gaFvYz;vRN-+2+Qk{8|O}e86nVck)fZn3sg$j#dLVham{yGkc$I#!HF7mRS%f* z!+NdzG49K(qaO^SBlp@K@D?|^rAq;8{*@kRc4sYSNQmoy7@_RS_ksWl2T_38h2A)# ziU2WXWD03(NqS&Mu*?0-iK8X_Z3w`}c7MPv0qZ7iM|L3xdTnR{y!7{#82$}uJCiGT zqa=8<9L05hu6 z1N+2n7OzT{NEf?gS@eq7@buCDFe9mAxY%THo^b@BHckKK>jg6{@)>n z43cPs%$Qi0iwyZ+{C491>FRu5+6baJ{&XXXC@Sp+b!QE|{7_d?lm5K=B z)myKEcxjFm74+drF|JCYcxdY%ASig#YoRBRUV7An7f-%rqj%PHECbxh#5476cEq@NQL?dI6gUqvS@w zq!WmD(aR0{NxItAZCKDCVw=Zu{9WGDu^i?2g zLerPiOU*HSaXg^3CdOX^F6c9MiHINP339N%)a96`^Z-c#&EogcxMSYo0Cb4{-}q1( zRrJine`P|6WRkm8u4Ja1QRYq$AR>b7tugd#EsT-VmXN-t!TYjZy}i!uKi6$u>EJ?w zvdHZg+hp+5ree?>fdJAX)5#Wtm#2M-{~2jfX2{G`)?D6UD1MevdeeU;;HCi}AtJr( SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/examples/example-project/gradle/wrapper/gradle-wrapper.properties b/examples/example-project/gradle/wrapper/gradle-wrapper.properties index 4baf5a11..381baa9c 100644 --- a/examples/example-project/gradle/wrapper/gradle-wrapper.properties +++ b/examples/example-project/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 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 delta 34176 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 SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4baf5a11..381baa9c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 948150afd6800313a5083f94d663740f264d9448 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 22:03:54 +0000 Subject: [PATCH 08/35] Bump org.openapi.generator from 7.2.0 to 7.3.0 (#159) Bumps org.openapi.generator from 7.2.0 to 7.3.0. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gabriel Feo --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index f1065d0b..5038d350 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false id("org.jetbrains.dokka") version "1.9.20" apply false - id("org.openapi.generator") version "7.2.0" apply false + id("org.openapi.generator") version "7.3.0" apply false } val group by project.properties From 3d92565f880f32884b103bb41fa76757cba88373 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 29 Mar 2024 03:20:54 +0000 Subject: [PATCH 09/35] Migrate to gradle/actions/dependency-submission (#166) `mikepenz/gradle-dependency-submission` is superseded by `gradle/actions/dependency-submission`. Tested with [workflow_run](https://github.com/gabrielfeo/gradle-enterprise-api-kotlin/actions/runs/8474440982/job/23220812657) --- .github/workflows/submit-dependencies.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/submit-dependencies.yml b/.github/workflows/submit-dependencies.yml index d564f1da..3bcedd99 100644 --- a/.github/workflows/submit-dependencies.yml +++ b/.github/workflows/submit-dependencies.yml @@ -20,13 +20,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Gradle - uses: ./.github/actions/build - name: Submit Gradle dependencies - uses: mikepenz/gradle-dependency-submission@v0.9.2 - with: - use-gradlew: false - include-build-environment: true - gradle-build-configuration-mapping: | - :library|compileClasspath - :library|runtimeClasspath + uses: gradle/actions/dependency-submission@v3 + env: + DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS: '.*[Tt]est(Compile|Runtime)Classpath' From f0987e782e5132628c56d449da8791518bec9101 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:50:35 +0100 Subject: [PATCH 10/35] Bump the retrofit group with 3 updates (#167) Bumps the retrofit group with 3 updates from 2.10.0 to 2.11.0: [com.squareup.retrofit2:retrofit](https://github.com/square/retrofit), [com.squareup.retrofit2:converter-moshi](https://github.com/square/retrofit) and [com.squareup.retrofit2:converter-scalars](https://github.com/square/retrofit). Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- library/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index c6c64992..e647286a 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -245,9 +245,9 @@ dependencies { implementation("com.squareup.moshi:moshi-kotlin:1.15.1") api("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") - api("com.squareup.retrofit2:retrofit:2.10.0") - implementation("com.squareup.retrofit2:converter-moshi:2.10.0") - implementation("com.squareup.retrofit2:converter-scalars:2.10.0") + api("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-moshi:2.11.0") + implementation("com.squareup.retrofit2:converter-scalars:2.11.0") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") } From de7c6a2513a09e6b319a9bb2e353a1de9609c5f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:18:24 +0100 Subject: [PATCH 11/35] Bump gitpython from 3.1.42 to 3.1.43 in /.github/scripts (#169) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.42 to 3.1.43. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index af1a79b5..791ab187 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -1 +1 @@ -GitPython==3.1.42 +GitPython==3.1.43 From a37add13aea6ba8ba103081dc2cdc3f52251b16b Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Mon, 1 Apr 2024 22:09:37 +0100 Subject: [PATCH 12/35] Ignore generated DevelocityApi (#170) Like the generated `GradleEnterpriseApi` class, this just duplicates all methods from `BuildsApi` and others. --- library/.openapi-generator-ignore | 1 + 1 file changed, 1 insertion(+) diff --git a/library/.openapi-generator-ignore b/library/.openapi-generator-ignore index e0e41620..447e7c5a 100644 --- a/library/.openapi-generator-ignore +++ b/library/.openapi-generator-ignore @@ -6,4 +6,5 @@ build/generated/openapi-generator/docs/* build/generated/openapi-generator/src/main/AndroidManifest.xml build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/infrastructure/ApiClient.kt build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/DevelocityApi.kt build/generated/openapi-generator/src/test/**/* From 3c381d853de7999828ee704e309f189c0b997336 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Tue, 2 Apr 2024 18:49:01 +0100 Subject: [PATCH 13/35] Add missing APIs to GradleEnterpriseApi (#173) The beta `ProjectsApi` (added in 2023.3) and `TestsApi` (added in 2023.4) weren't accessible from `GradleEnterpriseApi`. Fix and an integration test to prevent it from happening again. --- library/build.gradle.kts | 1 + .../api/GradleEnterpriseApiIntegrationTest.kt | 27 +++++++++++++++++++ .../enterprise/api/GradleEnterpriseApi.kt | 4 +++ 3 files changed, 32 insertions(+) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index e647286a..60f039b7 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -201,6 +201,7 @@ testing { register("integrationTest") { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + implementation("com.google.guava:guava:33.1.0-jre") } } withType().configureEach { diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt index f9e52a41..d689ae09 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt @@ -1,10 +1,15 @@ package com.gabrielfeo.gradle.enterprise.api import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.google.common.reflect.ClassPath import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.reflect.KVisibility.PUBLIC +import kotlin.reflect.full.memberProperties +import kotlin.reflect.javaType import kotlin.test.* +@OptIn(ExperimentalStdlibApi::class) class GradleEnterpriseApiIntegrationTest { @Test @@ -33,4 +38,26 @@ class GradleEnterpriseApiIntegrationTest { GradleEnterpriseApi.newInstance(config) } } + + @Test + fun mainApiInterfaceExposesAllGeneratedApiClasses() = runTest { + val generatedApiTypes = getGeneratedApiTypes() + val mainApiInterfaceProperties = getMainApiInterfaceProperties() + generatedApiTypes.forEach { + mainApiInterfaceProperties.singleOrNull { type -> type == it } + ?: fail("No property in GradleEnterpriseApi for $it") + } + } + + private fun getGeneratedApiTypes(): List { + val cp = ClassPath.from(this::class.java.classLoader) + return cp.getTopLevelClasses("com.gabrielfeo.gradle.enterprise.api") + .filter { it.simpleName.endsWith("Api") } + .filter { !it.simpleName.endsWith("GradleEnterpriseApi") } + .map { it.name } + } + + private fun getMainApiInterfaceProperties() = GradleEnterpriseApi::class.memberProperties + .filter { it.visibility == PUBLIC } + .map { it.returnType.javaType.typeName } } \ No newline at end of file diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt index 1bbb1f16..a8c5648f 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt @@ -35,6 +35,8 @@ interface GradleEnterpriseApi { val buildsApi: BuildsApi val buildCacheApi: BuildCacheApi + val projectsApi: ProjectsApi + val testsApi: TestsApi val metaApi: MetaApi val testDistributionApi: TestDistributionApi @@ -78,6 +80,8 @@ internal class RealGradleEnterpriseApi( override val buildsApi: BuildsApi by lazy { retrofit.create() } override val buildCacheApi: BuildCacheApi by lazy { retrofit.create() } + override val projectsApi: ProjectsApi by lazy { retrofit.create() } + override val testsApi: TestsApi by lazy { retrofit.create() } override val metaApi: MetaApi by lazy { retrofit.create() } override val testDistributionApi: TestDistributionApi by lazy { retrofit.create() } From b0c32613e26368ba181bfbe750972777c06bad08 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Tue, 2 Apr 2024 20:42:48 +0100 Subject: [PATCH 14/35] Update README and examples for 2023.4.0 (#176) - Update README for 2023.4 API with misc. improvements - Update example code replacing `getGradleAttributesFlow` for 2023.4 models + filtering via query --- .gitignore | 1 + README.md | 102 ++- docs/AccessKeys.md | 30 + .../MostFrequentBuilds.ipynb | 829 ++++++++++++++---- examples/example-project/app/build.gradle.kts | 2 +- .../example/analysis/MostFrequentBuilds.kt | 18 +- examples/example-script.main.kts | 13 +- 7 files changed, 780 insertions(+), 215 deletions(-) create mode 100644 docs/AccessKeys.md diff --git a/.gitignore b/.gitignore index b360c326..90de942b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build !*/*/src/**/build .ipynb_checkpoints +.venv __pycache__ **/.log diff --git a/README.md b/README.md index 4b9f0493..58c6ab12 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Gradle Enterprise API Kotlin -[![Maven Central](https://img.shields.io/badge/Maven%20Central-2023.3.1-blue)][14] -[![Javadoc](https://img.shields.io/badge/Javadoc-2023.3.1-orange)][7] +[![Maven Central](https://img.shields.io/badge/Maven%20Central-2023.4.0-blue)][14] +[![Javadoc](https://img.shields.io/badge/Javadoc-2023.4.0-orange)][7] A Kotlin library to access the [Gradle Enterprise API][1], easy to use from: @@ -11,7 +11,7 @@ A Kotlin library to access the [Gradle Enterprise API][1], easy to use from: ```kotlin val api = GradleEnterpriseApi.newInstance() -api.buildsApi.getBuilds(since = yesterdayMilli).forEach { +api.buildsApi.getBuildsFlow(fromInstant = 0, query = "buildStartTime<-1d").forEach { println(it) } ``` @@ -20,27 +20,12 @@ api.buildsApi.getBuilds(since = yesterdayMilli).forEach { ## Setup -Set up once and use the library from anywhere in your machine: +Set up environment variables and use the library from any notebook, script or project: -- [`GRADLE_ENTERPRISE_API_URL`][16] environment variable: the URL of your Gradle Enterprise instance -- [`GRADLE_ENTERPRISE_API_TOKEN`][17] environment variable: an API access token for the Gradle - Enterprise instance. - - Or a macOS keychain entry labeled `gradle-enterprise-api-token` (recommended). - -
      - - How to get an API token - - The Gradle Enterprise user must have the “Export build data via the API” permission. - - 1. Sign in to Gradle Enterprise - 2. Go to "My settings" from the user menu in the top right-hand corner of the page - 3. Go to "Access keys" from the sidebar - 4. Click "Generate" on the right-hand side and copy the generated token. - -
      - -That's it! You can now use the library without any code configuration from notebooks, scripts or -projects. +- [`GRADLE_ENTERPRISE_API_URL`][16]: the URL of your Gradle Enterprise instance +- [`GRADLE_ENTERPRISE_API_TOKEN`][17]: an [access key][31] for the Gradle Enterprise instance +- [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`][12] (optional, off by default): enables caching for some + requests (see [caveats][13]) ### Setup snippets @@ -52,7 +37,7 @@ recommended over JitPack. ``` %useLatestDescriptors -%use gradle-enterprise-api-kotlin(version=2023.3.1) +%use gradle-enterprise-api-kotlin(version=2023.4.0) ``` @@ -61,7 +46,7 @@ recommended over JitPack. Add to a Kotlin script ```kotlin -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") +@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") ``` @@ -71,7 +56,7 @@ recommended over JitPack. ```kotlin dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") + implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") } ``` @@ -80,12 +65,14 @@ dependencies { ## Usage The [`GradleEnterpriseApi`][9] interface represents the Gradle Enterprise REST API. It contains -the 4 APIs exactly as listed in the [REST API Manual][5]: +the 6 APIs exactly as listed in the [REST API Manual][5]: ```kotlin interface GradleEnterpriseApi { val buildsApi: BuildsApi val buildCacheApi: BuildCacheApi + val projectsApi: projectsApi + val testsApi: TestsApi val metaApi: MetaApi val testDistributionApi: TestDistributionApi // ... @@ -105,14 +92,10 @@ For most cases like scripts and notebooks, simply use [runBlocking][30]: ```kotlin runBlocking { - val builds: List = api.buildsApi.getBuilds(since = yesterdayMilli) + val builds: List = api.buildsApi.getBuilds(fromInstant = 0, query = "...") } ``` -It's recommended to call [`GradleEnterpriseApi.shutdown()`][11] at the end of scripts to release -resources and let the program exit. Otherwise, it'll keep running for an extra ~60s after code -finishes, as an [expected behavior of OkHttp][4]. - ### Caching HTTP caching is available, which can speed up queries significantly, but is @@ -124,19 +107,48 @@ off by default. Enable by simply setting [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`] Explore the library's convenience extensions: [`com.gabrielfeo.gradle.enterprise.api.extension`][25]. -What you'll probably use the most is [`getGradleAttributesFlow`][24], which will call -`/api/builds` to get the list of build IDs since a given date and join each with -`/api/builds/{id}/gradle-attributes`, which contains tags and custom values on each build. It -also takes care of paging under-the-hood, returning a [`Flow`][26] of all builds since the given -date, so you don't have to worry about the REST API's limit of 1000 builds per request: +By default, the API's most common endpoint, `/api/builds`, is paginated. The library provides a +[`getBuildsFlow`][24] extension to handle paging under-the-hood and yield all builds as you collect +them: ```kotlin -val builds: Flow = api.buildsApi.getGradleAttributesFlow(since = lastYear) +val builds: Flow = api.buildsApi.getBuildsFlow(fromInstant = 0, query = "...") builds.collect { // ... } ``` +### Shutdown + +By default, the library keeps some of its resources (like threads) alive until idle, in +case they're needed again. This is an optimization of [OkHttp][4]. If you're working on a notebook +or have a long-living program that fetches builds continuosly, no shutdown is needed. + +```kotlin +val api = GradleEnterpriseApi.newInstance() +while (true) { + delay(2.minutes) + processNewBuilds(api.buildsApi.getBuildsFlow(query = "...")) + // Don't worry about shutdown +} +``` + +In other cases (i.e. fetching some builds and exiting), you might want to call +[`GradleEnterpriseApi.shutdown()`][11] so that the program exits immediately: + +```kotlin +val api = GradleEnterpriseApi.newInstance() +printMetrics(api.buildsApi.getBuildsFlow(query = "...")) +// Call shutdown if you expect the program to exit now +api.shutdown() +``` + +### Working samples + +- [Jupyter notebooks with the Kotlin kernel][29] +- [Kotlin scripts (`kts`)][27] +- [Kotlin projects][28] + ## Documentation [![Javadoc](https://img.shields.io/badge/javadoc-latest-orange)][7] @@ -148,7 +160,7 @@ from the same OpenAPI spec. ## Optional setup Creating a custom [`Config`][8] allows you to change library settings via code instead of -environment variables. It also lets you share resource between the library's `OkHttpClient` and +environment variables. It also lets you share resources between the library's `OkHttpClient` and your own. For example: ```kotlin @@ -158,19 +170,17 @@ val config = Config( clientBuilder = existingClient.newBuilder(), ) val api = GradleEnterpriseApi.newInstance(config) -api.buildsApi.getBuilds(since = yesterdayMilli) +api.buildsApi.getBuilds(fromInstant = yesterdayMilli) ``` See the [`Config`][8] documentation for more. ## More info -- Currently built for Gradle Enterprise `2022.4`, but should work fine with previous and - future versions. The library will be updated regularly for new API versions. - Use JDK 8 or 14+ to run, if you want to avoid the ["illegal reflective access" warning about Retrofit][3] - All classes live in these packages. If you need to make small edits to scripts where there's - no auto-complete, wildcard imports can be used: + no auto-complete, wildcard imports can be used (in notebooks, they're added automatically): ```kotlin import com.gabrielfeo.gradle.enterprise.api.* @@ -190,7 +200,7 @@ import com.gabrielfeo.gradle.enterprise.api.model.extension.* [11]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/shutdown.html [12]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/-cache-config/cache-enabled.html [13]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/-cache-config/index.html -[14]: https://central.sonatype.com/artifact/com.gabrielfeo/gradle-enterprise-api-kotlin/2023.3.1 +[14]: https://central.sonatype.com/artifact/com.gabrielfeo/gradle-enterprise-api-kotlin/2023.4.0 [16]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/api-url.html [17]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/api-token.html [18]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/index.html @@ -199,10 +209,12 @@ import com.gabrielfeo.gradle.enterprise.api.model.extension.* [21]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/get-builds.html [22]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/get-gradle-attributes.html [23]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/-default-instance/index.html -[24]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-gradle-attributes-flow.html +[24]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-builds-flow.html [25]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/index.html [26]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/ [27]: ./examples/example-script.main.kts [28]: ./examples/example-project [29]: https://nbviewer.org/github/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb [30]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html +[31]: ./docs/AccessKeys.md +[32]: ./examples diff --git a/docs/AccessKeys.md b/docs/AccessKeys.md new file mode 100644 index 00000000..61b80995 --- /dev/null +++ b/docs/AccessKeys.md @@ -0,0 +1,30 @@ +# Access key / API token + +[All API requests require authentication][1]. Provide a valid access key of your Gradle Enterprise instance as the `GRADLE_ENTERPRISE_API_TOKEN` environment variable. + +## How to get an access key + +1. Sign in to Gradle Enterprise (with a user that has “Export build data” permission) +2. Go to "My settings" from the user menu in the top right-hand corner of the page +3. Go to "Access keys" from the sidebar +4. Click "Generate" on the right-hand side +5. Set key as the `GRADLE_ENTERPRISE_API_TOKEN` environment variable when using the library + +## Migrating from macOS keychain support + +This library used to support storing the key in the macOS keychain as `gradle-enterprise-api-kotlin`. +This feature was deprecated. You may use the method of your choice (secret managers, password manager CLIs, etc.) to store and retrieve the key to an environment. + +If you used the key from keychain and need a drop-in replacement: + +``` +# Create an alias in your shell to fetch the key from keychain +echo 'alias ge-api-token="security find-generic-password -w -a "$LOGNAME" -s gradle-enterprise-api-kotlin"' >> ~/.zshrc + +# Retrieve it to the environment variable before running the program +GRADLE_ENTERPRISE_API_TOKEN="$(ge-api-token)" ./my-script.main.kts +GRADLE_ENTERPRISE_API_TOKEN="$(ge-api-token)" jupyter lab +GRADLE_ENTERPRISE_API_TOKEN="$(ge-api-token)" idea my-project +``` + +[1]: https://docs.gradle.com/enterprise/api-manual/#access_control diff --git a/examples/example-notebooks/MostFrequentBuilds.ipynb b/examples/example-notebooks/MostFrequentBuilds.ipynb index 1a1bfb65..9be54949 100644 --- a/examples/example-notebooks/MostFrequentBuilds.ipynb +++ b/examples/example-notebooks/MostFrequentBuilds.ipynb @@ -35,7 +35,7 @@ "Add libraries to use, via line magics. `%use` is a [line magic](https://github.com/Kotlin/kotlin-jupyter#line-magics) of the Kotlin kernel that can do much more than adding the library. To illustrate, this setup can be replaced with a single line magic.\n", "\n", "```kotlin\n", - "@file:DependsOn(\"com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1\")\n", + "@file:DependsOn(\"com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0\")\n", "\n", "import com.gabrielfeo.gradle.enterprise.api.*\n", "import com.gabrielfeo.gradle.enterprise.api.model.*\n", @@ -45,7 +45,8 @@ "is the same as:\n", "\n", "```\n", - "%use gradle-enterprise-api-kotlin(version=2023.3.1)\n", + "%use gradle-enterprise-api-kotlin(version=2023.4.0)\n", + "\n", "```" ] }, @@ -53,39 +54,19 @@ "cell_type": "code", "execution_count": 1, "id": "97aad45e-fdb0-4ca3-8049-cfa7ce934bbb", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:43.176052Z", + "start_time": "2024-04-01T20:24:41.907349Z" + } + }, "outputs": [], "source": [ "%useLatestDescriptors\n", - "%use gradle-enterprise-api-kotlin(version=2023.3.1)\n", - "%use coroutines(v=1.7.1)" - ] - }, - { - "cell_type": "markdown", - "id": "6d9bd2a4-dab0-4366-9afe-c5f9439cdc18", - "metadata": {}, - "source": [ - "## Parameters\n", - "\n", - "Change these to process a longer or shorter time range (and test faster)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cb319237-fb3d-4973-80ff-0a1ef508cfed", - "metadata": {}, - "outputs": [], - "source": [ - "import java.time.*\n", + "%use gradle-enterprise-api-kotlin(version=2023.4.0)\n", + "%use coroutines(v=1.7.1)\n", "\n", - "val startDate = LocalDate.now().minusWeeks(1)\n", - "\n", - "val buildFilter: (GradleAttributes) -> Boolean = { build ->\n", - " \"LOCAL\" in build.tags\n", - " && !build.hasFailed\n", - "}" + "val api = GradleEnterpriseApi.newInstance()" ] }, { @@ -95,30 +76,53 @@ "source": [ "## Fetch builds\n", "\n", - "[getGradleAttributesFlow][1] is a utility to call `/api/builds` and join each ID with `/api/builds/{id}/gradle-attributes` at once.\n", + "Use [getBuildsFlow][1] to fetch all builds for a [query][2] with `/api/builds`.\n", "\n", - "[1]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/gradle-enterprise-api-kotlin/com.gabrielfeo.gradle.enterprise.api/get-gradle-attributes-flow.html" + "By default, \"builds\" from the API are just an ID and an upload time, but we can request more info to come in the same \n", + "response using the `models` parameter. \"Models\" are build details that would come from other endpoints. For example, \n", + "requesting models=[gradleAttributes][3] brings data from `/api/builds/{id}/gradle-attributes` in the same response.\n", + "\n", + "[1]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-builds-flow.html\n", + "[2]: https://docs.gradle.com/enterprise/api-manual/#advanced_search_syntax \n", + "[3]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api\n", + ".model/-build-model-name/gradle-attributes/index.html" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "588a699f", "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:45.458079Z", + "start_time": "2024-04-01T20:24:43.318588Z" + }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "17907 builds\n" + ] + } + ], "source": [ "import java.time.temporal.*\n", "import java.util.LinkedList\n", "\n", - "val api = GradleEnterpriseApi.newInstance()\n", "val builds: List = runBlocking {\n", - " val startMilli = startDate.atStartOfDay(ZoneId.of(\"UTC\")).toInstant().toEpochMilli()\n", - " api.buildsApi.getGradleAttributesFlow(since = startMilli)\n", - " .filter(buildFilter)\n", - " .toList(LinkedList())\n", - "}" + " api.buildsApi.getBuildsFlow(\n", + " fromInstant = 0,\n", + " query = \"\"\"buildStartTime<-7d tag:local buildOutcome:failed\"\"\",\n", + " models = listOf(BuildModelName.gradleAttributes),\n", + " ).map {\n", + " it.models!!.gradleAttributes!!.model!!\n", + " }.toList(LinkedList())\n", + "}\n", + "\n", + "println(\"${builds.size} builds\")" ] }, { @@ -133,9 +137,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "5cd75a65-a819-497e-87c2-f0911cc1554f", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:47.969270Z", + "start_time": "2024-04-01T20:24:45.457832Z" + } + }, "outputs": [ { "data": { @@ -347,7 +356,7 @@ } ], "source": [ - "%use dataframe(v=0.10.0)" + "%use dataframe(v=0.13.1)" ] }, { @@ -360,15 +369,108 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "adbd028f-d30a-489e-94bd-d8f968a659a3", "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:48.548709Z", + "start_time": "2024-04-01T20:24:47.958241Z" + }, "tags": [] }, "outputs": [ { "data": { - "application/kotlindataframe+json": "{\"nrow\":266,\"ncol\":2,\"columns\":[\"tasks\",\"count\"],\"kotlin_dataframe\":[{\"tasks\":\"app:assembleBrazilDebug\",\"count\":977},{\"tasks\":\"IDE sync\",\"count\":285},{\"tasks\":\"droid-cli:installDist\",\"count\":80},{\"tasks\":\"feature:order-delivery:waiting-platform-components:impl:compileReleaseSources\",\"count\":48},{\"tasks\":\"feature:home:impl:compileReleaseSources\",\"count\":17},{\"tasks\":\"feature:chat:impl:testReleaseUnitTest --tests br.com.ifood.chat.presentation.chat.viewModel.ChatViewModelTest.dispatchViewAction_withInitializeAction_andChatCreated_\",\"count\":16},{\"tasks\":\"clean\",\"count\":13},{\"tasks\":\"analytics-braze:testReleaseUnitTest --tests br.com.ifood.analytics.braze.AppBrazeAnalyticsProviderTest.sendEvent_featureFlagCanSendEvent_sendEvent\",\"count\":12},{\"tasks\":\"analytics-braze:testReleaseUnitTest --tests br.com.ifood.analytics.braze.AppBrazeAnalyticsProviderTest\",\"count\":12},{\"tasks\":\"app:assembleBrazilDebug :app:installBrazilDebug\",\"count\":11},{\"tasks\":\"buildPlugin\",\"count\":11},{\"tasks\":\"infrastructure:design-system:global-components-sample:assembleDebug\",\"count\":11},{\"tasks\":\"setupDependencies\",\"count\":10},{\"tasks\":\"feature:groceries:shopping-list:impl:testReleaseUnitTest --tests br.com.ifood.groceries.shoppinglist.presentation.shoppinglistvoice.ShoppingListVoiceSharedViewModelTest\",\"count\":8},{\"tasks\":\"feature:review:evaluating:impl:testReleaseUnitTest --tests br.com.ifood.review.presentation.viewmodel.ReviewViewModelTest\",\"count\":7},{\"tasks\":\"build-plugins:test --tests rasp.DexProtectorConfigManagerTest\",\"count\":7},{\"tasks\":\"build-plugins:functionalTest --tests rasp.AndroidRaspProtectionPluginTest\",\"count\":7},{\"tasks\":\"droid-cli:test --tests br.com.ifood.cli.util.RunCommandShellTest\",\"count\":7},{\"tasks\":\"feature:order-delivery:waiting:impl:testReleaseUnitTest\",\"count\":6},{\"tasks\":\"feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.contextcard.mapper.CartModelToMinimizedCartUiModelMapperTest\",\"count\":5}]}", + "application/kotlindataframe+json": { + "columns": [ + "tasks", + "count" + ], + "kotlin_dataframe": [ + { + "count": 4677, + "tasks": "app:assembleBrazilDebug" + }, + { + "count": 577, + "tasks": "kotlinLSPProjectDeps" + }, + { + "count": 420, + "tasks": "IDE sync" + }, + { + "count": 112, + "tasks": "feature:home:impl:testReleaseUnitTest --tests br.com.ifood.home.*" + }, + { + "count": 103, + "tasks": "feature:hits:cards:impl:testReleaseUnitTest --tests br.com.ifood.hits.communitybuytaxonomyitemslist.data.CommunityBuyTaxonomyItemsCardResponseToUiMapperTest" + }, + { + "count": 96, + "tasks": "feature:splash:impl:testReleaseUnitTest --tests br.com.ifood.splash.animation.CommemorativeCustomizationDefaultServiceTest" + }, + { + "count": 92, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.*" + }, + { + "count": 90, + "tasks": "feature:chat:core:impl:testReleaseUnitTest --tests br.com.ifood.chat.domain.service.ChatCallForegroundServiceTest" + }, + { + "count": 73, + "tasks": "feature:chat:core:impl:testReleaseUnitTest --tests br.com.ifood.chat.domain.service.ChatCallNotificationServiceTest" + }, + { + "count": 70, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.plugin.standard.items.item.ReplacementsControllerTest.initReplacements_forceUiModelRemap" + }, + { + "count": 58, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest" + }, + { + "count": 54, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.domain.factory.DefaultCartRemoteConfigsFactoryTest" + }, + { + "count": 54, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.domain.model.MerchantModelTest" + }, + { + "count": 51, + "tasks": "feature:chat:core:impl:testReleaseUnitTest --tests br.com.ifood.chat.domain.service.ChatCallForegroundServiceTest.onStartCommand_rejectCallIntent_stopsService" + }, + { + "count": 48, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.plugin.standard.clubpurchase.ClubPurchasePluginViewModelTest" + }, + { + "count": 48, + "tasks": "feature:hits:page:impl:testReleaseUnitTest --tests br.com.ifood.hits.page.presentation.HitsPageViewModelTest" + }, + { + "count": 45, + "tasks": "feature:discoverycards:impl:testReleaseUnitTest" + }, + { + "count": 44, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.domain.usecase.club.SetClubSummaryMetadataModelTest.invoke_withValidRulesAndRendering_returnCartWithUpdatedModel" + }, + { + "count": 43, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.processor.viewmodel.OrderPoolingProcessorTest.resumeOrderPolling_withPaymentDataEventAndPaymentEventNotAuthorized_handlePaymentActionData" + }, + { + "count": 41, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.processor.viewmodel.ClubSubscriptionProcessorTest" + } + ], + "ncol": 2, + "nrow": 2901 + }, "text/html": [ " \n", " \n", @@ -524,32 +626,180 @@ "}\n", "\n", "\n", + ":root {\n", + " --background: #fff;\n", + " --background-odd: #f5f5f5;\n", + " --background-hover: #d9edfd;\n", + " --header-text-color: #474747;\n", + " --text-color: #848484;\n", + " --text-color-dark: #000;\n", + " --text-color-medium: #737373;\n", + " --text-color-pale: #b3b3b3;\n", + " --inner-border-color: #aaa;\n", + " --bold-border-color: #000;\n", + " --link-color: #296eaa;\n", + " --link-color-pale: #296eaa;\n", + " --link-hover: #1a466c;\n", + "}\n", + "\n", + ":root[theme=\"dark\"], :root [data-jp-theme-light=\"false\"], .dataframe_dark{\n", + " --background: #303030;\n", + " --background-odd: #3c3c3c;\n", + " --background-hover: #464646;\n", + " --header-text-color: #dddddd;\n", + " --text-color: #b3b3b3;\n", + " --text-color-dark: #dddddd;\n", + " --text-color-medium: #b2b2b2;\n", + " --text-color-pale: #737373;\n", + " --inner-border-color: #707070;\n", + " --bold-border-color: #777777;\n", + " --link-color: #008dc0;\n", + " --link-color-pale: #97e1fb;\n", + " --link-hover: #00688e;\n", + "}\n", + "\n", + "p.dataframe_description {\n", + " color: var(--text-color-dark);\n", + "}\n", + "\n", + "table.dataframe {\n", + " font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n", + " font-size: 12px;\n", + " background-color: var(--background);\n", + " color: var(--text-color-dark);\n", + " border: none;\n", + " border-collapse: collapse;\n", + "}\n", + "\n", + "table.dataframe th, td {\n", + " padding: 6px;\n", + " border: 1px solid transparent;\n", + " text-align: left;\n", + "}\n", + "\n", + "table.dataframe th {\n", + " background-color: var(--background);\n", + " color: var(--header-text-color);\n", + "}\n", + "\n", + "table.dataframe td {\n", + " vertical-align: top;\n", + "}\n", + "\n", + "table.dataframe th.bottomBorder {\n", + " border-bottom-color: var(--bold-border-color);\n", + "}\n", + "\n", + "table.dataframe tbody > tr:nth-child(odd) {\n", + " background: var(--background-odd);\n", + "}\n", + "\n", + "table.dataframe tbody > tr:nth-child(even) {\n", + " background: var(--background);\n", + "}\n", + "\n", + "table.dataframe tbody > tr:hover {\n", + " background: var(--background-hover);\n", + "}\n", + "\n", + "table.dataframe a {\n", + " cursor: pointer;\n", + " color: var(--link-color);\n", + " text-decoration: none;\n", + "}\n", + "\n", + "table.dataframe tr:hover > td a {\n", + " color: var(--link-color-pale);\n", + "}\n", + "\n", + "table.dataframe a:hover {\n", + " color: var(--link-hover);\n", + " text-decoration: underline;\n", + "}\n", + "\n", + "table.dataframe img {\n", + " max-width: fit-content;\n", + "}\n", + "\n", + "table.dataframe th.complex {\n", + " background-color: var(--background);\n", + " border: 1px solid var(--background);\n", + "}\n", + "\n", + "table.dataframe .leftBorder {\n", + " border-left-color: var(--inner-border-color);\n", + "}\n", + "\n", + "table.dataframe .rightBorder {\n", + " border-right-color: var(--inner-border-color);\n", + "}\n", + "\n", + "table.dataframe .rightAlign {\n", + " text-align: right;\n", + "}\n", + "\n", + "table.dataframe .expanderSvg {\n", + " width: 8px;\n", + " height: 8px;\n", + " margin-right: 3px;\n", + "}\n", + "\n", + "table.dataframe .expander {\n", + " display: flex;\n", + " align-items: center;\n", + "}\n", + "\n", + "/* formatting */\n", + "\n", + "table.dataframe .null {\n", + " color: var(--text-color-pale);\n", + "}\n", + "\n", + "table.dataframe .structural {\n", + " color: var(--text-color-medium);\n", + " font-weight: bold;\n", + "}\n", + "\n", + "table.dataframe .dataFrameCaption {\n", + " font-weight: bold;\n", + "}\n", + "\n", + "table.dataframe .numbers {\n", + " color: var(--text-color-dark);\n", + "}\n", + "\n", + "table.dataframe td:hover .formatted .structural, .null {\n", + " color: var(--text-color-dark);\n", + "}\n", + "\n", + "table.dataframe tr:hover .formatted .structural, .null {\n", + " color: var(--text-color-dark);\n", + "}\n", "\n", "\n", " \n", " \n", " \n", - " \n", - "
      \n", + "
      \n", "\n", - "

      ... showing only top 20 of 266 rows

      DataFrame: rowsCount = 266, columnsCount = 2

      \n", + "

      ... showing only top 20 of 2901 rows

      DataFrame: rowsCount = 2901, columnsCount = 2

      \n", + "
      taskscount
      app:assembleBrazilDebug4677
      kotlinLSPProjectDeps577
      IDE sync420
      feature:home:impl:testReleaseUnitTest...112
      feature:hits:cards:impl:testReleaseUn...103
      feature:splash:impl:testReleaseUnitTe...96
      feature:checkout:core:impl:testReleas...92
      feature:chat:core:impl:testReleaseUni...90
      feature:chat:core:impl:testReleaseUni...73
      feature:checkout:core:impl:testReleas...70
      feature:checkout:core:impl:testReleas...58
      feature:checkout:core:impl:testReleas...54
      feature:checkout:core:impl:testReleas...54
      feature:chat:core:impl:testReleaseUni...51
      feature:checkout:core:impl:testReleas...48
      feature:hits:page:impl:testReleaseUni...48
      feature:discoverycards:impl:testRelea...45
      feature:checkout:core:impl:testReleas...44
      feature:checkout:core:impl:testReleas...43
      feature:checkout:core:impl:testReleas...41
      \n", " \n", " \n", " " ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -565,7 +815,7 @@ " count() into \"count\"\n", "}.sortByDesc(\"count\")\n", "\n", - "// Jupyter will render the last cell line\n", + "// Jupyter will render the last cell line (a String, an Int, a DataFrame, etc.)\n", "buildCounts" ] }, @@ -581,67 +831,28 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "8c28e684-0f3b-43d9-a3d2-7b1ef91fa6c4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
      \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:25:13.190618Z", + "start_time": "2024-04-01T20:24:48.537811Z" } - ], + }, + "outputs": [], "source": [ - "%use kandy(v=0.4.1)" + "%use kandy(v=0.6.0)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 13, "id": "293b2b4d", "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:25:13.226464Z", + "start_time": "2024-04-01T20:24:50.790909Z" + }, "tags": [] }, "outputs": [ @@ -652,23 +863,19 @@ "output": { "data": { "count": [ - 977, - 285, - 80, - 48, - 17 + 4677, + 577, + 420 ], "tasks": [ "app:assembleBrazilDebug", - "IDE sync", - "droid-cli:installDist", - "feature:order-delivery:waiting-platform-components:impl:compileReleaseSources", - "feature:home:impl:compileReleaseSources" + "kotlinLSPProjectDeps", + "IDE sync" ] }, "ggsize": { "height": 250, - "width": 1000 + "width": 800 }, "kind": "plot", "layers": [ @@ -679,7 +886,7 @@ "y": "tasks" }, "orientation": "y", - "position": "identity", + "position": "dodge", "sampling": "none", "stat": "identity" } @@ -703,59 +910,373 @@ "swing_enabled": true }, "text/html": [ - " \n", - "
      \n", - " " + " </script>\n", + " </body>\n", + "</html>\"> \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 4,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 4,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " app:assembleBrazilDebug\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " kotlinLSPProjectDeps\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " IDE sync\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " tasks\n", + " \n", + " \n", + " \n", + " \n", + " count\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " " ] }, - "execution_count": 7, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "plot(buildCounts.take(5)) {\n", - " barsH { \n", + "plot(buildCounts.take(3)) {\n", + " barsH {\n", " x(\"count\")\n", " y(\"tasks\")\n", " }\n", - " layout.size = 1000 to 250\n", + " layout.size = 800 to 250\n", "}" ] } @@ -773,7 +1294,7 @@ "name": "kotlin", "nbconvert_exporter": "", "pygments_lexer": "kotlin", - "version": "1.8.20" + "version": "1.9.10" } }, "nbformat": 4, diff --git a/examples/example-project/app/build.gradle.kts b/examples/example-project/app/build.gradle.kts index e348acb4..54822179 100644 --- a/examples/example-project/app/build.gradle.kts +++ b/examples/example-project/app/build.gradle.kts @@ -14,5 +14,5 @@ java { } dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") + implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") } diff --git a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt b/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt index 43af9a47..71dda635 100644 --- a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt +++ b/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt @@ -3,9 +3,7 @@ package com.gabrielfeo.gradle.enterprise.api.example.analysis import com.gabrielfeo.gradle.enterprise.api.* import com.gabrielfeo.gradle.enterprise.api.extension.* import com.gabrielfeo.gradle.enterprise.api.model.* -import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import java.time.* import java.util.LinkedList /** @@ -24,16 +22,16 @@ import java.util.LinkedList */ suspend fun mostFrequentBuilds( api: BuildsApi, - startDate: LocalDate = LocalDate.now().minusWeeks(1), - buildFilter: (GradleAttributes) -> Boolean = { build -> - "LOCAL" in build.tags - }, + startTime: String = "-7d", ) { // Fetch builds from the API - val startMilli = startDate.atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli() - val builds: List = api.getGradleAttributesFlow(since = startMilli) - .filter(buildFilter) - .toList(LinkedList()) + val builds: List = api.getBuildsFlow( + fromInstant = 0, + query = """buildStartTime<$startTime tag:local buildOutcome:failed""", + models = listOf(BuildModelName.gradleAttributes), + ).map { + it.models!!.gradleAttributes!!.model!! + }.toList(LinkedList()) // Process builds and count how many times each was invoked val buildCounts = builds.groupBy { build -> diff --git a/examples/example-script.main.kts b/examples/example-script.main.kts index 4abb8ed9..c0b6b853 100644 --- a/examples/example-script.main.kts +++ b/examples/example-script.main.kts @@ -17,7 +17,7 @@ * Run this with at least 1GB of heap to accomodate the fetched data: JAVA_OPTS=-Xmx1g */ -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") +@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") import com.gabrielfeo.gradle.enterprise.api.* import com.gabrielfeo.gradle.enterprise.api.model.* @@ -36,10 +36,13 @@ val buildFilter: (GradleAttributes) -> Boolean = { build -> // Fetch builds from the API val api = GradleEnterpriseApi.newInstance() val builds: List = runBlocking { - val startMilli = startDate.atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli() - api.buildsApi.getGradleAttributesFlow(since = startMilli) - .filter(buildFilter) - .toList(LinkedList()) + api.buildsApi.getBuildsFlow( + fromInstant = 0, + query = """buildStartTime<-7d tag:local buildOutcome:failed""", + models = listOf(BuildModelName.gradleAttributes), + ).map { + it.models!!.gradleAttributes!!.model!! + }.toList(LinkedList()) } // Process builds and count how many times each was invoked From 07888a6890afca24d70ce37531ffd33541a7cb21 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Tue, 2 Apr 2024 21:01:39 +0100 Subject: [PATCH 15/35] Declare library version in gradle.properties (#175) The release flow could be more standard. Currently: 1. version on source code is always `SNAPSHOT` 2. Tag is pushed, e.g. `2023.4.0` (`version=SNAPSHOT` even on tag ref) 3. A `publish` build is ran on tag ref to publish the artifact passing `-Pversion=2023.4.0` 4. The README is only then updated on main for the new version (on tag ref, the README is obsolete) But in most OSS projects, the version on source code corresponds to the version that code was released in, e.g. `version=2023.3.0` on the `2023.3.0` tag ref, and `main` declares the next version, `version=2023.4.0` ("Prepare for next release" commits). Adjusting to this also makes README versioning more intuitive. --- .github/scripts/test_update_api_spec_version.py | 4 ++-- .github/scripts/update_api_spec_version.py | 4 ++++ .github/workflows/publish-library.yml | 4 +++- gradle.properties | 9 ++++----- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/scripts/test_update_api_spec_version.py b/.github/scripts/test_update_api_spec_version.py index 637a2242..d9c18248 100755 --- a/.github/scripts/test_update_api_spec_version.py +++ b/.github/scripts/test_update_api_spec_version.py @@ -34,12 +34,12 @@ def test_main_without_update_available(self, mock_get, _): def assert_properties_version(self, file, version): with open(file.name) as file: - expected = f"gradle.enterprise.version={version}\n1=2\n" + expected = f"gradle.enterprise.version={version}\nversion={version}.0\n1=2\n" self.assertEqual(file.read(), expected) def properties_file(self, version): file = NamedTemporaryFile() - content = f"gradle.enterprise.version={version}\n1=2\n" + content = f"gradle.enterprise.version={version}\nversion={version}.0\n1=2\n" file.write(content.encode()) file.flush() return file diff --git a/.github/scripts/update_api_spec_version.py b/.github/scripts/update_api_spec_version.py index e30e02af..44bef8d2 100755 --- a/.github/scripts/update_api_spec_version.py +++ b/.github/scripts/update_api_spec_version.py @@ -41,8 +41,12 @@ def update_version(properties_file, new_version): for line in fileinput.input(properties_file, inplace=True): if '=' in line: k, v = line.strip().split('=', maxsplit=2) + # Update target API spec version if k == 'gradle.enterprise.version': line = f"{k}={new_version}\n" + # Update library version + if k == 'version': + line = f"{k}={new_version}.0\n" sys.stdout.write(line) diff --git a/.github/workflows/publish-library.yml b/.github/workflows/publish-library.yml index ea8df358..5c8e2f54 100644 --- a/.github/workflows/publish-library.yml +++ b/.github/workflows/publish-library.yml @@ -27,6 +27,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Verify that version property matches tag + if: ${{ inputs.dry_run != true }} + run: grep -qP '^version=${{ github.ref_name }}$' gradle.properties - name: gradle publishLibraryPublicationToMavenCentralRepository uses: ./.github/actions/build with: @@ -34,7 +37,6 @@ jobs: args: >- publishLibraryPublicationToMavenCentralRepository --rerun-tasks - '-Pversion=${{ github.ref_name }}' '-Pmaven.central.username=${{ secrets.MAVEN_CENTRAL_USERNAME }}' '-Pmaven.central.password=${{ secrets.MAVEN_CENTRAL_PASSWORD }}' '-Psigning.password=${{ secrets.GPG_PASSWORD }}' diff --git a/gradle.properties b/gradle.properties index 4aaaf10b..ccd9f7b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,8 @@ +group=com.gabrielfeo +artifact=gradle-enterprise-api-kotlin +version=2023.4.0 +gradle.enterprise.version=2023.4 org.gradle.jvmargs=-Xmx5g org.gradle.caching=true # Becomes default in Gradle 9.0 org.gradle.kotlin.dsl.skipMetadataVersionCheck=false -version=SNAPSHOT -# Must be later than 2022.1 -gradle.enterprise.version=2023.4 -group=com.gabrielfeo -artifact=gradle-enterprise-api-kotlin From 72f75f3247df51475bfab8c7bf1495c888fc6edb Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Wed, 3 Apr 2024 00:25:46 +0100 Subject: [PATCH 16/35] Move from java.util logging to SLF4J (#178) --- examples/example-project/app/build.gradle.kts | 2 +- library/build.gradle.kts | 7 ++- .../api/GradleEnterpriseApiIntegrationTest.kt | 8 +++- .../BuildsApiExtensionsIntegrationTest.kt | 2 +- .../api/internal/KeychainIntegrationTest.kt | 2 +- .../gradle/enterprise/api/Config.kt | 24 +++++----- .../enterprise/api/GradleEnterpriseApi.kt | 3 +- .../enterprise/api/internal/Keychain.kt | 13 ++++- .../enterprise/api/internal/LoggerFactory.kt | 22 +++++++++ .../enterprise/api/internal/OkHttpClient.kt | 47 ++++++++++++------- .../caching/CacheHitLoggingInterceptor.kt | 8 ++-- .../gradle/enterprise/api/OkHttpClientTest.kt | 20 +------- .../gradle/enterprise/api/RetrofitTest.kt | 2 +- 13 files changed, 98 insertions(+), 62 deletions(-) create mode 100644 library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt diff --git a/examples/example-project/app/build.gradle.kts b/examples/example-project/app/build.gradle.kts index 54822179..1f90c73d 100644 --- a/examples/example-project/app/build.gradle.kts +++ b/examples/example-project/app/build.gradle.kts @@ -9,7 +9,7 @@ application { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) + languageVersion.set(JavaLanguageVersion.of(11)) } } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 60f039b7..aa08ffc3 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -153,7 +153,7 @@ java { withSourcesJar() withJavadocJar() toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) + languageVersion.set(JavaLanguageVersion.of(11)) vendor.set(JvmVendorSpec.AZUL) } } @@ -170,7 +170,7 @@ tasks.withType().configureEach { remoteUrl.set(URL("$repoUrl/blob/$version/src/main/kotlin")) remoteLineSuffix.set("#L") } - jdkVersion.set(8) + jdkVersion.set(11) suppressGeneratedFiles.set(false) documentedVisibilities.set(setOf(PUBLIC)) perPackageOption { @@ -230,6 +230,7 @@ tasks.named("check") { tasks.named("integrationTest") { jvmArgs("-Xmx512m") + environment("GRADLE_ENTERPRISE_API_LOG_LEVEL", "DEBUG") } java { @@ -250,6 +251,8 @@ dependencies { implementation("com.squareup.retrofit2:converter-moshi:2.11.0") implementation("com.squareup.retrofit2:converter-scalars:2.11.0") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation("org.slf4j:slf4j-api:2.0.11") + implementation("ch.qos.logback:logback-classic:1.4.14") } publishing { diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt index d689ae09..c5360e0c 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt @@ -15,8 +15,12 @@ class GradleEnterpriseApiIntegrationTest { @Test fun canFetchBuildsWithDefaultConfig() = runTest { env = RealEnv - keychain = RealKeychain(RealSystemProperties) - val api = GradleEnterpriseApi.newInstance() + keychain = realKeychain() + val api = GradleEnterpriseApi.newInstance( + config = Config( + cacheConfig = Config.CacheConfig(cacheEnabled = false) + ) + ) val builds = api.buildsApi.getBuilds( since = 0, maxBuilds = 5, diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt index 6eaa75be..cbe8b541 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt @@ -16,7 +16,7 @@ class BuildsApiExtensionsIntegrationTest { init { env = RealEnv - keychain = RealKeychain(RealSystemProperties) + keychain = realKeychain() } private val recorder = RequestRecorder() diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt index 5cc94a5d..9df461c8 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt @@ -8,7 +8,7 @@ internal class KeychainIntegrationTest { @Test fun getApiToken() { env = RealEnv - keychain = RealKeychain(RealSystemProperties) + keychain = realKeychain() val result = keychain.get("gradle-enterprise-api-token") assertIs(result) assertFalse(result.token.isNullOrBlank(), "Keychain returned null or blank") diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt index 21d0a1ed..848cb6ea 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt @@ -1,10 +1,10 @@ package com.gabrielfeo.gradle.enterprise.api +import com.gabrielfeo.gradle.enterprise.api.Config.CacheConfig import com.gabrielfeo.gradle.enterprise.api.internal.* import okhttp3.Dispatcher import okhttp3.OkHttpClient import java.io.File -import java.util.logging.Logger import kotlin.time.Duration.Companion.days /** @@ -14,11 +14,14 @@ import kotlin.time.Duration.Companion.days data class Config( /** - * Enables debug logging from the library. All logging is output to stderr. By default, uses - * environment variable `GRADLE_ENTERPRISE_API_DEBUG_LOGGING` or `false`. + * Forces a log level for internal library classes. By default, the library includes + * logback-classic with logging disabled, aimed at notebook and script usage. Logging may be + * enabled for troubleshooting by changing log level to "INFO", "DEBUG", etc. + * + * To use different SLF4J bindings, simply exclude the logback dependency. */ - val debugLoggingEnabled: Boolean = - env["GRADLE_ENTERPRISE_API_DEBUG_LOGGING"].toBoolean(), + val logLevel: String? = + env["GRADLE_ENTERPRISE_API_LOG_LEVEL"], /** * Provides the URL of a Gradle Enterprise API instance REST API. By default, uses @@ -33,7 +36,7 @@ data class Config( * `gradle-enterprise-api-token` or environment variable `GRADLE_ENTERPRISE_API_TOKEN`. */ val apiToken: () -> String = { - requireEnvOrKeychainToken(debugLoggingEnabled = debugLoggingEnabled) + requireEnvOrKeychainToken() }, /** @@ -192,16 +195,11 @@ data class Config( ) } -internal fun requireEnvOrKeychainToken(debugLoggingEnabled: Boolean): String { +internal fun requireEnvOrKeychainToken(): String { if (systemProperties["os.name"] == "Mac OS X") { when (val result = keychain.get("gradle-enterprise-api-token")) { is KeychainResult.Success -> return result.token - is KeychainResult.Error -> { - if (debugLoggingEnabled) { - val logger = Logger.getGlobal() - logger.info("Failed to get key from keychain (${result.description})") - } - } + is KeychainResult.Error -> {} } } return env["GRADLE_ENTERPRISE_API_TOKEN"] diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt index a8c5648f..33be8287 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt @@ -1,5 +1,6 @@ package com.gabrielfeo.gradle.enterprise.api +import com.gabrielfeo.gradle.enterprise.api.internal.RealLoggerFactory import com.gabrielfeo.gradle.enterprise.api.internal.buildOkHttpClient import com.gabrielfeo.gradle.enterprise.api.internal.buildRetrofit import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.Serializer @@ -67,7 +68,7 @@ internal class RealGradleEnterpriseApi( ) : GradleEnterpriseApi { private val okHttpClient by lazy { - buildOkHttpClient(config = config) + buildOkHttpClient(config = config, RealLoggerFactory(config)) } private val retrofit: Retrofit by lazy { diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt index 78b4ac10..7066d53b 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt @@ -1,6 +1,15 @@ package com.gabrielfeo.gradle.enterprise.api.internal -internal var keychain: Keychain = RealKeychain(systemProperties) +import com.gabrielfeo.gradle.enterprise.api.Config +import org.slf4j.Logger + +internal var keychain: Keychain = realKeychain() +internal fun realKeychain() = RealKeychain( + RealSystemProperties, + // Setting level via env will work, via code won't. Not worth fixing, since keychain will + // be removed soon. + RealLoggerFactory(Config()).newLogger(RealKeychain::class), +) internal interface Keychain { fun get(entry: String): KeychainResult @@ -13,6 +22,7 @@ internal sealed interface KeychainResult { internal class RealKeychain( private val systemProperties: SystemProperties, + private val logger: Logger, ) : Keychain { override fun get( entry: String, @@ -23,6 +33,7 @@ internal class RealKeychain( "security", "find-generic-password", "-w", "-a", login, "-s", entry ).start() val status = process.waitFor() + logger.debug("Keychain exit status: $status)") if (status != 0) { return KeychainResult.Error("exit $status") } diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt new file mode 100644 index 00000000..01c8c039 --- /dev/null +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt @@ -0,0 +1,22 @@ +package com.gabrielfeo.gradle.enterprise.api.internal + +import ch.qos.logback.classic.Level +import com.gabrielfeo.gradle.enterprise.api.Config +import org.slf4j.Logger +import kotlin.reflect.KClass + +interface LoggerFactory { + fun newLogger(cls: KClass<*>): Logger +} + +class RealLoggerFactory( + private val config: Config, +) : LoggerFactory { + + override fun newLogger(cls: KClass<*>): Logger { + val impl = org.slf4j.LoggerFactory.getLogger(cls.java) as ch.qos.logback.classic.Logger + return impl.apply { + level = Level.valueOf(config.logLevel) + } + } +} diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt index 84dfc855..9b084f3b 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt @@ -5,12 +5,13 @@ import com.gabrielfeo.gradle.enterprise.api.internal.auth.HttpBearerAuth import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheEnforcingInterceptor import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheHitLoggingInterceptor import okhttp3.Cache +import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC import okhttp3.logging.HttpLoggingInterceptor.Level.BODY import java.time.Duration -import java.util.logging.Level -import java.util.logging.Logger +import org.slf4j.Logger /** * Base instance just so that multiple created [Config]s will share resources by default. @@ -24,13 +25,14 @@ internal val basicOkHttpClient by lazy { */ internal fun buildOkHttpClient( config: Config, + loggerFactory: LoggerFactory, ) = with(config.clientBuilder) { readTimeout(Duration.ofMillis(config.readTimeoutMillis)) if (config.cacheConfig.cacheEnabled) { - cache(buildCache(config)) + cache(buildCache(config, loggerFactory)) } - addInterceptors(config) - addNetworkInterceptors(config) + addInterceptors(config, loggerFactory) + addNetworkInterceptors(config, loggerFactory) build().apply { config.maxConcurrentRequests?.let { dispatcher.maxRequests = it @@ -39,31 +41,44 @@ internal fun buildOkHttpClient( } } -private fun OkHttpClient.Builder.addInterceptors(config: Config) { - if (config.debugLoggingEnabled && config.cacheConfig.cacheEnabled) { - addInterceptor(CacheHitLoggingInterceptor()) +private fun OkHttpClient.Builder.addInterceptors( + config: Config, + loggerFactory: LoggerFactory, +) { + if (config.cacheConfig.cacheEnabled) { + val logger = loggerFactory.newLogger(CacheHitLoggingInterceptor::class) + addInterceptor(CacheHitLoggingInterceptor(logger)) } } -private fun OkHttpClient.Builder.addNetworkInterceptors(config: Config) { +private fun OkHttpClient.Builder.addNetworkInterceptors( + config: Config, + loggerFactory: LoggerFactory, +) { if (config.cacheConfig.cacheEnabled) { addNetworkInterceptor(buildCacheEnforcingInterceptor(config)) } - if (config.debugLoggingEnabled) { - addNetworkInterceptor(HttpLoggingInterceptor().apply { level = BODY }) + val httpLogger = loggerFactory.newLogger(HttpLoggingInterceptor::class) + getHttpLoggingInterceptorForLogger(httpLogger)?.let { + addNetworkInterceptor(it) } addNetworkInterceptor(HttpBearerAuth("bearer", config.apiToken())) } +private fun getHttpLoggingInterceptorForLogger(logger: Logger): Interceptor? = when { + logger.isDebugEnabled -> HttpLoggingInterceptor(logger = logger::debug).apply { level = BASIC } + logger.isTraceEnabled -> HttpLoggingInterceptor(logger = logger::debug).apply { level = BODY } + else -> null +} + internal fun buildCache( - config: Config + config: Config, + loggerFactory: LoggerFactory, ): Cache { val cacheDir = config.cacheConfig.cacheDir val maxSize = config.cacheConfig.maxCacheSize - if (config.debugLoggingEnabled) { - val logger = Logger.getGlobal() - logger.log(Level.INFO, "HTTP cache dir: $cacheDir (max ${maxSize}B)") - } + val logger = loggerFactory.newLogger(Cache::class) + logger.debug("HTTP cache dir: {} (max {}B)", cacheDir, maxSize) return Cache(cacheDir, maxSize) } diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt index 07f23289..d55b80d7 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt @@ -2,11 +2,11 @@ package com.gabrielfeo.gradle.enterprise.api.internal.caching import okhttp3.Interceptor import okhttp3.Response -import java.util.logging.Level -import java.util.logging.Logger +import org.slf4j.Logger + internal class CacheHitLoggingInterceptor( - private val logger: Logger = Logger.getGlobal(), + private val logger: Logger, ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { @@ -14,7 +14,7 @@ internal class CacheHitLoggingInterceptor( val response = chain.proceed(chain.request()) val wasHit = with(response) { cacheResponse != null && networkResponse == null } val hitOrMiss = if (wasHit) "hit" else "miss" - logger.log(Level.INFO, "Cache $hitOrMiss: $url") + logger.debug("Cache {}: {}", hitOrMiss, url) return response } } diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt index 41f68205..1ddcc252 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt @@ -40,24 +40,6 @@ class OkHttpClientTest { assertEquals(1, client.dispatcher.maxRequestsPerHost) } - @Test - fun `Given debug logging and cache enabled, adds logging interceptors`() { - val client = buildClient( - "GRADLE_ENTERPRISE_API_DEBUG_LOGGING" to "true", - "GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true", - ) - assertTrue(client.interceptors.any { it is CacheHitLoggingInterceptor }) - } - - @Test - fun `Given debug logging disabled, doesn't add logging interceptors`() { - val client = buildClient( - "GRADLE_ENTERPRISE_API_DEBUG_LOGGING" to "false", - "GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true", - ) - assertTrue(client.interceptors.none { it is CacheHitLoggingInterceptor }) - } - @Test fun `Given cache enabled, configures caching`() { val client = buildClient("GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true") @@ -96,6 +78,6 @@ class OkHttpClientTest { null -> Config() else -> Config(clientBuilder = clientBuilder) } - return buildOkHttpClient(config) + return buildOkHttpClient(config, RealLoggerFactory(config)) } } diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt index b3f23c84..c4e077fa 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt @@ -37,7 +37,7 @@ class RetrofitTest { val config = Config() return buildRetrofit( config = config, - client = buildOkHttpClient(config), + client = buildOkHttpClient(config, RealLoggerFactory(config)), moshi = Moshi.Builder().build() ) } From 456f7696be3861ae5f6c53f9d05e00824915761e Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Thu, 4 Apr 2024 15:44:43 +0100 Subject: [PATCH 17/35] Extract test suite setup to plugin (#179) `:library` build logic became convoluted. Splitting it into discrete convention plugins improves maintainability. Create a `build-logic` project and extract test suite setup to it as a precompiled script plugin. --- build-logic/build.gradle.kts | 9 +++ build-logic/settings.gradle.kts | 8 +++ .../gabrielfeo/kotlin-jvm-library.gradle.kts | 5 ++ .../com/gabrielfeo/test-suites.gradle.kts | 40 ++++++++++++++ build.gradle.kts | 1 - library/build.gradle.kts | 55 +++---------------- settings.gradle.kts | 4 ++ 7 files changed, 74 insertions(+), 48 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts create mode 100644 build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000..4b648284 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,9 @@ +@file:Suppress("UnstableApiUsage") + +plugins { + `kotlin-dsl` +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 00000000..90be858b --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,8 @@ +@file:Suppress("UnstableApiUsage") + +dependencyResolutionManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts new file mode 100644 index 00000000..50a48b50 --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts @@ -0,0 +1,5 @@ +package com.gabrielfeo + +plugins { + id("org.jetbrains.kotlin.jvm") +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts new file mode 100644 index 00000000..34fb8ef0 --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts @@ -0,0 +1,40 @@ +package com.gabrielfeo + +plugins { + id("org.jetbrains.kotlin.jvm") + `java-test-fixtures` +} + +testing { + suites { + // 'test' is registered by default + register("integrationTest") + withType().configureEach { + useKotlinTest() + } + } +} + +tasks.named("check") { + dependsOn("integrationTest") +} + +kotlin { + target { + val main by compilations.getting + val integrationTest by compilations.getting + val test by compilations.getting + val testFixtures by compilations.getting + test.associateWith(main) + test.associateWith(testFixtures) + integrationTest.associateWith(main) + integrationTest.associateWith(testFixtures) + testFixtures.associateWith(main) + } +} + +// TODO Unapply test-fixtures and delete the source set, since we're not publishing it? +components.getByName("java").apply { + withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } + withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } +} diff --git a/build.gradle.kts b/build.gradle.kts index 5038d350..aebec65b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,4 @@ plugins { - id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false id("org.jetbrains.dokka") version "1.9.20" apply false id("org.openapi.generator") version "7.3.0" apply false } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index aa08ffc3..f72ba605 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -5,13 +5,13 @@ import org.jetbrains.dokka.DokkaConfiguration.Visibility.PUBLIC import org.jetbrains.dokka.gradle.DokkaTask plugins { - id("org.jetbrains.kotlin.jvm") + id("com.gabrielfeo.kotlin-jvm-library") + id("com.gabrielfeo.test-suites") id("org.jetbrains.dokka") id("org.openapi.generator") `java-library` - `java-test-fixtures` `maven-publish` - `signing` + signing } val repoUrl = "https://github.com/gabrielfeo/gradle-enterprise-api-kotlin" @@ -158,11 +158,6 @@ java { } } -components.getByName("java").apply { - withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } - withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } -} - tasks.withType().configureEach { dokkaSourceSets.all { sourceLink { @@ -189,45 +184,6 @@ tasks.named("javadocJar") { from(tasks.dokkaHtml) } -testing { - suites { - getByName("test") { - dependencies { - implementation("com.squareup.okhttp3:mockwebserver:4.12.0") - implementation("com.squareup.okio:okio:3.9.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") - } - } - register("integrationTest") { - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") - implementation("com.google.guava:guava:33.1.0-jre") - } - } - withType().configureEach { - useKotlinTest() - } - } -} - -kotlin { - target { - val main by compilations.getting - val integrationTest by compilations.getting - val test by compilations.getting - val testFixtures by compilations.getting - test.associateWith(main) - test.associateWith(testFixtures) - integrationTest.associateWith(main) - integrationTest.associateWith(testFixtures) - testFixtures.associateWith(main) - } -} - -tasks.named("check") { - dependsOn("integrationTest") -} - tasks.named("integrationTest") { jvmArgs("-Xmx512m") environment("GRADLE_ENTERPRISE_API_LOG_LEVEL", "DEBUG") @@ -253,6 +209,11 @@ dependencies { api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") implementation("org.slf4j:slf4j-api:2.0.11") implementation("ch.qos.logback:logback-classic:1.4.14") + testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") + testImplementation("com.squareup.okio:okio:3.9.0") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + integrationTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + integrationTestImplementation("com.google.guava:guava:33.1.0-jre") } publishing { diff --git a/settings.gradle.kts b/settings.gradle.kts index fd448217..cc78a073 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,7 @@ +pluginManagement { + includeBuild("./build-logic") +} + plugins { id("com.gradle.enterprise") version("3.13.2") id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") From ec785f3fd6f7b067efd092d51450d249f3c7691c Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Thu, 4 Apr 2024 20:36:24 +0100 Subject: [PATCH 18/35] Extract code generation setup to plugin (#180) `:library` build logic became convoluted. Splitting it into discrete convention plugins improves maintainability. Extract code generation setup to it as a precompiled script plugin. --- .github/workflows/publish-library.yml | 2 +- build-logic/build.gradle.kts | 1 + .../develocity-api-code-generation.gradle.kts | 141 ++++++++++++++++++ .../gabrielfeo/kotlin-jvm-library.gradle.kts | 1 + build.gradle.kts | 1 - library/.openapi-generator-ignore | 1 + library/build.gradle.kts | 135 +---------------- 7 files changed, 146 insertions(+), 136 deletions(-) create mode 100644 build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts diff --git a/.github/workflows/publish-library.yml b/.github/workflows/publish-library.yml index 5c8e2f54..2e497e00 100644 --- a/.github/workflows/publish-library.yml +++ b/.github/workflows/publish-library.yml @@ -44,6 +44,6 @@ jobs: artifact-name: 'outputs' path-to-upload: | library/build/*-api.yaml - library/build/generated/open-api-generator/**/* + library/build/generated/openapi-generator/**/* library/build/publications/**/* library/build/libs/**/* diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 4b648284..7283b405 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -6,4 +6,5 @@ plugins { dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") + implementation("org.openapitools:openapi-generator-gradle-plugin:7.3.0") } diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts new file mode 100644 index 00000000..3067da2e --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts @@ -0,0 +1,141 @@ +package com.gabrielfeo + +import org.gradle.kotlin.dsl.* + +plugins { + id("com.gabrielfeo.kotlin-jvm-library") + id("org.openapi.generator") +} + +val localSpecPath = providers.gradleProperty("localSpecPath") +val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse( + providers.gradleProperty("gradle.enterprise.version").map { geVersion -> + val specName = "gradle-enterprise-$geVersion-api.yaml" + "https://docs.gradle.com/enterprise/api-manual/ref/$specName" + } +) + +val downloadApiSpec by tasks.registering { + onlyIf { !localSpecPath.isPresent() } + val spec = resources.text.fromUri(remoteSpecUrl) + val specName = remoteSpecUrl.map { it.substringAfterLast('/') } + val outFile = project.layout.buildDirectory.file(specName) + inputs.property("Spec URL", remoteSpecUrl) + outputs.file(outFile) + doLast { + logger.info("Downloaded API spec from ${remoteSpecUrl.get()}") + spec.asFile().renameTo(outFile.get().asFile) + } +} + +openApiGenerate { + generatorName.set("kotlin") + val spec = when { + localSpecPath.isPresent() -> localSpecPath.map { rootProject.file(it).absolutePath } + else -> downloadApiSpec.map { it.outputs.files.first().absolutePath } + } + inputSpec.set(spec) + val generateDir = project.layout.buildDirectory.file("generated/openapi-generator") + outputDir.set(generateDir.map { it.asFile.absolutePath }) + val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore") + ignoreFileOverride.set(ignoreFile.asFile.absolutePath) + apiPackage.set("com.gabrielfeo.gradle.enterprise.api") + modelPackage.set("com.gabrielfeo.gradle.enterprise.api.model") + packageName.set("com.gabrielfeo.gradle.enterprise.api.internal") + invokerPackage.set("com.gabrielfeo.gradle.enterprise.api.internal") + additionalProperties.put("library", "jvm-retrofit2") + additionalProperties.put("useCoroutines", true) +} + +tasks.openApiGenerate { + val srcDir = File(outputDir.get(), "src/main/kotlin") + doFirst { + logger.info("Using API spec ${inputSpec.get()}") + } + // Replace Response with X in every method return type of GradleEnterpriseApi.kt + doLast { + ant.withGroovyBuilder { + "replaceregexp"( + "match" to ": Response<(.*?)>$", + "replace" to """: \1""", + "flags" to "gm", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", + ) + } + } + } + // Add @JvmSuppressWildcards to avoid square/retrofit#3275 + doLast { + ant.withGroovyBuilder { + "replaceregexp"( + "match" to "interface", + "replace" to """ + @JvmSuppressWildcards + interface + """.trimIndent(), + "flags" to "m", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", + ) + } + } + } + // Workaround for properties generated with `arrayListOf(null,null)` as default value + doLast { + ant.withGroovyBuilder { + "replaceregexp"( + "match" to """arrayListOf\(null,null\)""", + "replace" to """emptyList()""", + "flags" to "gm", + ) { + "fileset"( + "dir" to srcDir + ) + } + } + } + // Workaround for missing imports of exploded queries + doLast { + val modelPackage = openApiGenerate.modelPackage.get() + val modelPackagePattern = modelPackage.replace(".", "\\.") + ant.withGroovyBuilder { + "replaceregexp"( + "match" to """(?:import $modelPackagePattern.[.\w]+\s)+""", + "replace" to "import $modelPackage.*\n", + "flags" to "m", + ) { + "fileset"( + "dir" to srcDir + ) + } + } + } + // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes + doLast { + ant.withGroovyBuilder { + "replaceregexp"( + "match" to "Minus", + "replace" to "", + "flags" to "mg", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + ) + } + } + } +} + +sourceSets { + main { + java { + srcDir(tasks.openApiGenerate) + } + } +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts index 50a48b50..d3e9f37e 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts @@ -2,4 +2,5 @@ package com.gabrielfeo plugins { id("org.jetbrains.kotlin.jvm") + `java-library` } diff --git a/build.gradle.kts b/build.gradle.kts index aebec65b..85c8ef6c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("org.jetbrains.dokka") version "1.9.20" apply false - id("org.openapi.generator") version "7.3.0" apply false } val group by project.properties diff --git a/library/.openapi-generator-ignore b/library/.openapi-generator-ignore index 447e7c5a..b3ac9fad 100644 --- a/library/.openapi-generator-ignore +++ b/library/.openapi-generator-ignore @@ -1,4 +1,5 @@ build/generated/openapi-generator/* +build/generated/openapi-generator/.* build/generated/openapi-generator/.github/* build/generated/openapi-generator/api/* build/generated/openapi-generator/gradle/**/* diff --git a/library/build.gradle.kts b/library/build.gradle.kts index f72ba605..0e6719e0 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -6,9 +6,9 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { id("com.gabrielfeo.kotlin-jvm-library") + id("com.gabrielfeo.develocity-api-code-generation") id("com.gabrielfeo.test-suites") id("org.jetbrains.dokka") - id("org.openapi.generator") `java-library` `maven-publish` signing @@ -16,139 +16,6 @@ plugins { val repoUrl = "https://github.com/gabrielfeo/gradle-enterprise-api-kotlin" -val localSpecPath = providers.gradleProperty("localSpecPath") -val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse( - providers.gradleProperty("gradle.enterprise.version").map { geVersion -> - val specName = "gradle-enterprise-$geVersion-api.yaml" - "https://docs.gradle.com/enterprise/api-manual/ref/$specName" - } -) - -val downloadApiSpec by tasks.registering { - onlyIf { !localSpecPath.isPresent() } - val spec = resources.text.fromUri(remoteSpecUrl) - val specName = remoteSpecUrl.map { it.substringAfterLast('/') } - val outFile = project.layout.buildDirectory.file(specName) - inputs.property("Spec URL", remoteSpecUrl) - outputs.file(outFile) - doLast { - logger.info("Downloaded API spec from ${remoteSpecUrl.get()}") - spec.asFile().renameTo(outFile.get().asFile) - } -} - -openApiGenerate { - generatorName.set("kotlin") - val spec = when { - localSpecPath.isPresent() -> localSpecPath.map { rootProject.file(it).absolutePath } - else -> downloadApiSpec.map { it.outputs.files.first().absolutePath } - } - inputSpec.set(spec) - val generateDir = project.layout.buildDirectory.file("generated/openapi-generator") - outputDir.set(generateDir.map { it.asFile.absolutePath }) - val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore") - ignoreFileOverride.set(ignoreFile.asFile.absolutePath) - apiPackage.set("com.gabrielfeo.gradle.enterprise.api") - modelPackage.set("com.gabrielfeo.gradle.enterprise.api.model") - packageName.set("com.gabrielfeo.gradle.enterprise.api.internal") - invokerPackage.set("com.gabrielfeo.gradle.enterprise.api.internal") - additionalProperties.put("library", "jvm-retrofit2") - additionalProperties.put("useCoroutines", true) -} - -tasks.openApiGenerate.configure { - val srcDir = File(outputDir.get(), "src/main/kotlin") - doFirst { - logger.info("Using API spec ${inputSpec.get()}") - } - // Replace Response with X in every method return type of GradleEnterpriseApi.kt - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to ": Response<(.*?)>$", - "replace" to """: \1""", - "flags" to "gm", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", - ) - } - } - } - // Add @JvmSuppressWildcards to avoid square/retrofit#3275 - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to "interface", - "replace" to """ - @JvmSuppressWildcards - interface - """.trimIndent(), - "flags" to "m", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", - ) - } - } - } - // Workaround for properties generated with `arrayListOf(null,null)` as default value - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to """arrayListOf\(null,null\)""", - "replace" to """emptyList()""", - "flags" to "gm", - ) { - "fileset"( - "dir" to srcDir - ) - } - } - } - // Workaround for missing imports of exploded queries - doLast { - val modelPackage = openApiGenerate.modelPackage.get() - val modelPackagePattern = modelPackage.replace(".", "\\.") - ant.withGroovyBuilder { - "replaceregexp"( - "match" to """(?:import $modelPackagePattern.[.\w]+\s)+""", - "replace" to "import $modelPackage.*\n", - "flags" to "m", - ) { - "fileset"( - "dir" to srcDir - ) - } - } - } - // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to "Minus", - "replace" to "", - "flags" to "mg", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", - ) - } - } - } -} - -sourceSets { - main { - java { - srcDir(tasks.openApiGenerate) - } - } -} - java { withSourcesJar() withJavadocJar() From af43de9312ed7dd60a4405dd86db0672aedecd4b Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Thu, 4 Apr 2024 21:25:15 +0100 Subject: [PATCH 19/35] Add tests on generated API post-processing (#181) Extract the logic of post-processing the generated Develocity API code into a proper Task and add tests. Allows it to be tested on its own, as the test setup for the whole plugin would be more complex (arguably not worth it for such a small project). Setup could be simpler by not using Ant and just unit-testing it, but simply having tests is a more important first step. Also remove the "Workaround for properties generated with `arrayListOf(null,null)` as default value". It was no longer needed (fixed in some past openapi-generator release), hence the resulting API is unchanged (see PR comment with diff). On `generated-api-diff` the changes for base-ref-checkout must be done in a separate PR for diffing to work for this PR. In base ref, the task doesn't exist yet and the output dir is the old one. --- .github/workflows/pr.yml | 4 +- .github/workflows/publish-library.yml | 2 +- build-logic/build.gradle.kts | 13 ++ .../task/PostProcessGeneratedApiTest.kt | 180 ++++++++++++++++++ .../develocity-api-code-generation.gradle.kts | 98 ++-------- .../kotlin/com/gabrielfeo/no-op.gradle.kts | 3 + .../task/PostProcessGeneratedApi.kt | 99 ++++++++++ library/.openapi-generator-ignore | 22 +-- 8 files changed, 321 insertions(+), 100 deletions(-) create mode 100644 build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt create mode 100644 build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts create mode 100644 build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d51e152b..de21b3d2 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -43,8 +43,8 @@ jobs: - name: gradle openApiGenerate (PR ref) uses: ./.github/actions/build with: - args: 'openApiGenerate' - - run: mv ./library/build/generated/openapi-generator ./pr-ref-api + args: 'openApiGenerate postProcessGeneratedApi' + - run: mv ./library/build/post-processed-api ./pr-ref-api - name: Checkout base ref uses: actions/checkout@v4 with: diff --git a/.github/workflows/publish-library.yml b/.github/workflows/publish-library.yml index 2e497e00..9f320388 100644 --- a/.github/workflows/publish-library.yml +++ b/.github/workflows/publish-library.yml @@ -44,6 +44,6 @@ jobs: artifact-name: 'outputs' path-to-upload: | library/build/*-api.yaml - library/build/generated/openapi-generator/**/* + library/build/post-processed-api/**/* library/build/publications/**/* library/build/libs/**/* diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 7283b405..5678b73c 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -4,7 +4,20 @@ plugins { `kotlin-dsl` } +testing { + suites { + register("functionalTest") { + useJUnitJupiter() + } + } +} + +gradlePlugin { + testSourceSets(sourceSets["functionalTest"]) +} + dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") implementation("org.openapitools:openapi-generator-gradle-plugin:7.3.0") + "functionalTestImplementation"(project) } diff --git a/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt b/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt new file mode 100644 index 00000000..605ad7d7 --- /dev/null +++ b/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt @@ -0,0 +1,180 @@ +package com.gabrielfeo.task + +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.io.File + +class PostProcessGeneratedApiTest { + + // Prefer over TempDir to inspect files after tests when troubleshooting + private val tempDir: File = File("./build/test-workdir/") + + @BeforeEach + fun setup() { + tempDir.deleteRecursively() + tempDir.mkdirs() + } + + /** + * - Fixes missing model imports by replacing all with a wildcard (OpenAPITools/openapi-generator#14871) + * - Replaces return types of Response for X, for more idiomatic usage with coroutines + * - Adds @JvmSuppressWildcards to avoid square/retrofit#3275 + */ + @Test + fun apiInterfacePostProcessing() = testPostProcessing( + inputPath = "com/gabrielfeo/gradle/enterprise/api/BuildsApi.kt", + inputContent = """ + package com.gabrielfeo.gradle.enterprise.api + + import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.CollectionFormats.* + import retrofit2.http.* + import retrofit2.Response + import okhttp3.RequestBody + import com.squareup.moshi.Json + + import com.gabrielfeo.gradle.enterprise.api.model.ApiProblem + import com.gabrielfeo.gradle.enterprise.api.model.Build + import com.gabrielfeo.gradle.enterprise.api.model.BuildModelQuery + import com.gabrielfeo.gradle.enterprise.api.model.BuildQuery + import com.gabrielfeo.gradle.enterprise.api.model.BuildsQuery + import com.gabrielfeo.gradle.enterprise.api.model.GradleAttributes + import com.gabrielfeo.gradle.enterprise.api.model.GradleBuildCachePerformance + import com.gabrielfeo.gradle.enterprise.api.model.GradleNetworkActivity + import com.gabrielfeo.gradle.enterprise.api.model.GradleProject + import com.gabrielfeo.gradle.enterprise.api.model.MavenAttributes + import com.gabrielfeo.gradle.enterprise.api.model.MavenBuildCachePerformance + import com.gabrielfeo.gradle.enterprise.api.model.MavenDependencyResolution + import com.gabrielfeo.gradle.enterprise.api.model.MavenModule + + interface BuildsApi { + /** + * Get the common attributes of a Build Scan. + * The contained attributes are build tool agnostic. + * Responses: + * - 200: The common attributes of a Build Scan. + * - 400: The request cannot be fulfilled due to a problem. + * - 404: The referenced resource either does not exist or the permissions to know about it are missing. + * - 500: The server encountered an unexpected error. + * - 503: The server is not ready to handle the request. + * + * @param id The Build Scan ID. + * @param models The list of build models to return in the response for each build. If not provided, no models are returned. (optional) + * @param availabilityWaitTimeoutSecs The time in seconds the server should wait for ingestion before returning a wait timeout response. (optional) + * @return [Build] + */ + @GET("api/builds/{id}") + suspend fun getBuild(@Path("id") id: kotlin.String, @Query("models") models: kotlin.collections.List? = null, @Query("availabilityWaitTimeoutSecs") availabilityWaitTimeoutSecs: kotlin.Int? = null): Response + """.trimIndent(), + outputPath = "com/gabrielfeo/gradle/enterprise/api/BuildsApi.kt", + outputContent = """ + package com.gabrielfeo.gradle.enterprise.api + + import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.CollectionFormats.* + import retrofit2.http.* + import retrofit2.Response + import okhttp3.RequestBody + import com.squareup.moshi.Json + + import com.gabrielfeo.gradle.enterprise.api.model.* + + @JvmSuppressWildcards + interface BuildsApi { + /** + * Get the common attributes of a Build Scan. + * The contained attributes are build tool agnostic. + * Responses: + * - 200: The common attributes of a Build Scan. + * - 400: The request cannot be fulfilled due to a problem. + * - 404: The referenced resource either does not exist or the permissions to know about it are missing. + * - 500: The server encountered an unexpected error. + * - 503: The server is not ready to handle the request. + * + * @param id The Build Scan ID. + * @param models The list of build models to return in the response for each build. If not provided, no models are returned. (optional) + * @param availabilityWaitTimeoutSecs The time in seconds the server should wait for ingestion before returning a wait timeout response. (optional) + * @return [Build] + */ + @GET("api/builds/{id}") + suspend fun getBuild(@Path("id") id: kotlin.String, @Query("models") models: kotlin.collections.List? = null, @Query("availabilityWaitTimeoutSecs") availabilityWaitTimeoutSecs: kotlin.Int? = null): Build + """.trimIndent(), + ) + + /** + * - Fixes enum case names: gradleMinusAttributes -> gradleAttributes + */ + @Test + fun buildModelNameEnumPostProcessing() = testPostProcessing( + inputPath = "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + inputContent = """ + @JsonClass(generateAdapter = false) + enum class BuildModelName(val value: kotlin.String) { + + @Json(name = "gradle-attributes") + gradleMinusAttributes("gradle-attributes"), + + @Json(name = "gradle-build-cache-performance") + gradleMinusBuildMinusCacheMinusPerformance("gradle-build-cache-performance"), + """.trimIndent(), + outputPath = "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + outputContent = """ + @JsonClass(generateAdapter = false) + enum class BuildModelName(val value: kotlin.String) { + + @Json(name = "gradle-attributes") + gradleAttributes("gradle-attributes"), + + @Json(name = "gradle-build-cache-performance") + gradleBuildCachePerformance("gradle-build-cache-performance"), + """.trimIndent(), + ) + + private fun testPostProcessing( + inputPath: String, + inputContent: String, + outputPath: String, + outputContent: String, + ) { + val inputDir = File(tempDir, "input").also { it.mkdirs() } + val outputDir = File(tempDir, "output").also { it.mkdirs() } + File(inputDir, inputPath).apply { + parentFile.mkdirs() + writeText(inputContent) + } + val projectDir = writeTestProject(inputDir, outputDir) + runBuild(projectDir, listOf("postProcessGeneratedApi", "--stacktrace")) + assertEquals(outputContent, File(outputDir, outputPath).readText()) + } + + @Suppress("SameParameterValue") + private fun runBuild(projectDir: File, args: List) { + GradleRunner.create() + .withProjectDir(projectDir) + .withPluginClasspath() + .withArguments(args) + .build() + } + + private fun writeTestProject(inputDir: File, outputDir: File): File { + val projectDir = File(tempDir, "project").also { it.mkdirs() } + File(projectDir, "settings.gradle").writeText("") + File(projectDir, "build.gradle").writeText( + // language=groovy + """ + import com.gabrielfeo.task.PostProcessGeneratedApi + + plugins { + id("com.gabrielfeo.no-op") + } + + tasks.register("postProcessGeneratedApi", PostProcessGeneratedApi) { + originalFiles = new File("${inputDir.absolutePath}") + modelsPackage = "com.gabrielfeo.gradle.enterprise.api.model" + postProcessedFiles = new File("${outputDir.absolutePath}") + } + """.trimIndent() + ) + return projectDir + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts index 3067da2e..af6a286d 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts @@ -1,5 +1,6 @@ package com.gabrielfeo +import com.gabrielfeo.task.PostProcessGeneratedApi import org.gradle.kotlin.dsl.* plugins { @@ -35,8 +36,9 @@ openApiGenerate { else -> downloadApiSpec.map { it.outputs.files.first().absolutePath } } inputSpec.set(spec) - val generateDir = project.layout.buildDirectory.file("generated/openapi-generator") - outputDir.set(generateDir.map { it.asFile.absolutePath }) + val generateDir = project.layout.buildDirectory.dir("generated-api") + .map { it.asFile.absolutePath } + outputDir.set(generateDir) val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore") ignoreFileOverride.set(ignoreFile.asFile.absolutePath) apiPackage.set("com.gabrielfeo.gradle.enterprise.api") @@ -47,95 +49,19 @@ openApiGenerate { additionalProperties.put("useCoroutines", true) } -tasks.openApiGenerate { - val srcDir = File(outputDir.get(), "src/main/kotlin") - doFirst { - logger.info("Using API spec ${inputSpec.get()}") - } - // Replace Response with X in every method return type of GradleEnterpriseApi.kt - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to ": Response<(.*?)>$", - "replace" to """: \1""", - "flags" to "gm", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", - ) - } - } - } - // Add @JvmSuppressWildcards to avoid square/retrofit#3275 - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to "interface", - "replace" to """ - @JvmSuppressWildcards - interface - """.trimIndent(), - "flags" to "m", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", - ) - } - } - } - // Workaround for properties generated with `arrayListOf(null,null)` as default value - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to """arrayListOf\(null,null\)""", - "replace" to """emptyList()""", - "flags" to "gm", - ) { - "fileset"( - "dir" to srcDir - ) - } - } - } - // Workaround for missing imports of exploded queries - doLast { - val modelPackage = openApiGenerate.modelPackage.get() - val modelPackagePattern = modelPackage.replace(".", "\\.") - ant.withGroovyBuilder { - "replaceregexp"( - "match" to """(?:import $modelPackagePattern.[.\w]+\s)+""", - "replace" to "import $modelPackage.*\n", - "flags" to "m", - ) { - "fileset"( - "dir" to srcDir - ) - } - } - } - // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to "Minus", - "replace" to "", - "flags" to "mg", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", - ) - } - } - } +val postProcessGeneratedApi by tasks.registering(PostProcessGeneratedApi::class) { + val generatedSrc = tasks.openApiGenerate + .flatMap { it.outputDir } + .map { File(it) } + originalFiles.convention(project.layout.dir(generatedSrc)) + postProcessedFiles.convention(project.layout.buildDirectory.dir("post-processed-api")) + modelsPackage.convention(tasks.openApiGenerate.flatMap { it.modelPackage }) } sourceSets { main { java { - srcDir(tasks.openApiGenerate) + srcDir(postProcessGeneratedApi) } } } diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts new file mode 100644 index 00000000..bc122ecc --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts @@ -0,0 +1,3 @@ +package com.gabrielfeo + +// Plugin is only used to test the tasks logic. Applying it adds the task classes to the classpath. \ No newline at end of file diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt b/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt new file mode 100644 index 00000000..5d6b9a77 --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt @@ -0,0 +1,99 @@ +package com.gabrielfeo.task + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.withGroovyBuilder +import java.io.File +import javax.inject.Inject + +@CacheableTask +abstract class PostProcessGeneratedApi @Inject constructor( + private val fsOperations: FileSystemOperations, +) : DefaultTask() { + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val originalFiles: DirectoryProperty + + @get:Input + abstract val modelsPackage: Property + + @get:OutputDirectory + abstract val postProcessedFiles: DirectoryProperty + + @TaskAction + fun doWork() { + postProcessedFiles.get().asFile.deleteRecursively() + fsOperations.copy { + from(originalFiles) + into(postProcessedFiles) + } + postProcess( + srcDir = postProcessedFiles.get().dir("src/main/kotlin").asFile, + modelsPackage = modelsPackage.get() + ) + } + + private fun postProcess(srcDir: File, modelsPackage: String) { + // Replace Response with X in every method return type of GradleEnterpriseApi.kt + ant.withGroovyBuilder { + "replaceregexp"( + "match" to ": Response<(.*?)>$", + "replace" to """: \1""", + "flags" to "gm", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", + ) + } + } + // Add @JvmSuppressWildcards to avoid square/retrofit#3275 + ant.withGroovyBuilder { + "replaceregexp"( + "match" to "interface", + "replace" to """ + @JvmSuppressWildcards + interface + """.trimIndent(), + "flags" to "m", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", + ) + } + } + // Workaround for missing imports of exploded queries + val escapedModelPackage = modelsPackage.replace(".", "\\.") + val lastModelImportInFilePattern = """(?:import $escapedModelPackage.[.\w]+\s)+""" + ant.withGroovyBuilder { + "replaceregexp"( + "match" to lastModelImportInFilePattern, + // Import all models instead: current + missing ones + "replace" to "import $modelsPackage.*\n", + "flags" to "m", + ) { + "fileset"( + "dir" to srcDir + ) + } + } + // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes + ant.withGroovyBuilder { + "replaceregexp"( + "match" to "Minus", + "replace" to "", + "flags" to "mg", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + ) + } + } + } +} \ No newline at end of file diff --git a/library/.openapi-generator-ignore b/library/.openapi-generator-ignore index b3ac9fad..692c5d76 100644 --- a/library/.openapi-generator-ignore +++ b/library/.openapi-generator-ignore @@ -1,11 +1,11 @@ -build/generated/openapi-generator/* -build/generated/openapi-generator/.* -build/generated/openapi-generator/.github/* -build/generated/openapi-generator/api/* -build/generated/openapi-generator/gradle/**/* -build/generated/openapi-generator/docs/* -build/generated/openapi-generator/src/main/AndroidManifest.xml -build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/infrastructure/ApiClient.kt -build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt -build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/DevelocityApi.kt -build/generated/openapi-generator/src/test/**/* +build/generated-api/* +build/generated-api/.* +build/generated-api/.github/* +build/generated-api/api/* +build/generated-api/gradle/**/* +build/generated-api/docs/* +build/generated-api/src/main/AndroidManifest.xml +build/generated-api/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/infrastructure/ApiClient.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/DevelocityApi.kt +build/generated-api/src/test/**/* From fef03cd6eeaba6f34ae98ff9723d15c43ffec24e Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Thu, 4 Apr 2024 21:47:41 +0100 Subject: [PATCH 20/35] Update paths for generated-api-diff (#182) Separate step mentioned in #181 --- .github/workflows/pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index de21b3d2..2ad8a9cc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -53,8 +53,8 @@ jobs: - name: gradle openApiGenerate (base ref) uses: ./.github/actions/build with: - args: '-p ./base-ref-checkout openApiGenerate' - - run: mv ./base-ref-checkout/library/build/generated/openapi-generator ./base-ref-api + args: '-p ./base-ref-checkout openApiGenerate postProcessGeneratedApi' + - run: mv ./base-ref-checkout/library/build/post-processed-api ./base-ref-api - name: Diff generated APIs run: | echo -e '### Generated API diff\n\n```diff' > comment.md From de97cca8113316327348439a68e7ec6b56ea4393 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Thu, 4 Apr 2024 22:19:18 +0100 Subject: [PATCH 21/35] Simplify example-project (#183) The example project was intended to be cloneable as-is but that adds unnecessary complexity. Delete `.gitattributes`, `gradlew`, etc. and make it a single-module project (delete `app`). Also move the example script to `example-scripts`. --- .editorconfig | 5 + .github/dependabot.yml | 4 - README.md | 2 +- build.gradle.kts | 10 - examples/build.gradle.kts | 19 +- examples/example-project/.gitattributes | 9 - examples/example-project/.gitignore | 5 - examples/example-project/app/build.gradle.kts | 18 -- examples/example-project/build.gradle.kts | 18 +- examples/example-project/gradle.properties | 6 - .../gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 8 - examples/example-project/gradlew | 249 ------------------ examples/example-project/gradlew.bat | 92 ------- examples/example-project/settings.gradle.kts | 13 - .../gradle/enterprise/api/example/Main.kt | 0 .../example/analysis/MostFrequentBuilds.kt | 0 .../example-script.main.kts | 0 settings.gradle.kts | 3 +- 19 files changed, 38 insertions(+), 423 deletions(-) create mode 100644 .editorconfig delete mode 100644 examples/example-project/.gitattributes delete mode 100644 examples/example-project/.gitignore delete mode 100644 examples/example-project/app/build.gradle.kts delete mode 100644 examples/example-project/gradle.properties delete mode 100644 examples/example-project/gradle/wrapper/gradle-wrapper.jar delete mode 100644 examples/example-project/gradle/wrapper/gradle-wrapper.properties delete mode 100755 examples/example-project/gradlew delete mode 100644 examples/example-project/gradlew.bat delete mode 100644 examples/example-project/settings.gradle.kts rename examples/example-project/{app => }/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt (100%) rename examples/example-project/{app => }/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt (100%) rename examples/{ => example-scripts}/example-script.main.kts (100%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..474f37b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.kt, *.kts] +indent_style = space +indent_size = 4 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 333f7f1d..d472299b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,10 +20,6 @@ updates: retrofit: patterns: - "com.squareup.retrofit2:*" - - package-ecosystem: "gradle" - directory: "/examples/example-project/" - schedule: - interval: "daily" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/README.md b/README.md index 58c6ab12..a6206b8e 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ import com.gabrielfeo.gradle.enterprise.api.model.extension.* [24]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-builds-flow.html [25]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/index.html [26]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/ -[27]: ./examples/example-script.main.kts +[27]: ./examples/example-scripts/example-script.main.kts [28]: ./examples/example-project [29]: https://nbviewer.org/github/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb [30]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html diff --git a/build.gradle.kts b/build.gradle.kts index 85c8ef6c..f70e0f5f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,3 @@ plugins { id("org.jetbrains.dokka") version "1.9.20" apply false } - -val group by project.properties -val artifact by project.properties - -project(":examples:example-project:app").configurations.configureEach { - resolutionStrategy.dependencySubstitution { - substitute(module("$group:$artifact")) - .using(project(":library")) - } -} diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index f09bed60..a21cc38a 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -4,20 +4,27 @@ plugins { base } +// Cross-configure so we don't pollute the example buildscript +project("example-project").configurations.configureEach { + resolutionStrategy.dependencySubstitution { + substitute(module("com.gabrielfeo:gradle-enterprise-api-kotlin")) + .using(project(":library")) + } +} + val exampleTestTasks = ArrayList>() exampleTestTasks += tasks.register("runExampleScript") { group = "Application" - description = "Runs the 'example-script.main.kts' script" - commandLine("kotlinc", "-script", file("example-script.main.kts")) + description = "Runs the './example-scripts/example-script.main.kts' script" + commandLine("kotlinc", "-script", file("./example-scripts/example-script.main.kts")) environment("JAVA_OPTS", "-Xmx1g") } -exampleTestTasks += tasks.register("runExampleProject") { +exampleTestTasks += tasks.register("runExampleProject") { group = "Application" - description = "Runs examples/example-project as a standalone build" - dir = file("example-project") - tasks = listOf("run") + description = "Runs examples/example-project" + dependsOn(":examples:example-project:run") } val notebooks = fileTree(file("example-notebooks")) { diff --git a/examples/example-project/.gitattributes b/examples/example-project/.gitattributes deleted file mode 100644 index 097f9f98..00000000 --- a/examples/example-project/.gitattributes +++ /dev/null @@ -1,9 +0,0 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# Linux start script should use lf -/gradlew text eol=lf - -# These are Windows script files and should use crlf -*.bat text eol=crlf - diff --git a/examples/example-project/.gitignore b/examples/example-project/.gitignore deleted file mode 100644 index 1b6985c0..00000000 --- a/examples/example-project/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore Gradle project-specific cache directory -.gradle - -# Ignore Gradle build output directory -build diff --git a/examples/example-project/app/build.gradle.kts b/examples/example-project/app/build.gradle.kts deleted file mode 100644 index 1f90c73d..00000000 --- a/examples/example-project/app/build.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - id("org.jetbrains.kotlin.jvm") - application -} - -application { - mainClass.set("com.gabrielfeo.gradle.enterprise.api.example.MainKt") -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - } -} - -dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") -} diff --git a/examples/example-project/build.gradle.kts b/examples/example-project/build.gradle.kts index e3873143..87300690 100644 --- a/examples/example-project/build.gradle.kts +++ b/examples/example-project/build.gradle.kts @@ -1,3 +1,19 @@ plugins { - id("org.jetbrains.kotlin.jvm") version "1.9.23" apply false + // in your project, replace for id("org.jetbrains.kotlin.jvm") + id("com.gabrielfeo.kotlin-jvm-library") + application +} + +application { + mainClass.set("com.gabrielfeo.gradle.enterprise.api.example.MainKt") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +dependencies { + implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") } diff --git a/examples/example-project/gradle.properties b/examples/example-project/gradle.properties deleted file mode 100644 index 5b8859a1..00000000 --- a/examples/example-project/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -# This file was generated by the Gradle 'init' task. -# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties -org.gradle.jvmargs=-Xmx1g -org.gradle.parallel=true -org.gradle.caching=true - diff --git a/examples/example-project/gradle/wrapper/gradle-wrapper.jar b/examples/example-project/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e6441136f3d4ba8a0da8d277868979cfbc8ad796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
      NnX)EH+Nua)3Y(c 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/examples/example-project/gradlew.bat b/examples/example-project/gradlew.bat deleted file mode 100644 index 25da30db..00000000 --- a/examples/example-project/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/examples/example-project/settings.gradle.kts b/examples/example-project/settings.gradle.kts deleted file mode 100644 index 57a2ce80..00000000 --- a/examples/example-project/settings.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -rootProject.name = "example-project" - -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") -} - -dependencyResolutionManagement { - repositories { - mavenCentral() - } -} - -include(":app") diff --git a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt similarity index 100% rename from examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt rename to examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt diff --git a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt similarity index 100% rename from examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt rename to examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt diff --git a/examples/example-script.main.kts b/examples/example-scripts/example-script.main.kts similarity index 100% rename from examples/example-script.main.kts rename to examples/example-scripts/example-script.main.kts diff --git a/settings.gradle.kts b/settings.gradle.kts index cc78a073..33402b7d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,7 +9,8 @@ plugins { include( ":library", - ":examples:example-project:app", + ":examples", + ":examples:example-project", ) dependencyResolutionManagement { From 835e9ab2b88c5d21b9ee2d60db9661a183f0d174 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:33:02 +0100 Subject: [PATCH 22/35] Bump GE API spec version to 2024.1 (#172) - Bump API spec version to 2024.0 - Bump library version to 2024.1.0 (next release) - Expose new `AuthApi` from library - Speed up integration test execution by requesting less builds --- README.md | 7 ++-- .../develocity-api-code-generation.gradle.kts | 7 +++- gradle.properties | 6 ++- library/build.gradle.kts | 1 - .../BuildsApiExtensionsIntegrationTest.kt | 11 +++-- .../enterprise/api/GradleEnterpriseApi.kt | 2 + .../api/extension/BuildsApiExtensions.kt | 3 ++ .../gradle/enterprise/api/FakeBuildsApi.kt | 1 + .../api/FakeGradleEnterpriseApiScaffold.kt | 41 ++++++++++--------- 9 files changed, 48 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index a6206b8e..e472d291 100644 --- a/README.md +++ b/README.md @@ -65,16 +65,17 @@ dependencies { ## Usage The [`GradleEnterpriseApi`][9] interface represents the Gradle Enterprise REST API. It contains -the 6 APIs exactly as listed in the [REST API Manual][5]: +all the APIs exactly as listed in the [REST API Manual][5]: ```kotlin interface GradleEnterpriseApi { val buildsApi: BuildsApi - val buildCacheApi: BuildCacheApi - val projectsApi: projectsApi val testsApi: TestsApi + val buildCacheApi: BuildCacheApi + val projectsApi: ProjectsApi val metaApi: MetaApi val testDistributionApi: TestDistributionApi + val authApi: AuthApi // ... } ``` diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts index af6a286d..3cbe8522 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts @@ -11,7 +11,11 @@ plugins { val localSpecPath = providers.gradleProperty("localSpecPath") val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse( providers.gradleProperty("gradle.enterprise.version").map { geVersion -> - val specName = "gradle-enterprise-$geVersion-api.yaml" + val majorVersion = geVersion.substringBefore('.').toInt() + val specName = when { + majorVersion <= 2023 -> "gradle-enterprise-$geVersion-api.yaml" + else -> "develocity-$geVersion-api.yaml" + } "https://docs.gradle.com/enterprise/api-manual/ref/$specName" } ) @@ -47,6 +51,7 @@ openApiGenerate { invokerPackage.set("com.gabrielfeo.gradle.enterprise.api.internal") additionalProperties.put("library", "jvm-retrofit2") additionalProperties.put("useCoroutines", true) + cleanupOutput.set(true) } val postProcessGeneratedApi by tasks.registering(PostProcessGeneratedApi::class) { diff --git a/gradle.properties b/gradle.properties index ccd9f7b8..594dabec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,10 @@ group=com.gabrielfeo artifact=gradle-enterprise-api-kotlin -version=2023.4.0 -gradle.enterprise.version=2023.4 +version=2024.1.0 +gradle.enterprise.version=2024.1 org.gradle.jvmargs=-Xmx5g org.gradle.caching=true # Becomes default in Gradle 9.0 org.gradle.kotlin.dsl.skipMetadataVersionCheck=false +systemProp.scan.capture-build-logging=false +systemProp.scan.capture-test-logging=false diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 0e6719e0..3e7adb2f 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -52,7 +52,6 @@ tasks.named("javadocJar") { } tasks.named("integrationTest") { - jvmArgs("-Xmx512m") environment("GRADLE_ENTERPRISE_API_LOG_LEVEL", "DEBUG") } diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt index cbe8b541..7222faef 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt @@ -28,29 +28,32 @@ class BuildsApiExtensionsIntegrationTest { } @Test - fun getBuildsFlowPreservesParamsAcrossRequests() = runTest(timeout = 3.minutes) { + fun getBuildsFlowPreservesParamsAcrossRequests() = runTest(timeout = 6.minutes) { api.buildsApi.getBuildsFlow( since = 0, query = "user:*", models = listOf(BuildModelName.gradleAttributes), + allModels = true, reverse = true, - ).take(2000).collect() + buildsPerPage = 2, + ).take(4).collect() recorder.requests.forEach { assertUrlParam(it, "query", "user:*") assertUrlParam(it, "models", "gradle-attributes") + assertUrlParam(it, "allModels", "true") assertUrlParam(it, "reverse", "true") } } @Test fun getBuildsFlowReplacesSinceForFromBuildAfterFirstRequest() = runTest { - api.buildsApi.getBuildsFlow(since = 1).take(2000).collect() + api.buildsApi.getBuildsFlow(since = 1, buildsPerPage = 2).take(10).collect() assertReplacedForFromBuildAfterFirstRequest(param = "since" to "1") } @Test fun getBuildsFlowReplacesFromInstantForFromBuildAfterFirstRequest() = runTest { - api.buildsApi.getBuildsFlow(fromInstant = 1).take(2000).collect() + api.buildsApi.getBuildsFlow(fromInstant = 1, buildsPerPage = 2).take(10).collect() assertReplacedForFromBuildAfterFirstRequest(param = "fromInstant" to "1") } diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt index 33be8287..8166de52 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt @@ -34,6 +34,7 @@ import retrofit2.create */ interface GradleEnterpriseApi { + val authApi: AuthApi val buildsApi: BuildsApi val buildCacheApi: BuildCacheApi val projectsApi: ProjectsApi @@ -79,6 +80,7 @@ internal class RealGradleEnterpriseApi( ) } + override val authApi: AuthApi by lazy { retrofit.create() } override val buildsApi: BuildsApi by lazy { retrofit.create() } override val buildCacheApi: BuildCacheApi by lazy { retrofit.create() } override val projectsApi: ProjectsApi by lazy { retrofit.create() } diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt index e5f7cd76..ae9dc67c 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt @@ -33,6 +33,7 @@ fun BuildsApi.getBuildsFlow( maxWaitSecs: Int? = null, buildsPerPage: Int = API_MAX_BUILDS, models: List? = null, + allModels: Boolean? = false, ): Flow { return flow { var builds = getBuilds( @@ -45,6 +46,7 @@ fun BuildsApi.getBuildsFlow( maxWaitSecs = maxWaitSecs, maxBuilds = buildsPerPage, models = models, + allModels = allModels, ) emitAll(builds.asFlow()) while (builds.isNotEmpty()) { @@ -55,6 +57,7 @@ fun BuildsApi.getBuildsFlow( maxWaitSecs = maxWaitSecs, maxBuilds = buildsPerPage, models = models, + allModels = allModels, ) emitAll(builds.asFlow()) } diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt index 87af1da0..31f52be4 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt @@ -21,6 +21,7 @@ class FakeBuildsApi( maxWaitSecs: Int?, query: String?, models: List?, + allModels: Boolean?, ): List { getBuildsCallCount.value++ check((reverse ?: maxWaitSecs ?: query ?: models) == null) { "Not supported" } diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt index a6903f38..775c58a4 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt @@ -12,6 +12,7 @@ interface FakeBuildsApiScaffold : BuildsApi { override suspend fun getBuild( id: String, models: List?, + allModels: Boolean?, availabilityWaitTimeoutSecs: Int?, ): Build { TODO("Not yet implemented") @@ -27,49 +28,45 @@ interface FakeBuildsApiScaffold : BuildsApi { maxWaitSecs: Int?, query: String?, models: List?, + allModels: Boolean?, ): List { TODO("Not yet implemented") } - override suspend fun getGradleNetworkActivity( + override suspend fun getGradleArtifactTransformExecutions( id: String, availabilityWaitTimeoutSecs: Int?, - ): GradleNetworkActivity { + ): GradleArtifactTransformExecutions { TODO("Not yet implemented") } - override suspend fun getMavenDependencyResolution( - id: String, - availabilityWaitTimeoutSecs: Int?, - ): MavenDependencyResolution { + override suspend fun getGradleAttributes(id: String, availabilityWaitTimeoutSecs: Int?): GradleAttributes { TODO("Not yet implemented") } - override suspend fun getGradleAttributes( + override suspend fun getGradleBuildCachePerformance( id: String, availabilityWaitTimeoutSecs: Int?, - ): GradleAttributes { + ): GradleBuildCachePerformance { TODO("Not yet implemented") } - override suspend fun getGradleBuildCachePerformance( - id: String, - availabilityWaitTimeoutSecs: Int?, - ): GradleBuildCachePerformance { + override suspend fun getGradleDeprecations(id: String, availabilityWaitTimeoutSecs: Int?): GradleDeprecations { TODO("Not yet implemented") } - override suspend fun getGradleProjects( + override suspend fun getGradleNetworkActivity( id: String, availabilityWaitTimeoutSecs: Int?, - ): List { + ): GradleNetworkActivity { TODO("Not yet implemented") } - override suspend fun getMavenAttributes( - id: String, - availabilityWaitTimeoutSecs: Int?, - ): MavenAttributes { + override suspend fun getGradleProjects(id: String, availabilityWaitTimeoutSecs: Int?): List { + TODO("Not yet implemented") + } + + override suspend fun getMavenAttributes(id: String, availabilityWaitTimeoutSecs: Int?): MavenAttributes { TODO("Not yet implemented") } @@ -80,10 +77,14 @@ interface FakeBuildsApiScaffold : BuildsApi { TODO("Not yet implemented") } - override suspend fun getMavenModules( + override suspend fun getMavenDependencyResolution( id: String, availabilityWaitTimeoutSecs: Int?, - ): List { + ): MavenDependencyResolution { + TODO("Not yet implemented") + } + + override suspend fun getMavenModules(id: String, availabilityWaitTimeoutSecs: Int?): List { TODO("Not yet implemented") } } \ No newline at end of file From 294e25a585caba833191522ac7cfa6151b435225 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 01:23:35 +0100 Subject: [PATCH 23/35] Update API spec bump workflow to new name (#185) Handle new name of yaml file and test against the latest HTML. Update commit message. Add missing packages to requirements.txt. Part of #184 --- .github/scripts/requirements.txt | 7 + .../scripts/test_resources/api_manual.html | 3822 +++++++++++++++-- .../scripts/test_update_api_spec_version.py | 2 +- .github/scripts/update_api_spec_version.py | 2 +- .github/workflows/update-api-spec.yml | 4 +- 5 files changed, 3475 insertions(+), 362 deletions(-) diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 791ab187..f7660f2b 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -1 +1,8 @@ +certifi==2024.2.2 +charset-normalizer==3.3.2 +gitdb==4.0.11 GitPython==3.1.43 +idna==3.6 +requests==2.31.0 +smmap==5.0.1 +urllib3==2.2.1 diff --git a/.github/scripts/test_resources/api_manual.html b/.github/scripts/test_resources/api_manual.html index 8a2d9f59..68aac1bc 100644 --- a/.github/scripts/test_resources/api_manual.html +++ b/.github/scripts/test_resources/api_manual.html @@ -1,366 +1,3472 @@ - - - - - - - Gradle Enterprise API User Manual | Gradle Enterprise Docs - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - -
      -
      -
      -
      -

      The Gradle Enterprise API allows programmatic interaction with various aspects of Gradle Enterprise, from configuration to inspecting build data.

      -
      -
      -
      -
      -

      Fundamentals

      -
      -
      -

      The Gradle Enterprise API is a REST-style API using JSON as the data format.

      -
      -
      -

      OpenAPI

      -
      -

      The API is defined using the OpenAPI standard, which is a declarative specification that allows tools and libraries to generate client code. The specification can be found in the Reference section.

      -
      -
      -
      -

      Versioning and compatibility

      -
      -

      The API is generally backwards compatible.

      -
      -
      -

      New functionality (e.g. endpoints, response attributes) may be added in new major versions (i.e. 2022.1, 2022.2).

      -
      -
      -

      Breaking changes will occur infrequently if at all, and only after a reasonable deprecation period. Deprecations will be communicated by:

      -
      -
      -
        -
      • The release notes for the major Gradle Enterprise version introducing the deprecation.

      • -
      • The deprecated annotation within the OpenAPI specification.

      • -
      • The Deprecated HTTP response header (indicating when the operation was deprecated).

      • -
      • The Sunset HTTP response header (indicating when the operation will be removed).

      • -
      -
      -
      -

      API documentation may be refined in patch releases, along with implementation defects that cause deviation from the specification.

      -
      -
      + $(window).scroll(adjustTOC).click(adjustTOC); + $(adjustTOC); + + + + -
      -
      -

      Getting started

      -
      -
      -

      Access control

      -
      -

      All requests must provide a Gradle Enterprise access key as “bearer token”.

      -
      -
      -

      To create an access key:

      -
      -
      -
        -
      1. Sign in to Gradle Enterprise.

      2. -
      3. Access "My settings" from the user menu in the top right-hand corner of the page.

      4. -
      5. Access "Access keys" from the left-hand menu.

      6. -
      7. Click "Generate" on the right-hand side and copy the generated access key.

      8. -
      -
      -
      -

      Different requests require different user permissions, as describe via the API specification.

      -
      -
      -

      To view the permissions assigned to you:

      -
      -
      -
        -
      1. Sign in to Gradle Enterprise.

      2. -
      3. Access "My settings" from the user menu in the top right-hand corner of the page.

      4. -
      5. Access "Permissions" from the left-hand menu.

      6. -
      7. Check if you have the required permissions.

      8. -
      -
      -
      -

      If you do not have the required permission, contact your Gradle Enterprise administrator.

      -
      -
      -
      -

      Making a request

      -
      -

      The following example demonstrates using cURL to make a request to the builds endpoint, which requires the “Access build data via the API” permission.

      -
      -
      -
      -
      $ curl -H "Authorization: Bearer 7asejatf24zun43yshqufp7qi4ovcefxpykbwzqbzilcpwzb52ja" "https://ge.mycompany.com/api/builds?since=0&maxBuilds=3"
      -
      -
      -
      -
      -
      [
      -  {
      -    "id": "7asfp6iy3d5ey",
      -    "availableAt": 1645452789334,
      -    "buildToolType": "gradle",
      -    "buildToolVersion": "7.4",
      -    "buildAgentVersion": "3.8.1"
      -  },
      -  {
      -    "id": "1saxebtd5d4xs",
      -    "availableAt": 1645452795414,
      -    "buildToolType": "maven",
      -    "buildToolVersion": "3.8.4",
      -    "buildAgentVersion": "1.12.4"
      -  },
      -  {
      -    "id": "hx4k23knk64ri",
      -    "availableAt": 1645453205526,
      -    "buildToolType": "gradle",
      -    "buildToolVersion": "7.4",
      -    "buildAgentVersion": "3.8.1"
      -  }
      -]
      -
      -
      -
      -

      The builds endpoint can be used to discover the builds observed by the system. This example fetches the 3 oldest builds due to the since=0&maxBuilds=3 parameters.

      -
      -
      -
      -

      Next steps

      -
      -

      Check out the sample project.

      -
      -
      -

      To learn more about the functionality provided by the API and what you can do with it, see Reference.

      -
      -
      -
      -
      -
      -

      Reference

      -
      -
      -

      The reference documentation is useful for discovering the functionality of the API.

      -
      -
      -

      The reference specification is the API described using the OpenAPI standard and is useful for generating client code with OpenAPI-based tooling.

      -
      - - -
      -
      -
      -

      Appendix A: About the Export API

      -
      -
      -

      The Gradle Enterprise API described in this document will eventually replace the Gradle Enterprise Export API. At this time, the Gradle Enterprise API does not allow consuming the raw events of a build as the Export API does.

      -
      -
      -

      If your existing usage of the Export API can be replaced with the easier-to-use Gradle Enterprise API, please consider migrating. For assistance, please contact Gradle Enterprise support.

      -
      +
      +
      +
      +
      +

      The Develocity API allows programmatic interaction with various aspects of Develocity, from configuration to inspecting build data.

      +
      +
      +
      +
      +

      + + Fundamentals +

      +
      +
      +

      The Develocity API is a REST-style API using JSON as the data format.

      +
      +
      +

      + + OpenAPI +

      +
      +

      + The API is defined using the + OpenAPI standard + , which is a declarative specification that allows + tools and libraries + to generate client code. The specification can be found in the + Reference + section. +

      +
      +
      +
      +

      + + Versioning and compatibility +

      +
      +

      The API is generally backwards compatible.

      +
      +
      +

      New functionality (e.g. endpoints, response attributes) may be added in new major versions (i.e. 2022.1, 2022.2).

      +
      +
      +

      Breaking changes will occur infrequently if at all, and only after a reasonable deprecation period. Deprecations will be communicated by:

      +
      +
      +
        +
      • +

        The release notes for the major Develocity version introducing the deprecation.

        +
      • +
      • +

        + The + + deprecated + + annotation within the OpenAPI specification. +

        +
      • +
      • +

        + The + + Deprecated + + HTTP response header (indicating when the operation was deprecated). +

        +
      • +
      • +

        + The + + Sunset + + HTTP response header (indicating when the operation will be removed). +

        +
      • +
      +
      +
      +

      API documentation may be refined in patch releases, along with implementation defects that cause deviation from the specification.

      +
      +
      +
      +
      +
      +

      + + Getting started +

      +
      +
      +

      + + Access control +

      +
      +

      All requests must provide a Develocity access key as “bearer token”.

      +
      +
      +

      To create an access key:

      +
      +
      +
        +
      1. +

        Sign in to Develocity.

        +
      2. +
      3. +

        Access "My settings" from the user menu in the top right-hand corner of the page.

        +
      4. +
      5. +

        Access "Access keys" from the left-hand menu.

        +
      6. +
      7. +

        Click "Generate" on the right-hand side and copy the generated access key.

        +
      8. +
      +
      +
      +

      Different requests require different user permissions, as describe via the API specification.

      +
      +
      +

      To view the permissions assigned to you:

      +
      +
      +
        +
      1. +

        Sign in to Develocity.

        +
      2. +
      3. +

        Access "My settings" from the user menu in the top right-hand corner of the page.

        +
      4. +
      5. +

        Access "Permissions" from the left-hand menu.

        +
      6. +
      7. +

        Check if you have the required permissions.

        +
      8. +
      +
      +
      +

      If you do not have the required permission, contact your Develocity administrator.

      +
      +
      +

      + + Short-lived access tokens +

      +
      +

      Develocity access keys are long-lived, creating risks if they are leaked. To avoid this, users can use short-lived access tokens to authenticate with Develocity. Access tokens can be used wherever an access key would be used. Access tokens are only valid for the Develocity instance that created them.

      +
      +
      + + + + + + + +
      + + Develocity server version 2024.1+ supports access tokens.
      +
      +
      + + + + + + + +
      + + Changing a Develocity instance’s hostname will cause all existing access tokens to become invalid.
      +
      +
      +

      To create an access token:

      +
      +
      +
        +
      1. +

        Get an access key or access token for the user you wish to create a token for.

        +
      2. +
      3. +

        Decide which permissions the new access token should have.

        +
      4. +
      5. +

        If project-level access control is enabled, decide which projects the new access token should be able to access.

        +
      6. +
      7. +

        Decide how long the new access token should live.

        +
      8. +
      9. +

        + Make a POST request to + /api/auth/token + , optionally with the following parameters. The response will be the access token. +

        +
        +
          +
        1. +

          + A + permissions= + query parameter with the + config values + of each permission you wish to grant. By default, all permissions for the credential used to authenticate the token request are granted to the created token. +

          +
        2. +
        3. +

          + If project-level access control is enabled, a + projectIds= + query parameter with the ID of each project you wish to grant access to. By default, all projects for the credential used to authenticate the token request are granted to the created token. +

          +
        4. +
        5. +

          + An + expiresInHours= + query parameter with the token’s intended lifetime in hours, with a maximum of 24. The default is two hours, or the remaining lifetime of the credential used to authenticate the request, whichever is smaller. +

          +
        6. +
        +
        +
      10. +
      +
      +
      +

      + The requested permissions and project ids can be specified as comma-seperated lists or repeated parameters. For example, + ?projectIds=a,b&projectIds=c + is valid and will request projects + a + , + b + , and + c + . +

      +
      +
      + + + + + + + +
      + + If project-level access control is not enabled, all access tokens will be granted the “Access all data without an associated project” permission even if it is not explicitly requested.
      +
      +
      +

      + If the user creating the token does not have one of the requested permissions or projects, Develocity will respond with a + 403 Forbidden + error. If an access token is used to authenticate the creation request, its permissions and projects will be used for this check instead of the user’s. The request will also error if the requested lifetime would cause the new access token to expire after the one used to authenticate the request. Together, this means you cannot create an access token with more access or a later expiration than the credentials used to authenticate the request. +

      +
      +
      + + + + + + + +
      + + + See the + API documentation + for more details on the + /api/auth/token + endpoint. +
      +
      +
      +

      Here is an example using CURL to create an access token:

      +
      +
      +
      +
      +                                    $ curl -X POST https://ge.mycompany.com/api/auth/token?permissions=publishScan,writeCache,accessDataWithoutAssociatedProject&projectIds=project-a,project-b&expiresInHours=1 \
      +                                        -H "Authorization: Bearer 7asejatf24zun43yshqufp7qi4ovcefxpykbwzqbzilcpwzb52ja"
      +
      +                                    eyJraWQiOiJ0ZXN0LWtleSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc19hbm9ueW1vdXMiOmZhbHNlLCJwZXJtaXNzaW9ucyI6WyJSRUFEX1ZFUlNJT04iLCJFWFBPUlRfREFUQSIsIkFDQ0VTU19EQVRBX1dJVEhPVVRfQVNTT0NJQVRFRF9QUk9KRUNUIl0sInByb2plY3RzIjp7ImEiOjEsImIiOjJ9LCJ1c2VyX2lkIjoic29tZS1pZCIsInVzZXJuYW1lIjoidGVzdCIsImZpcnN0X25hbWUiOiJhIiwibGFzdF9uYW1lIjoidXNlciIsImVtYWlsIjoiYkBncmFkbGUuY29tIiwic3ViIjoidGVzdCIsImV4cCI6NzIwMCwibmJmIjowLCJpYXQiOjAsImF1ZCI6ImV4YW1wbGUuZ3JhZGxlLmNvbSIsImlzcyI6ImV4YW1wbGUuZ3JhZGxlLmNvbSIsInRva2VuX3R5cGUiOiJhY2Nlc3NfdG9rZW4ifQ.H1_NEG1xuleP-WIAY_uvSmdd2o7i_-Ko3qhlo04zvCgrElJe7_F5jNuqsyDfnb5hvKlOe5UKG_7QPTgY9-3pFQ
      +                                
      +
      +
      +
      +

      The resulting token would have the following permissions:

      +
      +
      +
        +
      • +

        “Publish build scans”

        +
      • +
      • +

        “Read and write build cache data”

        +
      • +
      • +

        “Access all data without an associated project”

        +
      • +
      +
      +
      +

      And it would have access to these projects:

      +
      +
      +
        +
      • +

        “project-a”

        +
      • +
      • +

        “project-b”

        +
      • +
      +
      +
      +

      The token would only be usable for one hour.

      +
      +
      +
      +
      +

      + + Making a request +

      +
      +

      + The following example demonstrates using + + cURL + + to make a request to the + builds + endpoint, which requires the “Access build data via the API” permission. +

      +
      +
      +
      +
      +                                $ curl -H "Authorization: Bearer <access key or access token>" "https://ge.mycompany.com/api/builds?since=0&maxBuilds=3"
      +                            
      +
      +
      +
      +
      +
      [
      +                              {
      +                                "id": "7asfp6iy3d5ey",
      +                                "availableAt": 1645452789334,
      +                                "buildToolType": "gradle",
      +                                "buildToolVersion": "7.4",
      +                                "buildAgentVersion": "3.8.1"
      +                              },
      +                              {
      +                                "id": "1saxebtd5d4xs",
      +                                "availableAt": 1645452795414,
      +                                "buildToolType": "maven",
      +                                "buildToolVersion": "3.8.4",
      +                                "buildAgentVersion": "1.12.4"
      +                              },
      +                              {
      +                                "id": "hx4k23knk64ri",
      +                                "availableAt": 1645453205526,
      +                                "buildToolType": "gradle",
      +                                "buildToolVersion": "7.4",
      +                                "buildAgentVersion": "3.8.1"
      +                              }
      +                            ]
      +
      +
      +
      +

      + The + builds + endpoint can be used to discover the builds observed by the system. This example fetches the 3 oldest builds due to the + since=0&maxBuilds=3 + parameters. +

      +
      +
      +
      +

      + + Next steps +

      +
      +

      + Check out the + sample project + . +

      +
      +
      +

      + To learn more about the functionality provided by the API and what you can do with it, see + Reference + . +

      +
      +
      +
      +
      +
      +

      + + Reference +

      +
      +
      +

      The reference documentation is useful for discovering the functionality of the API.

      +
      +
      +

      + The reference specification is the API described using the OpenAPI standard and is useful for generating client code with + OpenAPI-based tooling + . +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +

      2024.1

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2023.4

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2023.3

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2023.2

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2023.1

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2022.4

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2022.3

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2022.2

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +

      2022.1

      +
      +

      + Documentation +

      +
      +

      + Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

      +
      +
      +
      +
      +

      + + Appendix A: About the Export API +

      +
      +
      +

      + The Develocity API described in this document will eventually replace the + Develocity Export API + . At this time, the Develocity API does not allow consuming the + raw events of a build as the Export API does + . +

      +
      +
      +

      If your existing usage of the Export API can be replaced with the easier-to-use Develocity API, please consider migrating. For assistance, please contact Develocity support.

      +
      +
      +
      +
      +

      + + Appendix B: Release history +

      +
      +

      2024.1

      +
      +
      + 2nd April 2024 +
      +
        +
      • +

        + Add new + /api/builds/{id}/gradle-artifact-transform-executions + endpoint +

        +
      • +
      • +

        + Add new + /api/builds/{id}/gradle-deprecations + endpoint +

        +
      • +
      • +

        + Add + allModels + query parameter to + /api/build/{id} + and + /api/builds + endpoint for requesting all build models +

        +
      • +
      +
      +

      2023.4

      +
      +
      + 5th December 2023 +
      +
        +
      • +

        + Add new + /api/builds/{id}/gradle-network-activity + endpoint +

        +
      • +
      • +

        + Add new + /api/builds/{id}/maven-dependency-resolution + endpoint +

        +
      • +
      • +

        + Add new + /api/tests/cases + endpoint +

        +
      • +
      • +

        + Add new + /api/tests/containers + endpoint +

        +
      • +
      • +

        + Add + develocitySettings + attribute on the + /api/builds/{id}/gradle-attributes + and + /api/builds/{id}/maven-attributes + operation responses. This value is identical to + gradleEnterpriseSettings + and should be used instead +

        +
      • +
      • +

        + Add + workUnitFingerprintingSummary + and + workUnitAvoidanceSavingsSummary + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + cacheKey + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + cacheKey + to + /api/builds/{id}/maven-build-cache-performance + operation response +

        +
      • +
      • +

        + Make + isDisabledDueToError + nullable on + /api/builds/{id}/gradle-build-cache-performance + and + /api/builds/{id}/maven-build-cache-performance + responses +

        +
      • +
      • +

        + Deprecate + rerunGoals + in favor of + rerunGoalsEnabled + within the + buildOptions + property on the + /api/builds/{id}/maven-attributes + operation responses +

        +
      • +
      • +

        + Deprecate + avoidanceSavingsSummary + in favor of + taskAvoidanceSavingsSummary + from + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      +
      +

      2023.3

      +
      +
      + 13th September 2023 +
      +
        +
      • +

        + Add + hasVerificationFailure + and + hasNonVerificationFailure + attributes on the + /api/builds/{id}/gradle-attributes + and + /api/builds/{id}/maven-attributes + operation response +

        +
      • +
      • +

        + Introduce optional + query + query parameter for the on the + /api/builds + endpoint, which can be used to filter the set of returned builds. This parameter’s value should be a URL encoded text query written in the advanced query language (the same advanced query language that is used in the scans list UI) +

        +
      • +
      +
      +

      2023.2

      +
      +
      + 18th July 2023 +
      +
        +
      • +

        + Add + effectiveWorkUnitExecutionTime + and + serialWorkUnitExecutionTime + on the + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Exclude potential artifact transforms from + effectiveTaskExecutionTime + and + serialTaskExecutionTime + on the + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + cacheArtifactRejectedReason + to + /api/builds/{id}/gradle-build-cache-performance + and + /api/builds/{id}/maven-build-cache-performance + operation response +

        +
      • +
      +
      +

      2023.1

      +
      +
      + 12th April 2023 +
      +
        +
      • +

        + Add + cacheArtifactSize + to + /api/builds/{id}/gradle-build-cache-performance + and + /api/builds/{id}/maven-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + excludedTasks + to + /api/builds/{id}/gradle-attributes + operation response +

        +
      • +
      +
      +

      2022.4

      +
      +
      + 8th December 2022 +
      +
        +
      • +

        + Add new + /api/test-distribution/* + endpoints for managing API keys and agent pools +

        +
      • +
      • +

        + Add new + /api/builds/{id}/gradle-projects + endpoint +

        +
      • +
      • +

        + Add new + /api/builds/{id}/maven-modules + endpoint +

        +
      • +
      • +

        + Introduce + fromInstant + , + fromBuild + , + reverse + query parameters on the + /api/builds + operation query +

        +
      • +
      • +

        + Deprecate + since + and + sinceBuild + query parameters on the + /api/builds + operation query +

        +
      • +
      +
      +

      2022.3

      +
      +
      + 10th August 2022 +
      +
        +
      • +

        + Add + taskExecution.nonCacheabilityCategory + and + taskExecution.nonCacheabilityReason + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + goalExecution.nonCacheabilityCategory + and + goalExecution.nonCacheabilityReason + to + /api/builds/{id}/maven-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + taskExecution.taskType + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + goalExecution.mojoType + to + /api/builds/{id}/maven-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + buildCaches.remote.type + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add + buildCaches.remote.className + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

        +
      • +
      • +

        + Add a + title + to + /api/builds + inlined query parameters schema +

        +
      • +
      +
      +

      2022.2

      +
      +
      + 19th April 2022 +
      +
        +
      • +

        Improve OpenAPI specification examples

        +
      • +
      • +

        + Add + buildCaches.local.isPushEnabled + to + /api/builds/{id}/maven-build-cache-performance + operation response +

        +
      • +
      +
      +

      2022.1

      +
      +
      + 17th March 2022 +
      +
        +
      • +

        Initial release

        +
      • +
      +
      +
      +
      +
      +

      + + Appendix C: Advanced search syntax +

      +
      +
      +

      Develocity supports the use of advanced search for filtering Build Scan data in the dashboards from version 2022.4, and in the Enterprise API from version 2023.3.

      +
      +
      +

      + Each query can be made from one or more + terms + separated by spaces. Each term is a field name and search pattern: + fieldName:pattern + . For example, + project:my-project + is equivalent to entering + my-project + in the + Project + field in the basic search. +

      +
      +
      +

      + + Supported fields +

      +
      +

      + + Generic fields +

      +
      +

      These fields are applicable for builds from all supported build tools.

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionData typeVersion availabilityExample
      +

      user

      +
      +

      Username of the OS account that executed the build.

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      user:dylan

      +
      +

      hostname

      +
      +

      Public hostname of the build machine.

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      hostname:*.somedomain.com

      +
      +

      project

      +
      +

      Name of the Gradle root project, Maven top level module, Bazel workspace, or Sbt root project.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      project:my-project

      +
      +

      rootProjectName

      +
      +

      Name of the Gradle root project, Maven top level module, Bazel workspace, or Sbt root project.

      +
      +

      String

      +
      +

      2022.4 to 2023.2.3

      +
      +

      rootProjectName:my-project

      +
      +

      requested

      +
      +

      Tasks, goals, or targets that were requested when the build was invoked.

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      requested:":my-project:*"

      +
      +

      buildTool

      +
      +

      + Must be one of + gradle + , + maven + , or + bazel + . In the Build Scan dashboards, but not in the Enterprise API, you can also use the value + sbt + . +

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      buildTool:gradle

      +
      +

      buildToolVersion

      +
      +

      Version of the build tool that executed the build.

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      buildToolVersion:8.*

      +
      +

      value

      +
      +

      + Custom values + added to the Build Scan in the form + name=value + . +

      +
      +

      Key-value pair

      +
      +

      >=2022.4

      +
      +

      value:"Git branch=abc/my-feature"

      +
      +

      tag

      +
      +

      + Tags + added to the Build Scan. +

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      tag:CI

      +
      +

      buildOutcome

      +
      +

      + succeeded + or + failed + . +

      +
      +

      String

      +
      +

      >=2022.4

      +
      +

      buildOutcome:failed

      +
      +

      buildDuration

      +
      +

      Duration of the build.

      +
      +

      Duration

      +
      +

      >=2022.4

      +
      +

      buildDuration>30m

      +
      +

      buildStartTime

      +
      +

      Start time of the build. This field should only be used when filtering Build Scan data from the Develocity API.

      +
      +

      Timestamp

      +
      +

      >=2022.4

      +
      +
      +
      +
        +
      • +

        buildStartTime:[2023-01-01 to 2023-02-01]

        +
      • +
      • +

        buildStartTime:[2023-09-01T13:00:00 to 2023-09-01T15:00:00]

        +
      • +
      • +

        buildStartTime>-5d

        +
      • +
      +
      +
      +
      +

      hasVerificationFailure

      +
      +

      True if the build fails and at least one of the failures is classified as "Verification". Otherwise, false.

      +
      +

      Boolean

      +
      +

      >=2024.1

      +
      +

      hasVerificationFailure

      +
      +

      hasNonVerificationFailure

      +
      +

      True if the build fails and at least one of the failures is classified as "Non-verification". Otherwise, false.

      +
      +

      Boolean

      +
      +

      >=2024.1

      +
      +

      hasNonVerificationFailure

      +
      +
      +
      +

      + + Gradle-specific fields +

      +
      +

      + Fields whose name is prefixed with " + gradle. + " are specific to Gradle and select only Gradle builds. +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionData typeVersion availability
      +

      gradle.pluginVersion

      +
      +

      The applied version of the Develocity Gradle plugin.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      gradle.requestedTasks

      +
      +

      The resolved set of requested tasks for the Gradle build.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      gradle.rootProjectName

      +
      +

      The root project name for the Gradle build.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      gradle.buildCache.hasError

      +
      +

      Whether any build cache error or artifact rejections occurred during the build.

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.buildCacheEnabled

      +
      +

      + Whether the + Gradle build cache + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.configurationCacheEnabled

      +
      +

      + Whether the + configuration cache + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.configurationOnDemandEnabled

      +
      +

      + Whether + configuration on demand + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.continueOnFailureEnabled

      +
      +

      + Whether + continue on failure + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.continuousBuildEnabled

      +
      +

      + Whether + continuous build execution + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.daemonEnabled

      +
      +

      + Whether the + Gradle daemon + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.dryRunEnabled

      +
      +

      + Whether + dry run mode + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.fileSystemWatchingEnabled

      +
      +

      + Whether + file system watching + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.maxNumberOfGradleWorkers

      +
      +

      + Maximum number of build workers + configured for the Gradle build. +

      +
      +

      Number

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.offlineModeEnabled

      +
      +

      + Whether + offline mode + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.parallelProjectExecution

      +
      +

      + Whether + parallel project execution + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.refreshDependenciesEnabled

      +
      +

      + Whether the build was configured to + refresh all dependencies + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.buildOptions.rerunTasksEnabled

      +
      +

      + Whether + rerun tasks + was enabled. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.develocitySettings.backgroundPublicationEnabled

      +
      +

      + Whether build scan data was + published in the background + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.develocitySettings.buildOutputCapturingEnabled

      +
      +

      + Whether the build was configured to + capture logging output + for the build. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.develocitySettings.taskInputsFileCapturingEnabled

      +
      +

      + Whether the build was configured to + capture task input files + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.develocitySettings.testOutputCapturingEnabled

      +
      +

      + Whether the build was configured to + capture test logging output + for the build. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      gradle.environment.jreVersion

      +
      +

      Version of the Java runtime that executed the build.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      gradle.environment.jvmCharset

      +
      +

      Default charset of the build JVM.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      gradle.environment.jvmLocale

      +
      +

      Locale of the build JVM.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      gradle.environment.jvmMaxMemoryHeapSize

      +
      +

      Maximum heap memory available to the build JVM.

      +
      +

      StorageSize

      +
      +

      >=2023.4

      +
      +

      gradle.environment.jvmVersion

      +
      +

      Version of the Java Virtual Machine that executed the build.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      gradle.environment.localIPAddress

      +
      +

      Local IP addresses of the build machine.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      gradle.environment.localHostname

      +
      +

      Hostname of the build machine, as specified by itself.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      gradle.environment.numberOfCpuCores

      +
      +

      Number of cores available to the build JVM.

      +
      +

      Number

      +
      +

      >=2023.4

      +
      +

      gradle.environment.operatingSystem

      +
      +

      Operating system of the build machine.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +
      +
      +

      + + Maven-specific fields +

      +
      +

      + Fields whose name is prefixed with " + maven. + " are specific to Maven and select only Maven builds. +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionData typeVersion availability
      +

      maven.extensionVersion

      +
      +

      The applied version of the Develocity Maven extension.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      maven.requestedGoals

      +
      +

      The resolved set of requested goals for the Maven build.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      maven.topLevelProjectName

      +
      +

      The name of the top level Maven project.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      maven.buildCache.hasError

      +
      +

      Whether any build cache error or artifact rejections occurred during the build.

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.batchModeEnabled

      +
      +

      + Whether the build was configured to run in + non-interactive (batch) mode + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.debugEnabled

      +
      +

      + Whether the build was configured to + produce execution debug output + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.errorsEnabled

      +
      +

      + Whether the build was configured to + produce execution error messages + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.failAtEndEnabled

      +
      +

      + Whether the build was configured to + only fail at the end + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.failFastEnabled

      +
      +

      + Whether the build was configured to + fail at the first error + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.failNeverEnabled

      +
      +

      + Whether the build was configured to + never fail, regardless of errors produced + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.laxChecksumsEnabled

      +
      +

      + Whether the build was configured to + only warn if checksums don’t match + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.maxNumberOfThreads

      +
      +

      Maximum number of threads used when executing the build.

      +
      +

      Number

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.nonRecursiveEnabled

      +
      +

      + Whether the build was configured to + not recurse into subprojects + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.noSnapshotUpdatesEnabled

      +
      +

      + Whether the build was configured to + suppress snapshot updates + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.offlineModeEnabled

      +
      +

      + Whether the build was configured to + run offline + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.quietEnabled

      +
      +

      + Whether the build was configured to + run in quiet mode + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.rerunGoalsEnabled

      +
      +

      + Whether the build was configured to + rerun goals without checking the build cache + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.strictChecksumsEnabled

      +
      +

      + Whether the build was configured to + fail if checksums don’t match + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.buildOptions.updateSnapshotsEnabled

      +
      +

      + Whether the build was configured to + force a check for missing releases and updated snapshots on remote repositories + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.develocitySettings.backgroundPublicationEnabled

      +
      +

      + Whether build scan data was + published in the background + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.develocitySettings.buildOutputCapturingEnabled

      +
      +

      + Whether the build was configured to + capture logging output + for the build. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.develocitySettings.goalInputsFileCapturingEnabled

      +
      +

      + Whether the build was configured to + capture goal input files + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.develocitySettings.testOutputCapturingEnabled

      +
      +

      + Whether the build was configured to + capture test logging output + for the build. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      maven.environment.jreVersion

      +
      +

      Version of the Java runtime that executed the build.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      maven.environment.jvmCharset

      +
      +

      Default charset of the build JVM.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      maven.environment.jvmLocale

      +
      +

      Locale of the build JVM.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      maven.environment.jvmMaxMemoryHeapSize

      +
      +

      Maximum heap memory available to the build JVM.

      +
      +

      StorageSize

      +
      +

      >=2023.4

      +
      +

      maven.environment.jvmVersion

      +
      +

      Version of the Java Virtual Machine that executed the build.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      maven.environment.localIPAddress

      +
      +

      Local IP addresses of the build machine.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      maven.environment.localHostname

      +
      +

      Hostname of the build machine, as specified by itself.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      maven.environment.numberOfCpuCores

      +
      +

      Number of cores available to the build JVM.

      +
      +

      Number

      +
      +

      >=2023.4

      +
      +

      maven.environment.operatingSystem

      +
      +

      Operating system of the build machine.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +
      +
      +

      + + Bazel-specific fields +

      +
      +

      + Fields whose name is prefixed with " + bazel. + " are specific to Bazel and select only Bazel builds. +

      +
      + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionData typeVersion availability
      +

      bazel.requestedTargets

      +
      +

      The resolved set of requested targets for the Bazel build.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +
      +
      +

      + + sbt-specific fields +

      +
      +

      + Fields whose name is prefixed with " + sbt. + " are specific to sbt and select only sbt builds. +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameDescriptionData typeVersion availability
      +

      sbt.pluginVersion

      +
      +

      The applied version of the Develocity sbt plugin.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      sbt.requestedTasks

      +
      +

      The resolved set of requested tasks for the sbt build.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      sbt.rootProjectName

      +
      +

      The name of the top level sbt project.

      +
      +

      String

      +
      +

      >=2023.3

      +
      +

      sbt.buildOptions.interactiveModeEnabled

      +
      +

      + Whether the build was run + from the sbt shell + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.buildOptions.offlineModeEnabled

      +
      +

      + Whether the build was configured to + run offline + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.buildOptions.parallelExecutionEnabled

      +
      +

      + Whether the build was configured to use + parallel tasks execution + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.buildOptions.semanticDbScalacPluginEnabled

      +
      +

      + Whether the projects in the build were configured to use the + SemanticDB Scalac plugin + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.buildOptions.turboEnabled

      +
      +

      + Whether the build was configured to use + turbo mode + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.buildOptions.useCoursierEnabled

      +
      +

      + Whether + Coursier + was used for dependency resolution. +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.develocitySettings.backgroundPublicationEnabled

      +
      +

      + Whether build scan data was + published in the background + . +

      +
      +

      Boolean

      +
      +

      >=2023.4

      +
      +

      sbt.environment.jreVersion

      +
      +

      Version of the Java runtime that executed the build.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      sbt.environment.jvmCharset

      +
      +

      Default charset of the build JVM.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      sbt.environment.jvmLocale

      +
      +

      Locale of the build JVM.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      sbt.environment.jvmMaxMemoryHeapSize

      +
      +

      Maximum heap memory available to the build JVM.

      +
      +

      StorageSize

      +
      +

      >=2023.4

      +
      +

      sbt.environment.jvmVersion

      +
      +

      Version of the Java Virtual Machine that executed the build.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      sbt.environment.localIPAddress

      +
      +

      Local IP addresses of the build machine.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      sbt.environment.localHostname

      +
      +

      Hostname of the build machine, as specified by itself.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +

      sbt.environment.numberOfCpuCores

      +
      +

      Number of cores available to the build JVM.

      +
      +

      Number

      +
      +

      >=2023.4

      +
      +

      sbt.environment.operatingSystem

      +
      +

      Operating system of the build machine.

      +
      +

      String

      +
      +

      >=2023.4

      +
      +
      +
      +
      +

      + + Field data types +

      +
      +

      Every supported field in the advanced search query language is associated with one data type. A field’s data type indicates what values it accepts, and each has its own syntax. The data types are all described below.

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Data typeDescription
      +

      String

      +
      +
      +
      +

      Strings can be exact matches or use the * wildcard. Whitespace and special characters need to be double-quoted. Searches are case-insensitive.

      +
      +
      +
        +
      • +

        project:my-project

        +
      • +
      • +

        tag:"Tag with spaces"

        +
      • +
      • +

        hostname:*.somedomain.com

        +
      • +
      +
      +
      +
      +

      Key-value pair

      +
      +
      +
      +

      These take the form fieldName:key=value, with wildcards allowed and double-quoting required for whitespace and special characters.

      +
      +
      +
        +
      • +

        value:"Git branch=abc/my-feature"

        +
      • +
      • +

        value:ci-agent=*.subdomain.com

        +
      • +
      +
      +
      +
      +

      Duration

      +
      +
      +
      +

      + Durations can be specified using + ISO 8601 duration syntax + or a simple syntax like + 5m + or + 24h + . +

      +
      +
      +
        +
      • +

        buildDuration>30m

        +
      • +
      • +

        buildDuration<=PT2H5.123S

        +
      • +
      +
      +
      +

      The units supported for simple duration syntax are 'd' for days, 'h' for hours, 'm' for minutes, 's' for seconds, and 'ms' for milliseconds.

      +
      +
      +
      +

      Timestamp

      +
      +
      +
      +

      Timestamps can be specified in a few different ways.

      +
      +
      +
        +
      • +

        YYYY-MM-DD - timestamp is at midnight on that day.

        +
      • +
      • +

        YYYY-MM-DDTHH:MM - seconds and milliseconds optional.

        +
      • +
      • +

        YYYY-MM-DDTHH:MM:SS.SSS

        +
      • +
      +
      +
      +

      + All timestamps can have an optional timezone offset appended in the form + Z + or + +HH:SS + to be specific about which timezone the timestamp is being specified in. Default is timezone of the current web browser when using advanced search in Build Scan dashboards. Default is always UTC when using the Enterprise API. +

      +
      +
      +

      Timestamps in the form of a date can be queried using equality.

      +
      +
      +
        +
      • +

        buildStartTime:2023-09-01 - builds that ran on this day, UTC.

        +
      • +
      • +

        buildStartTime:2023-09-01+01:00 - builds that ran on this day, BST (i.e. UTC+1).

        +
      • +
      +
      +
      +

      + Timestamps can be queried using ranges in the form + [start to finish] + . +

      +
      +
      +
        +
      • +

        buildStartTime:[2023-01-01 to 2023-02-01] - builds that ran from the Jan 1st - Feb 1st, inclusive.

        +
      • +
      • +

        buildStartTime:[2023-09-01T13:00:00 to 2023-09-01T15:00:00] - builds that ran on Sep 1st from 1pm to 3pm.

        +
      • +
      +
      +
      +

      A timestamp can also be specified relative to the current time using duration syntax.

      +
      +
      +
        +
      • +

        buildStartTime>-5d - builds in the last 5 days.

        +
      • +
      • +

        buildStartTime:[2023-01-01T05:23:30Z to -12h] - builds that ran from the start timestamp, until 12 hours ago.

        +
      • +
      +
      +
      +
      +

      Boolean

      +
      +
      +
      +

      Boolean fields can be included as terms themselves, or they can have a value specified. If the term is used by itself, it is treated as if the value is "true", as is conventional.

      +
      +
      +
        +
      • +

        gradle.buildOptions.buildCacheEnabled

        +
      • +
      • +

        gradle.buildOptions.buildCacheEnabled:true

        +
      • +
      • +

        gradle.buildOptions.buildCacheEnabled:false

        +
      • +
      +
      +
      +

      Some boolean fields distinguish between a false value, and an unknown value. You can use a wildcard to acknowledge the account for unknown booleans in your queries. The first query below will match all builds for which the build cache was known to be either enabled or disabled. The second will match all builds for which it is unknown whether the build cache was enabled or disabled.

      +
      +
      +
        +
      • +

        gradle.buildOptions.buildCacheEnabled:*

        +
      • +
      • +

        !gradle.buildOptions.buildCacheEnabled:*

        +
      • +
      +
      +
      +

      The following term will match builds where the build cache was not known to be enabled, rather than builds where the build cache was known to be disabled.

      +
      +
      +
        +
      • +

        !gradle.buildOptions.buildCacheEnabled

        +
      • +
      +
      +
      +
      +

      Number

      +
      +
      +
      +

      Numbers can be exact matches or use a range. They can be integers or decimals, although for some fields only integers really make sense, for example the max number of workers in a Gradle build is always an integer.

      +
      +
      +
        +
      • +

        gradle.buildOptions.maxNumberOfGradleWorkers:4

        +
      • +
      • +

        gradle.buildOptions.maxNumberOfGradleWorkers>=8

        +
      • +
      • +

        gradle.buildOptions.maxNumberOfGradleWorkers:[3 to 6]

        +
      • +
      • +

        gradle.buildOptions.maxNumberOfGradleWorkers<13.5

        +
      • +
      +
      +
      +
      +

      Storage size

      +
      +
      +
      +

      Storage sizes represent a number of bytes. They are ultimately numbers, but they can have units, for convenience. As with numbers, they can be exact matches or use a range. If no unit is appended, the number value is interpreted as a number of bytes.

      +
      +
      +
        +
      • +

        gradle.environment.jvmMaxMemoryHeapSize:3000000

        +
      • +
      • +

        gradle.environment.jvmMaxMemoryHeapSize>=5.3gi

        +
      • +
      • +

        gradle.environment.jvmMaxMemoryHeapSize:[8g to 12g]

        +
      • +
      +
      +
      +

      The units supported are:

      +
      +
      +
        +
      • +

        'k' for 'kilobyte' representing 1000 bytes

        +
      • +
      • +

        'ki' for 'kibibyte' representing 1024 bytes

        +
      • +
      • +

        + 'm' for 'megabyte' representing 1000 + 2 + bytes +

        +
      • +
      • +

        + 'mi' for 'mebibyte' representing 1024 + 2 + bytes +

        +
      • +
      • +

        + 'g' for 'gigabyte' representing 1000 + 3 + bytes +

        +
      • +
      • +

        + 'gi' for 'gibibyte' representing 1024 + 3 + bytes +

        +
      • +
      • +

        + 't' for 'terabyte' representing 1000 + 4 + bytes +

        +
      • +
      • +

        + 'ti' for 'tebibyte' representing 1024 + 4 + bytes +

        +
      • +
      +
      +
      +

      Optionally, a 'b' may be appended to the end of these units. Thus, 500mb is the same as 500m, and 500mib is the same as 500mi. Units are also case-insensitive, so 500MiB is the same as 500mib.

      +
      +
      +
      +
      +
      +

      + + Combining terms +

      +
      +

      By default, all terms must match.

      +
      +
      +

      + Terms can be combined using boolean + and + and + or + operators, and grouped using parentheses. +

      +
      +
      +
        +
      • +

        user:dylan or (tag:CI and value:branch=dylan/*)

        +
      • +
      +
      +
      +

      + Terms can also be negated using + - + or + not + . +

      +
      +
      +
        +
      • +

        project:my-project -tag:local

        +
      • +
      • +

        user:dylan or not (tag:CI and value:branch=dylan/*)

        +
      • +
      +
      +
      +
      +
      -
      -
      -

      Appendix B: Release history

      -
      -

      2023.1

      -
      -
      - 12th April 2023 -
      -
        -
      • Add new cacheArtifactSize to /api/builds/{id}/gradle-build-cache-performance and /api/builds/{id}/maven-build-cache-performance operation response

      • -
      • Add new excludedTasks to /api/builds/{id}/gradle-attributes operation response

      • -
      -
      -

      2022.4

      -
      -
      - 8th December 2022 -
      -
        -
      • Add new /api/test-distribution/* endpoints for managing API keys and agent pools

      • -
      • Add new /api/builds/{id}/gradle-projects endpoint

      • -
      • Add new /api/builds/{id}/maven-modules endpoint

      • -
      • Introduce fromInstant, fromBuild, reverse query parameters on the /api/builds operation query

      • -
      • Deprecate since and sinceBuild query parameters on the /api/builds operation query

      • -
      -
      -

      2022.3

      -
      -
      - 10th August 2022 -
      -
        -
      • Add taskExecution.nonCacheabilityCategory and taskExecution.nonCacheabilityReason to /api/builds/{id}/gradle-build-cache-performance operation response

      • -
      • Add goalExecution.nonCacheabilityCategory and goalExecution.nonCacheabilityReason to /api/builds/{id}/maven-build-cache-performance operation response

      • -
      • Add taskExecution.taskType to /api/builds/{id}/gradle-build-cache-performance operation response

      • -
      • Add goalExecution.mojoType to /api/builds/{id}/maven-build-cache-performance operation response

      • -
      • Add buildCaches.remote.type to /api/builds/{id}/gradle-build-cache-performance operation response

      • -
      • Add buildCaches.remote.className to /api/builds/{id}/gradle-build-cache-performance operation response

      • -
      • Add a title to /api/builds inlined query parameters schema

      • -
      -
      -

      2022.2

      -
      -
      - 19th April 2022 -
      -
        -
      • Improve OpenAPI specification examples

      • -
      • Add buildCaches.local.isPushEnabled to /api/builds/{id}/maven-build-cache-performance operation response

      • -
      -
      -

      2022.1

      -
      -
      - 17th March 2022 -
      -
        -
      • Initial release

      • -
      -
      + -
      -
      - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/.github/scripts/test_update_api_spec_version.py b/.github/scripts/test_update_api_spec_version.py index d9c18248..03f7f33d 100755 --- a/.github/scripts/test_update_api_spec_version.py +++ b/.github/scripts/test_update_api_spec_version.py @@ -8,7 +8,7 @@ TEST_RESOURCES = Path(__file__).parent / 'test_resources' TEST_API_MANUAL_HTML = TEST_RESOURCES / 'api_manual.html' -LATEST_VERSION = '2023.1' # Same as HTML +LATEST_VERSION = '2024.1' # Same as HTML class TestCheckForNewApiSpec(unittest.TestCase): diff --git a/.github/scripts/update_api_spec_version.py b/.github/scripts/update_api_spec_version.py index 44bef8d2..aac547e0 100755 --- a/.github/scripts/update_api_spec_version.py +++ b/.github/scripts/update_api_spec_version.py @@ -6,7 +6,7 @@ import sys VERSIONS_URL = "https://docs.gradle.com/enterprise/api-manual/" -LATEST_VERSION_REGEX = r']*href="ref/gradle-enterprise-([\d.]+)-api\.yaml">Specification' +LATEST_VERSION_REGEX = r']*href="ref/develocity-([\d.]+)-api\.yaml">Specification' def main(properties_file='gradle.properties'): diff --git a/.github/workflows/update-api-spec.yml b/.github/workflows/update-api-spec.yml index 8fe739a2..8828c131 100644 --- a/.github/workflows/update-api-spec.yml +++ b/.github/workflows/update-api-spec.yml @@ -47,8 +47,8 @@ jobs: uses: peter-evans/create-pull-request@v6 with: branch: "${{ env.UPDATE_BRANCH }}" - commit-message: "Bump GE API spec version to ${{ env.NEW_VERSION }}" - title: "Bump GE API spec version to ${{ env.NEW_VERSION }}" + commit-message: "Bump Develocity API spec version to ${{ env.NEW_VERSION }}" + title: "Bump Develocity API spec version to ${{ env.NEW_VERSION }}" body: "https://docs.gradle.com/enterprise/api-manual/#release_history" author: "github-actions " committer: "github-actions " From 23008ee445e5f888e2f724c9eba0e509e24d81ea Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 01:41:11 +0100 Subject: [PATCH 24/35] Update POM for new name (#186) Update POM for Develocity rename and publish a relocation POM. Part of #184 --- .github/workflows/publish-library.yml | 5 +- library/build.gradle.kts | 69 +++++++++++++++++---------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/.github/workflows/publish-library.yml b/.github/workflows/publish-library.yml index 9f320388..56b8becb 100644 --- a/.github/workflows/publish-library.yml +++ b/.github/workflows/publish-library.yml @@ -30,12 +30,13 @@ jobs: - name: Verify that version property matches tag if: ${{ inputs.dry_run != true }} run: grep -qP '^version=${{ github.ref_name }}$' gradle.properties - - name: gradle publishLibraryPublicationToMavenCentralRepository + - name: gradle publish uses: ./.github/actions/build with: dry-run: ${{ inputs.dry_run }} args: >- - publishLibraryPublicationToMavenCentralRepository + publishDevelocityApiKotlinPublicationToMavenCentralRepository + publishRelocationPublicationToMavenCentralRepository --rerun-tasks '-Pmaven.central.username=${{ secrets.MAVEN_CENTRAL_USERNAME }}' '-Pmaven.central.password=${{ secrets.MAVEN_CENTRAL_PASSWORD }}' diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 3e7adb2f..8874dc0d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -82,35 +82,56 @@ dependencies { integrationTestImplementation("com.google.guava:guava:33.1.0-jre") } +fun libraryPom() = Action { + name.set("Gradle Enterprise API Kotlin") + description.set("A library to use the Gradle Enterprise REST API in Kotlin") + url.set("https://github.com/gabrielfeo/gradle-enterprise-api-kotlin") + licenses { + license { + name.set("MIT") + url.set("https://spdx.org/licenses/MIT.html") + distribution.set("repo") + } + } + developers { + developer { + id.set("gabrielfeo") + name.set("Gabriel Feo") + email.set("gabriel@gabrielfeo.com") + } + } + scm { + val basicUrl = "github.com/gabrielfeo/gradle-enterprise-api-kotlin" + connection.set("scm:git:git://$basicUrl.git") + developerConnection.set("scm:git:ssh://$basicUrl.git") + url.set("https://$basicUrl/") + } +} + publishing { publications { - create("library") { - artifactId = "gradle-enterprise-api-kotlin" + create("develocityApiKotlin") { + artifactId = "develocity-api-kotlin" from(components["java"]) + pom(libraryPom()) + } + // For occasional maven local publishing + create("unsignedDevelocityApiKotlin") { + artifactId = "develocity-api-kotlin" + from(components["java"]) + pom(libraryPom()) + } + create("relocation") { pom { - name.set("Gradle Enterprise API Kotlin") - description.set("A library to use the Gradle Enterprise REST API in Kotlin") - url.set("https://github.com/gabrielfeo/gradle-enterprise-api-kotlin") - licenses { - license { - name.set("MIT") - url.set("https://spdx.org/licenses/MIT.html") - distribution.set("repo") + groupId = project.group.toString() + artifactId = "gradle-enterprise-api-kotlin" + distributionManagement { + relocation { + groupId = project.group.toString() + artifactId = "develocity-api-kotlin" + message = "artifactId has been changed. Part of the rename to Develocity." } } - developers { - developer { - id.set("gabrielfeo") - name.set("Gabriel Feo") - email.set("gabriel@gabrielfeo.com") - } - } - scm { - val basicUrl = "github.com/gabrielfeo/gradle-enterprise-api-kotlin" - connection.set("scm:git:git://$basicUrl.git") - developerConnection.set("scm:git:ssh://$basicUrl.git") - url.set("https://$basicUrl/") - } } } } @@ -135,7 +156,7 @@ publishing { fun isCI() = System.getenv("CI").toBoolean() signing { - sign(publishing.publications["library"]) + sign(publishing.publications["develocityApiKotlin"]) if (isCI()) { useInMemoryPgpKeys( project.properties["signing.secretKey"] as String?, From fbaa91a5ebdabd114cd3cb0a57726434b542271d Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 02:24:59 +0100 Subject: [PATCH 25/35] Rename many things to Develocity (#177) Find and replace `gradle[-_./]enterprise` and other variations for Develocity. Disable README links test in PRs for now. New links remain broken until after the repository rename and Pages publish at that path. Part of #184 --- .github/scripts/test_resources/README.md | 8 +-- .../scripts/test_resources/build.gradle.kts | 2 +- .../scripts/test_update_api_spec_version.py | 4 +- .github/scripts/update_api_spec_version.py | 4 +- .github/workflows/pr.yml | 5 +- README.md | 72 +++++++++---------- .../task/PostProcessGeneratedApiTest.kt | 72 +++++++++---------- .../develocity-api-code-generation.gradle.kts | 10 +-- .../task/PostProcessGeneratedApi.kt | 10 +-- examples/build.gradle.kts | 2 +- .../MostFrequentBuilds.ipynb | 24 +++---- examples/example-project/build.gradle.kts | 4 +- .../gradle/enterprise/api/example/Main.kt | 20 +++--- .../example/analysis/MostFrequentBuilds.kt | 8 +-- .../example-scripts/example-script.main.kts | 10 +-- gradle.properties | 4 +- library/.openapi-generator-ignore | 6 +- library/build.gradle.kts | 10 +-- .../api/GradleEnterpriseApiIntegrationTest.kt | 20 +++--- .../BuildsApiExtensionsIntegrationTest.kt | 12 ++-- .../api/extension/RequestRecorder.kt | 4 +- .../api/internal/KeychainIntegrationTest.kt | 4 +- .../gradle/enterprise/api/Config.kt | 28 ++++---- .../enterprise/api/GradleEnterpriseApi.kt | 30 ++++---- .../BuildAttributesValueExtensions.kt | 4 +- .../api/extension/BuildsApiExtensions.kt | 16 ++--- .../enterprise/api/extension/Mapping.kt | 6 +- .../enterprise/api/internal/ApiConstants.kt | 2 +- .../gradle/enterprise/api/internal/Env.kt | 2 +- .../enterprise/api/internal/Keychain.kt | 4 +- .../enterprise/api/internal/LoggerFactory.kt | 4 +- .../enterprise/api/internal/OkHttpClient.kt | 10 +-- .../enterprise/api/internal/Retrofit.kt | 4 +- .../api/internal/SystemProperties.kt | 2 +- .../caching/CacheEnforcingInterceptor.kt | 2 +- .../caching/CacheHitLoggingInterceptor.kt | 2 +- .../gradle/enterprise/api/CacheConfigTest.kt | 6 +- .../gradle/enterprise/api/ConfigTest.kt | 6 +- .../gradle/enterprise/api/FakeBuildsApi.kt | 6 +- .../api/GradleEnterpriseApiExtensionsTest.kt | 10 +-- .../enterprise/api/GradleEnterpriseApiTest.kt | 10 +-- .../gradle/enterprise/api/OkHttpClientTest.kt | 12 ++-- .../gradle/enterprise/api/RetrofitTest.kt | 6 +- .../enterprise/api/TestResourceUtils.kt | 4 +- .../api/extension/BuildsApiExtensionsTest.kt | 10 +-- .../enterprise/api/extension/MappingTest.kt | 8 +-- .../gradle/enterprise/api/internal/FakeEnv.kt | 2 +- .../enterprise/api/internal/FakeKeychain.kt | 2 +- .../api/internal/FakeSystemProperties.kt | 2 +- .../caching/CacheEnforcingInterceptorTest.kt | 2 +- .../BuildAttributesValueExtensionsTest.kt | 8 +-- .../gradle/enterprise/api/model/FakeBuild.kt | 2 +- .../api/FakeGradleEnterpriseApiScaffold.kt | 8 +-- .../gradle/enterprise/api/internal/FakeEnv.kt | 4 +- .../enterprise/api/internal/FakeKeychain.kt | 4 +- .../api/internal/FakeSystemProperties.kt | 4 +- 56 files changed, 274 insertions(+), 273 deletions(-) diff --git a/.github/scripts/test_resources/README.md b/.github/scripts/test_resources/README.md index e78a5d40..7d0e0794 100644 --- a/.github/scripts/test_resources/README.md +++ b/.github/scripts/test_resources/README.md @@ -3,15 +3,15 @@ [![Maven Central](https://img.shields.io/badge/Maven%20Central-0.17.0-blue)][14] ```kotlin -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:0.17.0") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:0.17.0") ``` ```kotlin -implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:0.17.0") +implementation("com.gabrielfeo:develocity-api-kotlin:0.17.0") ``` ``` -%use gradle-enterprise-api-kotlin(version=0.17.0) +%use develocity-api-kotlin(version=0.17.0) ``` -[14]: https://central.sonatype.com/artifact/com.gabrielfeo/gradle-enterprise-api-kotlin/0.17.0 +[14]: https://central.sonatype.com/artifact/com.gabrielfeo/develocity-api-kotlin/0.17.0 diff --git a/.github/scripts/test_resources/build.gradle.kts b/.github/scripts/test_resources/build.gradle.kts index 4bfbefee..813152ba 100644 --- a/.github/scripts/test_resources/build.gradle.kts +++ b/.github/scripts/test_resources/build.gradle.kts @@ -1,3 +1,3 @@ dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:0.17.0") + implementation("com.gabrielfeo:develocity-api-kotlin:0.17.0") } diff --git a/.github/scripts/test_update_api_spec_version.py b/.github/scripts/test_update_api_spec_version.py index 03f7f33d..47cbabe7 100755 --- a/.github/scripts/test_update_api_spec_version.py +++ b/.github/scripts/test_update_api_spec_version.py @@ -34,12 +34,12 @@ def test_main_without_update_available(self, mock_get, _): def assert_properties_version(self, file, version): with open(file.name) as file: - expected = f"gradle.enterprise.version={version}\nversion={version}.0\n1=2\n" + expected = f"develocity.version={version}\nversion={version}.0\n1=2\n" self.assertEqual(file.read(), expected) def properties_file(self, version): file = NamedTemporaryFile() - content = f"gradle.enterprise.version={version}\nversion={version}.0\n1=2\n" + content = f"develocity.version={version}\nversion={version}.0\n1=2\n" file.write(content.encode()) file.flush() return file diff --git a/.github/scripts/update_api_spec_version.py b/.github/scripts/update_api_spec_version.py index aac547e0..f3e8d253 100755 --- a/.github/scripts/update_api_spec_version.py +++ b/.github/scripts/update_api_spec_version.py @@ -24,7 +24,7 @@ def get_current_api_spec_version(properties_file) -> str: if '=' not in line: continue k, v = line.strip().split('=', maxsplit=2) - if k == 'gradle.enterprise.version': + if k == 'develocity.version': return v @@ -42,7 +42,7 @@ def update_version(properties_file, new_version): if '=' in line: k, v = line.strip().split('=', maxsplit=2) # Update target API spec version - if k == 'gradle.enterprise.version': + if k == 'develocity.version': line = f"{k}={new_version}\n" # Update library version if k == 'version': diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2ad8a9cc..3b31e96d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -30,8 +30,9 @@ jobs: - name: 'unittest discover' run: python3 -m unittest discover -bs .github/scripts - readme-links-test: - uses: ./.github/workflows/test-readme-links.yml + # TODO Re-enable once rename is done (too many dead links until new javadoc is published) + # readme-links-test: + # uses: ./.github/workflows/test-readme-links.yml generated-api-diff: runs-on: ubuntu-latest diff --git a/README.md b/README.md index e472d291..376f3e33 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# Gradle Enterprise API Kotlin +# Develocity API Kotlin [![Maven Central](https://img.shields.io/badge/Maven%20Central-2023.4.0-blue)][14] [![Javadoc](https://img.shields.io/badge/Javadoc-2023.4.0-orange)][7] -A Kotlin library to access the [Gradle Enterprise API][1], easy to use from: +A Kotlin library to access the [Develocity API][1], easy to use from: - [Jupyter notebooks with the Kotlin kernel][29] - [Kotlin scripts (`kts`)][27] - [Kotlin projects][28] ```kotlin -val api = GradleEnterpriseApi.newInstance() +val api = DevelocityApi.newInstance() api.buildsApi.getBuildsFlow(fromInstant = 0, query = "buildStartTime<-1d").forEach { println(it) } @@ -22,8 +22,8 @@ api.buildsApi.getBuildsFlow(fromInstant = 0, query = "buildStartTime<-1d").forEa Set up environment variables and use the library from any notebook, script or project: -- [`GRADLE_ENTERPRISE_API_URL`][16]: the URL of your Gradle Enterprise instance -- [`GRADLE_ENTERPRISE_API_TOKEN`][17]: an [access key][31] for the Gradle Enterprise instance +- [`GRADLE_ENTERPRISE_API_URL`][16]: the URL of your Develocity instance +- [`GRADLE_ENTERPRISE_API_TOKEN`][17]: an [access key][31] for the Develocity instance - [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`][12] (optional, off by default): enables caching for some requests (see [caveats][13]) @@ -37,7 +37,7 @@ recommended over JitPack. ``` %useLatestDescriptors -%use gradle-enterprise-api-kotlin(version=2023.4.0) +%use develocity-api-kotlin(version=2023.4.0) ``` @@ -46,7 +46,7 @@ recommended over JitPack. Add to a Kotlin script ```kotlin -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2023.4.0") ``` @@ -56,7 +56,7 @@ recommended over JitPack. ```kotlin dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") + implementation("com.gabrielfeo:develocity-api-kotlin:2023.4.0") } ``` @@ -64,11 +64,11 @@ dependencies { ## Usage -The [`GradleEnterpriseApi`][9] interface represents the Gradle Enterprise REST API. It contains +The [`DevelocityApi`][9] interface represents the Develocity REST API. It contains all the APIs exactly as listed in the [REST API Manual][5]: ```kotlin -interface GradleEnterpriseApi { +interface DevelocityApi { val buildsApi: BuildsApi val testsApi: TestsApi val buildCacheApi: BuildCacheApi @@ -106,7 +106,7 @@ off by default. Enable by simply setting [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`] ### Extensions Explore the library's convenience extensions: -[`com.gabrielfeo.gradle.enterprise.api.extension`][25]. +[`com.gabrielfeo.develocity.api.extension`][25]. By default, the API's most common endpoint, `/api/builds`, is paginated. The library provides a [`getBuildsFlow`][24] extension to handle paging under-the-hood and yield all builds as you collect @@ -126,7 +126,7 @@ case they're needed again. This is an optimization of [OkHttp][4]. If you're wor or have a long-living program that fetches builds continuosly, no shutdown is needed. ```kotlin -val api = GradleEnterpriseApi.newInstance() +val api = DevelocityApi.newInstance() while (true) { delay(2.minutes) processNewBuilds(api.buildsApi.getBuildsFlow(query = "...")) @@ -135,10 +135,10 @@ while (true) { ``` In other cases (i.e. fetching some builds and exiting), you might want to call -[`GradleEnterpriseApi.shutdown()`][11] so that the program exits immediately: +[`DevelocityApi.shutdown()`][11] so that the program exits immediately: ```kotlin -val api = GradleEnterpriseApi.newInstance() +val api = DevelocityApi.newInstance() printMetrics(api.buildsApi.getBuildsFlow(query = "...")) // Call shutdown if you expect the program to exit now api.shutdown() @@ -170,7 +170,7 @@ val config = Config( apiToken = { vault.getGeApiToken() }, clientBuilder = existingClient.newBuilder(), ) -val api = GradleEnterpriseApi.newInstance(config) +val api = DevelocityApi.newInstance(config) api.buildsApi.getBuilds(fromInstant = yesterdayMilli) ``` @@ -184,9 +184,9 @@ See the [`Config`][8] documentation for more. no auto-complete, wildcard imports can be used (in notebooks, they're added automatically): ```kotlin -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.model.* -import com.gabrielfeo.gradle.enterprise.api.model.extension.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.model.* +import com.gabrielfeo.develocity.api.model.extension.* ``` [1]: https://docs.gradle.com/enterprise/api-manual/ @@ -195,27 +195,27 @@ import com.gabrielfeo.gradle.enterprise.api.model.extension.* [4]: https://github.com/square/retrofit/issues/3144#issuecomment-508300518 [5]: https://docs.gradle.com/enterprise/api-manual/ref/2022.4.html [6]: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-gradle-plugin/README.adoc -[7]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/ -[8]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/index.html -[9]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/ -[11]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/shutdown.html -[12]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/-cache-config/cache-enabled.html -[13]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/-cache-config/index.html -[14]: https://central.sonatype.com/artifact/com.gabrielfeo/gradle-enterprise-api-kotlin/2023.4.0 -[16]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/api-url.html -[17]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/api-token.html -[18]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/index.html -[19]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.model/-gradle-attributes/index.html -[20]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/index.html -[21]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/get-builds.html -[22]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/get-gradle-attributes.html -[23]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/-default-instance/index.html -[24]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-builds-flow.html -[25]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/index.html +[7]: https://gabrielfeo.github.io/develocity-api-kotlin/ +[8]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/index.html +[9]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-develocity-api/ +[11]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-develocity-api/shutdown.html +[12]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/-cache-config/cache-enabled.html +[13]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/-cache-config/index.html +[14]: https://central.sonatype.com/artifact/com.gabrielfeo/develocity-api-kotlin/2023.4.0 +[16]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/api-url.html +[17]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/api-token.html +[18]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/index.html +[19]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.model/-gradle-attributes/index.html +[20]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/index.html +[21]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/get-builds.html +[22]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/get-gradle-attributes.html +[23]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-develocity-api/-default-instance/index.html +[24]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.extension/get-builds-flow.html +[25]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.extension/index.html [26]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/ [27]: ./examples/example-scripts/example-script.main.kts [28]: ./examples/example-project -[29]: https://nbviewer.org/github/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb +[29]: https://nbviewer.org/github/gabrielfeo/develocity-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb [30]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html [31]: ./docs/AccessKeys.md [32]: ./examples diff --git a/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt b/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt index 605ad7d7..02692a82 100644 --- a/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt +++ b/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt @@ -24,30 +24,30 @@ class PostProcessGeneratedApiTest { */ @Test fun apiInterfacePostProcessing() = testPostProcessing( - inputPath = "com/gabrielfeo/gradle/enterprise/api/BuildsApi.kt", + inputPath = "com/gabrielfeo/develocity/api/BuildsApi.kt", inputContent = """ - package com.gabrielfeo.gradle.enterprise.api - - import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.CollectionFormats.* + package com.gabrielfeo.develocity.api + + import com.gabrielfeo.develocity.api.internal.infrastructure.CollectionFormats.* import retrofit2.http.* import retrofit2.Response import okhttp3.RequestBody import com.squareup.moshi.Json - - import com.gabrielfeo.gradle.enterprise.api.model.ApiProblem - import com.gabrielfeo.gradle.enterprise.api.model.Build - import com.gabrielfeo.gradle.enterprise.api.model.BuildModelQuery - import com.gabrielfeo.gradle.enterprise.api.model.BuildQuery - import com.gabrielfeo.gradle.enterprise.api.model.BuildsQuery - import com.gabrielfeo.gradle.enterprise.api.model.GradleAttributes - import com.gabrielfeo.gradle.enterprise.api.model.GradleBuildCachePerformance - import com.gabrielfeo.gradle.enterprise.api.model.GradleNetworkActivity - import com.gabrielfeo.gradle.enterprise.api.model.GradleProject - import com.gabrielfeo.gradle.enterprise.api.model.MavenAttributes - import com.gabrielfeo.gradle.enterprise.api.model.MavenBuildCachePerformance - import com.gabrielfeo.gradle.enterprise.api.model.MavenDependencyResolution - import com.gabrielfeo.gradle.enterprise.api.model.MavenModule - + + import com.gabrielfeo.develocity.api.model.ApiProblem + import com.gabrielfeo.develocity.api.model.Build + import com.gabrielfeo.develocity.api.model.BuildModelQuery + import com.gabrielfeo.develocity.api.model.BuildQuery + import com.gabrielfeo.develocity.api.model.BuildsQuery + import com.gabrielfeo.develocity.api.model.GradleAttributes + import com.gabrielfeo.develocity.api.model.GradleBuildCachePerformance + import com.gabrielfeo.develocity.api.model.GradleNetworkActivity + import com.gabrielfeo.develocity.api.model.GradleProject + import com.gabrielfeo.develocity.api.model.MavenAttributes + import com.gabrielfeo.develocity.api.model.MavenBuildCachePerformance + import com.gabrielfeo.develocity.api.model.MavenDependencyResolution + import com.gabrielfeo.develocity.api.model.MavenModule + interface BuildsApi { /** * Get the common attributes of a Build Scan. @@ -67,18 +67,18 @@ class PostProcessGeneratedApiTest { @GET("api/builds/{id}") suspend fun getBuild(@Path("id") id: kotlin.String, @Query("models") models: kotlin.collections.List? = null, @Query("availabilityWaitTimeoutSecs") availabilityWaitTimeoutSecs: kotlin.Int? = null): Response """.trimIndent(), - outputPath = "com/gabrielfeo/gradle/enterprise/api/BuildsApi.kt", + outputPath = "com/gabrielfeo/develocity/api/BuildsApi.kt", outputContent = """ - package com.gabrielfeo.gradle.enterprise.api - - import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.CollectionFormats.* + package com.gabrielfeo.develocity.api + + import com.gabrielfeo.develocity.api.internal.infrastructure.CollectionFormats.* import retrofit2.http.* import retrofit2.Response import okhttp3.RequestBody import com.squareup.moshi.Json - - import com.gabrielfeo.gradle.enterprise.api.model.* - + + import com.gabrielfeo.develocity.api.model.* + @JvmSuppressWildcards interface BuildsApi { /** @@ -106,25 +106,25 @@ class PostProcessGeneratedApiTest { */ @Test fun buildModelNameEnumPostProcessing() = testPostProcessing( - inputPath = "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + inputPath = "com/gabrielfeo/develocity/api/model/BuildModelName.kt", inputContent = """ @JsonClass(generateAdapter = false) enum class BuildModelName(val value: kotlin.String) { - + @Json(name = "gradle-attributes") gradleMinusAttributes("gradle-attributes"), - + @Json(name = "gradle-build-cache-performance") gradleMinusBuildMinusCacheMinusPerformance("gradle-build-cache-performance"), """.trimIndent(), - outputPath = "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + outputPath = "com/gabrielfeo/develocity/api/model/BuildModelName.kt", outputContent = """ @JsonClass(generateAdapter = false) enum class BuildModelName(val value: kotlin.String) { - + @Json(name = "gradle-attributes") gradleAttributes("gradle-attributes"), - + @Json(name = "gradle-build-cache-performance") gradleBuildCachePerformance("gradle-build-cache-performance"), """.trimIndent(), @@ -163,18 +163,18 @@ class PostProcessGeneratedApiTest { // language=groovy """ import com.gabrielfeo.task.PostProcessGeneratedApi - + plugins { id("com.gabrielfeo.no-op") } - + tasks.register("postProcessGeneratedApi", PostProcessGeneratedApi) { originalFiles = new File("${inputDir.absolutePath}") - modelsPackage = "com.gabrielfeo.gradle.enterprise.api.model" + modelsPackage = "com.gabrielfeo.develocity.api.model" postProcessedFiles = new File("${outputDir.absolutePath}") } """.trimIndent() ) return projectDir } -} \ No newline at end of file +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts index 3cbe8522..c29d4585 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts @@ -10,7 +10,7 @@ plugins { val localSpecPath = providers.gradleProperty("localSpecPath") val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse( - providers.gradleProperty("gradle.enterprise.version").map { geVersion -> + providers.gradleProperty("develocity.version").map { geVersion -> val majorVersion = geVersion.substringBefore('.').toInt() val specName = when { majorVersion <= 2023 -> "gradle-enterprise-$geVersion-api.yaml" @@ -45,10 +45,10 @@ openApiGenerate { outputDir.set(generateDir) val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore") ignoreFileOverride.set(ignoreFile.asFile.absolutePath) - apiPackage.set("com.gabrielfeo.gradle.enterprise.api") - modelPackage.set("com.gabrielfeo.gradle.enterprise.api.model") - packageName.set("com.gabrielfeo.gradle.enterprise.api.internal") - invokerPackage.set("com.gabrielfeo.gradle.enterprise.api.internal") + apiPackage.set("com.gabrielfeo.develocity.api") + modelPackage.set("com.gabrielfeo.develocity.api.model") + packageName.set("com.gabrielfeo.develocity.api.internal") + invokerPackage.set("com.gabrielfeo.develocity.api.internal") additionalProperties.put("library", "jvm-retrofit2") additionalProperties.put("useCoroutines", true) cleanupOutput.set(true) diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt b/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt index 5d6b9a77..e3775845 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt +++ b/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt @@ -38,7 +38,7 @@ abstract class PostProcessGeneratedApi @Inject constructor( } private fun postProcess(srcDir: File, modelsPackage: String) { - // Replace Response with X in every method return type of GradleEnterpriseApi.kt + // Replace Response with X in every method return type of DevelocityApi.kt ant.withGroovyBuilder { "replaceregexp"( "match" to ": Response<(.*?)>$", @@ -47,7 +47,7 @@ abstract class PostProcessGeneratedApi @Inject constructor( ) { "fileset"( "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", + "includes" to "com/gabrielfeo/develocity/api/*Api.kt", ) } } @@ -63,7 +63,7 @@ abstract class PostProcessGeneratedApi @Inject constructor( ) { "fileset"( "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", + "includes" to "com/gabrielfeo/develocity/api/*Api.kt", ) } } @@ -91,9 +91,9 @@ abstract class PostProcessGeneratedApi @Inject constructor( ) { "fileset"( "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", + "includes" to "com/gabrielfeo/develocity/api/model/BuildModelName.kt", ) } } } -} \ No newline at end of file +} diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index a21cc38a..41edd4be 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -7,7 +7,7 @@ plugins { // Cross-configure so we don't pollute the example buildscript project("example-project").configurations.configureEach { resolutionStrategy.dependencySubstitution { - substitute(module("com.gabrielfeo:gradle-enterprise-api-kotlin")) + substitute(module("com.gabrielfeo:develocity-api-kotlin")) .using(project(":library")) } } diff --git a/examples/example-notebooks/MostFrequentBuilds.ipynb b/examples/example-notebooks/MostFrequentBuilds.ipynb index 9be54949..ed82a46d 100644 --- a/examples/example-notebooks/MostFrequentBuilds.ipynb +++ b/examples/example-notebooks/MostFrequentBuilds.ipynb @@ -9,19 +9,19 @@ "source": [ "# Most frequent builds\n", "\n", - "See what builds are most commonly invoked by developers, e.g. `clean assemble`, `test` or `check`. You can [set up the URL and a token for your Gradle Enterprise instance](https://github.com/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/README.md#setup) and run this notebook as-is for your own project.\n", + "See what builds are most commonly invoked by developers, e.g. `clean assemble`, `test` or `check`. You can [set up the URL and a token for your Develocity instance](https://github.com/gabrielfeo/develocity-api-kotlin/blob/main/README.md#setup) and run this notebook as-is for your own project.\n", "\n", "This is a simple example of something you can do with the API. It could bring insights, for example:\n", "\n", "- \"Our developers frequently `clean` together with `assemble`. We should ask them why, because they shouldn't have to. Just an old habit from Maven or are they working around a build issue we don't know about?\"\n", "- \"Some are doing `check` builds locally, which we set up to trigger our notably slow legacy tests. We should suggest they run `test` instead, leaving `check` for CI to run.\"\n", "\n", - "This notebook will take you through using gradle-enterprise-api-kotlin in Jupyter, but it won't get into what a notebook is and how to run it. If you're not familiar with Jupyter:\n", + "This notebook will take you through using develocity-api-kotlin in Jupyter, but it won't get into what a notebook is and how to run it. If you're not familiar with Jupyter:\n", "\n", "- [Kotlin for data science overview](https://kotlinlang.org/docs/data-science-overview.html)\n", "- [Kotlin for Jupyter notebooks](https://github.com/cheptsov/kotlin-jupyter-demo/blob/master/index.ipynb)\n", "\n", - "Note: GitHub preview won't render tables or graphs. I recommend previewing this in the [online Jupyter nbviewer](https://nbviewer.org/github/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb)." + "Note: GitHub preview won't render tables or graphs. I recommend previewing this in the [online Jupyter nbviewer](https://nbviewer.org/github/gabrielfeo/develocity-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb)." ] }, { @@ -35,17 +35,17 @@ "Add libraries to use, via line magics. `%use` is a [line magic](https://github.com/Kotlin/kotlin-jupyter#line-magics) of the Kotlin kernel that can do much more than adding the library. To illustrate, this setup can be replaced with a single line magic.\n", "\n", "```kotlin\n", - "@file:DependsOn(\"com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0\")\n", + "@file:DependsOn(\"com.gabrielfeo:develocity-api-kotlin:2023.4.0\")\n", "\n", - "import com.gabrielfeo.gradle.enterprise.api.*\n", - "import com.gabrielfeo.gradle.enterprise.api.model.*\n", - "import com.gabrielfeo.gradle.enterprise.api.extension.*\n", + "import com.gabrielfeo.develocity.api.*\n", + "import com.gabrielfeo.develocity.api.model.*\n", + "import com.gabrielfeo.develocity.api.extension.*\n", "```\n", "\n", "is the same as:\n", "\n", "```\n", - "%use gradle-enterprise-api-kotlin(version=2023.4.0)\n", + "%use develocity-api-kotlin(version=2023.4.0)\n", "\n", "```" ] @@ -63,10 +63,10 @@ "outputs": [], "source": [ "%useLatestDescriptors\n", - "%use gradle-enterprise-api-kotlin(version=2023.4.0)\n", + "%use develocitytlin(version=2023.4.0)\n", "%use coroutines(v=1.7.1)\n", "\n", - "val api = GradleEnterpriseApi.newInstance()" + "val api = DevelocityApi.newInstance()" ] }, { @@ -82,9 +82,9 @@ "response using the `models` parameter. \"Models\" are build details that would come from other endpoints. For example, \n", "requesting models=[gradleAttributes][3] brings data from `/api/builds/{id}/gradle-attributes` in the same response.\n", "\n", - "[1]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-builds-flow.html\n", + "[1]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.extension/get-builds-flow.html\n", "[2]: https://docs.gradle.com/enterprise/api-manual/#advanced_search_syntax \n", - "[3]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api\n", + "[3]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api\n", ".model/-build-model-name/gradle-attributes/index.html" ] }, diff --git a/examples/example-project/build.gradle.kts b/examples/example-project/build.gradle.kts index 87300690..68c8c4d3 100644 --- a/examples/example-project/build.gradle.kts +++ b/examples/example-project/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } application { - mainClass.set("com.gabrielfeo.gradle.enterprise.api.example.MainKt") + mainClass.set("com.gabrielfeo.develocity.api.example.MainKt") } java { @@ -15,5 +15,5 @@ java { } dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") + implementation("com.gabrielfeo:develocity-api-kotlin:2023.4.0") } diff --git a/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt index f21edf2b..37f23a7a 100644 --- a/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt +++ b/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt @@ -1,13 +1,13 @@ -package com.gabrielfeo.gradle.enterprise.api.example +package com.gabrielfeo.develocity.api.example -import com.gabrielfeo.gradle.enterprise.api.Config -import com.gabrielfeo.gradle.enterprise.api.GradleEnterpriseApi -import com.gabrielfeo.gradle.enterprise.api.example.analysis.mostFrequentBuilds +import com.gabrielfeo.develocity.api.Config +import com.gabrielfeo.develocity.api.DevelocityApi +import com.gabrielfeo.develocity.api.example.analysis.mostFrequentBuilds import okhttp3.OkHttpClient /* * Example main that runs all API analysis at once. In projects, you can share an - * OkHttpClient.Builder between GradleEnterpriseApi and your own project classes, in order to + * OkHttpClient.Builder between DevelocityApi and your own project classes, in order to * save resources. */ @@ -17,11 +17,11 @@ suspend fun main() { val newConfig = Config( clientBuilder = clientBuilder, ) - val gradleEnterpriseApi = GradleEnterpriseApi.newInstance(newConfig) - runAllAnalysis(gradleEnterpriseApi) - gradleEnterpriseApi.shutdown() + val develocityApi = DevelocityApi.newInstance(newConfig) + runAllAnalysis(develocityApi) + develocityApi.shutdown() } -private suspend fun runAllAnalysis(gradleEnterpriseApi: GradleEnterpriseApi) { - mostFrequentBuilds(api = gradleEnterpriseApi.buildsApi) +private suspend fun runAllAnalysis(develocityApi: DevelocityApi) { + mostFrequentBuilds(api = develocityApi.buildsApi) } diff --git a/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt index 71dda635..4e4d7990 100644 --- a/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt +++ b/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api.example.analysis +package com.gabrielfeo.develocity.api.example.analysis -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.extension.* -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.extension.* +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.flow.* import java.util.LinkedList diff --git a/examples/example-scripts/example-script.main.kts b/examples/example-scripts/example-script.main.kts index c0b6b853..88e1b39b 100644 --- a/examples/example-scripts/example-script.main.kts +++ b/examples/example-scripts/example-script.main.kts @@ -17,11 +17,11 @@ * Run this with at least 1GB of heap to accomodate the fetched data: JAVA_OPTS=-Xmx1g */ -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.4.0") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2023.4.0") -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.model.* -import com.gabrielfeo.gradle.enterprise.api.extension.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.model.* +import com.gabrielfeo.develocity.api.extension.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import java.time.* @@ -34,7 +34,7 @@ val buildFilter: (GradleAttributes) -> Boolean = { build -> } // Fetch builds from the API -val api = GradleEnterpriseApi.newInstance() +val api = DevelocityApi.newInstance() val builds: List = runBlocking { api.buildsApi.getBuildsFlow( fromInstant = 0, diff --git a/gradle.properties b/gradle.properties index 594dabec..ec51a764 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ group=com.gabrielfeo -artifact=gradle-enterprise-api-kotlin +artifact=develocity-api-kotlin version=2024.1.0 -gradle.enterprise.version=2024.1 +develocity.version=2024.1 org.gradle.jvmargs=-Xmx5g org.gradle.caching=true # Becomes default in Gradle 9.0 diff --git a/library/.openapi-generator-ignore b/library/.openapi-generator-ignore index 692c5d76..105d96e0 100644 --- a/library/.openapi-generator-ignore +++ b/library/.openapi-generator-ignore @@ -5,7 +5,7 @@ build/generated-api/api/* build/generated-api/gradle/**/* build/generated-api/docs/* build/generated-api/src/main/AndroidManifest.xml -build/generated-api/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/infrastructure/ApiClient.kt -build/generated-api/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt -build/generated-api/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/DevelocityApi.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/internal/infrastructure/ApiClient.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/GradleEnterpriseApi.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt build/generated-api/src/test/**/* diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 8874dc0d..e566ddbf 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -14,7 +14,7 @@ plugins { signing } -val repoUrl = "https://github.com/gabrielfeo/gradle-enterprise-api-kotlin" +val repoUrl = "https://github.com/gabrielfeo/develocity-api-kotlin" java { withSourcesJar() @@ -83,9 +83,9 @@ dependencies { } fun libraryPom() = Action { - name.set("Gradle Enterprise API Kotlin") - description.set("A library to use the Gradle Enterprise REST API in Kotlin") - url.set("https://github.com/gabrielfeo/gradle-enterprise-api-kotlin") + name.set("Develocity API Kotlin") + description.set("A library to use the Develocity API in Kotlin") + url.set(repoUrl) licenses { license { name.set("MIT") @@ -101,7 +101,7 @@ fun libraryPom() = Action { } } scm { - val basicUrl = "github.com/gabrielfeo/gradle-enterprise-api-kotlin" + val basicUrl = repoUrl.substringAfter("://") connection.set("scm:git:git://$basicUrl.git") developerConnection.set("scm:git:ssh://$basicUrl.git") url.set("https://$basicUrl/") diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt index c5360e0c..92a180d1 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import com.google.common.reflect.ClassPath import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.assertDoesNotThrow @@ -10,13 +10,13 @@ import kotlin.reflect.javaType import kotlin.test.* @OptIn(ExperimentalStdlibApi::class) -class GradleEnterpriseApiIntegrationTest { +class DevelocityApiIntegrationTest { @Test fun canFetchBuildsWithDefaultConfig() = runTest { env = RealEnv keychain = realKeychain() - val api = GradleEnterpriseApi.newInstance( + val api = DevelocityApi.newInstance( config = Config( cacheConfig = Config.CacheConfig(cacheEnabled = false) ) @@ -39,7 +39,7 @@ class GradleEnterpriseApiIntegrationTest { apiUrl = "https://google.com/api/", apiToken = { "" }, ) - GradleEnterpriseApi.newInstance(config) + DevelocityApi.newInstance(config) } } @@ -49,19 +49,19 @@ class GradleEnterpriseApiIntegrationTest { val mainApiInterfaceProperties = getMainApiInterfaceProperties() generatedApiTypes.forEach { mainApiInterfaceProperties.singleOrNull { type -> type == it } - ?: fail("No property in GradleEnterpriseApi for $it") + ?: fail("No property in DevelocityApi for $it") } } private fun getGeneratedApiTypes(): List { val cp = ClassPath.from(this::class.java.classLoader) - return cp.getTopLevelClasses("com.gabrielfeo.gradle.enterprise.api") + return cp.getTopLevelClasses("com.gabrielfeo.develocity.api") .filter { it.simpleName.endsWith("Api") } - .filter { !it.simpleName.endsWith("GradleEnterpriseApi") } + .filter { !it.simpleName.endsWith("DevelocityApi") } .map { it.name } } - private fun getMainApiInterfaceProperties() = GradleEnterpriseApi::class.memberProperties + private fun getMainApiInterfaceProperties() = DevelocityApi::class.memberProperties .filter { it.visibility == PUBLIC } .map { it.returnType.javaType.typeName } -} \ No newline at end of file +} diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt index 7222faef..129cf934 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.internal.* -import com.gabrielfeo.gradle.enterprise.api.model.BuildModelName +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.internal.* +import com.gabrielfeo.develocity.api.model.BuildModelName import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.take import kotlinx.coroutines.test.runTest @@ -72,7 +72,7 @@ class BuildsApiExtensionsIntegrationTest { } private fun buildApi(recorder: RequestRecorder) = - GradleEnterpriseApi.newInstance( + DevelocityApi.newInstance( config = Config( clientBuilder = recorder.clientBuilder(), cacheConfig = Config.CacheConfig(cacheEnabled = false), @@ -87,4 +87,4 @@ class BuildsApiExtensionsIntegrationTest { private fun assertUrlParamNotNull(request: Request, key: String) { assertNotNull(request.url.queryParameter(key), "Expected param $key, but was null (${request.url})") } -} \ No newline at end of file +} diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt index a0679cfa..8c51b6f5 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension import okhttp3.OkHttpClient import okhttp3.Request @@ -13,4 +13,4 @@ class RequestRecorder { requests += it.request() it.proceed(it.request()) } -} \ No newline at end of file +} diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt index 9df461c8..84d7d61f 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal -import com.gabrielfeo.gradle.enterprise.api.internal.keychain +import com.gabrielfeo.develocity.api.internal.keychain import kotlin.test.* internal class KeychainIntegrationTest { diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt index 848cb6ea..5c5f8dbf 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.Config.CacheConfig -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.Config.CacheConfig +import com.gabrielfeo.develocity.api.internal.* import okhttp3.Dispatcher import okhttp3.OkHttpClient import java.io.File @@ -24,7 +24,7 @@ data class Config( env["GRADLE_ENTERPRISE_API_LOG_LEVEL"], /** - * Provides the URL of a Gradle Enterprise API instance REST API. By default, uses + * Provides the URL of a Develocity API instance REST API. By default, uses * environment variable `GRADLE_ENTERPRISE_API_URL`. Must end with `/api/`. */ val apiUrl: String = @@ -32,7 +32,7 @@ data class Config( ?: error("GRADLE_ENTERPRISE_API_URL is required"), /** - * Provides the access token for a Gradle Enterprise API instance. By default, uses keychain entry + * Provides the access token for a Develocity API instance. By default, uses keychain entry * `gradle-enterprise-api-token` or environment variable `GRADLE_ENTERPRISE_API_TOKEN`. */ val apiToken: () -> String = { @@ -66,7 +66,7 @@ data class Config( /** * Timeout for reading an API response, used for [OkHttpClient.readTimeoutMillis]. * By default, uses environment variable `GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS` - * or 60_000. Keep in mind that GE API responses can be big and slow to send depending on + * or 60_000. Keep in mind that Develocity API responses can be big and slow to send depending on * the endpoint. */ val readTimeoutMillis: Long = @@ -81,7 +81,7 @@ data class Config( ) { /** - * HTTP cache is off by default, but can speed up requests significantly. The Gradle Enterprise + * HTTP cache is off by default, but can speed up requests significantly. The Develocity * API disallows HTTP caching, but this library forcefully enables it by overwriting * cache-related headers in API responses. Enable with [cacheEnabled]. * @@ -101,12 +101,12 @@ data class Config( * all depends on whether it was matched by [shortTermCacheUrlPattern] or * [longTermCacheUrlPattern]. * - * Whenever GE is upgraded, cache should be [clear]ed. + * Whenever Develocity is upgraded, cache should be [clear]ed. * * ### Caveats * * While not encouraged by the API, caching shouldn't have any major downsides other than a - * time gap for certain queries, or having to reset cache when GE is upgraded. + * time gap for certain queries, or having to reset cache when Develocity is upgraded. * * #### Time gap * @@ -115,10 +115,10 @@ data class Config( * included in the query until the cache is invalidated 24h later. If that's a problem, * caching can be disabled for this `/api/builds` by changing [shortTermCacheUrlPattern]. * - * #### GE upgrades + * #### Develocity upgrades * - * When GE is upgraded, any API response can change. New data might be available in API - * endpoints such as `/api/build/{id}/gradle-attributes`. Thus, whenever the GE version + * When Develocity is upgraded, any API response can change. New data might be available in API + * endpoints such as `/api/build/{id}/gradle-attributes`. Thus, whenever the Develocity version * itself is upgraded, cache should be [clear]ed. */ @Suppress("MemberVisibilityCanBePrivate") @@ -133,11 +133,11 @@ data class Config( /** * HTTP cache location. By default, uses environment variable `GRADLE_ENTERPRISE_API_CACHE_DIR` - * or the system temporary folder (`java.io.tmpdir` / gradle-enterprise-api-kotlin-cache). + * or the system temporary folder (`java.io.tmpdir` / develocity-api-kotlin-cache). */ val cacheDir: File = env["GRADLE_ENTERPRISE_API_CACHE_DIR"]?.let(::File) - ?: File(systemProperties["user.home"], ".gradle-enterprise-api-kotlin-cache"), + ?: File(systemProperties["user.home"], ".develocity-api-kotlin-cache"), /** * Max size of the HTTP cache. By default, uses environment variable diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt index 8166de52..a98cc73e 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt @@ -1,16 +1,16 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.RealLoggerFactory -import com.gabrielfeo.gradle.enterprise.api.internal.buildOkHttpClient -import com.gabrielfeo.gradle.enterprise.api.internal.buildRetrofit -import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.Serializer +import com.gabrielfeo.develocity.api.internal.RealLoggerFactory +import com.gabrielfeo.develocity.api.internal.buildOkHttpClient +import com.gabrielfeo.develocity.api.internal.buildRetrofit +import com.gabrielfeo.develocity.api.internal.infrastructure.Serializer import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.create /** - * Gradle Enterprise API client. API endpoints are grouped exactly as in the - * [Gradle Enterprise API Manual](https://docs.gradle.com/enterprise/api-manual/#reference_documentation): + * Develocity API client. API endpoints are grouped exactly as in the + * [Develocity API Manual](https://docs.gradle.com/enterprise/api-manual/#reference_documentation): * * - [buildsApi] * - [buildCacheApi] @@ -20,7 +20,7 @@ import retrofit2.create * Create an instance with [newInstance]: * * ```kotlin - * val api = GradleEnterpriseApi.newInstance() + * val api = DevelocityApi.newInstance() * api.buildsApi.getBuilds(...) * ``` * @@ -28,11 +28,11 @@ import retrofit2.create * * ```kotlin * val options = Options(clientBuilder = myOwnOkHttpClient.newBuilder()) - * val api = GradleEnterpriseApi.newInstance(options) + * val api = DevelocityApi.newInstance(options) * api.buildsApi.getBuilds(...) * ``` */ -interface GradleEnterpriseApi { +interface DevelocityApi { val authApi: AuthApi val buildsApi: BuildsApi @@ -55,18 +55,18 @@ interface GradleEnterpriseApi { companion object { /** - * Create a new instance of `GradleEnterpriseApi` with a custom `Config`. + * Create a new instance of `DevelocityApi` with a custom `Config`. */ - fun newInstance(config: Config = Config()): GradleEnterpriseApi { - return RealGradleEnterpriseApi(config) + fun newInstance(config: Config = Config()): DevelocityApi { + return RealDevelocityApi(config) } } } -internal class RealGradleEnterpriseApi( +internal class RealDevelocityApi( override val config: Config, -) : GradleEnterpriseApi { +) : DevelocityApi { private val okHttpClient by lazy { buildOkHttpClient(config = config, RealLoggerFactory(config)) diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt index 197ae317..b8f29bfb 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.model.BuildAttributesValue +import com.gabrielfeo.develocity.api.model.BuildAttributesValue operator fun List.get(name: String): String? { return find { it.name == name }?.value diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt index ae9dc67c..4f3d9338 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt @@ -1,11 +1,11 @@ @file:Suppress("unused") -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.Config -import com.gabrielfeo.gradle.enterprise.api.BuildsApi -import com.gabrielfeo.gradle.enterprise.api.internal.API_MAX_BUILDS -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.Config +import com.gabrielfeo.develocity.api.BuildsApi +import com.gabrielfeo.develocity.api.internal.API_MAX_BUILDS +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.* * * - Will request from the API until results end, collection stops or an error occurs. * - Parameters same as [BuildsApi.getBuilds]. - * - Using [query] is highly recommended for server-side filtering (equivalent to GE advanced + * - Using [query] is highly recommended for server-side filtering (equivalent to Develocity advanced * query). * - `maxBuilds` is the only unsupported parameter, because this Flow will instead fetch * continously. Use [Flow.take] to stop collecting at a specific count. @@ -89,8 +89,8 @@ fun BuildsApi.getBuildsFlow( "getBuildsFlow(since, sinceBuild, fromInstant, fromBuild, query, reverse," + "maxWaitSecs, models = listOf(BuildModelName.gradleAttributes))", imports = [ - "com.gabrielfeo.gradle.enterprise.api.extension.getBuildsFlow", - "com.gabrielfeo.gradle.enterprise.api.model.BuildModelName", + "com.gabrielfeo.develocity.api.extension.getBuildsFlow", + "com.gabrielfeo.develocity.api.model.BuildModelName", ] ), ) diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt index a4f2925b..d730844b 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt index 6354b399..a6396fe4 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal /** * Undocumented max value of `/api/builds?maxBuilds`. Last checked in 2022.4. diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt index 9183a1b6..4c5108c7 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal internal var env: Env = RealEnv diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt index 7066d53b..f16eec15 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal -import com.gabrielfeo.gradle.enterprise.api.Config +import com.gabrielfeo.develocity.api.Config import org.slf4j.Logger internal var keychain: Keychain = realKeychain() diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt index 01c8c039..061ad647 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal import ch.qos.logback.classic.Level -import com.gabrielfeo.gradle.enterprise.api.Config +import com.gabrielfeo.develocity.api.Config import org.slf4j.Logger import kotlin.reflect.KClass diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt index 9b084f3b..6bf825a3 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt @@ -1,9 +1,9 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal -import com.gabrielfeo.gradle.enterprise.api.Config -import com.gabrielfeo.gradle.enterprise.api.internal.auth.HttpBearerAuth -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheEnforcingInterceptor -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheHitLoggingInterceptor +import com.gabrielfeo.develocity.api.Config +import com.gabrielfeo.develocity.api.internal.auth.HttpBearerAuth +import com.gabrielfeo.develocity.api.internal.caching.CacheEnforcingInterceptor +import com.gabrielfeo.develocity.api.internal.caching.CacheHitLoggingInterceptor import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt index 359f9f2a..2e3b6843 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal -import com.gabrielfeo.gradle.enterprise.api.Config +import com.gabrielfeo.develocity.api.Config import com.squareup.moshi.Moshi import okhttp3.OkHttpClient import retrofit2.Retrofit diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt index 302007d1..4305f1db 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal internal var systemProperties: SystemProperties = RealSystemProperties diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt index 847e89d0..68f6be7d 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal.caching +package com.gabrielfeo.develocity.api.internal.caching import okhttp3.Interceptor import okhttp3.Request diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt index d55b80d7..f3149390 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt +++ b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal.caching +package com.gabrielfeo.develocity.api.internal.caching import okhttp3.Interceptor import okhttp3.Response diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt index c5f1bcef..d311d538 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import kotlin.test.* class CacheConfigTest { @@ -41,4 +41,4 @@ class CacheConfigTest { assertTrue(matches(it), "/$pattern/ doesn't match '$it'") } } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt index 769a54e1..5f6a5b4b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import org.junit.jupiter.api.assertDoesNotThrow import kotlin.test.* @@ -79,4 +79,4 @@ class ConfigTest { (env as FakeEnv)["GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS"] = "100000" assertEquals(100_000L, Config().readTimeoutMillis) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt index 31f52be4..9f1f1313 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.flow.MutableStateFlow import retrofit2.http.Query @@ -51,4 +51,4 @@ class FakeBuildsApi( val attrs = readFromJsonResource("gradle-attributes-response.json") return attrs.copy(id = id) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt index 82c9fc25..5cca4128 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.extension.getBuildsFlow -import com.gabrielfeo.gradle.enterprise.api.extension.getGradleAttributesFlow -import com.gabrielfeo.gradle.enterprise.api.model.FakeBuild +import com.gabrielfeo.develocity.api.extension.getBuildsFlow +import com.gabrielfeo.develocity.api.extension.getGradleAttributesFlow +import com.gabrielfeo.develocity.api.model.FakeBuild import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.runTest @@ -11,7 +11,7 @@ import kotlin.test.assertEquals import kotlin.time.Duration.Companion.seconds @OptIn(ExperimentalCoroutinesApi::class) -class GradleEnterpriseApiExtensionsTest { +class DevelocityApiExtensionsTest { private val api = FakeBuildsApi( builds = listOf( diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt index 31b23514..00abf392 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt @@ -1,12 +1,12 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import kotlin.test.Test import kotlin.test.assertContains -class GradleEnterpriseApiTest { +class DevelocityApiTest { @Test fun `Fails eagerly if no API URL`() { @@ -14,7 +14,7 @@ class GradleEnterpriseApiTest { keychain = FakeKeychain() systemProperties = FakeSystemProperties.linux val error = assertThrows { - GradleEnterpriseApi.newInstance(Config()) + DevelocityApi.newInstance(Config()) } error.assertRootMessageContains("GRADLE_ENTERPRISE_API_URL") } @@ -25,7 +25,7 @@ class GradleEnterpriseApiTest { keychain = FakeKeychain() systemProperties = FakeSystemProperties.linux val api = assertDoesNotThrow { - GradleEnterpriseApi.newInstance(Config()) + DevelocityApi.newInstance(Config()) } val error = assertThrows { api.buildsApi.toString() diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt index 1ddcc252..b7c4185f 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt @@ -1,10 +1,10 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* -import com.gabrielfeo.gradle.enterprise.api.internal.FakeKeychain -import com.gabrielfeo.gradle.enterprise.api.internal.auth.HttpBearerAuth -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheEnforcingInterceptor -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheHitLoggingInterceptor +import com.gabrielfeo.develocity.api.internal.* +import com.gabrielfeo.develocity.api.internal.FakeKeychain +import com.gabrielfeo.develocity.api.internal.auth.HttpBearerAuth +import com.gabrielfeo.develocity.api.internal.caching.CacheEnforcingInterceptor +import com.gabrielfeo.develocity.api.internal.caching.CacheHitLoggingInterceptor import okhttp3.Dispatcher import okhttp3.OkHttpClient import kotlin.test.* diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt index c4e077fa..780f0c60 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import com.squareup.moshi.Moshi import retrofit2.Retrofit import kotlin.test.* @@ -41,4 +41,4 @@ class RetrofitTest { moshi = Moshi.Builder().build() ) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt index 80b64cc7..862b131b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.Serializer +import com.gabrielfeo.develocity.api.internal.infrastructure.Serializer import com.squareup.moshi.adapter import okio.buffer import okio.source diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt index fa2aa5e0..767c067a 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.FakeBuildsApi -import com.gabrielfeo.gradle.enterprise.api.model.Build -import com.gabrielfeo.gradle.enterprise.api.model.FakeBuild +import com.gabrielfeo.develocity.api.FakeBuildsApi +import com.gabrielfeo.develocity.api.model.Build +import com.gabrielfeo.develocity.api.model.FakeBuild import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -63,4 +63,4 @@ class BuildsApiExtensionsTest { assertEquals(2, api.getBuildsCallCount.value) channel.close() } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt index cced9987..caa580b9 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.FakeBuildsApi -import com.gabrielfeo.gradle.enterprise.api.model.FakeBuild +import com.gabrielfeo.develocity.api.FakeBuildsApi +import com.gabrielfeo.develocity.api.model.FakeBuild import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.asFlow @@ -51,4 +51,4 @@ class MappingTest { } assertEquals(5, api.getGradleAttributesCallCount.value) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt index b4de697f..6ee2553f 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt @@ -1,2 +1,2 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt index b4de697f..6ee2553f 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt @@ -1,2 +1,2 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt index b4de697f..6ee2553f 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt @@ -1,2 +1,2 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt index 10e4a140..2120ce96 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal.caching +package com.gabrielfeo.develocity.api.internal.caching import okhttp3.OkHttpClient import okhttp3.Request diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt index 8ac01051..dea7df32 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.model +package com.gabrielfeo.develocity.api.model -import com.gabrielfeo.gradle.enterprise.api.extension.contains -import com.gabrielfeo.gradle.enterprise.api.extension.get +import com.gabrielfeo.develocity.api.extension.contains +import com.gabrielfeo.develocity.api.extension.get import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -25,4 +25,4 @@ class BuildAttributesValueExtensionsTest { assertTrue("foo" in list) assertTrue("bar" !in list) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt index cff233dc..c956c003 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt +++ b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.model +package com.gabrielfeo.develocity.api.model @Suppress("TestFunctionName") fun FakeBuild(id: String, availableAt: Long) = Build( diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt index 775c58a4..a1469de1 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt @@ -1,10 +1,10 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.model.* import retrofit2.http.Query /** - * Scaffold for a fake `GradleEnterpriseApi` implementation with default methods throwing a + * Scaffold for a fake `DevelocityApi` implementation with default methods throwing a * [NotImplementedError]. Extend this interface and override methods to fake behavior as needed. */ interface FakeBuildsApiScaffold : BuildsApi { @@ -87,4 +87,4 @@ interface FakeBuildsApiScaffold : BuildsApi { override suspend fun getMavenModules(id: String, availabilityWaitTimeoutSecs: Int?): List { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt index c79dd768..291648d8 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal class FakeEnv( vararg vars: Pair, @@ -10,4 +10,4 @@ class FakeEnv( operator fun set(name: String, value: String?) = vars.put(name, value) operator fun contains(name: String) = name in vars -} \ No newline at end of file +} diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt index 37fa4315..ebcfa861 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal internal class FakeKeychain( vararg entries: Pair, @@ -9,4 +9,4 @@ internal class FakeKeychain( override fun get(entry: String) = entries[entry]?.let { KeychainResult.Success(it) } ?: KeychainResult.Error("entry $entry not mocked") -} \ No newline at end of file +} diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt index 3694b67f..50732abb 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal class FakeSystemProperties( vararg vars: Pair, @@ -15,4 +15,4 @@ class FakeSystemProperties( operator fun set(name: String, value: String?) = vars.put(name, value) operator fun contains(name: String) = name in vars -} \ No newline at end of file +} From 9175810d07eb92e8c9fbe9fedbcfa5ac0d3afa89 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 02:33:08 +0100 Subject: [PATCH 26/35] Move files from **/gradle/enterprise to **/develocity (#187) Split from #177 for better git log. Part of #184. --- .../{gradle/enterprise => develocity}/api/example/Main.kt | 0 .../api/example/analysis/MostFrequentBuilds.kt | 0 .../api/DevelocityApiIntegrationTest.kt} | 0 .../api/extension/BuildsApiExtensionsIntegrationTest.kt | 0 .../enterprise => develocity}/api/extension/RequestRecorder.kt | 0 .../api/internal/KeychainIntegrationTest.kt | 0 .../gabrielfeo/{gradle/enterprise => develocity}/api/Config.kt | 0 .../GradleEnterpriseApi.kt => develocity/api/DevelocityApi.kt} | 0 .../api/extension/BuildAttributesValueExtensions.kt | 0 .../api/extension/BuildsApiExtensions.kt | 0 .../{gradle/enterprise => develocity}/api/extension/Mapping.kt | 0 .../enterprise => develocity}/api/internal/ApiConstants.kt | 0 .../{gradle/enterprise => develocity}/api/internal/Env.kt | 0 .../{gradle/enterprise => develocity}/api/internal/Keychain.kt | 0 .../enterprise => develocity}/api/internal/LoggerFactory.kt | 0 .../enterprise => develocity}/api/internal/OkHttpClient.kt | 0 .../{gradle/enterprise => develocity}/api/internal/Retrofit.kt | 0 .../enterprise => develocity}/api/internal/SystemProperties.kt | 0 .../api/internal/caching/CacheEnforcingInterceptor.kt | 0 .../api/internal/caching/CacheHitLoggingInterceptor.kt | 0 .../{gradle/enterprise => develocity}/api/CacheConfigTest.kt | 0 .../{gradle/enterprise => develocity}/api/ConfigTest.kt | 0 .../api/DevelocityApiExtensionsTest.kt} | 0 .../api/DevelocityApiTest.kt} | 0 .../{gradle/enterprise => develocity}/api/FakeBuildsApi.kt | 0 .../{gradle/enterprise => develocity}/api/OkHttpClientTest.kt | 0 .../{gradle/enterprise => develocity}/api/RetrofitTest.kt | 0 .../{gradle/enterprise => develocity}/api/TestResourceUtils.kt | 0 .../api/extension/BuildsApiExtensionsTest.kt | 0 .../enterprise => develocity}/api/extension/MappingTest.kt | 0 .../{gradle/enterprise => develocity}/api/internal/FakeEnv.kt | 0 .../enterprise => develocity}/api/internal/FakeKeychain.kt | 0 .../api/internal/FakeSystemProperties.kt | 0 .../api/internal/caching/CacheEnforcingInterceptorTest.kt | 0 .../api/model/BuildAttributesValueExtensionsTest.kt | 0 .../{gradle/enterprise => develocity}/api/model/FakeBuild.kt | 0 .../api/FakeDevelocityApiScaffold.kt} | 0 .../{gradle/enterprise => develocity}/api/internal/FakeEnv.kt | 0 .../enterprise => develocity}/api/internal/FakeKeychain.kt | 0 .../api/internal/FakeSystemProperties.kt | 0 40 files changed, 0 insertions(+), 0 deletions(-) rename examples/example-project/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/example/Main.kt (100%) rename examples/example-project/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/example/analysis/MostFrequentBuilds.kt (100%) rename library/src/integrationTest/kotlin/com/gabrielfeo/{gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt => develocity/api/DevelocityApiIntegrationTest.kt} (100%) rename library/src/integrationTest/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/BuildsApiExtensionsIntegrationTest.kt (100%) rename library/src/integrationTest/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/RequestRecorder.kt (100%) rename library/src/integrationTest/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/KeychainIntegrationTest.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/Config.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise/api/GradleEnterpriseApi.kt => develocity/api/DevelocityApi.kt} (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/BuildAttributesValueExtensions.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/BuildsApiExtensions.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/Mapping.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/ApiConstants.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/Env.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/Keychain.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/LoggerFactory.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/OkHttpClient.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/Retrofit.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/SystemProperties.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/caching/CacheEnforcingInterceptor.kt (100%) rename library/src/main/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/caching/CacheHitLoggingInterceptor.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/CacheConfigTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/ConfigTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt => develocity/api/DevelocityApiExtensionsTest.kt} (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise/api/GradleEnterpriseApiTest.kt => develocity/api/DevelocityApiTest.kt} (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/FakeBuildsApi.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/OkHttpClientTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/RetrofitTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/TestResourceUtils.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/BuildsApiExtensionsTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/extension/MappingTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/FakeEnv.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/FakeKeychain.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/FakeSystemProperties.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/caching/CacheEnforcingInterceptorTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/model/BuildAttributesValueExtensionsTest.kt (100%) rename library/src/test/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/model/FakeBuild.kt (100%) rename library/src/testFixtures/kotlin/com/gabrielfeo/{gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt => develocity/api/FakeDevelocityApiScaffold.kt} (100%) rename library/src/testFixtures/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/FakeEnv.kt (100%) rename library/src/testFixtures/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/FakeKeychain.kt (100%) rename library/src/testFixtures/kotlin/com/gabrielfeo/{gradle/enterprise => develocity}/api/internal/FakeSystemProperties.kt (100%) diff --git a/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/Main.kt similarity index 100% rename from examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt rename to examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/Main.kt diff --git a/examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/analysis/MostFrequentBuilds.kt similarity index 100% rename from examples/example-project/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt rename to examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/analysis/MostFrequentBuilds.kt diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt similarity index 100% rename from library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt rename to library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt similarity index 100% rename from library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt rename to library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/RequestRecorder.kt similarity index 100% rename from library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt rename to library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/RequestRecorder.kt diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/KeychainIntegrationTest.kt similarity index 100% rename from library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt rename to library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/KeychainIntegrationTest.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildAttributesValueExtensions.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildAttributesValueExtensions.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensions.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensions.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/Mapping.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/Mapping.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/ApiConstants.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/ApiConstants.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Env.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Env.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/LoggerFactory.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/LoggerFactory.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/LoggerFactory.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/OkHttpClient.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/OkHttpClient.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Retrofit.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Retrofit.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/SystemProperties.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/SystemProperties.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptor.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptor.kt diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheHitLoggingInterceptor.kt similarity index 100% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheHitLoggingInterceptor.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiExtensionsTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiExtensionsTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/FakeBuildsApi.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/FakeBuildsApi.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/TestResourceUtils.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/TestResourceUtils.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/MappingTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/MappingTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptorTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptorTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/model/BuildAttributesValueExtensionsTest.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/model/BuildAttributesValueExtensionsTest.kt diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/model/FakeBuild.kt similarity index 100% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/model/FakeBuild.kt diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/FakeDevelocityApiScaffold.kt similarity index 100% rename from library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt rename to library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/FakeDevelocityApiScaffold.kt diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt similarity index 100% rename from library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt rename to library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt similarity index 100% rename from library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt rename to library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt similarity index 100% rename from library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt rename to library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt From feb9db9cb47741c6a143318aa8b9e04d88b96a16 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 02:48:10 +0100 Subject: [PATCH 27/35] Rename environment variables to DEVELOCITY_ (#188) Part of #184 --- library/build.gradle.kts | 2 +- .../com/gabrielfeo/develocity/api/Config.kt | 50 +++++++++---------- .../develocity/api/internal/Keychain.kt | 2 +- .../develocity/api/CacheConfigTest.kt | 2 +- .../gabrielfeo/develocity/api/ConfigTest.kt | 14 +++--- .../develocity/api/DevelocityApiTest.kt | 6 +-- .../develocity/api/OkHttpClientTest.kt | 14 +++--- .../gabrielfeo/develocity/api/RetrofitTest.kt | 8 +-- 8 files changed, 49 insertions(+), 49 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index e566ddbf..f7c8c26e 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -52,7 +52,7 @@ tasks.named("javadocJar") { } tasks.named("integrationTest") { - environment("GRADLE_ENTERPRISE_API_LOG_LEVEL", "DEBUG") + environment("DEVELOCITY_API_LOG_LEVEL", "DEBUG") } java { diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt index 5c5f8dbf..ec9a1bb9 100644 --- a/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt @@ -21,19 +21,19 @@ data class Config( * To use different SLF4J bindings, simply exclude the logback dependency. */ val logLevel: String? = - env["GRADLE_ENTERPRISE_API_LOG_LEVEL"], + env["DEVELOCITY_API_LOG_LEVEL"], /** * Provides the URL of a Develocity API instance REST API. By default, uses - * environment variable `GRADLE_ENTERPRISE_API_URL`. Must end with `/api/`. + * environment variable `DEVELOCITY_API_URL`. Must end with `/api/`. */ val apiUrl: String = - env["GRADLE_ENTERPRISE_API_URL"] - ?: error("GRADLE_ENTERPRISE_API_URL is required"), + env["DEVELOCITY_API_URL"] + ?: error("DEVELOCITY_API_URL is required"), /** * Provides the access token for a Develocity API instance. By default, uses keychain entry - * `gradle-enterprise-api-token` or environment variable `GRADLE_ENTERPRISE_API_TOKEN`. + * `gradle-enterprise-api-token` or environment variable `DEVELOCITY_API_TOKEN`. */ val apiToken: () -> String = { requireEnvOrKeychainToken() @@ -53,7 +53,7 @@ data class Config( /** * Maximum amount of concurrent requests allowed. Further requests will be queued. By default, - * uses environment variable `GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS` or 5 (OkHttp's + * uses environment variable `DEVELOCITY_API_MAX_CONCURRENT_REQUESTS` or 5 (OkHttp's * default value of [Dispatcher.maxRequestsPerHost]). * * If set, will set [Dispatcher.maxRequests] and [Dispatcher.maxRequestsPerHost] of the @@ -61,16 +61,16 @@ data class Config( * if any. */ val maxConcurrentRequests: Int? = - env["GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS"]?.toInt(), + env["DEVELOCITY_API_MAX_CONCURRENT_REQUESTS"]?.toInt(), /** * Timeout for reading an API response, used for [OkHttpClient.readTimeoutMillis]. - * By default, uses environment variable `GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS` + * By default, uses environment variable `DEVELOCITY_API_READ_TIMEOUT_MILLIS` * or 60_000. Keep in mind that Develocity API responses can be big and slow to send depending on * the endpoint. */ val readTimeoutMillis: Long = - env["GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS"]?.toLong() + env["DEVELOCITY_API_READ_TIMEOUT_MILLIS"]?.toLong() ?: 60_000L, /** @@ -126,30 +126,30 @@ data class Config( /** * Whether caching is enabled. By default, uses environment variable - * `GRADLE_ENTERPRISE_API_CACHE_ENABLED` or `false`. + * `DEVELOCITY_API_CACHE_ENABLED` or `false`. */ val cacheEnabled: Boolean = - env["GRADLE_ENTERPRISE_API_CACHE_ENABLED"].toBoolean(), + env["DEVELOCITY_API_CACHE_ENABLED"].toBoolean(), /** - * HTTP cache location. By default, uses environment variable `GRADLE_ENTERPRISE_API_CACHE_DIR` + * HTTP cache location. By default, uses environment variable `DEVELOCITY_API_CACHE_DIR` * or the system temporary folder (`java.io.tmpdir` / develocity-api-kotlin-cache). */ val cacheDir: File = - env["GRADLE_ENTERPRISE_API_CACHE_DIR"]?.let(::File) + env["DEVELOCITY_API_CACHE_DIR"]?.let(::File) ?: File(systemProperties["user.home"], ".develocity-api-kotlin-cache"), /** * Max size of the HTTP cache. By default, uses environment variable - * `GRADLE_ENTERPRISE_API_MAX_CACHE_SIZE` or ~1 GB. + * `DEVELOCITY_API_MAX_CACHE_SIZE` or ~1 GB. */ - val maxCacheSize: Long = env["GRADLE_ENTERPRISE_API_MAX_CACHE_SIZE"]?.toLong() + val maxCacheSize: Long = env["DEVELOCITY_API_MAX_CACHE_SIZE"]?.toLong() ?: 1_000_000_000L, /** * Regex pattern to match API URLs that are OK to store long-term in the HTTP cache, up to * [longTermCacheMaxAge] (1y by default, max value). By default, uses environment variable - * `GRADLE_ENTERPRISE_API_LONG_TERM_CACHE_URL_PATTERN` or a pattern matching: + * `DEVELOCITY_API_LONG_TERM_CACHE_URL_PATTERN` or a pattern matching: * - {host}/api/builds/{id}/gradle-attributes * - {host}/api/builds/{id}/maven-attributes * - {host}/api/builds/{id}/gradle-build-cache-performance @@ -158,7 +158,7 @@ data class Config( * Use `|` to define multiple patterns in one, e.g. `.*gradle-attributes|.*test-distribution`. */ val longTermCacheUrlPattern: Regex = - env["GRADLE_ENTERPRISE_API_LONG_TERM_CACHE_URL_PATTERN"]?.toRegex() + env["DEVELOCITY_API_LONG_TERM_CACHE_URL_PATTERN"]?.toRegex() ?: Regex( """ .*/api/builds/[\d\w]+/(?:gradle|maven)-(?:attributes|build-cache-performance) @@ -167,30 +167,30 @@ data class Config( /** * Max age in seconds for URLs to be cached long-term (matched by [longTermCacheUrlPattern]). - * By default, uses environment variable `GRADLE_ENTERPRISE_API_LONG_TERM_CACHE_MAX_AGE` or 1 year. + * By default, uses environment variable `DEVELOCITY_API_LONG_TERM_CACHE_MAX_AGE` or 1 year. */ val longTermCacheMaxAge: Long = - env["GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() + env["DEVELOCITY_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() ?: 365.days.inWholeSeconds, /** * Regex pattern to match API URLs that are OK to store short-term in the HTTP cache, up to * [shortTermCacheMaxAge] (1d by default). By default, uses environment variable - * `GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_URL_PATTERN` or a pattern matching: + * `DEVELOCITY_API_SHORT_TERM_CACHE_URL_PATTERN` or a pattern matching: * - {host}/api/builds * * Use `|` to define multiple patterns in one, e.g. `.*gradle-attributes|.*test-distribution`. */ val shortTermCacheUrlPattern: Regex = - env["GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_URL_PATTERN"]?.toRegex() + env["DEVELOCITY_API_SHORT_TERM_CACHE_URL_PATTERN"]?.toRegex() ?: """.*/builds(?:\?.*|\Z)""".toRegex(), /** * Max age in seconds for URLs to be cached short-term (matched by [shortTermCacheUrlPattern]). - * By default, uses environment variable `GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_MAX_AGE` or 1 day. + * By default, uses environment variable `DEVELOCITY_API_SHORT_TERM_CACHE_MAX_AGE` or 1 day. */ val shortTermCacheMaxAge: Long = - env["GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() + env["DEVELOCITY_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() ?: 1.days.inWholeSeconds, ) } @@ -202,6 +202,6 @@ internal fun requireEnvOrKeychainToken(): String { is KeychainResult.Error -> {} } } - return env["GRADLE_ENTERPRISE_API_TOKEN"] - ?: error("GRADLE_ENTERPRISE_API_TOKEN is required") + return env["DEVELOCITY_API_TOKEN"] + ?: error("DEVELOCITY_API_TOKEN is required") } diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt index f16eec15..e96b57ab 100644 --- a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt @@ -47,5 +47,5 @@ internal class RealKeychain( private const val KEYCHAIN_DEPRECATION_WARNING = "WARNING: passing token via macOS keychain is deprecated. Please pass it as the " + - "GRADLE_ENTERPRISE_API_TOKEN environment variable instead. Keychain support will be " + + "DEVELOCITY_API_TOKEN environment variable instead. Keychain support will be " + "removed in the next release. See release notes for details and alternatives." diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt index d311d538..801eff1e 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt @@ -7,7 +7,7 @@ class CacheConfigTest { @BeforeTest fun before() { - env = FakeEnv("GRADLE_ENTERPRISE_API_URL" to "https://example.com/api/") + env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/") systemProperties = FakeSystemProperties.macOs keychain = FakeKeychain() } diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt index 5f6a5b4b..476a350b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt @@ -8,7 +8,7 @@ class ConfigTest { @BeforeTest fun before() { - env = FakeEnv("GRADLE_ENTERPRISE_API_URL" to "https://example.com/api/") + env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/") systemProperties = FakeSystemProperties.macOs keychain = FakeKeychain() } @@ -23,26 +23,26 @@ class ConfigTest { @Test fun `Given URL set in env, apiUrl is env URL`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_URL"] = "https://example.com/api/" + (env as FakeEnv)["DEVELOCITY_API_URL"] = "https://example.com/api/" assertEquals("https://example.com/api/", Config().apiUrl) } @Test fun `Given macOS and keychain token, keychain token used`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_TOKEN"] = "bar" + (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" keychain = FakeKeychain("gradle-enterprise-api-token" to "foo") assertEquals("foo", Config().apiToken()) } @Test fun `Given macOS but no keychain token, env token used`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_TOKEN"] = "bar" + (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" assertEquals("bar", Config().apiToken()) } @Test fun `Given Linux, keychain never tried and env token used`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_TOKEN"] = "bar" + (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" keychain = object : Keychain { override fun get(entry: String) = error("Error: Tried to access macOS keychain in Linux") @@ -68,7 +68,7 @@ class ConfigTest { @Test fun `maxConcurrentRequests accepts int`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS"] = "1" + (env as FakeEnv)["DEVELOCITY_API_MAX_CONCURRENT_REQUESTS"] = "1" assertDoesNotThrow { Config().maxConcurrentRequests } @@ -76,7 +76,7 @@ class ConfigTest { @Test fun `Given timeout set in env, readTimeoutMillis returns env value`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS"] = "100000" + (env as FakeEnv)["DEVELOCITY_API_READ_TIMEOUT_MILLIS"] = "100000" assertEquals(100_000L, Config().readTimeoutMillis) } } diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt index 00abf392..daeb5b2f 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt @@ -16,12 +16,12 @@ class DevelocityApiTest { val error = assertThrows { DevelocityApi.newInstance(Config()) } - error.assertRootMessageContains("GRADLE_ENTERPRISE_API_URL") + error.assertRootMessageContains("DEVELOCITY_API_URL") } @Test fun `Fails lazily if no API token`() { - env = FakeEnv("GRADLE_ENTERPRISE_API_URL" to "example-url") + env = FakeEnv("DEVELOCITY_API_URL" to "example-url") keychain = FakeKeychain() systemProperties = FakeSystemProperties.linux val api = assertDoesNotThrow { @@ -30,7 +30,7 @@ class DevelocityApiTest { val error = assertThrows { api.buildsApi.toString() } - error.assertRootMessageContains("GRADLE_ENTERPRISE_API_TOKEN") + error.assertRootMessageContains("DEVELOCITY_API_TOKEN") } private fun Throwable.assertRootMessageContains(text: String) { diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt index b7c4185f..4480444b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt @@ -20,7 +20,7 @@ class OkHttpClientTest { @Test fun `Given maxConcurrentRequests, sets values in Dispatcher`() { val client = buildClient( - "GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS" to "123" + "DEVELOCITY_API_MAX_CONCURRENT_REQUESTS" to "123" ) assertEquals(123, client.dispatcher.maxRequests) assertEquals(123, client.dispatcher.maxRequestsPerHost) @@ -42,14 +42,14 @@ class OkHttpClientTest { @Test fun `Given cache enabled, configures caching`() { - val client = buildClient("GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true") + val client = buildClient("DEVELOCITY_API_CACHE_ENABLED" to "true") assertTrue(client.networkInterceptors.any { it is CacheEnforcingInterceptor }) assertNotNull(client.cache) } @Test fun `Given cache disabled, no caching or cache logging`() { - val client = buildClient("GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "false") + val client = buildClient("DEVELOCITY_API_CACHE_ENABLED" to "false") assertTrue(client.networkInterceptors.none { it is CacheEnforcingInterceptor }) assertTrue(client.interceptors.none { it is CacheHitLoggingInterceptor }) assertNull(client.cache) @@ -67,10 +67,10 @@ class OkHttpClientTest { clientBuilder: OkHttpClient.Builder? = null, ): OkHttpClient { val fakeEnv = FakeEnv(*envVars) - if ("GRADLE_ENTERPRISE_API_TOKEN" !in fakeEnv) - fakeEnv["GRADLE_ENTERPRISE_API_TOKEN"] = "example-token" - if ("GRADLE_ENTERPRISE_API_URL" !in fakeEnv) - fakeEnv["GRADLE_ENTERPRISE_API_URL"] = "example-url" + if ("DEVELOCITY_API_TOKEN" !in fakeEnv) + fakeEnv["DEVELOCITY_API_TOKEN"] = "example-token" + if ("DEVELOCITY_API_URL" !in fakeEnv) + fakeEnv["DEVELOCITY_API_URL"] = "example-url" env = fakeEnv systemProperties = FakeSystemProperties.macOs keychain = FakeKeychain() diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt index 780f0c60..189ef6ed 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt @@ -10,7 +10,7 @@ class RetrofitTest { @Test fun `Sets instance URL from options, stripping api segment`() { val retrofit = buildRetrofit( - "GRADLE_ENTERPRISE_API_URL" to "https://example.com/api/", + "DEVELOCITY_API_URL" to "https://example.com/api/", ) // That's what generated classes expect assertEquals("https://example.com/", retrofit.baseUrl().toString()) @@ -20,7 +20,7 @@ class RetrofitTest { fun `Rejects invalid URL`() { assertFails { buildRetrofit( - "GRADLE_ENTERPRISE_API_URL" to "https://example.com/", + "DEVELOCITY_API_URL" to "https://example.com/", ) } } @@ -29,8 +29,8 @@ class RetrofitTest { vararg envVars: Pair, ): Retrofit { val fakeEnv = FakeEnv(*envVars) - if ("GRADLE_ENTERPRISE_API_TOKEN" !in fakeEnv) - fakeEnv["GRADLE_ENTERPRISE_API_TOKEN"] = "example-token" + if ("DEVELOCITY_API_TOKEN" !in fakeEnv) + fakeEnv["DEVELOCITY_API_TOKEN"] = "example-token" env = fakeEnv systemProperties = FakeSystemProperties.macOs keychain = FakeKeychain() From 53e95df3fb543b004e718721693a214a91e95ff4 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 02:56:40 +0100 Subject: [PATCH 28/35] Delete keychain support (#189) Deprecated in #164 / 2023.4.0 (last release). --- .../api/DevelocityApiIntegrationTest.kt | 2 - .../BuildsApiExtensionsIntegrationTest.kt | 1 - .../api/internal/KeychainIntegrationTest.kt | 16 ------ .../com/gabrielfeo/develocity/api/Config.kt | 18 ++----- .../develocity/api/internal/Keychain.kt | 51 ------------------- .../develocity/api/CacheConfigTest.kt | 2 - .../gabrielfeo/develocity/api/ConfigTest.kt | 36 ++----------- .../develocity/api/DevelocityApiTest.kt | 4 -- .../develocity/api/OkHttpClientTest.kt | 3 -- .../gabrielfeo/develocity/api/RetrofitTest.kt | 2 - .../develocity/api/internal/FakeKeychain.kt | 12 ----- .../api/internal/FakeSystemProperties.kt | 18 ------- 12 files changed, 8 insertions(+), 157 deletions(-) delete mode 100644 library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/KeychainIntegrationTest.kt delete mode 100644 library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt delete mode 100644 library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt delete mode 100644 library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt index 92a180d1..41864a71 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt @@ -15,7 +15,6 @@ class DevelocityApiIntegrationTest { @Test fun canFetchBuildsWithDefaultConfig() = runTest { env = RealEnv - keychain = realKeychain() val api = DevelocityApi.newInstance( config = Config( cacheConfig = Config.CacheConfig(cacheEnabled = false) @@ -33,7 +32,6 @@ class DevelocityApiIntegrationTest { @Test fun canBuildNewInstanceWithPureCodeConfiguration() = runTest { env = FakeEnv() - keychain = FakeKeychain() assertDoesNotThrow { val config = Config( apiUrl = "https://google.com/api/", diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt index 129cf934..3fc70699 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt @@ -16,7 +16,6 @@ class BuildsApiExtensionsIntegrationTest { init { env = RealEnv - keychain = realKeychain() } private val recorder = RequestRecorder() diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/KeychainIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/KeychainIntegrationTest.kt deleted file mode 100644 index 84d7d61f..00000000 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/KeychainIntegrationTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.gabrielfeo.develocity.api.internal - -import com.gabrielfeo.develocity.api.internal.keychain -import kotlin.test.* - -internal class KeychainIntegrationTest { - - @Test - fun getApiToken() { - env = RealEnv - keychain = realKeychain() - val result = keychain.get("gradle-enterprise-api-token") - assertIs(result) - assertFalse(result.token.isNullOrBlank(), "Keychain returned null or blank") - } -} diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt index ec9a1bb9..6aed79dd 100644 --- a/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt @@ -32,11 +32,12 @@ data class Config( ?: error("DEVELOCITY_API_URL is required"), /** - * Provides the access token for a Develocity API instance. By default, uses keychain entry - * `gradle-enterprise-api-token` or environment variable `DEVELOCITY_API_TOKEN`. + * Provides the access token for a Develocity API instance. By default, uses environment + * variable `DEVELOCITY_API_TOKEN`. */ val apiToken: () -> String = { - requireEnvOrKeychainToken() + env["DEVELOCITY_API_TOKEN"] + ?: error("DEVELOCITY_API_TOKEN is required") }, /** @@ -194,14 +195,3 @@ data class Config( ?: 1.days.inWholeSeconds, ) } - -internal fun requireEnvOrKeychainToken(): String { - if (systemProperties["os.name"] == "Mac OS X") { - when (val result = keychain.get("gradle-enterprise-api-token")) { - is KeychainResult.Success -> return result.token - is KeychainResult.Error -> {} - } - } - return env["DEVELOCITY_API_TOKEN"] - ?: error("DEVELOCITY_API_TOKEN is required") -} diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt deleted file mode 100644 index e96b57ab..00000000 --- a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Keychain.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.gabrielfeo.develocity.api.internal - -import com.gabrielfeo.develocity.api.Config -import org.slf4j.Logger - -internal var keychain: Keychain = realKeychain() -internal fun realKeychain() = RealKeychain( - RealSystemProperties, - // Setting level via env will work, via code won't. Not worth fixing, since keychain will - // be removed soon. - RealLoggerFactory(Config()).newLogger(RealKeychain::class), -) - -internal interface Keychain { - fun get(entry: String): KeychainResult -} - -internal sealed interface KeychainResult { - data class Success(val token: String) : KeychainResult - data class Error(val description: String) : KeychainResult -} - -internal class RealKeychain( - private val systemProperties: SystemProperties, - private val logger: Logger, -) : Keychain { - override fun get( - entry: String, - ): KeychainResult { - val login = systemProperties["user.name"] ?: - return KeychainResult.Error("null user.name") - val process = ProcessBuilder( - "security", "find-generic-password", "-w", "-a", login, "-s", entry - ).start() - val status = process.waitFor() - logger.debug("Keychain exit status: $status)") - if (status != 0) { - return KeychainResult.Error("exit $status") - } - println(KEYCHAIN_DEPRECATION_WARNING) - val token = process.inputStream.bufferedReader().use { - it.readText().trim() - } - return KeychainResult.Success(token) - } -} - -private const val KEYCHAIN_DEPRECATION_WARNING = - "WARNING: passing token via macOS keychain is deprecated. Please pass it as the " + - "DEVELOCITY_API_TOKEN environment variable instead. Keychain support will be " + - "removed in the next release. See release notes for details and alternatives." diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt index 801eff1e..9289734e 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt @@ -8,8 +8,6 @@ class CacheConfigTest { @BeforeTest fun before() { env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/") - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() } @Test diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt index 476a350b..99fb8977 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt @@ -9,8 +9,6 @@ class ConfigTest { @BeforeTest fun before() { env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/") - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() } @Test @@ -28,42 +26,16 @@ class ConfigTest { } @Test - fun `Given macOS and keychain token, keychain token used`() { - (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" - keychain = FakeKeychain("gradle-enterprise-api-token" to "foo") - assertEquals("foo", Config().apiToken()) - } - - @Test - fun `Given macOS but no keychain token, env token used`() { - (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" - assertEquals("bar", Config().apiToken()) - } - - @Test - fun `Given Linux, keychain never tried and env token used`() { - (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" - keychain = object : Keychain { - override fun get(entry: String) = - error("Error: Tried to access macOS keychain in Linux") - } - systemProperties = FakeSystemProperties.linux - assertEquals("bar", Config().apiToken()) - } - - @Test - fun `Given macOS and no token anywhere, error`() { + fun `Given no token, error`() { assertFails { Config().apiToken() } } @Test - fun `Given Linux and no env token, fails`() { - systemProperties = FakeSystemProperties.linux - assertFails { - Config().apiToken() - } + fun `Given token set in env, apiToken is env token`() { + (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" + assertEquals("bar", Config().apiToken()) } @Test diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt index daeb5b2f..4c19dbf4 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt @@ -11,8 +11,6 @@ class DevelocityApiTest { @Test fun `Fails eagerly if no API URL`() { env = FakeEnv() - keychain = FakeKeychain() - systemProperties = FakeSystemProperties.linux val error = assertThrows { DevelocityApi.newInstance(Config()) } @@ -22,8 +20,6 @@ class DevelocityApiTest { @Test fun `Fails lazily if no API token`() { env = FakeEnv("DEVELOCITY_API_URL" to "example-url") - keychain = FakeKeychain() - systemProperties = FakeSystemProperties.linux val api = assertDoesNotThrow { DevelocityApi.newInstance(Config()) } diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt index 4480444b..800abd9b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt @@ -1,7 +1,6 @@ package com.gabrielfeo.develocity.api import com.gabrielfeo.develocity.api.internal.* -import com.gabrielfeo.develocity.api.internal.FakeKeychain import com.gabrielfeo.develocity.api.internal.auth.HttpBearerAuth import com.gabrielfeo.develocity.api.internal.caching.CacheEnforcingInterceptor import com.gabrielfeo.develocity.api.internal.caching.CacheHitLoggingInterceptor @@ -72,8 +71,6 @@ class OkHttpClientTest { if ("DEVELOCITY_API_URL" !in fakeEnv) fakeEnv["DEVELOCITY_API_URL"] = "example-url" env = fakeEnv - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() val config = when (clientBuilder) { null -> Config() else -> Config(clientBuilder = clientBuilder) diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt index 189ef6ed..3a79fad2 100644 --- a/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt @@ -32,8 +32,6 @@ class RetrofitTest { if ("DEVELOCITY_API_TOKEN" !in fakeEnv) fakeEnv["DEVELOCITY_API_TOKEN"] = "example-token" env = fakeEnv - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() val config = Config() return buildRetrofit( config = config, diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt deleted file mode 100644 index ebcfa861..00000000 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.gabrielfeo.develocity.api.internal - -internal class FakeKeychain( - vararg entries: Pair, -) : Keychain { - - private val entries = entries.toMap() - - override fun get(entry: String) = - entries[entry]?.let { KeychainResult.Success(it) } - ?: KeychainResult.Error("entry $entry not mocked") -} diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt deleted file mode 100644 index 50732abb..00000000 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.gabrielfeo.develocity.api.internal - -class FakeSystemProperties( - vararg vars: Pair, -) : SystemProperties { - - companion object { - val macOs = FakeSystemProperties("os.name" to "Mac OS X") - val linux = FakeSystemProperties("os.name" to "Linux") - } - - private val vars = vars.toMap(HashMap()) - - override fun get(name: String) = vars[name] - - operator fun set(name: String, value: String?) = vars.put(name, value) - operator fun contains(name: String) = name in vars -} From 963df39c4cfdf61d9d486000ef7a7bcd3fe403d1 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 04:12:58 +0100 Subject: [PATCH 29/35] Rename vars in docs to DEVELOCITY_ (#190) Leftover from #188. Part of #184. --- README.md | 8 ++++---- docs/AccessKeys.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 376f3e33..ce2255d8 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ api.buildsApi.getBuildsFlow(fromInstant = 0, query = "buildStartTime<-1d").forEa Set up environment variables and use the library from any notebook, script or project: -- [`GRADLE_ENTERPRISE_API_URL`][16]: the URL of your Develocity instance -- [`GRADLE_ENTERPRISE_API_TOKEN`][17]: an [access key][31] for the Develocity instance -- [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`][12] (optional, off by default): enables caching for some +- [`DEVELOCITY_API_URL`][16]: the URL of your Develocity instance +- [`DEVELOCITY_API_TOKEN`][17]: an [access key][31] for the Develocity instance +- [`DEVELOCITY_API_CACHE_ENABLED`][12] (optional, off by default): enables caching for some requests (see [caveats][13]) ### Setup snippets @@ -100,7 +100,7 @@ runBlocking { ### Caching HTTP caching is available, which can speed up queries significantly, but is -off by default. Enable by simply setting [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`][12] to `true`. See +off by default. Enable by simply setting [`DEVELOCITY_API_CACHE_ENABLED`][12] to `true`. See [`CacheConfig`][13] for caveats. ### Extensions diff --git a/docs/AccessKeys.md b/docs/AccessKeys.md index 61b80995..49c5c2c8 100644 --- a/docs/AccessKeys.md +++ b/docs/AccessKeys.md @@ -1,6 +1,6 @@ # Access key / API token -[All API requests require authentication][1]. Provide a valid access key of your Gradle Enterprise instance as the `GRADLE_ENTERPRISE_API_TOKEN` environment variable. +[All API requests require authentication][1]. Provide a valid access key of your Gradle Enterprise instance as the `DEVELOCITY_API_TOKEN` environment variable. ## How to get an access key @@ -8,7 +8,7 @@ 2. Go to "My settings" from the user menu in the top right-hand corner of the page 3. Go to "Access keys" from the sidebar 4. Click "Generate" on the right-hand side -5. Set key as the `GRADLE_ENTERPRISE_API_TOKEN` environment variable when using the library +5. Set key as the `DEVELOCITY_API_TOKEN` environment variable when using the library ## Migrating from macOS keychain support @@ -22,9 +22,9 @@ If you used the key from keychain and need a drop-in replacement: echo 'alias ge-api-token="security find-generic-password -w -a "$LOGNAME" -s gradle-enterprise-api-kotlin"' >> ~/.zshrc # Retrieve it to the environment variable before running the program -GRADLE_ENTERPRISE_API_TOKEN="$(ge-api-token)" ./my-script.main.kts -GRADLE_ENTERPRISE_API_TOKEN="$(ge-api-token)" jupyter lab -GRADLE_ENTERPRISE_API_TOKEN="$(ge-api-token)" idea my-project +DEVELOCITY_API_TOKEN="$(ge-api-token)" ./my-script.main.kts +DEVELOCITY_API_TOKEN="$(ge-api-token)" jupyter lab +DEVELOCITY_API_TOKEN="$(ge-api-token)" idea my-project ``` [1]: https://docs.gradle.com/enterprise/api-manual/#access_control From 3bb209c3690e8176e733a7932c0a397924c00003 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 20:10:01 +0100 Subject: [PATCH 30/35] Use JupyterIntegration API (#191) As a suggestion from https://github.com/Kotlin/kotlin-jupyter-libraries/pull/358#discussion_r1553925067, move the imports from the library JSON descriptor to the library itself. Also add basic tests on the integration. This change makes the descriptor more flexible. Currently, with imports in JSON, if a new import is ever added to the descriptor, ```diff { "description": "A library to use the Develocity API in Kotlin scripts or projects", "properties": [ - { "name": "version", "value": "2024.1.0" }, + { "name": "version", "value": "2024.2.0" }, ... "imports": [ "com.gabrielfeo.develocity.api.*", "com.gabrielfeo.develocity.api.model.*", "com.gabrielfeo.develocity.api.extension.*", + "com.gabrielfeo.develocity.api.new.*" ] } ``` using it with a past version, which didn't yet contain the new package, would break: ``` %use develocity-api-kotlin(2024.1.0) \_ now fails with "Unresolved reference" if using the latest descriptor ``` --- gradle.properties | 1 + library/build.gradle.kts | 8 ++++ .../DevelocityApiJupyterIntegrationTest.kt | 46 +++++++++++++++++++ .../DevelocityApiJupyterIntegration.kt | 13 ++++++ 4 files changed, 68 insertions(+) create mode 100644 library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt create mode 100644 library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt diff --git a/gradle.properties b/gradle.properties index ec51a764..1999e4f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,3 +8,4 @@ org.gradle.caching=true org.gradle.kotlin.dsl.skipMetadataVersionCheck=false systemProp.scan.capture-build-logging=false systemProp.scan.capture-test-logging=false +kotlin.jupyter.add.testkit=false diff --git a/library/build.gradle.kts b/library/build.gradle.kts index f7c8c26e..58dafbd0 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -12,6 +12,7 @@ plugins { `java-library` `maven-publish` signing + kotlin("jupyter.api") version "0.12.0-181" } val repoUrl = "https://github.com/gabrielfeo/develocity-api-kotlin" @@ -47,6 +48,12 @@ tasks.withType().configureEach { } } +tasks.processJupyterApiResources { + libraryProducers = listOf( + "com.gabrielfeo.develocity.api.internal.jupyter.DevelocityApiJupyterIntegration", + ) +} + tasks.named("javadocJar") { from(tasks.dokkaHtml) } @@ -80,6 +87,7 @@ dependencies { testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") integrationTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") integrationTestImplementation("com.google.guava:guava:33.1.0-jre") + integrationTestImplementation("org.jetbrains.kotlinx:kotlin-jupyter-test-kit:0.12.0-181") } fun libraryPom() = Action { diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt new file mode 100644 index 00000000..f4a6b170 --- /dev/null +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt @@ -0,0 +1,46 @@ +package com.gabrielfeo.develocity.api.internal.jupyter + +import com.google.common.reflect.ClassPath +import com.google.common.reflect.ClassPath.ClassInfo +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlinx.jupyter.api.Code +import org.jetbrains.kotlinx.jupyter.testkit.JupyterReplTestCase +import kotlin.reflect.KVisibility +import kotlin.test.Test + +@ExperimentalStdlibApi +class DevelocityApiJupyterIntegrationTest : JupyterReplTestCase() { + + @Test + fun `imports all extensions`() = assertSucceeds(""" + com.gabrielfeo.develocity.api.BuildsApi::getGradleAttributesFlow + com.gabrielfeo.develocity.api.BuildsApi::getBuildsFlow + + val attrs = emptyList() + "custom value name" in attrs + attrs["custom value name"] + """) + + @Test + fun `imports all public classes`() { + val classes = allPublicClassesRecursive("com.gabrielfeo.develocity.api") + val references = classes.joinToString("\n") { "${it.name}::class" } + println("Running code:\n$references") + assertSucceeds(references) + } + + @Suppress("SameParameterValue") + private fun allPublicClassesRecursive(packageName: String): List { + val cp = ClassPath.from(this::class.java.classLoader) + return cp.getTopLevelClassesRecursive(packageName) + .filter { "internal" !in it.packageName } + .filter { !it.name.endsWith("Kt") } + .filter { Class.forName(it.name).kotlin.visibility == KVisibility.PUBLIC } + } + + private fun assertSucceeds(@Language("kts") code: Code) { + code.lines().forEach { + execRendered(it) + } + } +} \ No newline at end of file diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt new file mode 100644 index 00000000..59aeeb4b --- /dev/null +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt @@ -0,0 +1,13 @@ +package com.gabrielfeo.develocity.api.internal.jupyter + +import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration + +@Suppress("unused") +class DevelocityApiJupyterIntegration : JupyterIntegration() { + + override fun Builder.onLoaded() { + import("com.gabrielfeo.develocity.api.*") + import("com.gabrielfeo.develocity.api.model.*") + import("com.gabrielfeo.develocity.api.extension.*") + } +} From fb8229c7f7efc7de992583d7fdff23752575ded6 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 20:21:43 +0100 Subject: [PATCH 31/35] Fix vars in README and use statement (#192) Mistakes from #177 and #190. Part of #184. --- examples/example-notebooks/MostFrequentBuilds.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example-notebooks/MostFrequentBuilds.ipynb b/examples/example-notebooks/MostFrequentBuilds.ipynb index ed82a46d..6eff5c98 100644 --- a/examples/example-notebooks/MostFrequentBuilds.ipynb +++ b/examples/example-notebooks/MostFrequentBuilds.ipynb @@ -63,7 +63,7 @@ "outputs": [], "source": [ "%useLatestDescriptors\n", - "%use develocitytlin(version=2023.4.0)\n", + "%use develocity-api-kotlin(version=2023.4.0)\n", "%use coroutines(v=1.7.1)\n", "\n", "val api = DevelocityApi.newInstance()" From 12fdb1f7114b4ab279c6dbbd171201c867f83cd9 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 21:55:49 +0100 Subject: [PATCH 32/35] Bump openapi-generator from 7.3.0 to 7.4.0 (#193) Bump but keep current API by setting `enumPropertyNaming`. New default in 7.4.0 is to always match the spec, but it uses snake_case and hurts Kotlin conventions. --- build-logic/build.gradle.kts | 2 +- .../com/gabrielfeo/develocity-api-code-generation.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 5678b73c..89dc744c 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -18,6 +18,6 @@ gradlePlugin { dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") - implementation("org.openapitools:openapi-generator-gradle-plugin:7.3.0") + implementation("org.openapitools:openapi-generator-gradle-plugin:7.4.0") "functionalTestImplementation"(project) } diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts index c29d4585..a0e744ea 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts @@ -51,6 +51,7 @@ openApiGenerate { invokerPackage.set("com.gabrielfeo.develocity.api.internal") additionalProperties.put("library", "jvm-retrofit2") additionalProperties.put("useCoroutines", true) + additionalProperties.put("enumPropertyNaming", "camelCase") cleanupOutput.set(true) } From 61e28221db09f6f419cb9f1886919c173e088e2b Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Fri, 5 Apr 2024 22:57:22 +0100 Subject: [PATCH 33/35] Rename in last docs and replace version for 2024.1.0 (#194) Part of #184 --- README.md | 17 ++++++++--------- docs/AccessKeys.md | 16 +++++++++------- .../example-notebooks/MostFrequentBuilds.ipynb | 7 +++---- examples/example-project/build.gradle.kts | 2 +- .../example-scripts/example-script.main.kts | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ce2255d8..4e18d325 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Develocity API Kotlin -[![Maven Central](https://img.shields.io/badge/Maven%20Central-2023.4.0-blue)][14] -[![Javadoc](https://img.shields.io/badge/Javadoc-2023.4.0-orange)][7] +[![Maven Central](https://img.shields.io/badge/Maven%20Central-2024.1.0-blue)][14] +[![Javadoc](https://img.shields.io/badge/Javadoc-2024.1.0-orange)][7] + +(formerly `gradle-enterprise-api-kotlin`) A Kotlin library to access the [Develocity API][1], easy to use from: @@ -16,7 +18,7 @@ api.buildsApi.getBuildsFlow(fromInstant = 0, query = "buildStartTime<-1d").forEa } ``` - The library takes care of caching under the hood (opt-in) and provides some convenience extensions. +The library takes care of caching under the hood (opt-in) and provides some convenience extensions. ## Setup @@ -29,15 +31,12 @@ Set up environment variables and use the library from any notebook, script or pr ### Setup snippets -ℹ️ The library is now published to Maven Central under `com.gabrielfeo`. Maven Central is -recommended over JitPack. -
      Add to a Jupyter notebook ``` %useLatestDescriptors -%use develocity-api-kotlin(version=2023.4.0) +%use develocity-api-kotlin(version=2024.1.0) ```
      @@ -46,7 +45,7 @@ recommended over JitPack. Add to a Kotlin script ```kotlin -@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2023.4.0") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2024.1.0") ``` @@ -56,7 +55,7 @@ recommended over JitPack. ```kotlin dependencies { - implementation("com.gabrielfeo:develocity-api-kotlin:2023.4.0") + implementation("com.gabrielfeo:develocity-api-kotlin:2024.1.0") } ``` diff --git a/docs/AccessKeys.md b/docs/AccessKeys.md index 49c5c2c8..b02562c7 100644 --- a/docs/AccessKeys.md +++ b/docs/AccessKeys.md @@ -1,10 +1,11 @@ # Access key / API token -[All API requests require authentication][1]. Provide a valid access key of your Gradle Enterprise instance as the `DEVELOCITY_API_TOKEN` environment variable. +[All API requests require authentication][1]. Provide a valid access key of your Develocity instance +as the `DEVELOCITY_API_TOKEN` environment variable. ## How to get an access key -1. Sign in to Gradle Enterprise (with a user that has “Export build data” permission) +1. Sign in to Develocity (with a user that has “Export build data” permission) 2. Go to "My settings" from the user menu in the top right-hand corner of the page 3. Go to "Access keys" from the sidebar 4. Click "Generate" on the right-hand side @@ -13,18 +14,19 @@ ## Migrating from macOS keychain support This library used to support storing the key in the macOS keychain as `gradle-enterprise-api-kotlin`. -This feature was deprecated. You may use the method of your choice (secret managers, password manager CLIs, etc.) to store and retrieve the key to an environment. +This feature was deprecated in 2023.4.0, then removed in 2024.1.0. You may use the method of your choice +(secret managers, password manager CLIs, etc.) to store and retrieve the key to an environment. If you used the key from keychain and need a drop-in replacement: ``` # Create an alias in your shell to fetch the key from keychain -echo 'alias ge-api-token="security find-generic-password -w -a "$LOGNAME" -s gradle-enterprise-api-kotlin"' >> ~/.zshrc +echo 'alias dat="security find-generic-password -w -a "$LOGNAME" -s gradle-enterprise-api-kotlin"' >> ~/.zshrc # Retrieve it to the environment variable before running the program -DEVELOCITY_API_TOKEN="$(ge-api-token)" ./my-script.main.kts -DEVELOCITY_API_TOKEN="$(ge-api-token)" jupyter lab -DEVELOCITY_API_TOKEN="$(ge-api-token)" idea my-project +DEVELOCITY_API_TOKEN="$(dat)" ./my-script.main.kts +DEVELOCITY_API_TOKEN="$(dat)" jupyter lab +DEVELOCITY_API_TOKEN="$(dat)" idea my-project ``` [1]: https://docs.gradle.com/enterprise/api-manual/#access_control diff --git a/examples/example-notebooks/MostFrequentBuilds.ipynb b/examples/example-notebooks/MostFrequentBuilds.ipynb index 6eff5c98..721e2139 100644 --- a/examples/example-notebooks/MostFrequentBuilds.ipynb +++ b/examples/example-notebooks/MostFrequentBuilds.ipynb @@ -35,7 +35,7 @@ "Add libraries to use, via line magics. `%use` is a [line magic](https://github.com/Kotlin/kotlin-jupyter#line-magics) of the Kotlin kernel that can do much more than adding the library. To illustrate, this setup can be replaced with a single line magic.\n", "\n", "```kotlin\n", - "@file:DependsOn(\"com.gabrielfeo:develocity-api-kotlin:2023.4.0\")\n", + "@file:DependsOn(\"com.gabrielfeo:develocity-api-kotlin:2024.1.0\")\n", "\n", "import com.gabrielfeo.develocity.api.*\n", "import com.gabrielfeo.develocity.api.model.*\n", @@ -45,8 +45,7 @@ "is the same as:\n", "\n", "```\n", - "%use develocity-api-kotlin(version=2023.4.0)\n", - "\n", + "%use develocity-api-kotlin(version=2024.1.0)\n", "```" ] }, @@ -63,7 +62,7 @@ "outputs": [], "source": [ "%useLatestDescriptors\n", - "%use develocity-api-kotlin(version=2023.4.0)\n", + "%use develocity-api-kotlin(version=2024.1.0)\n", "%use coroutines(v=1.7.1)\n", "\n", "val api = DevelocityApi.newInstance()" diff --git a/examples/example-project/build.gradle.kts b/examples/example-project/build.gradle.kts index 68c8c4d3..7c7daa35 100644 --- a/examples/example-project/build.gradle.kts +++ b/examples/example-project/build.gradle.kts @@ -15,5 +15,5 @@ java { } dependencies { - implementation("com.gabrielfeo:develocity-api-kotlin:2023.4.0") + implementation("com.gabrielfeo:develocity-api-kotlin:2024.1.0") } diff --git a/examples/example-scripts/example-script.main.kts b/examples/example-scripts/example-script.main.kts index 88e1b39b..341c9634 100644 --- a/examples/example-scripts/example-script.main.kts +++ b/examples/example-scripts/example-script.main.kts @@ -17,7 +17,7 @@ * Run this with at least 1GB of heap to accomodate the fetched data: JAVA_OPTS=-Xmx1g */ -@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2023.4.0") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2024.1.0") import com.gabrielfeo.develocity.api.* import com.gabrielfeo.develocity.api.model.* From 20a64fd51e6b58af47bd7403d23397fceee37690 Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Sat, 6 Apr 2024 21:03:49 +0100 Subject: [PATCH 34/35] Fix javadoc and relocation POM publishing (#199) Fix dokka source root. Javadoc generation [started failing](https://github.com/gabrielfeo/develocity-api-kotlin/actions/runs/8576587721) after plugin refactoring. Some ordering change (probably) caused Dokka to no longer detect the source root and just output "Nothing to document". Add repo, developer info to relocation POM (#184) and sign it. Relocation POM failed publishing of 2024.1.0 due to failing these checks. --- library/build.gradle.kts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 58dafbd0..ff84edbc 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -27,7 +27,8 @@ java { } tasks.withType().configureEach { - dokkaSourceSets.all { + dokkaSourceSets.register("main") { + sourceRoot("src/main/kotlin") sourceLink { localDirectory.set(file("src/main/kotlin")) remoteUrl.set(URL("$repoUrl/blob/$version/src/main/kotlin")) @@ -90,7 +91,7 @@ dependencies { integrationTestImplementation("org.jetbrains.kotlinx:kotlin-jupyter-test-kit:0.12.0-181") } -fun libraryPom() = Action { +val libraryPom = Action { name.set("Develocity API Kotlin") description.set("A library to use the Develocity API in Kotlin") url.set(repoUrl) @@ -121,18 +122,18 @@ publishing { create("develocityApiKotlin") { artifactId = "develocity-api-kotlin" from(components["java"]) - pom(libraryPom()) + pom(libraryPom) } // For occasional maven local publishing create("unsignedDevelocityApiKotlin") { artifactId = "develocity-api-kotlin" from(components["java"]) - pom(libraryPom()) + pom(libraryPom) } create("relocation") { + artifactId = "gradle-enterprise-api-kotlin" pom { - groupId = project.group.toString() - artifactId = "gradle-enterprise-api-kotlin" + libraryPom(this) distributionManagement { relocation { groupId = project.group.toString() @@ -164,7 +165,10 @@ publishing { fun isCI() = System.getenv("CI").toBoolean() signing { - sign(publishing.publications["develocityApiKotlin"]) + sign( + publishing.publications["develocityApiKotlin"], + publishing.publications["relocation"], + ) if (isCI()) { useInMemoryPgpKeys( project.properties["signing.secretKey"] as String?, From 1605270fd1f35224c886f4ed5197f9808d5409ae Mon Sep 17 00:00:00 2001 From: Gabriel Feo Date: Sat, 6 Apr 2024 22:09:49 +0100 Subject: [PATCH 35/35] Fix javadoc missing generated API (#200) Move Dokka configuration to the `jvm-library` plugin, which is applied before `test-suites`, and stop passing a fixed source root for Dokka so that generated API source set is included. Also fix some configuration avoidance smells. When refactoring build logic into plugins, Dokka stopped recognizing the correct source set. #199 fixed this by passing a specific source root to Dokka, but this caused the resulting javadoc to not include the generated API. Bisecting shows that moving the test suite setup (456f7696be3861ae5f6c53f9d05e00824915761e), making test suites be set up before Dokka, was when the issue started. --- build-logic/build.gradle.kts | 1 + .../gabrielfeo/kotlin-jvm-library.gradle.kts | 43 +++++++++++++ .../com/gabrielfeo/test-suites.gradle.kts | 2 +- build.gradle.kts | 3 - gradle.properties | 1 + library/build.gradle.kts | 62 ++++--------------- 6 files changed, 58 insertions(+), 54 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 89dc744c..3119e294 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -18,6 +18,7 @@ gradlePlugin { dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.9.20") implementation("org.openapitools:openapi-generator-gradle-plugin:7.4.0") "functionalTestImplementation"(project) } diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts index d3e9f37e..10a28d29 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts @@ -1,6 +1,49 @@ package com.gabrielfeo +import org.jetbrains.dokka.DokkaConfiguration.Visibility.PUBLIC +import org.jetbrains.dokka.gradle.DokkaTask +import java.net.URL + plugins { id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.dokka") `java-library` } + +val repoUrl: Provider = providers.gradleProperty("repo.url") + +java { + withSourcesJar() + withJavadocJar() + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + vendor.set(JvmVendorSpec.AZUL) + } +} + +tasks.withType().configureEach { + dokkaSourceSets.all { + sourceRoot("src/main/kotlin") + sourceLink { + localDirectory.set(file("src/main/kotlin")) + remoteUrl.set(repoUrl.map { URL("$it/blob/$version/src/main/kotlin") }) + remoteLineSuffix.set("#L") + } + jdkVersion.set(11) + suppressGeneratedFiles.set(false) + documentedVisibilities.set(setOf(PUBLIC)) + perPackageOption { + matchingRegex.set(""".*\.internal.*""") + suppress.set(true) + } + externalDocumentationLink("https://kotlinlang.org/api/kotlinx.coroutines/") + externalDocumentationLink("https://square.github.io/okhttp/4.x/okhttp/") + externalDocumentationLink("https://square.github.io/retrofit/2.x/retrofit/") + externalDocumentationLink("https://square.github.io/moshi/1.x/moshi/") + externalDocumentationLink("https://square.github.io/moshi/1.x/moshi-kotlin/") + } +} + +tasks.named("javadocJar") { + from(tasks.dokkaHtml) +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts index 34fb8ef0..7b0c5f70 100644 --- a/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts +++ b/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts @@ -34,7 +34,7 @@ kotlin { } // TODO Unapply test-fixtures and delete the source set, since we're not publishing it? -components.getByName("java").apply { +components.named("java") { withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } } diff --git a/build.gradle.kts b/build.gradle.kts index f70e0f5f..e69de29b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +0,0 @@ -plugins { - id("org.jetbrains.dokka") version "1.9.20" apply false -} diff --git a/gradle.properties b/gradle.properties index 1999e4f3..9d16b8c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,7 @@ group=com.gabrielfeo artifact=develocity-api-kotlin version=2024.1.0 develocity.version=2024.1 +repo.url=https://github.com/gabrielfeo/develocity-api-kotlin org.gradle.jvmargs=-Xmx5g org.gradle.caching=true # Becomes default in Gradle 9.0 diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ff84edbc..250e7a34 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -8,57 +8,18 @@ plugins { id("com.gabrielfeo.kotlin-jvm-library") id("com.gabrielfeo.develocity-api-code-generation") id("com.gabrielfeo.test-suites") - id("org.jetbrains.dokka") `java-library` `maven-publish` signing kotlin("jupyter.api") version "0.12.0-181" } -val repoUrl = "https://github.com/gabrielfeo/develocity-api-kotlin" - -java { - withSourcesJar() - withJavadocJar() - toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - vendor.set(JvmVendorSpec.AZUL) - } -} - -tasks.withType().configureEach { - dokkaSourceSets.register("main") { - sourceRoot("src/main/kotlin") - sourceLink { - localDirectory.set(file("src/main/kotlin")) - remoteUrl.set(URL("$repoUrl/blob/$version/src/main/kotlin")) - remoteLineSuffix.set("#L") - } - jdkVersion.set(11) - suppressGeneratedFiles.set(false) - documentedVisibilities.set(setOf(PUBLIC)) - perPackageOption { - matchingRegex.set(""".*\.internal.*""") - suppress.set(true) - } - externalDocumentationLink("https://kotlinlang.org/api/kotlinx.coroutines/") - externalDocumentationLink("https://square.github.io/okhttp/4.x/okhttp/") - externalDocumentationLink("https://square.github.io/retrofit/2.x/retrofit/") - externalDocumentationLink("https://square.github.io/moshi/1.x/moshi/") - externalDocumentationLink("https://square.github.io/moshi/1.x/moshi-kotlin/") - } -} - tasks.processJupyterApiResources { libraryProducers = listOf( "com.gabrielfeo.develocity.api.internal.jupyter.DevelocityApiJupyterIntegration", ) } -tasks.named("javadocJar") { - from(tasks.dokkaHtml) -} - tasks.named("integrationTest") { environment("DEVELOCITY_API_LOG_LEVEL", "DEBUG") } @@ -94,6 +55,7 @@ dependencies { val libraryPom = Action { name.set("Develocity API Kotlin") description.set("A library to use the Develocity API in Kotlin") + val repoUrl = providers.gradleProperty("repo.url") url.set(repoUrl) licenses { license { @@ -110,27 +72,27 @@ val libraryPom = Action { } } scm { - val basicUrl = repoUrl.substringAfter("://") - connection.set("scm:git:git://$basicUrl.git") - developerConnection.set("scm:git:ssh://$basicUrl.git") - url.set("https://$basicUrl/") + val basicUrl = repoUrl.map { it.substringAfter("://") } + connection.set(basicUrl.map { "scm:git:git://$it.git" }) + developerConnection.set(basicUrl.map { "scm:git:ssh://$it.git" }) + url.set(basicUrl.map { "https://$it/" }) } } publishing { publications { - create("develocityApiKotlin") { + register("develocityApiKotlin") { artifactId = "develocity-api-kotlin" from(components["java"]) pom(libraryPom) } // For occasional maven local publishing - create("unsignedDevelocityApiKotlin") { + register("unsignedDevelocityApiKotlin") { artifactId = "develocity-api-kotlin" from(components["java"]) pom(libraryPom) } - create("relocation") { + register("relocation") { artifactId = "gradle-enterprise-api-kotlin" pom { libraryPom(this) @@ -165,10 +127,10 @@ publishing { fun isCI() = System.getenv("CI").toBoolean() signing { - sign( - publishing.publications["develocityApiKotlin"], - publishing.publications["relocation"], - ) + val signedPublications = publishing.publications.matching { + !it.name.contains("unsigned", ignoreCase = true) + } + sign(signedPublications) if (isCI()) { useInMemoryPgpKeys( project.properties["signing.secretKey"] as String?,