blob: 9115be9ba516c0256eabf20f74413303f828100d [file] [log] [blame]
/*
* Copyright (C) 2016 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.transforms;
import static com.android.build.gradle.internal.cxx.stripping.SymbolStripExecutableFinderKt.createSymbolStripExecutableFinder;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.Format;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.QualifiedContent.Scope;
import com.android.build.api.transform.SecondaryFile;
import com.android.build.api.transform.Status;
import com.android.build.api.transform.Transform;
import com.android.build.api.transform.TransformException;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.android.build.api.transform.TransformOutputProvider;
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.ndk.NdkHandler;
import com.android.build.gradle.internal.pipeline.ExtendedContentType;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.build.gradle.internal.process.GradleProcessExecutor;
import com.android.ide.common.process.LoggedProcessOutputHandler;
import com.android.ide.common.process.ProcessInfoBuilder;
import com.android.ide.common.process.ProcessResult;
import com.android.utils.FileUtils;
import com.android.utils.ILogger;
import com.android.utils.ImmutableCollectors;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.gradle.api.Project;
/**
* Transform to remove debug symbols from native libraries.
*/
public class StripDebugSymbolTransform extends Transform {
@NonNull
private final Project project;
@NonNull private final SymbolStripExecutableFinder stripToolFinder;
@NonNull
private final Set<PathMatcher> excludeMatchers;
private final boolean isLibrary;
public StripDebugSymbolTransform(
@NonNull Project project,
@NonNull NdkHandler ndkHandler,
@NonNull Set<String> excludePattern,
boolean isLibrary) {
this.excludeMatchers = excludePattern.stream()
.map(StripDebugSymbolTransform::compileGlob)
.collect(ImmutableCollectors.toImmutableSet());
this.isLibrary = isLibrary;
checkArgument(ndkHandler.isConfigured());
stripToolFinder = createSymbolStripExecutableFinder(ndkHandler);
this.project = project;
}
@NonNull
@Override
public String getName() {
return "stripDebugSymbol";
}
@NonNull
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return ImmutableSet.of(ExtendedContentType.NATIVE_LIBS);
}
@NonNull
@Override
public Set<? super Scope> getScopes() {
if (isLibrary) {
return TransformManager.PROJECT_ONLY;
}
return TransformManager.SCOPE_FULL_PROJECT;
}
@Override
public boolean isIncremental() {
return true;
}
@Override
public boolean isCacheable() {
return true;
}
@NonNull
@Override
public Collection<SecondaryFile> getSecondaryFiles() {
return stripToolFinder
.executables()
.stream()
.map(SecondaryFile::nonIncremental)
.collect(Collectors.toList());
}
@Override
public void transform(@NonNull final TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
checkNotNull(outputProvider, "Missing output object for transform " + getName());
boolean isIncremental = transformInvocation.isIncremental();
if (!isIncremental) {
outputProvider.deleteAll();
}
for (TransformInput transformInput : transformInvocation.getInputs()) {
for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
File folder = directoryInput.getFile();
File output = outputProvider.getContentLocation(
directoryInput.getName(),
getInputTypes(),
directoryInput.getScopes(),
Format.DIRECTORY);
if (isIncremental) {
for (Map.Entry<File, Status> fileStatus
: directoryInput.getChangedFiles().entrySet()) {
File input = fileStatus.getKey();
if (input.isDirectory()) {
continue;
}
String abiName = input.getParentFile().getName();
Abi abi = Abi.getByName(abiName);
String path = FileUtils.relativePossiblyNonExistingPath(input, folder);
File strippedLib = new File(
output,
FileUtils.relativePossiblyNonExistingPath(input, folder));
switch(fileStatus.getValue()) {
case ADDED:
case CHANGED:
if (excludeMatchers.stream().anyMatch(m -> m.matches(Paths.get(path)))) {
FileUtils.mkdirs(strippedLib.getParentFile());
FileUtils.copyFile(input, strippedLib);
} else {
stripFile(input, strippedLib, abi);
}
break;
case REMOVED:
FileUtils.deletePath(new File(output, path));
break;
default:
break;
}
}
} else {
for (File input : FileUtils.getAllFiles(folder)) {
if (input.isDirectory()) {
continue;
}
String abiName = input.getParentFile().getName();
Abi abi = Abi.getByName(abiName);
String path = FileUtils.relativePath(input, folder);
File strippedLib = new File(output, path);
if (excludeMatchers.stream().anyMatch(m -> m.matches(Paths.get(path)))) {
FileUtils.mkdirs(strippedLib.getParentFile());
FileUtils.copyFile(input, strippedLib);
} else {
stripFile(input, strippedLib, abi);
}
}
}
}
for (JarInput jarInput : transformInput.getJarInputs()) {
File outFile = outputProvider.getContentLocation(
jarInput.getName(),
getInputTypes(),
jarInput.getScopes(),
Format.JAR);
if (!isIncremental
|| jarInput.getStatus() == Status.ADDED
|| jarInput.getStatus() == Status.CHANGED) {
// Just copy the jar files. Native libraries in a jar files are not built by
// the plugin. We expect the libraries to be stripped as we won't be able to
// debug the libraries unless we extract them anyway.
FileUtils.mkdirs(outFile.getParentFile());
FileUtils.copyFile(jarInput.getFile(), outFile);
} else if (jarInput.getStatus() == Status.REMOVED) {
FileUtils.deleteIfExists(outFile);
}
}
}
}
private void stripFile(@NonNull File input, @NonNull File output, @Nullable Abi abi)
throws IOException {
FileUtils.mkdirs(output.getParentFile());
ILogger logger = new LoggerWrapper(project.getLogger());
File exe =
stripToolFinder.stripToolExecutableFile(
input,
abi,
msg -> {
logger.warning(msg + " Packaging it as is.");
return null;
});
if (exe == null) {
// The strip executable couldn't be found and a message about the failure was reported
// in getPathToStripExecutable.
// Fall back to copying the file to the output location
FileUtils.copyFile(input, output);
return;
}
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(exe);
builder.addArgs("--strip-unneeded");
builder.addArgs("-o");
builder.addArgs(output.toString());
builder.addArgs(input.toString());
ProcessResult result = new GradleProcessExecutor(project).execute(
builder.createProcess(),
new LoggedProcessOutputHandler(logger));
if (result.getExitValue() != 0) {
logger.warning(
"Unable to strip library '%s' due to error %s returned "
+ "from '%s', packaging it as is.",
result.getExitValue(), exe, input.getAbsolutePath());
FileUtils.copyFile(input, output);
}
}
@NonNull
private static PathMatcher compileGlob(@NonNull String pattern) {
FileSystem fs = FileSystems.getDefault();
if (!pattern.startsWith("/") && !pattern.startsWith("*")) {
pattern = "/" + pattern;
}
return fs.getPathMatcher("glob:" + pattern);
}
}