diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/MacDistributionCustomizer.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/MacDistributionCustomizer.kt index 99b577844d57..291475bd0f47 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/MacDistributionCustomizer.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/MacDistributionCustomizer.kt @@ -3,8 +3,10 @@ package org.jetbrains.intellij.build import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf +import org.jetbrains.annotations.ApiStatus import org.jetbrains.intellij.build.impl.support.RepairUtilityBuilder import java.nio.file.Path +import java.util.UUID import java.util.function.Predicate open class MacDistributionCustomizer { @@ -164,4 +166,12 @@ open class MacDistributionCustomizer { extraExecutables + context.getExtraExecutablePattern(OsFamily.MACOS) } + + /** + * @see org.jetbrains.intellij.build.NativeBinaryDownloader.getLauncher + */ + @ApiStatus.Internal + open fun getDistributionUUID(context: BuildContext): UUID { + return UUID.nameUUIDFromBytes("${context.fullBuildNumber}-${context.options.buildDateInSeconds}".toByteArray()) + } } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/MacDistributionBuilder.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/MacDistributionBuilder.kt index 300c42a31fec..6797045d44ab 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/MacDistributionBuilder.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/MacDistributionBuilder.kt @@ -14,6 +14,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.jetbrains.intellij.build.* import org.jetbrains.intellij.build.impl.OsSpecificDistributionBuilder.Companion.suffix import org.jetbrains.intellij.build.impl.client.createJetBrainsClientContextForLaunchers +import org.jetbrains.intellij.build.impl.macOS.MachOUuid import org.jetbrains.intellij.build.impl.productInfo.* import org.jetbrains.intellij.build.impl.qodana.generateQodanaLaunchData import org.jetbrains.intellij.build.impl.support.RepairUtilityBuilder @@ -24,6 +25,7 @@ import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption import java.time.LocalDate +import java.util.UUID import java.util.zip.Deflater import kotlin.io.path.* @@ -212,7 +214,9 @@ class MacDistributionBuilder( val executable = context.productProperties.baseFileName val (execPath, licensePath) = NativeBinaryDownloader.getLauncher(context, OsFamily.MACOS, arch) - copyFile(execPath, macDistDir.resolve("MacOS/${executable}")) + val copy = macDistDir.resolve("MacOS/$executable") + copyFile(execPath, copy) + MachOUuid(copy, customizer.getDistributionUUID(context), context).patch() copyFile(licensePath, macDistDir.resolve("license/launcher-third-party-libraries.html")) val icnsPath = Path.of((if (context.applicationInfo.isEAP) customizer.icnsPathForEAP else null) ?: customizer.icnsPath) diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/macOS/MachOUuid.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/macOS/MachOUuid.kt new file mode 100644 index 000000000000..3adf0c430fe1 --- /dev/null +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/macOS/MachOUuid.kt @@ -0,0 +1,64 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.intellij.build.impl.macOS + +import org.jetbrains.intellij.build.BuildContext +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption +import java.util.* + +/** + * Patches UUID value in the Mach-O [executable] with the [newUuid]. + * Only single-arch 64-bit files are supported. + */ +internal class MachOUuid(private val executable: Path, private val newUuid: UUID, private val context: BuildContext) { + private companion object { + const val LC_UUID = 0x1b + } + + fun patch() { + Files.newByteChannel(executable, StandardOpenOption.READ, StandardOpenOption.WRITE).use { channel -> + var buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN) + channel.read(buffer) + buffer.flip() + val magic = buffer.getInt() + check(magic == -0x1120531) { "Not a valid 64-bit Mach-O file: 0x" + Integer.toHexString(magic) } + buffer.clear() + channel.position(16) + channel.read(buffer) + buffer.flip() + val nCmds = buffer.getInt() + buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN) + channel.position(32) + (0..