#
# Copyright 2016 WebAssembly Community Group participants
#
# 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.
#

cmake_minimum_required(VERSION 2.6)
project(WABT)

option(BUILD_TESTS "Build GTest-based tests" ON)
option(BUILD_TOOLS "Build wabt commandline tools" ON)
option(USE_ASAN "Use address sanitizer" OFF)
option(USE_MSAN "Use memory sanitizer" OFF)
option(USE_LSAN "Use leak sanitizer" OFF)
option(USE_UBSAN "Use undefined behavior sanitizer" OFF)
option(CODE_COVERAGE "Build with code coverage enabled" OFF)
option(WITH_EXCEPTIONS "Build with exceptions enabled" OFF)
option(WERROR "Build with warnings as errors" OFF)

if (${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
  set(COMPILER_IS_CLANG 1)
  set(COMPILER_IS_GNU 0)
  set(COMPILER_IS_MSVC 0)
elseif (${CMAKE_C_COMPILER_ID} STREQUAL "GNU")
  set(COMPILER_IS_CLANG 0)
  set(COMPILER_IS_GNU 1)
  set(COMPILER_IS_MSVC 0)
elseif (${CMAKE_C_COMPILER_ID} STREQUAL "MSVC")
  set(COMPILER_IS_CLANG 0)
  set(COMPILER_IS_GNU 0)
  set(COMPILER_IS_MSVC 1)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
  set(COMPILER_IS_CLANG 1)
  set(COMPILER_IS_GNU 0)
  set(COMPILER_IS_MSVC 0)
else ()
  set(COMPILER_IS_CLANG 0)
  set(COMPILER_IS_GNU 0)
  set(COMPILER_IS_MSVC 0)
endif ()

include(CheckIncludeFile)
include(CheckSymbolExists)

check_include_file("alloca.h" HAVE_ALLOCA_H)
check_include_file("unistd.h" HAVE_UNISTD_H)
check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF)
check_symbol_exists(sysconf "unistd.h" HAVE_SYSCONF)
check_symbol_exists(strcasecmp "strings.h" HAVE_STRCASECMP)

if (WIN32)
  check_symbol_exists(ENABLE_VIRTUAL_TERMINAL_PROCESSING "windows.h" HAVE_WIN32_VT100)
endif ()

include(CheckTypeSize)
check_type_size(ssize_t SSIZE_T)
check_type_size(size_t SIZEOF_SIZE_T)

configure_file(
  ${WABT_SOURCE_DIR}/src/config.h.in
  ${WABT_BINARY_DIR}/config.h
)

include_directories(${WABT_SOURCE_DIR} ${WABT_BINARY_DIR})

if (COMPILER_IS_MSVC)
  # disable warning C4018: signed/unsigned mismatch
  # disable warning C4056, C4756: overflow in floating-point constant arithmetic
  #   seems to not like float compare w/ HUGE_VALF; bug?
  # disable warnings C4267 and C4244: conversion/truncation from larger to smaller type.
  # disable warning C4800: implicit conversion from larger int to bool
  add_definitions(-W3 -wd4018 -wd4056 -wd4756 -wd4267 -wd4244 -wd4800 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS)

  if (WERROR)
    add_definitions(-WX)
  endif ()

  if (NOT WITH_EXCEPTIONS)
    # disable exception use in C++ library
    add_definitions(-D_HAS_EXCEPTIONS=0)
  endif ()
else ()
  # disable -Wunused-parameter: this is really common when implementing
  #   interfaces, etc.
  # disable -Wpointer-arith: this is a GCC extension, and doesn't work in MSVC.
  add_definitions(
    -Wall -Wextra -Wno-unused-parameter -Wpointer-arith -g
    -Wuninitialized
  )

  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wold-style-cast")

  if (WERROR)
    add_definitions(-Werror)
  endif ()

  if (NOT WITH_EXCEPTIONS)
    add_definitions(-fno-exceptions)
  endif ()

  # Need to define __STDC_*_MACROS because C99 specifies that C++ shouldn't
  # define format (e.g. PRIu64) or limit (e.g. UINT32_MAX) macros without the
  # definition, and some libcs (e.g. glibc2.17 and earlier) follow that.
  add_definitions(-D__STDC_LIMIT_MACROS=1 -D__STDC_FORMAT_MACROS=1)

  if (MINGW)
    # _POSIX is needed to ensure we use mingw printf
    # instead of the VC runtime one.
    add_definitions(-D_POSIX)
  endif ()

  if (COMPILER_IS_GNU)
    # disable -Wclobbered: it seems to be guessing incorrectly about a local
    # variable being clobbered by longjmp.
    add_definitions(-Wno-clobbered)
  endif ()

  if (NOT EMSCRIPTEN)
    # try to get the target architecture by compiling a dummy.c file and
    # checking the architecture using the file command.
    file(WRITE ${WABT_BINARY_DIR}/dummy.c "main(){}")
    try_compile(
      COMPILE_OK
      ${WABT_BINARY_DIR}
      ${WABT_BINARY_DIR}/dummy.c
      COPY_FILE ${WABT_BINARY_DIR}/dummy
    )
    if (COMPILE_OK)
      execute_process(
        COMMAND file ${WABT_BINARY_DIR}/dummy
        RESULT_VARIABLE FILE_RESULT
        OUTPUT_VARIABLE FILE_OUTPUT
        ERROR_QUIET
      )

      if (FILE_RESULT EQUAL 0)
        if (${FILE_OUTPUT} MATCHES "x86[-_]64")
          set(TARGET_ARCH "x86-64")
        elseif (${FILE_OUTPUT} MATCHES "Intel 80386")
          set(TARGET_ARCH "i386")
        elseif (${FILE_OUTPUT} MATCHES "ARM")
          set(TARGET_ARCH "ARM")
        else ()
          message(WARNING "Unknown target architecture!")
        endif ()
      else ()
        message(WARNING "Error running `file` command on dummy executable")
      endif ()
    else ()
      message(WARNING "Error compiling dummy.c file")
    endif ()

    if (TARGET_ARCH STREQUAL "i386")
      # wasm doesn't allow for x87 floating point math
      add_definitions(-msse2 -mfpmath=sse)
    endif ()
  endif ()
endif ()

set(USE_SANITIZER FALSE)

function(SANITIZER NAME FLAGS)
  if (${NAME})
    if (USE_SANITIZER)
      message(FATAL_ERROR "Only one sanitizer allowed")
    endif ()
    set(USE_SANITIZER TRUE PARENT_SCOPE)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS}" PARENT_SCOPE)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAGS}" PARENT_SCOPE)
  endif ()
