#!/bin/bash
#
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Test the providers and rules related to toolchains.
#

# --- begin runfiles.bash initialization ---
# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
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; }

function set_up() {
  create_new_workspace
  # Clean out the WORKSPACE file.
  rm WORKSPACE
  touch WORKSPACE

  # Create shared report rule for printing toolchain info.
  mkdir -p report
  touch report/BUILD
  cat > report/report.bzl <<EOF
def _report_impl(ctx):
  toolchain = ctx.attr.toolchain[platform_common.ToolchainInfo]
  for field in ctx.attr.fields:
    value = getattr(toolchain, field)
    if type(value) == 'Target':
      value = value.label
    print('%s = "%s"' % (field, value))

report_toolchain = rule(
    implementation = _report_impl,
    attrs = {
        'fields': attr.string_list(),
        'toolchain': attr.label(providers = [platform_common.ToolchainInfo]),
    }
)
EOF
}

function write_test_toolchain() {
  local pkg="${1}"
  local toolchain_name="${2:-test_toolchain}"

  mkdir -p "${pkg}/toolchain"
  cat >> "${pkg}/toolchain/toolchain_${toolchain_name}.bzl" <<EOF
def _impl(ctx):
  toolchain = platform_common.ToolchainInfo(
      extra_label = ctx.attr.extra_label,
      extra_str = ctx.attr.extra_str)
  return [toolchain]

${toolchain_name} = rule(
    implementation = _impl,
    attrs = {
        'extra_label': attr.label(),
        'extra_str': attr.string(),
    }
)
EOF

  if [[ ! -e "${pkg}/toolchain/BUILD" ]]; then
    cat > "${pkg}/toolchain/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])
EOF
  fi

  cat >> "${pkg}/toolchain/BUILD" <<EOF
toolchain_type(name = '${toolchain_name}')
EOF
}

function write_test_rule() {
  local pkg="${1}"
  local rule_name="${2:-use_toolchain}"
  local toolchain_name="${3:-test_toolchain}"

  mkdir -p "${pkg}/toolchain"
  cat >> "${pkg}/toolchain/rule_${rule_name}.bzl" <<EOF
def _impl(ctx):
  if '//${pkg}/toolchain:${toolchain_name}' not in ctx.toolchains:
    fail('Toolchain type //${pkg}/toolchain:${toolchain_name} not found')
  toolchain = ctx.toolchains['//${pkg}/toolchain:${toolchain_name}']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain extra_str: "%s"' %
         (message, toolchain.extra_str))
  return []

${rule_name} = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = ['//${pkg}/toolchain:${toolchain_name}'],
)
EOF
}

function write_test_aspect() {
  local pkg="${1}"
  local aspect_name="${2:-use_toolchain}"
  local toolchain_name="${3:-test_toolchain}"

  mkdir -p "${pkg}/toolchain"
  cat >> "${pkg}/toolchain/aspect_${aspect_name}.bzl" <<EOF
def _impl(target, ctx):
  toolchain = ctx.toolchains['//${pkg}/toolchain:${toolchain_name}']
  message = ctx.rule.attr.message
  print(
      'Using toolchain in aspect: rule message: "%s", toolchain extra_str: "%s"' %
          (message, toolchain.extra_str))
  return []

${aspect_name} = aspect(
    implementation = _impl,
    attrs = {},
    toolchains = ['//${pkg}/toolchain:${toolchain_name}'],
    apply_to_generating_rules = True,
)
EOF
}

function write_register_toolchain() {
  local pkg="${1}"
  local toolchain_name="${2:-test_toolchain}"
  local exec_compatible_with="${3:-"[]"}"
  local target_compatible_with="${4:-"[]"}"

  cat >> WORKSPACE <<EOF
register_toolchains('//register/${pkg}:${toolchain_name}_1')
EOF

  mkdir -p "register/${pkg}"

  if [[ ! -e "register/${pkg}/BUILD" ]]; then
    cat > "register/${pkg}/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])
EOF
  fi
  cat >> "register/${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_${toolchain_name}.bzl', '${toolchain_name}')

# Define the toolchain.
filegroup(name = 'dep_rule_${toolchain_name}')
${toolchain_name}(
    name = '${toolchain_name}_impl_1',
    extra_label = ':dep_rule_${toolchain_name}',
    extra_str = 'foo from ${toolchain_name}',
)

# Declare the toolchain.
toolchain(
    name = '${toolchain_name}_1',
    toolchain_type = '//${pkg}/toolchain:${toolchain_name}',
    exec_compatible_with = ${exec_compatible_with},
    target_compatible_with = ${target_compatible_with},
    toolchain = ':${toolchain_name}_impl_1',
)
EOF
}

function test_toolchain_provider() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"

  mkdir -p "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')
load('//report:report.bzl', 'report_toolchain')

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

filegroup(name = 'dep_rule')
test_toolchain(
    name = 'linux_toolchain',
    extra_label = ':dep_rule',
    extra_str = 'bar',
)
report_toolchain(
  name = 'report',
  fields = ['extra_label', 'extra_str'],
  toolchain = ':linux_toolchain',
)
EOF

  bazel build "//${pkg}:report" &> $TEST_log || fail "Build failed"
  expect_log "extra_label = \"@//${pkg}:dep_rule\""
  expect_log 'extra_str = "bar"'
}

