// Copyright 2015 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 static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_KEYED_STRING_DICT;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static com.google.devtools.build.lib.packages.BuildType.TRISTATE;
import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromTemplates;
import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
import static com.google.devtools.build.lib.packages.Type.INTEGER;
import static com.google.devtools.build.lib.packages.Type.STRING;
import static com.google.devtools.build.lib.packages.Type.STRING_DICT;
import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
import static com.google.devtools.build.lib.util.FileTypeSet.NO_FILE;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.TransitionFactories;
import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
import com.google.devtools.build.lib.packages.Attribute.LabelLateBoundDefault;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ConfigurationDistinguisher;
import com.google.devtools.build.lib.rules.android.databinding.DataBinding;
import com.google.devtools.build.lib.rules.config.ConfigFeatureFlagProvider;
import com.google.devtools.build.lib.rules.cpp.CcInfo;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration;
import com.google.devtools.build.lib.rules.cpp.CppOptions;
import com.google.devtools.build.lib.rules.java.JavaConfiguration;
import com.google.devtools.build.lib.rules.java.JavaInfo;
import com.google.devtools.build.lib.rules.java.JavaRuleClasses;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.starlarkbuildapi.android.AndroidSplitTransititionApi;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;
import java.util.List;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.StarlarkInt;

/** Rule definitions for Android rules. */
public final class AndroidRuleClasses {
  /** Sources generated by a given target, in particular, {@code R.java}. */
  public static final SafeImplicitOutputsFunction ANDROID_JAVA_SOURCE_JAR =
      fromTemplates("%{name}.srcjar");
  /** Sources compiled in a given target, excluding {@link #ANDROID_JAVA_SOURCE_JAR}. */
  public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_SOURCE_JAR =
      JavaSemantics.JAVA_LIBRARY_SOURCE_JAR;
  /**
   * Compiled sources of a given target, excluding {@link #ANDROID_JAVA_SOURCE_JAR}. This is the
   * conventional output Jar of any java library target, including android libs.
   */
  public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_CLASS_JAR =
      JavaSemantics.JAVA_LIBRARY_CLASS_JAR;

  public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_AAR =
      fromTemplates("%{name}.aar");
  // TODO(b/30307842): Remove this once it is no longer needed for resources migration.
  public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_APK =
      fromTemplates("%{name}_files/library.ap_");

  /**
   * Source Jar for {@link #ANDROID_RESOURCES_CLASS_JAR}, which should be the same as {@link
   * #ANDROID_JAVA_SOURCE_JAR}.
   */
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_SOURCE_JAR =
      fromTemplates("%{name}_resources-src.jar");
  /** Compiled {@link #ANDROID_JAVA_SOURCE_JAR}. */
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_CLASS_JAR =
      fromTemplates("%{name}_resources.jar");

  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_APK =
      fromTemplates("%{name}.ap_");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_AAPT2_LIBRARY_APK =
      fromTemplates("%{name}_files/aapt2_library.apk");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_AAPT2_R_TXT =
      fromTemplates("%{name}_symbols/R.aapt2.txt");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_AAPT2_VALIDATION_ARTIFACT =
      fromTemplates("%{name}_symbols/aapt2.validation.txt");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_AAPT2_SOURCE_JAR =
      fromTemplates("%{name}_files/%{name}_resources_aapt2-src.jar");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_SHRUNK_APK =
      fromTemplates("%{name}_shrunk.ap_");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_OPTIMIZED_APK =
      fromTemplates("%{name}_optimized.ap_");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_ZIP =
      fromTemplates("%{name}_files/resource_files.zip");
  public static final SafeImplicitOutputsFunction ANDROID_ASSETS_ZIP =
      fromTemplates("%{name}_files/assets.zip");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCES_SHRUNK_ZIP =
      fromTemplates("%{name}_files/resource_files_shrunk.zip");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCE_SHRINKER_LOG =
      fromTemplates("%{name}_files/resource_shrinker.log");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCE_OPTIMIZATION_CONFIG =
      fromTemplates("%{name}_files/resource_optimization.cfg");
  public static final SafeImplicitOutputsFunction ANDROID_RESOURCE_PATH_SHORTENING_MAP =
      fromTemplates("%{name}_resource_paths.map");
  public static final SafeImplicitOutputsFunction ANDROID_INCREMENTAL_RESOURCES_APK =
      fromTemplates("%{name}_files/incremental.ap_");
  public static final SafeImplicitOutputsFunction ANDROID_BINARY_APK = fromTemplates("%{name}.apk");
  public static final SafeImplicitOutputsFunction ANDROID_BINARY_INCREMENTAL_APK =
      fromTemplates("%{name}_incremental.apk");
  public static final SafeImplicitOutputsFunction ANDROID_BINARY_UNSIGNED_APK =
      fromTemplates("%{name}_unsigned.apk");
  public static final SafeImplicitOutputsFunction ANDROID_BINARY_DEPLOY_JAR =
      fromTemplates("%{name}_deploy.jar");
  public static final SafeImplicitOutputsFunction ANDROID_BINARY_PROGUARD_JAR =
      fromTemplates("%{name}_proguard.jar");
  public static final SafeImplicitOutputsFunction ANDROID_BINARY_INSTRUMENTED_JAR =
      fromTemplates("%{name}_instrumented.jar");
  public static final SafeImplicitOutputsFunction ANDROID_TEST_FILTERED_JAR =
      fromTemplates("%{name}_filtered.jar");
  public static final SafeImplicitOutputsFunction ANDROID_R_TXT =
      fromTemplates("%{name}_symbols/R.txt");
  public static final SafeImplicitOutputsFunction ANDROID_LOCAL_SYMBOLS =
      fromTemplates("%{name}_symbols/local.bin");
  public static final SafeImplicitOutputsFunction ANDROID_MERGED_SYMBOLS =
      fromTemplates("%{name}_symbols/merged.bin");
  public static final SafeImplicitOutputsFunction ANDROID_ASSET_SYMBOLS =
      fromTemplates("%{name}_symbols/assets.bin");
  public static final SafeImplicitOutputsFunction ANDROID_COMPILED_SYMBOLS =
      fromTemplates("%{name}_symbols/symbols.zip");
  public static final SafeImplicitOutputsFunction ANDROID_ASSET_COMPILED_SYMBOLS =
      fromTemplates("%{name}_symbols/assets.zip");
  public static final SafeImplicitOutputsFunction ANDROID_SYMLINKED_MANIFEST =
      fromTemplates("%{name}_symlinked_manifest/AndroidManifest.xml");
  public static final SafeImplicitOutputsFunction ANDROID_PROCESSED_MANIFEST =
      fromTemplates("%{name}_processed_manifest/AndroidManifest.xml");
  public static final SafeImplicitOutputsFunction MOBILE_INSTALL_STUB_APPLICATION_MANIFEST =
      fromTemplates("%{name}_files/mobile_install/AndroidManifest.xml");
  public static final SafeImplicitOutputsFunction FULL_DEPLOY_MARKER =
      fromTemplates("%{name}_files/full_deploy_marker");
  public static final SafeImplicitOutputsFunction INCREMENTAL_DEPLOY_MARKER =
      fromTemplates("%{name}_files/incremental_deploy_marker");
  public static final SafeImplicitOutputsFunction SPLIT_DEPLOY_MARKER =
      fromTemplates("%{name}_files/split_deploy_marker");
  public static final SafeImplicitOutputsFunction MOBILE_INSTALL_ARGS =
      fromTemplates("%{name}_files/mobile_install_args");
  public static final SafeImplicitOutputsFunction DEPLOY_INFO =
      fromTemplates("%{name}_files/deploy_info.deployinfo.pb");
  public static final SafeImplicitOutputsFunction DEPLOY_INFO_INCREMENTAL =
      fromTemplates("%{name}_files/deploy_info_incremental.deployinfo.pb");
  public static final SafeImplicitOutputsFunction DEPLOY_INFO_SPLIT =
      fromTemplates("%{name}_files/deploy_info_split.deployinfo.pb");
  public static final SafeImplicitOutputsFunction REX_OUTPUT_PACKAGE_MAP =
      fromTemplates("%{name}_rex/rex_output_package.map");
  public static final SafeImplicitOutputsFunction INSTRUMENTATION_TEST_CHECK_RESULTS =
      fromTemplates("%{name}_files/instrumentation_test_check_results.txt");

