cmake_minimum_required(VERSION 2.8...3.27)
project(libredwg C)
# Supported options -DLIBREDWG_LIBONLY=On
#                   -DLIBREDWG_DISABLE_WRITE=On
#                   -DLIBREDWG_DISABLE_JSON=On
#                   -DENABLE_MIMALLOC=On
#                   -DENABLE_LTO=Off
#                   -DDXF_PRECISION=6
#                   -DGEOMJSON_PRECISION=6
# for smaller builds and lib.

if(MSVC)
  set(redwg libredwg)
  # Disable some overly strict MSVC warnings.
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -wd4244 -wd4800 -wd4805 -wd4101 -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS")
else()
  set(redwg redwg)
endif()

if (EXISTS ".version")
  file(READ .version NL_PACKAGE_VERSION)
else()
  find_package(Git)
  set(PACKAGE_VERSION "")
  execute_process(COMMAND ${GIT_EXECUTABLE} describe --long --tags --always
                  OUTPUT_VARIABLE NL_PACKAGE_VERSION)
endif()
string(STRIP "${NL_PACKAGE_VERSION}" PACKAGE_VERSION)

option(BUILD_SHARED_LIBS "shared libredwg library" ON)
option(LIBREDWG_LIBONLY "only the libredwg library" OFF)
option(LIBREDWG_DISABLE_WRITE "no libredwg write support" OFF)
option(DISABLE_WERROR "no -Werror" OFF)
option(ENABLE_LTO "IPO/LTO Link Time Optimizations (default ON)" ON)
# FIXME enable
# option(LIBREDWG_DISABLE_BINDINGS "no libredwg perl and python bindings" ON)
option(LIBREDWG_DISABLE_JSON "no libredwg json support" OFF)
# Rather disable installing a crippled shared lib.
# Only use these for static in-tree libs.
if (LIBREDWG_DISABLE_JSON)
  set(BUILD_SHARED_LIBS OFF)
endif()
if (LIBREDWG_DISABLE_WRITE)
  set(BUILD_SHARED_LIBS OFF)
endif()
option(DXF_PRECISION "number of DXF after-comma places" 16)
option(GEOJSON_PRECISION "number of GeoJSON after-comma places (6 recoomended by RFC)" 16)

include(CheckIncludeFile)
CHECK_INCLUDE_FILE("stddef.h" HAVE_STDDEF_H)
CHECK_INCLUDE_FILE("stdlib.h" HAVE_STDLIB_H)
CHECK_INCLUDE_FILE("string.h" HAVE_STRING_H)
CHECK_INCLUDE_FILE("strings.h" HAVE_STRINGS_H)
CHECK_INCLUDE_FILE("libgen.h" HAVE_LIBGEN_H)
CHECK_INCLUDE_FILE("unistd.h" HAVE_UNISTD_H)
CHECK_INCLUDE_FILE("float.h" HAVE_FLOAT_H)
CHECK_INCLUDE_FILE("ctype.h" HAVE_CTYPE_H)
CHECK_INCLUDE_FILE("wchar.h" HAVE_WCHAR_H)
CHECK_INCLUDE_FILE("wctype.h" HAVE_WCTYPE_H)
CHECK_INCLUDE_FILE("alloca.h" HAVE_ALLOCA_H)
CHECK_INCLUDE_FILE("getopt.h" HAVE_GETOPT_H)
CHECK_INCLUDE_FILE("stdint.h" HAVE_STDINT_H)
CHECK_INCLUDE_FILE("inttypes.h" HAVE_INTTYPES_H)
CHECK_INCLUDE_FILE("limits.h" HAVE_LIMITS_H)
CHECK_INCLUDE_FILE("malloc.h" HAVE_MALLOC_H)
CHECK_INCLUDE_FILE("memory.h" HAVE_MEMORY_H)
CHECK_INCLUDE_FILE("dirent.h" HAVE_DIRENT_H)
CHECK_INCLUDE_FILE("endian.h" HAVE_ENDIAN_H)
CHECK_INCLUDE_FILE("sys/endian.h" HAVE_SYS_ENDIAN_H)
CHECK_INCLUDE_FILE("byteorder.h" HAVE_BYTEORDER_H)
CHECK_INCLUDE_FILE("sys/byteorder.h" HAVE_SYS_BYTEORDER_H)
CHECK_INCLUDE_FILE("byteswap.h" HAVE_BYTESWAP_H)
if (WIN32)
  CHECK_INCLUDE_FILE("winsock2.h" HAVE_WINSOCK2_H)
