description = "Apache Tapestry 5 Project"

import org.apache.tools.ant.filters.ReplaceTokens

apply plugin: "base"
apply plugin: "sonar"

apply from: "ssh.gradle"
apply from: "md5.gradle"

project.ext.versions = [
    jetty: "7.6.11.v20130520",
    tomcat: "6.0.30",
    testng: "6.8.21",
    easymock: "3.3.1",
    servletapi: "2.5",
    spock: "1.0-groovy-2.3",
    hibernate: "4.3.6.Final",
    slf4j: "1.7.12",
    geb: "0.10.0",
    selenium: "2.46.0"
]

ext.continuousIntegrationBuild = Boolean.getBoolean("ci")

// Provided so that the CI server can override the normal version number for nightly builds.
project.version = tapestryVersion()

// Remember that when generating a release, this should be incremented. Also don"t forget to
// tag the release in Subversion.
// Version number is always "5.x(.y)?-SNAPSHOT" and only gets fixed, e.g. to 5.4-alpha-1
// during a release

def tapestryVersion() {

    def major = "5.4"
    def minor = "-beta-35"

    // When building on the CI server, make sure -SNAPSHOT is appended, as it is a nightly build.
    // When building normally, or for a release, no suffix is desired.
    continuousIntegrationBuild ? major + "-SNAPSHOT" : major + minor
}

// Let analysis.apache.org get in touch with our builds

project.ext {
    sonarUrl = System.getProperty("sonarUrl", "https://analysis.apache.org");
    sonarDbUrl = System.getProperty("sonarDbUrl", "");
    sonarDbUsername = System.getProperty("sonarDbUsername", "");
    sonarDbPassword = System.getProperty("sonarDbPassword", "");
    sonarDbDriverClassName = "com.mysql.jdbc.Driver"

    stagingUrl = "https://repository.apache.org/service/local/staging/deploy/maven2/"
    snapshotUrl = "https://repository.apache.org/content/repositories/snapshots"

    doSign = !project.hasProperty("noSign") && project.hasProperty("signing.keyId")

    // apacheDeployUserName and apacheDeployPassword should be specified in ~/.gradle/gradle.properties

    deployUsernameProperty = isSnapshot() ? "snapshotDeployUserName" : "apacheDeployUserName"
    deployPasswordProperty = isSnapshot() ? "snapshotDeployPassword" : "apacheDeployPassword"

    canDeploy = [deployUsernameProperty, deployPasswordProperty, "apacheArchivesFolder"].every { project.hasProperty(it) }

    // These are all deferred inside closures, to allow people without the necessary values in their
    // gradle.properties to build locally, just not deploy. getProperty() throws an exception if the property
    // is not present.
    deployUsername = { getProperty(deployUsernameProperty) }
    deployPassword = { getProperty(deployPasswordProperty) }

    archiveDeployFolder = { getProperty("apacheArchivesFolder") }
}

sonar {
    server {
        url = sonarUrl
    }
    database {
        url = sonarDbUrl
        driverClassName = sonarDbDriverClassName
        username = sonarDbUsername
        password = sonarDbPassword
    }
}

allprojects {

    apply plugin: "eclipse"
    apply plugin: "idea"
    apply plugin: "signing"


    repositories {
        mavenCentral()

        // All things JBoss/Hibernate
        maven {
            name "JBoss"
            url "https://repository.jboss.org/nexus/content/repositories/releases/"
        }
    }

    configurations {
        // Non-code artifacts, such as sources JARs and zipped JavaDocs
        meta
    }

}

idea {
    project {
        languageLevel = "1.6"
        // But this is what most (all?) of the devs are actually using:
        jdkName = "1.7"
    }
}

// Specific to top-level build, not set for subprojects:

configurations {
    javadoc
    published.extendsFrom archives, meta
    if (doSign) {
        published.extendsFrom signatures
    }
    binaries // additional dependencies included in the binary archive
}

