0

I'm trying to set up a C++ project using CMake but I think I'm missing something. When I'm trying to use my library in an executable I get the error:

Scanning dependencies of target dynamic-shadows-lib
[ 33%] Linking CXX static library libdynamic-shadows-lib.a
[ 33%] Built target dynamic-shadows-lib
Scanning dependencies of target main
[ 66%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
/usr/bin/ld: CMakeFiles/main.dir/main.cpp.o: in function `main':
main.cpp:(.text+0x83): undefined reference to `num3()'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/main.dir/build.make:85: main] Error 1
make[1]: *** [CMakeFiles/Makefile2:78: CMakeFiles/main.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

My file structure looks like this:

.
├── build
├── CMakeLists.txt
├── include
│   └── vec2f.hpp
├── main.cpp
└── src
    └── vec2f.cpp

3 directories, 4 files

My root (and only) CMakeLists.txt looks like this:

cmake_minimum_required(VERSION 3.16)

project(
    dynamic-shadows
    VERSION 1.0
    LANGUAGES CXX
)

# Set C++ to version 14
set(CMAKE_CXX_STANDARD 14)

# Set a name for the target
set(TARGET_LIB ${CMAKE_PROJECT_NAME}-lib)

# Make library ${TARGET_LIB}
add_library(${TARGET_LIB} STATIC)

# Set linker language to CXX (Gets error without it)
set_target_properties(${TARGET_LIB} PROPERTIES LINKER_LANGUAGE CXX)

# Set include directory for ${TARGET_LIB}
target_include_directories(
    ${TARGET_LIB}
    PUBLIC
        ${PROJECT_SOURCE_DIR}/include
)

# Set sources for ${TARGET_LIB} 
target_sources(
    ${TARGET_LIB} 
    PUBLIC
        ${PROJECT_SOURCE_DIR}/src
)

# Add a simple test executable to test the library
add_executable(main main.cpp)

# Link the ${TARGET_LIB} to main executable
target_link_libraries(
    main
    PUBLIC
        ${TARGET_LIB}
)

I suspect the problem lies in my CMakeLists.txt since I'm new to this, but I can't figure out what it is. What am I missing? Could it be something else I'm doing wrong?

The code I'm trying to run is very simple but I'll include it for reference:

./include/vec2.hpp

#ifndef __VEC2F_HPP__
#define __VEC2F_HPP__

#include <iostream>

namespace ds {

class vec2f {

public:
    float x;
    float y;

    vec2f(float x_value, float y_value) : x(x_value), y(y_value) {}
};

} // End of namespace ds

std::ostream & operator<<(std::ostream &out, const ds::vec2f &v);

ds::vec2f operator+(const ds::vec2f &left, const ds::vec2f &right);

float num3();

#endif

./src/vec2f.cpp

#include "../include/vec2f.hpp"

/**
 * @brief Overload of << operator for ds::vec2f class to allow for printing it in std::cout.
 * 
 * @param out std::ostream reference (&)
 * @param v ds::vec2f reference (&)
 * @return std::ostream& out
 */
std::ostream & operator<<(std::ostream &out, const ds::vec2f &v)
{
    return out << "[" << v.x << ", " << v.y << "]";
}

/**
 * @brief Overload of + operator for ds::vec2f class to allow for vector addition.
 * 
 * @param left ds::vec2f
 * @param right ds::vec2f
 * @return ds::vec2f sum
 */
ds::vec2f operator+(const ds::vec2f &left, const ds::vec2f &right)
{
    return ds::vec2f(
        left.x + right.x,
        left.y + right.y
    );
}

float num3()
{
    return 3;
}

./main.cpp

#include "vec2f.hpp"

int main(int argc, char* argv[])
{
    std::cout << "Hello world!" << std::endl;

    ds::vec2f v1 = ds::vec2f(8, -2);
    ds::vec2f v2 = ds::vec2f(2, 5);

    float n = num3();

    std::cout << "Res: " << n << std::endl;

    return 0;
}

I've tried to follow solutions to similair problems which usually seems to have something to do with linking.

Most havn't really helped since I'm required to solve this using CMake. I've tried with a variety of CMakeLists.txt configurations but ended up with this one since it looked the cleanest and seemed to be using the latest implementations of commands (target_include_directory instead of include_directories etc..)

Jesper
  • 60
  • 5
  • 2
    Specifying `${PROJECT_SOURCE_DIR}/src` in `target_sources` call is not a correct way to collect all source files from `src` directory. The [duplicate question](https://stackoverflow.com/questions/3201154/automatically-add-all-files-in-a-folder-to-a-target-using-cmake) describes how to correctly do that. If you wonder whether it is a good approach to use GLOB in CMake, then see [another question](https://stackoverflow.com/questions/1027247/is-it-better-to-specify-source-files-with-glob-or-each-file-individually-in-cmak). – Tsyvarev Mar 31 '22 at 12:09

1 Answers1

1
# Set sources for ${TARGET_LIB} 
target_sources(
    ${TARGET_LIB} 
    PUBLIC
        ${PROJECT_SOURCE_DIR}/src
)

You add source files, not directories. Just:

 add_library(... STATIC
     src/vec2f.cpp
 )

Do not use PROJECT_SOURCE_DIR, it will change when someone does add_subirectory from above. If you want current project source dir, thats ${CMAKE_CURRENT_SOURCE_DIR}.

# Set linker language to CXX (Gets error without it)
set_target_properties(${TARGET_LIB} PROPERTIES LINKER_LANGUAGE CXX)

Remove it. Yes, without source files, no one knows what language your library is in.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thank you so much! That worked like a charm! What is the point of target_sources? When would you use it, I don't understand it's purpose from the documentation? – Jesper Mar 31 '22 at 11:59
  • 1
    https://crascit.com/2016/01/31/enhanced-source-file-handling-with-target_sources/ – KamilCuk Mar 31 '22 at 12:04