elseif (APPLE)
  CHECK_INCLUDE_FILE("machine/endian.h" HAVE_MACHINE_ENDIAN_H)
endif()
CHECK_INCLUDE_FILE("iconv.h" HAVE_ICONV_H)
CHECK_INCLUDE_FILE("pcre2.h" HAVE_PCRE2_H)
CHECK_INCLUDE_FILE("sys/stat.h" HAVE_SYS_STAT_H)
CHECK_INCLUDE_FILE("direct.h" HAVE_DIRECT_H)
CHECK_INCLUDE_FILE("sys/param.h" HAVE_SYS_PARAM_H)
CHECK_INCLUDE_FILE("sys/time.h" HAVE_SYS_TIME_H)
CHECK_INCLUDE_FILE("sys/types.h" HAVE_SYS_TYPES_H)
CHECK_INCLUDE_FILE("libps/pslib.h" HAVE_LIBPS_PSLIB_H)
CHECK_INCLUDE_FILE("mimalloc-override.h" HAVE_MIMALLOC_OVERRIDE_H)
include(CheckSymbolExists)
check_symbol_exists("floor" "math.h" HAVE_FLOOR)
check_symbol_exists("gettimeofday" "sys/time.h" HAVE_GETTIMEOFDAY)
check_symbol_exists("memchr" "string.h" HAVE_MEMCHR)
check_symbol_exists("memmem" "string.h" HAVE_MEMMEM)
check_symbol_exists("memmove" "string.h" HAVE_MEMMOVE)
check_symbol_exists("realloc" "stdlib.h" HAVE_REALLOC)
check_symbol_exists("scandir" "dirent.h" HAVE_SCANDIR)
check_symbol_exists("setenv" "stdlib.h" HAVE_SETENV)
check_symbol_exists("strcasecmp" "strings.h" HAVE_STRCASECMP)
check_symbol_exists("strcasestr" "string.h" HAVE_STRCASESTR)
check_symbol_exists("strchr" "string.h" HAVE_STRCHR)
check_symbol_exists("strnlen" "string.h" HAVE_STRNLEN)
check_symbol_exists("strrchr" "string.h" HAVE_STRRCHR)
check_symbol_exists("strtol" "stdlib.h" HAVE_STRTOL)
check_symbol_exists("strtoul" "stdlib.h" HAVE_STRTOUL)
check_symbol_exists("strtoull" "stdlib.h" HAVE_STRTOULL)
check_symbol_exists("wcscmp" "wchar.h" HAVE_WCSCMP)
check_symbol_exists("wcscpy" "wchar.h" HAVE_WCSCPY)
check_symbol_exists("wcslen" "wchar.h" HAVE_WCSLEN)
check_symbol_exists("wcsnlen" "wchar.h" HAVE_WCSNLEN)
include(CheckTypeSize)
check_type_size(size_t SIZE_T)
check_type_size(wchar_t WCHAR_T)
if (NOT SIZE_T)
  message(WARNING "SIZE_T detection failed, assuming 8")
  set(SIZE_T 8)
endif()
if (NOT WCHAR_T)
  message(WARNING "WCHAR_T detection failed, assuming 2")
  set(WCHAR_T 2)
endif()
if (WCHAR_T EQUAL 2)
  set(HAVE_PCRE2_16 1)
endif()
if (NOT HAVE_MIMALLOC_OVERRIDE_H)
  if (ENABLE_MIMALLOC)
    message(WARNING "Should not ENABLE_MIMALLOC without mimalloc-override.h")
  endif()
endif()
if (NOT MSVC)
  SET(LIBS "-lm")