dependencies {
    javadoc project(":tapestry-javadoc")

    // From tapestry-ioc:
    binaries "javax.inject:javax.inject:1"
    binaries "org.slf4j:slf4j-api:${versions.slf4j}"
    binaries "commons-codec:commons-codec:1.5"
    binaries "org.antlr:antlr-runtime:3.3", { transitive = false }
}

subprojects {
    version = parent.version

    group = "org.apache.tapestry"

    configurations {
        provided
    }

    apply plugin: "java"
    apply plugin: "groovy" // mostly for testing
    apply plugin: "maven"  // for deployment
    apply plugin: "project-report"
    apply plugin: "jacoco"

    sourceCompatibility = "1.6"
    targetCompatibility = "1.6"

    // See http://jira.codehaus.org/browse/GRADLE-784

    sourceSets {
        main {
            compileClasspath += [configurations.provided]
        }
        test {
            compileClasspath += [configurations.provided]
            runtimeClasspath += [configurations.provided]
        }
    }

    idea.module {
        scopes.PROVIDED.plus += [configurations.provided]
    }

    eclipse.classpath.plusConfigurations += [configurations.provided]

    dependencies {
        
        testCompile "org.spockframework:spock-core:${versions.spock}"
         
        testRuntime "org.slf4j:slf4j-log4j12:${versions.slf4j}"
    }

    compileTestGroovy {
        configure(groovyOptions.forkOptions) {
            memoryMaximumSize = '1g'
            jvmArgs = ['-XX:MaxPermSize=512m', '-Xms512m', '-Xmx1g']
        }
    }

    test {
        useTestNG()

        options.suites("src/test/conf/testng.xml")

        maxHeapSize "800M"

        // Turn off live service reloading

        systemProperties["tapestry.service-reloading-enabled"] = "false"
        systemProperties["java.io.tmpdir"] = temporaryDir.absolutePath

        jvmArgs("-XX:MaxPermSize=512m", "-Dfile.encoding=UTF-8")

        environment.LANG = 'en_US.UTF-8'
    }

    jar {
        from(projectDir) {
            include "*.txt"
            into "META-INF"
        }
    }

    task sourcesJar(type: Jar) {
        dependsOn classes
        classifier "sources"
        from sourceSets.main.allSource
        from(projectDir) {
            include "*.txt"
            into "META-INF"
        }
    }

    artifacts {
        archives sourcesJar
        meta sourcesJar
    }


    configurations {
        // published -- what gets uploaded to the Nexus repository
        published.extendsFrom archives, meta

        if (rootProject.doSign) {
            published.extendsFrom signatures
        }
    }

    if (rootProject.doSign) {
        // sign (create PGP signature for) archives (standard JARs)
        // and meta (sources JARs)
        signing { sign configurations.archives, configurations.meta }
    }

    uploadPublished {

        doFirst {
            if (!canDeploy) {
                throw new InvalidUserDataException("Missing upload credentials. Set '$deployUsernameProperty' and '$deployPasswordProperty' root project properties.")
            }
        }

        if (canDeploy) {
            repositories {

                project.ext.deployer = repositories.mavenDeployer {

                    if (doSign) {
                        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
                    }

                    repository(url: stagingUrl) {
                        authentication(userName: deployUsername(), password: deployPassword())
                    }

                    snapshotRepository(url: snapshotUrl) {
                        authentication(userName: deployUsername(), password: deployPassword())
                    }
                }
            }
        }
    }
}

subprojects.each { project.evaluationDependsOn(it.name) }

