/*
* 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.model.BuildTargets;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.ExplicitBuildTargetSourcePath;
import com.facebook.buck.rules.RecordFileSha1Step;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.zip.ZipCompressionLevel;
import com.facebook.buck.zip.ZipStep;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hashing;
import java.nio.file.Path;
/**
* Buildable responsible for compiling non-english string resources to {@code .fbstr} files stored
* as assets. Only applicable for {@code android_binary} rules with {@code resource_compression}
* parameter set to {@link com.facebook.buck.android.ResourcesFilter.ResourceCompressionMode
* #ENABLED_WITH_STRINGS_AS_ASSETS}.
*
* <p>Produces an all_locales_string_assets.zip containing .fbstr files for all locales the app has
* strings for, and a string_assets.zip file that contains the .fbstr files filtered by the set of
* locales provided. The contents of string_assets.zip is built into the assets of the APK.
* all_locales_string_assets.zip is used for debugging purposes.
*/
public class PackageStringAssets extends AbstractBuildRule {
private static final String STRING_ASSETS_ZIP_HASH = "STRING_ASSETS_ZIP_HASH";
@VisibleForTesting static final String STRING_ASSET_FILE_EXTENSION = ".fbstr";
public static final String STRING_ASSETS_DIR_FORMAT = "__strings_%s__";
private final FilteredResourcesProvider filteredResourcesProvider;
private final SourcePath rDotTxtPath;
private final ImmutableSet<String> locales;
public PackageStringAssets(
BuildRuleParams params,
ImmutableSet<String> locales,
FilteredResourcesProvider filteredResourcesProvider,
SourcePath rDotTxtPath) {
super(params);
this.locales = locales;
this.filteredResourcesProvider = filteredResourcesProvider;
this.rDotTxtPath = rDotTxtPath;
}
// TODO(russell): Add an integration test for packaging string assets
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context, BuildableContext buildableContext) {
if (filteredResourcesProvider.getResDirectories().isEmpty()) {
// There is no zip file, but we still need to provide a consistent hash to
// ComputeExopackageDepsAbi in this case.
buildableContext.addMetadata(STRING_ASSETS_ZIP_HASH, Hashing.sha1().hashInt(0).toString());
return ImmutableList.of();
}
ImmutableList.Builder<Step> steps = ImmutableList.builder();
// We need to generate a zip file with the following dir structure:
// /assets/strings/*.fbstr
Path pathToBaseDir = getPathToStringAssetsDir();
steps.addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), pathToBaseDir));
Path pathToDirContainingAssetsDir = pathToBaseDir.resolve("string_assets");
steps.addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), pathToDirContainingAssetsDir));
final Path pathToStrings = pathToDirContainingAssetsDir.resolve("assets").resolve("strings");
Function<String, Path> assetPathBuilder =
locale -> pathToStrings.resolve(locale + STRING_ASSET_FILE_EXTENSION);
Path pathToStringAssetsZip = getPathToStringAssetsZip();
Path pathToAllLocalesStringAssetsZip = getPathToAllLocalesStringAssetsZip();
steps.addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), pathToStrings));
steps.add(
new CompileStringsStep(
getProjectFilesystem(),
filteredResourcesProvider.getStringFiles(),
context.getSourcePathResolver().getIdeallyRelativePath(rDotTxtPath),
assetPathBuilder));
steps.add(
new ZipStep(
getProjectFilesystem(),
pathToAllLocalesStringAssetsZip,
ImmutableSet.of(),
false,
ZipCompressionLevel.MAX_COMPRESSION_LEVEL,
pathToDirContainingAssetsDir));
steps.add(
new ZipStep(
getProjectFilesystem(),
pathToStringAssetsZip,
locales.stream().map(assetPathBuilder::apply).collect(MoreCollectors.toImmutableSet()),
false,
ZipCompressionLevel.MAX_COMPRESSION_LEVEL,
pathToDirContainingAssetsDir));
steps.add(
new RecordFileSha1Step(
getProjectFilesystem(),
pathToStringAssetsZip,
STRING_ASSETS_ZIP_HASH,
buildableContext));
buildableContext.recordArtifact(pathToAllLocalesStringAssetsZip);
buildableContext.recordArtifact(pathToStringAssetsZip);
return steps.build();
}
Path getPathToStringAssetsZip() {
return getPathToStringAssetsDir().resolve("string_assets.zip");
}
private Path getPathToAllLocalesStringAssetsZip() {
return getPathToStringAssetsDir().resolve("all_locales_string_assets.zip");
}
@Override
public SourcePath getSourcePathToOutput() {
return new ExplicitBuildTargetSourcePath(getBuildTarget(), getPathToStringAssetsDir());
}
private Path getPathToStringAssetsDir() {
return BuildTargets.getScratchPath(
getProjectFilesystem(), getBuildTarget(), STRING_ASSETS_DIR_FORMAT);
}
public SourcePath getSourcePathToStringAssetsZip() {
return new ExplicitBuildTargetSourcePath(getBuildTarget(), getPathToStringAssetsZip());
}
}