// Copyright 2014 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.bazel.rules;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.ActionEnvironment;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.RuleSet;
import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
import com.google.devtools.build.lib.analysis.PlatformConfiguration;
import com.google.devtools.build.lib.analysis.ShellConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
import com.google.devtools.build.lib.bazel.BazelConfiguration;
import com.google.devtools.build.lib.bazel.repository.LocalConfigPlatformRule;
import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryRule;
import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAarImportRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidBinaryRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidDevice;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidDeviceScriptFixture;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidHostServiceFixture;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidInstrumentationTestRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidLibraryRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidLocalTestRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidSdkRule;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidSemantics;
import com.google.devtools.build.lib.bazel.rules.android.BazelAndroidToolsDefaultsJar;
import com.google.devtools.build.lib.bazel.rules.android.BazelDexArchiveAspect;
import com.google.devtools.build.lib.bazel.rules.android.BazelSdkToolchainRule;
import com.google.devtools.build.lib.bazel.rules.java.proto.BazelJavaLiteProtoLibraryRule;
import com.google.devtools.build.lib.bazel.rules.java.proto.BazelJavaProtoLibraryRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyBinaryRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyBuiltins;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyLibraryRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyRuleClasses;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyTestRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPythonConfiguration;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.packages.PackageCallable;
import com.google.devtools.build.lib.rules.android.AarImportBaseRule;
import com.google.devtools.build.lib.rules.android.AndroidApplicationResourceInfo;
import com.google.devtools.build.lib.rules.android.AndroidAssetsInfo;
import com.google.devtools.build.lib.rules.android.AndroidBinaryDataInfo;
import com.google.devtools.build.lib.rules.android.AndroidBinaryNativeLibsInfo;
import com.google.devtools.build.lib.rules.android.AndroidCcLinkParamsProvider;
import com.google.devtools.build.lib.rules.android.AndroidConfiguration;
import com.google.devtools.build.lib.rules.android.AndroidDeviceBrokerInfo;
import com.google.devtools.build.lib.rules.android.AndroidDeviceRule;
import com.google.devtools.build.lib.rules.android.AndroidDeviceScriptFixtureRule;
import com.google.devtools.build.lib.rules.android.AndroidDexInfo;
import com.google.devtools.build.lib.rules.android.AndroidFeatureFlagSetProvider;
import com.google.devtools.build.lib.rules.android.AndroidHostServiceFixtureRule;
import com.google.devtools.build.lib.rules.android.AndroidIdeInfoProvider;
import com.google.devtools.build.lib.rules.android.AndroidIdlProvider;
import com.google.devtools.build.lib.rules.android.AndroidInstrumentationInfo;
import com.google.devtools.build.lib.rules.android.AndroidInstrumentationTestBaseRule;
import com.google.devtools.build.lib.rules.android.AndroidLibraryAarInfo;
import com.google.devtools.build.lib.rules.android.AndroidLibraryBaseRule;
import com.google.devtools.build.lib.rules.android.AndroidLibraryResourceClassJarProvider;
import com.google.devtools.build.lib.rules.android.AndroidLocalTestBaseRule;
import com.google.devtools.build.lib.rules.android.AndroidLocalTestConfiguration;
import com.google.devtools.build.lib.rules.android.AndroidManifestInfo;
import com.google.devtools.build.lib.rules.android.AndroidNativeLibsInfo;
import com.google.devtools.build.lib.rules.android.AndroidNeverLinkLibrariesProvider;
import com.google.devtools.build.lib.rules.android.AndroidNeverlinkAspect;
import com.google.devtools.build.lib.rules.android.AndroidOptimizedJarInfo;
import com.google.devtools.build.lib.rules.android.AndroidPreDexJarProvider;
import com.google.devtools.build.lib.rules.android.AndroidProguardInfo;
import com.google.devtools.build.lib.rules.android.AndroidResourcesInfo;
import com.google.devtools.build.lib.rules.android.AndroidRuleClasses;
import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.AndroidToolsDefaultsJarRule;
import com.google.devtools.build.lib.rules.android.AndroidSdkBaseRule;
import com.google.devtools.build.lib.rules.android.AndroidSdkProvider;
import com.google.devtools.build.lib.rules.android.AndroidStarlarkCommon;
import com.google.devtools.build.lib.rules.android.ApkInfo;
import com.google.devtools.build.lib.rules.android.BaselineProfileProvider;
import com.google.devtools.build.lib.rules.android.BazelAndroidConfiguration;
import com.google.devtools.build.lib.rules.android.DexArchiveAspect;
import com.google.devtools.build.lib.rules.android.ProguardMappingProvider;
import com.google.devtools.build.lib.rules.android.databinding.DataBindingV2Provider;
import com.google.devtools.build.lib.rules.config.ConfigRules;
import com.google.devtools.build.lib.rules.core.CoreRules;
import com.google.devtools.build.lib.rules.cpp.CcSharedLibraryRule;
import com.google.devtools.build.lib.rules.cpp.CcStarlarkInternal;
import com.google.devtools.build.lib.rules.cpp.proto.CcProtoLibraryRule;
import com.google.devtools.build.lib.rules.objc.BazelObjcStarlarkInternal;
import com.google.devtools.build.lib.rules.objc.ObjcStarlarkInternal;
import com.google.devtools.build.lib.rules.platform.PlatformRules;
import com.google.devtools.build.lib.rules.proto.BazelProtoCommon;
import com.google.devtools.build.lib.rules.proto.BazelProtoLibraryRule;
import com.google.devtools.build.lib.rules.proto.ProtoConfiguration;
import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainRule;
import com.google.devtools.build.lib.rules.python.PyRuleClasses.PySymlink;
import com.google.devtools.build.lib.rules.python.PyRuntimeRule;
import com.google.devtools.build.lib.rules.python.PythonConfiguration;
import com.google.devtools.build.lib.rules.repository.CoreWorkspaceRules;
import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryRule;
import com.google.devtools.build.lib.rules.test.TestingSupportRules;
import com.google.devtools.build.lib.starlarkbuildapi.android.AndroidBootstrap;
import com.google.devtools.build.lib.starlarkbuildapi.core.ContextGuardedValue;
import com.google.devtools.build.lib.starlarkbuildapi.proto.ProtoBootstrap;
import com.google.devtools.build.lib.starlarkbuildapi.python.PyBootstrap;
import com.google.devtools.build.lib.starlarkbuildapi.stubs.ProviderStub;
import com.google.devtools.build.lib.starlarkbuildapi.stubs.StarlarkAspectStub;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.ResourceFileLoader;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionMetadataTag;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import javax.annotation.Nullable;
import net.starlark.java.eval.Starlark;

