| /* |
| * Copyright (C) 2019 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 |
| |
| import com.android.SdkConstants |
| import com.google.common.annotations.VisibleForTesting |
| import com.google.common.base.Charsets |
| import java.io.File |
| import java.io.FileInputStream |
| import java.io.InputStreamReader |
| import java.util.Properties |
| |
| /** |
| * SdkType represents which kind is the SDK located (or if it's missing). |
| */ |
| enum class SdkType { |
| REGULAR, PLATFORM, TEST, MISSING |
| } |
| |
| /** |
| * SdkLocation is the result of calling {@code SdkLocator.getSdkLocation(sourceSet) and contains |
| * the located SDK directory and its type. |
| */ |
| data class SdkLocation(val directory: File?, val type: SdkType) |
| |
| /** |
| * A SdkLocationSourceSet represents all the variables and information necessary for locating the |
| * SDK to be used based on all the possible ways that the user can set their preferences. |
| * |
| * <p>Create a new instance using {@code SdkLocationSourceSet(gradleProject.rootDir)} and the |
| * constructor will collect the proper variables from the local.properties file, the environment |
| * variables and system properties. |
| * |
| * <p>For testing reasons you can override any of the components. |
| */ |
| data class SdkLocationSourceSet( |
| val projectRoot: File, |
| internal val localProperties: Properties = GradleLocalPropertiesFactory.get(projectRoot), |
| internal val environmentProperties: Properties = EnvironmentVariablesPropertiesFactory.get(), |
| internal val systemProperties: Properties = SystemPropertiesFactory.get()) |
| |
| /** |
| * SdkLocator contains the logic to select the SDK based on the variables set by the user. |
| * |
| * <p>Use {@link SdkLocator.getSdkLocation(sourceSet)} to fetch a {@code SdkLocation} object based |
| * on the user preferences represented by the passed {@code sourceSet}. If this sourceSet was used |
| * before than the result will be cached. |
| * |
| * <p>To clear the cache, at the end of the build for example, use {@link SdkLocator.resetCache()}. |
| */ |
| object SdkLocator { |
| |
| @JvmStatic |
| @VisibleForTesting |
| var sdkTestDirectory: File? = null |
| |
| @VisibleForTesting |
| const val ANDROID_DIR_PROPERTY = "android.dir" |
| @VisibleForTesting |
| const val ANDROID_HOME_SYSTEM_PROPERTY = "android.home" |
| |
| // Order defines the preference for matching an SDK directory. |
| internal enum class SdkLocationSource(val sdkType: SdkType) { |
| TEST_SDK_DIRECTORY(SdkType.TEST) { |
| override fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? { |
| return sdkTestDirectory?.absolutePath |
| } |
| }, |
| LOCAL_SDK_DIR(SdkType.REGULAR) { |
| override fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? { |
| return sourceSet.localProperties.getProperty(SdkConstants.SDK_DIR_PROPERTY) |
| } |
| |
| }, LOCAL_ANDROID_DIR(SdkType.PLATFORM) { // TODO: Check if this is still used. |
| override fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? { |
| return sourceSet.localProperties.getProperty(ANDROID_DIR_PROPERTY) |
| } |
| |
| }, ENV_ANDROID_HOME(SdkType.REGULAR) { |
| override fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? { |
| return sourceSet.environmentProperties.getProperty(SdkConstants.ANDROID_HOME_ENV) |
| } |
| |
| }, ENV_SDK_ROOT(SdkType.REGULAR) { |
| override fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? { |
| return sourceSet.environmentProperties.getProperty(SdkConstants.ANDROID_SDK_ROOT_ENV) |
| } |
| |
| }, SYS_ANDROID_HOME(SdkType.REGULAR) { |
| override fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? { |
| return sourceSet.systemProperties.getProperty(ANDROID_HOME_SYSTEM_PROPERTY) |
| } |
| }; |
| |
| fun getSdkLocation(sourceSet: SdkLocationSourceSet): SdkLocation? { |
| return getSdkPathProperty(sourceSet)?.let { |
| path -> validateSdkPath(path, sourceSet.projectRoot)?.let { |
| SdkLocation(it, sdkType) |
| } |
| } |
| } |
| abstract fun getSdkPathProperty(sourceSet: SdkLocationSourceSet): String? |
| |
| // Basic SDK path validation: |
| // * If it's not an absolute path, uses the project rootDir as base. |
| // * Check if the path points to a directory in the disk. |
| private fun validateSdkPath(path: String, rootDir: File): File? { |
| var sdk = File(path) |
| if (!sdk.isAbsolute) { |
| // Canonical file transforms paths like: .../userHome/projectRoot/../AndroidSDK |
| // Into: .../userHome/AndroidSDK |
| // This makes no difference on the behavior of the code, but for error messages is much |
| // more clear to present the canonical path to the user. |
| sdk = File(rootDir, path).canonicalFile |
| } |
| return if (sdk.isDirectory) sdk else null |
| } |
| } |
| |
| // We cache by the source set used, so if any property changes we don't use the cache and locate |
| // the SDK directory again. |
| private var cachedSdkLocationKey: SdkLocationSourceSet? = null |
| private var cachedSdkLocation: SdkLocation? = null |
| |
| @JvmStatic |
| fun getSdkLocation(projectRootDir: File): SdkLocation { |
| return getSdkLocation(SdkLocationSourceSet(projectRootDir)) |
| } |
| |
| @JvmStatic |
| @Synchronized |
| fun getSdkLocation(sourceSet: SdkLocationSourceSet): SdkLocation { |
| cachedSdkLocationKey?.let { |
| if (it == sourceSet) { |
| return cachedSdkLocation!! |
| } |
| } |
| |
| for (source in SdkLocationSource.values()) { |
| source.getSdkLocation(sourceSet)?.let { |
| updateCache(it, sourceSet) |
| return it |
| } |
| } |
| |
| SdkLocation(null, SdkType.MISSING).let { |
| updateCache(it, sourceSet) |
| return it |
| } |
| } |
| |
| @Synchronized |
| private fun updateCache(sdkLocation: SdkLocation, sourceSet: SdkLocationSourceSet) { |
| cachedSdkLocationKey = sourceSet |
| cachedSdkLocation = sdkLocation |
| |
| } |
| |
| @JvmStatic |
| @Synchronized |
| fun resetCache() { |
| GradleLocalPropertiesFactory.resetCache() |
| cachedSdkLocationKey = null |
| cachedSdkLocation = null |
| |
| } |
| } |
| |
| private object GradleLocalPropertiesFactory { |
| |
| val cache = mutableMapOf<File, Properties>() |
| |
| @Synchronized |
| internal fun get(projectRoot: File): Properties { |
| val properties = Properties() |
| val localProperties = File(projectRoot, SdkConstants.FN_LOCAL_PROPERTIES) |
| |
| if (localProperties.isFile) { |
| InputStreamReader(FileInputStream(localProperties), Charsets.UTF_8).use { reader -> |
| properties.load(reader) |
| } |
| } |
| |
| cache[projectRoot] = properties |
| return properties |
| } |
| |
| @Synchronized |
| internal fun resetCache() { |
| cache.clear() |
| } |
| |
| } |
| |
| private object EnvironmentVariablesPropertiesFactory { |
| internal fun get(): Properties { |
| val properties = Properties() |
| System.getenv(SdkConstants.ANDROID_HOME_ENV)?.let { |
| properties.setProperty(SdkConstants.ANDROID_HOME_ENV, it) } |
| System.getenv(SdkConstants.ANDROID_SDK_ROOT_ENV)?.let { |
| properties.setProperty(SdkConstants.ANDROID_SDK_ROOT_ENV, it) } |
| return properties |
| } |
| } |
| |
| private object SystemPropertiesFactory { |
| internal fun get(): Properties { |
| val properties = Properties() |
| System.getProperty(SdkLocator.ANDROID_HOME_SYSTEM_PROPERTY)?.let { |
| properties.setProperty(SdkLocator.ANDROID_HOME_SYSTEM_PROPERTY, it) } |
| return properties |
| } |
| } |