  // This needs to be in its own directory because ApkBuilder only has a function (-rf) for source
  // folders but not source files, and it's easiest to guarantee that nothing gets put beside this
  // file in the ApkBuilder invocation in this manner
  public static final SafeImplicitOutputsFunction MOBILE_INSTALL_STUB_APPLICATION_DATA =
      fromTemplates("%{name}_files/stub_application_data/stub_application_data.txt");
  public static final SafeImplicitOutputsFunction DEX_MANIFEST =
      fromTemplates("%{name}_files/dexmanifest.txt");
  public static final SafeImplicitOutputsFunction JAVA_RESOURCES_JAR =
      fromTemplates("%{name}_files/java_resources.jar");
  public static final String BUILD_INCREMENTAL_DEXMANIFEST_LABEL =
      "//tools/android:build_incremental_dexmanifest";
  public static final String STUBIFY_MANIFEST_LABEL = "//tools/android:stubify_manifest";
  public static final String INCREMENTAL_INSTALL_LABEL = "//tools/android:incremental_install";
  public static final String BUILD_SPLIT_MANIFEST_LABEL = "//tools/android:build_split_manifest";
  public static final String STRIP_RESOURCES_LABEL = "//tools/android:strip_resources";
  public static final String DEFAULT_INCREMENTAL_STUB_APPLICATION =
      "//tools/android:incremental_stub_application";
  public static final String DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION =
      "//tools/android:incremental_split_stub_application";
  public static final String DEFAULT_RESOURCES_BUSYBOX = "//tools/android:busybox";
  public static final String DEFAULT_SDK = "//tools/android:sdk";
  public static final SafeImplicitOutputsFunction ANDROID_DEVICE_USERDATA_IMAGES =
      fromTemplates("%{name}_images/userdata_images.dat");
  public static final SafeImplicitOutputsFunction ANDROID_DEVICE_EMULATOR_METADATA =
      fromTemplates("%{name}_images/emulator-meta-data.pb");
  static final FileType APK = FileType.of(".apk");

  public static final String NOCOMPRESS_EXTENSIONS_ATTR = "nocompress_extensions";

  public static final ImmutableList<StarlarkProviderIdentifier> CONTAINS_CC_INFO_PARAMS =
      ImmutableList.of(StarlarkProviderIdentifier.forKey(CcInfo.PROVIDER.getKey()));

  /** The default label of android_sdk option */
  public static LabelLateBoundDefault<?> getAndroidSdkLabel(Label androidSdk) {
    return LabelLateBoundDefault.fromTargetConfiguration(
        AndroidConfiguration.class,
        androidSdk,
        (rule, attributes, configuration) -> configuration.getSdk());
  }

  @AutoCodec
  public static final AndroidSplitTransition ANDROID_SPLIT_TRANSITION =
      new AndroidSplitTransition();

