#!/bin/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.

# 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; }

test_result_recorded() {
  mkdir result_recorded && cd result_recorded
  rm -rf fetchrepo
  mkdir fetchrepo
  cd fetchrepo
  cat > rule.bzl <<'EOF'
def _rule_impl(ctx):
  ctx.symlink(ctx.attr.build_file, "BUILD")
  return {"build_file": ctx.attr.build_file, "extra_arg": "foobar"}

trivial_rule = repository_rule(
  implementation = _rule_impl,
  attrs = { "build_file" : attr.label() },
)

EOF
  cat > ext.BUILD <<'EOF'
genrule(
  name = "foo",
  outs = ["foo.txt"],
  cmd = "echo bar > $@",
)
EOF
  touch BUILD
  cat  > WORKSPACE <<'EOF'
load("//:rule.bzl", "trivial_rule")
trivial_rule(
  name = "ext",
  build_file = "//:ext.BUILD",
)
EOF

  bazel clean --expunge
  bazel build --experimental_repository_resolved_file=../repo.bzl --record_rule_instantiation_callstack @ext//... \
        > "${TEST_log}" 2>&1 || fail "Expected success"
  inplace-sed -e "s?$(pwd)?PWD?g" "$TEST_log"
  bazel shutdown

  # We expect the additional argument to be reported to the user...
  expect_log 'extra_arg.*foobar'
  # ...as well as the location of the rule instantiation and definition.
  expect_log 'Repository ext instantiated at:'
  expect_log '  PWD/WORKSPACE:2:'
  expect_log 'Repository rule trivial_rule defined at:'
  expect_log '  PWD/rule.bzl:5:'

  # Verify that bazel can read the generated repo.bzl file and that it contains
  # the expected information
  cd ..
  echo; cat repo.bzl; echo; echo
  mkdir analysisrepo
  mv repo.bzl analysisrepo
  cd analysisrepo
  touch WORKSPACE
  cat > BUILD <<'EOF'
load("//:repo.bzl", "resolved")

[ genrule(
    name = "out",
    outs = ["out.txt"],
    cmd = "echo %s > $@" % entry["repositories"][0]["attributes"]["extra_arg"],
  ) for entry in resolved if entry["original_rule_class"] == "//:rule.bzl%trivial_rule"
]

[ genrule(
    name = "origcount",
    outs = ["origcount.txt"],
    cmd = "echo %s > $@" % len(entry["original_attributes"])
  ) for entry in resolved if entry["original_rule_class"] == "//:rule.bzl%trivial_rule"
]
EOF
  bazel build :out :origcount || fail "Expected success"
  grep "foobar" `bazel info bazel-genfiles`/out.txt \
      || fail "Did not find the expected value"
  [ $(cat `bazel info bazel-genfiles`/origcount.txt) -eq 2 ] \
      || fail "Not the correct number of original attributes"
}

test_git_return_value() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  export GIT_CONFIG_NOSYSTEM=YES

  mkdir extgit
  (cd extgit && git init \
       && git config user.email 'me@example.com' \
       && git config user.name 'E X Ample' )
  echo Hello World > extgit/hello.txt
  (cd extgit
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'initial commit'
   git tag mytag)

  # Check out the external git repository at the given tag, and record
  # the return value of the git rule.
  mkdir tagcheckout
  cd tagcheckout
  cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
new_git_repository(
  name="ext",
  remote="file://${EXTREPODIR}/extgit/.git",
  tag="mytag",
  build_file_content="exports_files([\"hello.txt\"])",
)
EOF


  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel shutdown

  cd ..
  echo; cat repo.bzl; echo

  # Now add an additional commit to the upstream repository and
  # force update the tag
  echo CHANGED > extgit/hello.txt
  (cd extgit
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'change hello.txt'
   git tag -f mytag)

  # Verify that the recorded resolved information is what we expect. In
  # particular, verify that we don't get the new upstream commit.
  mkdir analysisrepo
  cd analysisrepo
  cp ../repo.bzl .
  cat > workspace.bzl <<'EOF'
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
load("//:repo.bzl", "resolved")

def repo():
    for entry in resolved:
        if entry["original_attributes"]["name"] == "ext":
            new_git_repository(**(entry["repositories"][0]["attributes"]))
EOF
  cat > WORKSPACE <<'EOF'
