I have very little experience with cmake
, this is really the first time I used it for a project. In the past I used some autotools
and recently mostly bazel
. I would appreciate some suggestions how to better structure the code. For example during compilation I noticed that the same targets get compiled multiple times, which ideally I would like to avoid.
cmake_minimum_required(VERSION 3.10)
project(schwifty)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DELPP_FEATURE_CRASH_LOG")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out)
find_package(PythonInterp 3.6 REQUIRED)
file(MAKE_DIRECTORY downloads external)
################################################################################
# Easylogging++
################################################################################
if(EXISTS "external/easyloggingpp")
else()
file(MAKE_DIRECTORY external/easyloggingpp)
file(DOWNLOAD
https://github.com/muflihun/easyloggingpp/archive/v9.96.4.zip
downloads/easyloggingpp.zip)
execute_process(COMMAND unzip downloads/easyloggingpp.zip -d downloads)
file(GLOB easyloggingpp_files downloads/easyloggingpp-9.96.4/src/easylogging++.*)
file(COPY ${easyloggingpp_files} DESTINATION external/easyloggingpp)
endif()
set(ast ast.h ast.cc)
set(codegen codegen.h codegen.cc)
set(functions functions.h functions.cc)
set(parser parser.h parser.cc)
include_directories(external/easyloggingpp)
set(easyloggingpp external/easyloggingpp/easylogging++.cc)
set(SOURCE_FILES
ast_compare_visitor.cc
ast_compare_visitor.h
classes.cc
classes.h
compilation_context.cc
compilation_context.h
common.h
errors.h
errors.cc
expression_type_visitor.cc
expression_type_visitor.h
functions.cc
functions.h
jit.cc
jit.h
lexer.cc
lexer.h
lexer_common.cc
lexer_common.h
runtime.h
runtime.cc
utils.h
utils.cc
type.cc
type.h
type_inference_visitor.cc
type_inference_visitor.h
enum.cc
enum.h
type_inference.cc
type_inference.h
operators.cc
operators.h
symbol_visitor.cc
symbol_visitor.h)
add_library(sources ${SOURCE_FILES})
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
llvm_map_components_to_libnames(llvm_libs all)
find_package(FMT REQUIRED CONFIG)
add_executable(schwifty
schwifty.cc
${ast}
${codegen}
${easyloggingpp}
${parser})
target_link_libraries(schwifty ${llvm_libs})
target_link_libraries(schwifty fmt::fmt)
target_link_libraries(schwifty sources)
################################################################################
# Testing
################################################################################
enable_testing()
find_package(gtest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
add_executable(codegen_test codegen_test.cc ${ast} ${codegen} ${easyloggingpp}
${functions} ${parser})
target_link_libraries(codegen_test ${GTEST_BOTH_LIBRARIES})
target_link_libraries(codegen_test ${llvm_libs})
target_link_libraries(codegen_test fmt::fmt)
target_link_libraries(codegen_test sources)
add_test(codegen_test COMMAND out/codegen_test)
add_executable(lexer_test lexer_test.cc ${ast} ${codegen} ${easyloggingpp}
${functions} ${parser})
target_link_libraries(lexer_test ${GTEST_BOTH_LIBRARIES})
target_link_libraries(lexer_test ${llvm_libs})
target_link_libraries(lexer_test fmt::fmt)
target_link_libraries(lexer_test sources)
add_test(lexer_test COMMAND out/lexer_test)
add_executable(parser_test parser_test.cc ${ast} ${codegen} ${easyloggingpp}
${functions} ${parser})
target_link_libraries(parser_test ${GTEST_BOTH_LIBRARIES})
target_link_libraries(parser_test ${llvm_libs})
target_link_libraries(parser_test fmt::fmt)
target_link_libraries(parser_test sources)
add_test(parser_test COMMAND out/parser_test)
add_executable(type_test type_test.cc ${ast} ${codegen} ${easyloggingpp}
${functions} ${parser})
target_link_libraries(type_test ${GTEST_BOTH_LIBRARIES})
target_link_libraries(type_test ${llvm_libs})
target_link_libraries(type_test fmt::fmt)
target_link_libraries(type_test sources)
add_test(type_test COMMAND out/type_test)
add_executable(type_inference_test type_inference_test.cc ${ast} ${codegen}
${easyloggingpp} ${functions} ${parser})
target_link_libraries(type_inference_test ${GTEST_BOTH_LIBRARIES})
target_link_libraries(type_inference_test ${llvm_libs})
target_link_libraries(type_inference_test fmt::fmt)
target_link_libraries(type_inference_test sources)
add_test(type_inference_test COMMAND ./out/type_inference_test)
add_test(NAME end_to_end_tests WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY}
COMMAND ${PYTHON_EXECUTABLE} ${CTEST_SOURCE_DIRECTORY}/end_to_end_tests.py)
2 Answers 2
I'm by no means a professional CMake user, I try to follow best practices though and have seen many talks and articles about modern CMake best practices, so lets go through your CMakeLists.
set(CMAKE_CXX_STANDARD 14)
This is generally frowned upon for two different reasons. First and most important, you set this configuration globally for every target you create or import through add_subdirectory
. In modern CMake best practices you should always prefer target_
functions whenever possible to configure exactly the target which needs the configuration instead of setting it globally.
Second, you should not need to set the C++ standard directly, instead you should select features which you need to compile your project and let CMake decide the standard. See target_compile_features.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DELPP_FEATURE_CRASH_LOG")
Same problem as previously, use target_compile_definitions(mytarget PUBLIC ELPP_FEATURE_CRASH_LOG)
#
# Easylogging++
#
if(EXISTS "external/easyloggingpp")
else()
file(MAKE_DIRECTORY external/easyloggingpp)
file(DOWNLOAD https://github.com/muflihun/easyloggingpp/archive/v9.96.4.zip
downloads/easyloggingpp.zip)
execute_process(COMMAND unzip downloads/easyloggingpp.zip -d downloads)
file(GLOB easyloggingpp_files
downloads/easyloggingpp-9.96.4/src/easylogging++.*)
file(COPY ${easyloggingpp_files} DESTINATION external/easyloggingpp)
endif()
This looks like a messy hack to pull in a dependency. If this dependency is required to build your project you should probably add it as a subrepository to your own git source repository and use it using add_subdirectory
(assuming it's a CMake project). Alternatively, there is also the ExternalProject module which exists for this sole reason, to pull in and compile external dependencies.
include_directories(external/easyloggingpp)
add_library(easyloggingpp external/easyloggingpp/easylogging++.cc)
You're configuring globally again with include_directories
, use target_include_directories
instead. Also, I'd really consider splitting up your CMakeLists file, there is too much going on. Subdivide your project repository to subdirectories, one per library, and then use add_subdirectory
to pull in all the libraries you need.
find_package(gtest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
You're setting include directories globally again. Actually, you don't even need to set the directories at all. target_link_libraries
does a lot more than linking. It probably should've called differently. Since gtest
exports a target with it's INTERFACE_INCLUDE_DIRECTORIES
set up, solely linking (target_link_libraries
) to gtest sets up include directories automatically for the target.
If you follow best practices and set all configurations with target_
functions then all you need to pull in a library should be a sole target_link_library
since all other configurations (compiler features, include directories, ...) are automatically pulled in, given they're either set PUBLIC
or INTERFACE
on this library. As I said, target_link_library
does a lot more than just linking, it's name is very misleading.
A perfect example is the fmt
package you're using. All you're doing is find_package(FMT REQUIRED CONFIG)
and target_link_libraries(mytarget fmt::fmt)
and everything else to use this package is set up by the target_link_libraries
command since the fmt
package exports all its own requirements and include paths through its target.
I'm pretty sure I missed a few things but I hope these pointers help you to get started.
Instead of using set
for source files, which made them get recompiled multiple times, I switched add_library
and now it builds everything much faster. No more unnecessarily compiling the same file again.
cmake_minimum_required(VERSION 3.10)
project(schwifty)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DELPP_FEATURE_CRASH_LOG")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out)
find_package(PythonInterp 3.6 REQUIRED)
file(MAKE_DIRECTORY downloads external)
#
# Easylogging++
#
if(EXISTS "external/easyloggingpp")
else()
file(MAKE_DIRECTORY external/easyloggingpp)
file(DOWNLOAD https://github.com/muflihun/easyloggingpp/archive/v9.96.4.zip
downloads/easyloggingpp.zip)
execute_process(COMMAND unzip downloads/easyloggingpp.zip -d downloads)
file(GLOB easyloggingpp_files
downloads/easyloggingpp-9.96.4/src/easylogging++.*)
file(COPY ${easyloggingpp_files} DESTINATION external/easyloggingpp)
endif()
include_directories(external/easyloggingpp)
add_library(easyloggingpp external/easyloggingpp/easylogging++.cc)
#
# Local lib targets
#
add_library(ast ast.h ast.cc)
add_library(ast_compare_visitor ast_compare_visitor.h ast_compare_visitor.cc)
add_library(classes classes.h classes.cc)
add_library(codegen
codegen.h
codegen.cc
codegen_common.h
codegen_common.cc
expression_type_visitor.cc
expression_type_visitor.h)
add_library(common common.h utils.h utils.cc)
add_library(compilation_context
compilation_context.h
compilation_context.cc
enum.h
enum.cc
errors.h
errors.cc
operators.h
operators.cc
type.h
type.cc)
add_library(functions functions.h functions.cc)
add_library(jit jit.cc jit.h)
add_library(lexer lexer.cc lexer.h lexer_common.cc lexer_common.h)
add_library(parser parser.h parser.cc)
add_library(runtime runtime.cc runtime.h)
add_library(type_inference
type_inference.h
type_inference.cc
symbol_visitor.cc
symbol_visitor.h
type_inference_visitor.cc
type_inference_visitor.h)
#
# External lib targets
#
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
llvm_map_components_to_libnames(llvm_libs all)
find_package(FMT REQUIRED CONFIG)
#
# Schwifty main executable
#
add_executable(schwifty schwifty.cc)
target_link_libraries(schwifty
${llvm_libs}
ast
classes
codegen
common
compilation_context
easyloggingpp
fmt::fmt
functions
lexer
parser
runtime
type_inference)
#
# Testing
#
enable_testing()
find_package(gtest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
add_executable(codegen_test codegen_test.cc)
target_link_libraries(codegen_test
${GTEST_BOTH_LIBRARIES}
${llvm_libs}
easyloggingpp
ast
classes
codegen
common
compilation_context
fmt::fmt
functions
jit
lexer
parser
runtime
type_inference)
add_test(codegen_test COMMAND out/codegen_test)
add_executable(lexer_test lexer_test.cc)
target_link_libraries(lexer_test
${GTEST_BOTH_LIBRARIES}
ast
common
compilation_context
easyloggingpp
functions
lexer
parser
fmt::fmt)
add_test(lexer_test COMMAND out/lexer_test)
add_executable(parser_test parser_test.cc)
target_link_libraries(parser_test
${GTEST_BOTH_LIBRARIES}
ast
ast_compare_visitor
compilation_context
common
easyloggingpp
functions
lexer
parser
fmt::fmt)
add_test(parser_test COMMAND out/parser_test)
add_executable(type_test type_test.cc)
target_link_libraries(type_test
${GTEST_BOTH_LIBRARIES}
ast
common
compilation_context
easyloggingpp
functions
lexer
parser)
add_test(type_test COMMAND out/type_test)
add_executable(type_inference_test type_inference_test.cc)
target_link_libraries(type_inference_test
${GTEST_BOTH_LIBRARIES}
easyloggingpp
ast
classes
common
compilation_context
functions
fmt::fmt
lexer
parser
runtime
type_inference)
add_test(type_inference_test COMMAND ./out/type_inference_test)
add_test(NAME end_to_end_tests
WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY}
COMMAND ${PYTHON_EXECUTABLE} end_to_end_tests.py)