/** A rule class provider implementing the rules Bazel knows. */
public class BazelRuleClassProvider {
  /** Command-line options. */
  public static class StrictActionEnvOptions extends FragmentOptions {
    @Option(
        name = "incompatible_strict_action_env",
        oldName = "experimental_strict_action_env",
        defaultValue = "false",
        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
        effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
        metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
        help =
            "If true, Bazel uses an environment with a static value for PATH and does not "
                + "inherit LD_LIBRARY_PATH. Use --action_env=ENV_VARIABLE if you want to "
                + "inherit specific environment variables from the client, but note that doing so "
                + "can prevent cross-user caching if a shared cache is used.")
    public boolean useStrictActionEnv;

    @Override
    public StrictActionEnvOptions getExec() {
      StrictActionEnvOptions exec = (StrictActionEnvOptions) getDefault();
      exec.useStrictActionEnv = useStrictActionEnv;
      return exec;
    }
  }

  private static final PathFragment FALLBACK_SHELL = PathFragment.create("/bin/bash");

  public static final ImmutableMap<OS, PathFragment> SHELL_EXECUTABLE =
      ImmutableMap.<OS, PathFragment>builder()
          .put(OS.WINDOWS, PathFragment.create("c:/tools/msys64/usr/bin/bash.exe"))
          .put(OS.FREEBSD, PathFragment.create("/usr/local/bin/bash"))
          .put(OS.OPENBSD, PathFragment.create("/usr/local/bin/bash"))
          .put(OS.UNKNOWN, FALLBACK_SHELL)
          .buildOrThrow();