load("//:workspace.bzl", "repo")
repo()
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "out",
  outs = ["out.txt"],
  srcs = ["@ext//:hello.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel build //:out
  grep "Hello World" `bazel info bazel-genfiles`/out.txt \
       || fail "ext not taken at the right commit"
  grep "CHANGED" `bazel info bazel-genfiles`/out.txt  \
       && fail "not taking the frozen commit" || :
}

test_git_follow_branch() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  export GIT_CONFIG_NOSYSTEM=YES

  mkdir extgit
  (cd extgit && git init \
       && git config user.email 'me@example.com' \
       && git config user.name 'E X Ample' )
  echo Hello World > extgit/hello.txt
  (cd extgit
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'initial commit')
  # Check out the external git repository at the given branch, and record
  # the return value of the git rule.
  mkdir branchcheckout
  cd branchcheckout
  cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
new_git_repository(
  name="ext",
  remote="file://${EXTREPODIR}/extgit/.git",
  branch="master",
  build_file_content="exports_files([\"hello.txt\"])",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "out",
  outs = ["out.txt"],
  srcs = ["@ext//:hello.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel build :out
  grep "CHANGED" `bazel info bazel-genfiles`/out.txt  \
       && fail "Unexpected content in out.txt" || :
  cd ..
  echo; cat repo.bzl; echo

  # Now add an additional commit to the upstream repository
  echo CHANGED > extgit/hello.txt
  (cd extgit
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'change hello.txt')


  # First verify that `bazel sync` sees the new commit (we don't record it).
  cd branchcheckout
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir
  bazel build :out
  grep "CHANGED" `bazel info bazel-genfiles`/out.txt  \
       || fail "sync did not update the external repository"
  bazel shutdown
  cd ..
  echo

  # Verify that the recorded resolved information is what we expect. In
  # particular, verify that we don't get the new upstream commit.
  mkdir analysisrepo
  cd analysisrepo
  cp ../repo.bzl .
  cat > workspace.bzl <<'EOF'
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
load("//:repo.bzl", "resolved")

def repo():
    for entry in resolved:
        if entry["original_attributes"]["name"] == "ext":
            new_git_repository(**(entry["repositories"][0]["attributes"]))
EOF
  cat > WORKSPACE <<'EOF'
load("//:workspace.bzl", "repo")
repo()
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "out",
  outs = ["out.txt"],
  srcs = ["@ext//:hello.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel build //:out
  grep "Hello World" `bazel info bazel-genfiles`/out.txt \
       || fail "ext not taken at the right commit"
  grep "CHANGED" `bazel info bazel-genfiles`/out.txt  \
       && fail "not taking the frozen commit" || :
}

test_sync_follows_git_branch() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  export GIT_CONFIG_NOSYSTEM=YES

  rm -f gitdir
  mkdir gitdir
  (cd gitdir && git init \
       && git config user.email 'me@example.com' \
       && git config user.name 'E X Ample' )
  echo Hello World > gitdir/hello.txt
  (cd gitdir
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'initial commit')
  echo Hello Stable World > gitdir/hello.txt
  (cd gitdir
   git checkout -b stable
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'stable commit')

  # Follow the stable branch of the git repository
  mkdir followbranch
  cat > followbranch/WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
new_git_repository(
  name="ext",
  remote="file://${EXTREPODIR}/gitdir/.git",
  branch="stable",
  build_file_content="exports_files([\"hello.txt\"])",
)
EOF
  cat > followbranch/BUILD <<'EOF'
genrule(
  name = "out",
  outs = ["out.txt"],
  srcs = ["@ext//:hello.txt"],
  cmd = "cp $< $@",
)
EOF
  (cd followbranch && bazel build :out \
       && cat `bazel info bazel-genfiles`/out.txt > "${TEST_log}")
  expect_log 'Hello Stable World'

  # New upstream commits on the branch followed
  echo CHANGED > gitdir/hello.txt
  (cd gitdir
   git checkout stable
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'stable commit')

  # Verify that sync followed by build gets the correct version
  (cd followbranch && bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir && bazel build :out \
       && cat `bazel info bazel-genfiles`/out.txt > "${TEST_log}")
  expect_log 'CHANGED'
  expect_not_log 'Hello Stable World'
}

test_http_return_value() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir -p a
  touch a/WORKSPACE
  touch a/BUILD
  touch a/f.txt

  zip a.zip a/*
  expected_sha256="$(sha256sum "${EXTREPODIR}/a.zip" | head -c 64)"
  rm -rf a

  # http_archive rule doesn't specify the sha256 attribute
  mkdir -p main
  cat > main/WORKSPACE <<EOF
workspace(name = "main")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="a",
  strip_prefix="a",
  urls=["file://${EXTREPODIR}/a.zip"],
)
EOF
  touch main/BUILD

  cd main
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
      --experimental_repository_resolved_file=../repo.bzl

  grep ${expected_sha256} ../repo.bzl || fail "didn't return commit"
}

test_sync_calls_all() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir sync_calls_all && cd sync_calls_all
  rm -rf fetchrepo
  mkdir fetchrepo
  rm -f repo.bzl
  cd fetchrepo
  cat > rule.bzl <<'EOF'
def _rule_impl(ctx):
  ctx.file("foo.bzl", """
it = "foo"
other = "bar"
""")
  ctx.file("BUILD", "")
  return {"comment" : ctx.attr.comment }

trivial_rule = repository_rule(
  implementation = _rule_impl,
  attrs = { "comment" : attr.string() },
)
EOF
  touch BUILD
  cat  > WORKSPACE <<'EOF'
load("//:rule.bzl", "trivial_rule")
trivial_rule(name = "a", comment = "bootstrap")
load("@a//:foo.bzl", "it")
trivial_rule(name = "b", comment = it)
trivial_rule(name = "c", comment = it)
load("@c//:foo.bzl", "other")
trivial_rule(name = "d", comment = other)
EOF

  bazel clean --expunge
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel shutdown

  cd ..
  echo; cat repo.bzl; echo
  touch WORKSPACE
  cat > BUILD <<'EOF'
load("//:repo.bzl", "resolved")

names = [entry["original_attributes"]["name"]
         for entry in resolved
         if "native" not in entry]

[
  genrule(
   name = name,
   outs = [ "%s.txt" % (name,) ],
   cmd = "echo %s > $@" % (name,),
  ) for name in names
]
EOF
  bazel build :a :b :c :d || fail "Expected all 4 repositories to be present"
}

test_sync_call_invalidates() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir sync_call_invalidates && cd sync_call_invalidates
  rm -rf fetchrepo
  mkdir fetchrepo
  rm -f repo.bzl
  touch BUILD
  cat > rule.bzl <<'EOF'
def _rule_impl(ctx):
  ctx.file("BUILD", """
genrule(
  name = "it",
  outs = ["it.txt"],
  cmd = "echo hello world > $@",
)
""")
  ctx.file("WORKSPACE", "")

trivial_rule = repository_rule(
  implementation = _rule_impl,
  attrs = {},
)
EOF
  cat > WORKSPACE <<'EOF'
load("//:rule.bzl", "trivial_rule")

trivial_rule(name = "a")
trivial_rule(name = "b")
EOF

  bazel build @a//... @b//...
  echo; echo sync run; echo
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel shutdown

  cd ..
  echo; cat repo.bzl; echo
  touch WORKSPACE
  cat > BUILD <<'EOF'
load("//:repo.bzl", "resolved")

names = [entry["original_attributes"]["name"]
         for entry in resolved
         if "native" not in entry]
[
  genrule(
   name = name,
   outs = [ "%s.txt" % (name,) ],
   cmd = "echo %s > $@" % (name,),
  ) for name in names
]
EOF
  bazel build :a :b || fail "Expected both repositories to be present"
}

test_sync_load_errors_reported() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  rm -rf fetchrepo
  mkdir fetchrepo
  cd fetchrepo
  cat > WORKSPACE <<'EOF'
load("//does/not:exist.bzl", "randomfunction")

radomfunction(name="foo")
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir > "${TEST_log}" 2>&1 && fail "Expected failure" || :
  expect_log '//does/not:exist.bzl'
}

test_sync_reporting() {
  # Verify that debug and error messages in starlark functions are reported.
  # Also verify that the fact that the repository is fetched is reported as well.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  rm -rf fetchrepo
  mkdir fetchrepo
  cd fetchrepo
  cat > rule.bzl <<'EOF'
def _broken_rule_impl(ctx):
  print("DEBUG-message")
  fail("Failure-message")

broken_rule = repository_rule(
  implementation = _broken_rule_impl,
  attrs = {},
)
EOF
  touch BUILD
  cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'
load("//:rule.bzl", "broken_rule")

broken_rule(name = "broken")
EOF
  bazel sync --curses=yes --experimental_ui_actions_shown=100 --distdir=${EXTREPODIR}/test_WORKSPACE/distdir > "${TEST_log}" 2>&1 && fail "expected failure" || :
  expect_log 'Fetching @broken'
  expect_log "DEBUG-message"
  expect_log "Failure-message"
}

test_indirect_call() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  rm -rf fetchrepo
  mkdir fetchrepo
  cd fetchrepo
  touch BUILD
  cat > rule.bzl <<'EOF'
def _trivial_rule_impl(ctx):
  ctx.file("BUILD","genrule(name='hello', outs=['hello.txt'], cmd=' echo hello world > $@')")

trivial_rule = repository_rule(
  implementation = _trivial_rule_impl,
  attrs = {},
)
EOF
  cat > indirect.bzl <<'EOF'
def call(fn_name, **args):
  fn_name(**args)
EOF
  cat > WORKSPACE <<'EOF'
load("//:rule.bzl", "trivial_rule")
load("//:indirect.bzl", "call")

call(trivial_rule, name="foo")
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel shutdown

  cd ..
  echo; cat repo.bzl; echo
  touch WORKSPACE
  cat > BUILD <<'EOF'
load("//:repo.bzl", "resolved")

ruleclass = "".join([entry["original_rule_class"] for entry in resolved if entry["original_attributes"]["name"]=="foo"])

genrule(
  name = "ruleclass",
  outs = ["ruleclass.txt"],
  cmd = "echo %s > $@" % (ruleclass,)
)
EOF
  bazel build //:ruleclass
  cat `bazel info bazel-genfiles`/ruleclass.txt > ${TEST_log}
  expect_log '//:rule.bzl%trivial_rule'
  expect_not_log 'fn_name'
}

test_resolved_file_reading() {
  # Verify that the option to read a resolved file instead of the WORKSPACE
  # file works as expected.
  EXTREPODIR=`pwd`
  export GIT_CONFIG_NOSYSTEM=YES
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir extgit
  (cd extgit && git init \
       && git config user.email 'me@example.com' \
       && git config user.name 'E X Ample' )
  echo Hello World > extgit/hello.txt
  (cd extgit
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'initial commit'
   git tag mytag)

  mkdir main
  cd main
  cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
new_git_repository(
  name="ext",
  remote="file://${EXTREPODIR}/extgit/.git",
  branch="master",
  build_file_content="exports_files([\"hello.txt\"])",
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "out",
  outs = ["out.txt"],
  srcs = ["@ext//:hello.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=resolved.bzl
  echo; cat resolved.bzl; echo

  bazel clean --expunge
  echo 'Do not use any more' > WORKSPACE
  bazel build \
        --experimental_resolved_file_instead_of_workspace=`pwd`/resolved.bzl \
        :out || fail "Expected success with resolved file replacing WORKSPACE"
  rm WORKSPACE && touch WORKSPACE # bazel info needs a valid WORKSPACE
  grep 'Hello World' `bazel info bazel-genfiles`/out.txt \
      || fail "Did not find the expected output"
}

test_label_resolved_value() {
  # Verify that label arguments in a repository rule end up in the resolved
  # file in a parsable form.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar
  mkdir ext
  echo Hello World > ext/file.txt
  zip ext.zip ext/*

  mkdir main
  cd main
  cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${EXTREPODIR}/ext.zip"],
  build_file="@//:exit.BUILD",
)
EOF
  echo 'exports_files(["file.txt"])' > exit.BUILD
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  outs = ["local.txt"],
  srcs = ["@ext//:file.txt"],
  cmd = "cp $< $@",
)
EOF

  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=resolved.bzl
  rm WORKSPACE
  touch WORKSPACE
  echo; cat resolved.bzl; echo

  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl \
        //:local || fail "Expected success"
  grep World `bazel info bazel-genfiles`/local.txt \
      || fail "target not built correctly"
}

test_resolved_file_not_remembered() {
  # Verify that the --experimental_resolved_file_instead_of_workspace option
  # does not leak into a subsequent sync
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  export GIT_CONFIG_NOSYSTEM=YES

  rm -f gitdir
  mkdir gitdir
  (cd gitdir && git init \
       && git config user.email 'me@example.com' \
       && git config user.name 'E X Ample' )
  echo Hello Stable World > gitdir/hello.txt
  (cd gitdir
   git checkout -b stable
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'stable commit')

  # The project follows the stable branch of the git repository
  mkdir followbranch
  cat > followbranch/WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
new_git_repository(
  name="ext",
  remote="file://${EXTREPODIR}/gitdir/.git",
  branch="stable",
  build_file_content="exports_files([\"hello.txt\"])",
)
EOF
  cat > followbranch/BUILD <<'EOF'
genrule(
  name = "out",
  outs = ["out.txt"],
  srcs = ["@ext//:hello.txt"],
  cmd = "cp $< $@",
)
EOF
  (cd followbranch \
    && bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=resolved.bzl)
  # New upstream commits on the branch followed
  echo CHANGED > gitdir/hello.txt
  (cd gitdir
   git checkout stable
   git add .
   git commit --author="A U Thor <author@example.com>" -m 'stable commit')

  cd followbranch
  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl :out
  cat `bazel info bazel-genfiles`/out.txt > "${TEST_log}"
  expect_log 'Hello Stable World'
  expect_not_log 'CHANGED'
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=resolved.bzl
  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl :out
  cat `bazel info bazel-genfiles`/out.txt > "${TEST_log}"
  expect_log 'CHANGED'
  expect_not_log 'Hello Stable World'
}

create_sample_repository() {
  # Create, in the current directory, a repository that creates an external
  # repository `foo` containing
  # - file with fixed data, generated by ctx.file,
  # - a BUILD file linked from the main repository
  # - a symlink to ., and
  # - danling absolute and reproducible symlink.
  touch BUILD
  cat > rule.bzl <<'EOF'
def _trivial_rule_impl(ctx):
  ctx.symlink(ctx.attr.build_file, "BUILD")
  ctx.file("data.txt", "some data")
  ctx.execute(["ln", "-s", ".", "self_link"])
  ctx.execute(["ln", "-s", "/does/not/exist", "dangling"])

trivial_rule = repository_rule(
  implementation = _trivial_rule_impl,
  attrs = { "build_file" : attr.label() },
)
EOF
  echo '# fixed contents' > BUILD.remote
  cat > WORKSPACE <<'EOF'
load("//:rule.bzl", "trivial_rule")

trivial_rule(name="foo", build_file="@//:BUILD.remote")
EOF
}

test_hash_included_and_reproducible() {
  # Verify that a hash of the output directory is included, that
  # the hash is invariant under
  # - change of the working directory, and
  # - and current time.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  rm -rf fetchrepoA
  mkdir fetchrepoA
  cd fetchrepoA
  create_sample_repository
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel shutdown

  cd ..
  echo; cat repo.bzl; echo
  touch WORKSPACE
  cat > BUILD <<'EOF'
load("//:repo.bzl", "resolved")
hashes = [entry["repositories"][0]["output_tree_hash"]
         for entry in resolved if entry["original_attributes"]["name"]=="foo"]

[genrule(
  name="hash",
  outs=["hash.txt"],
  cmd="echo '%s' > $@" % (hash,),
) for hash in hashes]
EOF
  bazel build //:hash
  cp `bazel info bazel-genfiles`/hash.txt hashA.txt
  cat hashA.txt > "${TEST_log}"
  [ `cat hashA.txt | wc -c` -gt 2 ] \
      || fail "A hash of reasonable length expected"
  bazel clean --expunge
  rm repo.bzl


  rm -rf fetchrepoB
  mkdir fetchrepoB
  cd fetchrepoB
  create_sample_repository
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=../repo.bzl
  bazel shutdown

  cd ..
  echo; cat repo.bzl; echo
  bazel build //:hash
  cp `bazel info bazel-genfiles`/hash.txt hashB.txt
  cat hashB.txt > "${TEST_log}"
  diff hashA.txt hashB.txt || fail "Expected hash to be reproducible"
}

test_non_reproducibility_detected() {
    EXTREPODIR=`pwd`
    tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar
    # Verify that a non-reproducible rule is detected by hash verification
    mkdir repo
    cd repo
    touch BUILD
    cat > rule.bzl <<'EOF'
def _time_rule_impl(ctx):
  ctx.execute(["bash", "-c", "date +%s > timestamp"])

time_rule = repository_rule(
  implementation = _time_rule_impl,
  attrs = {},
)
EOF
  cat > WORKSPACE <<'EOF'
load("//:rule.bzl", "time_rule")

time_rule(name="timestamprepo")
EOF

    bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_resolved_file=resolved.bzl
    cat resolved.bzl > /dev/null || fail "resolved.bzl should exist"
    bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir --experimental_repository_hash_file=`pwd`/resolved.bzl \
          --experimental_verify_repository_rules='//:rule.bzl%time_rule' \
          > "${TEST_log}" 2>&1 && fail "expected failure" || :
    expect_log "timestamprepo.*hash"
}

test_chain_resolved() {
  # Verify that a cahin of dependencies in external repositories is reflected
  # in the resolved file in such a way, that the resolved file can be used.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir rulerepo
  cat > rulerepo/rule.bzl <<'EOF'
def _rule_impl(ctx):
  ctx.file("data.txt", "Hello World")
  ctx.file("BUILD", "exports_files(['data.txt'])")

trivial_rule = repository_rule(
  implementation = _rule_impl,
  attrs = {},
)
EOF
  touch rulerepo/BUILD
  zip rule.zip rulerepo/*
  rm -rf rulerepo

  cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="rulerepo",
  strip_prefix="rulerepo",
  urls=["file://${EXTREPODIR}/rule.zip"],
)
load("@rulerepo//:rule.bzl", "trivial_rule")
trivial_rule(name="a")
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs =  ["@a//:data.txt"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
        --experimental_repository_resolved_file=resolved.bzl
  bazel clean --expunge
  echo; cat resolved.bzl; echo

  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl \
        //:local || fail "Expected success"
}

test_usage_order_respected() {
   # Verify that if one rules uses a file from another (without any load
   # statement between), then still the resolved file is such that it can
   # be used as a workspace replacement.
   EXTREPODIR=`pwd`
   tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

   mkdir datarepo
   echo 'Pure data' > datarepo/data.txt
   zip datarepo.zip datarepo/*
   rm -rf datarepo

   mkdir metadatarepo
   echo 'exports_files(["data.txt"])' > metadatarepo/datarepo.BUILD
   touch metadatarepo/BUILD
   zip metadatarepo.zip metadatarepo/*
   rm -rf metadatarepo

   mkdir main
   cd main
   cat > WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="datarepo",
  strip_prefix="datarepo",
  urls=["file://${EXTREPODIR}/datarepo.zip"],
  build_file="@metadatarepo//:datarepo.BUILD",
)
http_archive(
  name="metadatarepo",
  strip_prefix="metadatarepo",
  urls=["file://${EXTREPODIR}/metadatarepo.zip"],
)
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "local",
  srcs =  ["@datarepo//:data.txt"],
  outs = ["local.txt"],
  cmd = "cp $< $@",
)
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
     --experimental_repository_resolved_file=resolved.bzl
  bazel clean --expunge
  echo; cat resolved.bzl; echo


  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl \
        //:local || fail "Expected success"
}

test_order_reproducible() {
  # Verify that the order of repositories in the resolved file is reproducible
  # and does not depend on the parameters or timing of the actual rules.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir main
  cd main

  cat > rule.bzl <<'EOF'
def _impl(ctx):
  ctx.execute(["/bin/sh", "-c", "sleep %s" % (ctx.attr.sleep,)])
  ctx.file("data", "some test data")
  ctx.file("BUILD", "exports_files(['data'])")

sleep_rule = repository_rule(
  implementation = _impl,
  attrs = {"sleep": attr.int()},
)
EOF
  cat > BUILD <<'EOF'
load("//:repo.bzl", "resolved")

genrule(
  name = "order",
  outs = ["order.txt"],
  cmd = ("echo '%s' > $@" %
    ([entry["original_attributes"]["name"] for entry in resolved],)),
)
EOF
  cat > WORKSPACE <<'EOF'
load("//:rule.bzl", "sleep_rule")

sleep_rule(name="a", sleep=1)
sleep_rule(name="c", sleep=3)
sleep_rule(name="b", sleep=5)
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
        --experimental_repository_resolved_file=repo.bzl
  bazel build //:order
  cp `bazel info bazel-genfiles`/order.txt order-first.txt
  bazel clean --expunge

  cat > WORKSPACE <<'EOF'
load("//:rule.bzl", "sleep_rule")

sleep_rule(name="a", sleep=5)
sleep_rule(name="c", sleep=3)
sleep_rule(name="b", sleep=1)
EOF
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
        --experimental_repository_resolved_file=repo.bzl
  bazel build //:order
  cp `bazel info bazel-genfiles`/order.txt order-second.txt

  echo; cat order-first.txt; echo; cat order-second.txt; echo

  diff order-first.txt order-second.txt \
      || fail "expected order to be reproducible"
}

test_non_starlarkrepo() {
  # Verify that entries in the WORKSPACE that are not starlark repositoires
  # are correctly reported in the resolved file.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir local
  touch local/WORKSPACE
  echo Hello World > local/data.txt
  echo 'exports_files(["data.txt"])' > local/BUILD

  mkdir newlocal
  echo Pure data > newlocal/data.txt

  mkdir main
  cd main
  mkdir target_to_be_bound
  echo More data > target_to_be_bound/data.txt
  echo 'exports_files(["data.txt"])' > target_to_be_bound/BUILD
  cat > WORKSPACE <<'EOF'
local_repository(name="thisislocal", path="../local")
new_local_repository(name="newlocal", path="../newlocal",
                     build_file_content='exports_files(["data.txt"])')
bind(name="bound", actual="//target_to_be_bound:data.txt")
EOF
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  srcs = ["@thisislocal//:data.txt", "@newlocal//:data.txt",
          "//external:bound"],
  outs = ["it.txt"],
  cmd = "cat $(SRCS) > $@",
)
EOF

  bazel build //:it || fail "Expected success"

  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
        --experimental_repository_resolved_file=resolved.bzl
  echo > WORKSPACE # remove workspace, only work from the resolved file
  bazel clean --expunge
  echo; cat resolved.bzl; echo
  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl \
        //:it || fail "Expected success"
}

test_hidden_symbols() {
  # Verify that the resolved file can be used for building, even if it
  # legitimately contains a private symbol
  mkdir main
  cd main
  cat > BUILD <<'EOF'
genrule(
  name = "it",
  srcs = ["@foo//:data.txt"],
  outs = ["it.txt"],
  cmd = "cp $< $@",
)
EOF

  cat > repo.bzl <<'EOF'
_THE_DATA="42"

def _data_impl(ctx):
  ctx.file("BUILD", "exports_files(['data.txt'])")
  ctx.file("data.txt", ctx.attr.data)

_repo = repository_rule(
  implementation = _data_impl,
  attrs = { "data" : attr.string() },
)

def data_repo(name):
  _repo(name=name, data=_THE_DATA)

EOF
  cat > WORKSPACE <<'EOF'
load("//:repo.bzl", "data_repo")

data_repo("foo")
EOF

  bazel build --experimental_repository_resolved_file=resolved.bzl //:it
  echo > WORKSPACE # remove workspace, only work from the resolved file
  bazel clean --expunge
  echo; cat resolved.bzl; echo

  bazel build --experimental_resolved_file_instead_of_workspace=resolved.bzl \
        //:it || fail "Expected success"
}

test_toolchain_recorded() {
  # Verify that the registration of toolchains and execution platforms is
  # recorded in the resolved file
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir ext
  touch ext/BUILD
  cat > ext/toolchains.bzl <<'EOF'
def ext_toolchains():
  native.register_toolchains("@ext//:toolchain")
  native.register_execution_platforms("@ext//:platform")
EOF
  tar cvf ext.tar ext
  rm -rf ext

  mkdir main
  cd main
  cat >> WORKSPACE <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
  name="ext",
  strip_prefix="ext",
  urls=["file://${EXTREPODIR}/ext.tar"],
)
load("@ext//:toolchains.bzl", "ext_toolchains")
ext_toolchains()
EOF
  touch BUILD
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
        --experimental_repository_resolved_file=resolved.bzl
  echo; cat resolved.bzl; echo

  grep 'register_toolchains.*ext//:toolchain' resolved.bzl \
      || fail "tool chain not registered in resolved file"
  grep 'register_execution_platforms.*ext//:platform' resolved.bzl \
      || fail "execution platform not registered in resolved file"
}

test_local_config_platform_recorded() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  # Verify that the auto-generated local_config_platform repo is
  # recorded in the resolved file
  mkdir main
  cd main
  # Clear out the WORKSPACE.
  cat >> WORKSPACE <<EOF
EOF
  touch BUILD
  bazel sync \
      --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
      --experimental_repository_resolved_file=resolved.bzl
  echo; cat resolved.bzl; echo

  grep 'local_config_platform' resolved.bzl \
      || fail "local_config_platform in resolved file"
}

test_definition_location_recorded() {
  # Verify that for Starlark repositories the location of the definition
  # is recorded in the resolved file.
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir ext
  touch ext/BUILD

  tar cvf ext.tar ext
  rm -rf ext

  mkdir main
  cd main
  touch BUILD
  mkdir -p first/path
  cat > first/path/foo.bzl <<'EOF'
load("//:another/directory/bar.bzl", "bar")

def foo():
  bar()
EOF
  mkdir -p another/directory
  cat > another/directory/bar.bzl <<EOF
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

def bar():
  http_archive(
    name = "ext",
    url = "file://${EXTREPODIR}/ext.tar",
  )
EOF
  cat > WORKSPACE <<'EOF'
load("//:first/path/foo.bzl", "foo")

foo()
EOF

  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir \
        --record_rule_instantiation_callstack \
        --experimental_repository_resolved_file=resolved.bzl

  echo; cat resolved.bzl; echo

  cat > BUILD <<'EOF'
load("//:finddef.bzl", "finddef")

genrule(
  name = "ext_def",
  outs = ["ext_def.txt"],
  cmd = "echo '%s' > $@" % (finddef("ext"),),
)
EOF
  cat > finddef.bzl <<'EOF'
load("//:resolved.bzl", "resolved")

def finddef(name):
  for repo in resolved:
    if repo["original_attributes"]["name"] == name:
      return repo["definition_information"]
EOF

  bazel build --record_rule_instantiation_callstack //:ext_def

  cat `bazel info bazel-genfiles`/ext_def.txt > "${TEST_log}"
  inplace-sed -e "s?$(pwd)/?PWD/?g" -e "s?$TEST_TMPDIR/?TEST_TMPDIR/?g" "${TEST_log}"
  expect_log "Repository ext instantiated at:"
  expect_log "  PWD/WORKSPACE:3"
  expect_log "  PWD/first/path/foo.bzl:4"
  expect_log "  PWD/another/directory/bar.bzl:4"
  expect_log "Repository rule http_archive defined at:"
  expect_log "  TEST_TMPDIR/.*/external/bazel_tools/tools/build_defs/repo/http.bzl:"
}

