#!/bin/bash
#
# Copyright 2016 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 the examples provided in Bazel
#

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

if [ "${PLATFORM}" != "darwin" ]; then
  echo "This test suite requires running on OS X" >&2
  exit 0
fi

source "${CURRENT_DIR}/apple_common.sh" \
  || { echo "apple_common.sh not found!" >&2; exit 1; }

function set_up() {
  copy_examples
  setup_objc_test_support

  # Find the version number for an installed Xcode.
  XCODE_VERSION=$(xcodebuild -version | grep ^Xcode | cut -d' ' -f2)

  create_new_workspace
}

function test_fat_binary_no_srcs() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_binary_rule_in package

  cat > package/BUILD <<EOF
load("//package:starlark_apple_binary.bzl", "starlark_apple_binary")
objc_library(
    name = "lib_a",
    srcs = ["a.m"],
)
objc_library(
    name = "lib_b",
    srcs = ["b.m"],
)
starlark_apple_binary(
    name = "main_binary",
    deps = [":lib_a", ":lib_b"],
    platform_type = "ios",
    minimum_os_version = "10.0",
)
genrule(
  name = "lipo_run",
  srcs = [":main_binary_lipobin"],
  outs = ["lipo_out"],
  cmd =
      "set -e && " +
      "lipo -info \$(location :main_binary_lipobin) > \$(@)",
  tags = ["requires-darwin"],
)
EOF
  touch package/a.m
  cat > package/b.m <<EOF
int main() {
  return 0;
}
EOF

  bazel build --verbose_failures --xcode_version=$XCODE_VERSION \
      //package:lipo_out --ios_multi_cpus=i386,x86_64 \
      --noincompatible_enable_cc_toolchain_resolution \
      || fail "should build starlark_apple_binary and obtain info via lipo"

  cat bazel-genfiles/package/lipo_out | grep "i386 x86_64" \
    || fail "expected output binary to contain 2 architectures"
}

function test_additive_cpus_flag() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_binary_rule_in package

  cat > package/BUILD <<EOF
load("//package:starlark_apple_binary.bzl", "starlark_apple_binary")
objc_library(
    name = "lib_a",
    srcs = ["a.m"],
)
objc_library(
    name = "lib_b",
    srcs = ["b.m"],
)
starlark_apple_binary(
    name = "main_binary",
    deps = [":lib_a", ":lib_b"],
    platform_type = "ios",
    minimum_os_version = "10.0",
)
genrule(
  name = "lipo_run",
  srcs = [":main_binary_lipobin"],
  outs = ["lipo_out"],
  cmd =
      "set -e && " +
      "lipo -info \$(location :main_binary_lipobin) > \$(@)",
  tags = ["requires-darwin"],
)
EOF
  touch package/a.m
  cat > package/b.m <<EOF
int main() {
  return 0;
}
EOF

  bazel build --verbose_failures --xcode_version=$XCODE_VERSION \
      //package:lipo_out \
      --noincompatible_enable_cc_toolchain_resolution \
      --ios_multi_cpus=i386 --ios_multi_cpus=x86_64 \
      || fail "should build starlark_apple_binary and obtain info via lipo"

  cat bazel-genfiles/package/lipo_out | grep "i386 x86_64" \
    || fail "expected output binary to contain 2 architectures"
}

