diff --git a/platform/platform-impl/api-dump-unreviewed.txt b/platform/platform-impl/api-dump-unreviewed.txt index 67dbd3970870..e52f1d8ed794 100644 --- a/platform/platform-impl/api-dump-unreviewed.txt +++ b/platform/platform-impl/api-dump-unreviewed.txt @@ -2014,6 +2014,7 @@ c:com.intellij.help.impl.HelpManagerImpl - invokeHelp(java.lang.String):V com.intellij.ide.AboutPopupDescriptionProvider - a:getDescription():java.lang.String +- getExtendedDescription():java.lang.String f:com.intellij.ide.ChangeProjectIconPalette - com.intellij.util.ui.ColorPalette - (I):V diff --git a/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/OsDataCollector.kt b/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/OsDataCollector.kt index 879c84a415c3..0b315f8b1c02 100644 --- a/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/OsDataCollector.kt +++ b/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/OsDataCollector.kt @@ -10,15 +10,12 @@ import com.intellij.internal.statistic.eventLog.events.EventFields.Version import com.intellij.internal.statistic.eventLog.events.EventPair import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector import com.intellij.openapi.util.SystemInfo -import com.intellij.util.UnixUtil.getGlibcVersion +import com.intellij.util.UnixUtil import org.jetbrains.annotations.ApiStatus -import java.io.IOException -import java.nio.file.Files import java.nio.file.Path import java.time.OffsetDateTime import java.util.* import kotlin.io.path.name -import kotlin.streams.asSequence internal class OsDataCollector : ApplicationUsagesCollector() { private val OS_NAMES = listOf("Windows", "Mac", "Linux", "FreeBSD", "Solaris", "Other") @@ -64,12 +61,12 @@ internal class OsDataCollector : ApplicationUsagesCollector() { TIMEZONE.metric(tz)) when { SystemInfo.isLinux -> { - val (distro, release) = getReleaseData() - val isUnderWsl = detectIsUnderWsl() - val glibcVersion = getGlibcVersion() - val linuxMetrics = mutableListOf>(DISTRO.with(distro), RELEASE.with(release), UNDER_WSL.with(isUnderWsl)) - if (glibcVersion != null) { - linuxMetrics.add(GLIBC.with(glibcVersion.toString())) + val distroInfo = UnixUtil.getOsInfo() + val linuxMetrics = mutableListOf>(DISTRO.with(DISTROS.coerce(distroInfo.distro)), + RELEASE.with(distroInfo.release), + UNDER_WSL.with(distroInfo.isUnderWsl)) + if (distroInfo.glibcVersion != null) { + linuxMetrics.add(GLIBC.with(distroInfo.glibcVersion.toString())) } metrics += LINUX.metric(*linuxMetrics.toTypedArray()) } @@ -98,32 +95,6 @@ internal class OsDataCollector : ApplicationUsagesCollector() { if (SystemInfo.isWindows) null else SHELLS.coerce(runCatching { System.getenv("SHELL")?.let { Path.of(it).name } }.getOrNull()) - // https://www.freedesktop.org/software/systemd/man/os-release.html - private fun getReleaseData(): Pair = - try { - Files.lines(Path.of("/etc/os-release")).use { lines -> - val fields = setOf("ID", "VERSION_ID") - val values = lines.asSequence() - .map { it.split('=') } - .filter { it.size == 2 && it[0] in fields } - .associate { it[0] to it[1].trim('"') } - val distro = DISTROS.coerce(values["ID"]) - distro to values["VERSION_ID"] - } - } - catch (ignored: IOException) { - "unknown" to null - } - - private fun detectIsUnderWsl(): Boolean = - try { - @Suppress("SpellCheckingInspection") val kernel = Files.readString(Path.of("/proc/sys/kernel/osrelease")) - kernel.contains("-microsoft-") - } - catch(e: IOException) { - false - } - private fun List.coerce(value: String?): String = when (value) { null -> "unknown" diff --git a/platform/platform-impl/src/com/intellij/ide/AboutPopupDescriptionProvider.kt b/platform/platform-impl/src/com/intellij/ide/AboutPopupDescriptionProvider.kt index cd57568d8293..db0e2d722b31 100644 --- a/platform/platform-impl/src/com/intellij/ide/AboutPopupDescriptionProvider.kt +++ b/platform/platform-impl/src/com/intellij/ide/AboutPopupDescriptionProvider.kt @@ -13,4 +13,9 @@ interface AboutPopupDescriptionProvider { * Return additional info which should be shown in the "About" dialog. */ fun getDescription(): @DetailedDescription String? + + /** + * Return additional info which should be copied into clipboard in the "About" dialog. + */ + fun getExtendedDescription(): @DetailedDescription String? = getDescription() } diff --git a/platform/platform-impl/src/com/intellij/ide/OsDataLogger.kt b/platform/platform-impl/src/com/intellij/ide/OsDataLogger.kt new file mode 100644 index 000000000000..73feda9d5faf --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/OsDataLogger.kt @@ -0,0 +1,52 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.util.NlsContexts.DetailedDescription +import com.intellij.openapi.util.SystemInfo +import com.intellij.util.UnixUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Service(Service.Level.APP) +private class OsDataLogger(val coroutineScope: CoroutineScope) { + internal var osInfoAboutString: String? = null + + fun reportLinuxDistro() { + coroutineScope.launch { + if (SystemInfo.isLinux || SystemInfo.isFreeBSD) { + val osInfo = UnixUtil.getOsInfo() + + val name = osInfo.prettyName + ?: (osInfo.distro ?: "Unknown Distro").appendNotBlank(" ", osInfo.release) + val info = name + .appendNotBlank(" ", if (osInfo.isUnderWsl) "(in WSL)" else null) + .appendNotBlank("; glibc: ", osInfo.glibcVersion?.toString()) + logger().info(info) + + osInfoAboutString = info + } + } + } + + private fun String.appendNotBlank(delimiter: String?, value: String?): String { + if (value.isNullOrBlank()) return this + return this + delimiter + value + } +} + +internal class OsDataLoggerApplicationInitializedListener : ApplicationInitializedListener { + override suspend fun execute() { + service().reportLinuxDistro() + } +} + +internal class OsDataLoggerAboutPopupDescriptionProvider : AboutPopupDescriptionProvider { + override fun getDescription(): @DetailedDescription String? = null + + override fun getExtendedDescription(): @DetailedDescription String? { + return service().osInfoAboutString + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/actions/AboutDialog.java b/platform/platform-impl/src/com/intellij/ide/actions/AboutDialog.java index 13f93fa9c5e8..6488b9d94373 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/AboutDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/AboutDialog.java @@ -194,8 +194,7 @@ public final class AboutDialog extends DialogWrapper { myInfo.add(MessageFormat.format("VM: {0} by {1}", vmVersion, vmVendor)); //Print extra information from plugins - ExtensionPointName ep = new ExtensionPointName<>("com.intellij.aboutPopupDescriptionProvider"); - for (AboutPopupDescriptionProvider aboutInfoProvider : ep.getExtensions()) { + for (AboutPopupDescriptionProvider aboutInfoProvider : EP_NAME.getExtensionList()) { String description = aboutInfoProvider.getDescription(); if (description != null) { lines.add(description); @@ -287,7 +286,7 @@ public final class AboutDialog extends DialogWrapper { text.append(SystemInfo.getOsNameAndVersion()).append('\n'); for (var aboutInfoProvider : EP_NAME.getExtensionList()) { - var description = aboutInfoProvider.getDescription(); + var description = aboutInfoProvider.getExtendedDescription(); if (description != null) { text.append(description).append('\n'); } diff --git a/platform/platform-impl/src/com/intellij/util/UnixUtil.kt b/platform/platform-impl/src/com/intellij/util/UnixUtil.kt index f9a649e3fcdf..b88e8793d319 100644 --- a/platform/platform-impl/src/com/intellij/util/UnixUtil.kt +++ b/platform/platform-impl/src/com/intellij/util/UnixUtil.kt @@ -1,11 +1,16 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.util +import com.intellij.execution.ExecutionException import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.util.ExecUtil import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.SystemInfo import org.jetbrains.annotations.ApiStatus +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import kotlin.streams.asSequence private const val COMMAND_LINE_TIMEOUT = 1000 private const val LIBC_LIBRARY_NAME = "LIBC" @@ -14,21 +19,75 @@ private const val LIBC_LIBRARY_NAME = "LIBC" object UnixUtil { private val LOG = Logger.getInstance(UnixUtil::class.java.name) + fun getOsInfo(): OsInfo { + val osRelease = getReleaseData() + val isUnderWsl = detectIsUnderWsl() + val glibcVersion = getGlibcVersion() + return OsInfo(osRelease.distro, osRelease.release, osRelease.prettyName, + isUnderWsl, glibcVersion) + } + + private fun detectIsUnderWsl(): Boolean { + try { + @Suppress("SpellCheckingInspection") val kernel = Files.readString(Path.of("/proc/sys/kernel/osrelease")) + return kernel.contains("-microsoft-") + } + catch (_: IOException) { + return false + } + } + + // https://www.freedesktop.org/software/systemd/man/os-release.html + private fun getReleaseData(): OsRelease { + try { + Files.lines(Path.of("/etc/os-release")).use { lines -> + val fields = setOf("ID", "VERSION_ID", "PRETTY_NAME") + val values = lines.asSequence() + .map { it.split('=') } + .filter { it.size == 2 && it[0] in fields } + .associate { it[0] to it[1].trim('"') } + return OsRelease(values["ID"], values["VERSION_ID"], values["PRETTY_NAME"]) + } + } + catch (_: IOException) { + return OsRelease(null, null, null) + } + } + @JvmStatic fun getGlibcVersion(): Double? { - check(SystemInfo.isLinux) { "glibc version is only supported on Linux" } - val commandLine = GeneralCommandLine("ldd", "--version") - val output = ExecUtil.execAndGetOutput(commandLine, COMMAND_LINE_TIMEOUT) - if (output.exitCode != 0) { - LOG.debug("Failed to execute ${commandLine.commandLineString} exit code ${output.exitCode}") + try { + check(SystemInfo.isLinux) { "glibc version is only supported on Linux" } + val commandLine = GeneralCommandLine("ldd", "--version") + val output = ExecUtil.execAndGetOutput(commandLine, COMMAND_LINE_TIMEOUT) + if (output.exitCode != 0) { + LOG.debug("Failed to execute ${commandLine.commandLineString} exit code ${output.exitCode}") + return null + } + val outputStr = output.stdout.split("\n").firstOrNull { it.contains(LIBC_LIBRARY_NAME, true) } + if (outputStr == null) { + LOG.debug("Failed to find $LIBC_LIBRARY_NAME in ${output.stdout}") + return null + } + val version = outputStr.split(" ").lastOrNull { it.isNotEmpty() } ?: return null + return version.toDoubleOrNull() + } + catch (_: ExecutionException) { return null } - val outputStr = output.stdout.split("\n").firstOrNull { it.contains(LIBC_LIBRARY_NAME, true) } - if (outputStr == null) { - LOG.debug("Failed to find $LIBC_LIBRARY_NAME in ${output.stdout}") - return null - } - val version = outputStr.split(" ").lastOrNull { it.isNotEmpty() } ?: return null - return version.toDoubleOrNull() } + + data class OsInfo( + val distro: String?, + val release: String?, + val prettyName: String?, + val isUnderWsl: Boolean, + val glibcVersion: Double?, + ) + + private data class OsRelease( + val distro: String?, + val release: String?, + val prettyName: String?, + ) } \ No newline at end of file diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index e334dc0239d5..c332b9cce6c6 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -1035,6 +1035,9 @@ + + +