#!/usr/bin/env bash
# Copyright 2018 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.

# Tests that --server_javabase and Java toolchain resolution work as expected
# for Bazel with the embedded JDK.

# Load the test setup defined in the parent directory
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CURRENT_DIR}/../integration_test_setup.sh" \
  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }

# should match the java_runtime in `_BASE_TOOLCHAIN_CONFIGURATION` in
# `@rules_java//toolchains/default_java_toolchain.bzl`
DEFAULT_JAVA_RUNTIME_VERSION="remotejdk21"

function test_server_javabase() {
  mkdir -p test_server_javabase/bin
  MAGIC="the cake is a lie"

  cat << EOF > test_server_javabase/bin/java
#!/usr/bin/env bash
echo "$MAGIC"
EOF
  chmod +x test_server_javabase/bin/java

  # Check that we're able to change the server_javabase to a user specified
  # version.
  bazel --batch --server_javabase=test_server_javabase version >& $TEST_log
  expect_log "$MAGIC"

  bazel --batch version >& $TEST_log
  expect_not_log "$MAGIC"

  # Check that we're using the embedded JDK by default as server_javabase.
  bazel --batch info >& $TEST_log
  expect_log "java-home: .*/embedded_tools/jdk"
}

# Javabuilder shall be executed using JDK defined in java_toolchain's java_runtime attribute.
# Java targets shall be executed using JDK matching --java_runtime_version version.
function test_java_runtime() {
  add_rules_java "MODULE.bazel"
  add_platforms "MODULE.bazel"
  touch BUILD
  cat << EOF > extensions.bzl
load("@rules_java//toolchains:local_java_repository.bzl", "local_java_repository")

local_java_repo_ext = module_extension(
    implementation =
    lambda ctx: local_java_repository(
        name = "host_javabase",
        java_home = "$PWD/foobar",
        version = "11",
    ),
)
EOF
  cat << EOF >> MODULE.bazel
local_java_repo_ext = use_extension("//:extensions.bzl", "local_java_repo_ext")
use_repo(local_java_repo_ext, "host_javabase")
register_toolchains("@host_javabase//:runtime_toolchain_definition")
register_toolchains("@host_javabase//:bootstrap_runtime_toolchain_definition")
EOF

  mkdir java
  cat << EOF > java/BUILD
load("@rules_java//java:java_library.bzl", "java_library")

java_library(
    name = "javalib",
    srcs = ["HelloWorld.java"],
)
EOF
  touch java/HelloWorld.java

  mkdir -p foobar/bin
  touch foobar/bin/java

  # We expect the given host_javabase does not appear in the command line of
  # java_library actions.
  bazel aquery --output=text --tool_java_runtime_version='host_javabase' //java:javalib >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"
  expect_not_log "exec external/host_javabase/bin/java"

  # If we don't specify anything, we expect the remote JDK to be used.
  bazel aquery --output=text //java:javalib >& $TEST_log
  expect_not_log "exec external/embedded_jdk/bin/java"
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"

  bazel aquery --output=text --java_runtime_version='host_javabase' //java:javalib >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"
  expect_not_log "exec external/host_javabase/bin/java"
}

# Javabuilder shall be executed using JDK defined in java_toolchain's java_runtime attribute.
# Rationale: Javabuilder uses JDK internals for compilation. This ensures
# compatibility between JDK and Javabuilder, and ability to compile desired source.
# Testing: Javabuilder in target configuration.
function test_toolchain_java_runtime_set_from_toolchain() {
  add_rules_java "MODULE.bazel"

  mkdir java
  cat << EOF > java/BUILD
load("@rules_java//java:java_library.bzl", "java_library")

java_library(
    name = "javalib",
    srcs = ["HelloWorld.java"],
)
EOF
  touch java/HelloWorld.java

  mkdir -p foobar/bin
  touch foobar/bin/java

  bazel aquery --output=text --java_language_version=8  //java:javalib >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"

  bazel aquery --output=text --java_language_version=11  //java:javalib >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"

  bazel aquery --output=text  --java_language_version=17 //java:javalib >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"
}

