# vi: set ft=cmake:

#
# CMakeGlobals.txt
#
# Copyright (C) 2022 by RStudio, PBC
#
# Unless you have received this program directly from RStudio pursuant
# to the terms of a commercial license agreement with RStudio, then
# this program is licensed to you under the terms of version 3 of the
# GNU Affero General Public License. This program is distributed WITHOUT
# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
#


# include guard
if(RSTUDIO_CMAKE_GLOBALS_INCLUDED)
   return()
endif()
set(RSTUDIO_CMAKE_GLOBALS_INCLUDED YES)

# helper for detecting Linux
if(UNIX AND NOT APPLE)
   set(LINUX TRUE)
endif()

# version info
if ("$ENV{RSTUDIO_VERSION_MAJOR}" STREQUAL "")
  string(TIMESTAMP CPACK_PACKAGE_VERSION_MAJOR "%Y")
  set(RSTUDIO_UNVERSIONED_BUILD TRUE)
else()
  set(CPACK_PACKAGE_VERSION_MAJOR $ENV{RSTUDIO_VERSION_MAJOR})
endif()
if ("$ENV{RSTUDIO_VERSION_MINOR}" STREQUAL "")
   string(TIMESTAMP CPACK_PACKAGE_VERSION_MINOR "%m")
else()
  set(CPACK_PACKAGE_VERSION_MINOR $ENV{RSTUDIO_VERSION_MINOR})
endif()
if ("$ENV{RSTUDIO_VERSION_PATCH}" STREQUAL "")
  set(CPACK_PACKAGE_VERSION_PATCH "999")
else()
  set(CPACK_PACKAGE_VERSION_PATCH $ENV{RSTUDIO_VERSION_PATCH})
endif()
if ("$ENV{RSTUDIO_VERSION_PATCH}" STREQUAL "")
  set(CPACK_PACKAGE_VERSION_SUFFIX "-dev+999")
else()
  set(CPACK_PACKAGE_VERSION_SUFFIX $ENV{RSTUDIO_VERSION_SUFFIX})
endif()
set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${CPACK_PACKAGE_VERSION_SUFFIX}")

string(TIMESTAMP CPACK_COPYRIGHT_YEAR "%Y")
string(TIMESTAMP CPACK_BUILD_DATE "%Y-%m-%d")

# create numeric format for major version (needed when version must be strictly numeric)
set(CPACK_PACKAGE_VERSION_MAJOR_NUMERIC "${CPACK_PACKAGE_VERSION_MAJOR}${CPACK_PACKAGE_VERSION_MINOR}")
message(STATUS "Building RStudio ${CPACK_PACKAGE_VERSION}")

# detect pro builds
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/upstream")
   set(RSTUDIO_PRO_BUILD 1)
else()
   set(RSTUDIO_PRO_BUILD 0)
endif()

# detect architecture
if(UNIX)

   execute_process(
      COMMAND uname -m
      OUTPUT_VARIABLE UNAME_M
      OUTPUT_STRIP_TRAILING_WHITESPACE)
   message(STATUS "Machine architecture: ${UNAME_M}")

endif()

# default to debug builds
if(NOT CMAKE_BUILD_TYPE)

   foreach(BUILD_TYPE Debug Release RelWithDebInfo MinSizeRel)
      if(CMAKE_BINARY_DIR MATCHES "${BUILD_TYPE}")
         set(CMAKE_BUILD_TYPE "${BUILD_TYPE}")
         break()
      endif()
   endforeach()

   if(NOT CMAKE_BUILD_TYPE)
      set(CMAKE_BUILD_TYPE Debug)
   endif()


endif()

message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}")

# enable testing on all builds unless explicitly disabled
if(NOT RSTUDIO_UNIT_TESTS_DISABLED)
  set(RSTUDIO_UNIT_TESTS_ENABLED true)
  add_definitions(-DRSTUDIO_UNIT_TESTS_ENABLED)
endif()

