blob: 9e26d9041a800fcf63d05c10bb76065ec9464124 [file] [log] [blame]
/*
* Copyright (C) 2017 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.scope
import com.android.build.api.artifact.ArtifactType
import com.android.build.FilterData
import com.android.build.VariantOutput
import com.android.build.gradle.internal.api.artifact.toArtifactType
import com.android.build.gradle.internal.ide.FilterDataImpl
import com.android.builder.core.VariantTypeImpl
import com.google.common.collect.ImmutableList
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import java.io.File
import java.io.FileNotFoundException
import java.io.FileReader
import java.io.IOException
import java.io.Reader
import java.nio.file.Path
/**
* Factory for {@link BuildElements} that can load its content from save metadata file (.json)
*/
class ExistingBuildElements {
companion object {
const val METADATA_FILE_NAME = "output.json"
/**
* create a [BuildElements] from an existing [Directory].
* @param artifactType the expected element type of the BuildElements.
* @param directoryProvider the directory containing the metadata file.
*/
@JvmStatic
fun from(artifactType: ArtifactType<Directory>, directoryProvider: Provider<Directory>): BuildElements {
return if (directoryProvider.isPresent) {
from(artifactType, directoryProvider.get().asFile)
} else BuildElements(version = BuildElements.METADATA_FILE_VERSION,
applicationId = "",
variantType = VariantTypeImpl.BASE_APK.toString(),
elements = listOf())
}
/**
* create a {@link BuildElement} from a previous task execution metadata file collection.
* @param elementType the expected element type of the BuildElements.
* @param from the file collection containing the metadata file.
*/
@JvmStatic
fun from(elementType: ArtifactType<Directory>, from: FileCollection): BuildElements {
val metadataFile = getMetadataFileIfPresent(from)
return loadFrom(elementType, metadataFile)
}
/**
* create a {@link BuildElement} from a previous task execution metadata file.
* @param elementType the expected element type of the BuildElements.
* @param from the folder containing the metadata file.
*/
@JvmStatic
fun from(elementType: ArtifactType<Directory>, from: File): BuildElements {
val metadataFile = getMetadataFileIfPresent(from)
return loadFrom(elementType, metadataFile)
}
/**
* create a {@link BuildElement} containing all artifact types from a previous task
* execution metadata file.
* @param from the folder containing the metadata file.
*/
@JvmStatic
fun from(from: File): BuildElements {
val metadataFile = getMetadataFileIfPresent(from)
return loadFrom(null, metadataFile)
}
/**
* create a {@link BuildElement} containing all artifact types from a previous task
* execution metadata file.
* @param from the metadata file.
*/
@JvmStatic
fun fromFile(from: File): BuildElements = loadFrom(null, from)
private fun loadFrom(
elementType: ArtifactType<Directory>?,
metadataFile: File?): BuildElements {
if (metadataFile == null || !metadataFile.exists()) {
return BuildElements(version = BuildElements.METADATA_FILE_VERSION,
applicationId = "",
variantType = VariantTypeImpl.BASE_APK.toString(),
elements = ImmutableList.of())
}
try {
FileReader(metadataFile).use { reader ->
return load(metadataFile.parentFile.toPath(),
elementType,
reader)
}
} catch (e: IOException) {
return BuildElements(version = BuildElements.METADATA_FILE_VERSION,
applicationId = "",
variantType = VariantTypeImpl.BASE_APK.toString(),
elements = ImmutableList.of<BuildOutput>())
}
}
private fun getMetadataFileIfPresent(fileCollection: FileCollection): File? {
return fileCollection.asFileTree.files.find { it.name == METADATA_FILE_NAME }
}
@JvmStatic
fun getMetadataFileIfPresent(folder: File): File? {
val outputFile = getMetadataFile(folder)
return if (outputFile.exists()) outputFile else null
}
@JvmStatic
fun getMetadataFile(folder: File): File {
return File(folder, METADATA_FILE_NAME)
}
@JvmStatic
fun load(
projectPath: Path,
outputType: ArtifactType<Directory>?,
reader: Reader): BuildElements {
val gsonBuilder = GsonBuilder()
gsonBuilder.registerTypeAdapter(ApkData::class.java, ApkDataAdapter())
gsonBuilder.registerTypeAdapter(
ArtifactType::class.java,
OutputTypeTypeAdapter())
val gson = gsonBuilder.create()
val buildOutputs = gson.fromJson<BuildElements>(reader, BuildElements::class.java)
// resolve the file path to the current project location.
return BuildElements(
version = buildOutputs.version,
applicationId = buildOutputs.applicationId,
variantType = buildOutputs.variantType,
elements = buildOutputs
.asSequence()
.filter { outputType == null || it.type.name() == outputType.name() }
.map { buildOutput ->
BuildOutput(
buildOutput.type,
buildOutput.apkData,
projectPath.resolve(buildOutput.outputPath),
buildOutput.properties)
}
.toList())
}
}
internal class ApkDataAdapter: TypeAdapter<ApkData>() {
@Throws(IOException::class)
override fun write(out: JsonWriter, value: ApkData?) {
if (value == null) {
out.nullValue()
return
}
out.beginObject()
out.name("type").value(value.type.toString())
out.name("splits").beginArray()
for (filter in value.filters) {
out.beginObject()
out.name("filterType").value(filter.filterType)
out.name("value").value(filter.identifier)
out.endObject()
}
out.endArray()
out.name("versionCode").value(value.versionCode.toLong())
if (value.versionName != null) {
out.name("versionName").value(value.versionName)
}
if (value.filterName != null) {
out.name("filterName").value(value.filterName)
}
if (value.outputFileName != null) {
out.name("outputFile").value(value.outputFileName)
}
out.name("fullName").value(value.fullName)
out.name("baseName").value(value.baseName)
out.name("dirName").value(value.dirName)
out.endObject()
}
@Throws(IOException::class)
override fun read(reader: JsonReader): ApkData {
reader.beginObject()
var outputType: String? = null
val filters = ImmutableList.builder<FilterData>()
var versionCode = 0
var versionName: String? = null
var outputFile: String? = null
var fullName: String? = null
var baseName: String? = null
var filterName: String? = null
var dirName: String? = null
while (reader.hasNext()) {
when (reader.nextName()) {
"type" -> outputType = reader.nextString()
"splits" -> readFilters(reader, filters)
"versionCode" -> versionCode = reader.nextInt()
"versionName" -> versionName = reader.nextString()
"outputFile" -> outputFile = reader.nextString()
"filterName" -> filterName = reader.nextString()
"baseName" -> baseName = reader.nextString()
"fullName" -> fullName = reader.nextString()
"dirName" -> dirName = reader.nextString()
}
}
reader.endObject()
val filterData = filters.build()
val apkType = VariantOutput.OutputType.valueOf(outputType!!)
return ApkData.of(
apkType,
filterData,
versionCode,
versionName,
filterName,
outputFile,
fullName ?: "",
baseName ?: "",
dirName ?: "")
}
@Throws(IOException::class)
private fun readFilters(reader: JsonReader, filters: ImmutableList.Builder<FilterData>) {
reader.beginArray()
while (reader.hasNext()) {
reader.beginObject()
var filterType: VariantOutput.FilterType? = null
var value: String? = null
while (reader.hasNext()) {
when (reader.nextName()) {
"filterType" -> filterType = VariantOutput.FilterType.valueOf(reader.nextString())
"value" -> value = reader.nextString()
}
}
if (filterType != null && value != null) {
filters.add(FilterDataImpl(filterType, value))
}
reader.endObject()
}
reader.endArray()
}
}
internal class OutputTypeTypeAdapter : TypeAdapter<ArtifactType<*>>() {
@Throws(IOException::class)
override fun write(out: JsonWriter, value: ArtifactType<*>) {
out.beginObject()
out.name("type").value(value.name())
out.endObject()
}
@Throws(IOException::class)
override fun read(reader: JsonReader): ArtifactType<Directory> {
reader.beginObject()
if (!reader.nextName().endsWith("type")) {
throw IOException("Invalid format")
}
val nextString = reader.nextString()
val outputType: ArtifactType<Directory> =
nextString.toArtifactType() as ArtifactType<Directory>
reader.endObject()
return outputType
}
}
}