function test_toolchain_use_in_rule {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"
  write_register_toolchain "${pkg}"

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')
package(default_visibility = ["//visibility:public"])
# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_alias_use_in_rule {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  mkdir -p "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchain.
filegroup(name = 'dep_rule_test_toolchain')
test_toolchain(
    name = 'test_toolchain_impl_1',
    extra_label = ':dep_rule_test_toolchain',
    extra_str = 'foo from test_toolchain',
)
alias(
    name = 'test_toolchain_impl_1_alias',
    actual = ':test_toolchain_impl_1',
)

# Declare the toolchain.
toolchain(
    name = 'test_toolchain_1',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [],
    target_compatible_with = [],
    toolchain = ':test_toolchain_impl_1_alias',
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build \
    "--extra_toolchains=//${pkg}:test_toolchain_1" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_alias_chain_use_in_rule {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  mkdir -p "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchain.
filegroup(name = 'dep_rule_test_toolchain')
test_toolchain(
    name = 'test_toolchain_impl_1',
    extra_label = ':dep_rule_test_toolchain',
    extra_str = 'foo from test_toolchain',
)
alias(
    name = 'test_toolchain_impl_1_alias_alpha',
    actual = ':test_toolchain_impl_1',
)
alias(
    name = 'test_toolchain_impl_1_alias_beta',
    actual = ':test_toolchain_impl_1_alias_alpha',
)

# Declare the toolchain.
toolchain(
    name = 'test_toolchain_1',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [],
    target_compatible_with = [],
    toolchain = ':test_toolchain_impl_1_alias_beta',
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build \
    "--extra_toolchains=//${pkg}:test_toolchain_1" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_type_alias_use_in_toolchain {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  # Create an alias for the toolchain type.
  mkdir -p "${pkg}/alias"
  cat > "${pkg}/alias/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

alias(
    name = 'toolchain_type',
    actual = '//${pkg}/toolchain:test_toolchain',
)
EOF

  # Use the alias.
  mkdir -p "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchain.
filegroup(name = 'dep_rule_test_toolchain')
test_toolchain(
    name = 'test_toolchain_impl_1',
    extra_label = ':dep_rule_test_toolchain',
    extra_str = 'foo from test_toolchain',
)

# Declare the toolchain.
toolchain(
    name = 'test_toolchain_1',
    toolchain_type = '//${pkg}/alias:toolchain_type',
    exec_compatible_with = [],
    target_compatible_with = [],
    toolchain = ':test_toolchain_impl_1',
)
EOF

  # The rule uses the original, non-aliased type.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build \
    "--extra_toolchains=//${pkg}:test_toolchain_1" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_type_alias_use_in_rule {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_register_toolchain "${pkg}"

  # Create an alias for the toolchain type.
  mkdir -p "${pkg}/alias"
  cat > "${pkg}/alias/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

alias(
    name = 'toolchain_type',
    actual = '//${pkg}/toolchain:test_toolchain',
)
EOF

  # Use the alias in a rule.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/aliased_rule.bzl" <<EOF
def _impl(ctx):
  toolchain = ctx.toolchains['//${pkg}/alias:toolchain_type']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain extra_str: "%s"' %
         (message, toolchain.extra_str))
  return []

aliased_rule = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = ['//${pkg}/alias:toolchain_type'],
)
EOF

  cat > "${pkg}/demo/BUILD" <<EOF
load(':aliased_rule.bzl', 'aliased_rule')

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

# Use the toolchain.
aliased_rule(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_use_in_rule_missing {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"
  #rite_register_toolchain
  # Do not register test_toolchain to trigger the error.

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "While resolving toolchains for target //${pkg}/demo:use: No matching toolchains found for types //${pkg}/toolchain:test_toolchain."
}

function test_multiple_toolchain_use_in_rule {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}" test_toolchain_1
  write_test_toolchain "${pkg}" test_toolchain_2

  write_register_toolchain "${pkg}" test_toolchain_1
  write_register_toolchain "${pkg}" test_toolchain_2

  # The rule uses two separate toolchains.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchains.bzl" <<EOF
def _impl(ctx):
  toolchain_1 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_1']
  toolchain_2 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_2']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain 1 extra_str: "%s", toolchain 2 extra_str: "%s"' %
         (message, toolchain_1.extra_str, toolchain_2.extra_str))
  return []

use_toolchains = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = [
        '//${pkg}/toolchain:test_toolchain_1',
        '//${pkg}/toolchain:test_toolchain_2',
    ],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchains.bzl', 'use_toolchains')

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

# Use the toolchain.
use_toolchains(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain 1 extra_str: "foo from test_toolchain_1", toolchain 2 extra_str: "foo from test_toolchain_2"'
}

function test_multiple_toolchain_use_in_rule_with_optional_missing {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}" test_toolchain_1
  write_test_toolchain "${pkg}" test_toolchain_2

  write_register_toolchain "${pkg}" test_toolchain_1

  # The rule uses two separate toolchains.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchains.bzl" <<EOF
def _impl(ctx):
  toolchain_1 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_1']
  toolchain_2 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_2']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain 1 extra_str: "%s", toolchain 2 is none: %s' %
         (message, toolchain_1.extra_str, toolchain_2 == None))
  return []

use_toolchains = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = [
        '//${pkg}/toolchain:test_toolchain_1',
        config_common.toolchain_type('//${pkg}/toolchain:test_toolchain_2', mandatory = False),
    ],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchains.bzl', 'use_toolchains')

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

# Use the toolchain.
use_toolchains(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain 1 extra_str: "foo from test_toolchain_1", toolchain 2 is none: True'
}

function test_multiple_toolchain_use_in_rule_one_missing {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}" test_toolchain_1
  write_test_toolchain "${pkg}" test_toolchain_2

  write_register_toolchain "${pkg}" test_toolchain_1
  # Do not register test_toolchain_2 to cause the error.

  # The rule uses two separate toolchains.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchains.bzl" <<EOF
def _impl(ctx):
  toolchain_1 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_1']
  toolchain_2 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_2']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain 1 extra_str: "%s", toolchain 2 extra_str: "%s"' %
         (message, toolchain_1.extra_str, toolchain_2.extra_str))
  return []

use_toolchains = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = [
        '//${pkg}/toolchain:test_toolchain_1',
        '//${pkg}/toolchain:test_toolchain_2',
    ],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchains.bzl', 'use_toolchains')

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

# Use the toolchain.
use_toolchains(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "While resolving toolchains for target //${pkg}/demo:use: No matching toolchains found for types //${pkg}/toolchain:test_toolchain_2."
}

function test_toolchain_use_in_rule_non_required_toolchain {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_register_toolchain "${pkg}"

  # The rule argument toolchains requires one toolchain, but the implementation requests a different
  # one.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchain.bzl" <<EOF
def _impl(ctx):
  toolchain = ctx.toolchains['//${pkg}/toolchain:wrong_toolchain']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain extra_str: "%s"' %
         (message, toolchain.extra_str))
  return []

use_toolchain = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = ['//${pkg}/toolchain:test_toolchain'],
)
EOF

  # Trigger the wrong toolchain.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "In use_toolchain rule //${pkg}/demo:use, toolchain type //${pkg}/toolchain:wrong_toolchain was requested but only types \[//${pkg}/toolchain:test_toolchain\] are configured"
}

function test_toolchain_debug_messages {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"
  write_register_toolchain "${pkg}"

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build \
    --toolchain_resolution_debug=toolchain:test_toolchain \
    --platform_mappings= \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log "ToolchainResolution:   Type //${pkg}/toolchain:test_toolchain: target platform ${default_host_platform}: execution ${default_host_platform}: Selected toolchain //register/${pkg}:test_toolchain_impl_1"
  expect_log "ToolchainResolution: Target platform ${default_host_platform}: Selected execution platform ${default_host_platform}, type //${pkg}/toolchain:test_toolchain -> toolchain //register/${pkg}:test_toolchain_impl_1"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_debug_messages_target {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"
  write_register_toolchain "${pkg}"

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build \
    --toolchain_resolution_debug=demo:use \
    --platform_mappings= \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log "ToolchainResolution:   Type //${pkg}/toolchain:test_toolchain: target platform ${default_host_platform}: execution ${default_host_platform}: Selected toolchain //register/${pkg}:test_toolchain_impl_1"
  expect_log "ToolchainResolution: Target platform ${default_host_platform}: Selected execution platform ${default_host_platform}, type //${pkg}/toolchain:test_toolchain -> toolchain //register/${pkg}:test_toolchain_impl_1"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_use_in_aspect {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_aspect "${pkg}"
  write_register_toolchain "${pkg}"

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/demo.bzl" <<EOF
def _impl(ctx):
  return []

demo = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    }
)
EOF
  cat > "${pkg}/demo/BUILD" <<EOF
load(':demo.bzl', 'demo')

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

demo(
    name = 'use',
    message = 'bar from demo')
EOF

  bazel build \
    --aspects //${pkg}/toolchain:aspect_use_toolchain.bzl%use_toolchain \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain in aspect: rule message: "bar from demo", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_use_in_aspect_with_output_file {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_aspect "${pkg}"
  write_register_toolchain "${pkg}"

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/demo.bzl" <<EOF
def _impl(ctx):
    output = ctx.outputs.out
    ctx.actions.write(output = output, content = ctx.attr.message)

demo = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
        'out': attr.output(),
    }
)
EOF
  cat > "${pkg}/demo/BUILD" <<EOF
load(':demo.bzl', 'demo')

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

demo(
    name = 'use',
    message = 'bar from demo',
    out = 'use.log',
)
EOF

  # Also test aspects executing on an output file.
  bazel build \
    --aspects //${pkg}/toolchain:aspect_use_toolchain.bzl%use_toolchain \
    "//${pkg}/demo:use.log" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain in aspect: rule message: "bar from demo", toolchain extra_str: "foo from test_toolchain"'
}