# Regression test for #11040.
#
# Test that a canonical repo warning is generated for explicitly specified
# attributes whose values differ, and that it is never generated for implicitly
# created attributes (in particular, the generator_* attributes).
test_canonical_warning() {
  EXTREPODIR=`pwd`
  tar xvf ${TEST_SRCDIR}/test_WORKSPACE_files/archives.tar

  mkdir main
  touch main/BUILD
  cat > main/reporule.bzl <<EOF
def _impl(repository_ctx):
    repository_ctx.file("a.txt", "A")
    repository_ctx.file("BUILD.bazel", "exports_files(['a.txt'])")
    # Don't include "name", test that we warn about it below.
    return {"myattr": "bar"}

reporule = repository_rule(
    implementation = _impl,
    attrs = {
        "myattr": attr.string()
    })

# We need to use a macro for the generator_* attributes to be defined.
def instantiate_reporule(name, **kwargs):
    reporule(name=name, **kwargs)
EOF
  cat > main/WORKSPACE <<EOF
workspace(name = "main")
load("//:reporule.bzl", "instantiate_reporule")
instantiate_reporule(
  name = "myrepo",
  myattr = "foo"
)
EOF

  cd main
  # We should get a warning for "myattr" having a changed value and for "name"
  # being dropped, but not for the generator_* attributes.
  bazel sync --distdir=${EXTREPODIR}/test_WORKSPACE/distdir >/dev/null 2>$TEST_log
  bazel clean --expunge
  expect_log "Rule 'myrepo' indicated that a canonical reproducible form \
can be obtained by modifying arguments myattr = \"bar\" and dropping \
\[.*\"name\".*\]"
  expect_not_log "Rule 'myrepo' indicated .*generator"
}

run_suite "workspace_resolved_test tests"