  @AutoCodec
  static final LabelLateBoundDefault<AndroidConfiguration> LEGACY_MAIN_DEX_LIST_GENERATOR =
      LabelLateBoundDefault.fromTargetConfiguration(
          AndroidConfiguration.class,
          null,
          (rule, attributes, androidConfig) -> androidConfig.getLegacyMainDexListGenerator());

  /** Android Split configuration transition for properly handling native dependencies */
  public static final class AndroidSplitTransition
      implements SplitTransition, AndroidSplitTransititionApi {
    private static void setCrosstoolToAndroid(BuildOptionsView options) {
      AndroidConfiguration.Options androidOptions = options.get(AndroidConfiguration.Options.class);

      CppOptions cppOptions = options.get(CppOptions.class);
      if (androidOptions.androidCrosstoolTop != null) {
        cppOptions.crosstoolTop = androidOptions.androidCrosstoolTop;
      }

      androidOptions.configurationDistinguisher = ConfigurationDistinguisher.ANDROID;
    }

    @Override
    public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() {
      return ImmutableSet.of(
          AndroidConfiguration.Options.class,
          CoreOptions.class,
          CppOptions.class,
          PlatformOptions.class);
    }

    @Override
    public ImmutableMap<String, BuildOptions> split(
        BuildOptionsView buildOptions, EventHandler eventHandler) {

      AndroidConfiguration.Options androidOptions =
          buildOptions.get(AndroidConfiguration.Options.class);
      CppOptions cppOptions = buildOptions.get(CppOptions.class);
      Label androidCrosstoolTop = androidOptions.androidCrosstoolTop;

      if (androidOptions.fatApkCpus.isEmpty()) {

        if (androidOptions.cpu.isEmpty()
            || androidCrosstoolTop == null
            || androidCrosstoolTop.equals(cppOptions.crosstoolTop)) {
          return ImmutableMap.of(
              buildOptions.get(CoreOptions.class).cpu, buildOptions.underlying());

        } else {

          BuildOptionsView splitOptions = buildOptions.clone();
          splitOptions.get(CoreOptions.class).cpu = androidOptions.cpu;
          setCommonAndroidOptions(androidOptions, splitOptions);
          return ImmutableMap.of(androidOptions.cpu, splitOptions.underlying());
        }

      } else {

        ImmutableMap.Builder<String, BuildOptions> result = ImmutableMap.builder();
        for (String cpu : ImmutableSortedSet.copyOf(androidOptions.fatApkCpus)) {
          BuildOptionsView splitOptions = buildOptions.clone();
          // Disable fat APKs for the child configurations.
          splitOptions.get(AndroidConfiguration.Options.class).fatApkCpus = ImmutableList.of();

          // Set the cpu & android_cpu.
          // TODO(bazel-team): --android_cpu doesn't follow --cpu right now; it should.
          splitOptions.get(AndroidConfiguration.Options.class).cpu = cpu;
          splitOptions.get(CoreOptions.class).cpu = cpu;
          setCommonAndroidOptions(androidOptions, splitOptions);
          result.put(cpu, splitOptions.underlying());

          if (cpu.equals("arm64-v8a") && androidOptions.fatApkHwasan) {
            BuildOptionsView hwasanSplitOptions = splitOptions.clone();

            // Setting the fatApkSplitSanitizer has these consequences:
            // - changes the native library install directory to lib/arm64-v8a-hwasan
            // - adds "hwasan" to the default feature set such that it gets removed when
            //   transitioning to host or exec configurations as a consequence of
            //   CoreOptions.getHost() not copying the fatApkSplitSanitizer field (which rules out
            //   adding it to defaultFeatures, which gets copied).
            hwasanSplitOptions.get(CoreOptions.class).fatApkSplitSanitizer =
                CoreOptions.FatApkSplitSanitizer.HWASAN;

            result.put(cpu + "-hwasan", hwasanSplitOptions.underlying());
          }
        }
        return result.build();
      }
    }

    private void setCommonAndroidOptions(
        AndroidConfiguration.Options androidOptions, BuildOptionsView newOptions) {
      newOptions.get(CppOptions.class).cppCompiler = androidOptions.cppCompiler;
      newOptions.get(CppOptions.class).libcTopLabel = androidOptions.androidLibcTopLabel;
      newOptions.get(CppOptions.class).dynamicMode = androidOptions.dynamicMode;
      setCrosstoolToAndroid(newOptions);

      // Ensure platforms aren't set so that platform mapping can take place.
      newOptions.get(PlatformOptions.class).platforms = ImmutableList.of();
    }

    @Override
    public boolean isImmutable() {
      return true;
    }

    @Override
    public void repr(Printer printer) {
      printer.append("android_common.multi_cpu_configuration");
    }
  }

  public static final FileType ANDROID_IDL = FileType.of(".aidl");

  public static final String[] ALLOWED_DEPENDENCIES = {
    "aar_import",
    "android_library",
    "cc_library",
    "java_import",
    "java_library",
    "java_lite_proto_library",
  };