function test_toolchain_use_in_aspect_non_required_toolchain {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_register_toolchain "${pkg}"

  # The aspect argument toolchains requires one toolchain, but the implementation requests a
  # different one.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/aspect_use_toolchain.bzl" <<EOF
def _impl(target, ctx):
  toolchain = ctx.toolchains['//${pkg}/toolchain:wrong_toolchain']
  message = ctx.rule.attr.message
  print(
      'Using toolchain in aspect: rule message: "%s", toolchain extra_str: "%s"' %
          (message, toolchain.extra_str))
  return []

use_toolchain = aspect(
    implementation = _impl,
    attrs = {},
    toolchains = ['//${pkg}/toolchain:test_toolchain'],
)
EOF

  # Trigger the wrong toolchain.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/demo.bzl" <<EOF
def _impl(ctx):
  return []

demo = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    }
)
EOF
  cat > "${pkg}/demo/BUILD" <<EOF
load(':demo.bzl', 'demo')

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

demo(
    name = 'use',
    message = 'bar from demo')
EOF

  bazel build \
    --aspects "//${pkg}/toolchain:aspect_use_toolchain.bzl%use_toolchain" \
    "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "In aspect //${pkg}/toolchain:aspect_use_toolchain.bzl%use_toolchain applied to demo rule //${pkg}/demo:use, toolchain type //${pkg}/toolchain:wrong_toolchain was requested but only types \[//${pkg}/toolchain:test_toolchain\] are configured"
}

function test_toolchain_constraints() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:toolchain_1')
register_toolchains('//${pkg}:toolchain_2')
EOF

  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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


# Define constraints.
constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)

# Define the toolchain.
filegroup(name = 'dep_rule')
test_toolchain(
    name = 'toolchain_impl_1',
    extra_label = ':dep_rule',
    extra_str = 'foo from 1',
)
test_toolchain(
    name = 'toolchain_impl_2',
    extra_label = ':dep_rule',
    extra_str = 'foo from 2',
)

# Declare the toolchain.
toolchain(
    name = 'toolchain_1',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [':value1'],
    target_compatible_with = [':value2'],
    toolchain = ':toolchain_impl_1')
toolchain(
    name = 'toolchain_2',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [':value2'],
    target_compatible_with = [':value1'],
    toolchain = ':toolchain_impl_2')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  # This should use toolchain_1.
  bazel build \
    --host_platform="//${pkg}:platform1" \
    --platforms="//${pkg}:platform2" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 1"'

  # This should use toolchain_2.
  bazel build \
    --host_platform="//${pkg}:platform2" \
    --platforms="//${pkg}:platform1" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 2"'

  # This should not match any toolchains.
  bazel build \
    --host_platform="//${pkg}:platform1" \
    --platforms="//${pkg}:platform1" \
    "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "While resolving toolchains for target //${pkg}/demo:use: No matching toolchains found for types //${pkg}/toolchain:test_toolchain."
  expect_not_log 'Using toolchain: rule message:'
}

function test_register_toolchain_error_invalid_label() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"
  write_register_toolchain "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('/:invalid:label:syntax')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "error parsing target pattern \"/:invalid:label:syntax\": invalid package name '/': package names may not start with '/'"
}

function test_register_toolchain_error_invalid_target() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}/demo:not_a_target')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "While resolving toolchains for target //${pkg}/demo:use: invalid registered toolchain '//${pkg}/demo:not_a_target': no such target '//${pkg}/demo:not_a_target': target 'not_a_target' not declared in package '${pkg}/demo'"
}

function test_register_toolchain_error_target_not_a_toolchain() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}/demo:invalid')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/out.log" <<EOF
INVALID
EOF
  cat > "${pkg}/demo/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

filegroup(
    name = "invalid",
    srcs = ["out.log"],
)

load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')
# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "While resolving toolchains for target //${pkg}/demo:use: invalid registered toolchain '//${pkg}/demo:invalid': target does not provide the DeclaredToolchainInfo provider"
}


function test_register_toolchain_error_invalid_pattern() {
  local -r pkg="${FUNCNAME[0]}"
  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:bad1')
register_toolchains('//${pkg}:bad2')
EOF

  mkdir -p "${pkg}"
  cat >"${pkg}/rules.bzl" <<EOF
def _impl(ctx):
  toolchain = ctx.toolchains['//${pkg}:dummy']
  return []

foo = rule(
  implementation = _impl,
  toolchains = ['//${pkg}:dummy'],
)
EOF

  cat > "${pkg}/BUILD" <<EOF
load(":rules.bzl", "foo")

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

toolchain_type(name = 'dummy')
foo(name = "foo")
EOF

  bazel build "//${pkg}:foo" &> $TEST_log && fail "Build failure expected"
  # It's uncertain which error will happen first, so handle either.
  expect_log "While resolving toolchains for target //${pkg}:foo: invalid registered toolchain '//${pkg}:bad[12]': no such target"
}


function test_toolchain_error_invalid_target() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  # Write toolchain with an invalid target.
  mkdir -p "${pkg}/invalid"
  cat > "${pkg}/invalid/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

toolchain(
    name = 'invalid_toolchain',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [],
    target_compatible_with = [],
    toolchain = '//${pkg}/toolchain:does_not_exist',
)
EOF

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}/invalid:invalid_toolchain')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "Target '//${pkg}/demo:use' depends on toolchain '//${pkg}/toolchain:does_not_exist', which cannot be found: no such target '//${pkg}/toolchain:does_not_exist': target 'does_not_exist' not declared in package '${pkg}/toolchain'"
}


function test_platforms_options_error_invalid_target() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"
  write_register_toolchain "${pkg}"

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  # Write an invalid rule to be the platform.
  mkdir -p "${pkg}/platform"
  cat > "${pkg}/platform/BUILD" <<EOF

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

filegroup(name = 'not_a_platform')
EOF

  bazel build \
    --platforms="//${pkg}/platform:not_a_platform" \
    "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "Target //${pkg}/platform:not_a_platform was referenced as a platform, but does not provide PlatformInfo"

  bazel build \
    --host_platform="//${pkg}/platform:not_a_platform" \
    "//${pkg}/demo:use" &> $TEST_log && fail "Build failure expected"
  expect_log "Target //${pkg}/platform:not_a_platform was referenced as a platform, but does not provide PlatformInfo"
}


function test_native_rule_target_exec_constraints() {
  local -r pkg="${FUNCNAME[0]}"
  mkdir -p "${pkg}/platform"
  cat > "${pkg}/platform/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

constraint_setting(name = "test")

constraint_value(
    name = "test_enabled",
    constraint_setting = ":test",
)

platform(
    name = "test_platform",
    constraint_values = [
        ":test_enabled",
    ],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

genrule(
    name = "target",
    outs = ["out.txt"],
    cmd = """
      echo "platform" > \$@
    """,
    exec_compatible_with = [
        "//${pkg}/platform:test_enabled",
    ],
)
EOF

  # When no platform has the constraint, an error
  bazel build \
    --toolchain_resolution_debug=.* \
    "//${pkg}/demo:target" &> $TEST_log && fail "Build failure expected"
  expect_log "While resolving toolchains for target //${pkg}/demo:target: .* from available execution platforms \[\]"

  # When the platform exists, it is used.
  bazel build \
    --extra_execution_platforms="//${pkg}/platform:test_platform" \
    --toolchain_resolution_debug=.* \
    "//${pkg}/demo:target" &> $TEST_log || fail "Build failed"
  expect_log "Selected execution platform //${pkg}/platform:test_platform"
}


function test_rule_with_default_execution_constraints() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_register_toolchain "${pkg}"

  # Add test platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)
EOF

  # Add a rule with default execution constraints.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _impl(ctx):
  return []

sample_rule = rule(
  implementation = _impl,
  attrs = {},
  exec_compatible_with = [
    '//${pkg}/platforms:value2',
  ],
  toolchains = ['//${pkg}/toolchain:test_toolchain'],
)
EOF

  # Use the new rule.
  cat > "${pkg}/demo/BUILD" <<EOF
load(':rule.bzl', 'sample_rule')

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

sample_rule(name = 'use')
EOF

  # Build the target, using debug messages to verify the correct platform was selected.
  bazel build \
    --extra_execution_platforms="//${pkg}/platforms:all" \
    --toolchain_resolution_debug=toolchain:test_toolchain \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log "Selected execution platform //${pkg}/platforms:platform2"
}


