diff options
author | bingran <bingran@google.com> | 2023-04-11 15:12:12 -0700 |
---|---|---|
committer | Bingran Li <bingran@google.com> | 2023-05-23 20:54:25 +0000 |
commit | 23721bebb6b0c25c81d7513585f20ca1283c366e (patch) | |
tree | 66b0bddc1671ca1472de6cfa88a6769c23acd214 | |
parent | bf7cd87a2e5c0a2017ec6b2c3a3d835a1c571bd5 (diff) | |
download | base-23721bebb6b0c25c81d7513585f20ca1283c366e.tar.gz |
Fix file creation issue during configuration time
This CL uses ValueSource to help us create file or directory during
configuration time without causing configuration cache miss.
Bug: 278767328
Test: existing
Change-Id: I9066834a315171f0dc424a5a66009e785c898ba2
6 files changed, 125 insertions, 20 deletions
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/AndroidLocationsBuildService.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/AndroidLocationsBuildService.kt index f62bba6286..16444781a0 100644 --- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/AndroidLocationsBuildService.kt +++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/AndroidLocationsBuildService.kt @@ -24,12 +24,15 @@ import com.android.prefs.AbstractAndroidLocations import com.android.prefs.AndroidLocationsProvider import com.android.utils.ILogger import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.Logging import org.gradle.api.provider.ProviderFactory import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters +import java.io.File import java.nio.file.Path import javax.inject.Inject +import kotlin.io.path.absolutePathString /** * A build service around [AndroidLocations] in order to make this basically a singleton @@ -63,10 +66,30 @@ abstract class AndroidLocationsBuildService @Inject constructor( // nothing to be done here } + /** + * Use [ConfigPhaseFileCreator] to create android folder during configuration phase to avoid + * configuration cache miss. + */ private val androidLocations = AndroidLocations( EnvironmentProviderImpl(GradleEnvironmentProviderImpl(providerFactory)), LoggerWrapper(Logging.getLogger("AndroidLocations")) - ) + ).also { androidLocations -> + providerFactory.of(AndroidDirectoryCreator::class.java) { + it.parameters.androidDir.set(File(androidLocations.computeAndroidFolder().absolutePathString())) + }.get() + } + + abstract class AndroidDirectoryCreator : + ConfigPhaseFileCreator<String, AndroidDirectoryCreator.Params> { + interface Params : ConfigPhaseFileCreator.Params { + val androidDir: DirectoryProperty + } + + override fun obtain(): String { + parameters.androidDir.get().asFile.mkdirs() + return IGNORE_FILE_CREATION + } + } class RegistrationAction( project: Project diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/ConfigPhaseFileCreator.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/ConfigPhaseFileCreator.kt new file mode 100644 index 0000000000..d3d419c147 --- /dev/null +++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/ConfigPhaseFileCreator.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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 com.android.build.gradle.internal.services + +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters + +/** + * During configuration time, creating a file or directory could lead to configuration cache miss + * in the following build because Gradle inspects the return value of some File operations(e.g. + * [File.exists]) between builds and treats them as configuration input. For example, if a file is + * created in the first run and it already exists before the second run, calling [File.exists] to + * that file at configuration time would give us different value which means configuration cache + * is not reusable from Gradle point of view. + * + * There are some cases where we have to create a file or directory during configuration time and + * we know whether that file/directory exist or not doesn't affect configuration. In such cases, + * this class helps us create a file or directory during configuration time without causing + * configuration cache miss. + * + * To create a file/directory during configuration time: create them in [obtain] function using the + * path declared in [Params] instead of computing the path directly. The return value of [obtain] + * function needs to be same between different runs unless you want to config cache to be invalided. + * It can be a constant like [IGNORE_FILE_CREATION] if you don't need this function to return + * anything(Note [Unit] won't work). It can also be a [File] or other types. + * + */ +interface ConfigPhaseFileCreator<T, P : ConfigPhaseFileCreator.Params> : ValueSource<T, P> { + + interface Params : ValueSourceParameters +} + +const val IGNORE_FILE_CREATION = "" diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/FakeDependencyJarBuildService.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/FakeDependencyJarBuildService.kt index b1c2b6df5f..9f71759f75 100644 --- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/FakeDependencyJarBuildService.kt +++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/services/FakeDependencyJarBuildService.kt @@ -17,12 +17,15 @@ package com.android.build.gradle.internal.services import com.android.builder.packaging.JarFlinger -import com.android.builder.utils.SynchronizedFile import org.gradle.api.Project +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters import java.io.File +import javax.inject.Inject private const val FAKE_DEPENDENCY_JAR = "FakeDependency.jar" private const val ANDROID_SUBDIR = "android" @@ -34,16 +37,35 @@ abstract class FakeDependencyJarBuildService : BuildService<FakeDependencyJarBui val gradleUserHome: Property<File> } - val lazyCachedFakeJar: File by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { - SynchronizedFile.getInstanceWithMultiProcessLocking( - parameters.gradleUserHome.get().resolve(ANDROID_SUBDIR) - ).write { - val fakeJar = it.resolve(FAKE_DEPENDENCY_JAR) - if (!fakeJar.exists()) { - fakeJar.parentFile.mkdirs() - JarFlinger(fakeJar.toPath()).use {} + @get:Inject + abstract val providerFactory: ProviderFactory + + @get:Inject + abstract val objectFactory: ObjectFactory + + /** + * Use [ConfigPhaseFileCreator] to create fake dependency jar during configuration phase to + * avoid configuration cache miss. + */ + val lazyCachedFakeJar: File = providerFactory.of(FakeDependencyJarCreator::class.java) { + it.parameters.fakeDependencyJar.set( + parameters.gradleUserHome.get().resolve(ANDROID_SUBDIR).resolve(FAKE_DEPENDENCY_JAR) + ) + }.get() + + abstract class FakeDependencyJarCreator : + ConfigPhaseFileCreator<File, FakeDependencyJarCreator.Params> { + interface Params: ConfigPhaseFileCreator.Params { + val fakeDependencyJar: RegularFileProperty + } + + override fun obtain(): File { + val fakeDependencyJar = parameters.fakeDependencyJar.get().asFile + if (!fakeDependencyJar.exists()) { + fakeDependencyJar.parentFile.mkdirs() + JarFlinger(fakeDependencyJar.toPath()).use {} } - fakeJar + return fakeDependencyJar } } diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/options/ProjectOptions.java b/build-system/gradle-core/src/main/java/com/android/build/gradle/options/ProjectOptions.java index 7b26f043ea..35b2263693 100644 --- a/build-system/gradle-core/src/main/java/com/android/build/gradle/options/ProjectOptions.java +++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/options/ProjectOptions.java @@ -54,12 +54,6 @@ public final class ProjectOptions { integerOptionValues = createOptionValues(IntegerOption.values()); replacedOptionValues = createOptionValues(ReplacedOption.values()); stringOptionValues = createOptionValues(StringOption.values()); - // Initialize AnalyticsSettings before we access its properties in isAnalyticsEnabled - // function - AnalyticsSettings.initialize( - LoggerWrapper.getLogger(ProjectOptions.class), - null, - com.android.tools.analytics.Environment.getSYSTEM()); Environment.initialize(Environment.Companion.getSYSTEM()); } @@ -183,11 +177,31 @@ public final class ProjectOptions { } public boolean isAnalyticsEnabled() { + if (!needToInitializeAnalytics()) { + return false; + } + AnalyticsSettings.initialize( + LoggerWrapper.getLogger(ProjectOptions.class), + null, + com.android.tools.analytics.Environment.getSYSTEM()); + return AnalyticsSettings.getOptedIn() || get(BooleanOption.ENABLE_PROFILE_JSON) || get(StringOption.PROFILE_OUTPUT_DIR) != null; } + /** + * If the analytics.settings file doesn't exist, which means the user has never used studio or + * has manually deleted that file, AGP doesn't need to create that file and check if analytics + * is opted in. In that case, it is considered as not opted in. This will also help us avoid + * the configuration cache miss caused by file creation in configuration phase for CI users. + */ + private boolean needToInitializeAnalytics() { + return AnalyticsSettings.settingsFileExists() + || get(BooleanOption.ENABLE_PROFILE_JSON) + || get(StringOption.PROFILE_OUTPUT_DIR) != null; + } + public <OptionT extends Option<ValueT>, ValueT> ImmutableMap<OptionT, ValueT> getExplicitlySetOptions( ImmutableMap<OptionT, OptionValue<OptionT, ValueT>> optionValues) { diff --git a/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/analytics/AnalyticsConfigurationCachingTest.kt b/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/analytics/AnalyticsConfigurationCachingTest.kt index 2c0fa91d8c..57e2eae3e7 100644 --- a/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/analytics/AnalyticsConfigurationCachingTest.kt +++ b/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/analytics/AnalyticsConfigurationCachingTest.kt @@ -19,6 +19,8 @@ package com.android.build.gradle.integration.analytics import com.android.build.gradle.integration.common.fixture.GradleTestProject import com.android.build.gradle.integration.common.fixture.ProfileCapturer import com.android.build.gradle.integration.common.fixture.app.HelloWorldApp +import com.android.build.gradle.internal.LoggerWrapper +import com.android.build.gradle.internal.profile.AnalyticsService import com.google.common.truth.Truth import com.google.wireless.android.sdk.stats.GradleBuildProfileSpan.ExecutionType import org.junit.Rule @@ -97,8 +99,6 @@ class AnalyticsConfigurationCachingTest { val nonCachedRun = capturer.capture { project.execute("assembleDebug") }.single() Truth.assertThat(nonCachedRun.buildTime).isGreaterThan(0) val configCachedRun = capturer.capture { project.execute("assembleDebug") }.single() - Truth.assertThat(configCachedRun.buildTime).isGreaterThan(0) - Truth.assertThat(nonCachedRun.buildTime).isGreaterThan(configCachedRun.buildTime) } } diff --git a/common/src/main/java/com/android/prefs/AbstractAndroidLocations.kt b/common/src/main/java/com/android/prefs/AbstractAndroidLocations.kt index ce4e62c86b..fdd9064d20 100644 --- a/common/src/main/java/com/android/prefs/AbstractAndroidLocations.kt +++ b/common/src/main/java/com/android/prefs/AbstractAndroidLocations.kt @@ -138,7 +138,7 @@ This is the path of preference folder expected by the Android tools.""" * ANDROID_USER_HOME does not exist * */ - private fun computeAndroidFolder(): Path { + fun computeAndroidFolder(): Path { val locator = AndroidPathLocator(environmentProvider, if (!silent) logger else NullLogger()) val folder = |