blob: d509d120619435c2477823ea6b617de00b0d088d [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.tasks
import com.android.build.gradle.internal.LoggerWrapper
import com.android.build.gradle.internal.core.Abi
import com.android.build.gradle.internal.cxx.stripping.SymbolStripExecutableFinder
import com.android.build.gradle.internal.process.GradleProcessExecutor
import com.android.build.gradle.internal.scope.BuildArtifactsHolder
import com.android.build.gradle.internal.scope.InternalArtifactType.MERGED_NATIVE_LIBS
import com.android.build.gradle.internal.scope.InternalArtifactType.STRIPPED_NATIVE_LIBS
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.android.ide.common.process.LoggedProcessOutputHandler
import com.android.ide.common.process.ProcessExecutor
import com.android.ide.common.process.ProcessInfoBuilder
import com.android.ide.common.resources.FileStatus
import com.android.ide.common.resources.FileStatus.CHANGED
import com.android.ide.common.resources.FileStatus.NEW
import com.android.ide.common.resources.FileStatus.REMOVED
import com.android.ide.common.workers.WorkerExecutorFacade
import com.android.utils.FileUtils
import com.google.common.annotations.VisibleForTesting
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.Logging
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.PathMatcher
import java.nio.file.Paths
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskProvider
import java.io.Serializable
import javax.inject.Inject
/**
* Task to remove debug symbols from native libraries.
*/
@CacheableTask
abstract class StripDebugSymbolsTask : IncrementalTask() {
@get:Classpath
abstract val inputDir: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Input
lateinit var excludePatterns: List<String>
private set
@Input
@Optional
fun getStripExecutablesMap(): Map<Abi, File>? {
// if no input files, return null, because no need for executable(s), and we don't want to
// spend the extra time or print out NDK-related spam if not necessary.
return if (FileUtils.getAllFiles(inputDir.get().asFile).isEmpty) {
null
} else {
stripToolFinderProvider.get().stripExecutables.toSortedMap()
}
}
private lateinit var stripToolFinderProvider: Provider<SymbolStripExecutableFinder>
/**
* TODO(https://issuetracker.google.com/129217943)
*
* <p>We can not use gradle worker in this task as we use [GradleProcessExecutor], which should
* not be serialized.
*/
private val workers = Workers.withThreads(project.name, path)
override val incremental: Boolean
get() = true
override fun doFullTaskAction() {
StripDebugSymbolsDelegate(
workers,
inputDir.get().asFile,
outputDir.get().asFile,
excludePatterns,
stripToolFinderProvider,
GradleProcessExecutor(project),
null
).run()
}
override fun doIncrementalTaskAction(changedInputs: Map<File, FileStatus>) {
StripDebugSymbolsDelegate(
workers,
inputDir.get().asFile,
outputDir.get().asFile,
excludePatterns,
stripToolFinderProvider,
GradleProcessExecutor(project),
changedInputs
).run()
}
class CreationAction(
variantScope: VariantScope
) : VariantTaskCreationAction<StripDebugSymbolsTask>(variantScope) {
override val name: String
get() = variantScope.getTaskName("strip", "DebugSymbols")
override val type: Class<StripDebugSymbolsTask>
get() = StripDebugSymbolsTask::class.java
override fun handleProvider(taskProvider: TaskProvider<out StripDebugSymbolsTask>) {
super.handleProvider(taskProvider)
variantScope.artifacts.producesDir(
STRIPPED_NATIVE_LIBS,
BuildArtifactsHolder.OperationType.APPEND,
taskProvider,
StripDebugSymbolsTask::outputDir,
fileName = "out"
)
}
override fun configure(task: StripDebugSymbolsTask) {
super.configure(task)
variantScope.artifacts.setTaskInputToFinalProduct(MERGED_NATIVE_LIBS, task.inputDir)
task.excludePatterns =
variantScope.globalScope.extension.packagingOptions.doNotStrip.sorted()
task.stripToolFinderProvider =
variantScope.globalScope.sdkComponents.stripExecutableFinderProvider
}
}
}
/**
* Delegate to strip debug symbols from native libraries
*/
@VisibleForTesting
class StripDebugSymbolsDelegate(
val workers: WorkerExecutorFacade,
val inputDir: File,
val outputDir: File,
val excludePatterns: List<String>,
val stripToolFinderProvider: Provider<SymbolStripExecutableFinder>,
val processExecutor: ProcessExecutor,
val changedInputs: Map<File, FileStatus>?
) {
fun run() {
if (changedInputs == null) {
FileUtils.cleanOutputDir(outputDir)
}
val excludeMatchers = excludePatterns.map { compileGlob(it) }
// by lazy, because we don't want to spend the extra time or print out NDK-related spam if
// there are no .so files to strip
val stripToolFinder by lazy { stripToolFinderProvider.get() }
if (changedInputs != null) {
for (input in changedInputs.keys) {
if (input.isDirectory) {
continue
}
val path = FileUtils.relativePossiblyNonExistingPath(input, inputDir)
val output = File(outputDir, path)
when (changedInputs[input]) {
NEW, CHANGED -> {
val justCopyInput =
excludeMatchers.any { matcher -> matcher.matches(Paths.get(path)) }
workers.use {
it.submit(
StripDebugSymbolsRunnable::class.java,
StripDebugSymbolsRunnable.Params(
input,
output,
Abi.getByName(input.parentFile.name),
justCopyInput,
stripToolFinder,
processExecutor
)
)
}
}
REMOVED -> FileUtils.deletePath(output)
}
}
} else {
for (input in FileUtils.getAllFiles(inputDir)) {
if (input.isDirectory) {
continue
}
val path = FileUtils.relativePath(input, inputDir)
val output = File(outputDir, path)
val justCopyInput =
excludeMatchers.any {matcher -> matcher.matches(Paths.get(path)) }
workers.use {
it.submit(
StripDebugSymbolsRunnable::class.java,
StripDebugSymbolsRunnable.Params(
input,
output,
Abi.getByName(input.parentFile.name),
justCopyInput,
stripToolFinder,
processExecutor
)
)
}
}
}
}
}
/**
* Runnable to strip debug symbols from a native library
*/
private class StripDebugSymbolsRunnable @Inject constructor(val params: Params): Runnable {
override fun run() {
val logger = LoggerWrapper(Logging.getLogger(StripDebugSymbolsTask::class.java))
FileUtils.mkdirs(params.output.parentFile)
val exe =
params.stripToolFinder.stripToolExecutableFile(params.input, params.abi) {
logger.warning("$it Packaging it as is.")
return@stripToolExecutableFile null
}
if (exe == null || params.justCopyInput) {
// If exe == null, the strip executable couldn't be found and a message about the
// failure was reported in getPathToStripExecutable, so we fall back to copying the file
// to the output location.
FileUtils.copyFile(params.input, params.output)
return
}
val builder = ProcessInfoBuilder()
builder.setExecutable(exe)
builder.addArgs("--strip-unneeded")
builder.addArgs("-o")
builder.addArgs(params.output.toString())
builder.addArgs(params.input.toString())
val result =
params.processExecutor.execute(
builder.createProcess(), LoggedProcessOutputHandler(logger)
)
if (result.exitValue != 0) {
logger.warning(
"Unable to strip library ${params.input.absolutePath} due to error "
+ "${result.exitValue} returned from $exe, packaging it as is."
)
FileUtils.copyFile(params.input, params.output)
}
}
data class Params(
val input: File,
val output: File,
val abi: Abi?,
val justCopyInput: Boolean,
val stripToolFinder: SymbolStripExecutableFinder,
val processExecutor: ProcessExecutor
): Serializable
}
private fun compileGlob(pattern: String): PathMatcher {
val maybeSlash = if (pattern.startsWith("/") || pattern.startsWith("*")) "" else "/"
return FileSystems.getDefault().getPathMatcher("glob:$maybeSlash$pattern")
}