Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/.idea
.DS_Store
build/
*/.kotlin/*
/captures
.externalNativeBuild

Expand Down
14 changes: 0 additions & 14 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import com.osacky.doctor.DoctorExtension
import org.gradle.api.tasks.testing.logging.TestLogEvent

// Upgrade transitive dependencies in plugin classpath
Expand All @@ -18,19 +17,6 @@ buildscript {
plugins {
alias(libs.plugins.kgp)
alias(libs.plugins.versions)
id("com.osacky.doctor")
}

configure<DoctorExtension> {
disallowMultipleDaemons.set(false)
GCWarningThreshold.set(0.01f)
enableTestCaching.set(false)
downloadSpeedWarningThreshold.set(2.0f)
daggerThreshold.set(100)
javaHome {
ensureJavaHomeMatches.set(true)
ensureJavaHomeIsSet.set(true)
}
}

tasks.withType(Test::class.java).configureEach {
Expand Down
2 changes: 1 addition & 1 deletion doctor-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ gradlePlugin {
displayName = "Doctor Plugin"
description = "The right prescription for your gradle build."
tags.addAll(listOf("doctor", "android"))
implementationClass = "com.osacky.doctor.DoctorPlugin"
implementationClass = "com.osacky.doctor.DoctorSettingsPlugin"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import org.junit.Test
class KotlinDaemonFallbackIntegrationTest : AbstractIntegrationTest() {
@Test
fun testDisallowKotlinCompileDaemonFallback() {
writeKotlinBuildGradle(true)
writeSettingsFile()
writeKotlinBuildGradle()
writeSettingsFile(true)
testProjectRoot.newFolder("src/main/java/foo")
testProjectRoot.newFolder("src/test/java/foo")
testProjectRoot.writeFileToName(
Expand Down Expand Up @@ -41,8 +41,8 @@ class KotlinDaemonFallbackIntegrationTest : AbstractIntegrationTest() {

@Test
fun allowKotlinCompileFallback() {
writeKotlinBuildGradle(false)
writeSettingsFile()
writeKotlinBuildGradle()
writeSettingsFile(false)
testProjectRoot.newFolder("src/main/java/foo")
testProjectRoot.writeFileToName(
"src/main/java/foo/Foo.kt",
Expand All @@ -62,7 +62,7 @@ class KotlinDaemonFallbackIntegrationTest : AbstractIntegrationTest() {
assertThat(result.output).contains("SUCCESS")
}

private fun writeSettingsFile() {
private fun writeSettingsFile(allowDaemonFallback: Boolean) {
testProjectRoot.writeFileToName(
"settings.gradle",
"""
Expand All @@ -72,6 +72,17 @@ class KotlinDaemonFallbackIntegrationTest : AbstractIntegrationTest() {
gradlePluginPortal()
}
}

plugins {
id "com.osacky.doctor"
}

doctor {
warnIfKotlinCompileDaemonFallback = $allowDaemonFallback
javaHome {
ensureJavaHomeMatches = false
}
}
""".trimIndent(),
)
}
Expand All @@ -80,22 +91,15 @@ class KotlinDaemonFallbackIntegrationTest : AbstractIntegrationTest() {
createRunner()
.withArguments("check", "-Dkotlin.daemon.jvm.options=invalid_jvm_argument_to_fail_process_startup")

private fun writeKotlinBuildGradle(allowDaemonFallback: Boolean) {
private fun writeKotlinBuildGradle() {
testProjectRoot.writeBuildGradle(
"""
plugins {
id "com.osacky.doctor"
id "org.jetbrains.kotlin.jvm" version "1.6.10"
}
repositories {
mavenCentral()
}
doctor {
warnIfKotlinCompileDaemonFallback = $allowDaemonFallback
javaHome {
ensureJavaHomeMatches = false
}
}
""".trimIndent(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class TestIntegrationTest {

@Test
fun testIgnoreOnEmptyDirectories() {
testProjectRoot.writeBuildGradle(
testProjectRoot.writeBuildGradle("")
val fixtureName = "java-fixture"
testProjectRoot.writeSettingsGradle(
"""
|plugins {
| id "com.osacky.doctor"
Expand All @@ -25,10 +27,9 @@ class TestIntegrationTest {
| failOnEmptyDirectories = true
| warnWhenNotUsingParallelGC = false
|}
""".trimMargin("|"),
|include '$fixtureName'
""".trimMargin("|")
)
val fixtureName = "java-fixture"
testProjectRoot.newFile("settings.gradle").writeText("include '$fixtureName'")
testProjectRoot.setupFixture(fixtureName)
testProjectRoot.newFolder("java-fixture", "src", "main", "java", "com", "foo")

Expand All @@ -46,7 +47,9 @@ class TestIntegrationTest {

@Test
fun testDirectoriesIgnoredIn6dot8() {
testProjectRoot.writeBuildGradle(
testProjectRoot.writeBuildGradle("")
val fixtureName = "java-fixture"
testProjectRoot.writeSettingsGradle(
"""
|plugins {
| id "com.osacky.doctor"
Expand All @@ -59,10 +62,9 @@ class TestIntegrationTest {
| failOnEmptyDirectories = true
| warnWhenNotUsingParallelGC = false
|}
""".trimMargin("|"),
|include '$fixtureName'
""".trimMargin("|")
)
val fixtureName = "java-fixture"
testProjectRoot.newFile("settings.gradle").writeText("include '$fixtureName'")
testProjectRoot.setupFixture(fixtureName)
testProjectRoot.newFolder("java-fixture", "src", "main", "java", "com", "foo")

Expand Down Expand Up @@ -135,18 +137,8 @@ class TestIntegrationTest {
testProjectRoot.writeBuildGradle(
"""
plugins {
id "com.osacky.doctor"
id 'java-library'
}
doctor {
disallowMultipleDaemons = false
javaHome {
ensureJavaHomeMatches = false
}
warnWhenNotUsingParallelGC = false
disallowCleanTaskDependencies = $disallowCleanTaskDependencies
}

tasks.register('foo') {
doFirst {
println 'foo'
Expand All @@ -158,5 +150,21 @@ class TestIntegrationTest {
}
""".trimIndent(),
)

testProjectRoot.writeSettingsGradle(
"""
plugins {
id "com.osacky.doctor"
}
doctor {
disallowMultipleDaemons = false
javaHome {
ensureJavaHomeMatches = false
}
warnWhenNotUsingParallelGC = false
disallowCleanTaskDependencies = $disallowCleanTaskDependencies
}
""".trimIndent()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.osacky.doctor

import org.gradle.api.Project
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters

abstract class AppProjectCollectorBuildService : BuildService<BuildServiceParameters.None> {
private val appProjects = mutableSetOf<Project>()

fun addProject(project: Project) {
appProjects.add(project)
}

fun getProjects(): Set<Project> {
return appProjects
}
}

fun Project.getAppProjectCollectorBuildService(): AppProjectCollectorBuildService {
return project.gradle.sharedServices.registerIfAbsent(
"appProjectCollector",
AppProjectCollectorBuildService::class.java,
{}
).get()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.osacky.doctor

import com.osacky.doctor.internal.farthestEmptyParent
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.testing.Test
import org.gradle.util.GradleVersion

class DoctorChildModulePlugin : Plugin<Project> {
override fun apply(target: Project) {
val extension = target.getDoctorExtension()
target.tasks.withType(SourceTask::class.java).configureEach {
if (!gradleIgnoresEmptyDirectories() && extension.failOnEmptyDirectories.get()) {
// Fail build if empty directories are found. These cause build cache misses and should be ignored by Gradle.
doFirst {
source.visit {
if (file.isDirectory && file.listFiles().isEmpty()) {
val farthestEmptyParent = file.farthestEmptyParent()
throw IllegalStateException(
"Empty src dir(s) found. This causes build cache misses. Run the following command to fix it.\n" +
"rmdir ${farthestEmptyParent.absolutePath}",
)
}
}
}
}
}
// Ensure we are not caching any test tasks. Tests may not declare all inputs properly or depend on things like the date and caching them can lead to dangerous false positives.
target.tasks.withType(Test::class.java).configureEach {
if (!extension.enableTestCaching.get()) {
outputs.upToDateWhen { false }
}
}
target.plugins.whenPluginAdded plugin@{
if (this.javaClass.name == "com.android.build.gradle.AppPlugin") {
target.getAppProjectCollectorBuildService().addProject(target)
}
}
}

/**
* Gradle now ignores empty directories starting in 6.8
* https://docs.gradle.org/6.8-rc-1/release-notes.html#performance-improvements
**/
private fun gradleIgnoresEmptyDirectories(): Boolean = GradleVersion.current() >= GradleVersion.version("6.8-rc-1")

}
13 changes: 13 additions & 0 deletions doctor-plugin/src/main/java/com/osacky/doctor/DoctorExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package com.osacky.doctor

