/* * Copyright 2017-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.RDotTxtEntry; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.InternalFlavor; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.BuildRules; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.coercer.ManifestEntries; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.util.Collection; import java.util.EnumSet; import java.util.Optional; import org.immutables.value.Value; class AndroidBinaryResourcesGraphEnhancer { static final Flavor RESOURCES_FILTER_FLAVOR = InternalFlavor.of("resources_filter"); static final Flavor AAPT_PACKAGE_FLAVOR = InternalFlavor.of("aapt_package"); static final Flavor AAPT2_LINK_FLAVOR = InternalFlavor.of("aapt2_link"); static final Flavor PACKAGE_STRING_ASSETS_FLAVOR = InternalFlavor.of("package_string_assets"); private static final Flavor MERGE_ASSETS_FLAVOR = InternalFlavor.of("merge_assets"); private final SourcePathRuleFinder ruleFinder; private final FilterResourcesStep.ResourceFilter resourceFilter; private final ResourcesFilter.ResourceCompressionMode resourceCompressionMode; private final ImmutableSet<String> locales; private final BuildRuleParams buildRuleParams; private final BuildRuleResolver ruleResolver; private final SourcePathResolver pathResolver; private final AndroidBinary.AaptMode aaptMode; private final SourcePath manifest; private final Optional<String> resourceUnionPackage; private final boolean shouldBuildStringSourceMap; private final boolean skipCrunchPngs; private final boolean includesVectorDrawables; private final EnumSet<RDotTxtEntry.RType> bannedDuplicateResourceTypes; private final ManifestEntries manifestEntries; private final BuildTarget originalBuildTarget; private final Optional<Arg> postFilterResourcesCmd; public AndroidBinaryResourcesGraphEnhancer( BuildRuleParams buildRuleParams, BuildRuleResolver ruleResolver, BuildTarget originalBuildTarget, SourcePath manifest, AndroidBinary.AaptMode aaptMode, FilterResourcesStep.ResourceFilter resourceFilter, ResourcesFilter.ResourceCompressionMode resourceCompressionMode, ImmutableSet<String> locales, Optional<String> resourceUnionPackage, boolean shouldBuildStringSourceMap, boolean skipCrunchPngs, boolean includesVectorDrawables, EnumSet<RDotTxtEntry.RType> bannedDuplicateResourceTypes, ManifestEntries manifestEntries, Optional<Arg> postFilterResourcesCmd) { this.ruleResolver = ruleResolver; this.ruleFinder = new SourcePathRuleFinder(ruleResolver); this.pathResolver = new SourcePathResolver(ruleFinder); this.resourceFilter = resourceFilter; this.resourceCompressionMode = resourceCompressionMode; this.locales = locales; this.buildRuleParams = buildRuleParams; this.aaptMode = aaptMode; this.manifest = manifest; this.resourceUnionPackage = resourceUnionPackage; this.shouldBuildStringSourceMap = shouldBuildStringSourceMap; this.skipCrunchPngs = skipCrunchPngs; this.includesVectorDrawables = includesVectorDrawables; this.bannedDuplicateResourceTypes = bannedDuplicateResourceTypes; this.manifestEntries = manifestEntries; this.originalBuildTarget = originalBuildTarget; this.postFilterResourcesCmd = postFilterResourcesCmd; } @Value.Immutable @BuckStyleImmutable interface AbstractAndroidBinaryResourcesGraphEnhancementResult { SourcePath getPathToRDotTxt(); Optional<SourcePath> getRDotJavaDir(); SourcePath getPrimaryResourcesApkPath(); SourcePath getAndroidManifestXml(); SourcePath getAaptGeneratedProguardConfigFile(); Optional<PackageStringAssets> getPackageStringAssets(); ImmutableList<BuildRule> getEnhancedDeps(); ImmutableList<SourcePath> getPrimaryApkAssetZips(); ImmutableList<SourcePath> getExoResources(); } public AndroidBinaryResourcesGraphEnhancementResult enhance( AndroidPackageableCollection packageableCollection) throws NoSuchBuildTargetException { ImmutableList.Builder<BuildRule> enhancedDeps = ImmutableList.builder(); AndroidPackageableCollection.ResourceDetails resourceDetails = packageableCollection.getResourceDetails(); ImmutableSortedSet<BuildRule> resourceRules = getTargetsAsRules(resourceDetails.getResourcesWithNonEmptyResDir()); ImmutableCollection<BuildRule> rulesWithResourceDirectories = ruleFinder.filterBuildRuleInputs(resourceDetails.getResourceDirectories()); FilteredResourcesProvider filteredResourcesProvider; boolean needsResourceFiltering = resourceFilter.isEnabled() || resourceCompressionMode.isStoreStringsAsAssets() || !locales.isEmpty(); if (needsResourceFiltering) { ResourcesFilter resourcesFilter = createResourcesFilter(resourceDetails, resourceRules, rulesWithResourceDirectories); ruleResolver.addToIndex(resourcesFilter); filteredResourcesProvider = resourcesFilter; enhancedDeps.add(resourcesFilter); resourceRules = ImmutableSortedSet.of(resourcesFilter); } else { filteredResourcesProvider = new IdentityResourcesProvider( resourceDetails .getResourceDirectories() .stream() .map( sourcePath -> buildRuleParams .getProjectFilesystem() .relativize(pathResolver.getAbsolutePath(sourcePath))) .collect(MoreCollectors.toImmutableList())); } AaptOutputInfo aaptOutputInfo; switch (aaptMode) { case AAPT1: { // Create the AaptPackageResourcesBuildable. AaptPackageResources aaptPackageResources = createAaptPackageResources(resourceDetails, filteredResourcesProvider); ruleResolver.addToIndex(aaptPackageResources); enhancedDeps.add(aaptPackageResources); aaptOutputInfo = aaptPackageResources.getAaptOutputInfo(); } break; case AAPT2: { Aapt2Link aapt2Link = createAapt2Link(resourceDetails); ruleResolver.addToIndex(aapt2Link); enhancedDeps.add(aapt2Link); aaptOutputInfo = aapt2Link.getAaptOutputInfo(); } break; default: throw new RuntimeException("Unexpected aaptMode: " + aaptMode); } Optional<PackageStringAssets> packageStringAssets = Optional.empty(); ImmutableList.Builder<SourcePath> primaryApkAssetZips = ImmutableList.builder(); if (resourceCompressionMode.isStoreStringsAsAssets()) { packageStringAssets = Optional.of( createPackageStringAssets( resourceRules, rulesWithResourceDirectories, filteredResourcesProvider, aaptOutputInfo)); ruleResolver.addToIndex(packageStringAssets.get()); enhancedDeps.add(packageStringAssets.get()); primaryApkAssetZips.add(packageStringAssets.get().getSourcePathToStringAssetsZip()); } MergeAssets mergeAssets = createMergeAssetsRule( packageableCollection.getAssetsDirectories(), aaptOutputInfo.getPrimaryResourcesApkPath()); ruleResolver.addToIndex(mergeAssets); enhancedDeps.add(mergeAssets); return AndroidBinaryResourcesGraphEnhancementResult.builder() .setAaptGeneratedProguardConfigFile(aaptOutputInfo.getAaptGeneratedProguardConfigFile()) .setAndroidManifestXml(aaptOutputInfo.getAndroidManifestXml()) .setPathToRDotTxt(aaptOutputInfo.getPathToRDotTxt()) .setRDotJavaDir(aaptOutputInfo.getRDotJavaDir()) .setPrimaryResourcesApkPath(mergeAssets.getSourcePathToOutput()) .setPrimaryApkAssetZips(primaryApkAssetZips.build()) .setPackageStringAssets(packageStringAssets) .setEnhancedDeps(enhancedDeps.build()) .setExoResources(ImmutableList.of()) .build(); } private Aapt2Link createAapt2Link(AndroidPackageableCollection.ResourceDetails resourceDetails) throws NoSuchBuildTargetException { ImmutableList.Builder<Aapt2Compile> compileListBuilder = ImmutableList.builder(); for (BuildTarget resTarget : resourceDetails.getResourcesWithNonEmptyResDir()) { compileListBuilder.add( (Aapt2Compile) ruleResolver.requireRule( resTarget.withAppendedFlavors(AndroidResourceDescription.AAPT2_COMPILE_FLAVOR))); } return new Aapt2Link( buildRuleParams .withAppendedFlavor(AAPT2_LINK_FLAVOR) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of())), ruleFinder, compileListBuilder.build(), getTargetsAsResourceDeps(resourceDetails.getResourcesWithNonEmptyResDir()), manifest, manifestEntries, resourceUnionPackage, bannedDuplicateResourceTypes); } private ResourcesFilter createResourcesFilter( AndroidPackageableCollection.ResourceDetails resourceDetails, ImmutableSortedSet<BuildRule> resourceRules, ImmutableCollection<BuildRule> rulesWithResourceDirectories) { return new ResourcesFilter( buildRuleParams .withAppendedFlavor(RESOURCES_FILTER_FLAVOR) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(resourceRules) .addAll(rulesWithResourceDirectories) .build()), Suppliers.ofInstance(ImmutableSortedSet.of())), resourceDetails.getResourceDirectories(), ImmutableSet.copyOf(resourceDetails.getWhitelistedStringDirectories()), locales, resourceCompressionMode, resourceFilter, postFilterResourcesCmd); } private AaptPackageResources createAaptPackageResources( AndroidPackageableCollection.ResourceDetails resourceDetails, FilteredResourcesProvider filteredResourcesProvider) { return new AaptPackageResources( buildRuleParams .withAppendedFlavor(AAPT_PACKAGE_FLAVOR) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of())), ruleFinder, ruleResolver, manifest, filteredResourcesProvider, getTargetsAsResourceDeps(resourceDetails.getResourcesWithNonEmptyResDir()), resourceUnionPackage, shouldBuildStringSourceMap, skipCrunchPngs, includesVectorDrawables, bannedDuplicateResourceTypes, manifestEntries); } private PackageStringAssets createPackageStringAssets( ImmutableSortedSet<BuildRule> resourceRules, ImmutableCollection<BuildRule> rulesWithResourceDirectories, FilteredResourcesProvider filteredResourcesProvider, AaptOutputInfo aaptOutputInfo) { return new PackageStringAssets( buildRuleParams .withAppendedFlavor(PACKAGE_STRING_ASSETS_FLAVOR) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(ruleFinder.filterBuildRuleInputs(aaptOutputInfo.getPathToRDotTxt())) .addAll(resourceRules) .addAll(rulesWithResourceDirectories) // Model the dependency on the presence of res directories, which, in the // case of resource filtering, is cached by the `ResourcesFilter` rule. .addAll( Iterables.filter( ImmutableList.of(filteredResourcesProvider), BuildRule.class)) .build()), Suppliers.ofInstance(ImmutableSortedSet.of())), locales, filteredResourcesProvider, aaptOutputInfo.getPathToRDotTxt()); } private MergeAssets createMergeAssetsRule( ImmutableSet<SourcePath> assetsDirectories, SourcePath aaptOutputApk) { MergeAssets mergeAssets = new MergeAssets( buildRuleParams .withAppendedFlavor(MERGE_ASSETS_FLAVOR) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of())), ruleFinder, Optional.of(aaptOutputApk), ImmutableSortedSet.copyOf(assetsDirectories)); ruleResolver.addToIndex(mergeAssets); return mergeAssets; } private ImmutableSortedSet<BuildRule> getTargetsAsRules(Collection<BuildTarget> buildTargets) { return BuildRules.toBuildRulesFor(originalBuildTarget, ruleResolver, buildTargets); } private ImmutableList<HasAndroidResourceDeps> getTargetsAsResourceDeps( Collection<BuildTarget> targets) { return getTargetsAsRules(targets) .stream() .map( input -> { Preconditions.checkState(input instanceof HasAndroidResourceDeps); return (HasAndroidResourceDeps) input; }) .collect(MoreCollectors.toImmutableList()); } }