blob: 9f665f91f4871b3abc673eebd319be20fe42fa21 [file] [log] [blame]
/*
* Copyright (C) 2018 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.tasks
import com.android.SdkConstants
import com.android.SdkConstants.FN_CLASSES_JAR
import com.android.build.api.transform.QualifiedContent
import com.android.build.gradle.internal.packaging.JarCreatorFactory
import com.android.build.gradle.internal.packaging.JarCreatorType
import com.android.build.gradle.internal.publishing.AndroidArtifacts.PublishedConfigType
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.build.gradle.options.BooleanOption
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.io.Serializable
import java.nio.file.Files
import java.util.function.Predicate
import java.util.function.Supplier
import java.util.regex.Pattern
import java.util.zip.Deflater
import javax.inject.Inject
private val CLASS_PATTERN = Pattern.compile(".*\\.class$")
private val META_INF_PATTERN = Pattern.compile("^META-INF/.*$")
/**
* Bundle all library classes in a jar. Additional filters can be specified, in addition to ones
* defined in [LibraryAarJarsTask.getDefaultExcludes].
*/
abstract class BundleLibraryClasses : NonIncrementalTask() {
@get:OutputFile
abstract val output: RegularFileProperty
@get:Internal
lateinit var packageName: Lazy<String>
private set
@get:Classpath
abstract val classes: ConfigurableFileCollection
@get:Input
abstract val packageRClass: Property<Boolean>
@get:Input
abstract val toIgnoreRegExps: ListProperty<String>
@get:Input
lateinit var jarCreatorType: JarCreatorType
private set
override fun doTaskAction() {
getWorkerFacadeWithWorkers().use {
it.submit(
BundleLibraryClassesRunnable::class.java,
BundleLibraryClassesRunnable.Params(
packageName = packageName.value,
toIgnore = toIgnoreRegExps.get(),
output = output.get().asFile,
input = classes.files,
packageRClass = packageRClass.get(),
jarCreatorType = jarCreatorType
)
)
}
}
class CreationAction(
scope: VariantScope,
private val publishedType: PublishedConfigType,
private val toIgnoreRegExps: Supplier<List<String>> = Supplier { emptyList<String>() }
) :
VariantTaskCreationAction<BundleLibraryClasses>(scope) {
private val inputs: FileCollection
init {
check(
publishedType == PublishedConfigType.API_ELEMENTS
|| publishedType == PublishedConfigType.RUNTIME_ELEMENTS
) { "Library classes bundling is supported only for api and runtime." }
// Because ordering matters for TransformAPI, we need to fetch classes from the
// transform pipeline as soon as this creation action is instantiated.
inputs = if (publishedType == PublishedConfigType.RUNTIME_ELEMENTS) {
scope.transformManager.getPipelineOutputAsFileCollection { types, scopes ->
types.contains(QualifiedContent.DefaultContentType.CLASSES)
&& scopes.size == 1 && scopes.contains(QualifiedContent.Scope.PROJECT)
}
} else {
variantScope.artifacts.getAllClasses()
}
}
override val name: String =
scope.getTaskName(
if (publishedType == PublishedConfigType.API_ELEMENTS)
"bundleLibCompile"
else
"bundleLibRuntime"
)
override val type: Class<BundleLibraryClasses> = BundleLibraryClasses::class.java
override fun handleProvider(taskProvider: TaskProvider<out BundleLibraryClasses>) {
super.handleProvider(taskProvider)
val request = variantScope.artifacts.getOperations()
.setInitialProvider(taskProvider,
BundleLibraryClasses::output).withName(FN_CLASSES_JAR)
if (publishedType == PublishedConfigType.API_ELEMENTS) {
request.on(InternalArtifactType.COMPILE_LIBRARY_CLASSES)
} else {
request.on(InternalArtifactType.RUNTIME_LIBRARY_CLASSES)
}
}
override fun configure(task: BundleLibraryClasses) {
super.configure(task)
task.packageName = lazy { variantScope.variantConfiguration.packageFromManifest }
task.classes.from(inputs)
val packageRClass =
variantScope.globalScope.projectOptions[BooleanOption.COMPILE_CLASSPATH_LIBRARY_R_CLASSES] &&
publishedType == PublishedConfigType.API_ELEMENTS &&
!variantScope.globalScope.extension.aaptOptions.namespaced
task.packageRClass.set(packageRClass)
if (packageRClass) {
task.classes.from(variantScope.artifacts.getFinalProduct(InternalArtifactType.COMPILE_ONLY_NOT_NAMESPACED_R_CLASS_JAR))
}
// FIXME pass this as List<TextResources>
task.toIgnoreRegExps.set(
variantScope.globalScope.project.provider(toIgnoreRegExps::get)
)
task.jarCreatorType = variantScope.jarCreatorType
}
}
}
/** Packages files to jar using the provided filter. */
class BundleLibraryClassesRunnable @Inject constructor(private val params: Params) : Runnable {
data class Params(
val packageName: String,
val toIgnore: List<String>,
val output: File,
val input: Set<File>,
val packageRClass: Boolean,
val jarCreatorType: JarCreatorType
) :
Serializable
override fun run() {
Files.deleteIfExists(params.output.toPath())
params.output.parentFile.mkdirs()
val ignorePatterns =
(LibraryAarJarsTask.getDefaultExcludes(
packagePath = params.packageName,
packageR = params.packageRClass
) + params.toIgnore)
.map { Pattern.compile(it) }
val predicate = Predicate<String> { entry ->
(CLASS_PATTERN.matcher(entry).matches() || META_INF_PATTERN.matcher(entry).matches())
&& !ignorePatterns.any { it.matcher(entry).matches() }
}
JarCreatorFactory.make(
params.output.toPath(),
predicate,
params.jarCreatorType
).use { out ->
// Don't compress because compressing takes extra time, and this jar doesn't go into any
// APKs or AARs
out.setCompressionLevel(Deflater.NO_COMPRESSION)
params.input.forEach { base ->
if (base.isDirectory) {
out.addDirectory(base.toPath())
} else if (base.toString().endsWith(SdkConstants.DOT_JAR)) {
out.addJar(base.toPath())
}
}
}
}
}