blob: d5371624b56eaaea2023a717b8c148399c7691b0 [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.io.MorePaths;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildOutputInitializer;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.InitializableFromDisk;
import com.facebook.buck.rules.OnDiskBuildInfo;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Functions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.annotation.Nullable;
/**
* Buildable that is responsible for taking a set of res/ directories and applying an optional
* resource filter to them, ultimately generating the final set of res/ directories whose contents
* should be included in an APK.
* <p>
* Clients of this Buildable may need to know:
* <ul>
* <li>The set of res/ directories that was used to calculate the R.java file. (These are needed
* as arguments to aapt to create the unsigned APK, as well as arguments to create a
* ProGuard config, if appropriate.)
* <li>The set of non-english {@code strings.xml} files identified by the resource filter.
* </ul>
*/
public class ResourcesFilter extends AbstractBuildRule
implements FilteredResourcesProvider, InitializableFromDisk<ResourcesFilter.BuildOutput> {
private static final String RES_DIRECTORIES_KEY = "res_directories";
private static final String NON_ENGLISH_STRING_FILES_KEY = "non_english_string_files";
static enum ResourceCompressionMode {
DISABLED(/* isCompressResources */ false, /* isStoreStringsAsAssets */ false),
ENABLED(/* isCompressResources */ true, /* isStoreStringsAsAssets */ false),
ENABLED_WITH_STRINGS_AS_ASSETS(
/* isCompressResources */ true,
/* isStoreStringsAsAssets */ true),
;
private final boolean isCompressResources;
private final boolean isStoreStringsAsAssets;
private ResourceCompressionMode(boolean isCompressResources, boolean isStoreStringsAsAssets) {
this.isCompressResources = isCompressResources;
this.isStoreStringsAsAssets = isStoreStringsAsAssets;
}
public boolean isCompressResources() {
return isCompressResources;
}
public boolean isStoreStringsAsAssets() {
return isStoreStringsAsAssets;
}
}
private final ImmutableList<Path> resDirectories;
private final ImmutableSet<Path> whitelistedStringDirs;
private final ImmutableSet<String> locales;
private final ResourceCompressionMode resourceCompressionMode;
private final FilterResourcesStep.ResourceFilter resourceFilter;
private final BuildOutputInitializer<BuildOutput> buildOutputInitializer;
public ResourcesFilter(
BuildRuleParams params,
SourcePathResolver resolver,
ImmutableList<Path> resDirectories,
ImmutableSet<Path> whitelistedStringDirs,
ImmutableSet<String> locales,
ResourceCompressionMode resourceCompressionMode,
FilterResourcesStep.ResourceFilter resourceFilter) {
super(params, resolver);
this.resDirectories = resDirectories;
this.whitelistedStringDirs = whitelistedStringDirs;
this.locales = locales;
this.resourceCompressionMode = resourceCompressionMode;
this.resourceFilter = resourceFilter;
this.buildOutputInitializer = new BuildOutputInitializer<>(params.getBuildTarget(), this);
}
@Override
public ImmutableList<Path> getResDirectories() {
return buildOutputInitializer.getBuildOutput().resDirectories;
}
@Override
public ImmutableSet<Path> getNonEnglishStringFiles() {
return buildOutputInitializer.getBuildOutput().nonEnglishStringFiles;
}
@Override
public ImmutableCollection<Path> getInputsToCompareToOutput() {
// Rule key correctness is ensured by depping on all android_resource rules in
// Builder.setAndroidResourceDepsFinder()
return ImmutableSet.of();
}
@Override
public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) {
return builder
.setReflectively("resourceCompressionMode", resourceCompressionMode.toString())
.setReflectively("resourceFilter", resourceFilter.getDescription())
.setReflectively("locales", locales);
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context,
final BuildableContext buildableContext) {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
final ImmutableList.Builder<Path> filteredResDirectoriesBuilder = ImmutableList.builder();
final FilterResourcesStep filterResourcesStep = createFilterResourcesStep(
resDirectories,
whitelistedStringDirs,
locales,
filteredResDirectoriesBuilder);
steps.add(filterResourcesStep);
final ImmutableList<Path> filteredResDirectories = filteredResDirectoriesBuilder.build();
final Supplier<ImmutableSet<Path>> nonEnglishStringFiles = Suppliers.memoize(
new Supplier<ImmutableSet<Path>>() {
@Override
public ImmutableSet<Path> get() {
return filterResourcesStep.getNonEnglishStringFiles();
}
});
for (Path outputResourceDir : filteredResDirectories) {
buildableContext.recordArtifactsInDirectory(outputResourceDir);
}
steps.add(new AbstractExecutionStep("record_build_output") {
@Override
public int execute(ExecutionContext context) {
buildableContext.addMetadata(
RES_DIRECTORIES_KEY,
Iterables.transform(filteredResDirectories, Functions.toStringFunction()));
buildableContext.addMetadata(
NON_ENGLISH_STRING_FILES_KEY,
Iterables.transform(nonEnglishStringFiles.get(), Functions.toStringFunction()));
return 0;
}
});
return steps.build();
}
/**
* Sets up filtering of resources, images/drawables and strings in particular, based on build
* rule parameters {@link #resourceFilter} and {@link #isStoreStringsAsAssets}.
*
* {@link com.facebook.buck.android.FilterResourcesStep.ResourceFilter} {@code resourceFilter}
* determines which drawables end up in the APK (based on density - mdpi, hdpi etc), and also
* whether higher density drawables get scaled down to the specified density (if not present).
*
* {@code isStoreStringsAsAssets} determines whether non-english string resources are packaged
* separately as assets (and not bundled together into the {@code resources.arsc} file).
*
* @param whitelistedStringDirs overrides storing non-english strings as assets for resources
* inside these directories.
*/
@VisibleForTesting
FilterResourcesStep createFilterResourcesStep(
ImmutableList<Path> resourceDirectories,
ImmutableSet<Path> whitelistedStringDirs,
ImmutableSet<String> locales,
ImmutableList.Builder<Path> filteredResDirectories) {
ImmutableBiMap.Builder<Path, Path> filteredResourcesDirMapBuilder = ImmutableBiMap.builder();
String resDestinationBasePath = getResDestinationBasePath();
int count = 0;
for (Path resDir : resourceDirectories) {
Path filteredResourceDir = Paths.get(resDestinationBasePath, String.valueOf(count++));
filteredResourcesDirMapBuilder.put(resDir, filteredResourceDir);
filteredResDirectories.add(filteredResourceDir);
}
ImmutableBiMap<Path, Path> resSourceToDestDirMap = filteredResourcesDirMapBuilder.build();
FilterResourcesStep.Builder filterResourcesStepBuilder = FilterResourcesStep.builder()
.setInResToOutResDirMap(resSourceToDestDirMap)
.setResourceFilter(resourceFilter);
if (isStoreStringsAsAssets()) {
filterResourcesStepBuilder.enableStringsFilter();
filterResourcesStepBuilder.setWhitelistedStringDirs(whitelistedStringDirs);
}
filterResourcesStepBuilder.setLocales(locales);
return filterResourcesStepBuilder.build();
}
@Override
public boolean isStoreStringsAsAssets() {
return resourceCompressionMode.isStoreStringsAsAssets();
}
private String getResDestinationBasePath() {
return BuildTargets.getBinPath(getBuildTarget(), "__filtered__%s__").toString();
}
@Override
public BuildOutput initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
ImmutableList<Path> resDirectories =
FluentIterable.from(onDiskBuildInfo.getValues(RES_DIRECTORIES_KEY).get())
.transform(MorePaths.TO_PATH)
.toList();
ImmutableSet<Path> nonEnglishStringFiles =
FluentIterable.from(onDiskBuildInfo.getValues(NON_ENGLISH_STRING_FILES_KEY).get())
.transform(MorePaths.TO_PATH)
.toSet();
return new BuildOutput(resDirectories, nonEnglishStringFiles);
}
@Override
public BuildOutputInitializer<BuildOutput> getBuildOutputInitializer() {
return buildOutputInitializer;
}
public static class BuildOutput {
private final ImmutableList<Path> resDirectories;
private final ImmutableSet<Path> nonEnglishStringFiles;
public BuildOutput(
ImmutableList<Path> resDirectories,
ImmutableSet<Path> nonEnglishStringFiles) {
this.resDirectories = resDirectories;
this.nonEnglishStringFiles = nonEnglishStringFiles;
}
}
@Nullable
@Override
public Path getPathToOutputFile() {
return null;
}
}