function test_host_xcodes() {
  XCODE_VERSION=$(env -i xcodebuild -version | grep "Xcode" \
      | sed -E "s/Xcode (([0-9]|.)+).*/\1/")
  XCODE_BUILD_VERSION=$(env -i xcodebuild -version | grep "Build version" \
      | sed -E "s/Build version (([0-9]|.)+).*/\1/")
  IOS_SDK=$(env -i xcodebuild -version -sdk | grep iphoneos \
      | sed -E "s/.*\(iphoneos(([0-9]|.)+)\).*/\1/")
  MACOSX_SDK=$(env -i xcodebuild -version -sdk | grep "(macosx" \
      | sed -E "s/.*\(macosx(([0-9]|.)+)\).*/\1/" | head -n 1)

  # Unfortunately xcodebuild -version doesn't always pad with trailing .0, so,
  # for example, may produce "6.4", which is bad for this test.
  if [[ ! $XCODE_VERSION =~ [0-9].[0-9].[0-9] ]]
  then
    XCODE_VERSION="${XCODE_VERSION}.0"
  fi

  XCODE_VERSION_FULL="${XCODE_VERSION}.${XCODE_BUILD_VERSION}"

  bazel build @local_config_xcode//:host_xcodes >"${TEST_log}" 2>&1 \
     || fail "Expected host_xcodes to build"

  bazel query "attr(version, $XCODE_VERSION_FULL, \
      attr(default_ios_sdk_version, $IOS_SDK, \
      attr(default_macos_sdk_version, $MACOSX_SDK, \
      labels('versions', '@local_config_xcode//:host_xcodes'))))" \
      > xcode_version_target

  assert_contains "local_config_xcode" xcode_version_target

  DEFAULT_LABEL=$(bazel query \
      "labels('default', '@local_config_xcode//:host_xcodes')")

  assert_equals $DEFAULT_LABEL $(cat xcode_version_target)
}

function test_host_available_xcodes() {

  XCODE_VERSION=$(env -i xcodebuild -version | grep "Xcode" \
      | sed -E "s/Xcode (([0-9]|.)+).*/\1/")
  IOS_SDK=$(env -i xcodebuild -version -sdk | grep iphoneos \
      | sed -E "s/.*\(iphoneos(([0-9]|.)+)\).*/\1/")
  MACOSX_SDK=$(env -i xcodebuild -version -sdk | grep "(macosx" \
      | sed -E "s/.*\(macosx(([0-9]|.)+)\).*/\1/" | head -n 1)

  # Unfortunately xcodebuild -version doesn't always pad with trailing .0, so,
  # for example, may produce "6.4", which is bad for this test.
  if [[ ! $XCODE_VERSION =~ [0-9].[0-9].[0-9] ]]
  then
    XCODE_VERSION="${XCODE_VERSION}.0"
  fi

  bazel build @local_config_xcode//:host_available_xcodes >"${TEST_log}" 2>&1 \
     || fail "Expected host_available_xcodes to build"

  bazel query "attr(version, $XCODE_VERSION, \
      attr(default_ios_sdk_version, $IOS_SDK, \
      attr(default_macos_sdk_version, $MACOSX_SDK, \
      labels('versions', '@local_config_xcode//:host_available_xcodes'))))" \
      > xcode_version_target

  assert_contains "local_config_xcode" xcode_version_target

  DEFAULT_LABEL=$(bazel query \
      "labels('default', '@local_config_xcode//:host_available_xcodes')")

  assert_equals "$DEFAULT_LABEL" "$(cat xcode_version_target)"
}

function test_apple_binary_crosstool_ios() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_binary_rule_in package

  cat > package/BUILD <<EOF
load("//package:starlark_apple_binary.bzl", "starlark_apple_binary")
objc_library(
    name = "lib_a",
    srcs = ["a.m"],
)
objc_library(
    name = "lib_b",
    srcs = ["b.m"],
    deps = [":cc_lib"],
)
cc_library(
    name = "cc_lib",
    srcs = ["cc_lib.cc"],
)
starlark_apple_binary(
    name = "main_binary",
    deps = [":main_lib"],
    platform_type = "ios",
    minimum_os_version = "10.0",
)
objc_library(
    name = "main_lib",
    deps = [":lib_a", ":lib_b"],
    srcs = ["main.m"],
)
genrule(
  name = "lipo_run",
  srcs = [":main_binary_lipobin"],
  outs = ["lipo_out"],
  cmd =
      "set -e && " +
      "lipo -info \$(location :main_binary_lipobin) > \$(@)",
  tags = ["requires-darwin"],
)
EOF
  touch package/a.m
  touch package/b.m
  cat > package/main.m <<EOF
int main() {
  return 0;
}
EOF
  cat > package/cc_lib.cc << EOF
#include <string>

std::string GetString() { return "h3ll0"; }
EOF

  bazel build --verbose_failures //package:lipo_out \
    --noincompatible_enable_cc_toolchain_resolution \
    --ios_multi_cpus=i386,x86_64 \
    --xcode_version=$XCODE_VERSION \
    || fail "should build starlark_apple_binary and obtain info via lipo"

  cat bazel-genfiles/package/lipo_out | grep "i386 x86_64" \
    || fail "expected output binary to be for x86_64 architecture"
}

