Enforce that projects are in docs-tip-of-tree

This checks that any project with API tracking enabled can be found in docs-tip-of-tree/build.gradle unless it has been opted-out of docs with `doNotDocumentReason`.

This is to ensure that any docs errors are caught during developement and not at release time when the project is added to docs-public/build.gradle.

Bug: 289373390
Test: All projects with `doNotDocumentReason` added were found by running `./gradlew checkDocsTipOfTree` locally
Change-Id: I5d1fc4f6d3b54df9bf2ec67e76c8dcd5e150bacc
diff --git a/appactions/interaction/interaction-proto/build.gradle b/appactions/interaction/interaction-proto/build.gradle
index dc6e370..72a6b56 100644
--- a/appactions/interaction/interaction-proto/build.gradle
+++ b/appactions/interaction/interaction-proto/build.gradle
@@ -100,4 +100,5 @@
     inceptionYear = "2022"
     description = "Protos for use with App Action interaction libraries."
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "No public API"
 }
diff --git a/appsearch/appsearch-debug-view/build.gradle b/appsearch/appsearch-debug-view/build.gradle
index 50a0025..a16fc5d 100644
--- a/appsearch/appsearch-debug-view/build.gradle
+++ b/appsearch/appsearch-debug-view/build.gradle
@@ -56,5 +56,6 @@
     inceptionYear = "2021"
     description = "A support library for AndroidX AppSearch that contains activities and views " +
             "for debugging an application's integration with AppSearch."
+    doNotDocumentReason = "No public API"
     metalavaK2UastEnabled = true
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 0574cbc..8a128c1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -375,6 +375,8 @@
     var runApiTasks: RunApiTasks = RunApiTasks.Auto
         get() = if (field == RunApiTasks.Auto && type != LibraryType.UNSET) type.checkApi else field
 
+    var doNotDocumentReason: String? = null
+
     var type: LibraryType = LibraryType.UNSET
     var failOnDeprecationWarnings = true
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt
index f84fbe7..10fc945 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt
@@ -20,11 +20,14 @@
 import androidx.build.Release
 import androidx.build.RunApiTasks
 import androidx.build.Version
+import androidx.build.addToBuildOnServer
+import androidx.build.docs.CheckTipOfTreeDocsTask
 import androidx.build.isWriteVersionedApiFilesEnabled
 import androidx.build.java.JavaCompileInputs
 import androidx.build.metalava.MetalavaTasks
 import androidx.build.resources.ResourceTasks
 import androidx.build.stableaidl.setupWithStableAidlPlugin
+import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import androidx.build.version
 import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.tasks.ProcessLibraryManifest
@@ -142,6 +145,22 @@
             return@afterEvaluate
         }
 
+        // Require docs to be set up unless opted-out
+        if (extension.doNotDocumentReason == null) {
+            val checkDocs = project.tasks.register(
+                "checkDocsTipOfTree",
+                CheckTipOfTreeDocsTask::class.java
+            ) { task ->
+                task.tipOfTreeBuildFile.set(
+                    project.rootProject.layout.projectDirectory
+                        .file("docs-tip-of-tree/build.gradle")
+                )
+                task.projectPathProvider.set(path)
+                task.cacheEvenIfNoOutputs()
+            }
+            project.addToBuildOnServer(checkDocs)
+        }
+
         val builtApiLocation = project.getBuiltApiLocation()
         val versionedApiLocation = project.getVersionedApiLocation()
         val currentApiLocation = project.getCurrentApiLocation()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt
new file mode 100644
index 0000000..4304dae
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/CheckTipOfTreeDocsTask.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 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.build.docs
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Verifies that the text of the [projectPathProvider] can be found in the [tipOfTreeBuildFile] to
+ * enforce that projects enable docs generation.
+ */
+@CacheableTask
+abstract class CheckTipOfTreeDocsTask : DefaultTask() {
+    @get:[InputFile PathSensitive(PathSensitivity.NONE)]
+    abstract val tipOfTreeBuildFile: RegularFileProperty
+
+    @get:Input
+    abstract val projectPathProvider: Property<String>
+
+    @TaskAction
+    fun exec() {
+        // Make sure not to allow a partial project path match, e.g. ":activity:activity" shouldn't
+        // match ":activity:activity-ktx", both need to be listed separately.
+        val projectPath = projectPathProvider.get()
+        val fullExpectedText = "project(\"$projectPath\")"
+        if (!tipOfTreeBuildFile.asFile.get().readText().contains(fullExpectedText)) {
+            val message = "Project $projectPath not found in docs-tip-of-tree/build.gradle\n\n" +
+                "Use the project creation script (development/project-creator/create_project.py) " +
+                "when setting up a project to make sure all required steps are complete.\n\n" +
+                "The project should be added to docs-tip-of-tree/build.gradle as " +
+                "\'docs(project(\"$projectPath\"))\' (use 'kmpDocs' instead of 'docs' for KMP " +
+                "projects).\n\n" +
+                "If this project should not have published refdocs, first check that the library " +
+                "type listed in its build.gradle file is accurate. If it is, opt out of refdoc " +
+                "generation using \'doNotDocumentReason = \"some reason\"\' in the 'androidx' " +
+                "configuration section (this is not common)."
+            throw GradleException(message)
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 7c9b212..23cd759 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -113,4 +113,5 @@
             "and reliable camera foundation that enables great camera driven experiences across " +
             "all of Android."
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "Not shipped externally"
 }
diff --git a/camera/camera-camera2-pipe-testing/build.gradle b/camera/camera-camera2-pipe-testing/build.gradle
index fbba598..4f625c4 100644
--- a/camera/camera-camera2-pipe-testing/build.gradle
+++ b/camera/camera-camera2-pipe-testing/build.gradle
@@ -74,4 +74,5 @@
             "consistent and reliable camera foundation that enables great camera driven " +
             "experiences across all of Android."
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "Not shipped externally"
 }
diff --git a/camera/camera-camera2-pipe/build.gradle b/camera/camera-camera2-pipe/build.gradle
index 3c3ebc4..2afb6a4 100644
--- a/camera/camera-camera2-pipe/build.gradle
+++ b/camera/camera-camera2-pipe/build.gradle
@@ -82,4 +82,5 @@
     description = "A set of opinionated camera interfaces and implementations on top of Camera2 " +
             "that will form a flexible shim layer to power Frameserver and CameraX."
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "Not shipped externally"
 }
