{ lib
  # tooling
, callPackage
, fetchFromGitHub
, newScope
, recurseIntoAttrs
, runCommandCC
, stdenv
  # inputs
, Foundation
, bazel_self
, lr
, xe
, lockfile
, ...
}:
let
  inherit (stdenv.hostPlatform) isDarwin;

  testsDistDir = testsRepoCache;
  testsRepoCache = callPackage ./bazel-repository-cache.nix {
    # Bazel builtin tools versions are hard-coded in bazel. If the project
    # lockfile has not been generated by the same bazel version as this one
    # then it may be missing depeendencies for builtin tools. Export
    # dependencies from baazel itself here, and let projects also import their
    # own if need be. It's just a symlinkJoin after all. See ./cpp-test.nix
    inherit lockfile;

    # Take all the rules_ deps, bazel_ deps and their transitive dependencies,
    # but none of the platform-specific binaries, as they are large and useless.
    requiredDepNamePredicate = name:
      name == "_main~bazel_build_deps~workspace_repo_cache"
      || null == builtins.match ".*(macos|osx|linux|win|android|maven).*" name
      && null != builtins.match "(platforms|com_google_|protobuf|rules_|.*bazel_|apple_support).*" name;
  };

  runLocal = name: attrs: script:
    let
      attrs' = removeAttrs attrs [ "buildInputs" ];
      buildInputs = attrs.buildInputs or [ ];
    in
    runCommandCC name
      ({
        inherit buildInputs;
        preferLocalBuild = true;
        meta.platforms = bazel_self.meta.platforms;
      } // attrs')
      script;

  # bazel wants to extract itself into $install_dir/install every time it runs,
  # so let’s do that only once.
  extracted = bazelPkg:
    let
      install_dir =
        # `install_base` field printed by `bazel info`, minus the hash.
        # yes, this path is kinda magic. Sorry.
        "$HOME/.cache/bazel/_bazel_nixbld";
    in
    runLocal "bazel-extracted-homedir" { passthru.install_dir = install_dir; } ''
      export HOME=$(mktemp -d)
      touch WORKSPACE # yeah, everything sucks
      install_base="$(${bazelPkg}/bin/bazel info install_base)"
      # assert it’s actually below install_dir
      [[ "$install_base" =~ ${install_dir} ]] \
        || (echo "oh no! $install_base but we are \
      trying to copy ${install_dir} to $out instead!"; exit 1)
      cp -R ${install_dir} $out
    '';

  bazelTest = { name, bazelScript, workspaceDir, bazelPkg, buildInputs ? [ ] }:
    runLocal name
      {
        inherit buildInputs;
        # Necessary for the tests to pass on Darwin with sandbox enabled.
        __darwinAllowLocalNetworking = true;
      }
      ''
        # Bazel needs a real home for self-extraction and internal cache
        mkdir bazel_home
        export HOME=$PWD/bazel_home

        ${# Concurrent bazel invocations have the same workspace path.
          # On darwin, for some reason, it means they access and corrupt the
          # same outputRoot, outputUserRoot and outputBase
          # Ensure they use build-local outputRoot by setting TEST_TMPDIR
          lib.optionalString isDarwin ''
            export TEST_TMPDIR=$HOME/.cache/bazel
          ''
        }
        ${# Speed-up tests by caching bazel extraction.
          # Except on Darwin, because nobody knows how Darwin works.
          let bazelExtracted = extracted bazelPkg;
          in lib.optionalString (!isDarwin) ''
            mkdir -p ${bazelExtracted.install_dir}
            cp -R ${bazelExtracted}/install ${bazelExtracted.install_dir}

            # https://stackoverflow.com/questions/47775668/bazel-how-to-skip-corrupt-installation-on-centos6
            # Bazel checks whether the mtime of the install dir files
            # is >9 years in the future, otherwise it extracts itself again.
            # see PosixFileMTime::IsUntampered in src/main/cpp/util
            # What the hell bazel.
            ${lr}/bin/lr -0 -U ${bazelExtracted.install_dir} | ${xe}/bin/xe -N0 -0 touch --date="9 years 6 months" {}
          ''
        }
        ${# Note https://github.com/bazelbuild/bazel/issues/5763#issuecomment-456374609
          # about why to create a subdir for the workspace.
          '' cp -r ${workspaceDir} wd && chmod ug+rw -R wd && cd wd ''
        }
        ${# run the actual test snippet
          bazelScript
        }
        ${# Try to keep darwin clean of our garbage
          lib.optionalString isDarwin ''
            rm -rf $HOME || true
          ''
        }

        touch $out
      '';

  bazel-examples = fetchFromGitHub {
    owner = "bazelbuild";
    repo = "examples";
    rev = "93564e1f1e7a3c39d6a94acee12b8d7b74de3491";
    hash = "sha256-DaPKp7Sn5uvfZRjdDx6grot3g3B7trqCyL0TRIdwg98=";
  };

  callBazelTests = bazel:
    let
      callBazelTest = newScope {
        inherit runLocal bazelTest bazel-examples;
        inherit Foundation;
        inherit bazel;
        distDir = testsDistDir;
        extraBazelArgs = "--noenable_bzlmod";
        repoCache = testsRepoCache;
      };
    in
    recurseIntoAttrs (
      (lib.optionalAttrs (!isDarwin) {
        # `extracted` doesn’t work on darwin
        shebang = callBazelTest ../shebang-test.nix {
          inherit extracted;
          extraBazelArgs = "--noenable_bzlmod";
        };
      }) // {
        bashTools = callBazelTest ../bash-tools-test.nix { };
        cpp = callBazelTest ./cpp-test.nix {
          extraBazelArgs = "";
        };
        java = callBazelTest ./java-test.nix { };
        pythonBinPath = callBazelTest ../python-bin-path-test.nix { };
        protobuf = callBazelTest ./protobuf-test.nix { };
      }
    );

  bazelWithNixHacks = bazel_self.override { enableNixHacks = true; };

in
recurseIntoAttrs {
  distDir = testsDistDir;
  testsRepoCache = testsRepoCache;

  vanilla = callBazelTests bazel_self;
  withNixHacks = callBazelTests bazelWithNixHacks;

  # add some downstream packages using buildBazelPackage
  downstream = recurseIntoAttrs ({
    # TODO: fix bazel-watcher build with bazel 7, or find other packages
    #inherit bazel-watcher;
  });
}

