I keep getting linker error, could you help me fix it?

I can’t figure out why but I keep getting linker errors.

I followed/read several problems for example this and I can’t get it to work.

My root is as follows:

.
├── bin
├── CMakeLists.txt
├── external
│   ├── CMakeLists.txt
│   └── tinyformat.h
├── source
│   ├── CMakeLists.txt
│   ├── main.cpp
│   └── utils
│       ├── assert.cpp
│       ├── assert.hpp
│       ├── CMakeLists.txt
│       ├── config.hpp
│       ├── print.hpp
│       ├── timer.cpp
│       └── timer.hpp
└── tests
    ├── CMakeLists.txt
    └── test.cpp

Also TLDR

I tried looking for repos with a similar (possible smarter) structure. If you find any, let me know and I will try to make that work.

Code

In timer.hpp I have defined a class:

#pragma once

#include "config.hpp"
#include <chrono>
#include <iostream>
#include <string>
#include <vector>

namespace WingDesigner {
    /**
 * Simple timer class: add checkpoints throughout the code and measure execution time between them.
 */
    class Timer {
    protected:
        typedef std::chrono::steady_clock::time_point time_type;/// Time type.
        std::vector<std::string> m_Labels;                      /// List of checkpoint labels.
        std::vector<time_type> m_Times;                         /// List of checkpoint times.
       // and some methods...
    };
}

which is then implemented in timer.cpp.

In my main.cpp I want to create an instance of Timer and some prnstatement which is a macro.

#include "utils/timer.hpp"
#include <iostream>
#include <omp.h>
#include <vector>
using namespace WingDesigner;

int main() {
  std::cout << "Hello, World!" << std::endl;

  Timer t;
  prn("Hi");

  return 0;
}

My assert.hpp also uses an external header from external directory.

/**
 * Implementation of custom assert and debug utilities.
 */

#pragma once
#include "config.hpp"
#include "print.hpp"
#include "tinyformat.h"

namespace WingDesigner {

// print macro
#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define VA_NUM_ARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N
#define macro_dispatcher(func, ...)                                            \
  macro_dispatcher_(func, VA_NUM_ARGS(__VA_ARGS__))
#define macro_dispatcher_(func, nargs) macro_dispatcher__(func, nargs)
#define macro_dispatcher__(func, nargs) func##nargs
#define addflag(a)                                                             \
  { std::cerr << "flags=[flags, " << (a) << "];" << std::endl; }
#define prnv2(a, b)                                                            \
  { std::cerr << a << " = " << (b) << ";" << std::endl; }
#define prnv1(a)                                                               \
  { std::cerr << #a << " = " << (a) << ";" << std::endl; }
/**
 * Prints a variable name and value to standard output. Can take one or two
 * parameters. Example: int a = 6; prn(a) // prints 'a = 6;' prn("value", a) //
 * prints 'value = 6;'
 */
#define prn(...) macro_dispatcher(prnv, __VA_ARGS__)(__VA_ARGS__)

using tinyformat::format;
using tinyformat::printf;

/// Namespace holding custom assert implementation.
namespace assert_internal {
/**
 * Actual assert implementation.
 * @param condition Condition to test, e.g.\ `n > 0`.
 * @param file File where the assertion failed.
 * @param func_name Function name where the assertion failed.
 * @param line Line on which the assertion failed.
 * @param message Message as specified in the `assert_msg` macro.
 * @param format_list List of format field values to pass to
 * `tinyformat::format` function.
 */
bool assert_handler_implementation(const char *condition, const char *file,
                                   const char *func_name, int line,
                                   const char *message,
                                   tfm::FormatListRef format_list);
/// Assert handler that unpacks varargs.
template <typename... Args>
bool assert_handler(const char *condition, const char *file,
                    const char *func_name, int line, const char *message,
                    const Args &...args) { // unpacks first argument
  tfm::FormatListRef arg_list = tfm::makeFormatList(args...);
  return assert_handler_implementation(condition, file, func_name, line,
                                       message, arg_list);
}
} // namespace assert_internal

#ifdef NDEBUG
#define assert_msg(cond, ...) ((void)sizeof(cond))
#else
/**
 * @brief Assert with better error reporting.
 * @param cond Conditions to test.
 * @param ... The second parameter is also required and represents the message
 * to print on failure. For every %* field in message one additional parameter
 * must be present.
 *
 * Example:
 * @code
 * assert_msg(n > 0, "n must be positive, got %d.", n);
 * @endcode
 */
#define assert_msg(cond, ...)                                                  \
  ((void)(!(cond) &&                                                           \
          WingDesigner::assert_internal::assert_handler(                       \
              #cond, __FILE__, __PRETTY_FUNCTION__, __LINE__, __VA_ARGS__) &&  \
          (assert(0), 1)))
//            #cond, __FILE__, __PRETTY_FUNCTION__, __LINE__, __VA_ARGS__) &&
//            (exit(1), 1)))
#endif

/**
 * Prints given text in bold red.
 * @param s text to print.
 */
inline void print_red(const std::string &s) {
  std::cout << "\x1b[31;1m" << s << "\x1b[37;0m";
}
/**
 * Prints given text in bold white.
 * @param s text to print.
 */
inline void print_white(const std::string &s) {
  std::cout << "\x1b[37;1m" << s << "\x1b[37;0m";
}
/**
 * Prints given text in bold green.
 * @param s text to print.
 */
inline void print_green(const std::string &s) {
  std::cout << "\x1b[32;1m" << s << "\x1b[37;0m";
}

} // namespace WingDesigner

Note that my top level CMakeLists.txt is:

cmake_minimum_required(VERSION 3.22)
project(wingdesigner LANGUAGES CXX)

# Variables.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(source)

# contains an "external" library we will link to
add_subdirectory(external)

# enable testing and define tests
enable_testing()
add_subdirectory(tests)

my source/CMakeLists.txt is:

add_executable(wingdesigner main.cpp)

add_subdirectory(utils)

target_link_libraries(wingdesigner
        PRIVATE
        tinyformat
        utils
)

my source/utils//CMakeLists.txtis:

add_library(assert "")
target_sources(assert
        PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}/assert.cpp
        PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/assert.hpp
)
target_include_directories(assert
        PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}
)