subprojects {

    // Prefer this version, because it has source artifacts available.
    def servletAPI = 'org.mortbay.jetty:servlet-api-2.5:6.1.11'

    configurations.all {

        resolutionStrategy.force "antlr:antlr:2.7.7",
            "cglib:cglib-nodep:2.2",
            "commons-codec:commons-codec:1.10",
            "commons-io:commons-io:2.4",
            "commons-logging:commons-logging:1.1.3",
            "hsqldb:hsqldb:2.2.8",
            "org.antlr:antlr-runtime:3.5.2",
            "org.apache.tomcat:dbcp:6.0.32",
            "org.hamcrest:hamcrest-core:1.3",
            "org.json:json:20140107",
            "org.yaml:snakeyaml:1.8",
            "xml-apis:xml-apis:1.4.01"

        resolutionStrategy.eachDependency { DependencyResolveDetails details ->


            if (details.requested.name.startsWith("servlet-api")) {
                logger.info "Overriding dependency ${details.requested.group}:${details.requested.name}:${details.requested.version} --> $servletAPI"
                details.useTarget servletAPI
            }

        }
    }
}

// Cribbed from https://github.com/hibernate/hibernate-core/blob/master/release/release.gradle#L19

task aggregateJavadoc(type: Javadoc) {
    doFirst {
        // Temporary: don't check on the CI server. This can be removed when the CI server is upgraded.
        if (!continuousIntegrationBuild && !checkJDK())
            throw new StopActionException("Update your JDK to fix VU#225657")
    }
    dependsOn configurations.javadoc
    group "Documentation"

    description "Build the aggregated JavaDocs for all modules"
    maxMemory "512m"
    destinationDir file("$buildDir/documentation/javadocs")

    def tapestryStylesheet = isJDKAfter6() ? file("src/javadoc/stylesheet7.css") : file("src/javadoc/stylesheet.css");

    configure(options) {
        splitIndex true
        linkSource true
        stylesheetFile tapestryStylesheet
        windowTitle "Tapestry API - ${project.version}"
        header "Tapestry API - ${project.version}"
        docTitle "Tapestry API - ($project.version)"
        bottom '${project.version} - Copyright &copy; 2003-2015 <a href="http://tapestry.apache.org">The Apache Software Foundation</a>.'
        use = true // 'use' seems to be a reserved word for the DSL
        links "http://download.oracle.com/javase/6/docs/api/"
        links "http://download.oracle.com/javaee/6/api/"
        addStringOption "tagletpath", configurations.javadoc.asPath
        addStringOption "taglet", "org.apache.tapestry5.javadoc.TapestryDocTaglet"
        exclude "org/apache/tapestry5/internal/plastic/asm/**"
    }

    def allMainSourceSets = subprojects*.sourceSets*.main.flatten()
    def allMainJavaFiles = allMainSourceSets*.java
    def allMainJavaSrcDirs = allMainJavaFiles*.srcDirs

    source allMainJavaFiles

    classpath += files(allMainSourceSets*.compileClasspath)

    inputs.files allMainJavaSrcDirs

    // As part of generating the documentation, ALSO copy any related files:
    // Any extra images (Tapestry logo)
    // Any images stored under src/main/java ... everything but .java, .xdoc and package.html

    doLast {
        copy {
            from allMainJavaSrcDirs
            into aggregateJavadoc.destinationDir
            exclude "**/*.java"
            exclude "**/*.xdoc"
            exclude "**/package.html"
        }

        copy {
            from file("src/javadoc/images")
            into aggregateJavadoc.destinationDir
        }
    }
}

task coffeeScriptDocs(type: Exec) {
    group "Documentation"
    description "Build docco documentation for all CoffeeScript sources"
    dependsOn project(":tapestry-core").tasks.preprocessCoffeeScript

    def outputDir = file("$buildDir/documentation/coffeescript")

    def sources = files()

    subprojects.each { sub ->
        sources += sub.fileTree("src/main/coffeescript", { include "**/*.coffee" })
    }

    sources += project(":tapestry-core").tasks.preprocessCoffeeScript.outputs.files.asFileTree

    // Needs to be installed via "npm install -g docco@0.6.3"
    executable isWindows() ? "docco.cmd" : "docco"
    args "--output", outputDir
    args sources.files.sort({ a, b -> a.name.compareTo b.name })
}