# platform specific default for targets
if(NOT RSTUDIO_TARGET)

   # for macOS pro builds, default to desktop as otherwise
   # we will try (and fail) to build Linux-only launcher pieces
   if(APPLE AND RSTUDIO_PRO_BUILD)
      set(RSTUDIO_TARGET "Desktop")
   else()
      set(RSTUDIO_TARGET "Development")
   endif()

   # if no target was set, assume this is a development build
   set(RSTUDIO_DEVELOPMENT TRUE)

endif()

# set desktop and server build flags
if(NOT DEFINED RSTUDIO_SERVER)
   if(NOT WIN32 AND (RSTUDIO_TARGET STREQUAL "Development" OR RSTUDIO_TARGET STREQUAL "Server"))
      set(RSTUDIO_SERVER TRUE)
   endif()
endif()

if(NOT DEFINED RSTUDIO_DESKTOP)
   if(RSTUDIO_TARGET STREQUAL "Electron")
      set(RSTUDIO_ELECTRON TRUE)
   elseif(LINUX AND UNAME_M STREQUAL aarch64)
      message(STATUS "Desktop builds not currently supported on aarch64; disabling RSTUDIO_DESKTOP")
      set(RSTUDIO_DESKTOP FALSE)
   elseif(APPLE AND UNAME_M STREQUAL arm64)
      message(STATUS "Desktop builds not currently supported on arm64; disabling RSTUDIO_DESKTOP")
      set(RSTUDIO_DESKTOP FALSE)
   elseif(RSTUDIO_TARGET STREQUAL "Development" OR RSTUDIO_TARGET STREQUAL "Desktop")
      set(RSTUDIO_DESKTOP TRUE)
   endif()
endif()

# make sure RSTUDIO_ELECTRON is defined so it can be used in configure_file
if(NOT DEFINED RSTUDIO_ELECTRON)
   set(RSTUDIO_ELECTRON FALSE)
endif()

# override if requested
if(RSTUDIO_NO_DESKTOP)
   set(RSTUDIO_DESKTOP FALSE)
endif()

# set session32 if specified
if(WIN32)
   if(RSTUDIO_TARGET STREQUAL "SessionWin32")
      set(RSTUDIO_SESSION_WIN32 TRUE)
      add_definitions(-D_X86_)
   else()
      add_definitions(-D_AMD64_)
   endif()
endif()

# record git revision hash (cache it since we don't use this in development
# mode and we don't want it to force rebuilds there)
if(NOT RSTUDIO_SESSION_WIN32 AND NOT RSTUDIO_GIT_REVISION_HASH)
   find_program(GIT_EXECUTABLE git)
   if(GIT_EXECUTABLE)
      execute_process(
         COMMAND git rev-parse HEAD
         WORKING_DIRECTORY "${ROOT_SRC_DIR}"
         OUTPUT_VARIABLE RSTUDIO_GIT_REVISION_HASH
         OUTPUT_STRIP_TRAILING_WHITESPACE)
      SET(RSTUDIO_GIT_REVISION_HASH "${RSTUDIO_GIT_REVISION_HASH}" CACHE STRING "Git Revision Hash")
   endif()
endif()

# record these from Jenkins if available
if("$ENV{GIT_COMMIT}" STREQUAL "")
  string(LENGTH "${RSTUDIO_GIT_REVISION_HASH}" HASH_LENGTH)
  if(HASH_LENGTH EQUAL 40) 
    # use the cached revision hash if we have one
    set(RSTUDIO_GIT_COMMIT "${RSTUDIO_GIT_REVISION_HASH}")
  else()
    # make one up if we don't
    set(RSTUDIO_GIT_COMMIT "99999999999999999999999999999999")
  endif()
else()
  # use the git commit from Jenkins
  set(RSTUDIO_GIT_COMMIT $ENV{GIT_COMMIT})
endif()
if("$ENV{BUILD_ID}" STREQUAL "")
  # no known build ID
  set(RSTUDIO_BUILD_ID "unknown")
else()
  # use build ID from Jenkins
  set(RSTUDIO_BUILD_ID $ENV{BUILD_ID})
