project(runtime)

cmake_minimum_required(VERSION 2.6)

#
# Main configuration.
#

include(CheckTypeSize)
check_type_size(void*  ptr_size)
if(${ptr_size} MATCHES "^8$") ## if it's 64-bit OS
    set(MULTILIB_SUFFIX 32)
else()
    set(MULTILIB_SUFFIX 64)
endif()

set(DMDFE_MINOR_VERSION   0)
set(DMDFE_PATCH_VERSION   60)
set(DMDFE_VERSION         ${D_VERSION}.${DMDFE_MINOR_VERSION}.${DMDFE_PATCH_VERSION})

set(MULTILIB              OFF                                                           CACHE BOOL    "Build both 64-bit and 32-bit libraries")
set(BUILD_BC_LIBS         OFF                                                           CACHE BOOL    "Build the runtime as bytecode libraries")
set(BUILD_SINGLE_LIB      ON                                                            CACHE BOOL    "Build single runtime library")
set(LIB_SUFFIX            ""                                                            CACHE STRING  "Takes an empty string or 64. Directory where lib will be installed: lib or lib64")
set(INCLUDE_INSTALL_DIR   ${CMAKE_INSTALL_PREFIX}/include/d                             CACHE PATH    "Directory where will be put header files")
set(BUILD_SHARED_LIBS     OFF                                                           CACHE BOOL    "Build as shared library or as static library")
set(CMAKE_INSTALL_LIBDIR  ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}                      CACHE PATH    "Directory where lib will be installed")
set(D_FLAGS -g -w -d CACHE STRING "runtime build flags, separated by ;")

if(BUILD_SHARED_LIBS)
    list(APPEND D_FLAGS -relocation-model=pic)
    if(APPLE)
         # We need to explicitly specify that __Dmain should be resolved at
         # runtime with the default OS X tool chain.
         list(APPEND LD_FLAGS -Wl,-U,__Dmain)
    endif()
    set(D_LIBRARY_TYPE SHARED)
else(BUILD_SHARED_LIBS)
    set(D_LIBRARY_TYPE STATIC)
    set(CXX_COMPILE_FLAGS " ")
endif(BUILD_SHARED_LIBS)

get_directory_property(PROJECT_PARENT_DIR DIRECTORY ${PROJECT_SOURCE_DIR} PARENT_DIRECTORY)
set(RUNTIME_DIR ${PROJECT_SOURCE_DIR}/druntime CACHE PATH "runtime source dir")

if(D_VERSION EQUAL 1)
    set(RUNTIME_AIO tango)
    configure_file(${PROJECT_PARENT_DIR}/${LDC_EXE}_install.conf.in ${PROJECT_BINARY_DIR}/../bin/${LDC_EXE}_install.conf)
    configure_file(${PROJECT_PARENT_DIR}/${LDC_EXE}.rebuild.conf.in ${PROJECT_BINARY_DIR}/../bin/${LDC_EXE}_install.rebuild.conf)

    message(STATUS "Note: Tango is no longer included in D1 builds, please compile and install it separately using its own build infrastructure (bob).")
    return()
endif()

#
# Gather source files.
#

