Add `darwinBenchmarkResults` to native-mac-host-tests.

* Also adds XCodeBuildService to make sure we run 1 `xcodebuild` at a time.
* Boots up simulators automatically after discovering the simulator versions.

Test: DIST_DIR=/Users/rahulrav/Workspace/Projects/androidx/out/dist ANDROIDX_PROJECTS=KMP ./gradlew --no-build-cache --no-configuration-cache :collection:collection-benchmark:darwinBenchmarkResults

Change-Id: Ic7a85974c581b6573f2cbeb34c244bab25e5943b
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt
index a016541..6ef1b7f 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt
@@ -76,6 +76,9 @@
             project.layout.buildDirectory.dir("$name.xcresult")
         }
 
+        // Configure the XCode Build Service so we don't run too many benchmarks at the same time.
+        project.configureXCodeBuildService()
+
         val fetchXCodeGenTask = project.tasks.register(
             FETCH_XCODEGEN_TASK, FetchXCodeGenTask::class.java
         ) {
@@ -96,6 +99,13 @@
         val runDarwinBenchmarks = project.tasks.register(
             RUN_DARWIN_BENCHMARKS_TASK, RunDarwinBenchmarksTask::class.java
         ) {
+            val sharedService =
+                project
+                    .gradle
+                    .sharedServices
+                    .registrations.getByName(XCodeBuildService.XCODE_BUILD_SERVICE_NAME)
+                    .service
+            it.usesService(sharedService)
             it.xcodeProjectPath.set(generateXCodeProjectTask.flatMap { task ->
                 task.xcProjectPath
             })
@@ -152,6 +162,7 @@
         const val DIST_DIR = "DIST_DIR"
         const val LIBRARY_METRICS = "librarymetrics"
         const val DARWIN_BENCHMARKS_DIR = "darwinBenchmarks"
+
         // Gradle Properties
         const val XCODEGEN_DOWNLOAD_URI = "androidx.benchmark.darwin.xcodeGenDownloadUri"
 
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt
index 7d4136d..dfca8e9 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt
@@ -17,7 +17,7 @@
 package androidx.benchmark.darwin.gradle
 
 import androidx.benchmark.darwin.gradle.skia.Metrics
-import androidx.benchmark.darwin.gradle.xcode.Models
+import androidx.benchmark.darwin.gradle.xcode.GsonHelpers
 import androidx.benchmark.darwin.gradle.xcode.XcResultParser
 import java.io.ByteArrayInputStream
 import java.io.ByteArrayOutputStream
@@ -67,7 +67,7 @@
         }
         val (record, summaries) = parser.parseResults()
         val metrics = Metrics.buildMetrics(record, summaries)
-        val output = Models.gsonBuilder()
+        val output = GsonHelpers.gsonBuilder()
             .setPrettyPrinting()
             .create()
             .toJson(metrics)
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt
index 065e7ff..39f283b 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt
@@ -85,5 +85,11 @@
         require(copied) {
             "Unable to copy $sourceFile to $targetFile"
         }
+        // Delete the generated Info.plist file given our source folders should be clean.
+        // Context: b/258545725
+        val deleted = sourceFile.deleteRecursively()
+        require(deleted) {
+            "Unable to delete $sourceFile"
+        }
     }
 }
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt
index d33dd6a..66f15d8 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt
@@ -49,21 +49,24 @@
     @TaskAction
     fun runBenchmarks() {
         requireXcodeBuild()
+        val simCtrl = XCodeSimCtrl(execOperations, destination.get())
         val xcodeProject = xcodeProjectPath.get().asFile
         val xcResultFile = xcResultPath.get().asFile
         if (xcResultFile.exists()) {
             xcResultFile.deleteRecursively()
         }
-        val args = listOf(
-            "xcodebuild",
-            "test",
-            "-project", xcodeProject.absolutePath.toString(),
-            "-scheme", scheme.get(),
-            "-destination", destination.get(),
-            "-resultBundlePath", xcResultFile.absolutePath,
-        )
-        logger.info("Command : ${args.joinToString(" ")}")
-        execOperations.executeQuietly(args)
+        simCtrl.start { destinationDesc ->
+            val args = listOf(
+                "xcodebuild",
+                "test",
+                "-project", xcodeProject.absolutePath.toString(),
+                "-scheme", scheme.get(),
+                "-destination", destinationDesc,
+                "-resultBundlePath", xcResultFile.absolutePath,
+            )
+            logger.info("Command : ${args.joinToString(" ")}")
+            execOperations.executeQuietly(args)
+        }
     }
 
     private fun requireXcodeBuild() {
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/XCodeBuildService.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/XCodeBuildService.kt
new file mode 100644
index 0000000..6f83f22
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/XCodeBuildService.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 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.benchmark.darwin.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
+/**
+ * A service that manages simulators / devices. Also manages booting up and tearing down instances
+ */
+interface XCodeBuildService : BuildService<BuildServiceParameters.None> {
+    companion object {
+        const val XCODE_BUILD_SERVICE_NAME = "DarwinXCodeBuildService"
+    }
+}
+
+/**
+ * Register the [XCodeBuildService] as a shared gradle service.
+ */
+fun Project.configureXCodeBuildService() {
+    gradle.sharedServices.registerIfAbsent(
+        XCodeBuildService.XCODE_BUILD_SERVICE_NAME,
+        XCodeBuildService::class.java
+    ) { spec ->
+        // Run one xcodebuild at a time.
+        spec.maxParallelUsages.set(1)
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/XCodeSimCtrl.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/XCodeSimCtrl.kt
new file mode 100644
index 0000000..4820dcf
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/XCodeSimCtrl.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2022 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.benchmark.darwin.gradle
+
+import androidx.benchmark.darwin.gradle.xcode.GsonHelpers
+import androidx.benchmark.darwin.gradle.xcode.SimulatorRuntimes
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import org.gradle.process.ExecOperations
+
+/**
+ * Controls XCode Simulator instances.
+ */
+class XCodeSimCtrl(
+    private val execOperations: ExecOperations,
+    private val destination: String
+) {
+
+    private var device: String? = null
+
+    // A device type looks something like
+    // platform=iOS Simulator,name=iPhone 13,OS=15.2
+
+    fun start(block: (destinationDesc: String) -> Unit) {
+        try {
+            device = boot(destination, execOperations)
+            block(device!!)
+        } finally {
+            val id = device
+            if (id != null) {
+                shutDownAndDelete(execOperations)
+            }
+        }
+    }
+
+    companion object {
+        private const val PLATFORM_KEY = "platform"
+        private const val NAME_KEY = "name"
+        private const val RUNTIME_KEY = "OS"
+        private const val IOS_SIMULATOR = "iOS Simulator"
+        private const val IPHONE_PRODUCT_FAMILY = "iPhone"
+
+        internal fun boot(
+            destination: String,
+            execOperations: ExecOperations
+        ): String {
+            val parsed = parse(destination)
+            return when (platform(parsed)) {
+                // Simulator needs to be booted up.
+                IOS_SIMULATOR -> bootSimulator(destination, parsed, execOperations)
+                // For other destinations, we don't have much to do.
+                else -> destination
+            }
+        }
+
+        private fun discoverSimulatorRuntimeVersion(
+            execOperations: ExecOperations
+        ): String? {
+            val json = executeCommand(
+                execOperations, listOf(
+                    "xcrun", "simctl", "list", "runtimes", "--json"
+                )
+            )
+            val simulatorRuntimes = GsonHelpers.gson().fromJson(json, SimulatorRuntimes::class.java)
+            // There is usually one version of the simulator runtime available per xcode version
+            val supported = simulatorRuntimes.runtimes.firstOrNull { runtime ->
+                runtime.isAvailable && runtime.supportedDeviceTypes.any { deviceType ->
+                    deviceType.productFamily == IPHONE_PRODUCT_FAMILY
+                }
+            }
+            return supported?.version
+        }
+
+        private fun bootSimulator(
+            destination: String,
+            parsed: Map<String, String>,
+            execOperations: ExecOperations
+        ): String {
+            val deviceName = deviceName(parsed)
+            val supported = discoverSimulatorRuntimeVersion(execOperations)
+            // While this is not strictly correct, these versions should be pretty close.
+            val runtimeVersion = supported ?: runtimeVersion(parsed)
+            check(deviceName != null && runtimeVersion != null) {
+                "Invalid destination spec: $destination"
+            }
+            val deviceId = executeCommand(
+                execOperations, listOf(
+                    "xcrun",
+                    "simctl",
+                    "create",
+                    deviceName, // Use the deviceName as the name
+                    deviceName,
+                    "iOS$runtimeVersion"
+                )
+            )
+            check(deviceId.isNotBlank()) {
+                "Invalid device id for simulator: $deviceId (Destination: $destination)"
+            }
+            executeCommand(
+                execOperations, listOf("xcrun", "simctl", "boot", deviceId)
+            )
+            // Return a new descriptor
+            return "id=$deviceId"
+        }
+
+        internal fun shutDownAndDelete(execOperations: ExecOperations) {
+            // Cleans up all running simulators.
+            executeCommand(
+                execOperations, listOf("xcrun", "simctl", "shutdown", "all")
+            )
+            executeCommand(
+                execOperations, listOf("xcrun", "simctl", "delete", "all")
+            )
+        }
+
+        private fun executeCommand(execOperations: ExecOperations, args: List<String>): String {
+            val output = ByteArrayOutputStream()
+            output.use {
+                execOperations.exec { spec ->
+                    spec.commandLine = args
+                    spec.standardOutput = output
+                }
+                val input = ByteArrayInputStream(output.toByteArray())
+                return input.use {
+                    // Trimming is important here, otherwise ExecOperations encodes the string
+                    // with shell specific escape sequences which mangle the device
+                    input.reader().readText().trim()
+                }
+            }
+        }
+
+        private fun platform(parsed: Map<String, String>): String? {
+            return parsed[PLATFORM_KEY]
+        }
+
+        private fun deviceName(parsed: Map<String, String>): String? {
+            return parsed[NAME_KEY]
+        }
+
+        private fun runtimeVersion(parsed: Map<String, String>): String? {
+            return parsed[RUNTIME_KEY]
+        }
+
+        private fun parse(destination: String): Map<String, String> {
+            return destination.splitToSequence(",")
+                .map { split ->
+                    check(split.contains("=")) {
+                        "Invalid destination spec: $destination"
+                    }
+                    val (key, value) = split.split("=", limit = 2)
+                    key.trim() to value.trim()
+                }
+                .toMap()
+        }
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/GsonHelpers.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/GsonHelpers.kt
new file mode 100644
index 0000000..3d9414c
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/GsonHelpers.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 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.benchmark.darwin.gradle.xcode
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+
+object GsonHelpers {
+    internal fun gsonBuilder(): GsonBuilder {
+        val builder = GsonBuilder()
+        builder.registerTypeAdapter(
+            ActionsTestSummaryGroupOrMeta::class.java,
+            ActionTestSummaryDeserializer()
+        )
+        return builder
+    }
+
+    fun gson(): Gson {
+        return gsonBuilder().create()
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/Models.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XCResultModels.kt
similarity index 93%
rename from benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/Models.kt
rename to benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XCResultModels.kt
index 863da27..237a8e2 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/Models.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XCResultModels.kt
@@ -16,8 +16,6 @@
 
 package androidx.benchmark.darwin.gradle.xcode
 
-import com.google.gson.Gson
-import com.google.gson.GsonBuilder
 import com.google.gson.JsonDeserializationContext
 import com.google.gson.JsonDeserializer
 import com.google.gson.JsonElement
@@ -203,10 +201,10 @@
         context: JsonDeserializationContext
     ): ActionsTestSummaryGroupOrMeta {
         return if (checkType(jsonElement, ACTION_TEST_SUMMARY_GROUP)) {
-            val adapter = Models.gson().getAdapter(ActionTestSummaryGroup::class.java)
+            val adapter = GsonHelpers.gson().getAdapter(ActionTestSummaryGroup::class.java)
             adapter.fromJson(jsonElement.toString())
         } else if (checkType(jsonElement, ACTION_TEST_SUMMARY_META)) {
-            val adapter = Models.gson().getAdapter(ActionTestSummaryMeta::class.java)
+            val adapter = GsonHelpers.gson().getAdapter(ActionTestSummaryMeta::class.java)
             adapter.fromJson(jsonElement.toString())
         } else {
             reportException(jsonElement)
@@ -227,7 +225,7 @@
             val json = jsonElement.asJsonObject
             val jsonType: JsonElement? = json.get(TYPE)
             if (jsonType != null && jsonType.isJsonObject) {
-                val adapter = Models.gson().getAdapter(TypeDefinition::class.java)
+                val adapter = GsonHelpers.gson().getAdapter(TypeDefinition::class.java)
                 val type = adapter.fromJson(jsonType.toString())
                 return type.name == name
             }
@@ -324,18 +322,3 @@
         return activitySummaries.title()
     }
 }
-
-object Models {
-    internal fun gsonBuilder(): GsonBuilder {
-        val builder = GsonBuilder()
-        builder.registerTypeAdapter(
-            ActionsTestSummaryGroupOrMeta::class.java,
-            ActionTestSummaryDeserializer()
-        )
-        return builder
-    }
-
-    fun gson(): Gson {
-        return gsonBuilder().create()
-    }
-}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XCodeSimulatorModels.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XCodeSimulatorModels.kt
new file mode 100644
index 0000000..5255a96
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XCodeSimulatorModels.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 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.benchmark.darwin.gradle.xcode
+
+/**
+ * A representation of the output of `xcrun simctl runtimes list --json`.
+ *
+ * That produces an object that contains a [List] of [SimulatorRuntime].
+ */
+data class SimulatorRuntimes(
+    val runtimes: List<SimulatorRuntime>
+)
+
+/**
+ * An XCode simulator runtime. The serialized representation looks something like:
+ *
+ * ```json
+ * {
+ *   "bundlePath" : "...\/Profiles\/Runtimes\/watchOS.simruntime",
+ *   "buildversion" : "19S51",
+ *   "runtimeRoot" : "....\/Runtimes\/watchOS.simruntime\/Contents\/Resources\/RuntimeRoot",
+ *   "identifier" : "com.apple.CoreSimulator.SimRuntime.watchOS-8-3",
+ *   "version" : "8.3",
+ *    "isAvailable" : true,
+ *    "supportedDeviceTypes" : [
+ *      ...
+ *    ]
+ * }
+ * ```
+ */
+data class SimulatorRuntime(
+    val identifier: String,
+    val version: String,
+    val isAvailable: Boolean,
+    val supportedDeviceTypes: List<SupportedDeviceType>
+)
+
+/**
+ * A serialized supported device type has a representation that looks like:
+ *
+ * ```json
+ * {
+ *  "bundlePath" : "...\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone 6s.simdevicetype",
+ *  "name" : "iPhone 6s",
+ *  "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-6s",
+ *  "productFamily" : "iPhone"
+ *  }
+ * ```
+ */
+data class SupportedDeviceType(
+    val bundlePath: String,
+    val name: String,
+    val identifier: String,
+    val productFamily: String
+)
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt
index c0c3c15..953b771 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt
@@ -29,7 +29,7 @@
 ) {
     fun parseResults(): Pair<ActionsInvocationRecord, List<ActionTestSummary>> {
         val json = commandExecutor(xcRunCommand())
-        val gson = Models.gson()
+        val gson = GsonHelpers.gson()
         val record = gson.fromJson(json, ActionsInvocationRecord::class.java)
         val summaries = record.actions.testReferences().flatMap { testRef ->
             val summary = commandExecutor(xcRunCommand(testRef))
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt
index 663b7ea..20bc13a 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt
@@ -17,7 +17,7 @@
 import androidx.benchmark.darwin.gradle.xcode.ActionTestPlanRunSummaries
 import androidx.benchmark.darwin.gradle.xcode.ActionTestSummary
 import androidx.benchmark.darwin.gradle.xcode.ActionsInvocationRecord
-import androidx.benchmark.darwin.gradle.xcode.Models
+import androidx.benchmark.darwin.gradle.xcode.GsonHelpers
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,7 +28,7 @@
     @Test
     fun parseXcResultOutputs() {
         val json = testData(XCRESULT_OUTPUT_JSON).readText()
-        val gson = Models.gson()
+        val gson = GsonHelpers.gson()
         val record = gson.fromJson(json, ActionsInvocationRecord::class.java)
         assertThat(record.actions.testReferences().size).isEqualTo(1)
         assertThat(record.metrics.size()).isEqualTo(1)
@@ -38,7 +38,7 @@
     @Test
     fun parseTestsReferenceOutput() {
         val json = testData(XC_TESTS_REFERENCE_OUTPUT_JSON).readText()
-        val gson = Models.gson()
+        val gson = GsonHelpers.gson()
         val testPlanSummaries = gson.fromJson(json, ActionTestPlanRunSummaries::class.java)
         val testSummaryMetas = testPlanSummaries.testSummaries()
         assertThat(testSummaryMetas.size).isEqualTo(1)
@@ -49,7 +49,7 @@
     @Test
     fun parseTestOutput() {
         val json = testData(XC_TEST_OUTPUT_JSON).readText()
-        val gson = Models.gson()
+        val gson = GsonHelpers.gson()
         val testSummary = gson.fromJson(json, ActionTestSummary::class.java)
         assertThat(testSummary.title()).isNotEmpty()
         assertThat(testSummary.isSuccessful()).isTrue()
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt
index 00468a9..2ce55a7 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt
@@ -15,7 +15,7 @@
  */
 
 import androidx.benchmark.darwin.gradle.skia.Metrics
-import androidx.benchmark.darwin.gradle.xcode.Models
+import androidx.benchmark.darwin.gradle.xcode.GsonHelpers
 import androidx.benchmark.darwin.gradle.xcode.XcResultParser
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assume.assumeTrue
@@ -51,7 +51,7 @@
         assertThat(record.metrics.size()).isEqualTo(2)
         assertThat(summaries.isNotEmpty()).isTrue()
         val metrics = Metrics.buildMetrics(record, summaries)
-        val json = Models.gsonBuilder()
+        val json = GsonHelpers.gsonBuilder()
             .setPrettyPrinting()
             .create()
             .toJson(metrics)
diff --git a/benchmark/benchmark-darwin-xcode/projects/benchmark-darwin-samples-xcode.yml b/benchmark/benchmark-darwin-xcode/projects/benchmark-darwin-samples-xcode.yml
index a7c06ed..42177bd 100644
--- a/benchmark/benchmark-darwin-xcode/projects/benchmark-darwin-samples-xcode.yml
+++ b/benchmark/benchmark-darwin-xcode/projects/benchmark-darwin-samples-xcode.yml
@@ -38,4 +38,4 @@
   CODE_SIGNING_REQUIRED: 'NO'
   CODE_SIGN_ENTITLEMENTS: ''
   CODE_SIGNING_ALLOWED: 'NO'
-  IPHONEOS_DEPLOYMENT_TARGET: 15.2
+  IPHONEOS_DEPLOYMENT_TARGET: 15.0
diff --git a/benchmark/benchmark-darwin-xcode/projects/collection-benchmark-ios.yml b/benchmark/benchmark-darwin-xcode/projects/collection-benchmark-ios.yml
index c40aa28..79abc41 100644
--- a/benchmark/benchmark-darwin-xcode/projects/collection-benchmark-ios.yml
+++ b/benchmark/benchmark-darwin-xcode/projects/collection-benchmark-ios.yml
@@ -38,4 +38,4 @@
   CODE_SIGNING_REQUIRED: 'NO'
   CODE_SIGN_ENTITLEMENTS: ''
   CODE_SIGNING_ALLOWED: 'NO'
-  IPHONEOS_DEPLOYMENT_TARGET: 15.2
+  IPHONEOS_DEPLOYMENT_TARGET: 15.0
diff --git a/busytown/androidx-native-mac-host-tests.sh b/busytown/androidx-native-mac-host-tests.sh
index b39a384..4edcfaa 100755
--- a/busytown/androidx-native-mac-host-tests.sh
+++ b/busytown/androidx-native-mac-host-tests.sh
@@ -10,7 +10,7 @@
 
 cd "$(dirname $0)"
 
-impl/build.sh allTests \
+impl/build.sh darwinBenchmarkResults allTests \
     --no-configuration-cache \
     -Pandroidx.ignoreTestFailures \
     -Pandroidx.displayTestOutput=false \