endif()
if("$ENV{PACKAGE_OS}" STREQUAL "")
  # no known package OS
  set(RSTUDIO_PACKAGE_OS "Unknown OS")
else()
  set(RSTUDIO_PACKAGE_OS $ENV{PACKAGE_OS})
endif()

# required R version
set(RSTUDIO_R_MAJOR_VERSION_REQUIRED 3)
set(RSTUDIO_R_MINOR_VERSION_REQUIRED 0)
set(RSTUDIO_R_PATCH_VERSION_REQUIRED 1)

# allow opting out of version checking (for building on older distros)
if(NOT DEFINED RSTUDIO_VERIFY_R_VERSION)
   if(RSTUDIO_PACKAGE_BUILD)
      set(RSTUDIO_VERIFY_R_VERSION FALSE)
   else()
      set(RSTUDIO_VERIFY_R_VERSION TRUE)
   endif()
endif()

# pandoc version
set(PANDOC_VERSION "2.16.2" CACHE INTERNAL "Pandoc version")

# quarto support
if(LINUX AND UNAME_M STREQUAL aarch64)
   # disabled on linux aarch64
   message(STATUS "quarto does not yet support aarch64 builds of Linux; disabling quarto")
   set(QUARTO_ENABLED FALSE CACHE INTERNAL "")
else()
   # enable by default
   set(QUARTO_ENABLED TRUE CACHE INTERNAL "")
   add_definitions(-DQUARTO_ENABLED)
endif()

# install freedesktop integration files if we are installing into /usr
if(NOT DEFINED RSTUDIO_INSTALL_FREEDESKTOP)
   if(${CMAKE_INSTALL_PREFIX} MATCHES "/usr/.*")
      set(RSTUDIO_INSTALL_WITH_PRIV TRUE)
   else()
      set(RSTUDIO_INSTALL_WITH_PRIV FALSE)
   endif()
   if(RSTUDIO_INSTALL_WITH_PRIV AND UNIX AND NOT APPLE)
      set(RSTUDIO_INSTALL_FREEDESKTOP TRUE)
   else()
      set(RSTUDIO_INSTALL_FREEDESKTOP FALSE)
   endif()
endif()

# cmake modules (compute path relative to this file)
get_filename_component(ROOT_SRC_DIR ${CMAKE_CURRENT_LIST_FILE} PATH)
set(CMAKE_MODULE_PATH "${ROOT_SRC_DIR}/cmake/modules/")

# dependencies
if(WIN32)
   set(RSTUDIO_WINDOWS_DEPENDENCIES_DIR "${ROOT_SRC_DIR}/dependencies/windows")
else()
   # look for system-wide (global) dependencies folder
   if(EXISTS "/opt/rstudio-tools/dependencies")
      set(RSTUDIO_DEPENDENCIES_DIR "/opt/rstudio-tools/dependencies")
   endif()
endif()

# look for dependencies in the source folder if not installed globally
if(NOT EXISTS "${RSTUDIO_DEPENDENCIES_DIR}")
   set(RSTUDIO_DEPENDENCIES_DIR "${ROOT_SRC_DIR}/dependencies")
endif()

# tools
if(NOT DEFINED RSTUDIO_TOOLS_ROOT)
   if(DEFINED ENV{RSTUDIO_TOOLS_ROOT})
      set(RSTUDIO_TOOLS_ROOT $ENV{RSTUDIO_TOOLS_ROOT})
   elseif(WIN32)
      set(RSTUDIO_TOOLS_ROOT "${RSTUDIO_DEPENDENCIES_DIR}")
   elseif(APPLE)
      find_path(RSTUDIO_TOOLS_ROOT
         NAMES boost
         HINTS
            "$ENV{HOME}/opt/rstudio-tools/${UNAME_M}"
            "/opt/rstudio-tools/${UNAME_M}")
   else()
      set(RSTUDIO_TOOLS_ROOT "/opt/rstudio-tools/${UNAME_M}")
   endif()
endif()