set(PHOBOS2_DIR ${PROJECT_SOURCE_DIR}/phobos CACHE PATH "phobos2 source dir")
set(RUNTIME_CC druntime-core)
set(RUNTIME_GC druntime-gc-basic)
set(RUNTIME_DC druntime-rt-ldc)
set(RUNTIME_AIO druntime-ldc)
set(RUNTIME_DC_DIR ${RUNTIME_DIR}/src/rt)
set(RUNTIME_GC_DIR ${RUNTIME_DIR}/src/gc)
set(RUNTIME_INCLUDE ${RUNTIME_DIR}/src)
file(GLOB CORE_D ${RUNTIME_DIR}/src/core/*.d )
file(GLOB CORE_D_SYNC ${RUNTIME_DIR}/src/core/sync/*.d )
file(GLOB CORE_D_STDC ${RUNTIME_DIR}/src/core/stdc/*.d )
file(GLOB_RECURSE GC_D ${RUNTIME_GC_DIR}/*.d)
file(GLOB_RECURSE DCRT_D ${RUNTIME_DC_DIR}/*.d)
file(GLOB_RECURSE LDC_D ${RUNTIME_DIR}/src/ldc/*.d)
list(REMOVE_ITEM DCRT_D
    ${RUNTIME_DC_DIR}/alloca.d
    ${RUNTIME_DC_DIR}/critical_.d
    ${RUNTIME_DC_DIR}/deh.d
    ${RUNTIME_DC_DIR}/deh2.d
    ${RUNTIME_DC_DIR}/llmath.d
    ${RUNTIME_DC_DIR}/memory_osx.d
    ${RUNTIME_DC_DIR}/qsort2.d
    ${RUNTIME_DC_DIR}/trace.d
)
file(GLOB DCRT_C ${RUNTIME_DC_DIR}/*.c)
list(REMOVE_ITEM DCRT_C ${RUNTIME_DC_DIR}/deh.c ${RUNTIME_DC_DIR}/dylib_fixes.c)
if(UNIX)
    file(GLOB_RECURSE CORE_D_SYS ${RUNTIME_DIR}/src/core/sys/posix/*.d)
    if(APPLE)
        file(GLOB_RECURSE CORE_D_SYS_OSX ${RUNTIME_DIR}/src/core/sys/osx/*.d)
        list(APPEND CORE_D_SYS ${CORE_D_SYS_OSX})
    endif()
    list(REMOVE_ITEM LDC_D ${RUNTIME_DIR}/src/ldc/eh2.d)
    list(REMOVE_ITEM DCRT_C ${RUNTIME_DC_DIR}/msvc.c)
elseif(WIN32)
    file(GLOB_RECURSE CORE_D_SYS ${RUNTIME_DIR}/src/core/sys/windows/*.d)
    list(REMOVE_ITEM LDC_D ${RUNTIME_DIR}/src/ldc/eh.d)
    list(REMOVE_ITEM DCRT_C ${RUNTIME_DC_DIR}/monitor.c)
endif()
list(APPEND CORE_D ${CORE_D_SYNC} ${CORE_D_SYS} ${CORE_D_STDC} )
list(APPEND GENERATE_DI ${CORE_D})
list(APPEND CORE_D ${LDC_D} ${RUNTIME_DIR}/src/object_.d)
file(GLOB CORE_C ${RUNTIME_DIR}/src/core/stdc/*.c)

if(PHOBOS2_DIR)
    if(BUILD_SHARED_LIBS)
        # std.net.curl depends on libcurl – when building a shared library, we
        # need to take care of that.
        find_package(CURL REQUIRED)
    endif()

    file(GLOB PHOBOS2_D ${PHOBOS2_DIR}/std/*.d)
    file(GLOB PHOBOS2_D_NET ${PHOBOS2_DIR}/std/net/*.d)
    file(GLOB_RECURSE PHOBOS2_D_INTERNAL ${PHOBOS2_DIR}/std/internal/*.d)
    file(GLOB PHOBOS2_D_C ${PHOBOS2_DIR}/std/c/*.d)
    file(GLOB PHOBOS2_ETC ${PHOBOS2_DIR}/etc/c/*.d)
    if(APPLE)
        file(GLOB PHOBOS2_D_C_SYS ${PHOBOS2_DIR}/std/c/osx/*.d)
    elseif(UNIX)
        # Install Linux headers on all non-Apple *nixes - not correct, but
        # shouldn't cause any harm either.
        file(GLOB PHOBOS2_D_C_SYS ${PHOBOS2_DIR}/std/c/linux/*.d)
    elseif(WIN32)
        file(GLOB PHOBOS2_D_C_SYS ${PHOBOS2_DIR}/std/c/windows/*.d)
    endif()
    file(GLOB ZLIB_C ${PHOBOS2_DIR}/etc/c/zlib/*.c)
    list(REMOVE_ITEM ZLIB_C
        ${PHOBOS2_DIR}/etc/c/zlib/minigzip.c
        ${PHOBOS2_DIR}/etc/c/zlib/example.c
        ${PHOBOS2_DIR}/etc/c/zlib/gzio.c
    )
    if(WIN32)
        file(GLOB PHOBOS2_D_WIN ${PHOBOS2_DIR}/std/windows/*.d)
    endif(WIN32)
    list(APPEND PHOBOS2_D
            ${PHOBOS2_D_NET}
            ${PHOBOS2_D_INTERNAL}
            ${PHOBOS2_D_WIN}
            ${PHOBOS2_D_C}
            ${PHOBOS2_D_C_SYS}
            ${PHOBOS2_ETC}
            ${PHOBOS2_DIR}/crc32.d
    )
    list(REMOVE_ITEM PHOBOS2_D
            ${PHOBOS2_DIR}/std/intrinsic.d
    )
    set(CONFIG_NAME ${LDC_EXE}_phobos)
else(PHOBOS2_DIR)
    set(CONFIG_NAME ${LDC_EXE})
endif(PHOBOS2_DIR)

# should only be necessary if run independently from ldc cmake project
if(NOT LDC_LOC)
    if(NOT LDC_EXE)
        set(LDC_EXE ldc2)
    endif(NOT LDC_EXE)

    find_program(LDC_LOC ${LDC_EXE} ${PROJECT_BINARY_DIR}/../bin DOC "path to ldc binary")
    if(NOT LDC_LOC)
        message(SEND_ERROR "ldc not found")
    endif(NOT LDC_LOC)
    set(LDC_EXE_NAME ${LDC_EXE})
endif(NOT LDC_LOC)

#
# Create configuration files.
#

if(MULTILIB)
    set(MULTILIB_ADDITIONAL_PATH         "\n        \"-L-L${CMAKE_BINARY_DIR}/lib${MULTILIB_SUFFIX}\",")
    set(MULTILIB_ADDITIONAL_INSTALL_PATH "\n        \"-L-L${CMAKE_INSTALL_PREFIX}/lib${MULTILIB_SUFFIX}\",")
endif(MULTILIB)

configure_file(${PROJECT_PARENT_DIR}/${CONFIG_NAME}.conf.in ${PROJECT_BINARY_DIR}/../bin/${LDC_EXE}.conf)
# Prepare the config files for installation in bin.
configure_file(${PROJECT_PARENT_DIR}/${LDC_EXE}_install.conf.in ${PROJECT_BINARY_DIR}/../bin/${LDC_EXE}_install.conf)
configure_file(${PROJECT_PARENT_DIR}/${LDC_EXE}.rebuild.conf.in ${PROJECT_BINARY_DIR}/../bin/${LDC_EXE}_install.rebuild.conf)

#
# Macros.
#

macro(dc INPUT_D OUTLIST_O OUTLIST_BC MOREFLAGS PATH SUFFIX)
    if ("${PATH}" STREQUAL "")
        file(RELATIVE_PATH output ${RUNTIME_DIR} ${INPUT_D})
    else ("${PATH}" STREQUAL "")
        file(RELATIVE_PATH output ${PATH} ${INPUT_D})
    endif ("${PATH}" STREQUAL "")
    get_filename_component(name ${output} NAME_WE)
    get_filename_component(path ${output} PATH)
    if ("${path}" STREQUAL "")
        set(output ${name})
    else ("${path}" STREQUAL "")
        set(output ${path}/${name})
    endif ("${path}" STREQUAL "")
    set(OUTPUT_O ${PROJECT_BINARY_DIR}/${output}${SUFFIX}${CMAKE_C_OUTPUT_EXTENSION})
    set(OUTPUT_BC ${PROJECT_BINARY_DIR}/${output}${SUFFIX}.bc)
    list(APPEND ${OUTLIST_O} ${OUTPUT_O})
    if(BUILD_BC_LIBS)
        list(APPEND ${OUTLIST_BC} ${OUTPUT_BC})
    endif()

    list(FIND GENERATE_DI "${INPUT_D}" INDEX)
    set(DI_CMD "")
    if(NOT INDEX EQUAL -1)
        string(REGEX REPLACE "src/ldc" "src/core" output ${output})
        string(REGEX REPLACE "^src/" "" di_output ${output})

        # If a hand-written .di file exists along the source in src/, just copy
        # it instead of running it through the compiler.
        if(EXISTS "${INPUT_D}i")
            configure_file("${INPUT_D}i" ${CMAKE_BINARY_DIR}/import/${di_output}.di COPYONLY)
        else()
            set(DI_CMD -Hf=${CMAKE_BINARY_DIR}/import/${di_output}.di)
        endif()

        list(REMOVE_AT GENERATE_DI ${INDEX})
    endif()

    # Compile
    if(BUILD_BC_LIBS)
        set(OUTPUT ${OUTPUT_O} ${OUTPUT_BC})
        set(DC_FLAGS --output-o --output-bc)
    else()
        set(OUTPUT ${OUTPUT_O})
        set(DC_FLAGS --output-o)
    endif()
    add_custom_command(
        OUTPUT
            ${OUTPUT}
        COMMAND ${LDC_LOC} ${DC_FLAGS} -c -I${RUNTIME_INCLUDE} -I${RUNTIME_GC_DIR} ${INPUT_D} -of${OUTPUT_O} ${DI_CMD} ${D_FLAGS} ${MOREFLAGS}
        WORKING_DIRECTORY ${PROJECT_PARENT_DIR}
        DEPENDS ${LDC_LOC}
            ${INPUT_D}
            ${LDC_IMPORTS}
            ${PROJECT_BINARY_DIR}/../bin/${LDC_EXE}.conf
    )
endmacro(dc)

macro(build_runtime d_flags c_flags ld_flags lib_suffix path_suffix)
    set(output_path ${CMAKE_BINARY_DIR}/lib${path_suffix})

    set(target_suffix "")
    if(NOT "${lib_suffix}" STREQUAL "")
        set(target_suffix "_${lib_suffix}")
    endif(NOT "${lib_suffix}" STREQUAL "")
    if(NOT "${path_suffix}" STREQUAL "")
        set(target_suffix "${target_suffix}_${path_suffix}")
    endif(NOT "${path_suffix}" STREQUAL "")

    set(CORE_O "")
    set(CORE_BC "")
    foreach(f ${CORE_D})
        dc(${f} CORE_O CORE_BC "${d_flags};-disable-invariants" "" "${target_suffix}")
    endforeach(f)

    set(GC_O "")
    set(GC_BC "")
    foreach(f ${GC_D})
        dc(${f} GC_O GC_BC "${d_flags};-disable-invariants" "" "${target_suffix}")
    endforeach(f)

    set(DCRT_O "")
    set(DCRT_BC "")
    foreach(f ${DCRT_D})
        dc(${f} DCRT_O DCRT_BC "${d_flags}" "" "${target_suffix}")
    endforeach(f)

    if(EXISTS ${RUNTIME_DIR})
        if(BUILD_SINGLE_LIB)
            add_library(${RUNTIME_AIO}${target_suffix}
                        ${D_LIBRARY_TYPE}
                        ${CORE_O}
                        ${CORE_C}
                        ${GC_O}
                        ${DCRT_O}
                        ${DCRT_C}
            )
            set(LIBS ${RUNTIME_AIO}${target_suffix})
            set_target_properties(${RUNTIME_AIO}${target_suffix} PROPERTIES OUTPUT_NAME ${RUNTIME_AIO}${lib_suffix})
        else(BUILD_SINGLE_LIB)
            add_library(${RUNTIME_CC}${target_suffix} ${D_LIBRARY_TYPE} ${CORE_O} ${CORE_C})
            add_library(${RUNTIME_GC}${target_suffix} ${D_LIBRARY_TYPE} ${GC_O})
            add_library(${RUNTIME_DC}${target_suffix} ${D_LIBRARY_TYPE} ${DCRT_O} ${DCRT_C})
            set_target_properties(${RUNTIME_CC}${target_suffix} PROPERTIES OUTPUT_NAME ${RUNTIME_CC}${lib_suffix})
            set_target_properties(${RUNTIME_GC}${target_suffix} PROPERTIES OUTPUT_NAME ${RUNTIME_GC}${lib_suffix})
            set_target_properties(${RUNTIME_DC}${target_suffix} PROPERTIES OUTPUT_NAME ${RUNTIME_DC}${lib_suffix})
            set(LIBS
                ${RUNTIME_CC}${lib_suffix}
                ${RUNTIME_GC}${lib_suffix}
                ${RUNTIME_DC}${lib_suffix}
            )
        endif(BUILD_SINGLE_LIB)
    endif()

    if(BUILD_BC_LIBS)
        find_program(LLVM_AR_EXE llvm-ar ${LLVM_INSTDIR}/bin DOC "path to llvm-ar tool")
        if(NOT LLVM_AR_EXE)
            message(SEND_ERROR "llvm-ar not found")
        endif(NOT LLVM_AR_EXE)

        add_library(${RUNTIME_CC}-c ${CORE_C})
        add_library(${RUNTIME_DC}-c ${DCRT_C})
        list(APPEND LIBS
            ${RUNTIME_CC}-c
            ${RUNTIME_DC}-c
        )
        add_custom_command(
            OUTPUT bclibs
            COMMAND ${LLVM_AR_EXE} rs lib${RUNTIME_CC}-bc.a ${CORE_BC}
            COMMAND ${LLVM_AR_EXE} rs lib${RUNTIME_GC}-bc.a ${GC_BC}
            # cannot parse genobj.bc if built with -g
            # COMMAND ${LLVM_AR_EXE} rs lib${RUNTIME_DC}-bc.a ${DCRT_BC}
            WORKING_DIRECTORY ${output_path}
            DEPENDS
                ${CORE_BC}
                ${GC_BC}
                ${DCRT_BC}
                ${LDC_IMPORTS}
        )
        set(BCLIBS bclibs)
    endif(BUILD_BC_LIBS)

    set_target_properties(
        ${LIBS} PROPERTIES
        VERSION                     ${DMDFE_VERSION}
        SOVERSION                   ${DMDFE_PATCH_VERSION}
        LINKER_LANGUAGE             C
        ARCHIVE_OUTPUT_DIRECTORY    ${output_path}
        LIBRARY_OUTPUT_DIRECTORY    ${output_path}
        RUNTIME_OUTPUT_DIRECTORY    ${output_path}
        COMPILE_FLAGS               "${c_flags}"
        LINK_FLAGS                  "${ld_flags}"
    )
    install(TARGETS ${LIBS} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib${path_suffix})

    # BCLIBS is empty if BUILD_BC_LIBS is not selected
    add_custom_target(runtime${target_suffix} DEPENDS ${LIBS} ${BCLIBS})

    if(PHOBOS2_DIR)
        set(PHOBOS2_O "")
        set(PHOBOS2_BC "")
        foreach(f ${PHOBOS2_D})
             dc(${f} PHOBOS2_O PHOBOS2_BC "${d_flags};-I${PHOBOS2_DIR}" ${PHOBOS2_DIR} "${target_suffix}")
        endforeach(f)

        add_library(phobos-ldc${target_suffix} ${D_LIBRARY_TYPE}
            ${ZLIB_C}
            ${PHOBOS2_O}
            ${CORE_O}
            ${CORE_C}
            ${GC_O}
            ${DCRT_O}
            ${DCRT_C}
        )
        add_dependencies(phobos-ldc${target_suffix} runtime)
        set_target_properties(
            phobos-ldc${target_suffix} PROPERTIES
            VERSION                     ${DMDFE_VERSION}
            SOVERSION                   ${DMDFE_PATCH_VERSION}
            OUTPUT_NAME                 phobos-ldc${lib_suffix}
            LINKER_LANGUAGE             C
            ARCHIVE_OUTPUT_DIRECTORY    ${output_path}
            LIBRARY_OUTPUT_DIRECTORY    ${output_path}
            RUNTIME_OUTPUT_DIRECTORY    ${output_path}
            COMPILE_FLAGS               "${c_flags}"
            LINK_FLAGS                  "${ld_flags}"
        )
        # Phobos now uses curl
        if(BUILD_SHARED_LIBS)
            target_link_libraries(phobos-ldc${target_suffix} "curl")
        endif()
        install(TARGETS phobos-ldc${target_suffix} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib${path_suffix})
        add_dependencies(phobos2 DEPENDS phobos-ldc${target_suffix})
    endif(PHOBOS2_DIR)
endmacro(build_runtime d_flags c_flags ld_flags lib_suffix path_suffix)

#
# Set up build targets.
#

if(PHOBOS2_DIR)
    add_custom_target(phobos2)
endif(PHOBOS2_DIR)
build_runtime("" "" "${LD_FLAGS}" "" "${LIB_SUFFIX}")
set(GENERATE_DI "")
if(MULTILIB)
    build_runtime("-m${MULTILIB_SUFFIX}" "-m${MULTILIB_SUFFIX}" "-m${MULTILIB_SUFFIX} ${LD_FLAGS}" "" "${MULTILIB_SUFFIX}")
endif(MULTILIB)

#
# Install target.
#

install(DIRECTORY     ${CMAKE_BINARY_DIR}/import/core                  DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.di")
if(PHOBOS2_DIR)
    install(DIRECTORY ${PHOBOS2_DIR}/std                               DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.d")
    install(DIRECTORY ${PHOBOS2_DIR}/etc                               DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.d")
    install(FILES     ${PHOBOS2_DIR}/crc32.d                           DESTINATION ${INCLUDE_INSTALL_DIR})
endif(PHOBOS2_DIR)
install(FILES         ${RUNTIME_DIR}/src/object.di                  DESTINATION ${INCLUDE_INSTALL_DIR}/ldc)
install(DIRECTORY     ${RUNTIME_DIR}/import/ldc                        DESTINATION ${INCLUDE_INSTALL_DIR} FILES_MATCHING PATTERN "*.di")
