Building libxml (or an autotools/Makefile based library)

Hello,

I am trying to build libxml2 with gradle, here is what I have so far:

task autoReconfigure(type : Exec) << {
    executable "autoreconf"
    args "-vif"
    workingDir "."
}

task configureTask(type : Exec, dependsOn : autoReconfigure) << {
    executable "./configure"
    args "--without-zlib"
    workingDir "."
}

task makeFileTask(type : Exec, dependsOn : configureTask) << {
    executable "make"
    args "-f", "Makefile"
    workingDir "."
}

build.dependsOn(makeFileTask)
assemble.dependsOn(makeFileTask)

This works, however this would rebuild the library everytime I call build or assemble. Also there is a problem that the tasks are rather repetitive for libraries like libpng or libfreetype so I refactored them into custom tasks into the folder:

buildSrc/src/main/groovy/com/ulabs/gradle

package com.ulabs.gradle

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.Project
import org.gradle.api.tasks.incremental.IncrementalTaskInputs

class AutoConfigureTask extends DefaultTask {
    @Input
    @Optional
    def extraArgs

    @TaskAction
    def execConfigure(IncrementalTaskInputs inputs) {
        println "Launching AutoConfigureTask from: " + project.projectDir

        if(!new File(project.projectDir, 'configure.ac').exists() && 
           !new File(project.projectDir, 'configure.in').exists()) {
            throw new FileNotFoundException(
                'autoconfigure task should have either configure.in or configure.ac ')
        }

        boolean outDated = false
        inputs.outOfDate { change ->
            outDated = true
        }

        if(outDated) {
            project.exec {
                executable "autoreconf"
                args "-ivf", hasProperty("extraArgs") ? extraArgs : ""
            }
        }
    }
}
package com.ulabs.gradle

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.Project
import org.gradle.api.tasks.incremental.IncrementalTaskInputs

class ConfigureTask extends DefaultTask {
    @Input
    @Optional
    def extraArgs

    @TaskAction
    def execConfigure(IncrementalTaskInputs inputs) {
        if(!new File(project.projectDir, 'configure').exists()) {
            throw new FileNotFoundException(
                'configure task should have a configure script')
        }

        boolean outDated = false
        inputs.outOfDate { change ->
            outDated = true
        }

        if(outDated) {
            project.exec {
                executable "./configure"
                args hasProperty("extraArgs") ? extraArgs : ""
            }
        }
    }
}

package com.ulabs.gradle

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.Project
import org.gradle.api.tasks.incremental.IncrementalTaskInputs

class MakefileTask extends DefaultTask {
    @Input
    @Optional
    def extraArgs

    @Input
    @Optional
    def env

    @TaskAction
    def execConfigure(IncrementalTaskInputs inputs) {
        if(!new File(project.projectDir, 'configure').exists()) {
            throw new FileNotFoundException(
                'makefile task should have a Makefile script')
        }

        boolean outDated = false
        inputs.outOfDate { change ->
            outDated = true
        }

        if(outDated) {
            project.exec {
                executable "make"
                args hasProperty("extraArgs") ? extraArgs : "-f Makefile"
            }
        }
    }
}

and in build.gradle:

apply plugin: 'c'

import com.ulabs.gradle.AutoConfigureTask
import com.ulabs.gradle.MakefileTask
import com.ulabs.gradle.ConfigureTask

gradle.allprojects {
    ext.getLibXmlLibsPath = {
        def distDir = new File(project(":libxml2").projectDir, "buildLibxml2")
        return distDir.toString()
    }
    ext.getLibXmlHeaderPath = {
        def hDir = new File(project(":libxml2").projectDir, "include")
        return hDir.toString()
    }
}

project(':libxml2') {
    model {
        toolChains {
            visualCpp(VisualCpp)
            gcc(Gcc)
            clang(Clang)
        }

        platforms {
            x86 {
                architecture "x86"
            }
            x64 {
                architecture "x86_64"
            }
            itanium {
                architecture "ia-64"
            }
        }

        components {
            xml2(NativeLibrarySpec) {
                sources {
                    c {
                        exportedHeaders {
                            srcDir "include"
                            include "libxml/*.h"
                        }
                        exportedHeaders {
                            srcDir "include"
                            include "win32config.h", "wsockcompat.h"
                        }
                        exportedHeaders {
                            srcDir "."
                            include "libxml.h"
                        }
                    }
                }
            }
        }

        repositories {
            libs(PrebuiltLibraries) {
                libxml2 {
                    headers.srcDir getLibXmlHeaderPath()
                    binaries.withType(StaticLibraryBinary) {
                        def baseDir = getLibXmlLibsPath()
                        staticLibraryFile = file("${baseDir}/libxml2.a")
                    }

                    binaries.withType(SharedLibraryBinary) {
                        def os = targetPlatform.operatingSystem
                        def baseDir = getLibXmlLibsPath()
                        if (os.windows) {
                            sharedLibraryFile = file("${baseDir}/libxml2.dll")
                            if (file("${baseDir}/util.lib").exists()) {
                                sharedLibraryLinkFile = file("${baseDir}/libxml2.lib")
                            }
                        } else if (os.macOsX) {
                            sharedLibraryFile = file("${baseDir}/libxml2.dylib")
                        } else {
                            sharedLibraryFile = file("${baseDir}/libxml2.so")
                        }
                    }
                }
            }
        }
    }
        task autoConfigTask(type: AutoConfigureTask) {
            println "Running configure Task ${project}"
        }

        task configureTask(type: ConfigureTask, dependsOn: autoConfigTask) {
            println "Running configure Task ${project}"
        }

        task makeFileTask(type: MakefileTask, dependsOn: configureTask) {
            println "Running makefile Task ${project}"
        }

        task make(dependsOn : makeFileTask) {
            def distDir = new File(getLibXmlLibsPath())
            doLast {
                delete distDir.toString()
                distDir.mkdirs()
                def binDir = projectDir.toString() + "/.libs"
                FileTree tree = fileTree(binDir.toString()) {
                    include 'libxml2*'
                    exclude '*.la*'
                }
                tree.each {File file ->
                   copy {
                       from file.toString()
                       into distDir.toString()
                   }
                }
            }
        }
}

build.dependsOn(make)
assemble.dependsOn(make)

Question 1: Now the problem is that when I do gradle build it says Cannot convert org.gradle.model.dsl.internal.NonTransformedModelDslBacking@1a256d80 to a task.. Could you suggest what is wrong?

Running with a --stacktrace gives me:

Caused by: org.gradle.internal.typeconversion.UnsupportedNotationException: Cannot convert org.gradle.model.dsl.internal.NonTransformedModelDslBacking@497d4c96 to a task.

Edit: So I’ve solved this one, I was just defining tasks inside the model

Question 2: Gradle now gives me that all tasks are up-to date. This probably has to do with inputs that I’ve set optional, but is there a way to achieve the following functionality:

I would like that:

  1. ./configure, autotools or make are not run unless their arguments change (extraArgs or env)
  2. when the arguments change, I’d like them all to run in a chain as specified by dependencies
  3. I would like libxml2 to act like a project that can be linked with others and be able to match platform/architecture of the dependant, with linkage api, shared or static