diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java new file mode 100644 index 0000000000..0dca5ac607 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java @@ -0,0 +1,321 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// 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.google.devtools.build.lib.rules.android; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.rules.java.JavaUtil; +import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.util.Preconditions; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.Objects; +import javax.annotation.Nullable; + +/** The resources contributed by a single target. */ +@AutoValue +@Immutable +public abstract class ResourceContainer { + /** The type of resource in question: either asset or a resource. */ + public enum ResourceType { + ASSETS("assets"), + RESOURCES("resources"); + + private final String attribute; + + private ResourceType(String attribute) { + this.attribute = attribute; + } + + public String getAttribute() { + return attribute; + } + } + + public abstract Label getLabel(); + + @Nullable + public abstract String getJavaPackage(); + + @Nullable + public abstract String getRenameManifestPackage(); + + public abstract boolean getConstantsInlined(); + + @Nullable + public abstract Artifact getApk(); + + public abstract Artifact getManifest(); + + @Nullable + public abstract Artifact getJavaSourceJar(); + + @Nullable + public abstract Artifact getJavaClassJar(); + + abstract ImmutableList<Artifact> getAssets(); + + abstract ImmutableList<Artifact> getResources(); + + public ImmutableList<Artifact> getArtifacts(ResourceType resourceType) { + return resourceType == ResourceType.ASSETS ? getAssets() : getResources(); + } + + public Iterable<Artifact> getArtifacts() { + return Iterables.concat(getAssets(), getResources()); + } + + abstract ImmutableList<PathFragment> getAssetsRoots(); + + abstract ImmutableList<PathFragment> getResourcesRoots(); + + public ImmutableList<PathFragment> getRoots(ResourceType resourceType) { + return resourceType == ResourceType.ASSETS ? getAssetsRoots() : getResourcesRoots(); + } + + public abstract boolean isManifestExported(); + + @Nullable + public abstract Artifact getRTxt(); + + @Nullable + public abstract Artifact getSymbolsTxt(); + + // The limited hashCode and equals behavior is necessary to avoid duplication when building with + // fat_apk_cpu set. Artifacts generated in different configurations will naturally be different + // and non-equal objects, causing the ResourceContainer not to be automatically deduplicated at + // the android_binary level. + // TODO(bazel-team): deduplicate explicitly and remove hashCode and equals overrides to avoid + // breaking "equals means interchangeable" + @Override + public int hashCode() { + return Objects.hash(getLabel(), getRTxt(), getSymbolsTxt()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResourceContainer)) { + return false; + } + ResourceContainer other = (ResourceContainer) obj; + return Objects.equals(getLabel(), other.getLabel()) + && Objects.equals(getRTxt(), other.getRTxt()) + && Objects.equals(getSymbolsTxt(), other.getSymbolsTxt()); + } + + /** Converts this container back into a builder to create a modified copy. */ + public abstract Builder toBuilder(); + + /** Creates a new builder with default values. */ + public static Builder builder() { + return new AutoValue_ResourceContainer.Builder() + .setJavaPackageFrom(Builder.JavaPackageSource.MANIFEST) + .setConstantsInlined(false) + .setAssets(ImmutableList.<Artifact>of()) + .setResources(ImmutableList.<Artifact>of()) + .setAssetsRoots(ImmutableList.<PathFragment>of()) + .setResourcesRoots(ImmutableList.<PathFragment>of()); + } + + /** + * Creates a new builder with the label, Java package, manifest package override, Java source jar, + * and manifest-export switch set according to the given rule. + */ + public static Builder builderFromRule(RuleContext ruleContext) throws InterruptedException { + return builder().forRuleContext(ruleContext); + } + + /** Builder to construct resource containers. */ + @AutoValue.Builder + public abstract static class Builder { + /** Enum to determine what to do if a package hasn't been manually set. */ + public enum JavaPackageSource { + /** + * Uses the package from the manifest, i.e., the generated ResourceContainer will return null + * from {@link ResourceContainer#getJavaPackage()}. + */ + MANIFEST, + /** + * Uses the package from the path to the source jar (or, if the rule context has it set, + * the {@code custom_package} attribute). If the source jar is not under a valid Java root, + * this will result in an error being added to the rule context. This can only be used if the + * builder was created by {@link ResourceContainer#builderFromRule(RuleContext)}. + */ + SOURCE_JAR_PATH + } + + @Nullable private RuleContext ruleContext; + @Nullable private JavaPackageSource javaPackageSource; + + private Builder forRuleContext(RuleContext ruleContext) throws InterruptedException { + Preconditions.checkNotNull(ruleContext); + this.ruleContext = ruleContext; + return this.setLabel(ruleContext.getLabel()) + .setRenameManifestPackage(getRenameManifestPackage(ruleContext)) + .setJavaSourceJar( + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR)) + .setJavaPackageFrom(JavaPackageSource.SOURCE_JAR_PATH) + .setManifestExported(getExportsManifest(ruleContext)); + } + + /** + * Sets the Java package from the given source. Overrides earlier calls to + * {@link #setJavaPackageFrom(JavaPackageSource)} or {@link #setJavaPackageFromString(String)}. + * + * <p>To set the package from {@link JavaPackageSource#SOURCE_JAR_PATH}, this instance must have + * been created using {@link ResourceContainer#builderFromRule(RuleContext)}. Also in this case, + * the source jar must be set non-{@code null} when the {@link #build()} method is called. + * It defaults to the source jar implicit output when creating a builder out of a rule context. + */ + public Builder setJavaPackageFrom(JavaPackageSource javaPackageSource) { + Preconditions.checkNotNull(javaPackageSource); + Preconditions.checkArgument( + !(javaPackageSource == JavaPackageSource.SOURCE_JAR_PATH && ruleContext == null), + "setJavaPackageFrom(SOURCE_JAR_PATH) is only permitted when using builderFromRule."); + this.javaPackageSource = javaPackageSource; + return this.setJavaPackage(null); + } + + /** + * Sets the Java package from the given string. Overrides earlier calls to + * {@link #setJavaPackageFrom(JavaPackageSource)} or {@link #setJavaPackageFromString(String)}. + * + * <p>To make {@link ResourceContainer#getJavaPackage()} return {@code null}, call + * {@code setJavaPackageFrom(MANIFEST)} instead. + */ + public Builder setJavaPackageFromString(String javaPackageOverride) { + Preconditions.checkNotNull(javaPackageOverride); + this.javaPackageSource = null; + return this.setJavaPackage(javaPackageOverride); + } + + /** + * Sets the assets, resources, asset roots, and resource roots from the given local resource + * container. + * + * <p>This will override any of these values which were previously set directly. + */ + public Builder setAssetsAndResourcesFrom(LocalResourceContainer data) { + return this.setAssets(data.getAssets()) + .setResources(data.getResources()) + .setAssetsRoots(data.getAssetRoots()) + .setResourcesRoots(data.getResourceRoots()); + } + + public abstract Builder setLabel(Label label); + + abstract Builder setJavaPackage(@Nullable String javaPackage); + + public abstract Builder setRenameManifestPackage(@Nullable String renameManifestPackage); + + public abstract Builder setConstantsInlined(boolean constantsInlined); + + public abstract Builder setApk(@Nullable Artifact apk); + + public abstract Builder setManifest(Artifact manifest); + + @Nullable + abstract Artifact getJavaSourceJar(); + + public abstract Builder setJavaSourceJar(@Nullable Artifact javaSourceJar); + + public abstract Builder setJavaClassJar(@Nullable Artifact javaClassJar); + + public abstract Builder setAssets(ImmutableList<Artifact> assets); + + public abstract Builder setResources(ImmutableList<Artifact> resources); + + public abstract Builder setAssetsRoots(ImmutableList<PathFragment> assetsRoots); + + public abstract Builder setResourcesRoots(ImmutableList<PathFragment> resourcesRoots); + + public abstract Builder setManifestExported(boolean manifestExported); + + public abstract Builder setRTxt(@Nullable Artifact rTxt); + + public abstract Builder setSymbolsTxt(@Nullable Artifact symbolsTxt); + + abstract ResourceContainer autoBuild(); + + /** + * Builds and returns the ResourceContainer. + * + * <p>May result in the rule context adding a rule error if the Java package was to be set from + * the source jar path, but the source jar does not have an acceptable Java package. + */ + public ResourceContainer build() { + if (javaPackageSource == JavaPackageSource.SOURCE_JAR_PATH) { + Preconditions.checkState( + !(javaPackageSource == JavaPackageSource.SOURCE_JAR_PATH && ruleContext == null), + "setJavaPackageFrom(SOURCE_JAR_PATH) is only permitted when using builderFromRule."); + Preconditions.checkState( + getJavaSourceJar() != null, + "setJavaPackageFrom(SOURCE_JAR_PATH) was called, but no source jar was set."); + setJavaPackage(getJavaPackageFromSourceJarPath()); + } + return autoBuild(); + } + + @Nullable + private String getJavaPackageFromSourceJarPath() { + if (javaPackageSource != JavaPackageSource.SOURCE_JAR_PATH) { + return null; + } + if (hasCustomPackage(ruleContext)) { + return ruleContext.attributes().get("custom_package", Type.STRING); + } + Artifact rJavaSrcJar = getJavaSourceJar(); + // TODO(bazel-team): JavaUtil.getJavaPackageName does not check to see if the path is valid. + // So we need to check for the JavaRoot. + if (JavaUtil.getJavaRoot(rJavaSrcJar.getExecPath()) == null) { + ruleContext.ruleError( + "The location of your BUILD file determines the Java package used for " + + "Android resource processing. A directory named \"java\" or \"javatests\" will " + + "be used as your Java source root and the path of your BUILD file relative to " + + "the Java source root will be used as the package for Android resource " + + "processing. The Java source root could not be determined for \"" + + ruleContext.getPackageDirectory() + + "\". Move your BUILD file under a java or javatests directory, or set the " + + "'custom_package' attribute."); + } + return JavaUtil.getJavaPackageName(rJavaSrcJar.getExecPath()); + } + + private static boolean hasCustomPackage(RuleContext ruleContext) { + return ruleContext.attributes().isAttributeValueExplicitlySpecified("custom_package"); + } + + private static boolean getExportsManifest(RuleContext ruleContext) { + // AndroidLibraryBaseRule has exports_manifest but AndroidBinaryBaseRule does not. + // ResourceContainers are built for both, so we must check if exports_manifest is present. + return ruleContext.attributes().has("exports_manifest", Type.BOOLEAN) + && ruleContext.attributes().get("exports_manifest", Type.BOOLEAN); + } + + @Nullable + private static String getRenameManifestPackage(RuleContext ruleContext) { + return ruleContext.attributes().isAttributeValueExplicitlySpecified("rename_manifest_package") + ? ruleContext.attributes().get("rename_manifest_package", Type.STRING) + : null; + } + } +} |