function test_target_with_execution_constraints() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_register_toolchain "${pkg}"

  # Add test platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)
EOF

  # Add a rule with default execution constraints.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _impl(ctx):
  return []

sample_rule = rule(
  implementation = _impl,
  attrs = {},
  toolchains = ['//${pkg}/toolchain:test_toolchain'],
)
EOF

  # Use the new rule.
  cat > "${pkg}/demo/BUILD" <<EOF
load(':rule.bzl', 'sample_rule')

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

sample_rule(
  name = 'use',
  exec_compatible_with = [
    '//${pkg}/platforms:value2',
  ],
)
EOF

  # Build the target, using debug messages to verify the correct platform was selected.
  bazel build \
    --extra_execution_platforms="//${pkg}/platforms:all" \
    --toolchain_resolution_debug=toolchain:test_toolchain \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log "Selected execution platform //${pkg}/platforms:platform2"
}

function test_rule_and_target_with_execution_constraints() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_register_toolchain "${pkg}"

  # Add test platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting1')
constraint_value(name = 'value1', constraint_setting = ':setting1')
constraint_value(name = 'value2', constraint_setting = ':setting1')

constraint_setting(name = 'setting2')
constraint_value(name = 'value3', constraint_setting = ':setting2')
constraint_value(name = 'value4', constraint_setting = ':setting2')

platform(
    name = 'platform1_3',
    constraint_values = [':value1', ':value3'],
)
platform(
    name = 'platform1_4',
    constraint_values = [':value1', ':value4'],
)
platform(
    name = 'platform2_3',
    constraint_values = [':value2', ':value3'],
)
platform(
    name = 'platform2_4',
    constraint_values = [':value2', ':value4'],
)
EOF

  # Add a rule with default execution constraints.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _impl(ctx):
  return []

sample_rule = rule(
  implementation = _impl,
  attrs = {},
  exec_compatible_with = [
    '//${pkg}/platforms:value2',
  ],
  toolchains = ['//${pkg}/toolchain:test_toolchain'],
)
EOF

  # Use the new rule.
  cat > "${pkg}/demo/BUILD" <<EOF
load(':rule.bzl', 'sample_rule')

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

sample_rule(
  name = 'use',
  exec_compatible_with = [
    '//${pkg}/platforms:value4',
  ],
)
EOF

  # Build the target, using debug messages to verify the correct platform was selected.
  bazel build \
    --extra_execution_platforms="//${pkg}/platforms:all" \
    --toolchain_resolution_debug=toolchain:test_toolchain \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log "Selected execution platform //${pkg}/platforms:platform2_4"
}

function test_target_setting() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:toolchain_1')
register_toolchains('//${pkg}:toolchain_2')
EOF

  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchain.
filegroup(name = 'dep_rule')
test_toolchain(
    name = 'toolchain_impl_1',
    extra_label = ':dep_rule',
    extra_str = 'foo from 1',
)
test_toolchain(
    name = 'toolchain_impl_2',
    extra_label = ':dep_rule',
    extra_str = 'foo from 2',
)

# Define config setting
config_setting(
    name = "optimised",
    values = {"compilation_mode": "opt"}
)

# Declare the toolchain.
toolchain(
    name = 'toolchain_1',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    target_settings = [":optimised"],
    toolchain = ':toolchain_impl_1')
toolchain(
    name = 'toolchain_2',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    toolchain = ':toolchain_impl_2')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  # This should use toolchain_2.
  bazel build \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 2"'

  # This should use toolchain_1.
  bazel build \
    --compilation_mode=opt \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 1"'

  # This should match toolchain_2.
  bazel build \
    --compilation_mode=fastbuild \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 2"'
}

function test_target_setting_with_transition() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"
  write_test_rule "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:toolchain_1')
register_toolchains('//${pkg}:toolchain_2')
EOF

  mkdir -p "{$pkg}"
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchain.
filegroup(name = 'dep_rule')
test_toolchain(
    name = 'toolchain_impl_1',
    extra_label = ':dep_rule',
    extra_str = 'foo from 1',
)
test_toolchain(
    name = 'toolchain_impl_2',
    extra_label = ':dep_rule',
    extra_str = 'foo from 2',
)

# Define config setting
config_setting(
    name = "optimised",
    values = {"compilation_mode": "opt"}
)

# Declare the toolchain.
toolchain(
    name = 'toolchain_1',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    target_settings = [":optimised"],
    toolchain = ':toolchain_impl_1')
toolchain(
    name = 'toolchain_2',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    toolchain = ':toolchain_impl_2')
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _sample_rule_impl(ctx):
  return []

sample_rule = rule(
  implementation = _sample_rule_impl,
  attrs = {
    "dep": attr.label(cfg = 'exec'),
  },
)
EOF

  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')
load(':rule.bzl', 'sample_rule')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')

# Use the toolchain in exec configuration
sample_rule(
    name = 'sample',
    dep = ':use',
)
EOF

  # This should use toolchain_1 (because default host_compilation_mode = opt).
  bazel build \
    "//${pkg}/demo:sample" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 1"'

  # This should use toolchain_2.
  bazel build \
    --compilation_mode=opt --host_compilation_mode=dbg \
    "//${pkg}/demo:sample" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 2"'

  # This should use toolchain_2.
  bazel build \
    --host_compilation_mode=dbg \
    "//${pkg}/demo:sample" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain extra_str: "foo from 2"'
}

function test_default_constraint_values {
  local -r pkg="${FUNCNAME[0]}"
  # Add test constraints and platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting1', default_constraint_value = ':value_foo')
constraint_value(name = 'value_foo', constraint_setting = ':setting1')
constraint_value(name = 'value_bar', constraint_setting = ':setting1')

# Default constraint values don't block toolchain resolution.
constraint_setting(name = 'setting2', default_constraint_value = ':value_unused')
constraint_value(name = 'value_unused', constraint_setting = ':setting2')

platform(
    name = 'platform_default',
    constraint_values = [
      ':value_unused',
    ])
platform(
    name = 'platform_no_default',
    constraint_values = [
      ':value_bar',
      ':value_unused',
    ])
EOF

  # Add test toolchains using the constraints.
  write_test_toolchain "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchains.
test_toolchain(
    name = 'test_toolchain_impl_foo',
    extra_str = 'foo',
)

test_toolchain(
    name = 'test_toolchain_impl_bar',
    extra_str = 'bar',
)

# Declare the toolchains.
toolchain(
    name = 'test_toolchain_foo',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [],
    target_compatible_with = [
      '//${pkg}/platforms:value_foo',
    ],
    toolchain = ':test_toolchain_impl_foo',
)
toolchain(
    name = 'test_toolchain_bar',
    toolchain_type = '//${pkg}/toolchain:test_toolchain',
    exec_compatible_with = [],
    target_compatible_with = [
      '//${pkg}/platforms:value_bar',
    ],
    toolchain = ':test_toolchain_impl_bar',
)
EOF

  # Register the toolchains
  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:test_toolchain_foo', '//${pkg}:test_toolchain_bar')
EOF

  write_test_rule "${pkg}"
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchain.bzl', 'use_toolchain')

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

# Use the toolchain.
use_toolchain(
    name = 'use',
    message = 'this is the rule')
EOF

  # Test some builds and verify which was used.
  # This should use the default value.
  bazel build \
    --platforms="//${pkg}/platforms:platform_default" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'toolchain extra_str: "foo"'

  # This should use the explicit value.
  bazel build \
    --platforms="//${pkg}/platforms:platform_no_default" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'toolchain extra_str: "bar"'
}