  public static boolean hasProguardSpecs(AttributeMap rule) {
    // The below is a hack to support configurable attributes (proguard_specs seems like
    // too valuable an attribute to make nonconfigurable, and we don't currently
    // have the ability to know the configuration when determining implicit outputs). So if the
    // attribute is configurable, we create the proguard implicit output. At analysis time, we know
    // the actual value of proguard_specs, and if it is empty we do not use the proguarded jar for
    // dexing. If the user explicitly tries to build the proguard jar, it will fail.
    //
    // TODO(bazel-team): find a stronger approach for this. One simple approach is to somehow
    // receive 'rule' as an AggregatingAttributeMapper instead of a RawAttributeMapper,
    // check that all possible values are non-empty, and simply don't support configurable
    // instances that mix empty and non-empty lists. A more ambitious approach would be
    // to somehow determine implicit outputs after the configuration is known. A third
    // approach is to refactor the Android rule logic to avoid these dependencies in the
    // first place.
    return rule.isConfigurable("proguard_specs")
        || !rule.get("proguard_specs", LABEL_LIST).isEmpty();
  }

  public static final SafeImplicitOutputsFunction ANDROID_BINARY_IMPLICIT_OUTPUTS =
      new SafeImplicitOutputsFunction() {

        @Override
        public Iterable<String> getImplicitOutputs(EventHandler eventHandler, AttributeMap rule) {
          List<SafeImplicitOutputsFunction> functions = Lists.newArrayList();
          functions.add(AndroidRuleClasses.ANDROID_BINARY_APK);
          functions.add(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK);
          functions.add(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR);

          if (hasProguardSpecs(rule)) {
            functions.add(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_JAR);
            functions.add(JavaSemantics.JAVA_BINARY_PROGUARD_CONFIG);
            if (ProguardHelper.genProguardMapping(rule)) {
              functions.add(JavaSemantics.JAVA_BINARY_PROGUARD_MAP);
            }
          }
          return fromFunctions(functions).getImplicitOutputs(eventHandler, rule);
        }
      };

  public static final SafeImplicitOutputsFunction ANDROID_LIBRARY_IMPLICIT_OUTPUTS =
      new SafeImplicitOutputsFunction() {
        @Override
        public Iterable<String> getImplicitOutputs(
            EventHandler eventHandler, AttributeMap attributes) {

          ImmutableList.Builder<SafeImplicitOutputsFunction> implicitOutputs =
              ImmutableList.builder();

          implicitOutputs.add(
              AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR,
              AndroidRuleClasses.ANDROID_LIBRARY_SOURCE_JAR,
              AndroidRuleClasses.ANDROID_LIBRARY_AAR /* TODO(b/36518935): remove this */);

          if (AndroidResources.definesAndroidResources(attributes)) {
            implicitOutputs.add(
                AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR,
                AndroidRuleClasses.ANDROID_R_TXT,
                AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR);
          }

          return fromFunctions(implicitOutputs.build())
              .getImplicitOutputs(eventHandler, attributes);
        }
      };

