#!/bin/bash
#
# Copyright 2020 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.
#
# Test building targets that are declared as compatible only with certain
# platforms (see the "target_compatible_with" common build rule attribute).

# --- begin runfiles.bash initialization ---
set -euo pipefail

if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
  if [[ -f "$0.runfiles_manifest" ]]; then
    export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
  elif [[ -f "$0.runfiles/MANIFEST" ]]; then
    export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
  elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
    export RUNFILES_DIR="$0.runfiles"
  fi
fi
if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
  source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
  source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
            "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
else
  echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
  exit 1
fi
# --- end runfiles.bash initialization ---

source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }

# `uname` returns the current platform, e.g "MSYS_NT-10.0" or "Linux".
# `tr` converts all upper case letters to lower case.
# `case` matches the result if the `uname | tr` expression to string prefixes
# that use the same wildcards as names do in Bash, i.e. "msys*" matches strings
# starting with "msys", and "*" matches everything (it's the default case).
case "$(uname -s | tr [:upper:] [:lower:])" in
msys*)
  # As of 2019-01-15, Bazel on Windows only supports MSYS Bash.
  declare -r is_windows=true
  ;;
*)
  declare -r is_windows=false
  ;;
esac

if "$is_windows"; then
  export MSYS_NO_PATHCONV=1
  export MSYS2_ARG_CONV_EXCL="*"
fi

function set_up() {
  mkdir -p target_skipping || fail "couldn't create directory"
  cat > target_skipping/pass.sh <<'EOF'
#!/bin/bash
exit 0
EOF
  chmod +x target_skipping/pass.sh

  cat > target_skipping/fail.sh <<'EOF'
#!/bin/bash
exit 1
EOF
  chmod +x target_skipping/fail.sh
  # Not using 'EOF' because injecting default_host_platform
  cat > target_skipping/BUILD <<EOF
# We're not validating visibility here. Let everything access these targets.
package(default_visibility = ["//visibility:public"])

constraint_setting(name = "foo_version")

constraint_value(
    name = "foo1",
    constraint_setting = ":foo_version",
)

constraint_value(
    name = "foo2",
    constraint_setting = ":foo_version",
)

constraint_value(
    name = "foo3",
    constraint_setting = ":foo_version",
)

constraint_setting(name = "bar_version")

constraint_value(
    name = "bar1",
    constraint_setting = "bar_version",
)

constraint_value(
    name = "bar2",
    constraint_setting = "bar_version",
)

platform(
    name = "foo1_bar1_platform",
    parents = ["${default_host_platform}"],
    constraint_values = [
        ":foo1",
        ":bar1",
    ],
)

platform(
    name = "foo2_bar1_platform",
    parents = ["${default_host_platform}"],
    constraint_values = [
        ":foo2",
        ":bar1",
    ],
)

platform(
    name = "foo1_bar2_platform",
    parents = ["${default_host_platform}"],
    constraint_values = [
        ":foo1",
        ":bar2",
    ],
)

platform(
    name = "foo3_platform",
    parents = ["${default_host_platform}"],
    constraint_values = [
        ":foo3",
    ],
)

platform(
    name = "foo3_bar2_platform",
    parents = ["${default_host_platform}"],
    constraint_values = [
        ":foo3",
        ":bar2",
    ],
)

sh_test(
    name = "pass_on_foo1",
    srcs = ["pass.sh"],
    target_compatible_with = [":foo1"],
)

sh_test(
    name = "fail_on_foo2",
    srcs = ["fail.sh"],
    target_compatible_with = [":foo2"],
)

sh_test(
    name = "pass_on_foo1_bar2",
    srcs = ["pass.sh"],
    target_compatible_with = [
        ":foo1",
        ":bar2",
    ],
)

sh_binary(
    name = "some_foo3_target",
    srcs = ["pass.sh"],
    target_compatible_with = [
        ":foo3",
    ],
)

# Use this to let us change select() statements from the command line.
config_setting(
  name = "setting1",
  define_values = {
    "foo": "1",
  },
)
EOF
}

add_to_bazelrc "test --nocache_test_results"
add_to_bazelrc "build --incompatible_merge_genfiles_directory=true"

function tear_down() {
  bazel shutdown
}

function set_up_custom_toolchain() {
  mkdir -p target_skipping/custom_tools/
  cat > target_skipping/custom_tools/BUILD <<'EOF'
load(":toolchain.bzl", "custom_toolchain")

package(default_visibility = ["//visibility:public"])

toolchain_type(name = "toolchain_type")

custom_toolchain(
    name = "toolchain",
    compiler_path = "customc",
)

toolchain(
    name = "custom_foo3_toolchain",
    exec_compatible_with = [
        "//target_skipping:foo3",
    ],
    target_compatible_with = [
        "//target_skipping:foo3",
    ],
    toolchain = ":toolchain",
    toolchain_type = ":toolchain_type",
)
EOF

  cat > target_skipping/custom_tools/toolchain.bzl <<'EOF'
def _custom_binary_impl(ctx):
    info = ctx.toolchains["//target_skipping/custom_tools:toolchain_type"].custom_info
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(out, "%s %s" % (info.compiler_path, ctx.file.src.short_path))

custom_binary = rule(
    implementation = _custom_binary_impl,
    attrs = {
        "src": attr.label(allow_single_file=True),
    },
    toolchains = ["//target_skipping/custom_tools:toolchain_type"],
)

CustomInfo = provider(
    fields = {
        "compiler_path": "The path to the compiler binary",
    },
)

def _custom_toolchain_impl(ctx):
    return [platform_common.ToolchainInfo(
        custom_info = CustomInfo(
            compiler_path = ctx.attr.compiler_path,
        ),
    )]

custom_toolchain = rule(
    implementation = _custom_toolchain_impl,
    attrs = {
        "compiler_path": attr.string(),
    },
)

def _compiler_flag_impl(ctx):
    toolchain = ctx.toolchains["//target_skipping/custom_tools:toolchain_type"].custom_info
    return [config_common.FeatureFlagInfo(value = toolchain.compiler_path)]

compiler_flag = rule(
    implementation = _compiler_flag_impl,
    toolchains = ["//target_skipping/custom_tools:toolchain_type"],
)
EOF
}