function test_make_variables_custom_rule() {
  local -r pkg="${FUNCNAME[0]}"
  # Create a toolchain rule that also exposes make variables.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

toolchain_type(name = 'toolchain_var',
)
EOF
  cat > "${pkg}/toolchain/toolchain_var.bzl" <<EOF
def _impl(ctx):
  toolchain = platform_common.ToolchainInfo()
  value = ctx.attr.value
  templates = platform_common.TemplateVariableInfo({'VALUE': value})
  return [toolchain, templates]

toolchain_var = rule(
    implementation = _impl,
    attrs = {
        'value': attr.string(mandatory = True),
    }
)
EOF

  # Create a rule that consumes the toolchain.
  cat > "${pkg}/toolchain/rule_var.bzl" <<EOF
def _impl(ctx):
  toolchain = ctx.toolchains['//${pkg}/toolchain:toolchain_var']
  value = ctx.var['VALUE']
  print('Using toolchain: value "%s"' % value)
  return []

rule_var = rule(
    implementation = _impl,
    toolchains = ['//${pkg}/toolchain:toolchain_var'],
)
EOF

  # Create and register a toolchain
  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:toolchain_var_1')
EOF

  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_var.bzl', 'toolchain_var')

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

# Define the toolchain.
toolchain_var(
    name = 'toolchain_var_impl_1',
    value = 'foo',
)

# Declare the toolchain.
toolchain(
    name = 'toolchain_var_1',
    toolchain_type = '//${pkg}/toolchain:toolchain_var',
    exec_compatible_with = [],
    target_compatible_with = [],
    toolchain = ':toolchain_var_impl_1',
)
EOF

  # Instantiate the rule and verify the output.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_var.bzl', 'rule_var')

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

rule_var(name = 'demo')
EOF

  bazel build "//${pkg}/demo:demo" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain: value "foo"'
}

function test_local_config_platform() {
  if [ "${PRODUCT_NAME}" != "bazel" ]; then
    # Tests of external repositories only work under bazel.
    return 0
  fi
  bazel query @local_config_platform//... &> $TEST_log || fail "Build failed"
  expect_log '@local_config_platform//:host'
}

# Test cycles in registered toolchains, which can only happen when
# registered_toolchains is called for something that is not actually
# using the "toolchain" rule.
function test_registered_toolchain_cycle() {
  local -r pkg="${FUNCNAME[0]}"

  # Set up two sets of rules and toolchains, one depending on the other.
  mkdir -p "${pkg}"
  cat > "${pkg}/lower.bzl" <<EOF
def _lower_toolchain_impl(ctx):
  message = ctx.attr.message
  toolchain = platform_common.ToolchainInfo(
      message=message)
  return [toolchain]

lower_toolchain = rule(
    implementation = _lower_toolchain_impl,
    attrs = {
        'message': attr.string(),
    },
)

def _lower_library_impl(ctx):
  toolchain = ctx.toolchains['//${pkg}:lower']
  print('lower library: %s' % toolchain.message)
  return []

lower_library = rule(
    implementation = _lower_library_impl,
    attrs = {},
    toolchains = ['//${pkg}:lower'],
)
EOF

  cat >"${pkg}/upper.bzl" <<EOF
def _upper_toolchain_impl(ctx):
  tool_message = ctx.toolchains['//${pkg}:lower'].message
  message = ctx.attr.message
  toolchain = platform_common.ToolchainInfo(
      tool_message=tool_message,
      message=message)
  return [toolchain]

upper_toolchain = rule(
    implementation = _upper_toolchain_impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = ['//${pkg}:lower'],
)

def _upper_library_impl(ctx):
  toolchain = ctx.toolchains['//${pkg}:upper']
  print('upper library: %s (%s)' % (toolchain.message, toolchain.tool_message))
  return []

upper_library = rule(
    implementation = _upper_library_impl,
    attrs = {},
    toolchains = ['//${pkg}:upper'],
)
EOF

  # Define the actual targets using these.
  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}:lower.bzl', 'lower_toolchain', 'lower_library')
load('//${pkg}:upper.bzl', 'upper_toolchain', 'upper_library')

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

toolchain_type(name = 'lower')
toolchain_type(name = 'upper')

lower_library(
    name = 'lower_lib',
)

lower_toolchain(
    name = 'lower_toolchain',
    message = 'hi from lower',
)
toolchain(
    name = 'lower_toolchain_impl',
    toolchain_type = '//${pkg}:lower',
    toolchain = ':lower_toolchain',
)

upper_library(
    name = 'upper_lib',
)

upper_toolchain(
    name = 'upper_toolchain',
    message = 'hi from upper',
)
toolchain(
    name = 'upper_toolchain_impl',
    toolchain_type = '//${pkg}:upper',
    toolchain = ':upper_toolchain',
)
EOF

  # Finally, set up the misconfigured WORKSPACE file.
  cat >WORKSPACE <<EOF
register_toolchains(
    '//${pkg}:upper_toolchain', # Not a toolchain() target!
    '//${pkg}:lower_toolchain_impl',
    )
EOF

  # Execute the build and check the error message.
  bazel build "//${pkg}:upper_lib" &> $TEST_log && fail "Build succeeded unexpectedly"
  expect_not_log "java.lang.IllegalStateException"
  expect_log "Misconfigured toolchains: //${pkg}:upper_toolchain is declared as a toolchain but has inappropriate dependencies"
}


# Catch the error when a target platform requires a configuration which contains the same target platform.
# This can only happen when the target platform is not actually a platform.
function test_target_platform_cycle() {
  local -r pkg="${FUNCNAME[0]}"
  mkdir -p "${pkg}"
  cat > "${pkg}/hello.sh" <<EOF
  #!/bin/sh
  echo "Hello world"
EOF
  cat > "${pkg}/target.sh" <<EOF
  #!/bin/sh
  echo "Hello target"
EOF
  chmod +x "${pkg}/hello.sh"
  chmod +x "${pkg}/target.sh"
  cat > "${pkg}/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

sh_binary(
  name = "hello",
  srcs = ["hello.sh"],
)
sh_binary(
  name = "target",
  srcs = ["target.sh"],
)
EOF

  echo "START DEBUGGING"
  bazel build \
    --platforms="//${pkg}:hello" \
    "//${pkg}:target" &> $TEST_log && fail "Build succeeded unexpectedly"
  expect_log "Target //${pkg}:hello was referenced as a platform, but does not provide PlatformInfo"
}


function test_platform_duplicate_constraint_error() {
  local -r pkg="${FUNCNAME[0]}"
  # Write a platform with duplicate constraint values for the same setting.
  mkdir -p "${pkg}/platform"
  cat > "${pkg}/platform/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

constraint_setting(name = 'foo')
constraint_value(name = 'val1', constraint_setting = ':foo')
constraint_value(name = 'val2', constraint_setting = ':foo')
platform(
    name = 'test',
    constraint_values = [
        ':val1',
        ':val2',
    ],
)
EOF

  bazel build "//${pkg}/platform:test" &> $TEST_log && fail "Build failure expected"
  expect_log "Duplicate constraint values detected"
}

