blob: 523365a9960fe022fc76073da03e2905cf39a4d2 [file] [log] [blame]
/*
* Copyright (C) 2020 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.android.Version
import com.android.builder.core.ToolsRevisionUtils
import com.android.builder.errors.IssueReporter
import com.android.builder.internal.compiler.RenderScriptProcessor
import com.android.repository.Revision
import com.android.sdklib.AndroidTargetHash
import com.android.sdklib.AndroidVersion
import com.android.sdklib.BuildToolInfo
import com.android.sdklib.OptionalLibrary
import com.android.sdklib.repository.meta.DetailsTypes
import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.ImmutableList
import java.io.File
import java.util.Optional
import java.util.concurrent.ConcurrentHashMap
/**
* This loads the SDK and BuildTools specified inside the {@code options} directly, without
* performing a full SDK scan (see {@link SdkFullLoadingStrategy}).
*
* <p>This is done by trying to predict the directory of the buildtool/SDK requested and parsing
* only those package.xml files (with some basic validation).
*
* <p>Unlike the full SDK scan, it cannot load/find sdk components that are installed in a
* non-default directory structure.
*/
class SdkDirectLoadingStrategy(
private val sdkLocationSourceSet: SdkLocationSourceSet,
private val platformTargetHashSupplier: String?,
private val buildToolRevisionSupplier: Revision?,
private val useAndroidX: Boolean,
private val issueReporter: IssueReporter) {
companion object {
// We use Optional<> wrapper since ConcurrentHashMap don't support null values.
private val buildToolsCache = ConcurrentHashMap<Revision, Optional<BuildToolInfo>>()
private val platformCache = ConcurrentHashMap<String, Optional<PlatformComponents>>()
@VisibleForTesting
@Synchronized
fun clearCaches() {
buildToolsCache.clear()
platformCache.clear()
}
}
private val components: DirectLoadComponents? by lazy {
init()
}
/** Holder of all components loaded **/
private class DirectLoadComponents(
internal val platformTools: PlatformToolsComponents,
internal val supportTools: SupportToolsComponents,
internal val buildToolInfo: BuildToolInfo,
internal val platform: PlatformComponents
)
@Synchronized
private fun init(): DirectLoadComponents? {
val targetHash = checkNotNull(platformTargetHashSupplier) {
"Extension not initialized yet, couldn't access compileSdkVersion."}
val buildToolRevision = checkBuildToolsRevision(
checkNotNull(buildToolRevisionSupplier) {
"Extension not initialized yet, couldn't access buildToolsVersion."})
return loadSdkComponents(targetHash, buildToolRevision)
}
private fun loadSdkComponents(targetHash: String, buildToolRevision: Revision): DirectLoadComponents? {
val sdkLocation =
SdkLocator.getSdkLocation(
sdkLocationSourceSet,
issueReporter
)
if (sdkLocation.type == SdkType.MISSING) {
return null
}
val sdkDirectory = sdkLocation.directory!!
val platformTools =
PlatformToolsComponents.build(
sdkDirectory
)
val supportTools =
SupportToolsComponents.build(
sdkDirectory,
targetHash
)
val buildTools = buildToolsCache.getOrPut(buildToolRevision) {
Optional.ofNullable(
buildBuildTools(
sdkDirectory,
buildToolRevision
)
)
}.orElse(null)
val platform = platformCache.getOrPut(targetHash) {
Optional.ofNullable(
PlatformComponents.build(
sdkDirectory,
targetHash
)
)
}.orElse(null)
if (platformTools == null || supportTools == null || buildTools == null || platform == null) {
return null
}
return DirectLoadComponents(
platformTools,
supportTools,
buildTools,
platform
)
}
private fun checkBuildToolsRevision(revision: Revision): Revision {
if (revision < ToolsRevisionUtils.MIN_BUILD_TOOLS_REV) {
issueReporter
.reportWarning(
IssueReporter.Type.BUILD_TOOLS_TOO_LOW,
String.format(
"The specified Android SDK Build Tools version (%1\$s) is "
+ "ignored, as it is below the minimum supported "
+ "version (%2\$s) for Android Gradle Plugin %3\$s.\n"
+ "Android SDK Build Tools %4\$s will be used.\n"
+ "To suppress this warning, "
+ "remove \"buildToolsVersion '%1\$s'\" "
+ "from your build.gradle file, as each "
+ "version of the Android Gradle Plugin now has a "
+ "default version of the build tools.",
revision,
ToolsRevisionUtils.MIN_BUILD_TOOLS_REV,
Version.ANDROID_GRADLE_PLUGIN_VERSION,
ToolsRevisionUtils.DEFAULT_BUILD_TOOLS_REVISION),
ToolsRevisionUtils.DEFAULT_BUILD_TOOLS_REVISION.toString())
return ToolsRevisionUtils.DEFAULT_BUILD_TOOLS_REVISION
}
return revision
}
fun loadedSuccessfully() = components != null
// From "SdkInfo".
fun getAdbExecutable() = components?.platformTools?.adbExecutable
fun getAnnotationsJar() = components?.supportTools?.annotationsJar
// From IAndroidTarget
fun getAidlFramework() = components?.platform?.aidlFramework
fun getAndroidJar() = components?.platform?.androidJar
fun getAdditionalLibraries() = components?.platform?.additionalLibraries
fun getOptionalLibraries() = components?.platform?.optionalLibraries
fun getTargetPlatformVersion() = components?.platform?.targetPlatformVersion
fun getTargetBootClasspath() = components?.platform?.targetBootClasspath
// From BuildToolInfo
fun getBuildToolsInfo() = components?.buildToolInfo
fun getBuildToolsRevision() = getBuildToolsInfo()?.revision
private fun getFileFromBuildTool(component: BuildToolInfo.PathId) =
getBuildToolsInfo()?.let { File(it.getPath(component)) }
fun getAidlExecutable() = getFileFromBuildTool(BuildToolInfo.PathId.AIDL)
fun getCoreLambaStubs() = getFileFromBuildTool(BuildToolInfo.PathId.CORE_LAMBDA_STUBS)
fun getSplitSelectExecutable() = getFileFromBuildTool(BuildToolInfo.PathId.SPLIT_SELECT)
fun getRenderScriptSupportJar() = getBuildToolsInfo()?.let {
RenderScriptProcessor.getSupportJar(it.location, useAndroidX)
}
fun getSupportNativeLibFolder() = getBuildToolsInfo()?.let {
RenderScriptProcessor.getSupportNativeLibFolder(it.location)
}
fun getSupportBlasLibFolder() = getBuildToolsInfo()?.let {
RenderScriptProcessor.getSupportBlasLibFolder(it.location)
}
fun reset() {
clearCaches()
}
}
private class PlatformToolsComponents(
/** This is the ADB executable, usually $SDK/platform-tools/adb **/
internal val adbExecutable: File) {
companion object {
internal fun build(sdkDirectory: File): PlatformToolsComponents? {
val platformToolsPackageXml = sdkDirectory.resolve(SdkConstants.FD_PLATFORM_TOOLS).resolve("package.xml")
if (!platformToolsPackageXml.exists()) {
return null
}
return PlatformToolsComponents(
sdkDirectory.resolve(SdkConstants.FD_PLATFORM_TOOLS).resolve(SdkConstants.FN_ADB)
)
}
}
}
private class SupportToolsComponents(
/** This is the Annotations.jar, usually $SDK/tools/support/annotations.jar **/
internal val annotationsJar: File) {
companion object {
internal fun build(sdkDirectory: File, targetHash: String): SupportToolsComponents? {
val supportToolsPackageXml = sdkDirectory.resolve(SdkConstants.FD_TOOLS).resolve("package.xml")
val apiLevelLessThan16 = AndroidTargetHash.getVersionFromHash(targetHash)?.apiLevel?.let { it < 16 } ?: false
// We only require tools/support package to exist if we are targeting api < 16, otherwise
// the annotations included in the annotations.jar are already inside android.jar.
if (!supportToolsPackageXml.exists() && apiLevelLessThan16) {
return null
}
return SupportToolsComponents(
sdkDirectory.resolve(SdkConstants.FD_TOOLS).resolve(SdkConstants.FD_SUPPORT)
.resolve(SdkConstants.FN_ANNOTATIONS_JAR)
)
}
}
}
/**
* This holds all components related to the platform targeted, usually
* $SDK/platforms/android-{api_level}/
*/
private class PlatformComponents(
/** This is the platform version **/
internal val targetPlatformVersion: AndroidVersion,
/** This is the framework.aidl, usually $PLATFORM/framework.aidl **/
internal val aidlFramework: File,
/** This is the android.jar, usually $PLATFORM/android.jar **/
internal val androidJar: File,
/** This is the boot classpath of this platform, usually it's composed of only the android.jar **/
internal val targetBootClasspath: List<File>,
/** This is the additional libraries of this platform. **/
internal val additionalLibraries: List<OptionalLibrary>,
/** This is the optional libraries of this platform. **/
internal val optionalLibraries: List<OptionalLibrary>) {
companion object {
internal fun build(sdkDirectory: File, targetHash: String): PlatformComponents? {
if (!AndroidTargetHash.isPlatform(targetHash)) {
// We don't support add-on SDKs as we cannot predict where they
// are installed given only the targetHash, so we return null in order to fallback
// to the full loading mechanism.
return null;
}
val platformVersion = AndroidTargetHash.getVersionFromHash(targetHash)
?: return null // We are not sure which version this hash maps to.
val platformId = DetailsTypes.getPlatformPath(platformVersion)
val platformBase = sdkDirectory.resolve(platformId.replace(';', '/'))
val platformXml = platformBase.resolve("package.xml")
val platformPackage =
parsePackage(platformXml)
if (platformPackage == null || !platformId.equals(platformPackage.path)) {
return null
}
// Building a whole PlatformTarget is expensive, since it tries to parse everything.
// So we build manually the fields we need.
return PlatformComponents(
platformVersion,
platformBase.resolve(SdkConstants.FN_FRAMEWORK_AIDL),
platformBase.resolve(SdkConstants.FN_FRAMEWORK_LIBRARY),
// See PlatformTarget.java, getBootClasspath is always a list with only the android.jar.
ImmutableList.of(platformBase.resolve(SdkConstants.FN_FRAMEWORK_LIBRARY)),
parseAdditionalLibraries(
platformPackage
),
parseOptionalLibraries(
platformPackage
)
)
}
}
}