endfunction()
SANITIZER(USE_ASAN "-fsanitize=address")
SANITIZER(USE_MSAN "-fsanitize=memory")
SANITIZER(USE_LSAN "-fsanitize=leak")

if (USE_UBSAN)
  # -fno-sanitize-recover was deprecated, see if we are compiling with a newer
  # clang that requires -fno-sanitize-recover=all.
  set(UBSAN_BLACKLIST ${WABT_SOURCE_DIR}/ubsan.blacklist)
  include(CheckCXXCompilerFlag)
  CHECK_CXX_COMPILER_FLAG("-fsanitize=undefined -fno-sanitize-recover -Wall -Werror" HAS_UBSAN_RECOVER_BARE)
  if (HAS_UBSAN_RECOVER_BARE)
    SANITIZER(USE_UBSAN "-fsanitize=undefined -fno-sanitize-recover -fsanitize-blacklist=${UBSAN_BLACKLIST}")
  endif ()
  CHECK_CXX_COMPILER_FLAG("-fsanitize=undefined -fno-sanitize-recover=all -Wall -Werror" HAS_UBSAN_RECOVER_ALL)
  if (HAS_UBSAN_RECOVER_ALL)
    SANITIZER(USE_UBSAN "-fsanitize=undefined -fno-sanitize-recover=all -fsanitize-blacklist=${UBSAN_BLACKLIST}")
  endif ()
  if (NOT USE_SANITIZER)
    message(FATAL_ERROR "UBSAN is not supported")
  endif ()
endif ()

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${WABT_SOURCE_DIR}/cmake)

add_custom_target(everything)