# Validates that we get a good error message when passing a config_setting into
# the target_compatible_with attribute. This is a regression test for
# https://github.com/bazelbuild/bazel/issues/13250.
function test_config_setting_in_target_compatible_with() {
  cat >> target_skipping/BUILD <<'EOF'
config_setting(
    name = "foo3_config_setting",
    constraint_values = [":foo3"],
)

sh_binary(
    name = "problematic_foo3_target",
    srcs = ["pass.sh"],
    target_compatible_with = [
        ":foo3_config_setting",
    ],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    ... &> "${TEST_log}" && fail "Bazel succeeded unexpectedly."

  expect_log "'//target_skipping:foo3_config_setting' does not have mandatory providers: 'ConstraintValueInfo'"
}

# Validates that the console log provides useful information to the user for
# builds.
function test_console_log_for_builds() {
  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    ... &> "${TEST_log}" || fail "Bazel failed the test."

  cp "${TEST_log}" "${TEST_UNDECLARED_OUTPUTS_DIR}"/

  expect_log 'Target //target_skipping:some_foo3_target up-to-date'
  expect_log 'Target //target_skipping:fail_on_foo2 was skipped'
  expect_log 'Target //target_skipping:pass_on_foo1 was skipped'
  expect_log 'Target //target_skipping:pass_on_foo1_bar2 was skipped'
  expect_not_log 'Target //target_skipping:fail_on_foo2 failed to build'
  expect_not_log 'Target //target_skipping:pass_on_foo1 failed to build'
  expect_not_log 'Target //target_skipping:pass_on_foo1_bar2 failed to build'
  expect_not_log 'Target //target_skipping:fail_on_foo2 up-to-date'
  expect_not_log 'Target //target_skipping:pass_on_foo1 up-to-date'
  expect_not_log 'Target //target_skipping:pass_on_foo1_bar2 up-to-date'
}

# Validates that the console log provides useful information to the user for
# tests.
function test_console_log_for_tests() {
  cd target_skipping || fail "couldn't cd into workspace"

  # Get repeatable results from this test.
  bazel clean --expunge

  bazel test \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    ... &> "${TEST_log}" || fail "Bazel failed unexpectedly."
  expect_log 'Executed 1 out of 3 tests: 1 test passes and 2 were skipped'
  expect_log '^//target_skipping:pass_on_foo1  *  PASSED in'
  expect_log '^//target_skipping:fail_on_foo2  *  SKIPPED$'
  expect_log '^//target_skipping:pass_on_foo1_bar2  *  SKIPPED$'

  bazel test \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    ... &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'Executed 1 out of 3 tests: 1 fails .* and 2 were skipped.'
  expect_log '^//target_skipping:pass_on_foo1  *  SKIPPED$'
  expect_log '^//target_skipping:fail_on_foo2  *  FAILED in'
  expect_log '^//target_skipping:pass_on_foo1_bar2  *  SKIPPED$'

  # Use :all for this one to validate similar behaviour.
  bazel test \
    --host_platform=@//target_skipping:foo1_bar2_platform \
    --platforms=@//target_skipping:foo1_bar2_platform \
    :all &> "${TEST_log}" || fail "Bazel failed unexpectedly."
  expect_log 'Executed 2 out of 3 tests: 2 tests pass and 1 was skipped'
  expect_log '^//target_skipping:pass_on_foo1  *  PASSED in'
  expect_log '^//target_skipping:fail_on_foo2  *  SKIPPED$'
  expect_log '^//target_skipping:pass_on_foo1_bar2  *  PASSED in'
}

# Validates that filegroups and other rules that don't inherit from
# `NativeActionCreatingRule` can be marked with target_compatible_with. This is
# a regression test for https://github.com/bazelbuild/bazel/issues/12745.
function test_skipping_for_rules_that_dont_create_actions() {
  # Create a fake shared library for cc_import.
  echo > target_skipping/some_precompiled_library.so

  cat >> target_skipping/BUILD <<'EOF'
cc_import(
    name = "some_precompiled_library",
    shared_library = "some_precompiled_library.so",
    target_compatible_with = [
        ":foo3",
    ],
)

cc_binary(
    name = "some_binary",
    deps = [
        ":some_precompiled_library",
    ],
)

filegroup(
    name = "filegroup",
    srcs = [
        "some_precompiled_library.so",
    ],
    target_compatible_with = [
        ":foo3",
    ],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    :all &> "${TEST_log}" || fail "Bazel failed unexpectedly."
  expect_log 'Target //target_skipping:some_precompiled_library was skipped'
  expect_log 'Target //target_skipping:some_binary was skipped'
  expect_log 'Target //target_skipping:filegroup was skipped'

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    :filegroup &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'Target //target_skipping:filegroup is incompatible and cannot be built'
}

# Validates that incompatible target skipping errors behave nicely with
# --keep_going. In other words, even if there's an error in the target skipping
# (e.g. because the user explicitly requested an incompatible target) we still
# want Bazel to finish the rest of the build when --keep_going is specified.
function test_failure_on_incompatible_top_level_target() {
  cd target_skipping || fail "couldn't cd into workspace"

  # Validate a variety of ways to refer to the same target.
  local -r -a incompatible_targets=(
      :pass_on_foo1_bar2
      //target_skipping:pass_on_foo1_bar2
      @//target_skipping:pass_on_foo1_bar2
  )

  for incompatible_target in "${incompatible_targets[@]}"; do
    echo "Testing ${incompatible_target}"

    bazel test \
      --show_result=10 \
      --host_platform=@//target_skipping:foo1_bar1_platform \
      --platforms=@//target_skipping:foo1_bar1_platform \
      --build_event_text_file="${TEST_log}".build.json \
      "${incompatible_target}" &> "${TEST_log}" \
      && fail "Bazel passed unexpectedly."

    expect_log 'ERROR:.* Target //target_skipping:pass_on_foo1_bar2 is incompatible and cannot be built'
    expect_log '^ERROR: Build did NOT complete successfully'

    # Now look at the build event log.
    mv "${TEST_log}".build.json "${TEST_log}"

    expect_log '^    name: "PARSING_FAILURE"$'
    expect_log 'Target //target_skipping:pass_on_foo1_bar2 is incompatible and cannot be built.'
  done

  # Run an additional (passing) test and make sure we still fail the build.
  # This is intended to validate that --keep_going works as expected.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    --keep_going \
    //target_skipping:pass_on_foo1_bar2 \
    //target_skipping:pass_on_foo1 &> "${TEST_log}" && fail "Bazel passed unexpectedly."

  expect_log '^//target_skipping:pass_on_foo1  *  PASSED in'
  expect_log '^ERROR: command succeeded, but not all targets were analyzed'
  expect_log '^ERROR: Build did NOT complete successfully'
}

# https://github.com/bazelbuild/bazel/issues/17561 regression test: incompatible
# target skipping doesn't crash with --auto_cpu_environment_group.
function test_failure_on_incompatible_top_level_target_and_auto_cpu_environment_group() {
  cat >> target_skipping/BUILD <<EOF
sh_test(
    name = "always_incompatible",
    srcs = [":pass.sh"],
    target_compatible_with = ["@platforms//:incompatible"],
)
EOF

  mkdir -p buildenv/cpus
  cat > buildenv/cpus/BUILD <<EOF
package(default_visibility = ["//visibility:public"])

environment(name = "foo_cpu")
environment_group(
    name = "cpus",
    defaults = [":foo_cpu"],
    environments = [":foo_cpu"],
)
EOF

  bazel build \
    --nobuild \
    --cpu=foo_cpu \
    --auto_cpu_environment_group=//buildenv/cpus:cpus \
    --build_event_text_file=$TEST_log \
    //target_skipping:all &> "${TEST_log}" \
        || fail "Bazel failed unexpectedly."

  expect_log 'Target //target_skipping:always_incompatible build was skipped'
}

# Validates that incompatible target skipping works with top level targets when
# --skip_incompatible_explicit_targets is enabled.
function test_success_on_incompatible_top_level_target_with_skipping() {
  cd target_skipping || fail "couldn't cd into workspace"

  # Validate a variety of ways to refer to the same target.
  local -r -a incompatible_targets=(
      :pass_on_foo1_bar2
      //target_skipping:pass_on_foo1_bar2
      @//target_skipping:pass_on_foo1_bar2
  )

  for incompatible_target in "${incompatible_targets[@]}"; do
    echo "Testing ${incompatible_target}"

    bazel test \
      --show_result=10 \
      --host_platform=@//target_skipping:foo1_bar1_platform \
      --platforms=@//target_skipping:foo1_bar1_platform \
      --build_event_text_file="${TEST_log}".build.json \
      --skip_incompatible_explicit_targets \
      "${incompatible_target}" &> "${TEST_log}" \
      || fail "Bazel failed unexpectedly."

    expect_log '^//target_skipping:pass_on_foo1_bar2  *  SKIPPED$'
  done
}

# Crudely validates that the build event protocol contains useful information
# when targets are skipped due to incompatibilities.
function test_build_event_protocol() {
  cd target_skipping || fail "couldn't cd into workspace"

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --build_event_text_file=$TEST_log \
    --platforms=@//target_skipping:foo1_bar1_platform \
    ... || fail "Bazel failed unexpectedly."
  expect_not_log 'Target //target_skipping:pass_on_foo1 build was skipped\.'
  expect_log_n 'reason: SKIPPED\>' 3
  expect_log 'Target //target_skipping:fail_on_foo2 build was skipped\.'
  expect_log 'Target //target_skipping:pass_on_foo1_bar2 build was skipped\.'
}

# Validates that incompatibilities are transitive. I.e. targets that depend on
# incompatible targets are themselves deemed incompatible and should therefore
# not be built.
function test_non_top_level_skipping() {
  touch target_skipping/foo_test.sh
  chmod +x target_skipping/foo_test.sh

  cat >> target_skipping/BUILD <<'EOF'
genrule(
    name = "genrule_foo1",
    target_compatible_with = [":foo1"],
    outs = ["foo1.sh"],
    cmd = "echo 'Should not be executed' >&2; exit 1",
)

sh_binary(
    name = "sh_foo2",
    srcs = ["foo1.sh"],
    target_compatible_with = [":foo2"],
)

# Make sure that using an incompatible target in Make variable substitution
# doesn't produce an unexpected error.
sh_test(
    name = "foo_test",
    srcs = ["foo_test.sh"],
    data = [":some_foo3_target"],
    args = ["$(location :some_foo3_target)"],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:sh_foo2 &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:sh_foo2 is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:foo_test &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:foo_test is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'
}

# Validate that targets are skipped when the implementation is in Starlark
# instead of in Java.
function test_starlark_skipping() {
  cat > target_skipping/rules.bzl <<'EOF'
def _echo_rule_impl(ctx):
    ctx.actions.write(ctx.outputs.out, ctx.attr.text)
    return []

_echo_rule = rule(
    implementation = _echo_rule_impl,
    attrs = {
        "text": attr.string(),
        "out": attr.output(),
    },
)

def echo_rule(name, **kwargs):
    _echo_rule(
        name = name,
        out = name,
        **kwargs
    )
EOF

  cat >> target_skipping/BUILD <<'EOF'
load("//target_skipping:rules.bzl", "echo_rule")

echo_rule(
    name = "hello_world",
    text = "Hello World",
    target_compatible_with = [
        "//target_skipping:foo1",
    ],
)

sh_binary(
    name = "hello_world_bin",
    srcs = ["pass.sh"],
    data = [":hello_world"],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping:hello_world_bin &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:hello_world_bin is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'
}

# Validates that rules with custom providers are skipped when incompatible.
# This is valuable because we use providers to convey incompatibility.
function test_dependencies_with_providers() {
  cat > target_skipping/rules.bzl <<'EOF'
DummyProvider = provider()

def _dummy_rule_impl(ctx):
    return [DummyProvider()]

dummy_rule = rule(
    implementation = _dummy_rule_impl,
    attrs = {
        "deps": attr.label_list(providers=[DummyProvider]),
    },
)
EOF

  cat >> target_skipping/BUILD <<'EOF'
load("//target_skipping:rules.bzl", "dummy_rule")

dummy_rule(
    name = "dummy1",
    target_compatible_with = [
        "//target_skipping:foo1",
    ],
)

dummy_rule(
    name = "dummy2",
    deps = [
        ":dummy1",
    ],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  pwd >&2
  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping/... &> "${TEST_log}" || fail "Bazel failed unexpectedly."
  expect_log '^Target //target_skipping:dummy2 was skipped'
}

function test_dependencies_with_extensions() {
  cat > target_skipping/rules.bzl <<'EOF'
def _dummy_rule_impl(ctx):
    out = ctx.actions.declare_file(ctx.attr.name + ".cc")
    ctx.actions.write(out, "Dummy content")
    return DefaultInfo(files = depset([out]))

dummy_rule = rule(
    implementation = _dummy_rule_impl,
)
EOF

  cat >> target_skipping/BUILD <<'EOF'
load("//target_skipping:rules.bzl", "dummy_rule")

# Generates a dummy.cc file.
dummy_rule(
    name = "dummy_file",
    target_compatible_with = [":foo1"],
)

cc_library(
    name = "dummy_cc_lib",
    srcs = [
        "dummy_file",
    ],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  pwd >&2
  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping/... &> "${TEST_log}" || fail "Bazel failed unexpectedly."
  expect_log '^Target //target_skipping:dummy_cc_lib was skipped'
}

# Validates the same thing as test_non_top_level_skipping, but with a cc_test
# and adding one more level of dependencies.
function test_cc_test() {
  cat > target_skipping/generator_tool.cc <<'EOF'
#include <cstdio>
int main() {
  printf("int main() { return 1; }\\n");
  return 0;
}
EOF

  cat >> target_skipping/BUILD <<'EOF'
cc_binary(
    name = "generator_tool",
    srcs = ["generator_tool.cc"],
    target_compatible_with = [":foo1"],
)

genrule(
    name = "generate_with_tool",
    outs = ["generated_test.cc"],
    cmd = "$(location :generator_tool) > $(OUTS)",
    tools = [":generator_tool"],
)

cc_test(
    name = "generated_test",
    srcs = ["generated_test.cc"],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  # Validate the generated file that makes up the test.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:generated_test.cc &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log "ERROR:.* Target //target_skipping:generated_test.cc is incompatible and cannot be built, but was explicitly requested"

  # Validate that we get the dependency chain printed out.
  expect_log '^Dependency chain:$'
  expect_log '^    //target_skipping:generate_with_tool (.*)$'
  expect_log "^    //target_skipping:generator_tool (.*)   <-- target platform (//target_skipping:foo2_bar1_platform) didn't satisfy constraint //target_skipping:foo1"
  expect_log 'ERROR: Build did NOT complete successfully'

  # Validate the test.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:generated_test &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:generated_test is incompatible and cannot be built, but was explicitly requested'

  # Validate that we get the dependency chain printed out.
  expect_log '^Dependency chain:$'
  expect_log '^    //target_skipping:generated_test (.*)$'
  expect_log '^    //target_skipping:generate_with_tool (.*)$'
  expect_log "^    //target_skipping:generator_tool (.*)   <-- target platform (//target_skipping:foo2_bar1_platform) didn't satisfy constraint //target_skipping:foo1"
  expect_log 'ERROR: Build did NOT complete successfully'
}

# Validates the same thing as test_cc_test, but with multiple violated
# constraints.
function test_cc_test_multiple_constraints() {
  cat > target_skipping/generator_tool.cc <<'EOF'
#include <cstdio>
int main() {
  printf("int main() { return 1; }\\n");
  return 0;
}
EOF

  cat >> target_skipping/BUILD <<'EOF'
cc_binary(
    name = "generator_tool",
    srcs = ["generator_tool.cc"],
    target_compatible_with = [":foo1", ":bar2"],
)

genrule(
    name = "generate_with_tool",
    outs = ["generated_test.cc"],
    cmd = "$(location :generator_tool) > $(OUTS)",
    tools = [":generator_tool"],
)

cc_test(
    name = "generated_test",
    srcs = ["generated_test.cc"],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:generated_test &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:generated_test is incompatible and cannot be built, but was explicitly requested'

  # Validate that we get the dependency chain and constraints printed out.
  expect_log '^Dependency chain:$'
  expect_log '^    //target_skipping:generated_test (.*)$'
  expect_log '^    //target_skipping:generate_with_tool (.*)$'
  expect_log "^    //target_skipping:generator_tool (.*)   <-- target platform (//target_skipping:foo2_bar1_platform) didn't satisfy constraints \[//target_skipping:bar2, //target_skipping:foo1\]"
  expect_log 'ERROR: Build did NOT complete successfully'
}

# Validates that we can express targets being compatible with A _or_ B.
function test_or_logic() {
  cat >> target_skipping/BUILD <<'EOF'
sh_test(
    name = "pass_on_foo1_or_foo2_but_not_on_foo3",
    srcs = [":pass.sh"],
    target_compatible_with = select({
        ":foo1": [],
        ":foo2": [],
        "//conditions:default": ["@platforms//:incompatible"],
    }),
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    --nocache_test_results \
    //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3 &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log '^//target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3  *  PASSED in'

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    --nocache_test_results \
    //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3 &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log '^//target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3  *  PASSED in'

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3 &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."

  expect_log 'ERROR:.* Target //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3 is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'
}

# Regression test for b/277371822.
function test_missing_default() {
  cat >> target_skipping/BUILD <<'EOF'
sh_test(
    name = "pass_on_foo1_or_foo2_but_not_on_foo3",
    srcs = [":pass.sh"],
    target_compatible_with = select({
        ":foo1": [],
        # No default branch.
    }),
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3 &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."

  expect_log 'ERROR:.*configurable attribute "target_compatible_with" in //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3'
  expect_log 'ERROR: Build did NOT complete successfully'
  expect_not_log 'FATAL: bazel crashed'
}

# Validates that we can express targets being compatible with everything _but_
# A and B.
function test_inverse_logic() {
  setup_skylib_support

  # Not using 'EOF' because injecting skylib_package
  cat >> target_skipping/BUILD <<EOF
load("${skylib_package}lib:selects.bzl", "selects")

sh_test(
    name = "pass_on_everything_but_foo1_and_foo2",
    srcs = [":pass.sh"],
    target_compatible_with = selects.with_or({
        (":foo1", ":foo2"): ["@platforms//:incompatible"],
        "//conditions:default": [],
    }),
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  # Try with :foo1. This should fail.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:pass_on_everything_but_foo1_and_foo2  &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:pass_on_everything_but_foo1_and_foo2 is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'

  # Try with :foo2. This should fail.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo2_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:pass_on_everything_but_foo1_and_foo2 &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:pass_on_everything_but_foo1_and_foo2 is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'

  # Now with :foo3. This should pass.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    --nocache_test_results \
    //target_skipping:pass_on_everything_but_foo1_and_foo2 &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log '^//target_skipping:pass_on_everything_but_foo1_and_foo2  *  PASSED in'
}

# Validates that we can reference the same incompatible constraint in several,
# composed select() statements. This is useful for expressing compatibility for
# orthogonal constraints. It's also useful when a macro author wants to express
# incompatibility while also honouring the user-defined incompatibility.
function test_composition() {
  # The first select() statement might come from a macro. The second might come
  # from the user who's calling that macro.
  cat >> target_skipping/BUILD <<EOF
sh_test(
    name = "pass_on_foo3_and_bar2",
    srcs = [":pass.sh"],
    target_compatible_with = select({
        ":foo3": [],
        "//conditions:default": ["@platforms//:incompatible"],
    }) + select({
        ":bar2": [],
        "//conditions:default": ["@platforms//:incompatible"],
    }),
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_bar2_platform \
    --platforms=@//target_skipping:foo3_bar2_platform \
    --nocache_test_results \
    //target_skipping:pass_on_foo3_and_bar2 &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log '^//target_skipping:pass_on_foo3_and_bar2  *  PASSED in'

  # Validate that we get an error about the target being incompatible. Make
  # sure that the ":incompatible" constraint is only listed once even though
  # it appears twice in the configured "target_compatible_with" attribute.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:pass_on_foo3_and_bar2 &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."

  expect_log 'ERROR:.* Target //target_skipping:pass_on_foo3_and_bar2 is incompatible and cannot be built, but was explicitly requested'
  expect_log "^    //target_skipping:pass_on_foo3_and_bar2 (.*)   <-- target platform (//target_skipping:foo1_bar1_platform) didn't satisfy constraint @\?@platforms//:incompatible$"
  expect_log 'ERROR: Build did NOT complete successfully'
}

function test_incompatible_with_aliased_constraint() {
  cat >> target_skipping/BUILD <<'EOF'
alias(
    name = "also_foo3",
    actual = ":foo3",
)

alias(
    name = "again_foo3",
    actual = ":foo3",
)

cc_library(
    name = "some_library",
    target_compatible_with = select({
        ":also_foo3": [":again_foo3"],
        "//conditions:default": ["@platforms//:incompatible"],
    }),
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping/... &> "${TEST_log}" \
    || fail "Bazel build failed unexpectedly."
  expect_log 'Target //target_skipping:some_library up-to-date'
}

function test_incompatible_with_aliased_target() {
  cat >> target_skipping/BUILD <<'EOF'
alias(
    name = "also_some_foo3_target",
    actual = ":some_foo3_target",
)
EOF

  # Try with :foo1. This should fail.
  bazel test \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:also_some_foo3_target  &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:also_some_foo3_target is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'
}

# Validate what happens when setting `target_compatible_with` directly on an
# alias(). This is a regression test for
# https://github.com/bazelbuild/bazel/issues/17663.
function test_alias_incompatibility() {
  cat >> target_skipping/BUILD <<'EOF'
filegroup(
    name = "test_cc_filegroup",
    srcs = ["test.cc"],
)

alias(
    name = "test_cc_filegroup_alias",
    actual = ":test_cc_filegroup",
    target_compatible_with = [":foo3"],
)

cc_library(
    name = "test_cc",
    srcs = [":test_cc_filegroup_alias"],
)
EOF

  echo > target_skipping/test.cc

  cd target_skipping || fail "couldn't cd into workspace"
  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:test_cc &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly"
  expect_log_once 'ERROR:.* Target //target_skipping:test_cc is incompatible and cannot be built, but was explicitly requested.'
}

# Validate that an incompatible target with a toolchain not available for the
# current platform will not cause an analysis error. This is a regression test
# for https://github.com/bazelbuild/bazel/issues/12897.
function test_incompatible_with_missing_toolchain() {
  set_up_custom_toolchain
  cat >> WORKSPACE <<'EOF'
local_repository(name = 'build_bazel_apple_support', path = 'build_bazel_apple_support')
EOF
  mkdir -p build_bazel_apple_support/platforms
  touch build_bazel_apple_support/WORKSPACE
  cat > build_bazel_apple_support/platforms/BUILD <<'EOF'
package(default_visibility=["//visibility:public"])
platform(
  name = "darwin_x86_64",
  constraint_values = ["@platforms//os:macos", "@platforms//cpu:x86_64"],
)
EOF

  cat >> target_skipping/BUILD <<'EOF'
load(
    "//target_skipping/custom_tools:toolchain.bzl",
    "compiler_flag",
    "custom_binary",
)

objc_library(
    name = "objc",
    target_compatible_with = select({
        ":setting1": ["//target_skipping:foo1"],
        "//conditions:default": ["//target_skipping:foo2"],
    }),
)

custom_binary(
    name = "custom1",
    src = "custom.txt",
    target_compatible_with = ["//target_skipping:foo1"],
)

compiler_flag(name = "compiler_flag")

config_setting(
    name = "using_custom_toolchain",
    flag_values = {
        ":compiler_flag": "customc",
    },
)

custom_binary(
    name = "custom2",
    src = "custom.txt",
    target_compatible_with = select({
        ":using_custom_toolchain": ["@platforms//:incompatible"],
        "//conditions:default": [],
    }),
)
EOF

  cat > target_skipping/custom.txt <<EOF
This is a custom dummy file.
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build --define=foo=1 \
    --show_result=10 \
    --extra_toolchains=//target_skipping/custom_tools:custom_foo3_toolchain \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping/... &> "${TEST_log}" \
    || fail "Bazel build failed unexpectedly."
  expect_log 'Target //target_skipping:objc was skipped'
  expect_log 'Target //target_skipping:custom1 was skipped'
  expect_log 'Target //target_skipping:custom2 was skipped'
}

# Validates that if a target is "directly incompatible" then its dependencies
# are not evaluated. I.e. there should be no need to guard the dependencies
# with a select() statement.
function test_invalid_deps_are_ignored_when_incompatible() {
  cat >> target_skipping/BUILD <<'EOF'
cc_binary(
    name = "incompatible_tool",
    deps = [
        "//nonexistent_dep",
    ],
    target_compatible_with = [
        ":foo1",
    ],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping/... &> "${TEST_log}" \
    || fail "Bazel build failed unexpectedly."
  expect_log 'Target //target_skipping:incompatible_tool was skipped'
}

# Validates that a tool compatible with the host platform, but incompatible
# with the target platform can still be used as a host tool.
function test_host_tool() {
  # Create an arbitrary host tool.
  cat > target_skipping/host_tool.cc <<'EOF'
#include <cstdio>
int main() {
  printf("Hello World\\n");
  return 0;
}
EOF

  cat >> target_skipping/BUILD <<'EOF'
cc_binary(
    name = "host_tool",
    srcs = ["host_tool.cc"],
    target_compatible_with = [
        ":foo1",
    ],
)

genrule(
    name = "use_host_tool",
    outs = ["host_tool_message.txt"],
    cmd = "$(location :host_tool) >> $(OUTS)",
    tools = [":host_tool"],
    target_compatible_with = [
        ":foo2",
    ],
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  # Run with :foo1 in the host platform, but with :foo2 in the target platform.
  # Building the host tool should fail because it's incompatible with the
  # target platform.
  bazel build --show_result=10  \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:host_tool &> "${TEST_log}" && fail "Bazel passed unexpectedly."
  expect_log 'ERROR:.* Target //target_skipping:host_tool is incompatible and cannot be built, but was explicitly requested'
  expect_log 'ERROR: Build did NOT complete successfully'

  # Run with :foo1 in the host platform, but with :foo2 in the target platform.
  # This should work fine because we're not asking for any constraints to be
  # violated.
  bazel build --host_platform=@//target_skipping:foo1_bar2_platform \
    --platforms=@//target_skipping:foo2_bar1_platform \
    //target_skipping:host_tool_message.txt &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log " ${PRODUCT_NAME}-bin/target_skipping/host_tool_message.txt$"
  expect_log ' Build completed successfully, '

  # Make sure that the contents of the file are what we expect.
  cp ../${PRODUCT_NAME}-bin/target_skipping/host_tool_message.txt "${TEST_log}"
  expect_log 'Hello World'
}

# Validates that we successfully skip analysistest rule targets when they
# depend on incompatible targets.
function test_analysistest() {
  setup_skylib_support

  # Not using 'EOF' because injecting skylib_package
  cat > target_skipping/analysistest.bzl <<EOF
load("${skylib_package}lib:unittest.bzl", "analysistest")

def _analysistest_test_impl(ctx):
    env = analysistest.begin(ctx)
    print("Running the test")
    return analysistest.end(env)

analysistest_test = analysistest.make(_analysistest_test_impl)
EOF

  cat >> target_skipping/BUILD <<'EOF'
load(":analysistest.bzl", "analysistest_test")

analysistest_test(
    name = "foo3_analysistest_test",
    target_under_test = ":some_foo3_target",
)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel test --show_result=10  \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping:foo3_analysistest_test &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log '^//target_skipping:foo3_analysistest_test  *  PASSED in'

  bazel test --show_result=10  \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:all &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log '^Target //target_skipping:foo3_analysistest_test was skipped'
}

function write_query_test_targets() {
  cat >> target_skipping/BUILD <<'EOF'
genrule(
    name = "genrule_foo1",
    target_compatible_with = [":foo1"],
    outs = ["foo1.sh"],
    cmd = "echo 'exit 0' > $(OUTS)",
)

sh_binary(
    name = "sh_foo1",
    srcs = ["foo1.sh"],
)
EOF
}

function test_query() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel query \
    'deps(//target_skipping:sh_foo1)' &> "${TEST_log}" \
    || fail "Bazel query failed unexpectedly."
  expect_log '^//target_skipping:sh_foo1'
  expect_log '^//target_skipping:genrule_foo1'
}

# Regression test for http://b/189071321: --notool_deps should exclude constraints.
function test_query_no_tools() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel query \
    --notool_deps \
    'deps(//target_skipping:sh_foo1)' &> "${TEST_log}" \
    || fail "Bazel query failed unexpectedly."

  if "$is_windows"; then
    sed -i 's/\r//g' "${TEST_log}"
  fi

  expect_log '^//target_skipping:sh_foo1$'
  expect_log '^//target_skipping:genrule_foo1$'
  expect_not_log '^//target_skipping:foo1$'
}

# Run a cquery on a target that is compatible. This should pass.
function test_cquery_compatible_target() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel cquery \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    'deps(//target_skipping:some_foo3_target)' &> "${TEST_log}" \
    || fail "Bazel cquery failed unexpectedly."
}

# Run a cquery with a glob. This should only pick up compatible targets and
# should therefore pass.
function test_cquery_with_glob() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel cquery \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    'deps(//target_skipping/...)' &> "${TEST_log}" \
    || fail "Bazel cquery failed unexpectedly."
  expect_log '^//target_skipping:some_foo3_target '
  expect_log '^//target_skipping:sh_foo1 '
  expect_log '^//target_skipping:genrule_foo1 '
}

# Run a cquery on an incompatible target. This should also pass. Incompatibility
# is more important for builds, where users want meaningful output. For queries,
# understanding target and dependency relationships is more important than
# erroring on unbuildable targets.
function test_cquery_incompatible_target() {
  mkdir -p target_skipping
  cat >> target_skipping/BUILD <<'EOF'
sh_test(
    name = "depender",
    srcs = ["depender.sh"],
    data = [":never_compatible"],
    target_compatible_with = [":foo3"],
)
sh_binary(
    name = "never_compatible",
    srcs = [":never_used.sh"],
    target_compatible_with = ["@platforms//:incompatible"],
)
EOF

  bazel cquery \
    --platforms=@//target_skipping:foo3_platform \
    'somepath(//target_skipping:depender, //target_skipping:never_compatible)' \
    &> "${TEST_log}" \
    || fail "Bazel cquery failed unexpectedly."
  expect_log 'INFO: Found 2 targets'
  expect_log '//target_skipping:depender (.*)'
  expect_log "//target_skipping:never_compatible (.*)"
}

# Runs a cquery and makes sure that we can properly distinguish between
# incompatible targets and compatible targets.
function test_cquery_with_starlark_formatting() {
  cat > target_skipping/compatibility.cquery <<'EOF'
def format(target):
    if "IncompatiblePlatformProvider" in providers(target):
        result = "incompatible"
    else:
        result = "compatible"

    return "%s is %s" % (target.label, result)
EOF

  cd target_skipping || fail "couldn't cd into workspace"

  bazel cquery \
    --host_platform=//target_skipping:foo1_bar1_platform \
    --platforms=//target_skipping:foo1_bar1_platform \
    :all \
    --output=starlark --starlark:file=target_skipping/compatibility.cquery \
    &> "${TEST_log}"

  expect_log '^@@\?//target_skipping:pass_on_foo1 is compatible$'
  expect_log '^@@\?//target_skipping:fail_on_foo2 is incompatible$'
  expect_log '^@@\?//target_skipping:some_foo3_target is incompatible$'

  bazel cquery \
    --host_platform=//target_skipping:foo3_platform \
    --platforms=//target_skipping:foo3_platform \
    :all \
    --output=starlark --starlark:file=target_skipping/compatibility.cquery \
    &> "${TEST_log}"

  expect_log '^@@\?//target_skipping:pass_on_foo1 is incompatible$'
  expect_log '^@@\?//target_skipping:fail_on_foo2 is incompatible$'
  expect_log '^@@\?//target_skipping:some_foo3_target is compatible$'
}

# Run an aquery on a target that is compatible. This should pass.
function test_aquery_compatible_target() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel aquery \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    '//target_skipping:some_foo3_target' &> "${TEST_log}" \
    || fail "Bazel aquery failed unexpectedly."
}

# Run an aquery with a glob. This should only pick up compatible targets and
# should therefore pass.
function test_aquery_with_glob() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel aquery \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    '//target_skipping/...' &> "${TEST_log}" \
    || fail "Bazel aquery failed unexpectedly."
}

# Run an aquery on an incompatible target. This should fail.
function test_aquery_incompatible_target() {
  write_query_test_targets
  cd target_skipping || fail "couldn't cd into workspace"

  bazel aquery \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    '//target_skipping:sh_foo1' &> "${TEST_log}" \
    && fail "Bazel aquery passed unexpectedly."
  expect_log_once 'Target //target_skipping:sh_foo1 is incompatible and cannot be built, but was explicitly requested'
  expect_log_once "target platform (//target_skipping:foo3_platform) didn't satisfy constraint //target_skipping:foo1"
}

# Use aspects to interact with incompatible targets and validate the behaviour.
function test_aspect_skipping() {
  cat >> target_skipping/BUILD <<'EOF'
load(":defs.bzl", "basic_rule", "rule_with_aspect")

# This target is compatible with all platforms and configurations. This target
# exists to validate the behaviour of aspects running against incompatible
# targets. The expectation is that the aspect should _not_ propagate to this
# compatible target from an incompatible target. I.e. an aspect should _not_
# evaluate this target if "basic_foo3_target" is incompatible.
basic_rule(
    name = "basic_universal_target",
)
# An alias to validate that incompatible target skipping works as expected with
# aliases and aspects.
alias(
    name = "aliased_basic_universal_target",
    actual = ":basic_universal_target",
)
basic_rule(
    name = "basic_foo3_target",
    deps = [
        ":aliased_basic_universal_target",
    ],
    target_compatible_with = [
        ":foo3",
    ],
)
# This target is only compatible when "basic_foo3_target" is compatible. This
# target exists to validate the behaviour of aspects running against
# incompatible targets. The expectation is that the aspect should _not_
# evaluate this target when "basic_foo3_target" is incompatible.
basic_rule(
    name = "other_basic_target",
    deps = [
        ":basic_foo3_target",
    ],
)
alias(
    name = "aliased_other_basic_target",
    actual = ":other_basic_target",
)
rule_with_aspect(
    name = "inspected_foo3_target",
    inspect = ":aliased_other_basic_target",
)
basic_rule(
    name = "previously_inspected_basic_target",
    deps = [
        ":inspected_foo3_target",
    ],
)
rule_with_aspect(
    name = "twice_inspected_foo3_target",
    inspect = ":previously_inspected_basic_target",
)
genrule(
    name = "generated_file",
    outs = ["generated_file.txt"],
    cmd = "echo '' > $(OUTS)",
    target_compatible_with = [
        ":foo1",
    ],
)
rule_with_aspect(
    name = "inspected_generated_file",
    inspect = ":generated_file",
)
EOF
  cat > target_skipping/defs.bzl <<'EOF'
BasicProvider = provider()
def _basic_rule_impl(ctx):
    return [DefaultInfo(), BasicProvider()]
basic_rule = rule(
    implementation = _basic_rule_impl,
    attrs = {
        "deps": attr.label_list(
            providers = [BasicProvider],
        ),
    },
)
def _inspecting_aspect_impl(target, ctx):
    print("Running aspect on " + str(target))
    return []
_inspecting_aspect = aspect(
    implementation = _inspecting_aspect_impl,
    attr_aspects = ["deps"],
)
def _rule_with_aspect_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(out, "")
    return [
        DefaultInfo(files=depset([out])),
        BasicProvider(),
    ]
rule_with_aspect = rule(
    implementation = _rule_with_aspect_impl,
    attrs = {
        "inspect": attr.label(
            aspects = [_inspecting_aspect],
        ),
    },
)
EOF
  cd target_skipping || fail "couldn't cd into workspace"
  local debug_message1="Running aspect on <target //target_skipping:basic_universal_target>"
  local debug_message2="Running aspect on <target //target_skipping:basic_foo3_target>"
  local debug_message3="Running aspect on <target //target_skipping:other_basic_target>"
  local debug_message4="Running aspect on <target //target_skipping:previously_inspected_basic_target>"
  local debug_message5="Running aspect on <target //target_skipping:generated_file>"
  # Validate that aspects run against compatible targets.
  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo3_platform \
    --platforms=@//target_skipping:foo3_platform \
    //target_skipping:all &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log "${debug_message1}"
  expect_log "${debug_message2}"
  expect_log "${debug_message3}"
  expect_log "${debug_message4}"
  expect_not_log "${debug_message5}"
  # Invert the compatibility and validate that aspects run on the other targets
  # now.
  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:all &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_not_log "${debug_message1}"
  expect_not_log "${debug_message2}"
  expect_not_log "${debug_message3}"
  expect_not_log "${debug_message4}"
  expect_log "${debug_message5}"
  # Validate that explicitly trying to build a target with an aspect against an
  # incompatible target produces the normal error message.
  bazel build \
    --show_result=10 \
    --host_platform=@//target_skipping:foo1_bar1_platform \
    --platforms=@//target_skipping:foo1_bar1_platform \
    //target_skipping:twice_inspected_foo3_target &> "${TEST_log}" \
    && fail "Bazel passed unexpectedly."
  expect_log_once 'ERROR:.* Target //target_skipping:twice_inspected_foo3_target is incompatible and cannot be built, but was explicitly requested.'
  expect_log_once '^Dependency chain:$'
  expect_log_once '^    //target_skipping:twice_inspected_foo3_target '
  expect_log_once '^    //target_skipping:previously_inspected_basic_target '
  expect_log_once '^    //target_skipping:inspected_foo3_target '
  expect_log_once '^    //target_skipping:aliased_other_basic_target '
  expect_log_once '^    //target_skipping:other_basic_target '
  expect_log_once "    //target_skipping:basic_foo3_target .*  <-- target platform (//target_skipping:foo1_bar1_platform) didn't satisfy constraint //target_skipping:foo3$"
  expect_log 'ERROR: Build did NOT complete successfully'
  expect_not_log "${debug_message1}"
  expect_not_log "${debug_message2}"
  expect_not_log "${debug_message3}"
  expect_not_log "${debug_message4}"
  expect_not_log "${debug_message5}"
}

function test_skipping_with_missing_toolchain() {
  mkdir -p missing_toolchain

  cat > missing_toolchain/BUILD <<'EOF'
load(":rule.bzl", "my_rule")

toolchain_type(name = "my_toolchain_type")

my_rule(
    name = "my_rule",
    target_compatible_with = ["@platforms//:incompatible"],
)
EOF

  cat > missing_toolchain/rule.bzl <<'EOF'
def _my_rule_impl(ctx):
    pass

my_rule = rule(
    _my_rule_impl,
    toolchains = ["//missing_toolchain:my_toolchain_type"],
)
EOF

  bazel build --show_result=10 //missing_toolchain:all &> "${TEST_log}" \
    || fail "Bazel failed unexpectedly."
  expect_log "Target //missing_toolchain:my_rule was skipped"
}

run_suite "target_compatible_with tests"
