Dynamically include project references in subsets

Uses a static build.gradle parser to build a graph of project references
to allow including any project references that the explicitly tagged
ones in settings.gradle use, but themselves are not tagged

Performance cost is ~100ms
Before: https://ge.androidx.dev/s/w7opgfikpljvu/performance/configuration?details=code-unit-settings.gradle!:&openScriptsAndPlugins=WyJjb2RlLXVuaXQtc2V0dGluZ3MuZ3JhZGxlIl0
After: https://ge.androidx.dev/s/vjotrlwgxx3gy/performance/configuration?details=code-unit-settings.gradle!:&openScriptsAndPlugins=WyJjb2RlLXVuaXQtc2V0dGluZ3MuZ3JhZGxlIl0

This change also turns down kotlin native host tests

Test: ANDROIDX_PROJECTS=compose ./gradlew projects
Change-Id: Ice8b88d474c654a703327477e954e9152d370f7d
diff --git a/settings.gradle b/settings.gradle
index d92c515..94a76fb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,6 +2,9 @@
 import androidx.build.gradle.gcpbuildcache.GcpBuildCache
 import androidx.build.gradle.gcpbuildcache.GcpBuildCacheServiceFactory
 
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
 pluginManagement {
     repositories {
         maven {
@@ -303,6 +306,22 @@
     includeProject(name, null, filter)
 }
 
+// A map of project path to a set of project paths referenced directly by this project.
+@Field Map<String, Set<String>> projectReferences = new HashMap<String, Set<String>>()
+
+// A map of all project paths to their project directory.
+@Field Map<String, File> allProjects = new HashMap<String, File>()
+// A set of projects that the user asked to filter to.
+@Field Set<String> filteredProjects = new HashSet<String>()
+
+@Field Pattern projectReferencePattern = Pattern.compile(
+        "(project|projectOrArtifact)\\((path: )?[\"'](?<name>\\S*)[\"'](, configuration: .*)?\\)"
+)
+@Field Pattern inspection = Pattern.compile("packageInspector\\(project, \"(.*)\"\\)")
+@Field Pattern composePlugin = Pattern.compile("id\\(\"AndroidXComposePlugin\"\\)")
+@Field Pattern paparazziPlugin = Pattern.compile("id\\(\"AndroidXPaparazziPlugin\"\\)")
+@Field Pattern iconGenerator = Pattern.compile("IconGenerationTask\\.register")
+
 // Calling includeProject(name, filePath) is shorthand for:
 //
 //   include(name)
@@ -313,9 +332,7 @@
 //   the Maven artifactId
 //
 def includeProject(name, filePath, List<BuildType> filter = []) {
-    if (!shouldIncludeForFilter(filter)) return
-    settings.include(name)
-
+    if (shouldIncludeForFilter(filter)) filteredProjects.add(name)
     def file
     if (filePath != null) {
         if (filePath instanceof String) {
@@ -326,9 +343,35 @@
         } else {
             file = filePath
         }
-        project(name).projectDir = file
     } else {
-        file = project(name).projectDir
+        file = new File(rootDir, name.substring(1).replace(":", "/"))
+    }
+    allProjects[name] = file
+    File buildGradle = new File(file, "build.gradle")
+    if (buildGradle.exists()) {
+        Set<String> links = new HashSet<String>()
+        for (line in buildGradle.readLines()) {
+            Matcher m = projectReferencePattern.matcher(line)
+            if (m.find()) {
+                links.add(m.group("name"))
+            }
+            Matcher matcherInspection = inspection.matcher(line)
+            if (matcherInspection) {
+                links.add(matcherInspection.group(1))
+            }
+            if (composePlugin.matcher(line).find()) {
+                links.add(":compose:compiler:compiler")
+                links.add(":compose:lint:internal-lint-checks")
+            }
+            if (paparazziPlugin.matcher(line).find()) {
+                links.add(":test:screenshot:screenshot-proto")
+                links.add(":internal-testutils-paparazzi")
+            }
+            if (iconGenerator.matcher(line).find()) {
+                links.add(":compose:material:material:icons:generator")
+            }
+        }
+        projectReferences[name] = links
     }
     if (!file.exists()) {
         // This option is supported so that development/simplify_build_failure.sh can try
@@ -426,7 +469,6 @@
     includeProject(":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-dep", [BuildType.MAIN])
     includeProject(":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-main", [BuildType.MAIN])
 }
-includeProject(":buildSrc-tests:project-subsets", [BuildType.MAIN])
 includeProject(":camera:camera-camera2", [BuildType.CAMERA])
 includeProject(":camera:camera-camera2-pipe", [BuildType.CAMERA])
 includeProject(":camera:camera-camera2-pipe-integration", [BuildType.CAMERA])
@@ -566,7 +608,7 @@
 includeProject(":compose:ui:ui-graphics", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-graphics-lint", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-graphics:ui-graphics-benchmark", "compose/ui/ui-graphics/benchmark", [BuildType.COMPOSE])
-includeProject(":compose:ui:ui-graphics:ui-graphics-benchmark:test", [BuildType.COMPOSE])
+includeProject(":compose:ui:ui-graphics:ui-graphics-benchmark:test", "compose/ui/ui-graphics/benchmark/test", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-graphics:ui-graphics-samples", "compose/ui/ui-graphics/samples", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-inspection", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-lint", [BuildType.COMPOSE])
@@ -1127,3 +1169,20 @@
 
 // Workaround for b/203825166
 includeBuild("placeholder")
+
+// For a given project path add the transitive project references to the include set.
+void addReferences(String projectPath, Set<String> included) {
+    if (projectPath in included) return
+    included.add(projectPath)
+    for (reference in projectReferences[projectPath]) {
+        addReferences(reference, included)
+    }
+}
+Set<String> projectsToInclude = new HashSet<>()
+for (filteredProject in filteredProjects) {
+    addReferences(filteredProject, projectsToInclude)
+}
+for (entry in projectsToInclude) {
+    settings.include(entry)
+    project(entry).projectDir = allProjects[entry]
+}