dependencies {
    meta aggregateJavadoc.outputs.files
}

task combinedJacocoReport(type:JacocoReport){
  def subprojectsToConsider = subprojects.findAll {it.name != 'quickstart'}
  dependsOn = subprojectsToConsider.test
  additionalSourceDirs = files(subprojectsToConsider.sourceSets.main.allSource.srcDirs)
  sourceDirectories = files(subprojectsToConsider.sourceSets.main.allSource.srcDirs)
  classDirectories = files(subprojectsToConsider.sourceSets.main.output)
  executionData = files(subprojectsToConsider.jacocoTestReport.executionData)
  jacocoClasspath = files(subprojectsToConsider.jacocoTestReport.jacocoClasspath)
  reports {
      html {
        enabled = true
        destination = "$buildDir/reports/jacoco"
      }
      xml.enabled = false
      csv.enabled = false
  }
  onlyIf = {
      true
  }
  doFirst {
      executionData = files(executionData.findAll {
          it.exists()
      })
  }
}

task continuousIntegration {
    dependsOn subprojects.build, aggregateJavadoc, combinedJacocoReport
    description "Task executed on Jenkins CI server after SVN commits"
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.6'
    description "Regenerates the Gradle Wrapper files"
}

task zippedSources(type: Zip) {
    description "Creates a combined Zip file of all sub-project's sources"
    group "Release artifact"

    destinationDir buildDir
    baseName "apache-tapestry"
    version project.version
    classifier "sources"

    from project.projectDir
    exclude "out/**"
    exclude "**/*.iml"
    exclude "**/*.ipr"
    exclude "**/*.iws"
    exclude "**/.*/**"
    exclude "**/bin/**"
    exclude "**/target/**"
    exclude "**/build/**"
    exclude "**/test-output/**"  // Left around by TestNG sometimes
}

task zippedApidoc(type: Zip) {
    dependsOn aggregateJavadoc
    description "Zip archive of the project's aggregate JavaDoc and CoffeeScript documentation"
    group "Release artifact"

    destinationDir buildDir
    baseName "apache-tapestry"
    version project.version
    classifier "apidocs"

    from file("src/docroot-template"), {
        filter ReplaceTokens, tokens: [version: project.version]
        include "*.html"
    }

    from file("src/docroot-template"), {
        exclude "*.html"
    }

    into "apidocs", { from aggregateJavadoc.outputs.files }


    into "coffeescript", { from coffeeScriptDocs.outputs.files }

}

task zippedBinaries(type: Zip) {
    description "Zip archive of binaries of each sub-project"
    // TODO: Plus dependencies?
    group "Release artifact"
    // This may create a few unwanted dependencies, but does
    // seem to ensure that the subprojects are created
    inputs.files subprojects*.configurations*.archives.artifacts.files

    destinationDir buildDir
    baseName "apache-tapestry"
    version project.version
    classifier "bin"

    // This is via some experimentation
    from subprojects*.configurations*.archives.artifacts*.file*.findAll {
        !(it.name.endsWith(".asc") || it.name.startsWith("quickstart"))
    }

    from configurations.binaries

    // Pick up various licenses and notices

    from(projectDir) {
        include "*.txt"
    }

    subprojects.each { sub ->
        from(sub.projectDir) {
            include "*.txt"
            into sub.name
        }
    }
}