endif()
if (ENABLE_MIMALLOC)
  SET(LIBS "${LIBS} -lmimalloc")
endif()
FIND_PACKAGE(Iconv)
if (Iconv_FOUND)
  SET(HAVE_ICONV TRUE)
  SET(HAVE_ICONV_H TRUE)
  if (NOT "${Iconv_LIBRARY}" STREQUAL "")
    message(STATUS "add ${Iconv_LIBRARY}")
    link_libraries(${Iconv_LIBRARY})
  endif()
  if (NOT "${Iconv_INCLUDE_DIR}" STREQUAL "")
    message(STATUS "add -I\"${Iconv_INCLUDE_DIR}\"")
    include_directories(${Iconv_INCLUDE_DIR})
  endif()
else()
  message(STATUS "iconv not used")
endif()
if(CMAKE_MAJOR_VERSION GREATER 2)
  if (NOT MSVC)
    include(TestBigEndian)
    test_big_endian(IS_BIG_ENDIAN)
    if(IS_BIG_ENDIAN)
      message(ERROR "Big Endian not supported: <${error}>")
      add_definitions(-DWORDS_BIGENDIAN)
    endif()
  endif()
endif()

if (MSVC)
  add_compile_options(/W3)
else()
  add_compile_options(-W -Wall -Wextra -Wno-unused-variable -Wno-unused-parameter
      -Wno-unused-but-set-variable -Wcast-align -Wmissing-field-initializers)
  if (CMAKE_COMPILER_IS_GNUC)
    add_compile_options(-fno-semantic-interposition -fwrapv -fno-common
                        -fvisibility=hidden -fno-strict-aliasing)
  endif()
  if (${CMAKE_VERSION} VERSION_GREATER "3.18.0")
    include(CheckCompilerFlag)
    check_compiler_flag(C -ftrivial-auto-var-init=zero HAVE_C_FTRIVIAL_AUTO_VAR_INIT)
    check_compiler_flag(C -fstack-protector-strong HAVE_C_FSTACK_PROTECTOR_STRONG)
    check_compiler_flag(C -fstack-clash-protection HAVE_C_FSTACK_CLASH_PROTECTION)
    check_compiler_flag(C -fcf-protection=full HAVE_C_FCF_PROTECTION)
    check_compiler_flag(C -fno-delete-null-pointer-checks HAVE_C_FNO_DELETE_NULL_POINTER_CHECKS)
    check_compiler_flag(C -Warray-bounds HAVE_C_WARRAY_BOUNDS)
    if (HAVE_C_FTRIVIAL_AUTO_VAR_INIT)
      add_compile_options(-ftrivial-auto-var-init=zero)
      add_link_options(-ftrivial-auto-var-init=zero)
    endif()
    if (HAVE_C_FSTACK_PROTECTOR_STRONG)
      add_compile_options(-fstack-protector-strong)
      add_link_options(-fstack-protector-strong)
    endif()
    if (HAVE_C_FSTACK_CLASH_PROTECTION)
      add_compile_options(-fstack-clash-protection)
      add_link_options(-fstack-clash-protection)
    endif()
    if (HAVE_C_FCF_PROTECTION)
      add_compile_options(-fcf-protection=full)
      add_link_options(-fcf-protection=full)
    endif()
    if (HAVE_C_FNO_DELETE_NULL_POINTER_CHECKS)
      add_compile_options(-fno-delete-null-pointer-checks)
      add_link_options(-fno-delete-null-pointer-checks)
    endif()
    if (HAVE_C_WARRAY_BOUNDS)
      add_compile_options(-Wno-error=array-bounds)
    endif()
  endif()
endif()
if (NOT DISABLE_WERROR)
  if (MSVC)
    add_compile_options(/WX)
  else()
    add_compile_options(-Werror -Wno-error=cast-align)
  endif()
endif()

configure_file(src/cmakeconfig.h.in src/config.h)

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "Release"
      CACHE STRING "Choose the type of build, options are: Debug Release
  RelWithDebInfo MinSizeRel Asan." FORCE)
endif()