# Javabuilder shall be executed using JDK defined in java_toolchain's java_runtime attribute, not tool_java_runtime.
# Testing: Javabuilder in exec configuration.
function test_exec_toolchain_java_runtime_not_set_from_tool_java_runtime_version() {
  add_rules_java "MODULE.bazel"
  add_platforms "MODULE.bazel"
  touch BUILD
  cat << EOF > extensions.bzl
load("@rules_java//toolchains:local_java_repository.bzl", "local_java_repository")

local_java_repo_ext = module_extension(
    implementation =
    lambda ctx: local_java_repository(
        name = "host_javabase",
        java_home = "$PWD/foobar",
        version = "11",
    ),
)
EOF
  cat << EOF >> MODULE.bazel
local_java_repo_ext = use_extension("//:extensions.bzl", "local_java_repo_ext")
use_repo(local_java_repo_ext, "host_javabase")
register_toolchains("@host_javabase//:runtime_toolchain_definition")
register_toolchains("@host_javabase//:bootstrap_runtime_toolchain_definition")
EOF
  mkdir -p java
  cat >> java/rule.bzl <<EOF
def _sample_rule_impl(ctx):
  return []

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

  cat << EOF > java/BUILD
load(":rule.bzl", "sample_rule")
load("@rules_java//java:java_library.bzl", "java_library")

java_library(
    name = "javalib",
    srcs = ["HelloWorld.java"],
)
sample_rule(
    name = 'sample',
    dep = ':javalib',
)
EOF
  touch java/HelloWorld.java

  mkdir -p foobar/bin
  touch foobar/bin/java

  # We expect the given host_javabase does not appear in the command line of
  # java_library actions.
  bazel aquery --output=text --tool_java_runtime_version='host_javabase' 'deps(//java:sample,1)' >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"
  expect_not_log "exec external/host_javabase/bin/java"

  # If we don't specify anything, we expect the remote JDK to be used.
  # Note that this will change in the future but is the current state.
  bazel aquery --output=text 'deps(//java:sample,1)' >& $TEST_log
  expect_not_log "exec external/embedded_jdk/bin/java"
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"

  bazel aquery --output=text --tool_java_runtime_version='host_javabase' 'deps(//java:sample,1)' >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"
  expect_not_log "exec external/host_javabase/bin/java"

  bazel aquery --output=text --tool_java_language_version=17 --tool_java_runtime_version='host_javabase' 'deps(//java:sample,1)' >& $TEST_log
  expect_log "exec external/rules_java+.*+toolchains+${DEFAULT_JAVA_RUNTIME_VERSION}_.*/bin/java"
  expect_not_log "exec external/host_javabase/bin/java"
}



function test_javabase() {
  add_rules_java "MODULE.bazel"
  add_platforms "MODULE.bazel"
  touch BUILD
  cat << EOF > extensions.bzl
load("@rules_java//toolchains:local_java_repository.bzl", "local_java_repository")

local_java_repo_ext = module_extension(
    implementation =
    lambda ctx: local_java_repository(
        name = "javabase",
        java_home = "$PWD/zoo",
        version = "11",
    ),
)
EOF
  cat << EOF >> MODULE.bazel
local_java_repo_ext = use_extension("//:extensions.bzl", "local_java_repo_ext")
use_repo(local_java_repo_ext, "javabase")
register_toolchains("@javabase//:runtime_toolchain_definition")
register_toolchains("@javabase//:bootstrap_runtime_toolchain_definition")

java_toolchains = use_extension("@rules_java//java:extensions.bzl", "toolchains")
use_repo(java_toolchains, "local_jdk")
EOF

  mkdir -p zoo/bin
  touch zoo/bin/java

  cat << EOF > BUILD
load("@rules_java//toolchains:default_java_toolchain.bzl", "default_java_toolchain")
default_java_toolchain(
    name = "toolchain",
    # Implicitly use the host_javabase bootclasspath, since the target doesn't
    # exist in this test.
    bootclasspath = [],
    javabuilder = ["@bazel_tools//tools/jdk:vanillajavabuilder"],
    jvm_opts = [],
    visibility = ["//visibility:public"],
    java_runtime = "@local_jdk//:jdk",
)
EOF

  mkdir java
  cat << EOF > java/BUILD
load("@rules_java//java:java_binary.bzl", "java_binary")

java_binary(
    name = "javabin",
    srcs = ["HelloWorld.java"],
)
EOF
  cat << EOF > java/HelloWorld.java
public class HelloWorld {}
EOF

  # Check that the RHS javabase appears in the launcher.
  bazel build --extra_toolchains=//:all --java_runtime_version=javabase //java:javabin
  cat bazel-bin/java/javabin >& $TEST_log
  expect_log "JAVABIN=.*/zoo/bin/java"

  # Check that we use local_jdk when it's not specified.
  bazel build //java:javabin
  cat bazel-bin/java/javabin >& $TEST_log
  expect_log "JAVABIN=.*/rules_java+.*+toolchains+local_jdk/bin/java"
}

function write_javabase_files() {
  mkdir -p javabase_test
  cat << EOF > javabase_test/BUILD
load("@rules_java//java:java_binary.bzl", "java_binary")

java_binary(
    name = "a",
    srcs = ["A.java"],
    main_class = "A",
)
EOF

  cat << EOF > javabase_test/A.java
class A {
  public static void main(String[] args) {
    System.err.println("hello");
  }
}
EOF
}

