0

I have a main -> that calls a function from object2 -> that calls a function from object1. Both object2 and object1 are CMake object libraries. Objects are passed along the chain using $<TARGET_OBJECTS:xxx and usage requirements using target_link_libraries.

Project structure:

project
 |-- main
 |    |-- main.c
 |-- object2
 |    |-- object2.h
 |    |-- object2.c
 |-- object1
 |    |-- object1.h
 |    |-- object1.c
 |-- CMakeLists.txt

Contents of

// CMakeLists.txt

project(objlibs)

# Object library 1
add_library(ObjectLibrary1 OBJECT object1/object1.c)
target_include_directories(ObjectLibrary1 INTERFACE object1)

# Object library 2
add_library(ObjectLibrary2 OBJECT object2/object2.c $<TARGET_OBJECTS:ObjectLibrary1>)
target_include_directories(ObjectLibrary2 INTERFACE object2)
target_link_libraries(ObjectLibrary2 PUBLIC ObjectLibrary1)

# Final executable or library
add_executable(MyTarget
    main/main.c
    $<TARGET_OBJECTS:ObjectLibrary2>
)
target_link_libraries(MyTarget PRIVATE ObjectLibrary2)

Trying to build, I get a linker error.

ld.exe: CMakeFiles/ObjectLibrary2.dir/object2/object2.obj:object2.c:(.text+0x18): undefined reference to `obj1func'
collect2.exe: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

I noticed that if I modify the add_executable statement as follows (i.e. adding the $<TARGET_OBJECTS:ObjectLibrary1> line), linking goes through without issues.

add_executable(MyTarget
    main/main.c
    $<TARGET_OBJECTS:ObjectLibrary2>
    $<TARGET_OBJECTS:ObjectLibrary1>
)

Since ObjectLibrary2 requires a symbol (the very obj1func) from ObjectLibrary1, I would expect that $<TARGET_OBJECTS:ObjectLibrary1> (as shown in my working try) would be redundant.

CMake version: 3.25.2

// object1.h
#ifndef OBJECT1_H
#define OBJECT1_H
void obj1func(void);
#endif // OBJECT1_H
// object1.c
#include "object1.h"
#include <stdio.h>
void obj1func(void){
    printf("obj1func\n");
}
// object2.h
#ifndef OBJECT2_H
#define OBJECT2_H
void obj2func(void);
#endif // OBJECT2_H
// object2.c
#include "object2.h"
#include <stdio.h>
#include "object1.h"
void obj2func(void){
    printf("obj2func\n");
    obj1func();
}
// main.c
#include <stdio.h>
#include "object2.h"
int main(){
    printf("Hello world\n");
    obj2func();
}

I have tried the above with all combinations:

  • WSL (Ubuntu 20.04.5 LTS), Ninja (1.10.0), clang (10.0.0)
  • WSL (Ubuntu 20.04.5 LTS), makefiles (4.2.1), gcc (9.4.0)
  • Windows (10 22H2 19045.2486), Visual Studio 16, MSVC (19.29.30146.0)
  • Windows (10 22H2 19045.2486), Ninja (1.11.0), clang (15.0.0)
  • Windows (10 22H2 19045.2486), makefiles (4.3 for Win32), gcc (12.2.0)

The sequence of cmake commands is as follows. Out of source, so that I can quickly rm -rf the build tree/artifacts; building in source makes no difference.

cmake -B build/main -S .
cmake --build build/main --config Debug --verbose --clean-first

In all cases, the issue persists.

thekyria
  • 35
  • 5
  • 2
    Please don't rely on ChatGPT for any explanation as ChatGPT is literally just a chatbot. There's a reason why it's use is discouraged on SO. – Milan Š. Feb 03 '23 at 20:31
  • 1
    Secondly if you want to link object files together you are literally creating a library or an executable. So if you want to do it - create a library and then link the library to the project. Static libraries are literally archives of object files. – Milan Š. Feb 03 '23 at 20:33
  • 1
    And as a side note, apparently I am not experiencing the same issues as you do. As the provided **minimal reproducible example** works. (I did have to create my own `.h` files though) - EDIT: Are you by any chance using Unix Makefiles as the underlying build system and building in parallel? There are known issues with it. Try to clean the build directory (i.e. `rm -rf` it). Generate new makefiles and run just `make`. Also if the issue persists post the versions of CMake/LD etc.. that you are using – Milan Š. Feb 03 '23 at 21:08
  • 1
    I was unaware of the SO policy around AI bots. Apologies and removing the comment. – thekyria Feb 06 '23 at 07:38
  • Try removing the word `PRIVATE` from this line: `target_link_libraries(ObjectLibrary2 PRIVATE ObjectLibrary1)`. If that works I can post it as an answer. (Also, you could just make static libraries; I'm more familiar with that than this object library thing and it doesn't require weird syntax with `$` and `<` symbols.) – David Grayson Feb 06 '23 at 08:13
  • Dear @MilanŠ. thank you for your comments. I have updated a bit the example, correcting a obvious typo there was in my `CMakeLists.txt`, also adding the `.h` files and the `cmake` commands I use. It still doesn't work on my end. Can you please explain how it does on your end? – thekyria Feb 06 '23 at 08:16
  • Thanks @DavidGrayson. You comment on `PRIVATE` is insightful. I tried that out (i.e. using `PUBLIC` - notice you can't go `INTERFACE` since `object1.h` is needed by `object2.c`), but it did not work either. Let me update the question with this remark. Using static libraries is not a problem; however, I want to use object libraries in this example. – thekyria Feb 06 '23 at 08:17
  • Always remove the build folder - CMake is very weird when it comes to generating new build files and doesn't always clean up after itself. I've already stated in my answer that if you wish to link object files together you need to create a library. Or just add them to the executable. It has added benefits that the compiler optimizes out unneeded objects. – Milan Š. Feb 06 '23 at 08:21
  • Building out of source (as stated now in the question), has the benefit of allowing a quick `rm -rf build/main`. And yes I am doing this, just to be on the safe side. – thekyria Feb 06 '23 at 08:24
  • No adding `PUBLIC` won't do anything. As stated in my answer it doesn't make sense. You need to add the object files into a library or add them to the executable. It doesn't make sense to link object files together. The object files are just symbols. – Milan Š. Feb 06 '23 at 08:26
  • You also have to understand that the whole idea of object libraries is to not compile things twice. If you know that both LibA and LibB use C.obj then by creating C.obj - you compile it once and pack it into LibA and LibB. This is the primary use of object files (libraries) – Milan Š. Feb 06 '23 at 08:28
  • This is the essence of my question @MilanŠ. I know `object1` is needed by many things around by my (much more complex) structure hence, I want to have it as an object library. `object2` happens to be one part of the code that absolutely needs the objects from `object1`. Static libs work ok. I wanted to know whether object libraries can do the trick here to transitively include the objects of `object1` into executables/libraries linking to `object2`. Since, you mentioned that the example provided above (without static libs) for you, can you kindly elaborate? – thekyria Feb 06 '23 at 08:51
  • I have elaborated it mostly in my answer. The only options that you have here is to either create a new object file, static/dynamic library or just pack it into the executable. You are packing it into the executable "in the working example". In my answer I instead create a library first - because then the compiler/linker is free to optimize unwanted object files depending on its needs. Understand what happens when you "link" static libraries - you unpack the archive and repack it into the other binary. It's the equivalent of `add_executable` not linking. Because there is no linking at all. – Milan Š. Feb 06 '23 at 09:01
  • So as long as the object file is present in the binary - the symbols will be resolved. Linking object files together means you create some kind of an archive where they are packed and you add a symbol index to them. So that the linker can perform rapid symbol searches. There is no such thing for an object file. Object files are just symbols in machine readable form. Hence linking doesnt make sense. – Milan Š. Feb 06 '23 at 09:04
  • To summarize: Linking object files means that you pack them together (somehow). So either via `add_library` or `add_executable` – Milan Š. Feb 06 '23 at 09:04

1 Answers1

0

EDIT: Illustrated my answer. Please note that they might be inaccuracies here, as it's been some time since I've studied this.

I've added this graph to better illustrate what I tried to explain in the comments/here. It shows a very simplified and probably not as accurate demonstration of what happens when you link a static library (which is an archive of object files with a symbol index). Note that index is probably not the correct term, but rather symbol table (suggest an edit if you find inaccuracies please).

┌─────┐ linking    ┌────────┐
│lib.a├─┬─────────►│some.elf│
└─────┘ │       ▲  └────────┘
        │u      │
        │n      │
        │p      │p u
        │a      │a p
        │c      │c d
        │k      │k a
        ▼       │  t
     ┌─────┐    │i e
     │index│    │n
     └─────┴┐   │t i
            │l  │o n
            │o  │  d
            │o  │  e
            │k  │  x
            │u  │
            │p  │
  ┌───┐   ┌─▼─┐ │
  │a.o│   │b.o├─┘
  └───┘   └───┘

If you wish to "link" object files, then the equivalent isn't linking but rather packing them into their "final resting place" and updating the symbol table so that the binary knows where this symbol is located. Which in your case is the executable file.

Hence why adding the Object1Library into the executable doesn't yield any errors.

Linking object files doens't make any sense, beacuse object files are missing this symbol table, they are just symbols. So if you "just link" then you aren't updating anything in the object file.

What you want to do is outlined below:

Either add_library and pack the object files into an archive with a symbol table or

add them to the executable via add_executable which adds them to the executable and updates the symbol table there or

just create a new object file altogether.


To expand on my comments I will start by posting a link to a different SO question explaining the difference between object files and libraries. There is a huge misconception between what is what here, especially because CMake coined the term "Object libraries".

In short: think of objects as a collection of symbols. While a library is not just a collection of symbols but also has (let's call it) some form of a map to these symbols.

Linking an object file to an object file doesn't make sense if you think about it that way. It makes sense to create some archive with this map that tells the linker where to look for individual symbols.

Your sample will break and give the error if I remove the Object1Library object files from the add_executable.

To fix this we can create this "dummy archive" like so:

add_library(dummy_archive STATIC) #Note that I'm not adding any files!
target_link_libraries(dummy_archive ObjectLibrary2 ObjectLibrary1)

add_executable(MyTarget main/main.c)
target_link_libraries(MyTarget PRIVATE dummy_archive)

Try to add this snippet and you will see it finally works. This is "how you link object libraries together".

Milan Š.
  • 1,353
  • 1
  • 2
  • 11
  • @DavidGrayson I wouldn't be surprised if I got it wrong as it's been ages since I studied about this - so feel free to suggest an edit/correct me and I will fix it. I vaguely remember that there was an index of symbols even for static libraries. But as I said I might be wrong. – Milan Š. Feb 06 '23 at 08:18
  • @DavidGrayson to add, I've checked my old notes and indeed static libraries have a symbol index. Could you please send me a source that states it's just an archive? – Milan Š. Feb 06 '23 at 08:23
  • Also to answer the question regarding differences - you are not creating a collection of symbols by linking object file to object file. You would do so if you packed them into a library or all of them into the executable as OP posted in his question. – Milan Š. Feb 06 '23 at 08:37
  • Making a static library out of ObjectLibrary2 seems to be the most straightforward thing to do here: `ObjectLibrary2 OBJECT` -> `ObjectLibrary2 STATIC`. This is pretty (something like) your first suggestion Milan Š., thank you. – thekyria Apr 11 '23 at 15:04