Adding a Java project dependency to a C project

I have multi project build. One project is Java and uses the java plugin, one project is C and uses the c plugin.

The C code loads the JVM into a running executable, and as such the C project is dependent on the Java project. I have been trying to find a way to compile the Java code automatically prior to compiling and linking the C code.

When I have done this kind of thing before using multiple Java projects, I have used something like the following

dependencies {
    compile project(':second')
}

however I think this is only defined by the java addin, and therefore fails when I insert this into my C Project.

What would be the best way to ensure the Java code is always compiled and up to date prior to compiling and linking the C code?

Thanks.


as an update, this is my build file to date.

buildscript {
    repositories {
        maven { url "http://clojars.org/repo" }
        mavenCentral()
    }
    dependencies { classpath "clojuresque:clojuresque:1.7.0" }
}


    project(':broker') {
        apply plugin: "c"
        apply plugin: "cunit"
    
        model {

        platforms {
            x86 {
                architecture "x86"
            }
        }
        repositories {
            libs(PrebuiltLibraries) {
                cunit {
                    headers.srcDir "G:/dev/libs/CUnit-2.1-3/headers"
                    binaries.withType(StaticLibraryBinary) {
                        staticLibraryFile = file("G:/dev/libs/CUnit-2.1-3/lib/cunitStaticLibrary/vs2013/cunit.lib")
                    }
                }
                jvm_1_8_x32 {
                    headers.srcDirs "G:/ProgX32/java/jdk1.8.0_20-32bit/include", "G:/ProgX32/java/jdk1.8.0_20-32bit/include/win32"
                    binaries.withType(StaticLibraryBinary) {
                        staticLibraryFile = file("G:/ProgX32/java/jdk1.8.0_20-32bit/lib/jvm.lib")
                    }
                }


            }
        }
        components {
            broker(NativeLibrarySpec) {
                targetPlatform "x86"
            }
        }
    }

    binaries.withType(CUnitTestSuiteBinarySpec) {
        lib library: "cunit", linkage: "static"
    }

    binaries.all {
        lib library: "jvm_1_8_x32", linkage: "static"

        cCompiler.define "WIN32_LEAN_AND_MEAN"
        cCompiler.define "UNICODE"
        cCompiler.define "NDEBUG"
        cCompiler.define "EXPORTING"

        if (toolChain in VisualCpp) {
            cCompiler.args "/EHsc"
            cCompiler.args "/Zi"
            cCompiler.args "/FS"
            linker.args "/DEBUG"        }
    }
}

project(':trader') {
    apply plugin: "clojure"
    apply plugin: "java"

    sourceSets {
        main {
            clojure {
                srcDir './trader/src/main/clojure/'
            }
        }
    }

    repositories {
        mavenCentral()
        clojarsRepo()
    }


    clojure.aotCompile = true

    sourceCompatibility = 1.8
    version = '0.0'

    dependencies {
        compile 'org.clojure:clojure:1.6.0'
        compile 'com.taoensso:timbre:4.1.0'
        compile group: 'junit', name: 'junit', version: '4.11'
        testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
    }
}

I have been unable to so far to get trader to build prior to broker, given that broker is first alphabetically.

adding:

dependencies {
    compile project(":trader")
}

into the broker build file fails with

Could not find method compile() for arguments [project ‘:trader’] on org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler_Decorated@1329683.

presumably because the java plugin is not defined in the project, however adding it breaks the broker build, which fails with

Could not find method lib() for arguments [{library=jvm_1_8_x32, linkage=static}] on classes ‘main’.

I have also tried adding

evaluationDependsOn(’:trader’)

into the broker build, with little success

It seems like it should be an easy thing to do and maybe I am missing something obvious. Thanks in advance.

I’m guessing you don’t need the compiled java project during the compilation of the C project (your build script doesn’t show such a dependency). In that case, I would attach the appropriate lifecycle level of the java process to your native component’s binary lifecycle task like so:

model {
    components {
        main(NativeExecutableSpec) {
            binaries.all { binary ->
                project.tasks.all { task ->
                    if (binary.name = task.name) {
                        dependsOn evaluationDependsOn(':trader').jar
                    }
                }
            }
        }
    }

We basically find the lifecycle task for the binary then we attach the dependency to a lifecycle task from the Java project. The evaluationDependsOn(’:trader’) is needed as you need to make sure the other project was evaluated. If you only use project(’:trader’) you may get a project without anything configured (not even the java dsl).

A better solution would probably use binary.tasks.build directly instead of finding the right task by name. However, it seems that property is only set when the binary is finalized. I will dig around a bit more to know if it’s a bug.

Don’t hesitate if you have more question.

Many thanks for the response, this works well. Out of interest how is the conditional met? I was thinking that binary.name would be ‘broker’, but then I would not expect there to be a task.name equal to ‘broker’ also, so the conditional would not be met (however clearly it is met).

Over the last day I had placed the broker and trader build files into separate files / subprojects and was playing with the root build too. This is quite nice as I can choose to build just the broker if i am working on the broker, same for the trader, or choose the root build if working on both (always building both takes time). I had renamed my broker to xbroker temporarily just to get the ordering correct. Is there a better way of changing the ordering of the sub-projects without setting up a specific dependency, such that the broker can be built without building the trader if required?

Thank you!

For your first question, lets start with the premise that Gradle only execute task. For that Gradle conveniently create task noted as lifecycle task that doesn’t do anything special except depending on the task that does the actual work (e.g. compiler task, linker task, etc.). It also help you to figure out how to build your project when doing executing ‘gradle tasks’. Basically, when you do ‘gradle mainExecutable’ you are executing the task named mainExecutable which was created from the binary name. At some point during the configuration, the task is create and once that happen I attach the jar task as a dependency. Like I mentioned, you should be able to get the lifecycle task for a binary through binary.tasks.build instead of this condition kungfu. I need to dig deeper to figure out why it didn’t work. Maybe a Gradle engineer could explain?

As for your other question, Gradle philosophy (as I understand it) is to make dependency clear. You can’t rely on any ordering without having to specify it through the api just like we did with evaluationDependsOn. Although it seems troublesome to specify every single dependencies, it make your build script much more explicit for everyone working on it and enable Gradle to work some optimization in the background. Most collection in Gradle are Set which never guarantee ordering. A side effect of bad dependencies is intermittent failure between builds. If you start having lots of inter-project dependency, it may also means that you need to reengineer your project separation or think differently about your build.

For building the broker without trader and also have the chance to build the broker with trader, I would use an assemble type task. In Java, you have the assemble lifecyle task which basically prepare your project for release. In the native world, this is not yet present. However, to make it future proof (as I speculate the future will introduce an assemble task), I would create an assemble task which depends on your native binary task and your java task (i.e. jar task). This is much simpler as you could do something like this:

task assemble {
    dependsOn "mainExecutable", evaluationDependsOn(':trader').jar
}

Don’t hesitate if you have more question.

Hi Daniel, that makes sense. Many thanks the detailed explanation!