if((CMAKE_MAJOR_VERSION EQUAL 3 AND CMAKE_MINOR_VERSION GREATER_EQUAL 9)
   AND (CMAKE_BUILD_TYPE STREQUAL "Release") AND (NOT MSVC) AND (ENABLE_LTO))
  cmake_policy(SET CMP0069 NEW)
  include(CheckIPOSupported)
  check_ipo_supported(RESULT ipo_supported OUTPUT error)
endif()

set(libredwg_HEADERS
    include/dwg.h
    include/dwg_api.h)

if(NOT LIBREDWG_DISABLE_JSON)
  set(outjson_SOURCES
    src/out_json.c
    src/out_geojson.c)
  set(injson_SOURCES
    src/in_json.c)
endif()

if(NOT LIBREDWG_DISABLE_WRITE)
 set(write_SOURCES
    src/encode.c
    src/in_dxf.c
    ${injson_SOURCES}
    src/reedsolomon.c)
endif()

set(libredwg_SOURCES
    src/bits.c
    src/codepages.c
    src/common.c
    src/classes.c
    src/objects.c
    src/decode.c
    src/decode_r11.c
    src/decode_r2007.c
    src/dwg.c
    src/hash.c
    src/dwg_api.c
    src/dynapi.c
    src/dxfclasses.c
    src/free.c
    src/geom.c
    src/out_dxf.c
    ${outjson_SOURCES}
    src/out_dxfb.c
    ${write_SOURCES}
    src/print.c)

add_library(${redwg}
    ${libredwg_HEADERS}
    ${libredwg_SOURCES})

set_property(TARGET ${redwg} PROPERTY C_STANDARD 99)
set_property(TARGET ${redwg} PROPERTY C_STANDARD_REQUIRED ON)

set_target_properties(${redwg} PROPERTIES
  PUBLIC_HEADER "${libredwg_HEADERS}")
if (BUILD_SHARED_LIBS)
  target_compile_definitions(${redwg} PUBLIC DLL_EXPORT;ENABLE_SHARED)
endif()
target_include_directories(${redwg} PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${CMAKE_CURRENT_BINARY_DIR}/src)
target_include_directories(${redwg} PUBLIC
  ${CMAKE_CURRENT_SOURCE_DIR}/include)

link_libraries(${redwg} ${LIBS} ${CMAKE_THREAD_LIBS_INIT})

if(NOT LIBREDWG_LIBONLY)
  if(NOT HAVE_GETOPT_H)
    set(getopt_c programs/getopt.c)
  endif(NOT HAVE_GETOPT_H)
  set(executables_TARGETS dwgread dwg2dxf dwggrep dwglayers dwgbmp dwg2SVG)
  add_executable(dwgread programs/dwgread.c ${getopt_c})
  add_executable(dwg2dxf programs/dwg2dxf.c ${getopt_c})
  if(NOT LIBREDWG_DISABLE_WRITE)
    add_executable(dwgrewrite programs/dwgrewrite.c ${getopt_c})
    add_executable(dwgwrite programs/dwgwrite.c ${getopt_c})
    add_executable(dxf2dwg programs/dxf2dwg.c ${getopt_c})
    set(executables_TARGETS ${executables_TARGETS} dwgrewrite dwgwrite dxf2dwg)
  endif(NOT LIBREDWG_DISABLE_WRITE)
  add_executable(dwggrep programs/dwggrep.c ${getopt_c})
  add_executable(dwglayers programs/dwglayers.c ${getopt_c})
  add_executable(dwgbmp programs/dwgbmp.c ${getopt_c})
  add_executable(dwg2SVG programs/dwg2SVG.c
                         programs/escape.c ${getopt_c})

  if (HAVE_PCRE2_H)
    if(HAVE_PCRE2_16)
      target_link_libraries(dwggrep ${redwg} -lpcre2-8 -lpcre2-16)
    else()
      target_link_libraries(dwggrep ${redwg} -lpcre2-8)
    endif(HAVE_PCRE2_16)
    #target_include_directories(dwggrep PUBLIC pcre2)
  endif(HAVE_PCRE2_H)

endif(NOT LIBREDWG_LIBONLY)