function test_apple_binary_crosstool_watchos() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_binary_rule_in package

  cat > package/BUILD <<EOF
load("//package:starlark_apple_binary.bzl", "starlark_apple_binary")
genrule(
  name = "lipo_run",
  srcs = [":main_binary_lipobin"],
  outs = ["lipo_out"],
  cmd =
      "set -e && " +
      "lipo -info \$(location :main_binary_lipobin) > \$(@)",
  tags = ["requires-darwin"],
)

starlark_apple_binary(
    name = "main_binary",
    deps = [":main_lib"],
    platform_type = "watchos",
)
objc_library(
    name = "main_lib",
    srcs = ["main.m"],
    deps = [":lib_a"],
)
cc_library(
    name = "cc_lib",
    srcs = ["cc_lib.cc"],
)
# By depending on a library which requires it is built for watchos, this test
# verifies that dependencies of starlark_apple_binary are compiled for the
# specified platform_type.
objc_library(
    name = "lib_a",
    srcs = ["a.m"],
    deps = [":cc_lib"],
)
EOF
  cat > package/main.m <<EOF
#import <WatchKit/WatchKit.h>

// Note that WKExtensionDelegate is only available in Watch SDK.
@interface TestInterfaceMain : NSObject <WKExtensionDelegate>
@end

int main() {
  return 0;
}
EOF
  cat > package/a.m <<EOF
#import <WatchKit/WatchKit.h>

// Note that WKExtensionDelegate is only available in Watch SDK.
@interface TestInterfaceA : NSObject <WKExtensionDelegate>
@end

int aFunction() {
  return 0;
}
EOF
  cat > package/cc_lib.cc << EOF
#include <string>

std::string GetString() { return "h3ll0"; }
EOF

  bazel build --verbose_failures //package:lipo_out \
      --noincompatible_enable_cc_toolchain_resolution \
      --watchos_cpus=armv7k \
      --xcode_version=$XCODE_VERSION \
      || fail "should build watch binary"

  cat bazel-genfiles/package/lipo_out | grep "armv7k" \
      || fail "expected output binary to be for armv7k architecture"

  bazel build --verbose_failures //package:lipo_out \
      --noincompatible_enable_cc_toolchain_resolution \
      --watchos_cpus=i386 \
      --xcode_version=$XCODE_VERSION \
      || fail "should build watch binary"

  cat bazel-genfiles/package/lipo_out | grep "i386" \
      || fail "expected output binary to be for i386 architecture"
}

