From 1374e33f53737ecc36d228f10a3db68d9bf55311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Fri, 25 Oct 2024 15:22:20 +0200 Subject: [PATCH 01/12] Upgrade used IntelliJ platform for Android Studio plugin --- .gitignore | 1 + subprojects/studio-plugin/build.gradle.kts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3aebe34a..fae8b19a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +.intellijPlatform/ *.iml *.ipr *.iws diff --git a/subprojects/studio-plugin/build.gradle.kts b/subprojects/studio-plugin/build.gradle.kts index 7dccfd52..1a535c65 100644 --- a/subprojects/studio-plugin/build.gradle.kts +++ b/subprojects/studio-plugin/build.gradle.kts @@ -6,7 +6,7 @@ plugins { groovy `java-test-fixtures` id("profiler.allprojects") - id("org.jetbrains.intellij") version "1.17.2" + id("org.jetbrains.intellij") version "1.17.4" } description = "Contains logic for Android Studio plugin that communicates with profiler" @@ -37,7 +37,7 @@ project.configurations java { toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) + languageVersion.set(JavaLanguageVersion.of(17)) } } @@ -50,10 +50,10 @@ tasks.test { intellij { pluginName.set("gradle-profiler-studio-plugin") - version.set("2021.1.1") + version.set("2023.1.1") // Don't override "since-build" and "until-build" properties in plugin.xml, // so we don't need to update plugin to use it also on future IntelliJ versions. updateSinceUntilBuild.set(false) // Any plugin here must be also added to resources/META-INF/plugin.xml - plugins.set(listOf("java", "gradle", "android")) + plugins.set(listOf("java", "gradle", "org.jetbrains.android")) } From 6fc40c869d6c647dea6d22dd1398d5686050cf91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Fri, 25 Oct 2024 15:24:35 +0200 Subject: [PATCH 02/12] Make profiling work with Koala and Ladybug --- .../gradle/profiler/studio/StudioGradleClient.java | 2 ++ .../AbstractProfilerIntegrationTest.groovy | 3 ++- .../profiler/AndroidStudioIntegrationTest.groovy | 12 +++--------- .../studio/plugin/client/GradleProfilerClient.java | 14 +++++++++----- .../plugin/system/AndroidStudioSystemHelper.java | 6 +++--- .../studio/plugin/system/GradleSystemListener.java | 2 +- .../src/main/resources/META-INF/plugin.xml | 2 +- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/gradle/profiler/studio/StudioGradleClient.java b/src/main/java/org/gradle/profiler/studio/StudioGradleClient.java index 74e79924..5361c2dd 100644 --- a/src/main/java/org/gradle/profiler/studio/StudioGradleClient.java +++ b/src/main/java/org/gradle/profiler/studio/StudioGradleClient.java @@ -5,9 +5,11 @@ import org.gradle.profiler.InvocationSettings; import org.gradle.profiler.client.protocol.ServerConnection; import org.gradle.profiler.client.protocol.messages.*; +import org.gradle.profiler.gradle.DaemonControl; import org.gradle.profiler.instrument.GradleInstrumentation; import org.gradle.profiler.result.BuildActionResult; import org.gradle.profiler.studio.invoker.StudioBuildActionResult; +import org.gradle.profiler.studio.invoker.StudioGradleScenarioDefinition; import org.gradle.profiler.studio.invoker.StudioGradleScenarioDefinition.StudioGradleBuildConfiguration; import org.gradle.profiler.studio.process.StudioProcess.StudioConnections; import org.gradle.profiler.studio.process.StudioProcessController; diff --git a/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy index c0a74e17..3351fe95 100644 --- a/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy @@ -26,7 +26,8 @@ abstract class AbstractProfilerIntegrationTest extends AbstractIntegrationTest { "4.0", "4.7", "5.2.1", "5.6.3", "6.0.1", "6.1", "6.6.1", - "7.1.1", "7.5" + "7.1.1", "7.6.4", + "8.10.2" ]) @Shared diff --git a/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy index 3a9af45b..0e37f107 100644 --- a/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy @@ -46,7 +46,6 @@ class AndroidStudioIntegrationTest extends AbstractProfilerIntegrationTest { then: logFile.find("Gradle invocation 1 has completed in").size() == 5 - logFile.find("Gradle invocation 2 has completed in").size() == 0 logFile.find("Full sync has completed in").size() == 5 logFile.find("and it SUCCEEDED").size() == 5 logFile.find("* Cleaning Android Studio cache, this will require a restart...").size() == 0 @@ -73,22 +72,17 @@ class AndroidStudioIntegrationTest extends AbstractProfilerIntegrationTest { then: logFile.find("Gradle invocation 1 has completed in").size() == 2 - logFile.find("Gradle invocation 2 has completed in").size() == 2 - def firstDurations = (logFile.text =~ /Gradle invocation 1 has completed in: (\d+)ms/) + def duration = (logFile.text =~ /Gradle invocation 1 has completed in: (\d+)ms/) .findAll() .collect { it[1] as Integer } - def secondDurations = (logFile.text =~ /Gradle invocation 2 has completed in: (\d+)ms/) - .findAll() - .collect { it[1] as Integer } - logFile.find("Full Gradle execution time: ${firstDurations[0] + secondDurations[0]}ms").size() == 1 - logFile.find("Full Gradle execution time: ${firstDurations[1] + secondDurations[1]}ms").size() == 1 + logFile.find("Full Gradle execution time: ${duration[0]}ms").size() == 1 logFile.find("Full sync has completed in").size() == 2 logFile.find("and it SUCCEEDED").size() == 2 logFile.find("* Cleaning Android Studio cache, this will require a restart...").size() == 0 logFile.find("* Starting Android Studio").size() == 1 and: - resultFile.lines[3] == "value,total execution time,Gradle execution time #1,Gradle execution time #2,Gradle total execution time,IDE execution time" + resultFile.lines[3] == "value,total execution time,Gradle total execution time,IDE execution time" } def "benchmarks Android Studio sync by cleaning ide cache before build"() { diff --git a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/client/GradleProfilerClient.java b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/client/GradleProfilerClient.java index cbeae990..069188f0 100644 --- a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/client/GradleProfilerClient.java +++ b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/client/GradleProfilerClient.java @@ -5,14 +5,13 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtil; -import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import org.gradle.profiler.client.protocol.Client; import org.gradle.profiler.client.protocol.messages.StudioCacheCleanupCompleted; import org.gradle.profiler.client.protocol.messages.StudioRequest; import org.gradle.profiler.client.protocol.messages.StudioSyncRequestCompleted; -import org.gradle.profiler.studio.plugin.system.GradleSystemListener; import org.gradle.profiler.studio.plugin.system.GradleSyncResult; +import org.gradle.profiler.studio.plugin.system.GradleSystemListener; import org.jetbrains.plugins.gradle.service.project.open.GradleProjectImportUtil; import org.jetbrains.plugins.gradle.settings.GradleSettings; @@ -54,8 +53,8 @@ public StudioRequest listenForSyncRequests(Project project, GradleSystemListener private void maybeImportProject(Project project) { GradleSettings gradleSettings = GradleSettings.getInstance(project); if (gradleSettings.getLinkedProjectsSettings().isEmpty()) { - VirtualFile projectDir = ProjectUtil.guessProjectDir(project); // We disabled auto import with 'external.system.auto.import.disabled=true', so we need to link and refresh the project manually + VirtualFile projectDir = ProjectUtil.guessProjectDir(project); GradleProjectImportUtil.linkAndRefreshGradleProject(projectDir.getPath(), project); } } @@ -89,8 +88,13 @@ private void handleSyncRequest(StudioRequest request, Project project, GradleSys // In some cases sync could happen before we trigger it, // for example when we open a project for the first time. - waitOnPreviousGradleSyncFinish(project); - waitOnBackgroundProcessesFinish(project); + // Also in some versions sync triggers first and then indexing and in others it's the opposite, + // so we wait for sync finishing and then for background processes and then for sync again. + if (isStartup) { + waitOnPreviousGradleSyncFinish(project); + waitOnBackgroundProcessesFinish(project); + waitOnPreviousGradleSyncFinish(project); + } LOG.info(String.format("[SYNC REQUEST %s] Sync has started%n", request.getId())); Stopwatch stopwatch = isStartup ? startupStopwatch : Stopwatch.createStarted(); diff --git a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java index 1387e9c2..6ad78ac4 100644 --- a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java +++ b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java @@ -14,8 +14,8 @@ import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.util.messages.MessageBusConnection; import org.gradle.profiler.client.protocol.messages.StudioSyncRequestCompleted.StudioSyncRequestResult; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Supplier; @@ -35,7 +35,7 @@ public class AndroidStudioSystemHelper { */ public static GradleSyncResult getStartupSyncResult(Project project, GradleSystemListener gradleSystemListener) { ProjectSystemSyncManager.SyncResult lastSyncResult = ProjectSystemUtil.getSyncManager(project).getLastSyncResult(); - if (lastSyncResult == UNKNOWN) { + if (lastSyncResult == UNKNOWN || lastSyncResult == SKIPPED) { // Sync was not run before, we need to run it manually return startManualSync(project, gradleSystemListener); } @@ -78,7 +78,7 @@ public static void waitOnPreviousGradleSyncFinish(Project project) { if (ProjectSystemUtil.getSyncManager(project).isSyncInProgress()) { MessageBusConnection connection = project.getMessageBus().connect(); CompletableFuture future = new CompletableFuture<>(); - connection.subscribe(PROJECT_SYSTEM_SYNC_TOPIC, syncResult -> future.complete(null)); + connection.subscribe(PROJECT_SYSTEM_SYNC_TOPIC, (ProjectSystemSyncManager.SyncResultListener) syncResult -> future.complete(null)); future.join(); } } diff --git a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/GradleSystemListener.java b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/GradleSystemListener.java index bedf7fc6..425f20ef 100644 --- a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/GradleSystemListener.java +++ b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/GradleSystemListener.java @@ -3,9 +3,9 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.util.GradleConstants; -import javax.annotation.Nullable; import java.util.concurrent.atomic.AtomicReference; public class GradleSystemListener extends ExternalSystemTaskNotificationListenerAdapter { diff --git a/subprojects/studio-plugin/src/main/resources/META-INF/plugin.xml b/subprojects/studio-plugin/src/main/resources/META-INF/plugin.xml index 8a816adf..2f28bc69 100644 --- a/subprojects/studio-plugin/src/main/resources/META-INF/plugin.xml +++ b/subprojects/studio-plugin/src/main/resources/META-INF/plugin.xml @@ -7,7 +7,7 @@ >Runs IntelliJ/Android Studio functionalities with Gradle profiler - + From d8bb6abe0d96713e3afca6d762ed41209ea06c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Fri, 25 Oct 2024 21:02:51 +0200 Subject: [PATCH 03/12] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c491a63c..840ec5bc 100644 --- a/README.md +++ b/README.md @@ -274,9 +274,8 @@ Here is an example: androidStudioSync { title = "Android Studio Sync" # Measure an Android studio sync - # Note: Android Studio Bumblebee (2021.1.1) or newer is required - # Note2: If you are testing with Android Studio Giraffe (2022.3) or later - # you need to have local.properties file in your project with sdk.dir set + # Note: Android Studio Hedgehog (2023.1.1) or newer is required + # Note2: you need to have local.properties file in your project with sdk.dir set android-studio-sync { # Override default Android Studio jvm args # studio-jvm-args = ["-Xms256m", "-Xmx4096m"] From cdae01a94ddb80ab730a1147ecaf7c9916078421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Fri, 25 Oct 2024 21:42:29 +0200 Subject: [PATCH 04/12] Upgrade tested Android Studio to Ladybug --- .../profiler.android-studio-setup.gradle.kts | 48 +++++------ .../kotlin/tasks/ExtractAndroidStudioTask.kt | 85 +++++++++++++++++++ gradle/libs.versions.toml | 4 +- .../AndroidStudioIntegrationTest.groovy | 4 +- 4 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 buildSrc/src/main/kotlin/tasks/ExtractAndroidStudioTask.kt diff --git a/buildSrc/src/main/kotlin/profiler.android-studio-setup.gradle.kts b/buildSrc/src/main/kotlin/profiler.android-studio-setup.gradle.kts index b0153fe8..3cb963e3 100644 --- a/buildSrc/src/main/kotlin/profiler.android-studio-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/profiler.android-studio-setup.gradle.kts @@ -2,17 +2,23 @@ import extensions.AndroidStudioTestExtension import tasks.InstallAndroidSdkTask import providers.AndroidStudioInstallation import providers.AndroidStudioSystemProperties +import tasks.ExtractAndroidStudioTask repositories { - ivy { - // Url of Android Studio archive - url = uri("https://redirector.gvt1.com/edgedl/android/studio/ide-zips") - patternLayout { - artifact("[revision]/[artifact]-[revision]-[ext]") - } - metadataSources { artifact() } - content { - includeGroup("android-studio") + listOf( + // Urls of Android Studio archive + "https://redirector.gvt1.com/edgedl/android/studio/ide-zips", + "https://redirector.gvt1.com/edgedl/android/studio/install" + ).forEach { + ivy { + url = uri(it) + patternLayout { + artifact("[revision]/[artifact]-[revision]-[ext]") + } + metadataSources { artifact() } + content { + includeGroup("android-studio") + } } } } @@ -35,29 +41,17 @@ val androidStudioRuntime by configurations.creating dependencies { val fileExtension = when { isWindows() -> "windows.zip" - isMacOS() && isIntel() -> "mac.zip" - isMacOS() && !isIntel() -> "mac_arm.zip" + isMacOS() && isIntel() -> "mac.dmg" + isMacOS() && !isIntel() -> "mac_arm.dmg" isLinux() -> "linux.tar.gz" else -> throw IllegalStateException("Unsupported OS: $os") } androidStudioRuntime(extension.testAndroidStudioVersion.map { version -> "android-studio:android-studio:$version@$fileExtension" }) } -val unpackAndroidStudio = tasks.register("unpackAndroidStudio") { - from(Callable { - val singleFile = androidStudioRuntime.singleFile - when { - singleFile.name.endsWith(".tar.gz") -> tarTree(singleFile) - else -> zipTree(singleFile) - } - }) { - eachFile { - // Remove top folder when unzipping, that way we get rid of Android Studio.app folder that can cause issues on Mac - // where MacOS would kill the Android Studio process right after start, issue: https://github.com/gradle/gradle-profiler/issues/469 - relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray()) - } - } - into(layout.buildDirectory.dir("android-studio")) +val unpackAndroidStudio = tasks.register("unpackAndroidStudio") { + androidStudioLocation = androidStudioRuntime + outputDir = layout.buildDirectory.dir("android-studio") } val installAndroidSdk = tasks.register("installAndroidSdk") { @@ -68,7 +62,7 @@ val installAndroidSdk = tasks.register("installAndroidSdk } val androidStudioInstallation = objects.newInstance().apply { - studioInstallLocation.fileProvider(unpackAndroidStudio.map { it.destinationDir }) + studioInstallLocation = unpackAndroidStudio.flatMap { it.outputDir } } tasks.withType().configureEach { diff --git a/buildSrc/src/main/kotlin/tasks/ExtractAndroidStudioTask.kt b/buildSrc/src/main/kotlin/tasks/ExtractAndroidStudioTask.kt new file mode 100644 index 00000000..e13382b1 --- /dev/null +++ b/buildSrc/src/main/kotlin/tasks/ExtractAndroidStudioTask.kt @@ -0,0 +1,85 @@ +package tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.RelativePath +import org.gradle.api.internal.file.FileOperations +import org.gradle.api.tasks.* +import org.gradle.process.ExecOperations +import org.gradle.work.DisableCachingByDefault +import java.io.File +import javax.inject.Inject + + +@DisableCachingByDefault(because = "Not worth caching") +abstract class ExtractAndroidStudioTask @Inject constructor( + private val execOps: ExecOperations, + private val fsOps: FileSystemOperations, + private val fileOps: FileOperations +) : DefaultTask() { + + companion object { + private const val VOLUME_NAME = "AndroidStudioForGradle" + } + + @get:InputFiles + @get:PathSensitive(PathSensitivity.NONE) + abstract val androidStudioLocation: ConfigurableFileCollection + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @TaskAction + fun extract() { + val androidStudioDistribution = androidStudioLocation.singleFile + when { + androidStudioDistribution.name.endsWith(".dmg") -> extractDmg(androidStudioDistribution) + else -> extractZipOrTar(androidStudioDistribution) + } + } + + fun extractZipOrTar(androidStudioDistribution: File) { + fsOps.copy { + val src = when { + androidStudioDistribution.name.endsWith(".tar.gz") -> fileOps.tarTree(androidStudioDistribution) + else -> fileOps.zipTree(androidStudioDistribution) + } + + from(src) { + eachFile { + // Remove top folder when unzipping, that way we get rid of Android Studio.app folder that can cause issues on Mac + // where MacOS would kill the Android Studio process right after start, issue: https://github.com/gradle/gradle-profiler/issues/469 + @Suppress("SpreadOperator") + relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray()) + } + } + + into(outputDir) + } + } + + fun extractDmg(androidStudioDistribution: File) { + val volumeDir = "/Volumes/$VOLUME_NAME" + val srcDir = "/Volumes/$VOLUME_NAME/Android Studio.app" + require(!File(srcDir).exists()) { + "The directory $srcDir already exists. Please unmount it via `hdiutil detach $volumeDir`." + } + + try { + execOps.exec { + commandLine("hdiutil", "attach", androidStudioDistribution.absolutePath, "-mountpoint", volumeDir) + } + + outputDir.get().asFile.mkdirs() + execOps.exec { + commandLine("cp", "-r", "$volumeDir/Android Studio.app/Contents", outputDir.get().asFile.absolutePath) + } + } finally { + execOps.exec { + commandLine("hdiutil", "detach", volumeDir) + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bb8f54f7..d5a204dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] groovy = "3.0.15" spock = "2.1-groovy-3.0" -# Iguana (2023.2.1.7) Canary 7 -testAndroidStudioVersion = "2023.2.1.7" +# Ladybug Patch 1 (2024.2.1.10) +testAndroidStudioVersion = "2024.2.1.10" testAndroidSdkVersion = "7.3.0" [libraries] diff --git a/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy index 0e37f107..b0761ba9 100644 --- a/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/AndroidStudioIntegrationTest.groovy @@ -13,8 +13,8 @@ import spock.lang.Requires import static org.gradle.profiler.studio.AndroidStudioTestSupport.setupLocalProperties /** - * If running this tests with Android Studio Giraffe (2022.3) or later you need ANDROID_HOME or ANDROID_SDK_ROOT set or - * having Android sdk installed in /Library/Android/sdk (e.g. on Mac /Users//Library/Android/sdk) + * You need ANDROID_HOME or ANDROID_SDK_ROOT set or + * Android sdk installed in /Library/Android/sdk (e.g. on Mac /Users//Library/Android/sdk) */ @ShowAndroidStudioLogsOnFailure @Requires({ StudioFinder.findStudioHome() }) From e900334f524da291e3f256e01da718f06fb439d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Mon, 28 Oct 2024 14:33:15 +0100 Subject: [PATCH 05/12] Add Java17 to java installations paths --- .teamcity/settings/extensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.teamcity/settings/extensions.kt b/.teamcity/settings/extensions.kt index 92aaf4ea..6cd20c0a 100644 --- a/.teamcity/settings/extensions.kt +++ b/.teamcity/settings/extensions.kt @@ -17,7 +17,7 @@ fun BuildType.agentRequirement(os: Os, arch: Arch = Arch.AMD64) { fun toolchainConfiguration(os: Os) = listOf( "-Porg.gradle.java.installations.auto-detect=false", "-Porg.gradle.java.installations.auto-download=false", - """"-Porg.gradle.java.installations.paths=%${os.name}.java8.oracle.64bit%,%${os.name}.java11.openjdk.64bit%"""" + """"-Porg.gradle.java.installations.paths=%${os.name}.java8.oracle.64bit%,%${os.name}.java11.openjdk.64bit%,%${os.name}.java17.openjdk.64bit%"""" ).joinToString(" ") fun ParametrizedWithType.javaHome(os: Os, javaVersion: JavaVersion) { From 3f699ab3ae0ffb0a93ce8576232360618595646f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Mon, 28 Oct 2024 14:42:57 +0100 Subject: [PATCH 06/12] Set heap size for Gradle daemon --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index 11a80947..ef6e0c24 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ profiler.version=0.22.0-SNAPSHOT +org.gradle.jvmargs=-Xmx3100m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.caching=true org.gradle.parallel=true org.gradle.unsafe.configuration-cache=false From b382fe12241833cc86f66e2c670b272a92f7074b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Mon, 28 Oct 2024 19:02:20 +0100 Subject: [PATCH 07/12] Fix ChromeTraceIntegrationTest since Android Studio Ladybug syncs main build and buildSrc in one invocation --- .../org/gradle/profiler/ChromeTraceIntegrationTest.groovy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/groovy/org/gradle/profiler/ChromeTraceIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/ChromeTraceIntegrationTest.groovy index 5e28dfbd..d9d68145 100644 --- a/src/test/groovy/org/gradle/profiler/ChromeTraceIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/ChromeTraceIntegrationTest.groovy @@ -80,7 +80,7 @@ class ChromeTraceIntegrationTest extends AbstractProfilerIntegrationTest { @ShowAndroidStudioLogsOnFailure @Requires({ StudioFinder.findStudioHome() }) @Requires({ AndroidStudioTestSupport.findAndroidSdkPath() }) - def "profiles build to produce chrome trace output for builds with multiple gradle invocations"() { + def "profiles Android Studio build to produce chrome trace output for builds"() { given: def studioHome = StudioFinder.findStudioHome() setupLocalProperties(new File(projectDir, "local.properties")) @@ -104,8 +104,6 @@ class ChromeTraceIntegrationTest extends AbstractProfilerIntegrationTest { then: new File(outputDir, "scenario-${latestSupportedGradleVersion}-trace/scenario-${latestSupportedGradleVersion}-warm-up-build-1-invocation-1-trace.json").isFile() - new File(outputDir, "scenario-${latestSupportedGradleVersion}-trace/scenario-${latestSupportedGradleVersion}-warm-up-build-1-invocation-2-trace.json").isFile() new File(outputDir, "scenario-${latestSupportedGradleVersion}-trace/scenario-${latestSupportedGradleVersion}-measured-build-1-invocation-1-trace.json").isFile() - new File(outputDir, "scenario-${latestSupportedGradleVersion}-trace/scenario-${latestSupportedGradleVersion}-measured-build-1-invocation-2-trace.json").isFile() } } From be204df89bba482d9dd4821147bb4f60e8e85368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Mon, 28 Oct 2024 19:06:58 +0100 Subject: [PATCH 08/12] Adjust tested 6.x versions We probably don't need to test that many versions. --- .../org/gradle/profiler/AbstractProfilerIntegrationTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy index 3351fe95..0968cb09 100644 --- a/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy @@ -25,7 +25,7 @@ abstract class AbstractProfilerIntegrationTest extends AbstractIntegrationTest { "3.3", "3.5", "4.0", "4.7", "5.2.1", "5.6.3", - "6.0.1", "6.1", "6.6.1", + "6.0.1", "6.9.4", "7.1.1", "7.6.4", "8.10.2" ]) From 5a47a9967a22138e6f78cba733623d86bd2585d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Tue, 29 Oct 2024 12:06:54 +0100 Subject: [PATCH 09/12] Use -Xmx4g for gradle-profiler daemon We don't need to be so specific with -Xmx3100m. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ef6e0c24..1fa30a9b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ profiler.version=0.22.0-SNAPSHOT -org.gradle.jvmargs=-Xmx3100m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.caching=true org.gradle.parallel=true org.gradle.unsafe.configuration-cache=false From a52bd909d7280922021fb065884682229a6cc45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Tue, 29 Oct 2024 12:08:46 +0100 Subject: [PATCH 10/12] Reduce number of tested Gradle versions --- .../profiler/AbstractProfilerIntegrationTest.groovy | 10 ++++------ ...BuildOperationInstrumentationIntegrationTest.groovy | 4 ++-- .../org/gradle/profiler/ProfilerIntegrationTest.groovy | 5 +++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy index 0968cb09..2e170849 100644 --- a/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/AbstractProfilerIntegrationTest.groovy @@ -22,12 +22,10 @@ abstract class AbstractProfilerIntegrationTest extends AbstractIntegrationTest { @Shared List supportedGradleVersions = gradleVersionsSupportedOnCurrentJvm([ - "3.3", "3.5", - "4.0", "4.7", - "5.2.1", "5.6.3", - "6.0.1", "6.9.4", - "7.1.1", "7.6.4", - "8.10.2" + "5.6.4", + "6.9.4", + "7.6.4", + "8.0.2", "8.10.2" ]) @Shared diff --git a/src/test/groovy/org/gradle/profiler/BuildOperationInstrumentationIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/BuildOperationInstrumentationIntegrationTest.groovy index ecfcb2df..fe26bff5 100644 --- a/src/test/groovy/org/gradle/profiler/BuildOperationInstrumentationIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/BuildOperationInstrumentationIntegrationTest.groovy @@ -386,13 +386,13 @@ class BuildOperationInstrumentationIntegrationTest extends AbstractProfilerInteg output.contains("Scenario using Gradle ${gradleVersion}: Measuring build configuration is only supported for Gradle 6.1-milestone-3 and later") where: - gradleVersion << gradleVersionsSupportedOnCurrentJvm([minimalSupportedGradleVersion, "4.0", "4.10", "6.0"]) + gradleVersion << gradleVersionsSupportedOnCurrentJvm([minimalSupportedGradleVersion, "6.0"]) } def "complains when attempting to benchmark configuration time for build using unsupported Gradle version from scenario file"() { given: instrumentedBuildScript() - def unsupportedGradleVersions = gradleVersionsSupportedOnCurrentJvm(["${minimalSupportedGradleVersion}", "4.0", "4.10", "6.0"]) + def unsupportedGradleVersions = gradleVersionsSupportedOnCurrentJvm(["${minimalSupportedGradleVersion}", "6.0"]) def scenarioFile = file("performance.scenarios") scenarioFile.text = """ assemble { diff --git a/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy b/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy index d23580c6..6223a562 100644 --- a/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy +++ b/src/test/groovy/org/gradle/profiler/ProfilerIntegrationTest.groovy @@ -367,11 +367,12 @@ class ProfilerIntegrationTest extends AbstractProfilerIntegrationTest { println "" println "" """ + def buildScanVersion = "2.2.1" when: new Main(). run("--project-dir", projectDir.absolutePath, "--output-dir", outputDir.absolutePath, "--gradle-version", minimalSupportedGradleVersion, - "--profile", "buildscan", "--buildscan-version", "1.2", + "--profile", "buildscan", "--buildscan-version", buildScanVersion, "--profile", "jfr", "assemble") @@ -380,7 +381,7 @@ class ProfilerIntegrationTest extends AbstractProfilerIntegrationTest { logFile.find("").size() == 4 logFile.find("").size() == 3 - assertBuildScanPublished("1.2") + assertBuildScanPublished(buildScanVersion) def profileFile = new File(outputDir, "${minimalSupportedGradleVersion}.jfr") profileFile.isFile() From 72aa9f458a21bce1553e2dca185fc82688d2bc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Tue, 29 Oct 2024 16:48:12 +0100 Subject: [PATCH 11/12] Try to make startup sync more resilient to already started syncs --- .../studio/plugin/system/AndroidStudioSystemHelper.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java index 6ad78ac4..b0dc39eb 100644 --- a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java +++ b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/system/AndroidStudioSystemHelper.java @@ -37,9 +37,14 @@ public static GradleSyncResult getStartupSyncResult(Project project, GradleSyste ProjectSystemSyncManager.SyncResult lastSyncResult = ProjectSystemUtil.getSyncManager(project).getLastSyncResult(); if (lastSyncResult == UNKNOWN || lastSyncResult == SKIPPED) { // Sync was not run before, we need to run it manually - return startManualSync(project, gradleSystemListener); + GradleSyncResult result = startManualSync(project, gradleSystemListener); + if (result.getResult() == StudioSyncRequestResult.FAILED) { + // If it fails, it might fail because another sync just started a millisecond before we could start it + waitOnPreviousGradleSyncFinish(project); + waitOnBackgroundProcessesFinish(project); + } } - return convertToGradleSyncResult(lastSyncResult, gradleSystemListener.getLastException()); + return convertToGradleSyncResult(ProjectSystemUtil.getSyncManager(project).getLastSyncResult(), gradleSystemListener.getLastException()); } /** From 109ecd4cb4623e518febc187567e6547abb1e7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Sodja?= Date: Tue, 29 Oct 2024 18:24:51 +0100 Subject: [PATCH 12/12] Rewrite GradleProfilerStartupActivity to Kotlin So we can use new ProjectActivity interface. --- .gitignore | 1 + subprojects/studio-plugin/build.gradle.kts | 1 + .../plugin/GradleProfilerStartupActivity.java | 108 ------------------ .../plugin/GradleProfilerStartupActivity.kt | 95 +++++++++++++++ 4 files changed, 97 insertions(+), 108 deletions(-) delete mode 100644 subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.java create mode 100644 subprojects/studio-plugin/src/main/kotlin/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.kt diff --git a/.gitignore b/.gitignore index fae8b19a..36dd27a7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ **/.settings/ .gradle/ +.kotlin/ .DS_Store diff --git a/subprojects/studio-plugin/build.gradle.kts b/subprojects/studio-plugin/build.gradle.kts index 1a535c65..a1844133 100644 --- a/subprojects/studio-plugin/build.gradle.kts +++ b/subprojects/studio-plugin/build.gradle.kts @@ -7,6 +7,7 @@ plugins { `java-test-fixtures` id("profiler.allprojects") id("org.jetbrains.intellij") version "1.17.4" + id("org.jetbrains.kotlin.jvm") version "2.0.21" } description = "Contains logic for Android Studio plugin that communicates with profiler" diff --git a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.java b/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.java deleted file mode 100644 index 9c36b314..00000000 --- a/subprojects/studio-plugin/src/main/java/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.gradle.profiler.studio.plugin; - -import com.android.tools.idea.gradle.project.GradleProjectInfo; -import com.intellij.ide.impl.TrustedProjects; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupActivity; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.util.registry.RegistryValue; -import org.gradle.profiler.client.protocol.Client; -import org.gradle.profiler.client.protocol.messages.StudioRequest; -import org.gradle.profiler.studio.plugin.client.GradleProfilerClient; -import org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper; -import org.gradle.profiler.studio.plugin.system.GradleSystemListener; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.gradle.settings.GradleProjectSettings; -import org.jetbrains.plugins.gradle.settings.GradleSettings; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.Collection; -import java.util.List; -import java.util.Properties; -import java.util.stream.Collectors; - -import static org.gradle.profiler.client.protocol.messages.StudioRequest.StudioRequestType.EXIT_IDE; - -public class GradleProfilerStartupActivity implements StartupActivity.DumbAware { - - private static final Logger LOG = Logger.getInstance(GradleProfilerStartupActivity.class); - - public static final String PROFILER_PORT_PROPERTY = "gradle.profiler.port"; - - @Override - public void runActivity(@NotNull Project project) { - LOG.info("Project opened"); - logModifiedRegistryEntries(); - if (System.getProperty(PROFILER_PORT_PROPERTY) != null) { - // This solves the issue where Android Studio would run the Gradle sync automatically on the first import. - // Unfortunately it seems we can't always detect it because it happens very late and due to that there might - // a case where two simultaneous syncs would be run: one from automatic sync trigger and one from our trigger. - // With this line we disable that automatic sync, but we still trigger our sync later in the code. - GradleProjectInfo.getInstance(project).setSkipStartupActivity(true); - // If we don't disable external annotations, Android Studio will download some artifacts - // to .m2 folder if some project has for example com.fasterxml.jackson.core:jackson-core as a dependency - disableDownloadOfExternalAnnotations(project); - // Register system listener already here, so we can catch any failure for syncs that are automatically started - GradleSystemListener gradleSystemListener = registerGradleSystemListener(); - TrustedProjects.setTrusted(project, true); - ApplicationManager.getApplication().executeOnPooledThread(() -> { - StudioRequest lastRequest = listenForSyncRequests(project, gradleSystemListener); - if (lastRequest.getType() == EXIT_IDE) { - AndroidStudioSystemHelper.exit(project); - } - }); - } - } - - private void logModifiedRegistryEntries() { - String studioPropertiesPath = System.getenv("STUDIO_PROPERTIES"); - if (studioPropertiesPath == null || !new File(studioPropertiesPath).exists()) { - return; - } - try { - Properties properties = new Properties(); - properties.load(new FileInputStream(studioPropertiesPath)); - List modifiedValues = Registry.getAll().stream() - .filter(value -> properties.containsKey(value.getKey())) - .map(RegistryValue::toString) - .sorted() - .collect(Collectors.toList()); - LOG.info("Modified registry entries: " + modifiedValues); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private void disableDownloadOfExternalAnnotations(Project project) { - GradleSettings gradleSettings = GradleSettings.getInstance(project); - gradleSettings.getLinkedProjectsSettings() - .forEach(settings -> settings.setResolveExternalAnnotations(false)); - gradleSettings.subscribe(new DefaultGradleSettingsListener() { - @Override - public void onProjectsLinked(@NotNull Collection linkedProjectsSettings) { - linkedProjectsSettings.forEach(settings -> settings.setResolveExternalAnnotations(false)); - } - }, gradleSettings); - } - - private GradleSystemListener registerGradleSystemListener() { - GradleSystemListener gradleSystemListener = new GradleSystemListener(); - ExternalSystemProgressNotificationManager.getInstance().addNotificationListener(gradleSystemListener); - return gradleSystemListener; - } - - private StudioRequest listenForSyncRequests(@NotNull Project project, @NotNull GradleSystemListener gradleStartupListener) { - int port = Integer.getInteger(PROFILER_PORT_PROPERTY); - try (Client client = new Client(port)) { - return new GradleProfilerClient(client).listenForSyncRequests(project, gradleStartupListener); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/subprojects/studio-plugin/src/main/kotlin/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.kt b/subprojects/studio-plugin/src/main/kotlin/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.kt new file mode 100644 index 00000000..40f09af7 --- /dev/null +++ b/subprojects/studio-plugin/src/main/kotlin/org/gradle/profiler/studio/plugin/GradleProfilerStartupActivity.kt @@ -0,0 +1,95 @@ +package org.gradle.profiler.studio.plugin + +import com.android.tools.idea.gradle.project.GradleProjectInfo +import com.intellij.ide.impl.setTrusted +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.util.registry.RegistryValue +import org.gradle.profiler.client.protocol.Client +import org.gradle.profiler.client.protocol.messages.StudioRequest +import org.gradle.profiler.client.protocol.messages.StudioRequest.StudioRequestType +import org.gradle.profiler.studio.plugin.client.GradleProfilerClient +import org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper +import org.gradle.profiler.studio.plugin.system.GradleSystemListener +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.settings.GradleSettings +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.UncheckedIOException +import java.util.* +import java.util.function.Consumer +import java.util.stream.Collectors + +class GradleProfilerStartupActivity : ProjectActivity { + companion object { + private val LOG = Logger.getInstance(GradleProfilerStartupActivity::class.java) + const val PROFILER_PORT_PROPERTY: String = "gradle.profiler.port" + } + + override suspend fun execute(project: Project) { + LOG.info("Project opened") + logModifiedRegistryEntries() + if (System.getProperty(PROFILER_PORT_PROPERTY) != null) { + // This solves the issue where Android Studio would run the Gradle sync automatically on the first import. + // Unfortunately it seems we can't always detect it because it happens very late and due to that there might + // a case where two simultaneous syncs would be run: one from automatic sync trigger and one from our trigger. + // With this line we disable that automatic sync, but we still trigger our sync later in the code. + GradleProjectInfo.getInstance(project).isSkipStartupActivity = true + // If we don't disable external annotations, Android Studio will download some artifacts + // to .m2 folder if some project has for example com.fasterxml.jackson.core:jackson-core as a dependency + disableDownloadOfExternalAnnotations(project) + // Register system listener already here, so we can catch any failure for syncs that are automatically started + val gradleSystemListener = GradleSystemListener().apply { + ExternalSystemProgressNotificationManager.getInstance().addNotificationListener(this) + } + project.setTrusted(true) + + ApplicationManager.getApplication().executeOnPooledThread { + val lastRequest = listenForSyncRequests(project, gradleSystemListener) + if (lastRequest.type == StudioRequestType.EXIT_IDE) { + AndroidStudioSystemHelper.exit(project) + } + } + } + } + + private fun logModifiedRegistryEntries() { + val studioPropertiesPath = System.getenv("STUDIO_PROPERTIES") + if (studioPropertiesPath == null || !File(studioPropertiesPath).exists()) { + return + } + + val properties = Properties().apply { this.load(FileInputStream(studioPropertiesPath)) } + val modifiedValues = Registry.getAll() + .filter { value: RegistryValue -> properties.containsKey(value.key) } + .map { obj: RegistryValue -> obj.toString() } + .sorted() + LOG.info("Modified registry entries: $modifiedValues") + } + + private fun disableDownloadOfExternalAnnotations(project: Project) { + val gradleSettings = GradleSettings.getInstance(project) + gradleSettings.linkedProjectsSettings.forEach { + it.isResolveExternalAnnotations = false + } + gradleSettings.subscribe(object : DefaultGradleSettingsListener() { + override fun onProjectsLinked(linkedProjectsSettings: Collection) { + linkedProjectsSettings.forEach { + it.isResolveExternalAnnotations = false + } + } + }, gradleSettings) + } + + private fun listenForSyncRequests(project: Project, gradleStartupListener: GradleSystemListener): StudioRequest { + val port = Integer.getInteger(PROFILER_PORT_PROPERTY) + Client(port).use { + return GradleProfilerClient(it).listenForSyncRequests(project, gradleStartupListener) + } + } +}