Skip to content

Commit

Permalink
Extract Skippy to separate, standalone JVM project + CLI (#717)
Browse files Browse the repository at this point in the history
This allows us to invoke skippy completely independently from Gradle
(IFF we have a serialized graph and list of affected android test
projects cached/pre-computed).

This works by extracting skippy's core to a separate skippy artifact, as
well as a shared common utils artifact for shared APIs.

This implements a CLI along the way, based on the existing gradle task.
It works mostly the same way, but just via Clikt instead.

Opportunistically, I also split out the graph generation and android
test project path aggregation to separate ancestor tasks, which would
allow us to more granularly compute these before-hand and keep them
cached.

<!--
  ⬆ Put your description above this! ⬆

  Please be descriptive and detailed.
  
Please read our [Contributing
Guidelines](https://github.com/tinyspeck/slack-gradle-plugin/blob/main/.github/CONTRIBUTING.md)
and [Code of Conduct](https://slackhq.github.io/code-of-conduct).

Don't worry about deleting this, it's not visible in the PR!
-->
  • Loading branch information
ZacSweers authored Jan 12, 2024
1 parent 3ca7efc commit 97b368d
Show file tree
Hide file tree
Showing 33 changed files with 695 additions and 213 deletions.
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ agpAlpha = { module = "com.android.tools.build:gradle", version.ref = "agpAlpha"
autoService-annotations = "com.google.auto.service:auto-service-annotations:1.1.1"
autoService-ksp = "dev.zacsweers.autoservice:auto-service-ksp:1.1.0"
bugsnag = "com.bugsnag:bugsnag:3.7.1"
clikt = "com.github.ajalt.clikt:clikt:4.2.2"
commonsText = "org.apache.commons:commons-text:1.11.0"
coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "coroutines" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ include(
":agp-handlers:agp-handler-82",
":agp-handlers:agp-handler-83",
":agp-handlers:agp-handler-api",
":skippy",
":sgp-common",
":skate-plugin",
":skate-plugin:artifactory-authenticator",
":slack-plugin",
Expand Down
26 changes: 26 additions & 0 deletions sgp-common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 Slack Technologies, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
kotlin("jvm")
alias(libs.plugins.mavenPublish)
}

dependencies {
api(platform(libs.coroutines.bom))
api(libs.okio)

implementation(libs.coroutines.core)
}
3 changes: 3 additions & 0 deletions sgp-common/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=sgp-common
POM_NAME=SGP (Common)
POM_DESCRIPTION=SGP (Common)
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.util
package com.slack.sgp.common

import slack.gradle.avoidance.ComputeAffectedProjectsTask

internal fun <T, R> Collection<T>.mapToSet(transform: (T) -> R): Set<R> {
public fun <T, R> Collection<T>.mapToSet(transform: (T) -> R): Set<R> {
return mapTo(mutableSetOf(), transform)
}

internal fun <T, R> Collection<T>.flatMapToSet(transform: (T) -> Iterable<R>): Set<R> {
public fun <T, R> Collection<T>.flatMapToSet(transform: (T) -> Iterable<R>): Set<R> {
return flatMapTo(mutableSetOf(), transform)
}

/**
* Flips a map. In the context of [ComputeAffectedProjectsTask], we use this to flip a map of
* Flips a map. In the context of `ComputeAffectedProjectsTask`, we use this to flip a map of
* projects to their dependencies to a map of projects to the projects that depend on them. We use
* this to find all affected projects given a seed of changed projects.
*
Expand All @@ -39,7 +37,7 @@ internal fun <T, R> Collection<T>.flatMapToSet(transform: (T) -> Iterable<R>): S
* {b:[a], c:[a], d:[b, c]}
* ```
*/
internal fun Map<String, Set<String>>.flip(): Map<String, Set<String>> {
public fun Map<String, Set<String>>.flip(): Map<String, Set<String>> {
val flipped = mutableMapOf<String, MutableSet<String>>()
for ((project, dependenciesSet) in this) {
for (dependency in dependenciesSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.util
package com.slack.sgp.common

import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
Expand All @@ -25,7 +25,7 @@ import kotlinx.coroutines.sync.Semaphore
/**
* Applies a given [mapper] function in parallel over this [Iterable] with a given [parallelism].
*/
internal suspend inline fun <A, B> Iterable<A>.parallelMap(
public suspend inline fun <A, B> Iterable<A>.parallelMap(
parallelism: Int,
start: CoroutineStart = CoroutineStart.DEFAULT,
crossinline mapper: suspend (A) -> B
Expand All @@ -48,7 +48,7 @@ internal suspend inline fun <A, B> Iterable<A>.parallelMap(
* Applies a given [mapper] function in parallel over this [Iterable] with a given [parallelism],
* filtering out null [B]s.
*/
internal suspend inline fun <A, B> Iterable<A>.parallelMapNotNull(
public suspend inline fun <A, B> Iterable<A>.parallelMapNotNull(
parallelism: Int,
start: CoroutineStart = CoroutineStart.DEFAULT,
crossinline mapper: suspend (A) -> B?
Expand All @@ -72,7 +72,7 @@ internal suspend inline fun <A, B> Iterable<A>.parallelMapNotNull(
* Iterates this [Iterable] with a given [parallelism], passing the value for each emission to the
* given [action].
*/
internal suspend inline fun <A> Iterable<A>.parallelForEach(
public suspend inline fun <A> Iterable<A>.parallelForEach(
parallelism: Int,
start: CoroutineStart = CoroutineStart.DEFAULT,
crossinline action: suspend (A) -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.util
package com.slack.sgp.common

import okio.FileSystem
import okio.Path

internal fun Path.prepareForGradleOutput(fs: FileSystem) = apply {
public fun Path.prepareForGradleOutput(fs: FileSystem): Path = apply {
if (fs.exists(this)) fs.delete(this)
parent?.let(fs::createDirectories)
}

internal fun Path.readLines(fs: FileSystem): List<String> {
public fun Path.readLines(fs: FileSystem): List<String> {
return fs.read(this) {
val lines = mutableListOf<String>()
while (!exhausted()) {
Expand All @@ -33,7 +33,7 @@ internal fun Path.readLines(fs: FileSystem): List<String> {
}
}

internal fun Path.writeLines(lines: Iterable<String>, fs: FileSystem) {
public fun Path.writeLines(lines: Iterable<String>, fs: FileSystem) {
fs.write(this) {
lines.forEach { line ->
writeUtf8(line)
Expand All @@ -42,6 +42,6 @@ internal fun Path.writeLines(lines: Iterable<String>, fs: FileSystem) {
}
}

internal fun Path.writeText(text: String, fs: FileSystem) {
public fun Path.writeText(text: String, fs: FileSystem) {
fs.write(this) { writeUtf8(text) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.util

import org.gradle.api.logging.Logger
package com.slack.sgp.common

/** A simple logging abstraction for use in SGP. */
internal interface SgpLogger {
fun debug(message: String)
public interface SgpLogger {
public fun debug(message: String)

fun info(message: String)
public fun info(message: String)

fun lifecycle(message: String)
public fun lifecycle(message: String)

fun warn(message: String)
public fun warn(message: String)

fun warn(message: String, error: Throwable)
public fun warn(message: String, error: Throwable)

fun error(message: String)
public fun error(message: String)

fun error(message: String, error: Throwable)
public fun error(message: String, error: Throwable)

companion object {
fun gradle(logger: Logger): SgpLogger = GradleSgpLogger(logger)
public companion object {

fun noop(): SgpLogger = NoopSgpLogger
public fun noop(): SgpLogger = NoopSgpLogger

fun system(): SgpLogger = SystemSgpLogger
public fun system(): SgpLogger = SystemSgpLogger

fun prefix(prefix: String, delegate: SgpLogger): SgpLogger = PrefixSgpLogger(prefix, delegate)
public fun prefix(prefix: String, delegate: SgpLogger): SgpLogger =
PrefixSgpLogger(prefix, delegate)
}
}

Expand Down Expand Up @@ -129,34 +127,3 @@ private object SystemSgpLogger : SgpLogger {
error.printStackTrace(System.err)
}
}

/** A Gradle [Logger]-based [SgpLogger]. */
private class GradleSgpLogger(private val delegate: Logger) : SgpLogger {
override fun debug(message: String) {
delegate.debug(message)
}

override fun info(message: String) {
delegate.info(message)
}

override fun lifecycle(message: String) {
delegate.lifecycle(message)
}

override fun warn(message: String) {
delegate.warn(message)
}

override fun warn(message: String, error: Throwable) {
delegate.warn(message, error)
}

override fun error(message: String) {
delegate.error(message)
}

override fun error(message: String, error: Throwable) {
delegate.error(message, error)
}
}
38 changes: 38 additions & 0 deletions skippy/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 Slack Technologies, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
kotlin("jvm")
alias(libs.plugins.moshix)
alias(libs.plugins.mavenPublish)
}

dependencies {
api(platform(libs.coroutines.bom))

implementation(libs.clikt)
implementation(libs.coroutines.core)
implementation(libs.gradlePlugins.graphAssert) { because("To use in Gradle graphing APIs.") }
implementation(libs.kotlinCliUtil)
implementation(libs.moshi)
implementation(libs.okio)
implementation(projects.sgpCommon)

testImplementation(platform(libs.coroutines.bom))
testImplementation(libs.coroutines.test)
testImplementation(libs.junit)
testImplementation(libs.okio.fakefilesystem)
testImplementation(libs.truth)
}
3 changes: 3 additions & 0 deletions skippy/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=sgp-skippy
POM_NAME=SGP (Skippy)
POM_DESCRIPTION=SGP (Skippy)
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.avoidance
package com.slack.skippy

import com.slack.sgp.common.SgpLogger
import com.slack.skippy.SkippyConfig.Companion.GLOBAL_TOOL
import kotlin.time.measureTimedValue
import okio.FileSystem
import okio.Path
import slack.gradle.avoidance.SkippyConfig.Companion.GLOBAL_TOOL
import slack.gradle.util.SgpLogger

/**
* This is a program compute the set of Gradle projects that are affected by a set of changed files
Expand Down Expand Up @@ -79,7 +79,7 @@ import slack.gradle.util.SgpLogger
* @property debug Debugging flag. If enabled, extra diagnostics and logging is performed.
* @property logger A logger to use for logging.
*/
internal class AffectedProjectsComputer(
public class AffectedProjectsComputer(
private val rootDirPath: Path,
private val dependencyMetadata: DependencyMetadata,
private val changedFilePaths: List<Path>,
Expand All @@ -90,7 +90,7 @@ internal class AffectedProjectsComputer(
private val fileSystem: FileSystem = FileSystem.SYSTEM,
private val logger: SgpLogger = SgpLogger.noop(),
) {
fun compute(): AffectedProjectsResult? {
public fun compute(): AffectedProjectsResult? {
return logTimedValue("full computation of ${config.tool}") { computeImpl() }
}

Expand Down Expand Up @@ -285,7 +285,7 @@ internal class AffectedProjectsComputer(
}
}

companion object {
internal companion object {
/** Returns a filtered list of [filePaths] that match the given [includePatterns]. */
fun filterIncludes(filePaths: List<Path>, includePatterns: Collection<String>) =
filePaths.filter { includePatterns.any { pattern -> pattern.toPathMatcher().matches(it) } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.avoidance
package com.slack.skippy

import java.util.Collections.unmodifiableSet

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slack.gradle.avoidance
package com.slack.skippy

import java.util.SortedSet

internal data class AffectedProjectsResult(
public data class AffectedProjectsResult(
val affectedProjects: SortedSet<String>,
val focusProjects: SortedSet<String>,
val affectedAndroidTestProjects: SortedSet<String>,
Expand Down
Loading

0 comments on commit 97b368d

Please sign in to comment.