add_library(wabt STATIC
  src/apply-names.cc
  src/binary.cc
  src/binary-reader.cc
  src/binary-reader-ir.cc
  src/binary-reader-logging.cc
  src/binary-writer.cc
  src/binary-writer-spec.cc
  src/binding-hash.cc
  src/color.cc
  src/common.cc
  src/config.cc
  src/error-formatter.cc
  src/expr-visitor.cc
  src/feature.cc
  src/filenames.cc
  src/generate-names.cc
  src/hash-util.cc
  src/ir.cc
  src/leb128.cc
  src/lexer-source.cc
  src/lexer-source-line-finder.cc
  src/literal.cc
  src/opcode.cc
  src/opcode-code-table.c
  src/option-parser.cc
  src/resolve-names.cc
  src/stream.cc
  src/string-view.cc
  src/token.cc
  src/tracing.cc
  src/type-checker.cc
  src/utf8.cc
  src/validator.cc
  src/wast-lexer.cc
  src/wast-parser.cc
  src/wat-writer.cc

  # TODO(binji): Move this into its own library?
  src/interp/binary-reader-interp.cc
  src/interp/interp.cc
  src/interp/interp-disassemble.cc
  src/interp/interp-trace.cc
)


if (NOT EMSCRIPTEN)
  if (CODE_COVERAGE)
    add_definitions("-fprofile-arcs -ftest-coverage")
    if (COMPILER_IS_CLANG)
      set(CMAKE_EXE_LINKER_FLAGS "--coverage")
    else ()
      link_libraries(gcov)
    endif ()
  endif ()

  function(wabt_executable)
    cmake_parse_arguments(EXE "WITH_LIBM;INSTALL" "NAME" "SOURCES;LIBS" ${ARGN})

    # Always link libwabt.
    set(EXE_LIBS "${EXE_LIBS};wabt")

    # Optionally link libm.
    if (EXE_WITH_LIBM AND (COMPILER_IS_CLANG OR COMPILER_IS_GNU))
      set(EXE_LIBS "${EXE_LIBS};m")
    endif ()

    add_executable(${EXE_NAME} ${EXE_SOURCES})
    add_dependencies(everything ${EXE_NAME})
    target_link_libraries(${EXE_NAME} ${EXE_LIBS})
    set_property(TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11)
    set_property(TARGET ${EXE_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)

    if (EXE_INSTALL)
      list(APPEND WABT_EXECUTABLES ${EXE_NAME})
      set(WABT_EXECUTABLES ${WABT_EXECUTABLES} PARENT_SCOPE)

      add_custom_target(${EXE_NAME}-copy-to-bin ALL
        COMMAND ${CMAKE_COMMAND} -E make_directory ${WABT_SOURCE_DIR}/bin
        COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${EXE_NAME}> ${WABT_SOURCE_DIR}/bin
        DEPENDS ${EXE_NAME}
      )
    endif ()
  endfunction()

  if (BUILD_TOOLS)
    # wat2wasm
    wabt_executable(
      NAME wat2wasm
      SOURCES src/tools/wat2wasm.cc
      INSTALL
    )

    # wast2json
    wabt_executable(
      NAME wast2json
      SOURCES src/tools/wast2json.cc
      INSTALL
    )

    # wasm2wat
    wabt_executable(
      NAME wasm2wat
      SOURCES src/tools/wasm2wat.cc
      INSTALL
    )

    # wasm2c
    wabt_executable(
      NAME wasm2c
      SOURCES src/tools/wasm2c.cc src/c-writer.cc
      INSTALL
    )

    # wasm-opcodecnt
    wabt_executable(
      NAME wasm-opcodecnt
      SOURCES src/tools/wasm-opcodecnt.cc src/binary-reader-opcnt.cc
      INSTALL
    )

    # wasm-objdump
    wabt_executable(
      NAME wasm-objdump
      SOURCES src/tools/wasm-objdump.cc src/binary-reader-objdump.cc
      INSTALL
    )

    # wasm-interp
    wabt_executable(
      NAME wasm-interp
      SOURCES src/tools/wasm-interp.cc
      WITH_LIBM
      INSTALL
    )

    # spectest-interp
    wabt_executable(
      NAME spectest-interp
      SOURCES src/tools/spectest-interp.cc
      WITH_LIBM
      INSTALL
    )

    # wat-desugar
    wabt_executable(
      NAME wat-desugar
      SOURCES src/tools/wat-desugar.cc
      INSTALL
    )

    # wasm-validate
    wabt_executable(
      NAME wasm-validate
      SOURCES src/tools/wasm-validate.cc
      INSTALL
    )

    # wasm-strip
    wabt_executable(
      NAME wasm-strip
      SOURCES src/tools/wasm-strip.cc
      INSTALL
    )
  endif ()

  find_package(Threads)
  if (BUILD_TESTS)
    if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gtest/googletest)
      message(FATAL_ERROR "Can't find third_party/gtest. Run git submodule update --init, or disable with CMake -DBUILD_TESTS=OFF.")
    endif ()

    include_directories(
      third_party/gtest/googletest
      third_party/gtest/googletest/include
    )

    # gtest
    add_library(libgtest STATIC
      third_party/gtest/googletest/src/gtest-all.cc
    )

    # hexfloat-test
    set(HEXFLOAT_TEST_SRCS
      src/literal.cc
      src/test-hexfloat.cc
      third_party/gtest/googletest/src/gtest_main.cc
    )
    wabt_executable(
      NAME hexfloat_test
      SOURCES ${HEXFLOAT_TEST_SRCS}
      LIBS libgtest ${CMAKE_THREAD_LIBS_INIT}
    )

    # wabt-unittests
    set(UNITTESTS_SRCS
      src/test-binary-reader.cc
      src/test-circular-array.cc
      src/test-interp.cc
      src/test-intrusive-list.cc
      src/test-literal.cc
      src/test-string-view.cc
      src/test-filenames.cc
      src/test-utf8.cc
      src/test-wast-parser.cc
      third_party/gtest/googletest/src/gtest_main.cc
    )
    wabt_executable(
      NAME wabt-unittests
      SOURCES ${UNITTESTS_SRCS}
      LIBS libgtest ${CMAKE_THREAD_LIBS_INIT}
    )

    if (NOT CMAKE_VERSION VERSION_LESS "3.2")
      set(USES_TERMINAL USES_TERMINAL)
    endif ()

    # test running
    find_package(PythonInterp 2.7 REQUIRED)
    set(RUN_TESTS_PY ${WABT_SOURCE_DIR}/test/run-tests.py)
    add_custom_target(run-tests
      COMMAND ${CMAKE_BINARY_DIR}/wabt-unittests
      COMMAND ${PYTHON_EXECUTABLE} ${RUN_TESTS_PY} --bindir ${CMAKE_BINARY_DIR}
      DEPENDS ${WABT_EXECUTABLES}
      WORKING_DIRECTORY ${WABT_SOURCE_DIR}
      ${USES_TERMINAL}
    )
  endif ()

  # install
  if (BUILD_TOOLS OR BUILD_TESTS)
    install(TARGETS ${WABT_EXECUTABLES} DESTINATION bin)
    if (UNIX)
      if (NOT CMAKE_INSTALL_MANDIR)
        include(GNUInstallDirs)
      endif ()
      file(GLOB MAN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/man/*.1")
      foreach(MAN_FILE ${MAN_FILES})
        install(FILES ${MAN_FILE}
          DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
      endforeach()
    endif ()
  endif ()

else ()
  # emscripten stuff

  # just dump everything into one binary so we can reference it from JavaScript
  add_definitions(-Wno-warn-absolute-paths)
  add_executable(libwabtjs src/emscripten-helpers.cc)
  add_dependencies(everything libwabtjs)
  target_link_libraries(libwabtjs wabt)
  set_target_properties(libwabtjs PROPERTIES OUTPUT_NAME libwabt)

  set(WABT_POST_JS ${WABT_SOURCE_DIR}/src/wabt.post.js)
  set(EMSCRIPTEN_EXPORTED_JSON ${WABT_SOURCE_DIR}/src/emscripten-exported.json)

  set(LIBWABT_LINK_FLAGS
    --memory-init-file 0
    --post-js ${WABT_POST_JS}
    -s EXPORTED_FUNCTIONS=\"@${EMSCRIPTEN_EXPORTED_JSON}\"
    -s RESERVED_FUNCTION_POINTERS=10
    -s NO_EXIT_RUNTIME=1
    -s ALLOW_MEMORY_GROWTH=1
    -s MODULARIZE=1
    -s EXPORT_NAME=\"'WabtModule'\"
    -s WASM=0
    -Oz
    --llvm-lto 1
  )
  string(REPLACE ";" " " LIBWABT_LINK_FLAGS_STR "${LIBWABT_LINK_FLAGS}")

  set_target_properties(libwabtjs
    PROPERTIES
    LINK_FLAGS "${LIBWABT_LINK_FLAGS_STR}"
    LINK_DEPENDS "${WABT_POST_JS};${EMSCRIPTEN_EXPORTED_JSON}"
  )
endif ()
