How to reference a native library task for adding dependencies, input files, etc

When declaring a C++ binary library I also need to compile it against my Fortran library, which is not supported by Gradle. So I wrote a quick and dirty Fortran compile task with proper inputs/outputs. Now I need to make the C++ library depend on my fortran compile task but there is no C++ library task!

To be clear, the task exists, but it doesn’t exist until some later stage in the build process. This is not a problem for Java tasks created by the Java plugin - they exist early in the build process and can have dependencies and input files added - but this does seem to be a problem for native tasks created by native plugins like ‘cpp’.

How can I add dependencies and input files to my native tasks?

Here’s my Gradle script

apply plugin: 'cpp'
  libraries {
    ibnativetest {}
}
  sources {
    ibnativetest {
        cpp {
            // Necessary because Gradle will compile _ALL_ files including Vim
            // hidden swap files (e.g., '.ibnativetest.cpp.swp')
            source {
                include '**/*.cpp'
            }
        }
    }
}
  task compileIBNativeTestFortran() {
    ext.objName = 'ib_native_test.o'
    ext.outputDir = file("$buildDir/fortran/ibnativetest")
    ext.src = file("src/ibnativetest/f95/ib_native_test.f95")
    ext.obj = file("$outputDir/$objName")
    outputs.dir outputDir
    outputs.file obj
    inputs.file src
    doLast {
        outputDir.exists() || outputDir.mkdirs()
        exec {
            workingDir outputDir
            commandLine 'gfortran', '-o', objName, '-c', src
        }
    }
}
  //*
def linkIbnativetestTask = tasks.getByName('linkIbnativetestSharedLibrary')
linkIbnativetestTask.dependsOn compileIBNativeTestFortran
linkIbnativetestTask.inputs.file compileIBNativeTestFortran.obj
//*/
  libraries {
    'ibnativetest-jni' {
        binaries.all {
            if ( toolChain in GccCompatibleToolChain ) {
                linker.args "-L$ibiGccLibPath", '-lgfortran',
                    compileIBNativeTestFortran.obj.toString()
            }
            if ( toolChain in VisualCpp ) {
                println "---- need to configure for Visual CPP!! ----"
            }
        }
    }
}

Here’s the relevant output:

Task with name ‘linkIbnativetestSharedLibrary’ not found in project ‘:numerical_toolkit’.

You are correct, these tasks are created during a later stage of the configuration phase. In general, to reference these tasks, you need only nest your configuration inside a ‘model {…}’ block. Take a look at the release notes for Gradle 2.2 for more info.

model {

tasks.linkIbnativetestTask {

dependsOn ‘compileIBNativeTestFortran’

}

}

This doesn’t work. Details below in my next comment.

I added this code per your suggestion:

model {
    tasks.linkIbnativetestStaticLibrary {
        dependsOn compileIBNativeTestFortran
    }
}

The ‘linkIbnativetesStaticLibrary’ is of course the native library task defined by the ‘cpp’ plugin. The ‘compileIBNativeTestFortran’ is my task to compile F95 code.

The error I get is this:

* What went wrong:  A problem occurred configuring project ':numerical_toolkit'.  > The following model rules are unbound:



model.tasks.linkIbnativetestStaticLibrary



  Mutable:





 - tasks.linkIbnativetestStaticLibrary (java.lang.Object)  

In fact, if I remove the ‘dependsOn’ line I still get this error.

Try this (do not put in ‘model {…}’ block).

binaries.withType(StaticLibraryBinarySpec) {

binary.tasks.link.dependsOn compileIBNativeTestFortran

}

Right away, I’m guessing ‘binary’ should be ‘it’ or there should be a ‘binary ->’ immediately after the open curly brace.

Thanks Mark!

You’re previous comment led me to a solution that works. Here is my solution:

binaries.withType(SharedLibraryBinarySpec) {
    if (it.name == 'odrpack95-jniSharedLibrary') {
        it.tasks[0].dependsOn compileOdrSlimWrapFortran
        it.tasks[0].inputs.file compileOdrSlimWrapFortran.obj
    }
    if (it.name == 'ibnativetest-jniSharedLibrary') {
        it.tasks[0].dependsOn compileIBNativeTestFortran
        it.tasks[0].inputs.file compileIBNativeTestFortran.obj
    }
}

Also, there doesn’t seem to be a ‘link’ property on the ‘tasks’ object. I will use ‘tasks[0]’ instead… which isn’t the same and I can imagine would easily break in the future, but it works for now.

For anyone else reading these comments you’ll probably notice the switch between ‘shared’ and ‘static’ as well as my sometimes added ‘-jni’ suffix. Please pretend all those differences don’t exist :slight_smile: the examples are part of a larger build system where there are both shared and static libraries and most also have a JNI binary compiled after the library is compiled. I apologize for not being extra careful in keeping things consistent in the examples, and I hope you find the question and answers here useful.

You are correct, the ‘link’ property is only available on ‘NativeBinarySpec’ and it should be referencing ‘it’. This is a bit of a bastardized version of an integration test I found.

Jason, glad you got it working. I’d be a little uncomfortable referencing the task by index (unless I suppose there is only one). I’m not sure that ordering is deterministic or won’t change over time. I realize the task name is derived, perhaps something like ‘tasks.withType()’ would work and be a little less brittle.

I am uneasy referencing the task by index! I didn’t think to use ‘withType’, I’ll try that.

I’m continuing to revise my solution and I want the revisions to be here for future readers (and I may be a future reader! Hello future self :slight_smile:

This revision doesn’t rely on the index of the task and names the 'it’s so there is less confusion with the nested lambdas.

binaries.withType(SharedLibraryBinarySpec) { binary ->
    if (binary.name == 'odrpack95-jniSharedLibrary') {
        binary.tasks.withType(LinkSharedLibrary) { task ->
            task.dependsOn compileOdrSlimWrapFortran
            task.inputs.file compileOdrSlimWrapFortran.obj
        }
    }
    if (binary.name == 'ibnativetest-jniSharedLibrary') {
        binary.tasks.withType(LinkSharedLibrary) { task ->
            task.dependsOn compileIBNativeTestFortran
            task.inputs.file compileIBNativeTestFortran.obj
        }
    }
}