if (canDeploy) {

    configurations {
        archives
        uploads.extendsFrom archives, signatures
    }


    artifacts {
        archives zippedApidoc, zippedSources, zippedBinaries
    }

    configurations {
        upload.extendsFrom archives, signatures
    }

    task generateMD5Checksums(type: GenMD5) {
        group "Release artifact"
        description "Creates MD5 checksums for archives of source and JavaDoc"
        source tasks.withType(Zip)
        outputDir "$buildDir/md5"
    }

    if (doSign) {
        signing {
            sign configurations.archives
        }
    }

    // This requires that you have the apacheArchivesFolder property configured in your
    // ~/.gradle/gradle.properties. The folder should be a Subversion workspace for
    // https://dist.apache.org/repos/dist/dev/tapestry
    // after the build, you must manually add the new files to the workspace (using "svn add")
    // then commit ("svn commit").

    // The files will be visible in https://dist.apache.org/repos/dist/dev/tapestry/, allowing
    // committers to download and verify them.

    // After a successful release vote, the files can be moved to a second Subversion workspace
    // for https://dist.apache.org/repos/dist/release/tapestry. Adding the files and committing
    // there will publish them to http://www.apache.org/dist/tapestry ... and from there
    // to all Apache mirrors (after about a 24 hour delay).

    task copyArchives(type: Copy) {
        group "Release artifact"
        description "Copies build archives (source, bin, docs) to a configured deployment folder, along with MD5 checksums and PGP signatures (if signing is enabled)"

        destinationDir file(archiveDeployFolder())

        from generateMD5Checksums
        from configurations.uploads.allArtifacts.files
    }

    task generateRelease {
        dependsOn subprojects.uploadPublished, copyArchives
        group "Release artifact"
        description "Generates and uploads a final release to Apache Nexus and copies archives for deployment"
    }
}

boolean isSnapshot() {
    project.version.contains("SNAPSHOT")
}

boolean isWindows() {
    System.properties['os.name'].toLowerCase().contains('windows')
}

boolean isJDKAfter6() {
    ! System.properties['java.version'].matches('1\\.[5|6]\\..*')
}

// Check JDK version to prevent VU#225657 see:
// http://www.oracle.com/technetwork/topics/security/javacpujun2013-1899847.html
// http://www.kb.cert.org/vuls/id/225657
boolean checkJDK() {
    def jdkVersion = System.properties['java.version']
    def match = jdkVersion =~ /_(\d+)/

    if (!match.find())
        throw new IllegalStateException("""Could not parse minor version number out of "${jdkVersion}".""")

    def minor = match[0][1].toInteger()

    if (jdkVersion.startsWith("1.7")) {
        minor > 21
    }
    else if (jdkVersion.startsWith("1.5") || jdkVersion.startsWith("1.6")) {
        // JDK 6 and 5 require the same minor version
        minor > 45
    }
    else {
        true
    }
}

task updateBootstrap << {
  def bootstrapVersion = '3.3.5'
  def target = new File(temporaryDir, 'bootstrap.zip')
  ant.get(src: "https://github.com/twbs/bootstrap/archive/v${bootstrapVersion}.zip", dest: target)

  def adjustDirectory = {
      def relativePath = it.relativePath
      if (relativePath.pathString.contains('/dist/')){
          relativePath = new RelativePath(!it.file.isDirectory(), relativePath.segments[2..-1] as String[])
      } else {
          relativePath = new RelativePath(!it.file.isDirectory(), relativePath.segments[1..-1] as String[])
      }
      println "copying $it.relativePath to $relativePath"
      it.relativePath = relativePath

  }

  copy {
    from(zipTree(target)){
        include('*/js/*.js')
        include('*/dist/fonts/*')
        eachFile adjustDirectory
    }
    from(zipTree(target)){
        include('*/dist/css/bootstrap.css')
        include('*/dist/css/bootstrap-theme.css')
        eachFile adjustDirectory
        // TAP5-2351: remove source map reference from css files
        filter({ (it ==~ /\/\*\s*# sourceMappingURL=[\S]+\s*\*\//) ? "" : it })
    }
    into('tapestry-core/src/main/resources/META-INF/assets/tapestry5/bootstrap/')
  }

  copy {
    from(zipTree(target)){
        include('*/js/*.js')
        include('*/dist/fonts/*')
        include('*/less/**/*.less')

        eachFile adjustDirectory
    }
    into('tapestry-webresources/src/test/webapp/bootstrap/')
  }
}