if(ipo_supported)
  message(STATUS "IPO / LTO enabled")
  set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION True)
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLTO")
else()
  message(STATUS "IPO / LTO not supported: <${error}>")
endif()

include_directories(BEFORE
  ${CMAKE_CURRENT_SOURCE_DIR}/include
  ${CMAKE_CURRENT_SOURCE_DIR}/src
  ${CMAKE_CURRENT_BINARY_DIR}/src)

# no tests if included elsewhere
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
    include(CTest)
endif()
# nor turned off with -DBUILD_TESTING=OFF
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
  set(CAN_SH_TEST OFF cache bool "can shell test")
  if(NOT CMAKE_CROSSCOMPILING)
    set(CAN_SH_TEST ON FORCE)
  endif(NOT CMAKE_CROSSCOMPILING)
  if(CMAKE_HOST_WIN32)
    set(CAN_SH_TEST OFF FORCE)
  endif(CMAKE_HOST_WIN32)
  if(CAN_SH_TEST)
    if(NOT LIBREDWG_LIBONLY)
      set(top_builddir ${CMAKE_CURRENT_BINARY_DIR})
      set(top_srcdir ${CMAKE_CURRENT_SOURCE_DIR})
      set(CFLAGS ${CMAKE_C_FLAGS})
      set(SED "sed")
      set(JSON_SED_NAN "true")
      set(JQ "jq")
      set(RUN_JING "true")
      if (LIBREDWG_DISABLE_JSON)
        set(DISABLE_JSON 1)
      else()
        set(DISABLE_JSON )
      endif()
      if(UNIX)
        configure_file(programs/alive.test.in alive.test @ONLY)
        add_test(alive.test alive.test)
        if(NOT LIBREDWG_DISABLE_DXF)
          configure_file(programs/dxf.test.in dxf.test @ONLY)
          add_test(dxf.test dxf.test)
          if(NOT LIBREDWG_DISABLE_JSON)
            configure_file(programs/json.test.in json.test @ONLY)
            add_test(json.test json.test)
          endif(NOT LIBREDWG_DISABLE_JSON)
        endif(NOT LIBREDWG_DISABLE_DXF)
        configure_file(programs/svg.test.in svg.test @ONLY)
        add_test(svg.test svg.test)
      endif(UNIX)
    endif(NOT LIBREDWG_LIBONLY)
  endif(CAN_SH_TEST)

  add_subdirectory(test/unit-testing)
endif()

add_custom_target(
  regen-dynapi
  COMMAND perl src/gen-dynapi.pl
  DEPENDS src/gen-dynapi.pl include/dwg.h)

add_custom_target(
  regen-gperf
  COMMAND gperf --output-file src/dxfclasses.c src/dxfclasses.in
  DEPENDS src/dxfclasses.in
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})

add_custom_target(
  TAGS
  COMMAND etags --language=c++ *.c *.h
  DEPENDS ${SRCS}
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})

if(MSVC)
  install(TARGETS ${redwg} RUNTIME PUBLIC_HEADER
          DESTINATION libredwg-${PACKAGE_VERSION})
  if(NOT LIBREDWG_LIBONLY)
    install(TARGETS ${executables_TARGETS}
          DESTINATION libredwg-${PACKAGE_VERSION})
  endif()
  install(TARGETS ${redwg}
          LIBRARY
          COMPONENT ${redwg}
          DESTINATION libredwg-${PACKAGE_VERSION})
else()
  include(GNUInstallDirs)
  install(TARGETS ${redwg} PUBLIC_HEADER
          DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
  if(NOT LIBREDWG_LIBONLY)
    install(TARGETS ${executables_TARGETS}
            DESTINATION "${CMAKE_INSTALL_BINDIR}")
  endif()
endif()
install(TARGETS RUNTIME)

if(WIN32)
  add_custom_target(dist
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/README README
    COMMAND zip libredwg-${PACKAGE_VERSION}.zip dwg*.exe dxf*.exe *.lib *.dll
                README doc/LibreDWG.pdf
    DEPENDS ${redwg})
endif()