  /**
   * {@link BuildConfigurationFunction} constructs {@link BuildOptions} out of the options required
   * by the registered fragments. We create and register this fragment exclusively to ensure {@link
   * StrictActionEnvOptions} is always available.
   */
  @RequiresOptions(options = {StrictActionEnvOptions.class})
  public static class StrictActionEnvConfiguration extends Fragment {
    public StrictActionEnvConfiguration(BuildOptions buildOptions) {}
  }

  @Nullable
  public static PathFragment getDefaultPathFromOptions(ShellConfiguration.Options options) {
    if (options.shellExecutable != null) {
      return options.shellExecutable;
    }

    // Honor BAZEL_SH env variable for backwards compatibility.
    String path = System.getenv("BAZEL_SH");
    if (path != null) {
      return PathFragment.create(path);
    }
    return null;
  }

  @VisibleForTesting
  static PathFragment getShellExecutableForOs(OS os, ShellConfiguration.Options options) {
    // TODO(ulfjack): instead of using the OS Bazel runs on, we need to use the exec platform,
    // which may be different for remote execution. For now, this can be overridden with
    // --shell_executable, so at least there's a workaround.
    return getDefaultPathFromOptions(options) != null
        ? getDefaultPathFromOptions(options)
        : SHELL_EXECUTABLE.getOrDefault(os, FALLBACK_SHELL);
  }

  public static final Function<BuildOptions, ActionEnvironment> SHELL_ACTION_ENV =
      (BuildOptions options) -> {
        if (options.hasNoConfig()) {
          return ActionEnvironment.EMPTY;
        }
        boolean strictActionEnv = options.get(StrictActionEnvOptions.class).useStrictActionEnv;
        OS os = OS.getCurrent();
        // TODO(ulfjack): instead of using the OS Bazel runs on, we need to use the exec platform,
        // which may be different for remote execution. For now, this can be overridden with
        // --shell_executable, so at least there's a workaround.
        PathFragment shellExecutable =
            getShellExecutableForOs(os, options.get(ShellConfiguration.Options.class));

        TreeMap<String, String> env = new TreeMap<>();

        // All entries in the builder that have a value of null inherit the value from the client
        // environment, which is only known at execution time - we don't want to bake the client env
        // into the configuration since any change to the configuration requires rerunning the full
        // analysis phase.
        if (!strictActionEnv) {
          env.put("LD_LIBRARY_PATH", null);
        }

        if (strictActionEnv) {
          env.put("PATH", pathOrDefault(os, null, shellExecutable));
        } else if (os == OS.WINDOWS) {
          // TODO(ulfjack): We want to add the MSYS root to the PATH, but that prevents us from
          // inheriting PATH from the client environment. For now we use System.getenv even though
          // that is incorrect. We should enable strict_action_env by default and then remove this
          // code, but that change may break Windows users who are relying on the MSYS root being in
          // the PATH.
          env.put("PATH", pathOrDefault(os, System.getenv("PATH"), shellExecutable));
        } else {
          // The previous implementation used System.getenv (which uses the server's environment),
          // and fell back to a hard-coded "/bin:/usr/bin" if PATH was not set.
          env.put("PATH", null);
        }

        // Shell environment variables specified via options take precedence over the
        // ones inherited from the fragments. In the long run, these fragments will
        // be replaced by appropriate default rc files anyway.
        for (Map.Entry<String, String> entry : options.get(CoreOptions.class).actionEnvironment) {
          env.put(entry.getKey(), entry.getValue());
        }

        if (!BuildConfigurationValue.runfilesEnabled(options.get(CoreOptions.class))) {
          // Setting this environment variable is for telling the binary running
          // in a Bazel action when to use runfiles library or runfiles tree.
          // The downside is that it will discard cache for all actions once
          // --enable_runfiles changes, but this also prevents wrong caching result if a binary
          // behaves differently with and without runfiles tree.
          env.put("RUNFILES_MANIFEST_ONLY", "1");
        }

        return ActionEnvironment.split(env);
      };

