How to pass arguments to the linker when building a cpp-application

Hello,

I’m trying to compile some cpp code using gradle, but I don’t understand how to add linker arguments. With g++ it complies and links in one step, but the gradle compiler seems to do it in two steps. So I need a way to pass the linker arguments, so it knows which libraries to use when linking

My project is something like this:

root
├── extract_tiles
│   ├── build.gradle
│   └── src
│       └── main
│           ├── cpp
│           │   └── extract_tiles.cpp
│           └── headers
│               └── extract_tiles.h
├── parser
│   ├── build.gradle
│   └── src
│       └── main
│           ├── cpp
│           │   └── parser.cpp
│           └── public
│               └── parser.h
├── build.gradle
├── gradlew
├── gradlew.bat
└── settings.gradle

The extract_tiles build.gradle file has:

plugins {
    id 'cpp-application'
}
application {
    targetMachines.add(machines.linux.x86_64)
    dependencies {
        implementation project(':parser')
    }
}
model {
    toolChains {
        gcc(Gcc) {
            eachPlatform {
                cppCompiler.withArguments { args ->
                    args << '-std=c++11'
                    args << '-DPIC64'
                    args << '-static'
                    args << '-pthread'
                    // This needs to be passed to the linker, not the compiler
                    args << '-ltiff'
                    args << '-llzma'
                    args << '-ljpeg'
                    args << '-ljbig'
                    args << '-lz'
                    args << '-lpiclx20'
                }
            }
        }
    }
    platforms {
        x64 {
            architecture "x86_64"
        }
    }
}

As noted in the build.gradle file, the linker arguments are not passed on the g++ when linking the executable. How can I get gradle to use them when linking?

when I do

./gradlew linkRelease

I see:

3:22:19 PM: Executing task 'linkRelease'...

> Task :parser:compileReleaseCpp
> Task :extract_tiles:compileReleaseCpp
> Task :parser:linkRelease
> Task :parser:stripSymbolsRelease

> Task :extract_tiles:linkRelease FAILED
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
/home/wsi/dev/workspace-pathology/lib/tiff_tools/parser/extract_tiles/build/obj/main/release/acyfrmxeqecjrfs2pp4llhfk3/extract_tiles.o: In function `wsim::tile_worker(void*)':
/home/wsi/dev/workspace-pathology/lib/tiff_tools/parser/extract_tiles/src/main/cpp/extract_tiles.cpp:50: undefined reference to `TIFFOpen'
/home/wsi/dev/workspace-pathology/lib/tiff_tools/parser/extract_tiles/src/main/cpp/extract_tiles.cpp:56: undefined reference to `TIFFSetDirectory'
/home/wsi/dev/workspace-pathology/lib/tiff_tools/parser/extract_tiles/src/main/cpp/extract_tiles.cpp:182: undefined reference to `_TIFFfree'
/home/wsi/dev/workspace-pathology/lib/tiff_tools/parser/extract_tiles/src/main/cpp/extract_tiles.cpp:110: undefined reference to `TIFFTileSize'
1 Like

I think I’ve figured it out, maybe. I added this to the model section:

model {
    toolChains {
        gcc(Gcc) {
            eachPlatform {
                cppCompiler.withArguments { args ->
                    args << '-std=c++11'
                    args << '-DPIC64'
                    args << '-static'
                }
                linker.withArguments { args ->
                    args << '-pthread'
                    args << '-ltiff'
                    args << '-llzma'
                    args << '-ljpeg'
                    args << '-ljbig'
                    args << '-lz'
                    args << '-lpiclx20'
                }
            }
        }
    }
    platforms {
        x64 {
            architecture "x86_64"
        }
    }
}

Now I see this in the output:

3:33:20 PM: Executing task 'linkRelease'...

> Task :extract_tiles:compileReleaseCpp UP-TO-DATE
> Task :parser:compileReleaseCpp UP-TO-DATE
> Task :parser:linkRelease UP-TO-DATE
> Task :parser:stripSymbolsRelease UP-TO-DATE

> Task :extract_tiles:linkRelease FAILED
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status

However, I do the flags in the options.txt file:

-o
/home/wsi/dev/workspace/lib/tiff_tools/parser/extract_tiles/build/exe/main/release/extract_tiles
/home/wsi/dev/workspace/lib/tiff_tools/parser/extract_tiles/build/obj/main/release/acyfrmxeqecjrfs2pp4llhfk3/extract_tiles.o
/home/wsi/dev/workspace/lib/tiff_tools/parser/parser/build/lib/main/release/stripped/libparser.so
-m64
-pthread
-ltiff
-llzma
-ljpeg
-ljbig
-lz
-lpiclx20

Now I just have to figure out the problem with that Scrt1.o file. Any ideas?

1 Like

Good news, I am able to compile and link now. The problem was due to my ‘main()’ was inside my namespace declaration. Once I moved it out, everything worked well. Hope this helps future developers.

Thanks @craigtmoore for sharing your progress. With the software model native plugin, it is recommended to configure the compiler/linker flags on the component instead of the tool chain. At the tool chain level, you only want to configure generic platform flags.

Hi Daniel,Can you give an example of how I can add compiler/linker flags on the component level. I read through the gradle documents, but couldn’t find a good example of how to do this.

It should be something like this:

application { // or library
    binaries.configureEach {
        compileTask.get().compilerArgs.add('--foo-flag')
        linkerTask.get().linkgerArgs.add('--bar-flag') // Only for executable and shared libraries
        createTask.get().staticLibArgs.add('--foobar--flag') // Only for static libraries
    }
}
2 Likes

How does using binaries.configureEach compare with using tasks.withType(LinkExecutable).configureEach etc.? Is binaries.configureEach the recommended approach?

Using tasks.withType(LinkExecutable).configureEach will configure all tasks in the project that are LinkExecutable. It becomes a problem when different variants of the same application exists. In this case, it will only be possible to know what variant you have by using the name of the task. We never recommend basing configuration on task names as it creates fragile code. Instead, it’s better to depend on the model. The binaries.configureEach route allows you do query the information about the variant itself as well as getting the associated LinkExecutable tasks. Both can be valid, we just try to add the configuration that is the most future-proof.

In the software model, the tasks.withType(LinkExecutable) was even more at a disadvantage as multiple independent component could be found inside the same project which easily broken the original intention of the author.

1 Like

I’m also trying to set linker args, but when I do what’s in your code snippet (using library instead of application), I get Unresolved reference: linkerTask. Gradle version is 6.1. Is there some updated way to do this? compileTask appears to be working.

plugins {
    `cpp-library`
}

library {
    binaries.configureEach {
        linkerTask.get().linkerArgs.addAll(...)  // Fails here
    }
}
1 Like

This is a common gotcha that would need to be handled in better ways, the binaries for a library contains both CppStaticLibrary and CppSharedLibrary. Only the CppSharedLibrary has the linkerTask as it links to an actual binary. The CppStaticLibrary has a createTask instead. You can find out what binaries will be created by looking at the configuration of linkage on the library. It’s always better to be precise in what you are trying to configure: binaries.configureEach(CppSharedLibrary) { ... }

2 Likes

For future readers, try linkTask.get() instead of linkerTask.get()

Source