Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
shell: bash
run: ./gradlew build
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.TEST_PAT }}
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ This plugin is split into two: one for `settings` and the other for `project`. D
```shell
# settings.gradle

# to ensure that the repositories are resolved from settings.gradle
# (optional) ensure that the repositories are resolved from settings.gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
}
Expand Down Expand Up @@ -66,9 +66,10 @@ This plugin is split into two: one for `settings` and the other for `project`. D
val ghToken = ghCliAuth.token.get()
```

**NOTE**: When using both plugins, ensure that you **only** apply the plugin version to <u>settings</u> plugin block and not to the <u>project</u> plugin block, as it will lead to a conflict.
You also won't be able to obtain GitHub token from the `ghCliAuth` extension if you're setting `RepositoriesMode` as `FAIL_ON_PROJECT_REPOS`, as it is only _currently_ available in the `project` plugin.
This plugin is also **not** compatible with locally precompiled plugin scripts in your `settings.gradle.kts` file, due to the way Gradle handles plugin resolution during the initialization phase.
### Important Notes
- When using both plugins, ensure that you **only** apply the plugin version to <u>settings</u> plugin block and not to the <u>project</u> plugin block, as it will lead to a conflict.
- You won't be able to obtain GitHub token from the `ghCliAuth` extension if you're setting `RepositoriesMode` as `FAIL_ON_PROJECT_REPOS`, as it is only _currently_ available in the `project` plugin.
- By default, the settings plugin will configure default repositories for plugins and dependencies (google, mavenCentral, gradlePluginPortal). This is to ensure default repositories are always available.

### Configuration

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
org.gradle.configuration-cache=true
gradle.publish.version=1.0.5
gradle.publish.version=1.0.6
6 changes: 6 additions & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode

plugins {
`kotlin-dsl`
alias(libs.plugins.kotlin.jvm)
Expand All @@ -7,6 +9,10 @@ plugins {
group = "io.github.adelinosousa"
version = System.getenv("GRADLE_PUBLISH_VERSION") ?: project.findProperty("gradle.publish.version") ?: "1.0.1"

kotlin {
explicitApi = ExplicitApiMode.Strict
}

repositories {
mavenCentral()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class GhCliAuthProjectPluginFunctionalTest {
.withProjectDir(projectDir)
.withArguments("--stacktrace", "--info")
.withGradleVersion("8.14.2")
.buildAndFail()
.build()

// Verify the result
assertTrue(result.output.contains("GitHub CLI is not authenticated or does not have the required scopes"))
assertTrue(result.output.contains("Registering Maven GitHub repository for organization: test-org"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,81 @@ class GhCliAuthSettingsPluginFunctionalTest {
.withProjectDir(projectDir)
.withArguments("--stacktrace", "--info")
.withGradleVersion("8.14.2")
.buildAndFail()
.build()

// Verify the result
assertTrue(result.output.contains("GitHub CLI is not authenticated or does not have the required scopes"))
assertTrue(result.output.contains("Registering Maven GitHub repository for organization: test-org"))
}

@Test fun `correct number of repositories are configured for pluginManagement`() {
settingsFile.writeText("""
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

plugins {
id('io.github.adelinosousa.gradle.plugins.settings.gh-cli-auth')
}

dependencyResolutionManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
""".trimIndent())
buildFile.writeText("")
propertiesFile.writeText("gh.cli.auth.github.org=test-org")

val result = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withProjectDir(projectDir)
.withArguments("--stacktrace", "--info")
.withGradleVersion("8.14.2")
.build()

// Verify the result
assertTrue(result.output.contains("Adding Google repository"))
}

@Test fun `correct number of repositories are configured for dependencyResolutionManagement`() {
settingsFile.writeText("""
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}

plugins {
id('io.github.adelinosousa.gradle.plugins.settings.gh-cli-auth')
}

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
""".trimIndent())
buildFile.writeText("")
propertiesFile.writeText("gh.cli.auth.github.org=test-org")

val result = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withProjectDir(projectDir)
.withArguments("--stacktrace", "--info")
.withGradleVersion("8.14.2")
.build()

// Verify the result
assertTrue(result.output.contains("Adding Gradle Plugin Portal repository"))
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.adelinosousa.gradle.plugins

object Config {
const val GITHUB_ORG: String = "gh.cli.auth.github.org"
const val ENV_PROPERTY_NAME: String = "gh.cli.auth.env.name"
internal object Config {
internal const val GITHUB_ORG: String = "gh.cli.auth.github.org"
internal const val ENV_PROPERTY_NAME: String = "gh.cli.auth.env.name"
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package io.github.adelinosousa.gradle.plugins

object Environment {
fun getEnv(name: String): String? {
internal object Environment {
internal fun getEnv(name: String): String? {
return System.getenv(name)
}

fun getEnvCredentials(gitEnvTokenName: String) : RepositoryCredentials? {
internal fun getEnvCredentials(gitEnvTokenName: String) : RepositoryCredentials? {
val token = getEnv(gitEnvTokenName)
if (!token.isNullOrEmpty()) {
return RepositoryCredentials("", token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package io.github.adelinosousa.gradle.plugins
import org.gradle.api.GradleException
import org.gradle.api.provider.Provider

object GhCliAuth {
val requiredScopes: Set<String> = setOf("read:packages", "repo", "read:org")
internal object GhCliAuth {
val requiredScopes: Set<String> = setOf("read:packages", "read:org")

fun checkGhCliAuthenticatedWithCorrectScopes(authStatusProvider: Provider<String>): Provider<String> {
internal fun checkGhCliAuthenticatedWithCorrectScopes(authStatusProvider: Provider<String>): Provider<String> {
return authStatusProvider.map { output ->
if (output.contains("Token:")) {
val scopesLine = output.lines().firstOrNull { it.contains("Token scopes:") }
Expand All @@ -21,7 +21,7 @@ object GhCliAuth {
}
}

fun getGitHubCredentials(output: String): RepositoryCredentials {
internal fun getGitHubCredentials(output: String): RepositoryCredentials {
try {
val userLine = output.lines().firstOrNull { it.contains("Logged in to github.com account") }
val tokenLine = output.lines().firstOrNull { it.contains("Token:") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package io.github.adelinosousa.gradle.plugins

import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property

@Suppress("unused")
class GhCliAuthProjectPlugin : Plugin<Project> {
internal class GhCliAuthProjectPlugin : Plugin<Project> {
private companion object {
private val logger = Logging.getLogger(GhCliAuthSettingsPlugin::class.java)
}

override fun apply(project: Project) {
val extension = project.extensions.create("ghCliAuth", GhCliAuthExtension::class.java)

Expand All @@ -18,8 +22,8 @@ class GhCliAuthProjectPlugin : Plugin<Project> {

val repoCredentials = Environment.getEnvCredentials(gitEnvTokenName) ?: getGhCliCredentials(project)
if (repoCredentials.isValid()) {
logger.info("Registering Maven GitHub repository for organization: $githubOrg")
// Set the extension token to share with other tasks
println("Registering Maven GitHub repository for organization: $githubOrg")
extension.token.set(repoCredentials.token)
project.repositories.maven {
name = "GitHubPackages"
Expand All @@ -45,6 +49,6 @@ class GhCliAuthProjectPlugin : Plugin<Project> {
}
}

interface GhCliAuthExtension {
val token: Property<String?>
public interface GhCliAuthExtension {
public val token: Property<String?>
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package io.github.adelinosousa.gradle.plugins

import org.gradle.api.Plugin
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.initialization.Settings
import org.gradle.api.logging.Logging
import java.net.URI

@Suppress("unused")
class GhCliAuthSettingsPlugin : Plugin<Settings> {
internal class GhCliAuthSettingsPlugin : Plugin<Settings> {
private companion object {
private val logger = Logging.getLogger(GhCliAuthSettingsPlugin::class.java)
}

override fun apply(settings: Settings) {
val githubOrg = getGradleProperty(settings, Config.GITHUB_ORG)
val gitEnvTokenName = getGradleProperty(settings, Config.ENV_PROPERTY_NAME) ?: "GITHUB_TOKEN"
Expand All @@ -17,17 +22,8 @@ class GhCliAuthSettingsPlugin : Plugin<Settings> {

val repoCredentials = Environment.getEnvCredentials(gitEnvTokenName) ?: getGhCliCredentials(settings)
if (repoCredentials.isValid()) {
val githubMavenRepository = { repo: MavenArtifactRepository ->
repo.name = "GitHubPackages"
repo.url = URI("https://maven.pkg.github.com/$githubOrg/*")
repo.credentials {
this.username = repoCredentials.username
this.password = repoCredentials.token
}
}
println("Registering Maven GitHub repository for organization: $githubOrg")
settings.pluginManagement.repositories.maven(githubMavenRepository)
settings.dependencyResolutionManagement.repositories.maven(githubMavenRepository)
settings.pluginManagement.repositories.addRepositoriesWithDefaults(githubOrg, repoCredentials)
settings.dependencyResolutionManagement.repositories.addRepositoriesWithDefaults(githubOrg, repoCredentials)
} else {
throw IllegalStateException("Token not found in environment variable '${gitEnvTokenName}' or 'gh' CLI. Unable to configure GitHub Packages repository.")
}
Expand All @@ -42,4 +38,31 @@ class GhCliAuthSettingsPlugin : Plugin<Settings> {
private fun getGradleProperty(settings: Settings, propertyName: String): String? {
return settings.providers.gradleProperty(propertyName).orNull
}

private fun RepositoryHandler.addRepositoriesWithDefaults(githubOrg: String, repoCredentials: RepositoryCredentials) {
if (this.findByName("MavenRepo") == null) {
logger.info("Adding Maven Central repository")
this.mavenCentral()
}

if (this.findByName("Google") == null) {
logger.info("Adding Google repository")
this.google()
}

if (this.findByName("Gradle Central Plugin Repository") == null) {
logger.info("Adding Gradle Plugin Portal repository")
this.gradlePluginPortal()
}

logger.info("Registering Maven GitHub repository for organization: $githubOrg")
this.maven {
name = "GitHubPackages"
url = URI("https://maven.pkg.github.com/$githubOrg/*")
credentials {
this.username = repoCredentials.username
this.password = repoCredentials.token
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject

abstract class GitHubCLIProcess : ValueSource<String, ValueSourceParameters.None> {
internal abstract class GitHubCLIProcess : ValueSource<String, ValueSourceParameters.None> {

@get:Inject
abstract val execOperations: ExecOperations
internal abstract val execOperations: ExecOperations

override fun obtain(): String? {
return try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package io.github.adelinosousa.gradle.plugins

class RepositoryCredentials(
val username: String?,
val token: String?
internal class RepositoryCredentials(
internal val username: String?,
internal val token: String?
) {
fun isValid(): Boolean {
internal fun isValid(): Boolean {
return username != null && !token.isNullOrEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class GhCliAuthSettingsPluginTest {
every { settings.providers.gradleProperty(Config.GITHUB_ORG) } returns Providers.of(testOrg)
every { settings.providers.gradleProperty(Config.ENV_PROPERTY_NAME) } returns Providers.of(customEnvName)
every { Environment.getEnvCredentials(customEnvName) } returns RepositoryCredentials(username = "", token = testToken)
every { settings.pluginManagement.repositories.findByName(any()) } returns null
every { settings.dependencyResolutionManagement.repositories.findByName(any()) } returns null
every { settings.pluginManagement.repositories.maven(capture(pluginMavenAction)) } returns mockk()
every { settings.dependencyResolutionManagement.repositories.maven(capture(dependencyResolutionMavenAction)) } returns mockk()
every { mockRepo.credentials(any<Action<in PasswordCredentials>>()) } answers {
Expand All @@ -69,6 +71,8 @@ class GhCliAuthSettingsPluginTest {
every { GhCliAuth.getGitHubCredentials(any()) } returns RepositoryCredentials(testUsername, testToken)
every { settings.providers.gradleProperty(Config.GITHUB_ORG) } returns Providers.of(testOrg)
every { settings.providers.gradleProperty(Config.ENV_PROPERTY_NAME) } returns Providers.of("")
every { settings.pluginManagement.repositories.findByName(any()) } returns null
every { settings.dependencyResolutionManagement.repositories.findByName(any()) } returns null
every { settings.pluginManagement.repositories.maven(capture(pluginMavenAction)) } returns mockk()
every { settings.dependencyResolutionManagement.repositories.maven(capture(dependencyResolutionMavenAction)) } returns mockk()
every { mockRepo.credentials(any<Action<in PasswordCredentials>>()) } answers {
Expand Down