  /** Base class for rule definitions that support resource declarations. */
  public static final class AndroidResourceSupportRule implements RuleDefinition {
    @Override
    public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
      return builder
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(manifest) -->
          The name of the Android manifest file, normally <code>AndroidManifest.xml</code>.
          Must be defined if resource_files or assets are defined.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("manifest", LABEL).allowedFileTypes(FileType.of(".xml")))
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(resource_files) -->
          The list of resources to be packaged.
          This is typically a <code>glob</code> of all files under the
          <code>res</code> directory.
          <br/>
          Generated files (from genrules) can be referenced by
          <a href="../build-ref.html#labels">Label</a> here as well. The only restriction is that
          the generated outputs must be under the same "<code>res</code>" directory as any other
          resource files that are included.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("resource_files", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE))
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(assets_dir) -->
          The string giving the path to the files in <code>assets</code>.
          The pair <code>assets</code> and <code>assets_dir</code> describe packaged
          assets and either both attributes should be provided or none of them.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("assets_dir", STRING))
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(assets) -->
          The list of assets to be packaged.
          This is typically a <code>glob</code> of all files under the
          <code>assets</code> directory. You can also reference other rules (any rule that produces
          files) or exported files in the other packages, as long as all those files are under the
          <code>assets_dir</code> directory in the corresponding package.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("assets", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE))
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(inline_constants) -->
          Let the compiler inline the constants defined in the generated java sources.
          This attribute must be set to 0 for all <code>android_library</code> rules
          used directly by an <code>android_binary</code>,
          and for any <code>android_binary</code> that has an <code>android_library</code>
          in its transitive closure.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("inline_constants", BOOLEAN)
                  .undocumented("deprecated noop on library")
                  .value(false))
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(custom_package) -->
          Java package for which java sources will be generated.
          By default the package is inferred from the directory where the BUILD file
          containing the rule is. You can specify a different package but this is
          highly discouraged since it can introduce classpath conflicts with other
          libraries that will only be detected at runtime.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("custom_package", STRING))
          /* <!-- #BLAZE_RULE($android_resource_support).ATTRIBUTE(enable_data_binding) -->
          If true, this rule processes
          <a href="https://developer.android.com/topic/libraries/data-binding/index.html">data
          binding</a> expressions in layout resources included through the
          <a href="${link android_binary.resource_files}">resource_files</a> attribute. Without this
          setting, data binding expressions produce build failures.
          <p>
          To build an Android app with data binding, you must also do the following:
          <ol>
            <li>Set this attribute for all Android rules that transitively depend on this one.
              This is because dependers inherit the rule's data binding expressions through resource
              merging. So they also need to build with data binding to parse those expressions.
            <li>Add a <code>deps =</code> entry for the data binding runtime library to all targets
            that set this attribute. The location of this library depends on your depot setup.
          </ol>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("enable_data_binding", Type.BOOLEAN))
          // The javac annotation processor from Android's data binding library that turns
          // processed XML expressions into Java code.
          .add(
              attr(DataBinding.DATABINDING_ANNOTATION_PROCESSOR_ATTR, LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .value(env.getToolsLabel("//tools/android:databinding_annotation_processor")))
          .add(
              attr(DataBinding.DATABINDING_EXEC_PROCESSOR_ATTR, LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:databinding_exec")))
          .advertiseStarlarkProvider(
              StarlarkProviderIdentifier.forKey(AndroidResourcesInfo.PROVIDER.getKey()))
          .advertiseStarlarkProvider(
              StarlarkProviderIdentifier.forKey(AndroidNativeLibsInfo.PROVIDER.getKey()))
          .build();
    }

    @Override
    public Metadata getMetadata() {
      return RuleDefinition.Metadata.builder()
          .name("$android_resource_support")
          .type(RuleClassType.ABSTRACT)
          .ancestors(AndroidRuleClasses.AndroidBaseRule.class)
          .build();
    }
  }

  /** Base class for Android rule definitions. */
  public static final class AndroidBaseRule implements RuleDefinition {
    @Override
    public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
      return builder
          .add(
              attr(":android_sdk", LABEL)
                  .allowedRuleClasses("android_sdk")
                  .value(getAndroidSdkLabel(env.getToolsLabel(AndroidRuleClasses.DEFAULT_SDK))))
          /* <!-- #BLAZE_RULE($android_base).ATTRIBUTE(plugins) -->
          Java compiler plugins to run at compile-time.
          Every <code>java_plugin</code> specified in
          the plugins attribute will be run whenever
          this target is built.  Resources generated by
          the plugin will be included in the result jar of
          the target.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("plugins", LABEL_LIST)
                  .cfg(ExecutionTransitionFactory.create())
                  .allowedRuleClasses("java_plugin")
                  .legacyAllowAnyFileType())
          .add(
              attr(":java_plugins", LABEL_LIST)
                  .cfg(ExecutionTransitionFactory.create())
                  .allowedRuleClasses("java_plugin")
                  .silentRuleClassFilter()
                  .value(JavaSemantics.JAVA_PLUGINS))
          /* <!-- #BLAZE_RULE($android_base).ATTRIBUTE(javacopts) -->
          Extra compiler options for this target.
          Subject to <a href="${link make-variables}">"Make variable"</a> substitution and
          <a href="${link common-definitions#sh-tokenization}">Bourne shell tokenization</a>.
          <p>
          These compiler options are passed to javac after the global compiler options.</p>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("javacopts", STRING_LIST))
          .add(
              attr("$idlclass", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:IdlClass")))
          .add(
              attr("$desugar_java8_extra_bootclasspath", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .value(env.getToolsLabel("//tools/android:desugar_java8_extra_bootclasspath")))
          .add(
              attr("$android_resources_busybox", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel(DEFAULT_RESOURCES_BUSYBOX)))
          .build();
    }

    @Override
    public Metadata getMetadata() {
      return RuleDefinition.Metadata.builder()
          .name("$android_base")
          .type(RuleClassType.ABSTRACT)
          .ancestors(BaseRuleClasses.RuleBase.class)
          .build();
    }
  }

  /** Base class for Android rule definitions that produce binaries. */
  public static final class AndroidBinaryBaseRule implements RuleDefinition {

    private final AndroidNeverlinkAspect androidNeverlinkAspect;
    private final DexArchiveAspect dexArchiveAspect;

    public AndroidBinaryBaseRule(
        AndroidNeverlinkAspect androidNeverlinkAspect, DexArchiveAspect dexArchiveAspect) {
      this.androidNeverlinkAspect = androidNeverlinkAspect;
      this.dexArchiveAspect = dexArchiveAspect;
    }

    @Override
    public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
      return builder
          .requiresConfigurationFragments(
              AndroidConfiguration.class, JavaConfiguration.class, CppConfiguration.class)
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(srcs) -->
          The list of source files that are processed to create the target.
          <p><code>srcs</code> files of type <code>.java</code> are compiled.
          <em>For readability's sake</em>, it is not good to put the name of a
          generated <code>.java</code> source file into the <code>srcs</code>.
          Instead, put the depended-on rule name in the <code>srcs</code>, as
          described below.
          </p>
          <p><code>srcs</code> files of type <code>.srcjar</code> are unpacked and
          compiled. (This is useful if you need to generate a set of .java files with
          a genrule or build extension.)
          </p>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("srcs", LABEL_LIST)
                  .direct_compile_time_input()
                  .allowedFileTypes(JavaSemantics.JAVA_SOURCE, JavaSemantics.SOURCE_JAR))
          // manifest is required for android_binary to ensure that we have an Android package.
          .override(builder.copy("manifest").mandatory())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(deps) -->
          The list of other libraries to be linked in to the binary target.
          Permitted library types are: <code>android_library</code>,
          <code>java_library</code> with <code>android</code> constraint and
          <code>cc_library</code> wrapping or producing <code>.so</code> native libraries for the
          Android target platform.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .override(
              builder
                  .copy("deps")
                  .cfg(TransitionFactories.of(ANDROID_SPLIT_TRANSITION))
                  .allowedRuleClasses(ALLOWED_DEPENDENCIES)
                  .allowedFileTypes()
                  .mandatoryProviders(CONTAINS_CC_INFO_PARAMS)
                  .mandatoryProviders(JavaRuleClasses.CONTAINS_JAVA_PROVIDER)
                  .mandatoryProviders(
                      StarlarkProviderIdentifier.forKey(AndroidResourcesInfo.PROVIDER.getKey()),
                      StarlarkProviderIdentifier.forKey(AndroidAssetsInfo.PROVIDER.getKey()))
                  .aspect(androidNeverlinkAspect)
                  .aspect(dexArchiveAspect, DexArchiveAspect.PARAM_EXTRACTOR))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(debug_key) -->
          File containing the debug keystore to be used to sign the debug apk. Usually you do not
          want to use a key other than the default key, so this attribute should be omitted.
          <p><em class="harmful">WARNING: Do not use your production keys, they should be
          strictly safeguarded and not kept in your source tree</em>.</p>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("debug_key", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .legacyAllowAnyFileType()
                  .value(env.getToolsLabel("//tools/android:debug_keystore")))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(debug_signing_keys) -->
          List of files, debug keystores to be used to sign the debug apk. Usually you do not
          want to use keys other than the default key, so this attribute should be omitted.
          <p><em class="harmful">WARNING: Do not use your production keys, they should be
          strictly safeguarded and not kept in your source tree</em>.</p>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("debug_signing_keys", LABEL_LIST)
                  .cfg(ExecutionTransitionFactory.create())
                  .legacyAllowAnyFileType())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(debug_signing_lineage_file) -->
          File containing the signing lineage for the debug_signing_keys. Usually you do not
          want to use keys other than the default key, so this attribute should be omitted.
          <p><em class="harmful">WARNING: Do not use your production keys, they should be
          strictly safeguarded and not kept in your source tree</em>.</p>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("debug_signing_lineage_file", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .legacyAllowAnyFileType())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(nocompress_extensions) -->
          A list of file extension to leave uncompressed in apk.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("nocompress_extensions", STRING_LIST))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(crunch_png) -->
          Do PNG crunching (or not). This is independent of nine-patch processing, which is always
          done. This is a deprecated workaround for an
          <a href="https://code.google.com/p/android/issues/detail?id=74334">aapt bug</a> which has
          been fixed in aapt2.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("crunch_png", BOOLEAN).value(true))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(resource_configuration_filters) -->
          A list of resource configuration filters, such 'en' that will limit the resources in the
          apk to only the ones in the 'en' configuration.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr(ResourceFilterFactory.RESOURCE_CONFIGURATION_FILTERS_NAME, STRING_LIST))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(shrink_resources) -->
          Whether to perform resource shrinking. Resources that are not used by the binary will be
          removed from the APK. This is only supported for rules using local resources (i.e. the
          <code>manifest</code> and <code>resource_files</code> attributes) and requires ProGuard.
          It operates in mostly the same manner as the Gradle resource shrinker
          (https://developer.android.com/studio/build/shrink-code.html#shrink-resources).
          <p>Notable differences:
          <ul>
            <li>resources in <code>values/</code> will be removed as well as file based
                resources</li>
            <li>uses <code>strict mode</code> by default</li>
            <li>removing unused ID resources is only supported with aapt2</li>
          </ul>
          If resource shrinking is enabled, <code><var>name</var>_files/resource_shrinker.log</code>
          will also be generated, detailing the analysis and deletions performed.
          <p>Possible values:
          <ul>
            <li><code>shrink_resources = 1</code>: Turns on Android resource shrinking</li>
            <li><code>shrink_resources = 0</code>: Turns off Android resource shrinking</li>
            <li><code>shrink_resources = -1</code>: Shrinking is controlled by the
                <a href="../user-manual.html#flag--android_resource_shrinking">
                --android_resource_shrinking</a> flag.</li>
          </ul>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("shrink_resources", TRISTATE).value(TriState.AUTO))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(densities) -->
          Densities to filter for when building the apk.
          This will strip out raster drawable resources that would not be loaded by a device with
          the specified screen densities, to reduce APK size. A corresponding compatible-screens
          section will also be added to the manifest if it does not already contain a superset
          listing.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr(ResourceFilterFactory.DENSITIES_NAME, STRING_LIST))
          .add(
              attr("$build_incremental_dexmanifest", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel(BUILD_INCREMENTAL_DEXMANIFEST_LABEL)))
          .add(
              attr("$stubify_manifest", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel(STUBIFY_MANIFEST_LABEL)))
          .add(
              attr("$shuffle_jars", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:shuffle_jars")))
          .add(
              attr("$dexbuilder", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:dexbuilder")))
          .add(
              attr("$dexbuilder_after_proguard", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:dexbuilder_after_proguard")))
          .add(
              attr("$dexsharder", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:dexsharder")))
          .add(
              attr("$dexmerger", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:dexmerger")))
          .add(
              attr("$merge_dexzips", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:merge_dexzips")))
          .add(
              attr("$incremental_install", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel(INCREMENTAL_INSTALL_LABEL)))
          .add(
              attr("$build_split_manifest", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel(BUILD_SPLIT_MANIFEST_LABEL)))
          .add(
              attr("$strip_resources", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel(STRIP_RESOURCES_LABEL)))
          .add(
              attr("$incremental_stub_application", LABEL)
                  .value(env.getToolsLabel(DEFAULT_INCREMENTAL_STUB_APPLICATION))
                  .aspect(dexArchiveAspect, DexArchiveAspect.ONLY_DESUGAR_JAVA8))
          .add(
              attr("$incremental_split_stub_application", LABEL)
                  .value(env.getToolsLabel(DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION))
                  .aspect(dexArchiveAspect, DexArchiveAspect.ONLY_DESUGAR_JAVA8))
          .add(
              attr("$desugar", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:desugar_java8")))
          .add(
              attr("$java8_legacy_dex", LABEL)
                  .value(env.getToolsLabel("//tools/android:java8_legacy_dex")))
          .add(
              attr("$build_java8_legacy_dex", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:build_java8_legacy_dex")))
          .add(
              attr("$desugared_java8_legacy_apis", LABEL)
                  .value(env.getToolsLabel("//tools/android:desugared_java8_legacy_apis")))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(dexopts) -->
          Additional command-line flags for the dx tool when generating classes.dex.
          Subject to <a href="${link make-variables}">"Make variable"</a> substitution and
          <a href="${link common-definitions#sh-tokenization}">Bourne shell tokenization</a>.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("dexopts", STRING_LIST))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(dex_shards) -->
          Number of shards dexing should be decomposed into.
          This is makes dexing much faster at the expense of app installation and startup time. The
          larger the binary, the more shards should be used. 25 is a good value to start
          experimenting with.
          <p>
          Note that each shard will result in at least one dex in the final app. For this reason,
          setting this to more than 1 is not recommended for release binaries.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("dex_shards", INTEGER).value(StarlarkInt.of(1)))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(incremental_dexing) -->
          Force the target to be built with or without incremental dexing, overriding defaults
          and --incremental_dexing flag.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("incremental_dexing", TRISTATE)
                  // Read by DexArchiveAspect's attribute extractor
                  .nonconfigurable("AspectParameters don't support configurations."))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(multidex) -->
          Whether to split code into multiple dex files.<br/>
          Possible values:
          <ul>
            <li><code>native</code>: Split code into multiple dex files when the dex 64K index limit
              is exceeded. Assumes native platform support for loading multidex classes at runtime.
              <em class="harmful">This works with only Android L and newer</em>.</li>
            <li><code>legacy</code>: Split code into multiple dex files when the dex 64K index limit
              is exceeded. Assumes multidex classes are loaded through application code (i.e. no
              native platform support).</li>
            <li><code>manual_main_dex</code>: Split code into multiple dex files when the dex 64K
              index limit is exceeded. The content of the main dex file needs to be specified by
              providing a list of classes in a text file using the
              <a href="${link android_binary.main_dex_list}">main_dex_list</a> attribute.</li>
            <li><code>off</code>: Compile all code to a single dex file, even if it exceeds the
              index limit.</li>
          </ul>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("multidex", STRING)
                  .allowedValues(new AllowedValueSet(MultidexMode.getValidValues()))
                  .value(MultidexMode.OFF.getAttributeValue()))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(main_dex_list_opts) -->
          Command line options to pass to the main dex list builder.
          Use this option to affect the classes included in the main dex list.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("main_dex_list_opts", STRING_LIST))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(main_dex_list) -->

          A text file contains a list of class file names. Classes defined by those class files are
          put in the primary classes.dex. e.g.:<pre class="code">
          android/support/multidex/MultiDex$V19.class
          android/support/multidex/MultiDex.class
          android/support/multidex/MultiDexApplication.class
          com/google/common/base/Objects.class
                    </pre>
                    Must be used with <code>multidex="manual_main_dex"</code>.
                    <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("main_dex_list", LABEL).legacyAllowAnyFileType())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(main_dex_proguard_specs) -->
          Files to be used as the Proguard specifications to determine classes that must be kept in
          the main dex.
          Only allowed if the <code>multidex</code> attribute is set to <code>legacy</code>.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("main_dex_proguard_specs", LABEL_LIST).legacyAllowAnyFileType())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(proguard_specs) -->
          Files to be used as Proguard specification.
          This file will describe the set of specifications to be used by Proguard.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("proguard_specs", LABEL_LIST).legacyAllowAnyFileType())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(proguard_generate_mapping) -->
          Whether to generate Proguard mapping file.
          The mapping file will be generated only if <code>proguard_specs</code> is
          specified. This file will list the mapping between the original and
          obfuscated class, method, and field names.
          <p><em class="harmful">WARNING: If this attribute is used, the Proguard
          specification should contain neither <code>-dontobfuscate</code> nor
          <code>-printmapping</code>.</em></p>
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("proguard_generate_mapping", BOOLEAN)
                  .value(false)
                  .nonconfigurable("value is referenced in an ImplicitOutputsFunction"))
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(proguard_apply_mapping) -->
          File to be used as a mapping for proguard.
          A mapping file generated by <code>proguard_generate_mapping</code> to be
          re-used to apply the same mapping to a new build.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("proguard_apply_mapping", LABEL).legacyAllowAnyFileType())
          /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(proguard_apply_dictionary) -->
          File to be used as a mapping for proguard.
          A line separated file of "words" to pull from when renaming classes and members during
          obfuscation.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("proguard_apply_dictionary", LABEL).legacyAllowAnyFileType())
          .add(attr(":extra_proguard_specs", LABEL_LIST).value(JavaSemantics.EXTRA_PROGUARD_SPECS))
          .add(
              attr("$dex_list_obfuscator", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:dex_list_obfuscator")))
          .add(
              attr(":bytecode_optimizer", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .value(JavaSemantics.BYTECODE_OPTIMIZER))
          // We need the C++ toolchain for every sub-configuration to get the correct linker.
          .add(
              attr("$cc_toolchain_split", LABEL)
                  .cfg(TransitionFactories.of(ANDROID_SPLIT_TRANSITION))
                  .value(env.getToolsLabel("//tools/cpp:current_cc_toolchain")))
          /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_values) -->
          A dictionary of values to be overridden in the manifest. Any instance of ${name} in the
          manifest will be replaced with the value corresponding to name in this dictionary.
          applicationId, versionCode, versionName, minSdkVersion, targetSdkVersion and
          maxSdkVersion will also override the corresponding attributes of the manifest and
          uses-sdk tags. packageName will be ignored and will be set from either applicationId if
          specified or the package in manifest. When manifest_merger is set to legacy, only
          applicationId, versionCode and versionName will have any effect.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(attr("manifest_values", STRING_DICT))
          .add(
              attr(AndroidFeatureFlagSetProvider.FEATURE_FLAG_ATTR, LABEL_KEYED_STRING_DICT)
                  .undocumented("the feature flag feature has not yet been launched")
                  .allowedRuleClasses("config_feature_flag")
                  .allowedFileTypes()
                  .nonconfigurable("defines an aspect of configuration")
                  .mandatoryProviders(ImmutableList.of(ConfigFeatureFlagProvider.id())))
          .add(AndroidFeatureFlagSetProvider.getAllowlistAttribute(env))
          // The resource extractor is used at the binary level to extract java resources from the
          // deploy jar so that they can be added to the APK.
          .add(
              attr("$resource_extractor", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:resource_extractor")))
          /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(instruments) -->
          <p>The <code>android_binary</code> target to instrument.</p>
          <p>If this attribute is set, this <code>android_binary</code> will be treated as a test
          application for instrumentation tests. An <code>android_instrumentation_test</code>
          target can then specify this target in its
           <a href="${link android_instrumentation_test.test_app}">test_app</a> attribute.
          <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
          .add(
              attr("instruments", LABEL)
                  .allowedRuleClasses("android_binary")
                  .allowedFileTypes(NO_FILE))
          .add(
              attr("$instrumentation_test_check", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .value(
                      new Attribute.ComputedDefault() {
                        @Override
                        public Object getDefault(AttributeMap rule) {
                          return rule.isAttributeValueExplicitlySpecified("instruments")
                              ? env.getToolsLabel("//tools/android:instrumentation_test_check")
                              : null;
                        }
                      })
                  .exec())
          .add(
              attr("$zip_filter", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .exec()
                  .value(env.getToolsLabel("//tools/android:zip_filter")))
          .add(
              attr("application_resources", LABEL)
                  .mandatoryProviders(AndroidApplicationResourceInfo.PROVIDER.id())
                  .allowedFileTypes(NO_FILE)
                  .undocumented(
                      "Do not use this attribute. It's for the migration of "
                          + "Android resource processing to Starlark only."))
          // This comes from the --legacy_main_dex_list_generator flag.
          .add(
              attr(":legacy_main_dex_list_generator", LABEL)
                  .cfg(ExecutionTransitionFactory.create())
                  .value(LEGACY_MAIN_DEX_LIST_GENERATOR)
                  .exec())
          .removeAttribute("data")
          .advertiseStarlarkProvider(StarlarkProviderIdentifier.forKey(ApkInfo.PROVIDER.getKey()))
          .advertiseStarlarkProvider(StarlarkProviderIdentifier.forKey(JavaInfo.PROVIDER.getKey()))
          .build();
    }

    @Override
    public Metadata getMetadata() {
      return RuleDefinition.Metadata.builder()
          .name("$android_binary_base")
          .type(RuleClassType.ABSTRACT)
          .ancestors(AndroidResourceSupportRule.class)
          .build();
    }
  }

  /** Semantic options for the dexer's multidex behavior. */
  public enum MultidexMode {
    // Build dexes with multidex, assuming native platform support for multidex.
    NATIVE,
    // Build dexes with multidex and implement support at the application level.
    LEGACY,
    // Build dexes with multidex, main dex list needs to be manually specified.
    MANUAL_MAIN_DEX,
    // Build all dex code into a single classes.dex file.
    OFF;

    /** Returns the attribute value that specifies this mode. */
    public String getAttributeValue() {
      return toString().toLowerCase();
    }

    /**
     * Returns the name of the output dex classes file. In multidex mode, this is an archive of
     * (possibly) multiple files.
     */
    public String getOutputDexFilename() {
      return this == OFF ? "classes.dex" : "classes.dex.zip";
    }

    /** Converts an attribute value to a corresponding mode. Returns null on no match. */
    public static MultidexMode fromValue(String value) {
      for (MultidexMode mode : values()) {
        if (mode.getAttributeValue().equals(value)) {
          return mode;
        }
      }
      return null;
    }

    /** Enumerates valid values for the "multidex" attribute. */
    public static List<String> getValidValues() {
      List<String> ans = Lists.newArrayList();
      for (MultidexMode mode : MultidexMode.values()) {
        ans.add(mode.getAttributeValue());
      }
      return ans;
    }
  }

  /** Definition of the {@code android_tools_defaults_jar} rule. */
  public static final class AndroidToolsDefaultsJarRule implements RuleDefinition {

    private final Label[] compatibleWithAndroidEnvironments;

    private final Class<? extends AndroidToolsDefaultsJar> factoryClass;

    public AndroidToolsDefaultsJarRule(
        Class<? extends AndroidToolsDefaultsJar> factoryClass,
        Label... compatibleWithAndroidEnvironments) {
      this.factoryClass = factoryClass;
      this.compatibleWithAndroidEnvironments = compatibleWithAndroidEnvironments;
    }

    @Override
    public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) {
      builder
          .setUndocumented()
          .add(
              attr(":android_sdk", LABEL)
                  .allowedRuleClasses("android_sdk")
                  .value(getAndroidSdkLabel(environment.getToolsLabel(DEFAULT_SDK))));
      if (compatibleWithAndroidEnvironments.length > 0) {
        builder.compatibleWith(compatibleWithAndroidEnvironments);
      }
      return builder.build();
    }

    @Override
    public Metadata getMetadata() {
      return Metadata.builder()
          .name("android_tools_defaults_jar")
          .ancestors(BaseRuleClasses.BaseRule.class)
          .factoryClass(factoryClass)
          .build();
    }
  }
}