function test_toolchain_duplicate_constraint_error() {
  local -r pkg="${FUNCNAME[0]}"
  # Write a toolchain with duplicate constraint values for the same setting.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

constraint_setting(name = 'foo')
constraint_value(name = 'val1', constraint_setting = ':foo')
constraint_value(name = 'val2', constraint_setting = ':foo')
constraint_setting(name = 'bar')
constraint_value(name = 'val3', constraint_setting = ':bar')
constraint_value(name = 'val4', constraint_setting = ':bar')
toolchain_type(name = 'toolchain_type')
filegroup(name = 'toolchain')
toolchain(
    name = 'test',
    toolchain_type = ':toolchain_type',
    exec_compatible_with = [
        ':val1',
        ':val2',
    ],
    target_compatible_with = [
        ':val3',
        ':val4',
    ],
    toolchain = ':toolchain',
)
EOF

  bazel build "//${pkg}/toolchain:test" &> $TEST_log && fail "Build failure expected"
  expect_not_log "java.lang.IllegalArgumentException"
  expect_log "in exec_compatible_with attribute of toolchain rule //${pkg}/toolchain:test: Duplicate constraint values detected: constraint_setting //${pkg}/toolchain:foo has \[//${pkg}/toolchain:val1, //${pkg}/toolchain:val2\]"
  expect_log "in target_compatible_with attribute of toolchain rule //${pkg}/toolchain:test: Duplicate constraint values detected: constraint_setting //${pkg}/toolchain:bar has \[//${pkg}/toolchain:val3, //${pkg}/toolchain:val4\]"
}


function test_exec_transition() {
  local -r pkg="${FUNCNAME[0]}"
  # Add test platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)
EOF

  # Add a rule with default execution constraints.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _sample_rule_impl(ctx):
  return []

sample_rule = rule(
  implementation = _sample_rule_impl,
  attrs = {
    "dep": attr.label(cfg = 'exec'),
  },
)

def _display_platform_impl(ctx):
  print("%s target platform: %s" % (ctx.label, ctx.fragments.platform.platforms[0]))
  return []

display_platform = rule(
  implementation = _display_platform_impl,
  attrs = {},
  fragments = ['platform'],
)
EOF

  # Use the new rule.
  cat > "${pkg}/demo/BUILD" <<EOF
load(':rule.bzl', 'sample_rule', 'display_platform')

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

sample_rule(
  name = 'use',
  dep = ":dep",
  exec_compatible_with = [
    '//${pkg}/platforms:value2',
  ],
)

display_platform(name = 'dep')
EOF

  # Build the target, using debug messages to verify the correct platform was selected.
  bazel build \
    --extra_execution_platforms="//${pkg}/platforms:all" \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log "@//${pkg}/demo:dep target platform: @//${pkg}/platforms:platform2"
}

function test_config_setting_with_constraints {
  local -r pkg="${FUNCNAME[0]}"
  mkdir -p "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

constraint_setting(name = "setting1")
constraint_value(name = "value1", constraint_setting = ":setting1")
constraint_value(name = "value2", constraint_setting = ":setting1")
platform(name = "platform1",
  constraint_values = [":value1"],
)
platform(name = "platform2",
  constraint_values = [":value2"],
)

config_setting(name = "config1",
  constraint_values = [":value1"],
)
config_setting(name = "config2",
  constraint_values = [":value2"],
)

genrule(name = "demo",
  outs = ["demo.log"],
  cmd = select({
    ":config1": "echo 'config1 selected' > \$@",
    ":config2": "echo 'config2 selected' > \$@",
  }),
)
EOF

  bazel build \
    --platforms="//${pkg}:platform1" \
    "//${pkg}:demo" &> $TEST_log || fail "Build failed"
  cat "bazel-genfiles/${pkg}/demo.log" >> $TEST_log
  expect_log "config1 selected"

  bazel build \
    --platforms="//${pkg}:platform2" \
    "//${pkg}:demo" &> $TEST_log || fail "Build failed"
  cat "bazel-genfiles/${pkg}/demo.log" >> $TEST_log
  expect_log "config2 selected"
}

function test_config_setting_with_constraints_alias {
  local -r pkg="${FUNCNAME[0]}"
  mkdir -p "${pkg}"
  cat > "${pkg}/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

constraint_setting(name = "setting1")
constraint_value(name = "value1", constraint_setting = ":setting1")
constraint_value(name = "value2", constraint_setting = ":setting1")
platform(name = "platform1",
  constraint_values = [":value1"],
)
platform(name = "platform2",
  constraint_values = [":value2"],
)

alias(name = "alias1", actual = ":value1")
alias(name = "alias1a", actual = ":alias1")
alias(name = "alias2", actual = ":value2")
alias(name = "alias2a", actual = ":alias2")

config_setting(name = "config1",
  constraint_values = [":alias1a"],
)
config_setting(name = "config2",
  constraint_values = [":alias2a"],
)

genrule(name = "demo",
  outs = ["demo.log"],
  cmd = select({
    ":config1": "echo 'config1 selected' > \$@",
    ":config2": "echo 'config2 selected' > \$@",
  }),
)
EOF

  bazel build \
    --platforms="//${pkg}:platform1" \
    "//${pkg}:demo" &> $TEST_log || fail "Build failed"
  cat "bazel-genfiles/${pkg}/demo.log" >> $TEST_log
  expect_log "config1 selected"
  bazel build \
    --platforms="//${pkg}:platform2" \
    "//${pkg}:demo" &> $TEST_log || fail "Build failed"
  cat "bazel-genfiles/${pkg}/demo.log" >> $TEST_log
  expect_log "config2 selected"
}

function test_toolchain_modes {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}" foo_toolchain
  write_test_rule "${pkg}" test_rule foo_toolchain

  mkdir -p "${pkg}/project"
  cat > "${pkg}/project/flags.bzl" <<EOF
def _impl(ctx):
  pass

string_flag = rule(
    implementation = _impl,
    build_setting = config.string(flag = True),
)
EOF

  cat > "${pkg}/project/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_foo_toolchain.bzl', 'foo_toolchain')
load('//${pkg}/toolchain:rule_test_rule.bzl', 'test_rule')
load('//${pkg}/project:flags.bzl', 'string_flag')

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

string_flag(
  name = 'version',
  build_setting_default = 'production'
)

config_setting(
  name = 'production',
  flag_values = {
    ':version': 'production'
  }
)

config_setting(
  name = 'unstable',
  flag_values = {
    ':version': 'unstable'
  }
)

filegroup(name = 'dep')
foo_toolchain(
    name = 'production_toolchain',
    extra_label = ':dep',
    extra_str = 'production',
)

foo_toolchain(
    name = 'unstable_toolchain',
    extra_label = ':dep',
    extra_str = 'unstable',
)

toolchain(
    name = 'toolchain',
    toolchain_type = '//${pkg}/toolchain:foo_toolchain',
    toolchain = select({
      ':production': ':production_toolchain',
      ':unstable': ':unstable_toolchain',
    })
)

test_rule(
  name = 'test',
  message = 'hello',
)
EOF

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}/project:toolchain')
EOF

  bazel build \
    "//${pkg}/project:test" &> "${TEST_log}" || fail "Build failed"
  expect_log 'Using toolchain: rule message: "hello", toolchain extra_str: "production"'

  bazel build \
    "--//${pkg}/project:version=unstable" \
    "//${pkg}/project:test" &> "${TEST_log}" || fail "Build failed"
  expect_log 'Using toolchain: rule message: "hello", toolchain extra_str: "unstable"'
}

