Skip to content

Commit

Permalink
Import cmake improvement by Daan De Meyer
Browse files Browse the repository at this point in the history
  • Loading branch information
MaskRay committed Apr 7, 2018
1 parent 348240b commit 98a4ef5
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 181 deletions.
88 changes: 58 additions & 30 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ project(ccls LANGUAGES CXX)

list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
include(DefaultCMakeBuildType)
include(DownloadAndExtractLLVM)

set(CLANG_VERSION 6.0.0 CACHE STRING "Downloaded Clang version (6.0.0)")
option(SYSTEM_CLANG "Use system Clang instead of downloading Clang" OFF)
# Required libclang version
set(LIBCLANG_VERSION 6.0.0 CACHE STRING "libclang version")
set(LIBCLANG_DOWNLOAD_LOCATION ${CMAKE_BINARY_DIR}
CACHE STRING "Downloaded libclang location")
option(SYSTEM_LIBCLANG "Use system installation of libclang instead of \
downloading libclang" OFF)
option(ASAN "Compile with address sanitizers" OFF)
option(CLANG_CXX "Build with Clang C++ api required by some ccls \
features (warning: not available in LLVM Windows downloads)" OFF)

# Sources for the executable are specified at end of CMakeLists.txt
add_executable(ccls "")
Expand Down Expand Up @@ -63,40 +64,40 @@ else()
$<$<CONFIG:Debug>:-fno-limit-debug-info>)
endif()

if(CLANG_CXX)
# -Wno-comment: include/clang/Format/Format.h error: multi-line comment
# -fno-rtti: # Without -fno-rtti, some Clang C++ functions may report
# `undefined references to typeinfo`
target_compile_options(ccls PRIVATE -Wno-comment -fno-rtti)
endif()

if(ASAN)
target_compile_options(ccls PRIVATE -fsanitize=address,undefined)
# target_link_libraries also takes linker flags
target_link_libraries(ccls PRIVATE -fsanitize=address,undefined)
endif()
endif()

### Download Clang if required
### Download libclang if required

if(NOT SYSTEM_CLANG)
download_and_extract_llvm(${CLANG_VERSION})
if(NOT SYSTEM_LIBCLANG)
message(STATUS "Using downloaded libclang")

include(DownloadAndExtractClang)
download_and_extract_clang(${LIBCLANG_VERSION} ${LIBCLANG_DOWNLOAD_LOCATION})
# Used by FindClang
set(CLANG_ROOT ${DOWNLOADED_CLANG_DIR})
else()
message(STATUS "Using system libclang")
endif()

### Libraries

# See cmake/FindClang.cmake
find_package(Clang REQUIRED)
find_package(Clang ${CLANG_VERSION} REQUIRED)
target_link_libraries(ccls PRIVATE Clang::Clang)

# Enable threading support
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(ccls PRIVATE Threads::Threads)

if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
if(${CMAKE_SYSTEM_NAME} STREQUAL Darwin)
target_link_libraries(ccls PRIVATE -lc++experimental)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows)
else()
target_link_libraries(ccls PRIVATE -lstdc++fs)
endif()
Expand All @@ -117,12 +118,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows)
target_link_libraries(ccls PRIVATE Psapi)
endif()

if(CLANG_CXX)
# Clang C++ api uses ncurses
find_package(Curses REQUIRED)
target_link_libraries(ccls PRIVATE ${CURSES_LIBRARIES})
endif()

### Definitions

