Building libxml with gradle
Question: Could you please share your wisdom on cross-compilation of a library like
`libxml2`, `libpng`, `libfreetype` that has a configure script and a Makefile
for android and other hosts like linux, windows and Mac Os X using **gradle**?

At the moment I do not have a complete working example for either libraries
but would like to have a solution similar to the following:

<https://github.com/couchbase/couchbase-lite-java-
native/blob/master/crosscompile-build.gradle>

    
    
    //
    // To cross compile to ARM replace the default build.gradle with this file.
    // Before running the build install these additional linux packages
    //
    // gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
    //
    apply plugin: 'c'
    apply plugin: 'java'
    apply plugin: 'maven'
    
    version = System.getenv("MAVEN_UPLOAD_VERSION")
    
    model {
        platforms {
            osx_x86 {
                architecture "x86"
                operatingSystem "osx"
            }
            osx_x86_64 {
                architecture "x86_64"
                operatingSystem "osx"
            }
            linux_x86 {
                architecture "x86"
                operatingSystem "linux"
            }
            linux_x86_64 {
                architecture "x86_64"
                operatingSystem "linux"
            }
            linux_amd64 {
                architecture "amd64"
                operatingSystem "linux"
            }
            linux_arm {
                architecture "arm"
                operatingSystem "linux"
            }
            windows_x86 {
                architecture "x86"
                operatingSystem "windows"
            }
            windows_x86_64 {
                architecture "x86_64"
                operatingSystem "windows"
            }
            windows_amd64 {
                architecture "amd64"
                operatingSystem "windows"
            }
        }
    
        toolChains {
            visualCpp(VisualCpp)
            gcc(Gcc)
            gccArm(Gcc) {
                getCppCompiler().setExecutable 'arm-linux-gnueabihf-g++'
                getCCompiler().setExecutable 'arm-linux-gnueabihf-gcc'
                getAssembler().setExecutable 'arm-linux-gnueabihf-as'
                getLinker().setExecutable 'arm-linux-gnueabihf-gcc'
                getStaticLibArchiver().setExecutable 'arm-linux-gnueabihf-ar'
                addPlatformConfiguration(new ArmSupport())
            }
            clang(Clang)
        }
    }
    
        class ArmSupport implements TargetPlatformConfiguration {
            boolean supportsPlatform(Platform element) {
                return element.getArchitecture().name == "arm"
            }
    
            List<String> getCppCompilerArgs() {
                []
            }
    
            List<String> getObjectiveCppCompilerArgs() {
                []
            }
    
            List<String> getObjectiveCCompilerArgs() {
                []
            }
    
            List<String> getCCompilerArgs() {
                []
            }
    
            List<String> getAssemblerArgs() {
                []
            }
    
            List<String> getLinkerArgs() {
                []
            }
    
            List<String> getStaticLibraryArchiverArgs() {
                []
            }
        }
    
    sources {
        native_library {
            c {
                source {
                    srcDir "src/main/c"
                }
                exportedHeaders {
                    srcDir "src/main/include"
                }
            }
        }
    }
    
    libraries {
        native_library {
            baseName "CouchbaseLiteJavaNative"
        }
    
        all {
            binaries.withType(SharedLibraryBinary) { binary ->
                if (targetPlatform.operatingSystem.macOsX) {
                    cCompiler.args '-I', "/System/Library/Frameworks/JavaVM.framework/Headers"
                    linker.args '-framework', "JavaVM"
                } else if (targetPlatform.operatingSystem.linux) {
                    cCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
                    cCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"
                } else if (targetPlatform.operatingSystem.windows) {
                    cCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
                    cCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"
                    linker.args "--add-stdcall-alias"
                }
            }
        }
    }
    
    binaries.withType(SharedLibraryBinary) { binary ->
        if (!buildable) {
            return
        }
    
        def builderTask = binary.tasks.builder
    
        jar.into("native/${targetPlatform.operatingSystem.name}/${targetPlatform.architecture.name}") {
                from builderTask.outputFile
        }
    
        jar.dependsOn builderTask
    }
    
    task createMavenDirectory(type: Exec) {
        ext {
            uploadUser = System.getenv("MAVEN_UPLOAD_USERNAME") + ":" + System.getenv("MAVEN_UPLOAD_PASSWORD")
            mkcolPath  = System.getenv("MAVEN_UPLOAD_REPO_URL") + "com/couchbase/lite/java-native/" + version + "/"
        }
        commandLine "curl", "--user", uploadUser, "-X", "MKCOL", mkcolPath
    }
    
    // this hack is only needed for apache mod_dav based Maven repo's like file.couchbase.com.  otherwise, skip it
    createMavenDirectory.onlyIf { System.getenv("MAVEN_UPLOAD_REPO_URL").contains("files") }
    
    // first create the directory, then do the upload
    task uploadArchivesWrapper(dependsOn: createMavenDirectory) << {
        uploadArchives.execute()
    }
    
    // this will upload, but will not first create a directory (which is needed on some servers)
    uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: System.getenv("MAVEN_UPLOAD_REPO_URL")) {
                    authentication(userName: System.getenv("MAVEN_UPLOAD_USERNAME"), password: System.getenv("MAVEN_UPLOAD_PASSWORD"))
                }
                pom.version = version
                pom.groupId = 'com.couchbase.lite'
                pom.artifactId = 'java-native'
                pom.project {
                    licenses {
                        license {
                            name 'Couchbase Community Edition License Agreement'
                            url 'http://www.couchbase.com/agreement/community'
                            distribution 'repo'
                        }
                    }
                }
            }
        }
    }
    
    task sourcesJar(type: Jar, dependsOn: classes) {
        classifier = 'sources'
        from sourceSets.main.java.srcDirs
    }
    
    artifacts {
        archives sourcesJar
    }
    

It would be ideal to use the toolchain provided by **Android NDK** and not
specify **gnueabi** and link everything statically.

## Edit

I have extended the build file quite a bit, however I am getting `UP-TO DATE`
for all of the tasks:

The setup is: `libxml2-2.9.2`

`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) << {
            extraArgs ""
        }
    
        task configureTask(type: ConfigureTask, dependsOn: autoConfigTask) << {
            extraArgs "--without-python", "--without-zlib"
        }
    
        task makeFileTask(type: MakefileTask, dependsOn: configureTask) << {
            println "Running makefile Task ${project}"
        }
    
        task make(dependsOn : makeFileTask) << {
            def distDir = new File(getLibXmlLibsPath())
            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)
    

`buildSrc/src/main/groovy/com/ulabs/gradle/*.groovy` contains:

    
    
    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"
                }
            }
        }
    }
    

This most probably has something to do with inputs to tasks or messed up
condiguration/runtime stages of the tasks however I tried many options and
none seem to work.

So in the end I would like to have:

  * `./configure`, `autotools` or `make` are not run unless their arguments change (`extraArgs` or `env`)
  * when the arguments change, I'd like them all to run in a chain as specified by dependencies
  * 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


Answer: So it seems its better to just call the build system that is shipped with the
library itself.

Here is the script that does it:

    
    
    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)
    

I will try to add support for different toolchains/platforms as I go along