function test_add_exec_constraints_to_targets() {
  local -r pkg="${FUNCNAME[0]}"
  # Add test platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)
EOF

  # Add a rule with default execution constraints.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _sample_rule_impl(ctx):
  return []

sample_rule = rule(
  implementation = _sample_rule_impl,
  attrs = {
    "tool": attr.label(cfg = 'exec'),
  }
)

def _display_platform_impl(ctx):
  print("%s target platform: %s" % (ctx.label, ctx.fragments.platform.platforms[0]))
  return []

display_platform = rule(
  implementation = _display_platform_impl,
  attrs = {},
  fragments = ['platform'],
)
EOF

  # Use the new rule.
  cat > "${pkg}/demo/BUILD" <<EOF
load(':rule.bzl', 'sample_rule', 'display_platform')

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

sample_rule(
  name = 'sample',
  tool = ":tool",
)

display_platform(name = 'tool')
EOF

  bazel build \
    --extra_execution_platforms="//${pkg}/platforms:platform1,//${pkg}/platforms:platform2" \
    "//${pkg}/demo:sample" &> $TEST_log || fail "Build failed"
  expect_log "@//${pkg}/demo:tool target platform: @//${pkg}/platforms:platform1"

  bazel build \
      --extra_execution_platforms="//${pkg}/platforms:platform1,//${pkg}/platforms:platform2" \
      --experimental_add_exec_constraints_to_targets "//${pkg}/demo:sample=//${pkg}/platforms:value2" \
      "//${pkg}/demo:sample" &> $TEST_log || fail "Build failed"
  expect_log "@//${pkg}/demo:tool target platform: @//${pkg}/platforms:platform2"
}