add_library(timer
        ${CMAKE_CURRENT_LIST_DIR}/timer.hpp
        ${CMAKE_CURRENT_LIST_DIR}/timer.cpp)
target_link_libraries(timer assert)

and finally my external/CMakeLists.txt is:

add_library(tinyformat "")

target_include_directories(tinyformat
        PRIVATE
        include
)

The error

No matter what I try I get the following error:

CMake Error at external/CMakeLists.txt:1 (add_library):
  No SOURCES given to target: tinyformat

but there aren’t any sources, just a header.

Is there a better way to do this?

No SOURCES given to target: tinyformat

Looks like that library needs to be an INTERFACE library (also known as header-only library):

add_library(tinyformat INTERFACE) # also no need to put empty quotes here

Unrelated, but likely also a (potential) issue is this block:

target_include_directories(tinyformat
        PRIVATE
        include
)

There is no include folder inside external folder (or anywhere in your project), so this statement doesn’t do anything useful. In addition, there is no PUBLIC/INTERFACE scope there, so your other targets might still not discover tinyformat’s headers. Crudely scratching on a napkin, it should look like this instead:

target_include_directories(tinyformat
    INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
        #$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> # if also you intend to install it and make a relocatable package later
)
1 Like

That helped a lot. Also explained some things I did not understand.

However, the linker now complains about the utils, saying

/usr/bin/ld: cannot find -lutils: No such file or directory
collect2: error: ld returned 1 exit status
gmake[3]: *** [source/CMakeFiles/wingdesigner.dir/build.make:97: source/wingdesigner] Error 1
gmake[2]: *** [CMakeFiles/Makefile2:153: source/CMakeFiles/wingdesigner.dir/all] Error 2
gmake[1]: *** [CMakeFiles/Makefile2:160: source/CMakeFiles/wingdesigner.dir/rule] Error 2
gmake: *** [Makefile:134: wingdesigner] Error 2

And I again don’t see how as I added the sub directory and link to libraries in my source/CMakeLists.txt:

add_executable(wingdesigner main.cpp)

add_subdirectory(utils)

target_link_libraries(wingdesigner
        PRIVATE
        tinyformat
        utils
)

There is no add_library(utils) anywhere, as far as I can see, so that error makes sense. I would guess that you confused the linking targets here:

target_link_libraries(wingdesigner
    PRIVATE
        tinyformat
        # utils # there is no such target
        assert # is that what you wanted to link from utils?
        timer # is that what you wanted to link from utils?
)
1 Like

Crap, you are correct. I think I need to go to bed for a moment …

But before that, fixing the target_link_libraries as you suggested, results in all sorts of assert.hpp linker errors:

/usr/bin/ld: utils/libtimer.a(timer.cpp.o): in function `bool WingDesigner::assert_internal::assert_handler<int, int>(char const*, char const*, char const*, int, char const*, int const&, int const&)':
/home/mjancic/Documents/wingdesigner++/source/utils/assert.hpp:57: undefined reference to `WingDesigner::assert_internal::assert_handler_implementation(char const*, char const*, char const*, int, char const*, tinyformat::FormatList const&)'
/usr/bin/ld: /home/mjancic/Documents/wingdesigner++/source/utils/assert.hpp:57: undefined reference to `WingDesigner::assert_internal::assert_handler_implementation(char const*, char const*, char const*, int, char const*, tinyformat::FormatList const&)'
/usr/bin/ld: /home/mjancic/Documents/wingdesigner++/source/utils/assert.hpp:57: undefined reference to `WingDesigner::assert_internal::assert_handler_implementation(char const*, char const*, char const*, int, char const*, tinyformat::FormatList const&)'
/usr/bin/ld: utils/libtimer.a(timer.cpp.o): in function `bool WingDesigner::assert_internal::assert_handler<char const*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >(char const*, char const*, char const*, int, char const*, char const* const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&)':
/home/mjancic/Documents/wingdesigner++/source/utils/assert.hpp:57: undefined reference to `WingDesigner::assert_internal::assert_handler_implementation(char const*, char const*, char const*, int, char const*, tinyformat::FormatList const&)'
/usr/bin/ld: utils/libtimer.a(timer.cpp.o): in function `bool WingDesigner::assert_internal::assert_handler<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(char const*, char const*, char const*, int, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
/home/mjancic/Documents/wingdesigner++/source/utils/assert.hpp:57: undefined reference to `WingDesigner::assert_internal::assert_handler_implementation(char const*, char const*, char const*, int, char const*, tinyformat::FormatList const&)'
collect2: error: ld returned 1 exit status
gmake[3]: *** [source/CMakeFiles/wingdesigner.dir/build.make:100: source/wingdesigner] Error 1
gmake[2]: *** [CMakeFiles/Makefile2:154: source/CMakeFiles/wingdesigner.dir/all] Error 2
gmake[1]: *** [CMakeFiles/Makefile2:161: source/CMakeFiles/wingdesigner.dir/rule] Error 2
gmake: *** [Makefile:134: wingdesigner] Error 2

I feel totally incompetent when it comes to linker errors… All references from that hpp seem to be undefined. Would you have any ideas?