message(STATUS "Using RStudio tools root: ${RSTUDIO_TOOLS_ROOT}")

# special install directories for apple desktop
if (APPLE)
   if (RSTUDIO_ELECTRON)
      set(RSTUDIO_INSTALL_BIN        RStudio.app/Contents/Resources/app/bin)
      set(RSTUDIO_INSTALL_SUPPORTING RStudio.app/Contents/Resources/app)
   else()
      set(RSTUDIO_INSTALL_BIN        RStudio.app/Contents/MacOS)
      set(RSTUDIO_INSTALL_SUPPORTING RStudio.app/Contents/Resources)
   endif()
else()
   if (RSTUDIO_SESSION_WIN32)
     set(RSTUDIO_INSTALL_BIN x86)
   else()
     set(RSTUDIO_INSTALL_BIN bin)
   endif()
   set(RSTUDIO_INSTALL_SUPPORTING .)
endif()

# if the install prefix is /usr/local then tweak as appropriate
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
   if(APPLE)
      set(CMAKE_INSTALL_PREFIX "/Applications")
   elseif(UNIX)
      if(RSTUDIO_DESKTOP)
         set(CMAKE_INSTALL_PREFIX "/usr/local/lib/rstudio")
      else()
         set(CMAKE_INSTALL_PREFIX "/usr/local/lib/rstudio-server")
      endif()
   endif()
endif()

# detect lsb release
if (UNIX AND NOT APPLE)
   if(NOT RSTUDIO_LSB_RELEASE)
      execute_process(COMMAND /usr/bin/lsb_release "--id" "--short"
                      OUTPUT_VARIABLE RSTUDIO_LSB_RELEASE)
      if (RSTUDIO_LSB_RELEASE)
         string(STRIP ${RSTUDIO_LSB_RELEASE} RSTUDIO_LSB_RELEASE)
         string(TOLOWER  ${RSTUDIO_LSB_RELEASE} RSTUDIO_LSB_RELEASE)
         set(RSTUDIO_LSB_RELEASE ${RSTUDIO_LSB_RELEASE} CACHE STRING "LSB release")
         message(STATUS "LSB release: ${RSTUDIO_LSB_RELEASE}")
      endif()
   endif()
endif()

# make sure the CMAKE_INSTALL_PREFIX uses a cmake style path
file(TO_CMAKE_PATH "${CMAKE_INSTALL_PREFIX}" CMAKE_INSTALL_PREFIX)

# clear embedded packages variable (always do this first so that CMake caches
# are properly flushed / reset whenever we embed an unembed packages)
set(RSTUDIO_EMBEDDED_PACKAGES "" CACHE INTERNAL "Embedded R Packages")

# embedded packages
# set(RSTUDIO_EMBEDDED_PACKAGES rmarkdown rsconnect CACHE INTERNAL "Embedded R Packages")

# include utilities
include(RStudioCMakeUtils)

# define custom function to strip unnecessary path parts from binary filename
function(define_source_file_names targetname)
    get_target_property(SOURCE_FILES "${targetname}" SOURCES)
    foreach(SOURCE_FILE ${SOURCE_FILES})
        # Get source file's current list of compile definitions.
        get_property(SOURCE_DEFS SOURCE "${SOURCE_FILE}" PROPERTY COMPILE_DEFINITIONS)

        # Add the stripped filename to the list
        # Provided source paths can either be full paths or simply filenames
        string(FIND "${SOURCE_FILE}" "/src/cpp" SOURCE_INDEX)
        if (SOURCE_INDEX GREATER -1)
           set(FULL_SOURCE_NAME "${SOURCE_FILE}")
           MATH(EXPR SOURCE_INDEX "${SOURCE_INDEX}+1")
        else()
           string(FIND "${SOURCE_FILE}" "${CMAKE_BINARY_DIR}" SOURCE_INDEX)
           if (SOURCE_INDEX GREATER -1)
              set(FULL_SOURCE_NAME "${SOURCE_FILE}")
              string(LENGTH "${CMAKE_BINARY_DIR}" BINARY_DIR_LENGTH)
              MATH(EXPR SOURCE_INDEX "${SOURCE_INDEX}+${BINARY_DIR_LENGTH}+1")
           else()
              set(FULL_SOURCE_NAME "${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE}")
              string(FIND "${FULL_SOURCE_NAME}" "/src/cpp" SOURCE_INDEX)
              MATH(EXPR SOURCE_INDEX "${SOURCE_INDEX}+1")
           endif()
        endif()

        string(SUBSTRING "${FULL_SOURCE_NAME}" "${SOURCE_INDEX}" -1 STRIPPED_SOURCE)
        list(APPEND SOURCE_DEFS "STRIPPED_FILENAME=\"${STRIPPED_SOURCE}\"")

        # Set the updated compile definitions on the source file.
        set_property(
            SOURCE "${SOURCE_FILE}"
            PROPERTY COMPILE_DEFINITIONS ${SOURCE_DEFS})
    endforeach()