  /** Convenience wrapper around {@link #setup} that returns a final ConfiguredRuleClassProvider. */
  // Used by the build encyclopedia generator.
  public static ConfiguredRuleClassProvider create() {
    ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
    setup(builder);
    return builder.build();
  }

  /** Adds this class's definitions to a builder. */
  public static void setup(ConfiguredRuleClassProvider.Builder builder) {
    builder.setToolsRepository(RepositoryName.BAZEL_TOOLS);
    builder.setBuiltinsBzlZipResource(
        ResourceFileLoader.resolveResource(BazelRuleClassProvider.class, "builtins_bzl.zip"));
    builder.setBuiltinsBzlPackagePathInSource("src/main/starlark/builtins_bzl");

    for (RuleSet ruleSet : RULE_SETS) {
      ruleSet.init(builder);
    }
  }

  public static final RuleSet BAZEL_SETUP =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          ShellConfiguration.injectShellExecutableFinder(
              BazelRuleClassProvider::getDefaultPathFromOptions, SHELL_EXECUTABLE);
          builder
              .setPrelude("//tools/build_rules:prelude_bazel")
              .setRunfilesPrefix(LabelConstants.DEFAULT_REPOSITORY_DIRECTORY)
              .setPrerequisiteValidator(new BazelPrerequisiteValidator())
              .setActionEnvironmentProvider(SHELL_ACTION_ENV)
              .addUniversalConfigurationFragment(ShellConfiguration.class)
              .addUniversalConfigurationFragment(PlatformConfiguration.class)
              .addUniversalConfigurationFragment(BazelConfiguration.class)
              .addUniversalConfigurationFragment(StrictActionEnvConfiguration.class)
              .addConfigurationOptions(CoreOptions.class);

          builder.addStarlarkBuiltinsInternal(
              ObjcStarlarkInternal.NAME, new ObjcStarlarkInternal());
          builder.addStarlarkBuiltinsInternal(CcStarlarkInternal.NAME, new CcStarlarkInternal());
          builder.addStarlarkBuiltinsInternal(
              BazelObjcStarlarkInternal.NAME, new BazelObjcStarlarkInternal());