function test_no_javabase() {
  # Only run this test when there's no locally installed JDK.
  which javac && return

  write_javabase_files

  bazel build //javabase_test:a

  ($(bazel-bin/javabase_test/a --print_javabin) -version || true) >& $TEST_log
  expect_log "bazel-bin/javabase_test/a.runfiles/local_jdk/bin/java: No such file or directory"
}

function test_no_javabase() {
  # Only run this test when there's no locally installed JDK.
  which javac && return

  write_javabase_files

  bazel --batch build //javabase_test:a

  ($(bazel-bin/javabase_test/a --print_javabin) -version || true) >& $TEST_log
  expect_log "bazel-bin/javabase_test/a.runfiles/local_jdk/bin/java: No such file or directory"
}

# Tests non-existent java_home path.
function test_no_java_home_path() {
  add_rules_java "MODULE.bazel"
  add_platforms "MODULE.bazel"
  touch BUILD
  cat << EOF > extensions.bzl
load("@rules_java//toolchains:local_java_repository.bzl", "local_java_repository")

local_java_repo_ext = module_extension(
    implementation =
    lambda ctx: local_java_repository(
        name = "host_javabase",
        java_home = "$PWD/idontexist",
        version = "11",
    ),
)
EOF
  cat << EOF >> MODULE.bazel
local_java_repo_ext = use_extension("//:extensions.bzl", "local_java_repo_ext")
use_repo(local_java_repo_ext, "host_javabase")
register_toolchains("@host_javabase//:runtime_toolchain_definition")
register_toolchains("@host_javabase//:bootstrap_runtime_toolchain_definition")
EOF

  bazel build @host_javabase//... >& $TEST_log && fail "Build with missing java_home should fail."
  expect_log "The path indicated by the \"java_home\" attribute .* does not exist."
}


function test_genrule() {
  add_rules_java "MODULE.bazel"
  add_platforms "MODULE.bazel"
  touch BUILD
  cat << EOF > extensions.bzl
load("@rules_java//toolchains:local_java_repository.bzl", "local_java_repository")

local_java_repo_ext = module_extension(
    implementation =
    lambda ctx: local_java_repository(
        name = "foo_javabase",
        java_home = "$PWD/foo",
        version = "11",
    ),
)
EOF
  cat << EOF >> MODULE.bazel
local_java_repo_ext = use_extension("//:extensions.bzl", "local_java_repo_ext")
use_repo(local_java_repo_ext, "foo_javabase")
register_toolchains("@foo_javabase//:runtime_toolchain_definition")
register_toolchains("@foo_javabase//:bootstrap_runtime_toolchain_definition")
EOF

  mkdir -p foo/bin bar/bin
  cat << EOF > BUILD
load("@rules_java//java/toolchains:java_runtime.bzl", "java_runtime")

java_runtime(
    name = "bar_runtime",
    visibility = ["//visibility:public"],
    srcs = ["bar/bin/java"],
)

genrule(
    name = "without_java",
    srcs = ["in"],
    outs = ["out_without"],
    cmd = "cat \$(SRCS) > \$(OUTS)",
)

genrule(
    name = "with_java",
    srcs = ["in"],
    outs = ["out_with"],
    cmd = "echo \$(JAVA) > \$(OUTS)",
    toolchains = [":bar_runtime"],
)
EOF

  # Use --max_config_changes_to_show=0, as changed option names may otherwise
  # erroneously match the expected regexes.

  # Test the genrule with no java dependencies.
  bazel cquery --max_config_changes_to_show=0 --implicit_deps 'deps(//:without_java)' >& $TEST_log
  expect_not_log ":foo"
  expect_not_log ":bar"
  expect_not_log "embedded_jdk"
  expect_not_log "remotejdk_"
  expect_not_log "remotejdk11_"

  # Test the genrule that specifically depends on :bar_runtime.
  bazel cquery --max_config_changes_to_show=0 --implicit_deps 'deps(//:with_java)' >& $TEST_log
  expect_not_log ":foo"
  expect_log ":bar"
  expect_not_log "embedded_jdk"
  expect_not_log "remotejdk_"
  expect_not_log "remotejdk11_"

  # Setting the javabase should not change the use of :bar_runtime from the
  # roolchains attribute.
  bazel cquery --max_config_changes_to_show=0 --implicit_deps 'deps(//:with_java)' \
     --tool_java_runtime_version=foo_javabase >& $TEST_log
  expect_not_log ":foo"
  expect_log ":bar"
  expect_not_log "embedded_jdk"
  expect_not_log "remotejdk_"
  expect_not_log "remotejdk11_"
}

run_suite "Tests of specifying custom server_javabase/host_javabase and javabase."