function test_xcode_config_select() {
  mkdir -p a
  cat > a/BUILD <<'EOF'
xcode_config(
    name = "xcodes",
    default = ":version10",
    versions = [ ":version10", ":version20", ":version30" ],
    visibility = ["//visibility:public"],
)

xcode_version(
    name = "version10",
    default_ios_sdk_version = "1.1",
    default_macos_sdk_version = "1.2",
    default_tvos_sdk_version = "1.3",
    default_watchos_sdk_version = "1.4",
    version = "1.0",
)

xcode_version(
    name = "version20",
    default_ios_sdk_version = "2.1",
    default_macos_sdk_version = "2.2",
    default_tvos_sdk_version = "2.3",
    default_watchos_sdk_version = "2.4",
    version = "2.0",
)

xcode_version(
    name = "version30",
    default_ios_sdk_version = "3.1",
    default_macos_sdk_version = "3.2",
    default_tvos_sdk_version = "3.3",
    default_watchos_sdk_version = "3.4",
    version = "3.0",
)

config_setting(
    name = "xcode10",
    flag_values = { "@bazel_tools//tools/osx:xcode_version_flag": "1.0" },
)

config_setting(
    name = "xcode20",
    flag_values = { "@bazel_tools//tools/osx:xcode_version_flag": "2.0" },
)

config_setting(
    name = "ios11",
    flag_values = { "@bazel_tools//tools/osx:ios_sdk_version_flag": "1.1" },
)

config_setting(
    name = "ios21",
    flag_values = { "@bazel_tools//tools/osx:ios_sdk_version_flag": "2.1" },
)

genrule(
    name = "xcode",
    srcs = [],
    outs = ["xcodeo"],
    cmd = "echo " + select({
      ":xcode10": "XCODE 1.0",
      ":xcode20": "XCODE 2.0",
      "//conditions:default": "XCODE UNKNOWN",
    }) + " >$@",)

genrule(
    name = "ios",
    srcs = [],
    outs = ["ioso"],
    cmd = "echo " + select({
      ":ios11": "IOS 1.1",
      ":ios21": "IOS 2.1",
      "//conditions:default": "IOS UNKNOWN",
    }) + " >$@",)

EOF

  bazel build //a:xcode //a:ios --xcode_version_config=//a:xcodes || fail "build failed"
  assert_contains "XCODE 1.0" bazel-genfiles/a/xcodeo
  assert_contains "IOS 1.1" bazel-genfiles/a/ioso

  bazel build //a:xcode //a:ios --xcode_version_config=//a:xcodes \
      --xcode_version=2.0 || fail "build failed"
  assert_contains "XCODE 2.0" bazel-genfiles/a/xcodeo
  assert_contains "IOS 2.1" bazel-genfiles/a/ioso

  bazel build //a:xcode //a:ios --xcode_version_config=//a:xcodes \
      --xcode_version=3.0 || fail "build failed"
  assert_contains "XCODE UNKNOWN" bazel-genfiles/a/xcodeo
  assert_contains "IOS UNKNOWN" bazel-genfiles/a/ioso
}

function test_apple_binary_dsym_builds() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_binary_rule_in package

  cat > package/BUILD <<EOF
load("//package:starlark_apple_binary.bzl", "starlark_apple_binary")
starlark_apple_binary(
    name = "main_binary",
    deps = [":main_lib"],
    platform_type = "ios",
    minimum_os_version = "10.0",
)
objc_library(
    name = "main_lib",
    srcs = ["main.m"],
)
EOF
  cat > package/main.m <<EOF
int main() {
  return 0;
}
EOF

  bazel build --verbose_failures //package:main_binary \
      --noincompatible_enable_cc_toolchain_resolution \
      --ios_multi_cpus=i386,x86_64 \
      --xcode_version=$XCODE_VERSION \
      --apple_generate_dsym=true \
      || fail "should build starlark_apple_binary with dSYMs"
}

function test_apple_binary_spaces() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_binary_rule_in package

  cat > package/BUILD <<EOF
load("//package:starlark_apple_binary.bzl", "starlark_apple_binary")
starlark_apple_binary(
    name = "main_binary",
    deps = [":main_lib"],
    platform_type = "ios",
    minimum_os_version = "10.0",
)
objc_library(
    name = "main_lib",
    srcs = ["the main.m"],
)
EOF
  cat > "package/the main.m" <<EOF
int main() {
  return 0;
}
EOF

  bazel build --verbose_failures //package:main_binary \
      --noincompatible_enable_cc_toolchain_resolution \
      --ios_multi_cpus=i386,x86_64 \
      --xcode_version=$XCODE_VERSION \
      --apple_generate_dsym=true \
      || fail "should build starlark_apple_binary with dSYMs"
}


function test_apple_static_library() {
  rm -rf package
  mkdir -p package
  make_starlark_apple_static_library_rule_in package

  cat > package/BUILD <<EOF
load(
    "//package:starlark_apple_static_library.bzl",
    "starlark_apple_static_library",
)
starlark_apple_static_library(
    name = "static_lib",
    deps = [":dummy_lib"],
    platform_type = "ios",
    minimum_os_version = "10.0",
)
objc_library(
    name = "dummy_lib",
    srcs = ["dummy.m"],
)
EOF
  cat > "package/dummy.m" <<EOF
static int dummy __attribute__((unused,used)) = 0;
EOF

  bazel build --verbose_failures //package:static_lib \
      --noincompatible_enable_cc_toolchain_resolution \
      --ios_multi_cpus=i386,x86_64 \
      --ios_minimum_os=8.0 \
      --xcode_version=$XCODE_VERSION \
      || fail "should build starlark_apple_static_library"
}

run_suite "apple_tests"
