blob: a10f55524636f4bdcb99dd6ace9b92a23428bc62 [file] [log] [blame]
/*
* 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.Version
import com.android.builder.core.ToolsRevisionUtils
import com.android.builder.errors.IssueReporter
import com.android.io.CancellableFileIo
import com.android.repository.Revision
import com.android.repository.api.ConsoleProgressIndicator
import com.android.repository.api.LocalPackage
import com.android.repository.api.Repository
import com.android.repository.impl.meta.SchemaModuleUtil
import com.android.sdklib.AndroidTargetHash.getPlatformHashString
import com.android.sdklib.AndroidVersion
import com.android.sdklib.BuildToolInfo
import com.android.sdklib.OptionalLibrary
import com.android.sdklib.repository.AndroidSdkHandler
import com.android.sdklib.repository.meta.DetailsTypes
import com.android.sdklib.repository.targets.PlatformTarget
import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.ImmutableList
import java.io.File
import java.io.FileInputStream
import java.io.IOException
/**
* Try to load the BuildTool from {@code ${sdkDirectory}/build-tools/${revision}}.
*/
fun buildBuildTools(sdkDirectory: File, revision: Revision): BuildToolInfo? {
val buildToolsPath = DetailsTypes.getBuildToolsPath(revision).replace(';', '/')
val buildToolsXml = sdkDirectory.resolve(buildToolsPath).resolve("package.xml")
val buildToolsPackage = parsePackage(buildToolsXml) ?: return null
// BuildToolInfo is cheap to build and verify, so we can keep using it.
val buildToolInfo = BuildToolInfo.fromLocalPackage(buildToolsPackage)
if (!buildToolInfo.isValid(null)) {
// The build tools we loaded is missing some expected components.
return null
}
if (!buildToolInfo.revision.equals(revision)) {
// The path we guessed contained a build-tool but it had a different than the requested one.
return null
}
return buildToolInfo
}
/**
* Load and parse a {@link LocalPackage} ('package.xml') file from the disk.
*/
fun parsePackage(packageXml: File): LocalPackage? {
if (!packageXml.exists()) {
return null
}
val progress = object : ConsoleProgressIndicator() {
val prefix = "SDK processing. "
override fun logWarning(s: String, e: Throwable?) {
super.logWarning(prefix + s, e)
}
override fun logError(s: String, e: Throwable?) {
super.logError(prefix + s, e)
}
override fun logInfo(s: String) {
super.logInfo(prefix + s)
}
}
lateinit var repo: Repository
try {
val parsedObject = SchemaModuleUtil.unmarshal(
FileInputStream(packageXml),
AndroidSdkHandler.getAllModules(),
false,
progress,
packageXml.name) ?: return null
repo = parsedObject as Repository
} catch (e: IOException) {
// This shouldn't ever happen
progress.logError("Error parsing $packageXml.", e)
return null
}
repo.localPackage?.setInstalledPath(packageXml.toPath().parent)
return repo.localPackage
}
// Additional libraries always comes from addon targets.
/**
* Return the additional libraries collection fron {@code localPackage}.
*
* <p>These are only present if the LocalPackage represents a AddOn platform. Otherwise returns an
* empty list.
*/
fun parseAdditionalLibraries(localPackage: LocalPackage): List<OptionalLibrary> {
if (localPackage.typeDetails !is DetailsTypes.AddonDetailsType) {
return ImmutableList.of()
}
val details = localPackage.typeDetails as DetailsTypes.AddonDetailsType
// Looks strange, but getLibrary inside getLibraries returns a list of libraries...
return details.libraries?.let { libraries ->
libraries.library
.filter { it.localJarPath != null }
.onEach { it.setPackagePath(localPackage.location) }
} ?: emptyList()
}
// Optional libraries always comes from base/platform targets.
/**
* Load the optional libraries for {@code localPackage}.
*
* <p>If {@code localPackage} is a Platform, then the list of optional packages is located in the
* {@code optional/optional.json} file under it, like $SDK/platforms/android-28/optional/...
*/
fun parseOptionalLibraries(localPackage: LocalPackage): List<OptionalLibrary> {
val optionalJson = localPackage.location.resolve("optional/optional.json")
if (CancellableFileIo.isRegularFile(optionalJson)) {
return PlatformTarget.getLibsFromJson(optionalJson)
}
return emptyList()
}
fun parseAndroidVersion(localPackage: LocalPackage): AndroidVersion? =
(localPackage.typeDetails as? DetailsTypes.ApiDetailsType)?.androidVersion
fun warnIfCompileSdkTooNew(version: AndroidVersion, issueReporter: IssueReporter, suppressWarningIfTooNewForVersions: String?) {
warnIfCompileSdkTooNew(
version = version,
issueReporter = issueReporter,
maxVersion = ToolsRevisionUtils.MAX_RECOMMENDED_COMPILE_SDK_VERSION,
androidGradlePluginVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION,
suppressWarningIfTooNewForVersions = suppressWarningIfTooNewForVersions,
)
}
@VisibleForTesting
internal fun warnIfCompileSdkTooNew(
version: AndroidVersion,
issueReporter: IssueReporter,
maxVersion: AndroidVersion,
androidGradlePluginVersion: String,
suppressWarningIfTooNewForVersions: String? = null,
) {
if (version <= maxVersion) return
if (version.isPreview) return // No message if the SDK is a preview version
val suppressName = version.apiString
val suppressSet = suppressWarningIfTooNewForVersions?.splitToSequence(",")?.filter(String::isNotEmpty)?.toSet() ?: setOf()
if (suppressSet.contains(suppressName)) return
issueReporter.reportWarning(
IssueReporter.Type.COMPILE_SDK_VERSION_TOO_HIGH,
"We recommend using a newer Android Gradle plugin to use ${version.asDsl()}\n" +
"\n" +
"This Android Gradle plugin ($androidGradlePluginVersion) " +
"was tested up to ${AndroidVersion(maxVersion.apiLevel).asDsl()}" +
(if (maxVersion.isPreview) " (and ${maxVersion.asDsl()})" else "") + "\n" +
"\n" +
"This warning can be suppressed by " +
(if (suppressWarningIfTooNewForVersions.isNullOrEmpty()) {
"adding\n ${com.android.build.gradle.options.StringOption.SUPPRESS_UNSUPPORTED_COMPILE_SDK.propertyName}=$suppressName"
} else {
"updating\n ${com.android.build.gradle.options.StringOption.SUPPRESS_UNSUPPORTED_COMPILE_SDK.propertyName}=${suppressSet.joinToString(",")},$suppressName"
}) +
"\n" +
"to this project's gradle.properties\n" +
"\n" +
"The build will continue, but you are strongly encouraged to update your project to\n" +
"use a newer Android Gradle Plugin that has been tested with ${version.asDsl()}",
getPlatformHashString(maxVersion)
)
}
private fun AndroidVersion.asDsl(): String {
return if (isPreview) """compileSdkPreview = "$codename"""" else "compileSdk = $apiLevel"
}