          // Add the package() function.
          // TODO(bazel-team): Factor this into a group of similar BUILD definitions, or add a more
          // convenient way of obtaining a BuiltinFunction than addMethods().
          ImmutableMap.Builder<String, Object> symbols = ImmutableMap.builder();
          Starlark.addMethods(symbols, PackageCallable.INSTANCE);
          for (Map.Entry<String, Object> entry : symbols.buildOrThrow().entrySet()) {
            builder.addBuildFileToplevel(entry.getKey(), entry.getValue());
          }
        }
      };

  public static final RuleSet PROTO_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          builder.addConfigurationFragment(ProtoConfiguration.class);
          builder.addRuleDefinition(new BazelProtoLibraryRule());
          builder.addRuleDefinition(new ProtoLangToolchainRule());

          ProtoBootstrap bootstrap =
              new ProtoBootstrap(
                  BazelProtoCommon.INSTANCE,
                  new StarlarkAspectStub(),
                  new ProviderStub());
          builder.addStarlarkBootstrap(bootstrap);
        }

        @Override
        public ImmutableList<RuleSet> requires() {
          return ImmutableList.of(CoreRules.INSTANCE);
        }
      };

  public static final RuleSet CPP_PROTO_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          builder.addRuleDefinition(new CcProtoLibraryRule());
        }

        @Override
        public ImmutableList<RuleSet> requires() {
          return ImmutableList.of(CoreRules.INSTANCE, CcRules.INSTANCE);
        }
      };

  public static final RuleSet JAVA_PROTO_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          builder.addRuleDefinition(new BazelJavaProtoLibraryRule());
          builder.addRuleDefinition(new BazelJavaLiteProtoLibraryRule());
        }

        @Override
        public ImmutableList<RuleSet> requires() {
          return ImmutableList.of(CoreRules.INSTANCE, JavaRules.INSTANCE);
        }
      };

  public static final RuleSet ANDROID_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          RepositoryName toolsRepository = checkNotNull(builder.getToolsRepository());

          builder.addConfigurationFragment(AndroidConfiguration.class);
          builder.addConfigurationFragment(BazelAndroidConfiguration.class);
          builder.addConfigurationFragment(AndroidLocalTestConfiguration.class);

          AndroidNeverlinkAspect androidNeverlinkAspect = new AndroidNeverlinkAspect();
          DexArchiveAspect dexArchiveAspect = new BazelDexArchiveAspect(toolsRepository);
          builder.addNativeAspectClass(androidNeverlinkAspect);
          builder.addNativeAspectClass(dexArchiveAspect);

          builder.addRuleDefinition(new AndroidSdkBaseRule());
          builder.addRuleDefinition(new BazelAndroidSdkRule());
          builder.addRuleDefinition(
              new AndroidToolsDefaultsJarRule(BazelAndroidToolsDefaultsJar.class));
          builder.addRuleDefinition(new AndroidRuleClasses.AndroidBaseRule());
          builder.addRuleDefinition(new AndroidRuleClasses.AndroidResourceSupportRule());
          builder.addRuleDefinition(
              new AndroidRuleClasses.AndroidBinaryBaseRule(
                  androidNeverlinkAspect, dexArchiveAspect));
          builder.addRuleDefinition(new BazelSdkToolchainRule());
          builder.addRuleDefinition(new AndroidLibraryBaseRule(androidNeverlinkAspect));
          builder.addRuleDefinition(new BazelAndroidLibraryRule());
          builder.addRuleDefinition(new BazelAndroidBinaryRule());
          builder.addRuleDefinition(new AarImportBaseRule());
          builder.addRuleDefinition(new BazelAarImportRule());
          builder.addRuleDefinition(new AndroidDeviceRule(BazelAndroidDevice.class));
          builder.addRuleDefinition(new AndroidLocalTestBaseRule());
          builder.addRuleDefinition(new BazelAndroidLocalTestRule());
          builder.addRuleDefinition(new AndroidInstrumentationTestBaseRule());
          builder.addRuleDefinition(new BazelAndroidInstrumentationTestRule());
          builder.addRuleDefinition(
              new AndroidDeviceScriptFixtureRule(BazelAndroidDeviceScriptFixture.class));
          builder.addRuleDefinition(
              new AndroidHostServiceFixtureRule(BazelAndroidHostServiceFixture.class));

          AndroidBootstrap bootstrap =
              new AndroidBootstrap(
                  new AndroidStarlarkCommon(),
                  ApkInfo.PROVIDER,
                  AndroidInstrumentationInfo.PROVIDER,
                  AndroidDeviceBrokerInfo.PROVIDER,
                  AndroidResourcesInfo.PROVIDER,
                  AndroidNativeLibsInfo.PROVIDER,
                  AndroidApplicationResourceInfo.PROVIDER,
                  AndroidSdkProvider.PROVIDER,
                  AndroidManifestInfo.PROVIDER,
                  AndroidAssetsInfo.PROVIDER,
                  AndroidLibraryAarInfo.PROVIDER,
                  AndroidProguardInfo.PROVIDER,
                  AndroidIdlProvider.PROVIDER,
                  AndroidIdeInfoProvider.PROVIDER,
                  AndroidPreDexJarProvider.PROVIDER,
                  AndroidCcLinkParamsProvider.PROVIDER,
                  DataBindingV2Provider.PROVIDER,
                  AndroidLibraryResourceClassJarProvider.PROVIDER,
                  AndroidFeatureFlagSetProvider.PROVIDER,
                  ProguardMappingProvider.PROVIDER,
                  AndroidBinaryDataInfo.PROVIDER,
                  AndroidBinaryNativeLibsInfo.PROVIDER,
                  BaselineProfileProvider.PROVIDER,
                  AndroidNeverLinkLibrariesProvider.PROVIDER,
                  AndroidOptimizedJarInfo.PROVIDER,
                  AndroidDexInfo.PROVIDER);
          builder.addStarlarkBootstrap(bootstrap);

          try {
            builder.addWorkspaceFilePrefix(
                ResourceFileLoader.loadResource(BazelAndroidSemantics.class, "android.WORKSPACE"));
            builder.addWorkspaceFileSuffix(
                ResourceFileLoader.loadResource(
                    BazelAndroidSemantics.class, "android_remote_tools.WORKSPACE"));
            builder.addWorkspaceFileSuffix(
                ResourceFileLoader.loadResource(JavaRules.class, "coverage.WORKSPACE"));
          } catch (IOException e) {
            throw new IllegalStateException(e);
          }
        }

        @Override
        public ImmutableList<RuleSet> requires() {
          return ImmutableList.of(CoreRules.INSTANCE, CcRules.INSTANCE, JavaRules.INSTANCE);
        }
      };

  public static final RuleSet PYTHON_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          builder.addConfigurationFragment(PythonConfiguration.class);
          builder.addConfigurationFragment(BazelPythonConfiguration.class);

          builder.addRuleDefinition(new BazelPyRuleClasses.PyBaseRule());
          builder.addRuleDefinition(new BazelPyRuleClasses.PyBinaryBaseRule());
          builder.addRuleDefinition(new BazelPyLibraryRule());
          builder.addRuleDefinition(new BazelPyBinaryRule());
          builder.addRuleDefinition(new BazelPyTestRule());
          builder.addRuleDefinition(new PyRuntimeRule());

          // This symbol is overridden by exports.bzl
          builder.addBzlToplevel(
              "py_internal",
              ContextGuardedValue.onlyInAllowedRepos(
                  Starlark.NONE, PyBootstrap.allowedRepositories));
          builder.addStarlarkBuiltinsInternal(BazelPyBuiltins.NAME, new BazelPyBuiltins());
          builder.addStarlarkBootstrap(new PyBootstrap());
          builder.addSymlinkDefinition(PySymlink.PY2);
          builder.addSymlinkDefinition(PySymlink.PY3);

          try {
            builder.addWorkspaceFileSuffix(
                ResourceFileLoader.loadResource(BazelPyBinaryRule.class, "python.WORKSPACE"));
          } catch (IOException e) {
            throw new IllegalStateException(e);
          }
        }

        @Override
        public ImmutableList<RuleSet> requires() {
          return ImmutableList.of(CoreRules.INSTANCE, CcRules.INSTANCE);
        }
      };

  public static final RuleSet VARIOUS_WORKSPACE_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          // TODO(ulfjack): Split this up by conceptual units.
          builder.addRuleDefinition(new NewLocalRepositoryRule());
          builder.addRuleDefinition(new AndroidSdkRepositoryRule());
          builder.addRuleDefinition(new AndroidNdkRepositoryRule());
          builder.addRuleDefinition(new LocalConfigPlatformRule());
          builder.addRuleDefinition(new CcSharedLibraryRule());

          try {
            builder.addWorkspaceFileSuffix(
                ResourceFileLoader.loadResource(
                    LocalConfigPlatformRule.class, "local_config_platform.WORKSPACE"));
          } catch (IOException e) {
            throw new IllegalStateException(e);
          }
        }

        @Override
        public ImmutableList<RuleSet> requires() {
          return ImmutableList.of(CoreRules.INSTANCE, CoreWorkspaceRules.INSTANCE);
        }
      };

  static final RuleSet PACKAGING_RULES =
      new RuleSet() {
        @Override
        public void init(ConfiguredRuleClassProvider.Builder builder) {
          builder.addBzlToplevel("PackageSpecificationInfo", PackageSpecificationProvider.PROVIDER);
        }
      };

  private static final ImmutableSet<RuleSet> RULE_SETS =
      ImmutableSet.of(
          BAZEL_SETUP,
          CoreRules.INSTANCE,
          CoreWorkspaceRules.INSTANCE,
          GenericRules.INSTANCE,
          ConfigRules.INSTANCE,
          PlatformRules.INSTANCE,
          PROTO_RULES,
          ShRules.INSTANCE,
          CcRules.INSTANCE,
          CPP_PROTO_RULES,
          JavaRules.INSTANCE,
          JAVA_PROTO_RULES,
          ANDROID_RULES,
          PYTHON_RULES,
          ObjcRules.INSTANCE,
          J2ObjcRules.INSTANCE,
          TestingSupportRules.INSTANCE,
          VARIOUS_WORKSPACE_RULES,
          PACKAGING_RULES,
          // This rule set is a little special: it needs to depend on every configuration fragment
          // that has Make variables, so we put it last.
          ToolchainRules.INSTANCE);

  @VisibleForTesting
  public static String pathOrDefault(OS os, @Nullable String path, @Nullable PathFragment sh) {
    // TODO(ulfjack): The default PATH should be set from the exec platform, which may be different
    // from the local machine. For now, this can be overridden with --action_env=PATH=<value>, so
    // at least there's a workaround.
    if (os != OS.WINDOWS) {
      // The default used to be "/bin:/usr/bin". However, on Mac the Python 3 interpreter, if it is
      // installed at all, tends to be under /usr/local/bin. The autodetecting Python toolchain
      // searches PATH for "python3", so if we don't include this directory then we can't run PY3
      // targets with this toolchain if strict action environment is on.
      //
      // Note that --action_env does not propagate to the exec config, so it is not a viable
      // workaround when a genrule is itself built in the exec config (e.g. nested genrules). See
      // #8536.
      return "/bin:/usr/bin:/usr/local/bin";
    }

    String newPath = "";
    // Attempt to compute the MSYS root (the real Windows path of "/") from `sh`.
    if (sh != null && sh.getParentDirectory() != null) {
      newPath = sh.getParentDirectory().getPathString();
      if (sh.getParentDirectory().endsWith(PathFragment.create("usr/bin"))) {
        newPath +=
            ";" + sh.getParentDirectory().getParentDirectory().replaceName("bin").getPathString();
      } else if (sh.getParentDirectory().endsWith(PathFragment.create("bin"))) {
        newPath +=
            ";" + sh.getParentDirectory().replaceName("usr").getRelative("bin").getPathString();
      }
      newPath = newPath.replace('/', '\\');
    }
    // On Windows, the following dirs should always be available in PATH:
    //   C:\Windows
    //   C:\Windows\System32
    //   C:\Windows\System32\WindowsPowerShell\v1.0
    // They are similar to /bin:/usr/bin, which makes the basic tools on the platform available.
    String systemRoot = System.getenv("SYSTEMROOT");
    if (Strings.isNullOrEmpty(systemRoot)) {
      systemRoot = "C:\\Windows";
    }
    newPath += ";" + systemRoot;
    newPath += ";" + systemRoot + "\\System32";
    newPath += ";" + systemRoot + "\\System32\\WindowsPowerShell\\v1.0";
    if (path != null) {
      newPath += ";" + path;
    }
    return newPath;
  }
}