function test_deps_includes_exec_group_toolchain() {
  local -r pkg="${FUNCNAME[0]}"
  write_register_toolchain "${pkg}"
  write_test_toolchain "${pkg}"

  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchain.bzl" <<EOF

def _impl(ctx):
  print(ctx.exec_groups)
  print(ctx.exec_groups['group'].toolchains)
  return []

use_toolchain = rule(
  implementation = _impl,
  exec_groups = {
    "group": exec_group(
      toolchains = ["//${pkg}/toolchain:test_toolchain"],
    ),
  },
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load("//${pkg}/toolchain:rule_use_toolchain.bzl", "use_toolchain")

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

use_toolchain(name = "use")
EOF

  bazel cquery "deps(//${pkg}/demo:use, 1)" &> $TEST_log || fail "Build failed"
  expect_log "<toolchain_context.resolved_labels: //${pkg}/toolchain:test_toolchain"
  expect_log "<ctx.exec_groups: group>"
  expect_log "//register/${pkg}:test_toolchain_impl_1"
  expect_log "//${pkg}/toolchain:test_toolchain"
}

function test_two_toolchain_types_resolve_to_same_label() {
  local -r pkg="${FUNCNAME[0]}"
  write_test_toolchain "${pkg}"

  cat > WORKSPACE <<EOF
register_toolchains('//${pkg}:toolchain_1')
register_toolchains('//${pkg}:toolchain_2')
EOF

  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

toolchain_type(
    name = 'test_toolchain_1',
)
toolchain_type(
    name = 'test_toolchain_2',
)
EOF

  cat > "${pkg}/BUILD" <<EOF
load('//${pkg}/toolchain:toolchain_test_toolchain.bzl', 'test_toolchain')

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

# Define the toolchain.
test_toolchain(
    name = 'toolchain_impl_1',
)

# Declare the toolchain.
toolchain(
    name = 'toolchain_1',
    toolchain_type = '//${pkg}/toolchain:test_toolchain_1',
    toolchain = ':toolchain_impl_1')
toolchain(
    name = 'toolchain_2',
    toolchain_type = '//${pkg}/toolchain:test_toolchain_2',
    toolchain = ':toolchain_impl_1')
EOF

  cat > "${pkg}/toolchain/rule_use_toolchains.bzl" <<EOF
def _impl(ctx):
  toolchain1 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_1']
  toolchain2 = ctx.toolchains['//${pkg}/toolchain:test_toolchain_2']
  message = ctx.attr.message
  print(
      'Using toolchain1: rule message: "%s", toolchain extra_str: "%s"' %
         (message, toolchain1.extra_str))
  print(
      'Using toolchain2: rule message: "%s", toolchain extra_str: "%s"' %
         (message, toolchain2.extra_str))
  return []

use_toolchains = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = ['//${pkg}/toolchain:test_toolchain_1', '//${pkg}/toolchain:test_toolchain_2'],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchains.bzl', 'use_toolchains')

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

# Use both toolchains.
use_toolchains(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  expect_log 'Using toolchain1: rule message: "this is the rule"'
  expect_log 'Using toolchain2: rule message: "this is the rule"'
}


function test_invalid_toolchain_type() {
  local -r pkg="${FUNCNAME[0]}"
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load(":rule.bzl", "sample_rule")

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

sample_rule(name = "demo")
EOF
  cat > "${pkg}/demo/rule.bzl" <<EOF
def _sample_impl(ctx):
    pass

sample_rule = rule(
    implementation = _sample_impl,
    toolchains = ["//${pkg}/demo:toolchain_type"],
)
EOF

  bazel build "//${pkg}/demo" &> $TEST_log && fail "Expected build to fail"
  expect_log "target 'toolchain_type' not declared in package '${pkg}/demo'"
  expect_not_log "does not provide ToolchainTypeInfo"
}

# Tests for the case where a toolchain requires a different toolchain type.
# Regression test for https://github.com/bazelbuild/bazel/issues/13243
function test_toolchain_requires_toolchain() {
  local -r pkg="${FUNCNAME[0]}"
  # Create an inner toolchain.
  mkdir -p "${pkg}/inner"
  cat > "${pkg}/inner/toolchain.bzl" <<EOF
InnerToolchain = provider(fields = ["msg"])

def _impl(ctx):
    inner = InnerToolchain(msg = "Inner toolchain %s" % ctx.label)
    return [
        platform_common.ToolchainInfo(inner = inner)
    ]

inner_toolchain = rule(
    implementation = _impl,
)
EOF
  cat > "${pkg}/inner/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

load(":toolchain.bzl", "inner_toolchain")
toolchain_type(name = "toolchain_type")

inner_toolchain(name = "impl")
toolchain(
    name = "toolchain",
    toolchain_type = ":toolchain_type",
    toolchain = ":impl",
)
EOF

  # Create an outer toolchain the uses the inner.
  mkdir -p "${pkg}/outer"
  cat > "${pkg}/outer/toolchain.bzl" <<EOF
OuterToolchain = provider(fields = ["msg"])

def _impl(ctx):
    toolchain_info = ctx.toolchains["//${pkg}/inner:toolchain_type"]
    inner = toolchain_info.inner
    outer = OuterToolchain(msg = "Outer toolchain %s using inner: %s" % (ctx.label, inner.msg))
    return [
        platform_common.ToolchainInfo(outer = outer)
    ]

outer_toolchain = rule(
    implementation = _impl,
    toolchains = ["//${pkg}/inner:toolchain_type"],
)
EOF
  cat > "${pkg}/outer/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

load(":toolchain.bzl", "outer_toolchain")
toolchain_type(name = "toolchain_type")

outer_toolchain(name = "impl")
toolchain(
    name = "toolchain",
    toolchain_type = ":toolchain_type",
    toolchain = ":impl",
)
EOF

  # Register all the toolchains.
  cat >WORKSPACE <<EOF
register_toolchains("//${pkg}/inner:all")
register_toolchains("//${pkg}/outer:all")
EOF

  # Write a rule that uses the outer toolchain.
  mkdir -p "${pkg}/rule"
  cat > "${pkg}/rule/rule.bzl" <<EOF
def _impl(ctx):
    toolchain_info = ctx.toolchains["//${pkg}/outer:toolchain_type"]
    outer = toolchain_info.outer
    print("Demo rule: outer toolchain says: %s" % outer.msg)
    return []

demo_rule = rule(
    implementation = _impl,
    toolchains = ["//${pkg}/outer:toolchain_type"],
)
EOF
  cat > "${pkg}/rule/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

exports_files(["rule.bzl"])
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/rule:rule.bzl', 'demo_rule')

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

demo_rule(name = "demo")
EOF

  bazel build "//${pkg}/demo:demo" &> $TEST_log || fail "Build failed"
  expect_log "Inner toolchain @//${pkg}/inner:impl"
}

# Test that toolchain type labels are correctly resolved relative to the
# enclosing file, regardless of the repository name.
# See http://b/183060658 for details.
function test_repository_relative_toolchain_type() {
  if [ "${PRODUCT_NAME}" != "bazel" ]; then
    # Tests of external repositories only work under bazel.
    return 0
  fi
  local -r pkg="${FUNCNAME[0]}"
  # Create a repository that defines a toolchain type and simple rule.
  # The toolchain type used in the repository is relative to the repository.
  mkdir -p "${pkg}/external/rules_foo"
  touch "${pkg}/external/rules_foo/WORKSPACE"
  mkdir -p "${pkg}/external/rules_foo/rule"
  touch "${pkg}/external/rules_foo/rule/BUILD"
  cat > "${pkg}/external/rules_foo/rule/rule.bzl" <<EOF
def _foo_impl(ctx):
    print(ctx.toolchains["//toolchain:foo_toolchain_type"])
    return []

foo_rule = rule(
    implementation = _foo_impl,
    toolchains = ["//toolchain:foo_toolchain_type"],
)
EOF
  mkdir -p "${pkg}/external/rules_foo/toolchain/"
  cat > "${pkg}/external/rules_foo/toolchain/BUILD" <<EOF
load(":toolchain.bzl", "foo_toolchain")

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

toolchain_type(
  name = "foo_toolchain_type",
)

foo_toolchain(
    name = "foo_toolchain",
)

toolchain(
    name = "foo_default_toolchain",
    toolchain = ":foo_toolchain",
    toolchain_type = ":foo_toolchain_type",
)
EOF
  cat > "${pkg}/external/rules_foo/toolchain/toolchain.bzl" <<EOF
_ATTRS = dict(
  foo_tool = attr.label(
      allow_files = True,
      default = "//foo_tools:foo_tool",
  ),
)

def _impl(ctx):
    return [platform_common.ToolchainInfo(
        **{name: getattr(ctx.attr, name) for name in _ATTRS.keys()}
    )]

foo_toolchain = rule(
    implementation = _impl,
    attrs = _ATTRS,
)
EOF
  mkdir -p "${pkg}/external"/rules_foo/foo_tools
  cat > "${pkg}/external/rules_foo/foo_tools/BUILD" <<EOF
package(default_visibility = ["//visibility:public"])

sh_binary(
  name = "foo_tool",
  srcs = ["foo_tool.sh"],
)
EOF
  cat > "${pkg}/external/rules_foo/foo_tools/foo_tool.sh" <<EOF
echo creating \$1
touch \$1
EOF
  chmod +x "${pkg}/external/rules_foo/foo_tools/foo_tool.sh"

  # Create a target that uses the rule.
  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load("@rules_foo//rule:rule.bzl", "foo_rule")

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

foo_rule(name = "demo")
EOF

  # Set up the WORKSPACE.
  cat > WORKSPACE <<EOF
local_repository(
  name = "rules_foo",
  path = "${pkg}/external/rules_foo",
)

register_toolchains(
  "@rules_foo//toolchain:foo_default_toolchain",
)
EOF

  # Test the build.
  bazel build \
    "//${pkg}/demo:demo" &> $TEST_log || fail "Build failed"
  expect_log "foo_tool = <target @rules_foo//foo_tools:foo_tool>"
}

function test_exec_platform_order_with_mandatory_toolchains {
  local -r pkg="${FUNCNAME[0]}"

  # Add two possible execution platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)
EOF
  # Register them in order.
  cat >> WORKSPACE <<EOF
register_execution_platforms("//${pkg}/platforms:platform1", "//${pkg}/platforms:platform2")
EOF

  # Create a toolchain that only works with platform2
  write_test_toolchain "${pkg}" test_toolchain
  write_register_toolchain "${pkg}" test_toolchain "['//${pkg}/platforms:value2']"

  # The rule must receive the toolchain.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchains.bzl" <<EOF
def _impl(ctx):
  toolchain = ctx.toolchains['//${pkg}/toolchain:test_toolchain']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain is none: %s' %
         (message, toolchain == None))
  return []

use_toolchains = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = [
        config_common.toolchain_type('//${pkg}/toolchain:test_toolchain', mandatory = True),
    ],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchains.bzl', 'use_toolchains')

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

# Use the toolchain.
use_toolchains(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  bazel build \
    --toolchain_resolution_debug=.* \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  # Verify that a toolchain was provided.
  expect_log 'Using toolchain: rule message: "this is the rule", toolchain is none: False'
  # Verify that the exec platform is platform2.
  expect_log "Selected execution platform //${pkg}/platforms:platform2"
}

function test_exec_platform_order_with_optional_toolchains {
  local -r pkg="${FUNCNAME[0]}"

  # Add two possible execution platforms.
  mkdir -p "${pkg}/platforms"
  cat > "${pkg}/platforms/BUILD" <<EOF
package(default_visibility = ['//visibility:public'])

constraint_setting(name = 'setting')
constraint_value(name = 'value1', constraint_setting = ':setting')
constraint_value(name = 'value2', constraint_setting = ':setting')

platform(
    name = 'platform1',
    constraint_values = [':value1'],
)
platform(
    name = 'platform2',
    constraint_values = [':value2'],
)
EOF
  # Register them in order.
  cat >> WORKSPACE <<EOF
register_execution_platforms("//${pkg}/platforms:platform1", "//${pkg}/platforms:platform2")
EOF

  # Create a toolchain that only works with platform2
  write_test_toolchain "${pkg}" test_toolchain
  write_register_toolchain "${pkg}" test_toolchain "['//${pkg}/platforms:value2']"

  # The rule can optionally use the toolchain.
  mkdir -p "${pkg}/toolchain"
  cat > "${pkg}/toolchain/rule_use_toolchains.bzl" <<EOF
def _impl(ctx):
  toolchain = ctx.toolchains['//${pkg}/toolchain:test_toolchain']
  message = ctx.attr.message
  print(
      'Using toolchain: rule message: "%s", toolchain is none: %s' %
         (message, toolchain == None))
  return []

use_toolchains = rule(
    implementation = _impl,
    attrs = {
        'message': attr.string(),
    },
    toolchains = [
        config_common.toolchain_type('//${pkg}/toolchain:test_toolchain', mandatory = False),
    ],
)
EOF

  mkdir -p "${pkg}/demo"
  cat > "${pkg}/demo/BUILD" <<EOF
load('//${pkg}/toolchain:rule_use_toolchains.bzl', 'use_toolchains')

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

# Use the toolchain.
use_toolchains(
    name = 'use',
    message = 'this is the rule')
EOF

  bazel build "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"
  bazel build \
    --toolchain_resolution_debug=.* \
    "//${pkg}/demo:use" &> $TEST_log || fail "Build failed"

  # Verify that the exec platform is platform2.
  expect_log "Selected execution platform //${pkg}/platforms:platform2"
}

# TODO(katre): Test using toolchain-provided make variables from a genrule.

run_suite "toolchain tests"
