blob: 9afbce628c65a65e16ecaca74bdde6ad7b8b73db [file] [log] [blame]
/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.android;
import com.facebook.buck.android.aapt.MiniAapt;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.Hint;
import com.facebook.buck.rules.ImmutableBuildRuleType;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.infer.annotation.SuppressFieldNotInitialized;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class AndroidResourceDescription implements Description<AndroidResourceDescription.Arg> {
public static final BuildRuleType TYPE = ImmutableBuildRuleType.of("android_resource");
private static final ImmutableSet<String> NON_ASSET_FILENAMES =
ImmutableSet.of(".svn", ".git", ".ds_store", ".scc", "cvs", "thumbs.db", "picasa.ini");
@Override
public BuildRuleType getBuildRuleType() {
return TYPE;
}
@Override
public Arg createUnpopulatedConstructorArg() {
return new Arg();
}
@Override
public <A extends Arg> BuildRule createBuildRule(
BuildRuleParams params,
BuildRuleResolver resolver,
A args) {
// Only allow android resource and library rules as dependencies.
Optional<BuildRule> invalidDep = FluentIterable
.from(Iterables.concat(params.getDeclaredDeps(), params.getExtraDeps()))
.filter(Predicates.not(Predicates.or(
Predicates.instanceOf(AndroidResource.class),
Predicates.instanceOf(AndroidLibrary.class))))
.first();
if (invalidDep.isPresent()) {
throw new HumanReadableException(
params.getBuildTarget() + " (android_resource): dependency " +
invalidDep.get().getBuildTarget() + " (" + invalidDep.get().getType() +
") is not of type android_resource or android_library.");
}
ProjectFilesystem filesystem = params.getProjectFilesystem();
return new AndroidResource(
// We only propagate other AndroidResource rule dependencies, as these are
// the only deps which should control whether we need to re-run the aapt_package
// step.
params.copyWithDeps(
Suppliers.ofInstance(AndroidResourceHelper.androidResOnly(params.getDeclaredDeps())),
Suppliers.ofInstance(AndroidResourceHelper.androidResOnly(params.getExtraDeps()))),
new SourcePathResolver(resolver),
resolver.getAllRules(args.deps.get()),
args.res.orNull(),
collectInputFiles(filesystem, args.res),
args.rDotJavaPackage.orNull(),
args.assets.orNull(),
collectInputFiles(filesystem, args.assets),
args.manifest.orNull(),
args.hasWhitelistedStrings.or(false));
}
@VisibleForTesting
ImmutableSortedSet<Path> collectInputFiles(
ProjectFilesystem filesystem,
Optional<Path> inputDir) {
if (!inputDir.isPresent()) {
return ImmutableSortedSet.of();
}
final ImmutableSortedSet.Builder<Path> paths = ImmutableSortedSet.naturalOrder();
// We ignore the same files that mini-aapt and aapt ignore.
FileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(
Path dir,
BasicFileAttributes attr) throws IOException {
String dirName = dir.getFileName().toString();
// Special case: directory starting with '_' as per aapt.
if (dirName.charAt(0) == '_' || !isResource(dirName)) {
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException {
String filename = file.getFileName().toString();
if (isResource(filename)) {
paths.add(file);
}
return FileVisitResult.CONTINUE;
}
private boolean isResource(String fileOrDirName) {
if (NON_ASSET_FILENAMES.contains(fileOrDirName.toLowerCase())) {
return false;
}
if (fileOrDirName.charAt(fileOrDirName.length() - 1) == '~') {
return false;
}
if (MiniAapt.IGNORED_FILE_EXTENSIONS.contains(Files.getFileExtension(fileOrDirName))) {
return false;
}
return true;
}
};
try {
filesystem.walkRelativeFileTree(inputDir.get(), fileVisitor);
} catch (IOException e) {
throw new HumanReadableException(e, "Error traversing directory: %s.", inputDir.get());
}
return paths.build();
}
@SuppressFieldNotInitialized
public static class Arg {
public Optional<Path> res;
public Optional<Path> assets;
public Optional<Boolean> hasWhitelistedStrings;
@Hint(name = "package")
public Optional<String> rDotJavaPackage;
public Optional<SourcePath> manifest;
public Optional<ImmutableSortedSet<BuildTarget>> deps;
}
}