Gradle 2.5 google-test plugin tries to compile tests for incorrect platform

When building a cross-platform C++ project that uses the google-test plugin on Linux, gradle tries to compile not only the tests for the Linux platform, but the tests for the Windows platform too. I have modified the build.gradle file from the provided google-test sample to demonstrate the problem. I have changed the platform definitions to be OS-specific and removed the flavors - the rest of the build file and project remains unchanged.

apply plugin: "cpp"
apply plugin: "google-test"

model {
    platforms {
        windows_x86 {
            operatingSystem "Windows"
            architecture "x86"
        }
        linux_x86 {
            operatingSystem "Linux"
            architecture "x86"
        }
    }
    repositories {
        libs(PrebuiltLibraries) {
             googleTest {
                headers.srcDir "libs/googleTest/1.7.0/include"
                binaries.withType(StaticLibraryBinary) {
                    staticLibraryFile =
                        file("libs/googleTest/1.7.0/lib/" +
                             findGoogleTestCoreLibForPlatform(targetPlatform))
                }
            }
        }
    }
    components {
        operators(NativeLibrarySpec) {
            targetPlatform "windows_x86"
            targetPlatform "linux_x86"
        }
    }
}

binaries.withType(GoogleTestTestSuiteBinarySpec) {
    lib library: "googleTest", linkage: "static"

    if (targetPlatform.operatingSystem.linux) {
        cppCompiler.args '-pthread'
        linker.args '-pthread'
    }
}

tasks.withType(RunTestExecutable) {
    args "--gtest_output=xml:test_detail.xml"
}

def findGoogleTestCoreLibForPlatform(Platform platform) {
    if (platform.operatingSystem.windows) {
        return "vs2013/gtest.lib"
    } else if (platform.operatingSystem.macOsX) {
        return "osx/libgtest.a"
    } else {
        return "linux/libgtest.a"
    }
}

Normally, without the google-test plugin, this will compile only the Linux library binary when run on Linux. With the tests included, it compiles the library binary for the linux_x86 platform, then compiles and runs the tests for linux_x86 and then the build fails trying to compile the tests for the windows_x86 platform when it can’t find a suitable toolchain. The behaviour I expect is that it should not attempt to compile the tests for the Windows platform when building on Linux.

:compileLinux_x86OperatorsSharedLibraryOperatorsCpp
:linkLinux_x86OperatorsSharedLibrary
:linux_x86OperatorsSharedLibrary
:compileLinux_x86OperatorsStaticLibraryOperatorsCpp
:createLinux_x86OperatorsStaticLibrary
:linux_x86OperatorsStaticLibrary
:compileLinux_x86OperatorsTestGoogleTestExeOperatorsCpp
:compileLinux_x86OperatorsTestGoogleTestExeOperatorsTestCpp
:linkLinux_x86OperatorsTestGoogleTestExe
:linux_x86OperatorsTestGoogleTestExe
:assemble
:installLinux_x86OperatorsTestGoogleTestExe
:runLinux_x86OperatorsTestGoogleTestExe
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from OperatorTests
[ RUN      ] OperatorTests.test_plus
[       OK ] OperatorTests.test_plus (0 ms)
[ RUN      ] OperatorTests.test_minus
[       OK ] OperatorTests.test_minus (0 ms)
[----------] 2 tests from OperatorTests (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (0 ms total)
[  PASSED  ] 2 tests.
:compileWindows_x86OperatorsTestGoogleTestExeOperatorsCpp FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileWindows_x86OperatorsTestGoogleTestExeOperatorsCpp'.
> No tool chain is available to build for platform 'windows_x86':
    - Tool chain 'visualCpp' (Visual Studio): Visual Studio is not available on this operating system.
    - Tool chain 'gcc' (GNU GCC): Don't know how to build for platform 'windows_x86'.
    - Tool chain 'clang' (Clang): Don't know how to build for platform 'windows_x86'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

I have tried to dig a bit further and added the following to build.gradle

binaries.all {
    println "binary=${name} buildable=${buildable}"
}

which, as I expected, produces the following output when building:

binary=linux_x86OperatorsSharedLibrary buildable=true
binary=linux_x86OperatorsStaticLibrary buildable=true
binary=windows_x86OperatorsSharedLibrary buildable=false
binary=windows_x86OperatorsStaticLibrary buildable=false
binary=linux_x86OperatorsTestGoogleTestExe buildable=true
binary=windows_x86OperatorsTestGoogleTestExe buildable=false

So it looks to me like when it comes to compiling the test binaries gradle is ignoring the buildable property.

Please can someone confirm if this is a bug or if I’ve missed something. If it is indeed a bug, is there another way to disable building of inappropriate test binaries without using the buildable property?

I found the primary issue and it is a bug. The NativeBinariesTestPlugin which is applied as part of the CUnit and GoogleTest plugins is blindly attaching all generated RunTestExecutable tasks to the “CHECK” lifecycle task. When the run tasks are referencing non-buildable binaries the execution plan seems to force a compile task execution that fails. It seems to me in the case of cross compiling this might have to extend to stop the RunTestExecutable tasks from even trying to run incompatible cross compiled binaries.

I am currently working on a BoostTest plugin that had the same issue since I based its design on the GoogleTest plugin. The solution was to fork the NativeBinariesTestPlugin and add logic in the addToLifecycle method to avoid the injection of dependencies to binaries corresponding to non-buildable binaries. I am considering to change that to prevent cross compiled executables from being triggered as well since I would like to try using the cross compilation features.

I think the NativeBinariesTestPlugin needs to be smarter about this use case.

I subsequently submitted a pull request to fix the dependency on non-buildable binaries (https://github.com/gradle/gradle/pull/496) but it hasn’t been merged yet. Is that the same solution you arrived at?

You make a good point about incompatible cross compiled binaries. I hadn’t thought that far as I only cross compile to x86 on x86_64 and I want to run the tests against both. Do you have any ideas on how to determine which binaries can be executed?

Yes Tony, that one line in the attachBinariesToCheckLifecycle method was my fix as well. I have not come up with the right solution for “isRunnable” mechanism. I think there should be a core native algorithm that can provide this determination for binaries. Perhaps allow for an isRunnable as well as isBuildable attribute. Given the complexities of the platform concepts it may be best to allow the end user to specify in the “platforms” collection the isRunnable attribute.

Is there a way to have reliable matching for any os/architecture tuple? For example a Linux platform may be capable of running both 32/64 bit binaries. There needs to be some way to determine such a capability and have a soft match on the architecture bits.

FYI, I found a better fix for this IMHO which was to restrict creating tasks for binaries that are not buildable in the rule that creates the tasks instead of the attachBinariesToCheckLifecycle.

The createGoogleTestTestBinaries rule just needs to check the isBuildable() property. I still want to be able to check the platform somehow to avoid creating unit tests that are not runnable though. Any ideas are welcome.

I like the buildable vs runnable attribute for platforms. I think this suggests deeper changes need to be made to the platform/toolchain models. Ideally, a user should specify for which platforms their build should create binaries, and separately on which platforms the current/modeled environment can execute binaries. There are similar issues with the standard install/execute tasks (i.e. while the issue comes up when running test binaries, it’s not unique to that scenario).