import com.osacky.doctor.AppleRosettaTranslationCheckMode.ERROR
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.kotlin.dsl.newInstance
import org.gradle.kotlin.dsl.property
import javax.inject.Inject
Expand Down Expand Up @@ -100,6 +103,16 @@ open class DoctorExtension(
fun javaHome(action: Action<JavaHomeHandler>) {
action.execute(javaHomeHandler)
}

companion object {
const val EXTRAS_KEY = "_doctorExtension_settings"
}
}

fun Project.getDoctorExtension(): DoctorExtension {
val defaults = extensions.getByType(ExtraPropertiesExtension::class.java).get(DoctorExtension.EXTRAS_KEY)
as? DoctorExtension ?: throw GradleException("Settings extension type mismatch")
return defaults
}

abstract class JavaHomeHandler
Expand Down
46 changes: 3 additions & 43 deletions doctor-plugin/src/main/java/com/osacky/doctor/DoctorPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.osacky.doctor.internal.PillBoxPrinter
import com.osacky.doctor.internal.SystemClock
import com.osacky.doctor.internal.UnixDaemonChecker
import com.osacky.doctor.internal.UnsupportedOsDaemonChecker
import com.osacky.doctor.internal.farthestEmptyParent
import com.osacky.doctor.internal.shouldUseCoCaClasses
import org.gradle.api.Action
import org.gradle.api.GradleException
Expand All @@ -20,13 +19,10 @@ import org.gradle.api.Project
import org.gradle.api.internal.GradleInternal
import org.gradle.api.invocation.Gradle
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.testing.Test
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.internal.jvm.Jvm
import org.gradle.internal.operations.BuildOperationListener
import org.gradle.internal.operations.BuildOperationListenerManager
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.support.serviceOf
import org.gradle.kotlin.dsl.withType
import org.gradle.launcher.daemon.server.scaninfo.DaemonScanInfo
Expand All @@ -39,7 +35,7 @@ class DoctorPlugin : Plugin<Project> {
ensureMinimumSupportedGradleVersion()
ensureAppliedInProjectRoot(target)

val extension = target.extensions.create<DoctorExtension>("doctor")
val extension = target.getDoctorExtension()

val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
val cliCommandExecutor = CliCommandExecutor(target)
Expand Down Expand Up @@ -99,41 +95,11 @@ class DoctorPlugin : Plugin<Project> {

tagFreshDaemon(target, buildScanApi)

val appPluginProjects = mutableSetOf<Project>()

ensureNoCleanTaskDependenciesIfNeeded(target, extension, pillBoxPrinter)

target.subprojects project@{
tasks.withType(SourceTask::class.java).configureEach {
if (!gradleIgnoresEmptyDirectories() && extension.failOnEmptyDirectories.get()) {
// Fail build if empty directories are found. These cause build cache misses and should be ignored by Gradle.
doFirst {
source.visit {
if (file.isDirectory && file.listFiles().isEmpty()) {
val farthestEmptyParent = file.farthestEmptyParent()
throw IllegalStateException(
"Empty src dir(s) found. This causes build cache misses. Run the following command to fix it.\n" +
"rmdir ${farthestEmptyParent.absolutePath}",
)
}
}
}
}
}
// Ensure we are not caching any test tasks. Tests may not declare all inputs properly or depend on things like the date and caching them can lead to dangerous false positives.
tasks.withType(Test::class.java).configureEach {
if (!extension.enableTestCaching.get()) {
outputs.upToDateWhen { false }
}
}
plugins.whenPluginAdded plugin@{
if (this.javaClass.name == "com.android.build.gradle.AppPlugin") {
appPluginProjects.add(this@project)
}
}
}

target.gradle.taskGraph.whenReady {
val service = target.getAppProjectCollectorBuildService()
val appPluginProjects = service.getProjects()
// If there is only one application plugin, we don't need to check that we're assembling all the applications.
if (appPluginProjects.size <= 1 || extension.allowBuildingAllAndroidAppsSimultaneously.get()) {
return@whenReady
Expand Down Expand Up @@ -274,12 +240,6 @@ class DoctorPlugin : Plugin<Project> {
ops
}

/**
* Gradle now ignores empty directories starting in 6.8
* https://docs.gradle.org/6.8-rc-1/release-notes.html#performance-improvements
**/
private fun gradleIgnoresEmptyDirectories(): Boolean = GradleVersion.current() >= GradleVersion.version("6.8-rc-1")

private val Gradle.buildOperationListenerManager get() = (this as GradleInternal).services[BuildOperationListenerManager::class.java]

class TheActionThing(
Expand Down
Loading