From 1f1ea593e0f3b8b18d6bdb3fdff3380475f6f408 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 11 Dec 2025 10:51:54 -0500 Subject: [PATCH] dataconnect: fix `updateJson` task to find Data Connect executables whose file name is updated to also include the cpu architecture. This naming change started with Data Connect executable version 2.16.0, which started to be published in both x86_64 and arm64 variants. For example, the file name for the macos emulator version 2.15.1 was `dataconnect-emulator-macos-v2.15.1` but in 2.16.0 there were two executables named `dataconnect-emulator-macos-amd64-v2.16.0` and `dataconnect-emulator-macos-arm64-v2.16.0` (the only difference being one is named "amd64" and the other "arm64"). --- .../gradle/plugin/CpuArchitecture.kt | 44 ++++++++++ .../DataConnectExecutableVersionRegistry.kt | 29 +++++++ ...UpdateDataConnectExecutableVersionsTask.kt | 81 +++++++++++++++---- 3 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/CpuArchitecture.kt diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/CpuArchitecture.kt b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/CpuArchitecture.kt new file mode 100644 index 00000000000..15521ee9c39 --- /dev/null +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/CpuArchitecture.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Google LLC + * + * 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 com.google.firebase.dataconnect.gradle.plugin + +import java.util.Locale + +enum class CpuArchitecture { + AMD64, + ARM64; + + companion object { + fun current(): CpuArchitecture? { + val arch = System.getProperty("os.arch") + return if (arch === null) null else forName(arch) + } + + fun forName(arch: String): CpuArchitecture? = + forNameWithLowercaseArch(arch.lowercase(Locale.ROOT)) + + // This logic was adapted from + // https://github.com/gradle/gradle/blob/4457734e73fc567a43ccf96185341432b636bc47/platforms/core-runtime/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java#L357-L369 + private fun forNameWithLowercaseArch(arch: String): CpuArchitecture? = + when (arch) { + "x86_64", + "amd64" -> CpuArchitecture.AMD64 + "aarch64" -> CpuArchitecture.ARM64 + else -> null + } + } +} diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersionRegistry.kt b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersionRegistry.kt index a69517c1cbf..dd67fe01709 100644 --- a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersionRegistry.kt +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableVersionRegistry.kt @@ -70,6 +70,7 @@ object DataConnectExecutableVersionsRegistry { data class VersionInfo( @Serializable(with = LooseVersionSerializer::class) val version: Version, @Serializable(with = OperatingSystemSerializer::class) val os: OperatingSystem, + @Serializable(with = CpuArchitectureSerializer::class) val arch: CpuArchitecture? = null, val size: Long, val sha512DigestHex: String, ) @@ -95,6 +96,27 @@ object DataConnectExecutableVersionsRegistry { encoder.encodeString(value.serializedValue) } + private object CpuArchitectureSerializer : KSerializer { + override val descriptor = + PrimitiveSerialDescriptor( + "com.google.firebase.dataconnect.gradle.plugin.CpuArchitecture", + PrimitiveKind.STRING, + ) + + override fun deserialize(decoder: Decoder): CpuArchitecture = + decoder.decodeString().let { serializedValue -> + CpuArchitecture.entries.singleOrNull { it.serializedValue == serializedValue } + ?: throw DataConnectGradleException( + "yxnvjm2nxe", + "Unknown CPU architecture: $serializedValue " + + "(must be one of ${CpuArchitecture.entries.joinToString { it.serializedValue }})" + ) + } + + override fun serialize(encoder: Encoder, value: CpuArchitecture) = + encoder.encodeString(value.serializedValue) + } + val OperatingSystem.serializedValue: String get() = when (this) { @@ -102,4 +124,11 @@ object DataConnectExecutableVersionsRegistry { OperatingSystem.MacOS -> "macos" OperatingSystem.Linux -> "linux" } + + val CpuArchitecture.serializedValue: String + get() = + when (this) { + CpuArchitecture.AMD64 -> "amd64" + CpuArchitecture.ARM64 -> "arm64" + } } diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/UpdateDataConnectExecutableVersionsTask.kt b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/UpdateDataConnectExecutableVersionsTask.kt index f34d75bb9bf..81914d47028 100644 --- a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/UpdateDataConnectExecutableVersionsTask.kt +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/UpdateDataConnectExecutableVersionsTask.kt @@ -130,6 +130,7 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { private data class CloudStorageVersionInfo( val version: Version, val operatingSystem: OperatingSystem, + val cpuArchitecture: CpuArchitecture?, val blob: Blob, ) @@ -171,7 +172,7 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { return null } - val versionString = match.groups[2]?.value + val versionString = match.groups["version"]?.value val version = versionString?.toVersionOrNull(strict = false) if (version === null) { logger.info( @@ -205,7 +206,7 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { } val operatingSystem = - when (val operatingSystemString = match.groups[1]?.value) { + when (val operatingSystemString = match.groups["os"]?.value) { "linux" -> OperatingSystem.Linux "macos" -> OperatingSystem.MacOS "windows" -> OperatingSystem.Windows @@ -221,7 +222,24 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { } } - return CloudStorageVersionInfo(version, operatingSystem, blob = this) + val cpuArchitecture = + when (val cpuArchitectureString = match.groups["arch"]?.value) { + null -> null + "amd64" -> CpuArchitecture.AMD64 + "arm64" -> CpuArchitecture.ARM64 + else -> { + logger.info( + "WARNING: Ignoring Data Connect executable file: {} " + + "(unknown CPU architecture name: {} (in match for regex {}))", + name, + cpuArchitectureString, + fileNameRegex + ) + return null + } + } + + return CloudStorageVersionInfo(version, operatingSystem, cpuArchitecture, blob = this) } private fun CloudStorageVersionInfo.toRegistryVersionInfo(workDirectory: File): VersionInfo { @@ -229,7 +247,7 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { logger.lifecycle( "Downloading version {} ({} bytes, created {})", - "$version-${operatingSystem.serializedValue}", + toLogString(), blob.size.toStringWithThousandsSeparator(), dateFormatter.format(blob.createTimeOffsetDateTime.atZoneSameInstant(ZoneId.systemDefault())) ) @@ -244,31 +262,58 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { "never happen; if it _does_ happen it _could_ indicate a compromised " + "downloaded binary [y5967yd2cf]" } - return VersionInfo(version, operatingSystem, fileInfo.sizeInBytes, fileInfo.sha512DigestHex) + return VersionInfo( + version, + operatingSystem, + cpuArchitecture, + fileInfo.sizeInBytes, + fileInfo.sha512DigestHex + ) } private companion object { val versionInfoComparator = - compareBy { it.version }.thenByDescending { it.os.serializedValue } + compareBy { it.version } + .thenByDescending { it.os.serializedValue } + .thenBy { it.arch?.serializedValue } val cloudStorageVersionInfoComparator = compareBy { it.version } .thenByDescending { it.operatingSystem.serializedValue } - - @JvmName("toLogStringCloudStorageVersionInfo") - fun Iterable.toLogString(): String = joinToString { - "${it.version}-${it.operatingSystem.serializedValue}" - } - - @JvmName("toLogStringVersionInfo") - fun Iterable.toLogString(): String = joinToString { - "${it.version}-${it.os.serializedValue}" + .thenBy { it.cpuArchitecture?.serializedValue } + + @JvmName("iterableOfCloudStorageVersionInfoToLogString") + fun Iterable.toLogString(): String = joinToString { it.toLogString() } + + @JvmName("cloudStorageVersionInfoToLogString") + fun CloudStorageVersionInfo.toLogString(): String = + createLogStringFromVersionOsArch(version, operatingSystem, cpuArchitecture) + + @JvmName("iterableOfVersionInfoToLogString") + fun Iterable.toLogString(): String = joinToString { it.toLogString() } + + @JvmName("versionInfoToLogString") + fun VersionInfo.toLogString(): String = createLogStringFromVersionOsArch(version, os, arch) + + fun createLogStringFromVersionOsArch( + version: Version, + operatingSystem: OperatingSystem, + cpuArchitecture: CpuArchitecture? + ): String = buildString { + append(version) + append('-') + append(operatingSystem.serializedValue) + if (cpuArchitecture !== null) { + append('-') + append(cpuArchitecture.serializedValue) + } } val invalidVersions = setOf("1.15.0".toVersion()) val minVersion = "1.3.4".toVersion() - val fileNameRegex = ".*dataconnect-emulator-([^-]+)-v(.*)".toRegex() + val fileNameRegex = + ".*dataconnect-emulator-(?[^-]+)(-(?[^-]+))?-v(?.*)".toRegex() /** * Creates and returns a new list that contains all elements of the receiving [Iterable] that @@ -278,7 +323,9 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() { registry: DataConnectExecutableVersionsRegistry.Root ): List = filterNot { cloudStorageVersion -> registry.versions.any { - it.version == cloudStorageVersion.version && it.os == cloudStorageVersion.operatingSystem + it.version == cloudStorageVersion.version && + it.os == cloudStorageVersion.operatingSystem && + it.arch == cloudStorageVersion.cpuArchitecture } }