Repository: runningcode/gradle-doctor (kotlin)

Issue:
Allow switching Rosetta check to "warn only" mode or turning it off
The Rosetta check is amazing and it actually helped us realize we were using non-Apple silicon targeted JDK versions, which is great!

That said, it would be great to allow switching this check to "warn only" mode. I was just trying to help an engineer who had failing builds no matter what they tried, and we had to turn off Gradle Doctor completely to unblock their builds.

Pull request description:
Fixes #286.

Patch:
diff --git a/docs/configuration.md b/docs/configuration.md
index 13717573..7b3bad9a 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -69,6 +69,10 @@
        * https://youtrack.jetbrains.com/issue/KT-48843
        */
       warnIfKotlinCompileDaemonFallback = true
+      /**
+       * The mode in which the Apple Rosetta translation check is executed.
+       */
+      appleRosettaTranslationCheckMode = AppleRosettaTranslationCheckMode.ERROR
 
 
       /** Configuration properties relating to JAVA_HOME */
@@ -160,6 +164,10 @@
         * https://youtrack.jetbrains.com/issue/KT-48843
         */
        warnIfKotlinCompileDaemonFallback.set(true)
+       /**
+        * The mode in which the Apple Rosetta translation check is executed.
+        */
+       appleRosettaTranslationCheckMode.set(AppleRosettaTranslationCheckMode.ERROR)
 
       /** Configuration properties relating to JAVA_HOME */
       javaHome {
diff --git a/docs/index.md b/docs/index.md
index 392c0501..76382a70 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -35,6 +35,7 @@ Watch this Virtual Android Makers 2019 entitled [The Secrets of the Build Scan P
 * [Warnings for negative avoidance saving tasks.](slower-from-cache)
 * [Build scan tags to easily find and categorize builds with warnings](scan-tags).
 * Warn when not using Parallel GC in Java 9+.
+* Warn or fail build when using a Rosetta-translated JDK on a Mac computer with Apple silicon. 
 
 ### Configurable Warnings
 * Warn when build spends more than 10% of the time garbage collecting.
diff --git a/doctor-plugin/src/main/java/com/osacky/doctor/AppleRosettaTranslationCheck.kt b/doctor-plugin/src/main/java/com/osacky/doctor/AppleRosettaTranslationCheck.kt
index be7c311a..1b32b248 100644
--- a/doctor-plugin/src/main/java/com/osacky/doctor/AppleRosettaTranslationCheck.kt
+++ b/doctor-plugin/src/main/java/com/osacky/doctor/AppleRosettaTranslationCheck.kt
@@ -1,8 +1,12 @@
 package com.osacky.doctor
 
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.DISABLED
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.ERROR
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.WARN
 import com.osacky.doctor.internal.CliCommandExecutor
 import com.osacky.doctor.internal.PillBoxPrinter
 import org.gradle.api.GradleException
+import org.gradle.api.provider.Property
 import org.gradle.nativeplatform.platform.OperatingSystem
 import org.jetbrains.kotlin.com.google.common.annotations.VisibleForTesting
 
@@ -10,6 +14,7 @@ class AppleRosettaTranslationCheck(
     private val os: OperatingSystem,
     private val cliCommandExecutor: CliCommandExecutor,
     private val pillBoxPrinter: PillBoxPrinter,
+    private val appleRosettaTranslationCheckMode: Property<AppleRosettaTranslationCheckMode>,
 ) : BuildStartFinishListener {
     @VisibleForTesting
     val isTranslatedCheckCommand = arrayOf("/bin/bash", "-c", "sysctl sysctl.proc_translated")
@@ -22,13 +27,18 @@ class AppleRosettaTranslationCheck(
         "Attempt to run Gradle under Apple Silicon Rosetta translation. Make sure Gradle uses JDK for aarch64 architecture."
 
     override fun onStart() {
-        if (!os.isMacOsX) return
+        val appleRosettaTranslationCheckMode = appleRosettaTranslationCheckMode.get()
+        if (appleRosettaTranslationCheckMode == DISABLED || !os.isMacOsX) return
         val output =
             runCatching {
                 cliCommandExecutor.execute(isTranslatedCheckCommand, ignoreExitValue = true)
             }.getOrNull()
         if (output == translatedWithRosetta) {
-            throw GradleException(pillBoxPrinter.createPill(errorMessage))
+            if (appleRosettaTranslationCheckMode == ERROR) {
+                throw GradleException(pillBoxPrinter.createPill(errorMessage))
+            } else if (appleRosettaTranslationCheckMode == WARN) {
+                pillBoxPrinter.writePrescription(listOf(errorMessage))
+            }
         }
     }
 
diff --git a/doctor-plugin/src/main/java/com/osacky/doctor/DoctorExtension.kt b/doctor-plugin/src/main/java/com/osacky/doctor/DoctorExtension.kt
index 906510e5..5598a043 100644
--- a/doctor-plugin/src/main/java/com/osacky/doctor/DoctorExtension.kt
+++ b/doctor-plugin/src/main/java/com/osacky/doctor/DoctorExtension.kt
@@ -1,5 +1,6 @@
 package com.osacky.doctor
 
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.ERROR
 import org.gradle.api.Action
 import org.gradle.api.model.ObjectFactory
 import org.gradle.kotlin.dsl.newInstance
@@ -86,6 +87,11 @@ open class DoctorExtension(objects: ObjectFactory) {
      */
     val warnIfKotlinCompileDaemonFallback = objects.property<Boolean>().convention(true)
 
+    /**
+     * The mode in which the Apple Rosetta translation check is executed. Default is "ERROR".
+     */
+    val appleRosettaTranslationCheckMode = objects.property<AppleRosettaTranslationCheckMode>().convention(ERROR)
+
     /**
      * Configures `JAVA_HOME`-specific behavior.
      */
@@ -119,3 +125,23 @@ abstract class JavaHomeHandler
         @Suppress("CAST_NEVER_SUCCEEDS") // Cast is for overload ambiguity
         val extraMessage = objects.property<String>().convention(null as? String)
     }
+
+/**
+ * Defines different execution modes for the Apple Rosetta translation check.
+ */
+enum class AppleRosettaTranslationCheckMode {
+    /**
+     * The check will not be executed.
+     */
+    DISABLED,
+
+    /**
+     * The check will be executed on macOS machines, and a warning will be printed to the console if the check fails.
+     */
+    WARN,
+
+    /**
+     * The check will be executed on macOS machines, and the build will fail if the check fails.
+     */
+    ERROR,
+}
diff --git a/doctor-plugin/src/main/java/com/osacky/doctor/DoctorPlugin.kt b/doctor-plugin/src/main/java/com/osacky/doctor/DoctorPlugin.kt
index b2da2030..de1ed63d 100644
--- a/doctor-plugin/src/main/java/com/osacky/doctor/DoctorPlugin.kt
+++ b/doctor-plugin/src/main/java/com/osacky/doctor/DoctorPlugin.kt
@@ -47,7 +47,13 @@ class DoctorPlugin : Plugin<Project> {
         val pillBoxPrinter = PillBoxPrinter(target.logger)
         val daemonChecker = BuildDaemonChecker(extension, createDaemonChecker(os, cliCommandExecutor), pillBoxPrinter)
         val javaHomeCheck = JavaHomeCheck(extension, pillBoxPrinter)
-        val appleRosettaTranslationCheck = AppleRosettaTranslationCheck(os, cliCommandExecutor, pillBoxPrinter)
+        val appleRosettaTranslationCheck =
+            AppleRosettaTranslationCheck(
+                os,
+                cliCommandExecutor,
+                pillBoxPrinter,
+                extension.appleRosettaTranslationCheckMode,
+            )
         val garbagePrinter = GarbagePrinter(clock, DirtyBeanCollector(), extension)
         val buildOperations = getOperationEvents(target, extension)
         val javaAnnotationTime = JavaAnnotationTime(buildOperations, extension)


Test patch:
diff --git a/doctor-plugin/src/test/java/com/osacky/doctor/AppleRosettaTranslationCheckTest.kt b/doctor-plugin/src/test/java/com/osacky/doctor/AppleRosettaTranslationCheckTest.kt
index 02ee894f..e38ea941 100644
--- a/doctor-plugin/src/test/java/com/osacky/doctor/AppleRosettaTranslationCheckTest.kt
+++ b/doctor-plugin/src/test/java/com/osacky/doctor/AppleRosettaTranslationCheckTest.kt
@@ -5,9 +5,13 @@ import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.verify
 import com.nhaarman.mockitokotlin2.verifyZeroInteractions
 import com.nhaarman.mockitokotlin2.whenever
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.DISABLED
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.ERROR
+import com.osacky.doctor.AppleRosettaTranslationCheckMode.WARN
 import com.osacky.doctor.internal.CliCommandExecutor
 import com.osacky.doctor.internal.PillBoxPrinter
 import org.gradle.api.GradleException
+import org.gradle.api.provider.Property
 import org.gradle.nativeplatform.platform.OperatingSystem
 import org.junit.Test
 
@@ -15,11 +19,20 @@ class AppleRosettaTranslationCheckTest {
     private val operatingSystem = mock<OperatingSystem>()
     private val cliCommandExecutor = mock<CliCommandExecutor>()
     private val pillBoxPrinter = mock<PillBoxPrinter>()
+    private val appleRosettaTranslationCheckMode = mock<Property<AppleRosettaTranslationCheckMode>>()
 
-    private val underTest = AppleRosettaTranslationCheck(operatingSystem, cliCommandExecutor, pillBoxPrinter)
+    private val underTest =
+        AppleRosettaTranslationCheck(
+            operatingSystem,
+            cliCommandExecutor,
+            pillBoxPrinter,
+            appleRosettaTranslationCheckMode,
+        )
 
     @Test
     fun testOperatingSystemCheck() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(ERROR)
+
         underTest.onStart()
 
         verify(operatingSystem).isMacOsX
@@ -27,6 +40,7 @@ class AppleRosettaTranslationCheckTest {
 
     @Test
     fun testGradleRunsNotOnAppleNoException() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(ERROR)
         whenever(operatingSystem.isMacOsX).doReturn(false)
 
         underTest.onStart()
@@ -37,6 +51,7 @@ class AppleRosettaTranslationCheckTest {
 
     @Test(expected = GradleException::class)
     fun testGradleRunsUnderAppleRosettaExceptionThrownWithMessage() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(ERROR)
         whenever(operatingSystem.isMacOsX).doReturn(true)
         whenever(cliCommandExecutor.execute(underTest.isTranslatedCheckCommand, true))
             .thenReturn(underTest.translatedWithRosetta)
@@ -48,6 +63,7 @@ class AppleRosettaTranslationCheckTest {
 
     @Test
     fun testGradleRunsNativelyOnAppleNoException() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(ERROR)
         whenever(operatingSystem.isMacOsX).doReturn(true)
         whenever(cliCommandExecutor.execute(underTest.isTranslatedCheckCommand, true))
             .thenReturn("sysctl.proc_translated: 0")
@@ -59,6 +75,7 @@ class AppleRosettaTranslationCheckTest {
 
     @Test
     fun testErrorDuringTranslationDeterminationNoException() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(ERROR)
         whenever(operatingSystem.isMacOsX).doReturn(true)
         whenever(cliCommandExecutor.execute(underTest.isTranslatedCheckCommand, true))
             .thenReturn("sysctl.proc_translated: -1")
@@ -67,4 +84,29 @@ class AppleRosettaTranslationCheckTest {
 
         verifyZeroInteractions(pillBoxPrinter)
     }
+
+    @Test
+    fun testNoExceptionWhenCheckIsDisabled() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(DISABLED)
+        whenever(operatingSystem.isMacOsX).doReturn(true)
+        whenever(cliCommandExecutor.execute(underTest.isTranslatedCheckCommand, true))
+            .thenReturn(underTest.translatedWithRosetta)
+
+        underTest.onStart()
+
+        verifyZeroInteractions(cliCommandExecutor)
+        verifyZeroInteractions(pillBoxPrinter)
+    }
+
+    @Test
+    fun testNoExceptionButWarningPrintedWhenCheckIsInWarnMode() {
+        whenever(appleRosettaTranslationCheckMode.get()).doReturn(WARN)
+        whenever(operatingSystem.isMacOsX).doReturn(true)
+        whenever(cliCommandExecutor.execute(underTest.isTranslatedCheckCommand, true))
+            .thenReturn(underTest.translatedWithRosetta)
+
+        underTest.onStart()
+
+        verify(pillBoxPrinter).writePrescription(listOf(underTest.errorMessage))
+    }
 }