endfunction()

# define custom installation macro to strip symbols from the binary
macro(add_stripped_executable _target)
   add_executable(${_target} ${ARGN})

   # only strip debug info in release package builds
   if(RSTUDIO_PACKAGE_BUILD AND CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo)
      if(UNIX AND NOT APPLE)
         # strip debug info
         add_custom_command(TARGET ${_target} POST_BUILD
                            COMMAND objcopy --only-keep-debug ${_target} ${_target}.debug
                            COMMAND objcopy --strip-debug --strip-unneeded ${_target}
                            COMMAND objcopy --add-gnu-debuglink=${_target}.debug ${_target}
                            COMMENT "Stripping ${_target}")
      elseif(APPLE)
         if(${ARGV1} STREQUAL "MACOSX_BUNDLE")
            set(STRIP_TARGET "${_target}.app/contents/MacOS/${_target}")
         else()
            set(STRIP_TARGET "${_target}")
         endif()
         add_custom_command(TARGET ${_target} POST_BUILD
                            COMMAND dsymutil -o ./${_target}.dSYM ${STRIP_TARGET}
                            COMMAND strip ${STRIP_TARGET}
                            COMMENT "Stripping ${STRIP_TARGET}")
      endif()
   endif()
   define_source_file_names("${_target}")
endmacro(add_stripped_executable)

if(APPLE)

   # set Homebrew prefix directory
   if(NOT DEFINED HOMEBREW_PREFIX)

      if(UNAME_M STREQUAL arm64)
         set(HOMEBREW_PREFIX_FALLBACK /opt/homebrew)
      else()
         set(HOMEBREW_PREFIX_FALLBACK /usr/local)
      endif()
      
      find_path(HOMEBREW_PREFIX
         NAMES bin/brew
         HINTS
            "$ENV{HOME}/homebrew/${UNAME_M}"
            "${HOMEBREW_PREFIX_FALLBACK}")

      message(STATUS "Using Homebrew: ${HOMEBREW_PREFIX}")

   endif()

   # help Boost find icu4c
   if(RSTUDIO_USE_SYSTEM_BOOST)
      link_directories(${HOMEBREW_PREFIX}/opt/icu4c/lib)
   endif()

   # set OPENSSL_ROOT_DIR if unset
   if(NOT DEFINED OPENSSL_ROOT_DIR)

      # Homebrew now defaults to OpenSSL 3, but some older formulae that
      # we use require OpenSSL 1.1. Try to force CMake to use that OpenSSL.
      file(GLOB OPENSSL_ROOT_CANDIDATE "${HOMEBREW_PREFIX}/Cellar/openssl@1.1/*")
      if(EXISTS "${OPENSSL_ROOT_CANDIDATE}")
         set(OPENSSL_ROOT_DIR "${OPENSSL_ROOT_CANDIDATE}" CACHE INTERNAL "")
      else()
         set(OPENSSL_ROOT_DIR "${HOMEBREW_PREFIX}/opt/openssl" CACHE INTERNAL "")
      endif()

   endif()

endif()

