[GH] Migrate playground-plugin to Kotlin

## Proposed Changes

Migrate playground-plugin to Kotlin

## Testing

Test: ./gradlew tasks in activity/

This is an imported pull request from https://github.com/androidx/androidx/pull/286.

Resolves #286
Github-Pr-Head-Sha: 87e4facd13dd102b132e995b2355bce7f52796fe
GitOrigin-RevId: 69b809796452428398b5cd3317a7bbb823173dbd
Change-Id: I0e07b77a71a24b80b1af5f5b30419bcbcbd88d0c
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ac87caf..6bcb125 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -173,3 +173,6 @@
 wireRuntime = { module = "com.squareup.wire:wire-runtime", version.ref = "wire" }
 xpp3 = { module = "xpp3:xpp3", version = "1.1.4c" }
 xmlpull = { module = "xmlpull:xmlpull", version = "1.1.3.1" }
+
+[plugins]
+kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
diff --git a/playground-common/playground-plugin/build.gradle b/playground-common/playground-plugin/build.gradle
index 9fff29a..7b521cf 100644
--- a/playground-common/playground-plugin/build.gradle
+++ b/playground-common/playground-plugin/build.gradle
@@ -13,8 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- plugins {
+
+plugins {
     id "groovy-gradle-plugin"
+    alias(libs.plugins.kotlinJvm)
 }
 
 repositories {
@@ -26,6 +28,15 @@
     implementation "com.gradle:common-custom-user-data-gradle-plugin:1.5"
 }
 
+// Needed to be able to use Groovy code from Kotlin
+// https://docs.gradle.org/6.1-rc-1/release-notes.html#compilation-order
+tasks.named('compileGroovy') {
+    classpath = sourceSets.main.compileClasspath
+}
+tasks.named('compileKotlin') {
+    classpath += files(sourceSets.main.groovy.classesDirectory)
+}
+
 gradlePlugin {
     plugins {
         playgroundConventions {
diff --git a/playground-common/playground-plugin/settings.gradle b/playground-common/playground-plugin/settings.gradle
index 9c18267..cb8499c 100644
--- a/playground-common/playground-plugin/settings.gradle
+++ b/playground-common/playground-plugin/settings.gradle
@@ -33,3 +33,12 @@
         }
     }
 }
+
+enableFeaturePreview("VERSION_CATALOGS")
+dependencyResolutionManagement {
+    versionCatalogs {
+        libs {
+            from(files("../../gradle/libs.versions.toml"))
+        }
+    }
+}
\ No newline at end of file
diff --git a/playground-common/playground-plugin/src/main/groovy/androidx/playground/GradleEnterpriseConventionsPlugin.groovy b/playground-common/playground-plugin/src/main/groovy/androidx/playground/GradleEnterpriseConventionsPlugin.groovy
deleted file mode 100644
index 566904b..0000000
--- a/playground-common/playground-plugin/src/main/groovy/androidx/playground/GradleEnterpriseConventionsPlugin.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * 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
- *
- *      http://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.
- */
- package androidx.playground
-
-import org.gradle.api.Plugin
-import org.gradle.api.initialization.Settings
-import org.gradle.caching.http.HttpBuildCache
-
-class GradleEnterpriseConventionsPlugin implements Plugin<Settings> {
-    void apply(Settings settings) {
-        settings.apply(plugin: "com.gradle.enterprise")
-        settings.apply(plugin: "com.gradle.common-custom-user-data-gradle-plugin")
-
-        // Github Actions always sets a "CI" environment variable
-        var isCI = System.getenv("CI") != null
-
-        settings.gradleEnterprise {
-            server = "https://ge.androidx.dev"
-
-            buildScan {
-                publishAlways()
-                publishIfAuthenticated()
-
-                uploadInBackground = !isCI
-
-                capture {
-                    taskInputFiles = true
-                }
-                obfuscation {
-                    hostname { host -> "unset" }
-                    ipAddresses { addresses -> addresses.collect { address -> "0.0.0.0"} }
-                }
-            }
-        }
-
-        settings.buildCache {
-            remote(HttpBuildCache) {
-                url = "https://ge.androidx.dev/cache/"
-                var buildCachePassword = System.getenv("GRADLE_BUILD_CACHE_PASSWORD")
-                if (isCI && buildCachePassword != null && !buildCachePassword.empty) {
-                    push = true
-                    credentials {
-                        username = "ci"
-                        password = buildCachePassword
-                    }
-                } else {
-                    push = false
-                }
-            }
-        }
-    }
-}
diff --git a/playground-common/playground-plugin/src/main/groovy/androidx/playground/GradleWorkaround.groovy b/playground-common/playground-plugin/src/main/groovy/androidx/playground/GradleWorkaround.groovy
new file mode 100644
index 0000000..d85061c
--- /dev/null
+++ b/playground-common/playground-plugin/src/main/groovy/androidx/playground/GradleWorkaround.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+package androidx.playground
+
+import com.gradle.scan.plugin.BuildScanDataObfuscation
+
+class GradleWorkaround {
+    /**
+     * This is needed because Gradle configuration caching fails to serialize these lambdas
+     * if they are written in Kotlin
+     * https://github.com/gradle/gradle/issues/19047
+     */
+    static void obfuscate(BuildScanDataObfuscation obfuscation) {
+        obfuscation.hostname {"unset" }
+        obfuscation.ipAddresses { addresses -> addresses.collect { address -> "0.0.0.0"} }
+    }
+}
\ No newline at end of file
diff --git a/playground-common/playground-plugin/src/main/groovy/androidx/playground/PlaygroundExtension.groovy b/playground-common/playground-plugin/src/main/groovy/androidx/playground/PlaygroundExtension.groovy
deleted file mode 100644
index ab9daac..0000000
--- a/playground-common/playground-plugin/src/main/groovy/androidx/playground/PlaygroundExtension.groovy
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * 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
- *
- *      http://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.
- */
- package androidx.playground
-
-import javax.inject.Inject
-import org.gradle.api.GradleException
-import org.gradle.api.model.ObjectFactory
-import org.gradle.api.initialization.Settings
-
-class PlaygroundExtension {
-    private final ObjectFactory objectFactory
-    private final Settings settings
-
-    private File supportRootDir
-
-    @Inject
-    PlaygroundExtension(Settings settings, ObjectFactory objectFactory) {
-        this.settings = settings
-        this.objectFactory = objectFactory
-    }
-
-    /**
-     * Includes the project if it does not already exist.
-     * This is invoked from `includeProject` to ensure all parent projects are included. If they are
-     * not, gradle will use the root project path to set the projectDir, which might conflict in
-     * playground. Instead, this method checks if another project in that path exists and if so,
-     * changes the project dir to avoid the conflict.
-     * see b/197253160 for details.
-     */
-    private includeFakeParentProjectIfNotExists(String name, File projectDir) {
-        if (name.isEmpty()) return
-        if (settings.findProject(name)) {
-            return
-        }
-        if (settings.findProject(projectDir) != null) {
-            // Project directory conflicts with an existing project (possibly root). Move it
-            // to another directory to avoid the conflict.
-            projectDir = new File(projectDir.getParentFile(), ".ignore-${projectDir.name}")
-        }
-        includeProjectAt(name, projectDir)
-        // Set it to a gradle file that does not exist.
-        // We must always include projects starting with root, if we are including nested projects.
-        settings.project(name).buildFileName = "ignored.gradle"
-    }
-
-    private includeProjectAt(String name, File projectDir) {
-        if (settings.findProject(name) != null) {
-            throw new GradleException("Cannot include project twice: $name is already included.")
-        }
-        def parentPath = name.substring(0, name.lastIndexOf(":"))
-        def parentDir = projectDir.getParentFile()
-        // Make sure parent is created first. see: b/197253160 for details
-        includeFakeParentProjectIfNotExists(
-            parentPath,
-            parentDir
-        )
-        settings.include(name)
-        settings.project(name).projectDir = projectDir
-    }
-
-    /**
-     * Includes a project by name, with a path relative to the root of AndroidX.
-     */
-    def includeProject(String name, String filePath) {
-        if (supportRootDir == null) {
-            throw new GradleException("Must call setupPlayground() first.")
-        }
-        includeProjectAt(name, new File(supportRootDir, filePath))
-    }
-
-    /**
-    * Initializes the playground project to use public repositories as well as other internal projects
-    * that cannot be found in public repositories.
-    *
-    * @param settings The reference to the settings script
-    * @param relativePathToRoot The relative path of the project to the root AndroidX project
-    */
-    def setupPlayground(String relativePathToRoot) {
-        def projectDir = settings.rootProject.getProjectDir()
-        def supportRoot = new File(projectDir, relativePathToRoot).getCanonicalFile()
-        this.supportRootDir = supportRoot
-        def buildFile = new File(supportRoot, "playground-common/playground-build.gradle")
-        def relativePathToBuild = projectDir.toPath().relativize(buildFile.toPath()).toString()
-
-        Properties playgroundProperties = new Properties()
-        File propertiesFile = new File(supportRoot, "playground-common/playground.properties")
-        propertiesFile.withInputStream {
-            playgroundProperties.load(it)
-        }
-        settings.gradle.beforeProject { project ->
-            // load playground properties. These are not kept in the playground projects to prevent
-            // AndroidX build from reading them.
-            playgroundProperties.each {
-                project.ext[it.key] = it.value
-            }
-        }
-
-        settings.rootProject.buildFileName = relativePathToBuild
-        settings.enableFeaturePreview("VERSION_CATALOGS")
-
-        def catalogFiles = objectFactory.fileCollection().from("$supportRoot/gradle/libs.versions.toml")
-        settings.dependencyResolutionManagement {
-            versionCatalogs {
-                libs {
-                    from(catalogFiles)
-                }
-            }
-        }
-
-        includeProject(":lint-checks", "lint-checks")
-        includeProject(":lint-checks:integration-tests", "lint-checks/integration-tests")
-        includeProject(":fakeannotations", "fakeannotations")
-        includeProject(":internal-testutils-common", "testutils/testutils-common")
-        includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin")
-
-        // allow public repositories
-        System.setProperty("ALLOW_PUBLIC_REPOS", "true")
-
-        // specify out dir location
-        System.setProperty("CHECKOUT_ROOT", supportRoot.path)
-    }
-
-
-    /**
-    * A convenience method to include projects from the main AndroidX build using a filter.
-    *
-    * @param filter This filter will be called with the project name (project path in gradle).
-    *               If filter returns true, it will be included in the build.
-    */
-    def selectProjectsFromAndroidX(filter) {
-        if (supportRootDir == null) {
-            throw new RuntimeException("Must call setupPlayground() first.")
-        }
-
-        // Multiline matcher for anything of the form:
-        //  includeProject(name, path, ...)
-        // where '...' is anything except the ')' character.
-        def includeProjectPattern = ~/(?m)^[\n\r\s]*includeProject\("(?<name>[a-z0-9-:]*)",[\n\r\s]*"(?<path>[a-z0-9-\/]+)[^)]+\)$/
-        def supportSettingsFile = new File(supportRootDir, "settings.gradle")
-        def matcher = includeProjectPattern.matcher(supportSettingsFile.text)
-
-        while (matcher.find()) {
-            // check if is an include project line, if so, extract project gradle path and
-            // file system path and call the filter
-            def projectGradlePath = matcher.group("name")
-            def projectFilePath = matcher.group("path")
-            if (filter(projectGradlePath)) {
-                includeProject(projectGradlePath, projectFilePath)
-            }
-        }
-    }
-
-    /**
-    * Checks if a project is necessary for playground projects that involve compose.
-    */
-    def isNeededForComposePlayground(name) {
-        if (name == ":compose:lint:common") return true
-        if (name == ":compose:lint:internal-lint-checks") return true
-        if (name == ":compose:test-utils") return true
-        if (name == ":compose:lint:common-test") return true
-        if (name == ":test:screenshot:screenshot") return true
-        return false
-    }
-}
\ No newline at end of file
diff --git a/playground-common/playground-plugin/src/main/groovy/androidx/playground/PlaygroundPlugin.groovy b/playground-common/playground-plugin/src/main/groovy/androidx/playground/PlaygroundPlugin.groovy
deleted file mode 100644
index 4208b82..0000000
--- a/playground-common/playground-plugin/src/main/groovy/androidx/playground/PlaygroundPlugin.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * 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
- *
- *      http://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.
- */
- package androidx.playground
-
-import org.gradle.api.Plugin
-import org.gradle.api.initialization.Settings
-
-class PlaygroundPlugin implements Plugin<Settings> {
-    void apply(Settings settings) {
-        settings.apply(plugin: "playground-ge-conventions")
-
-        settings.extensions.create("playground", PlaygroundExtension, settings)
-
-        validateJvm(settings)
-    }
-
-    def validateJvm(Settings settings) {
-        // validate JVM version to print an understandable error if it is not set to the
-        // required value (11)
-        def jvmVersion = System.getProperty("java.vm.specification.version")
-        if (jvmVersion != "11") {
-            def guidance;
-            if (settings.gradle.startParameter.projectProperties.containsKey("android.injected.invoked.from.ide")) {
-                guidance = "Make sure to set the gradle JDK to JDK 11 in the project settings." +
-                        "(File -> Other Settings -> Default Project Structure)"
-            } else {
-                guidance = "Make sure your JAVA_HOME environment variable points to Java 11 JDK."
-            }
-            throw new IllegalStateException("""
-                    AndroidX build must be invoked with JDK 11.
-                    $guidance
-                    Current version: $jvmVersion
-                    Current JAVA HOME: ${System.getProperty("java.home")}""".stripIndent());
-        }
-
-    }
-}
diff --git a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleEnterpriseConventionsPlugin.kt b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleEnterpriseConventionsPlugin.kt
new file mode 100644
index 0000000..ef5a878
--- /dev/null
+++ b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/GradleEnterpriseConventionsPlugin.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+package androidx.playground
+
+import com.gradle.enterprise.gradleplugin.internal.extension.BuildScanExtensionWithHiddenFeatures
+import org.gradle.api.Plugin
+import org.gradle.api.initialization.Settings
+import org.gradle.caching.http.HttpBuildCache
+import org.gradle.kotlin.dsl.gradleEnterprise
+import java.net.URI
+
+class GradleEnterpriseConventionsPlugin : Plugin<Settings> {
+    override fun apply(settings: Settings) {
+        settings.apply(mapOf("plugin" to "com.gradle.enterprise"))
+        settings.apply(mapOf("plugin" to "com.gradle.common-custom-user-data-gradle-plugin"))
+
+        // Github Actions always sets a "CI" environment variable
+        val isCI = System.getenv("CI") != null
+
+        settings.gradleEnterprise {
+            server = "https://ge.androidx.dev"
+
+            buildScan.apply {
+                publishAlways()
+                (this as BuildScanExtensionWithHiddenFeatures).publishIfAuthenticated()
+                isUploadInBackground = !isCI
+                capture.isTaskInputFiles = true
+
+                GradleWorkaround.obfuscate(obfuscation)
+            }
+        }
+
+        settings.buildCache.remote(HttpBuildCache::class.java) { remote ->
+            remote.url = URI("https://ge.androidx.dev/cache/")
+            val buildCachePassword = System.getenv("GRADLE_BUILD_CACHE_PASSWORD")
+            if (isCI && !buildCachePassword.isNullOrEmpty()) {
+                remote.isPush = true
+                remote.credentials { credentials ->
+                    credentials.username = "ci"
+                    credentials.password = buildCachePassword
+                }
+            } else {
+                remote.isPush = false
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt
new file mode 100644
index 0000000..90a699a
--- /dev/null
+++ b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundExtension.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+package androidx.playground
+
+import org.gradle.api.GradleException
+import org.gradle.api.initialization.Settings
+import org.gradle.api.model.ObjectFactory
+import java.io.File
+import java.util.Properties
+import javax.inject.Inject
+
+open class PlaygroundExtension @Inject constructor(
+    private val settings: Settings,
+    private val objectFactory: ObjectFactory
+) {
+    private var supportRootDir: File? = null
+
+    /**
+     * Includes the project if it does not already exist.
+     * This is invoked from `includeProject` to ensure all parent projects are included. If they are
+     * not, gradle will use the root project path to set the projectDir, which might conflict in
+     * playground. Instead, this method checks if another project in that path exists and if so,
+     * changes the project dir to avoid the conflict.
+     * see b/197253160 for details.
+     */
+    private fun includeFakeParentProjectIfNotExists(name: String, projectDir: File) {
+        if (name.isEmpty()) return
+        if (settings.findProject(name) != null) {
+            return
+        }
+        val actualProjectDir: File = if (settings.findProject(projectDir) != null) {
+            // Project directory conflicts with an existing project (possibly root). Move it
+            // to another directory to avoid the conflict.
+            File(projectDir.parentFile, ".ignore-${projectDir.name}")
+        } else {
+            projectDir
+        }
+        includeProjectAt(name, actualProjectDir)
+        // Set it to a gradle file that does not exist.
+        // We must always include projects starting with root, if we are including nested projects.
+        settings.project(name).buildFileName = "ignored.gradle"
+    }
+
+    private fun includeProjectAt(name: String, projectDir: File) {
+        if (settings.findProject(name) != null) {
+            throw GradleException("Cannot include project twice: $name is already included.")
+        }
+        val parentPath = name.substring(0, name.lastIndexOf(":"))
+        val parentDir = projectDir.parentFile
+        // Make sure parent is created first. see: b/197253160 for details
+        includeFakeParentProjectIfNotExists(
+            parentPath,
+            parentDir
+        )
+        settings.include(name)
+        settings.project(name).projectDir = projectDir
+    }
+
+    /**
+     * Includes a project by name, with a path relative to the root of AndroidX.
+     */
+    fun includeProject(name: String, filePath: String) {
+        if (supportRootDir == null) {
+            throw GradleException("Must call setupPlayground() first.")
+        }
+        includeProjectAt(name, File(supportRootDir, filePath))
+    }
+
+    /**
+     * Initializes the playground project to use public repositories as well as other internal
+     * projects that cannot be found in public repositories.
+     *
+     * @param relativePathToRoot The relative path of the project to the root AndroidX project
+     */
+    fun setupPlayground(relativePathToRoot: String) {
+        val projectDir = settings.rootProject.projectDir
+        val supportRoot = File(projectDir, relativePathToRoot).canonicalFile
+        this.supportRootDir = supportRoot
+        val buildFile = File(supportRoot, "playground-common/playground-build.gradle")
+        val relativePathToBuild = projectDir.toPath().relativize(buildFile.toPath()).toString()
+
+        val playgroundProperties = Properties()
+        val propertiesFile = File(supportRoot, "playground-common/playground.properties")
+        playgroundProperties.load(propertiesFile.inputStream())
+        settings.gradle.beforeProject { project ->
+            // load playground properties. These are not kept in the playground projects to prevent
+            // AndroidX build from reading them.
+            playgroundProperties.forEach {
+                project.extensions.extraProperties[it.key as String] = it.value
+            }
+        }
+
+        settings.rootProject.buildFileName = relativePathToBuild
+        settings.enableFeaturePreview("VERSION_CATALOGS")
+
+        val catalogFiles =
+            objectFactory.fileCollection().from("$supportRoot/gradle/libs.versions.toml")
+        settings.dependencyResolutionManagement {
+            it.versionCatalogs.create("libs").from(catalogFiles)
+        }
+
+        includeProject(":lint-checks", "lint-checks")
+        includeProject(":lint-checks:integration-tests", "lint-checks/integration-tests")
+        includeProject(":fakeannotations", "fakeannotations")
+        includeProject(":internal-testutils-common", "testutils/testutils-common")
+        includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin")
+
+        // allow public repositories
+        System.setProperty("ALLOW_PUBLIC_REPOS", "true")
+
+        // specify out dir location
+        System.setProperty("CHECKOUT_ROOT", supportRoot.path)
+    }
+
+    /**
+     * A convenience method to include projects from the main AndroidX build using a filter.
+     *
+     * @param filter This filter will be called with the project name (project path in gradle).
+     *               If filter returns true, it will be included in the build.
+     */
+    fun selectProjectsFromAndroidX(filter: (String) -> Boolean) {
+        if (supportRootDir == null) {
+            throw RuntimeException("Must call setupPlayground() first.")
+        }
+
+        // Multiline matcher for anything of the form:
+        //  includeProject(name, path, ...)
+        // where '...' is anything except the ')' character.
+        /* ktlint-disable max-line-length */
+        val includeProjectPattern = Regex(
+            """[\n\r\s]*includeProject\("(?<name>[a-z0-9-:]*)",[\n\r\s]*"(?<path>[a-z0-9-\/]+)[^)]+\)$""",
+            setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)
+        ).toPattern()
+        val supportSettingsFile = File(supportRootDir, "settings.gradle")
+        val matcher = includeProjectPattern.matcher(supportSettingsFile.readText())
+
+        while (matcher.find()) {
+            // check if is an include project line, if so, extract project gradle path and
+            // file system path and call the filter
+            val projectGradlePath = matcher.group("name")
+            val projectFilePath = matcher.group("path")
+            if (filter(projectGradlePath)) {
+                includeProject(projectGradlePath, projectFilePath)
+            }
+        }
+    }
+
+    /**
+     * Checks if a project is necessary for playground projects that involve compose.
+     */
+    fun isNeededForComposePlayground(name: String): Boolean {
+        if (name == ":compose:lint:common") return true
+        if (name == ":compose:lint:internal-lint-checks") return true
+        if (name == ":compose:test-utils") return true
+        if (name == ":compose:lint:common-test") return true
+        if (name == ":test:screenshot:screenshot") return true
+        return false
+    }
+}
\ No newline at end of file
diff --git a/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt
new file mode 100644
index 0000000..ff190ae
--- /dev/null
+++ b/playground-common/playground-plugin/src/main/kotlin/androidx/playground/PlaygroundPlugin.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+package androidx.playground
+
+import org.gradle.api.Plugin
+import org.gradle.api.initialization.Settings
+
+class PlaygroundPlugin : Plugin<Settings> {
+    override fun apply(settings: Settings) {
+        settings.apply(mapOf("plugin" to "playground-ge-conventions"))
+        settings.extensions.create("playground", PlaygroundExtension::class.java, settings)
+        validateJvm(settings)
+    }
+
+    private fun validateJvm(settings: Settings) {
+        // validate JVM version to print an understandable error if it is not set to the
+        // required value (11)
+        val jvmVersion = System.getProperty("java.vm.specification.version")
+        check(jvmVersion == "11") {
+            """
+                AndroidX build must be invoked with JDK 11.
+                ${
+                    if (settings.gradle.startParameter.projectProperties.containsKey(
+                            "android.injected.invoked.from.ide"
+                        )
+                    ) {
+                        """
+                            Make sure to set the Gradle JDK to JDK 11 in the project settings.
+                            File -> Settings -> Build, Execution, Deployment -> Build Tools ->
+                            Gradle -> Gradle JDK"
+                        """
+                        } else {
+                        "Make sure your JAVA_HOME environment variable points to Java 11 JDK."
+                    }
+                }
+                Current version: $jvmVersion
+                Current JAVA HOME: ${System.getProperty("java.home")}
+            """.trimIndent()
+        }
+    }
+}
\ No newline at end of file