target_compile_definitions(ccls PRIVATE
Expand All @@ -131,10 +126,6 @@ target_compile_definitions(ccls PRIVATE
LOGURU_THREADNAME_WIDTH=13
DEFAULT_RESOURCE_DIRECTORY="${Clang_RESOURCE_DIR}")

if(CLANG_CXX)
target_compile_definitions(ccls PRIVATE USE_CLANG_CXX=1 LOGURU_RTTI=0)
endif()

### Includes

target_include_directories(ccls PRIVATE
Expand All @@ -150,9 +141,8 @@ target_include_directories(ccls PRIVATE

install(TARGETS ccls RUNTIME DESTINATION bin)

# We don't need to install libclang on Windows if we are using downloaded LLVM
# since libclang is distributed as a static library on Windows
if(NOT SYSTEM_CLANG AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL Windows)
# TODO: install libclang.dll on Windows as well
if(NOT SYSTEM_LIBCLANG AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL Windows)

if(${CMAKE_SYSTEM_NAME} MATCHES Linux|FreeBSD)
set_property(TARGET ccls APPEND PROPERTY
Expand All @@ -167,6 +157,44 @@ if(NOT SYSTEM_CLANG AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL Windows)
install(FILES ${LIBCLANG_PLUS_SYMLINKS} DESTINATION lib)
endif()

# Allow running from build Windows by copying libclang.dll to build directory
if(NOT SYSTEM_LIBCLANG AND ${CMAKE_SYSTEM_NAME} STREQUAL Windows)
add_custom_command(TARGET ccls
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${DOWNLOADED_CLANG_DIR}/bin/libclang.dll
$<TARGET_FILE_DIR:ccls>
COMMENT "Copying libclang.dll to build directory ...")
endif()

### Tools

# We use glob here since source files are already manually added with
# target_sources further down
file(GLOB SOURCES src/*.cc src/*.h src/serializers/*.cc src/serializers/*.h
src/messages/*.h src/messages/*.cc)

if(Clang_FORMAT AND ${Clang_VERSION} STREQUAL 6.0.0)
add_custom_target(format
COMMAND ${Clang_FORMAT} -i ${SOURCES}
# .clang-format is located in the ccls root project dir
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running clang-format ...")
else()
# Set error message depending on which condition was false
if (NOT Clang_FORMAT)
set(Clang_FORMAT_ERROR "Error: clang-format executable not found")
elseif(NOT ${Clang_VERSION} STREQUAL 6.0.0)
set(Clang_FORMAT_ERROR "Error: clang-format version does not match \
6.0.0. Due to differences in clang-format output between versions we only \
support clang-format 6.0.0")
endif()

add_custom_target(format
COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold
${Clang_FORMAT_ERROR})
endif()

### Sources

target_sources(ccls PRIVATE third_party/siphash.cc)
Expand Down
55 changes: 28 additions & 27 deletions cmake/DownloadAndExtract7zip.cmake
Original file line number Diff line number Diff line change
@@ -1,51 +1,52 @@
# Downloads and extracts the 7-Zip MSI installer from https://www.7-zip.org/.
#
# Returns the extracted 7-Zip directory in DOWNLOADED_7ZIP_DIR
#
# Downloads 7-Zip to extract LLVM if it isn't available in the PATH
function(download_and_extract_7zip)
function(download_and_extract_7zip 7ZIP_DOWNLOAD_LOCATION)

set(7ZIP_VERSION 1801)
set(7ZIP_EXT .msi)
set(7ZIP_NAME 7z${7ZIP_VERSION}-x64)
set(7ZIP_FULL_NAME ${7ZIP_NAME}${7ZIP_EXT})

set(7ZIP_FILE ${CMAKE_BINARY_DIR}/${7ZIP_FULL_NAME})
set(7ZIP_EXTRACT_DIR ${CMAKE_BINARY_DIR}/${7ZIP_NAME})
set(7ZIP_FILE ${7ZIP_DOWNLOAD_LOCATION}/${7ZIP_FULL_NAME})
set(7ZIP_EXTRACT_DIR ${7ZIP_DOWNLOAD_LOCATION}/${7ZIP_NAME})
set(7ZIP_URL https://www.7-zip.org/a/${7ZIP_FULL_NAME})

# msiexec requires Windows path separators (\)
file(TO_NATIVE_PATH ${7ZIP_FILE} 7ZIP_FILE)
file(TO_NATIVE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR)

if(NOT EXISTS ${7ZIP_FILE})
message(STATUS "Downloading 7-Zip ${7ZIP_VERSION} (${7ZIP_URL}) ...")
file(DOWNLOAD ${7ZIP_URL} ${7ZIP_FILE})
# Exit if 7-Zip is already downloaded and extracted
find_program(7ZIP_EXECUTABLE 7z NO_DEFAULT_PATH
PATHS ${7ZIP_EXTRACT_DIR}/Files/7-Zip)
if(7ZIP_EXECUTABLE)
message(STATUS "7-Zip already downloaded")
return()
endif()

if(NOT EXISTS ${7ZIP_EXTRACT_DIR})
message(STATUS "Downloading 7-Zip ${7ZIP_VERSION} (${7ZIP_URL}) ...")
file(DOWNLOAD ${7ZIP_URL} ${7ZIP_FILE})

find_program(MSIEXEC_EXECUTABLE msiexec)
if(NOT MSIEXEC_EXECUTABLE)
message(FATAL_ERROR "Unable to find msiexec (required to extract 7-Zip msi \
find_program(MSIEXEC_EXECUTABLE msiexec)
if(NOT MSIEXEC_EXECUTABLE)
message(FATAL_ERROR "Unable to find msiexec (required to extract 7-Zip msi \
installer). Install 7-Zip yourself and make sure it is available in the path")
endif()
endif()

message(STATUS "Extracting downloaded 7-Zip ...")
message(STATUS "Extracting downloaded 7-Zip ...")

# msiexec with /a option allows extraction of msi installers without requiring
# admin privileges. We use this to extract the 7-Zip installer without
# requiring any actions from the user
execute_process(COMMAND ${MSIEXEC_EXECUTABLE} /a ${7ZIP_FILE} /qn
TARGETDIR=${7ZIP_EXTRACT_DIR}
OUTPUT_QUIET)
endif()
# msiexec requires Windows path separators (\)
file(TO_NATIVE_PATH ${7ZIP_FILE} 7ZIP_FILE)
file(TO_NATIVE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR)

# msiexec with /a option allows extraction of msi installers without requiring
# admin privileges. We use this to extract the 7-Zip installer without
# requiring any actions from the user
execute_process(COMMAND ${MSIEXEC_EXECUTABLE} /a ${7ZIP_FILE} /qn
TARGETDIR=${7ZIP_EXTRACT_DIR}
WORKING_DIRECTORY ${7ZIP_DOWNLOAD_LOCATION}
OUTPUT_QUIET)

# Convert back to CMake separators (/) before returning
file(TO_CMAKE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR)

# Actual directory is nested inside the extract directory. We return the nested
# directory instead of the extract directory
# Actual 7-Zip directory is nested inside the extract directory.
set(DOWNLOADED_7ZIP_DIR ${7ZIP_EXTRACT_DIR}/Files/7-Zip PARENT_SCOPE)

endfunction()
124 changes: 124 additions & 0 deletions cmake/DownloadAndExtractClang.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Downloads and extracts the Clang archive for the current system from
# https://releases.llvm.org
#
# Returns the extracted Clang archive directory in DOWNLOADED_CLANG_DIR
#
# Downloads 7-Zip to extract Clang if it isn't available in the PATH
function(download_and_extract_clang CLANG_VERSION CLANG_DOWNLOAD_LOCATION)

set(CLANG_ARCHIVE_EXT .tar.xz)

if(${CMAKE_SYSTEM_NAME} STREQUAL Linux)

set(CLANG_ARCHIVE_NAME
clang+llvm-${CLANG_VERSION}-x86_64-linux-gnu-ubuntu-14.04)

elseif(${CMAKE_SYSTEM_NAME} STREQUAL Darwin)

set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin)

elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows)

set(CLANG_ARCHIVE_NAME LLVM-${CLANG_VERSION}-win64)
set(CLANG_ARCHIVE_EXT .exe)

elseif(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD)

if(${CLANG_VERSION} STREQUAL 6.0.0)
set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-amd64-unknown-freebsd-10)
else()
set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-amd64-unknown-freebsd10)
endif()

endif()

set(CLANG_ARCHIVE_FULL_NAME ${CLANG_ARCHIVE_NAME}${CLANG_ARCHIVE_EXT})
set(CLANG_ARCHIVE_FILE ${CLANG_DOWNLOAD_LOCATION}/${CLANG_ARCHIVE_FULL_NAME})
set(CLANG_ARCHIVE_EXTRACT_DIR ${CLANG_DOWNLOAD_LOCATION}/${CLANG_ARCHIVE_NAME})
set(CLANG_ARCHIVE_URL
https://releases.llvm.org/${CLANG_VERSION}/${CLANG_ARCHIVE_FULL_NAME})
set(CLANG_ARCHIVE_HASH_FILE
${CMAKE_SOURCE_DIR}/clang_archive_hashes/${CLANG_ARCHIVE_FULL_NAME}.SHA256)

# Exit if Clang is already downloaded and extracted
set(CLANG_ROOT ${CLANG_ARCHIVE_EXTRACT_DIR})
find_package(Clang ${CLANG_VERSION} QUIET)
if(Clang_FOUND)
message(STATUS "Clang already downloaded")
set(DOWNLOADED_CLANG_DIR ${CLANG_ARCHIVE_EXTRACT_DIR} PARENT_SCOPE)
return()
endif()

if(NOT CLANG_ARCHIVE_NAME)
message(FATAL_ERROR "No Clang archive url specified for current platform \
(${CMAKE_SYSTEM_NAME}). Please file an issue to get it added.")
endif()

if(NOT EXISTS ${CLANG_ARCHIVE_HASH_FILE})
message(FATAL_ERROR "No SHA256 hash available for the current platform \
(${CMAKE_SYSTEM_NAME}) + clang version (${CLANG_VERSION}) combination. Please \
file an issue to get it added.")
endif()

# Download Clang archive
message(STATUS "Downloading Clang ${CLANG_VERSION} (${CLANG_ARCHIVE_URL}) ...")
file(DOWNLOAD ${CLANG_ARCHIVE_URL} ${CLANG_ARCHIVE_FILE}
STATUS CLANG_ARCHIVE_DOWNLOAD_RESULT)

# Abort if download failed
list(GET ${CLANG_ARCHIVE_DOWNLOAD_RESULT} 0 ERROR_CODE)
if(${ERROR_CODE})
list(GET ${CLANG_ARCHIVE_DOWNLOAD_RESULT} 1 ERROR_STRING)
message(FATAL_ERROR ${ERROR_STRING})
endif()

# Retrieve expected hash from file and strip newline
file(READ ${CLANG_ARCHIVE_HASH_FILE} CLANG_ARCHIVE_EXPECTED_HASH)
string(STRIP ${CLANG_ARCHIVE_EXPECTED_HASH} CLANG_ARCHIVE_EXPECTED_HASH)
# Calculate actual hash
file(SHA256 ${CLANG_ARCHIVE_FILE} CLANG_ARCHIVE_HASH)
# Abort if hashes do not match
if(NOT ${CLANG_ARCHIVE_EXPECTED_HASH} STREQUAL ${CLANG_ARCHIVE_HASH})
message(FATAL_ERROR "SHA256 hash of downloaded Clang does not match \
expected hash. Remove the build directory and try running CMake again. If this \
keeps happening, file an issue to report the problem.")
endif()

if(${CLANG_ARCHIVE_EXT} STREQUAL .exe)
# Download and extract 7-zip if not found in PATH
find_program(7ZIP_EXECUTABLE 7z)
if(NOT 7ZIP_EXECUTABLE)
message(STATUS "7-Zip not found in PATH")

include(DownloadAndExtract7zip)
download_and_extract_7zip(${CLANG_DOWNLOAD_LOCATION})
find_program(7ZIP_EXECUTABLE 7z NO_DEFAULT_PATH
PATHS ${DOWNLOADED_7ZIP_DIR})
else()
message(STATUS "7-Zip found in PATH")
endif()

message(STATUS "Extracting downloaded Clang with 7-Zip ...")

# Avoid running the Clang installer by extracting the exe with 7-Zip
execute_process(COMMAND ${7ZIP_EXECUTABLE} x
-o${CLANG_ARCHIVE_EXTRACT_DIR}
-xr!$PLUGINSDIR ${CLANG_ARCHIVE_FILE}
WORKING_DIRECTORY ${CLANG_DOWNLOAD_LOCATION}
OUTPUT_QUIET)

elseif(${CLANG_ARCHIVE_EXT} STREQUAL .tar.xz)
message(STATUS "Extracting downloaded Clang with CMake built-in tar ...")

# CMake has builtin support for tar via the -E flag
execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${CLANG_ARCHIVE_FILE}
# Specify working directory to allow running cmake from
# everywhere
# (example: cmake -H"$HOME/ccls" -B"$home/ccls/build")
WORKING_DIRECTORY ${CLANG_DOWNLOAD_LOCATION}
OUTPUT_QUIET)
endif()

set(DOWNLOADED_CLANG_DIR ${CLANG_ARCHIVE_EXTRACT_DIR} PARENT_SCOPE)

endfunction()
Loading

0 comments on commit 98a4ef5

Please sign in to comment.