3
\$\begingroup\$

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)
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

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.

\$\endgroup\$
0
0
\$\begingroup\$

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)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.