diff --git a/compose/animation/animation-tooling-internal/build.gradle b/compose/animation/animation-tooling-internal/build.gradle
index 0d41720..dc22417 100644
--- a/compose/animation/animation-tooling-internal/build.gradle
+++ b/compose/animation/animation-tooling-internal/build.gradle
@@ -40,4 +40,5 @@
     publish = Publish.SNAPSHOT_AND_RELEASE
     runApiTasks = new RunApiTasks.Yes()
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "Only used externally by Android Studio"
 }
diff --git a/compose/ui/ui-test-manifest/build.gradle b/compose/ui/ui-test-manifest/build.gradle
index edec4a5..806bc76 100644
--- a/compose/ui/ui-test-manifest/build.gradle
+++ b/compose/ui/ui-test-manifest/build.gradle
@@ -41,6 +41,7 @@
     inceptionYear = "2021"
     description = "Compose testing library that should be added as a debugImplementation dependency to add properties to the debug manifest necessary for testing an application"
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "No public API"
 }
 
 android {
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index dec2908..8dfb4b7 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -342,11 +342,13 @@
     docs(project(":slice:slice-view"))
     docs(project(":slidingpanelayout:slidingpanelayout"))
     docs(project(":sqlite:sqlite"))
+    kmpDocs(project(":sqlite:sqlite-bundled"))
     docs(project(":sqlite:sqlite-framework"))
     docs(project(":sqlite:sqlite-ktx"))
     docs(project(":startup:startup-runtime"))
     docs(project(":swiperefreshlayout:swiperefreshlayout"))
     // androidx.test is not hosted in androidx
+    docs(project(":test:ext:junit-gtest"))
     docs(project(":test:uiautomator:uiautomator"))
     // androidx.textclassifier is not hosted in androidx
     docs(project(":tracing:tracing"))
diff --git a/fragment/fragment-testing-manifest/build.gradle b/fragment/fragment-testing-manifest/build.gradle
index 4f29962..4eb6be1 100644
--- a/fragment/fragment-testing-manifest/build.gradle
+++ b/fragment/fragment-testing-manifest/build.gradle
@@ -43,6 +43,7 @@
     inceptionYear = "2022"
     description = "Fragment testing library that should be added as a debugImplementation dependency to add properties to the debug manifest necessary for testing an application"
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "No public API"
 }
 
 android {
diff --git a/inspection/inspection/build.gradle b/inspection/inspection/build.gradle
index 8027038..2fb163d 100644
--- a/inspection/inspection/build.gradle
+++ b/inspection/inspection/build.gradle
@@ -49,6 +49,7 @@
             "Interfaces provided in this artifact should be binary compatible to guarantee " +
                     "that old inspectors are compatible with newer Android Studio versions")
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "Not shipped externally"
 }
 
 android {
diff --git a/kruth/kruth/build.gradle b/kruth/kruth/build.gradle
index 3a2ad51..1695cf0 100644
--- a/kruth/kruth/build.gradle
+++ b/kruth/kruth/build.gradle
@@ -101,4 +101,5 @@
     publish = Publish.SNAPSHOT_ONLY
     runApiTasks = new RunApiTasks.Yes() // Used to diff against Google Truth
     type = LibraryType.INTERNAL_TEST_LIBRARY
+    doNotDocumentReason = "Not shipped externally"
 }
diff --git a/tracing/tracing-perfetto-binary/build.gradle b/tracing/tracing-perfetto-binary/build.gradle
index 0a9ab3df..5bba72f 100644
--- a/tracing/tracing-perfetto-binary/build.gradle
+++ b/tracing/tracing-perfetto-binary/build.gradle
@@ -95,4 +95,5 @@
     description = "Provides native binaries required by AndroidX Tracing: Perfetto SDK " +
         "and is not intended to be used outside of that context."
     metalavaK2UastEnabled = true
+    doNotDocumentReason = "No public API"
 }