plugins {
  id 'java-library'
  id 'base'
}

dependencies {
  api project(':javacutil')
  api project(':checker-qual')

  // Node implements org.plumelib.util.UniqueId, so this dependency must be "api".
  api "org.plumelib:plume-util:${versions.plumeUtil}"

  implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}"

  // External dependencies:
  // If you add an external dependency, you must shadow its packages both in the dataflow-shaded
  // artifact (see shadowJar block below) and also in checker.jar (see the comment in
  // ../build.gradle in the createDataflowShaded task).
}

// Shadowing Test Sources and Dependencies
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

/**
 * Creates a task with name dataflow${shadedPkgName} that creates a shaded dataflow jar. The packages will be shaded to
 * "org.checkerframework.{@code shadedPkgName}" and the jar name is dataflow-${shadedPkgName}.jar.
 * @param shadedPkgName
 * @return
 */
def createDataflowShaded(shadedPkgName) {
  tasks.create(name: "dataflow${shadedPkgName}Jar", type: ShadowJar, dependsOn: compileJava, group: 'Build') {
    description "Builds dataflow-${shadedPkgName}.jar."
    includeEmptyDirs = false
    base {
      archivesName = "dataflow-${shadedPkgName}"
    }
    dependencies {
      exclude(project(':checker-qual'))
    }
    // Without this line, the Maven artifact will have the classifier "all".
    archiveClassifier.set('')

    from shadowJar.source
    configurations = shadowJar.configurations

    destinationDirectory = file("${buildDir}/shadow/dataflow${shadedPkgName}")


    relocate('org.checkerframework', "org.checkerframework.${shadedPkgName}") {
      // Shade all Checker Framework packages, except for the dataflow qualifiers.
      exclude 'org.checkerframework.dataflow.qual.*'
    }

    // Relocate external dependencies
    relocate 'org.plumelib', "org.checkerframework.${shadedPkgName}.org.plumelib"
    relocate 'com.google', "org.checkerframework.${shadedPkgName}.com.google"
  }
}

// Creates a new shaded dataflow artifact.  To add a new one, add a new method call below, and add
// the new jar to publishing and signing blocks.
createDataflowShaded('shaded')
createDataflowShaded('nullaway')
createDataflowShaded('errorprone')

task manual(group: 'Documentation') {
  description 'Build the manual'
  doLast {
    exec {
      commandLine 'make', '-C', 'manual'
    }
  }
}

tasks.withType(Test) {
  dependsOn('allDataflowTests')
}

tasks.create(name: 'allDataflowTests', group: 'Verification') {
  description 'Run all dataflow analysis tests'
  // 'allDataflowTests' is automatically populated by testDataflowAnalysis().
}

/**
 * Create a task to run a dataflow analysis test case.
 * To add a new dataflow analysis test, you need to provide the
 * task name, the directory which contains the test files, and the fully-qualified class
 * name of the test class.
 * Optionally, when option {@code diff} is true, the output is compared against a
 * {@code Expected.txt} file.
 * See org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher for more details.
 * @param taskName the task name for the new task
 * @param dirName the directory name of the directory that contains the test files
 * @param className the fully-qualified name of the test class
 * @param diff whether to compare the generated output with a {@code Expected.txt} file
 * @return
 */
def testDataflowAnalysis(taskName, dirName, className, diff) {
  tasks.create(name: taskName, dependsOn: [assemble, compileTestJava], group: 'Verification') {
    description "Run the ${dirName} dataflow framework test."
    if (diff) {
      inputs.file("tests/${dirName}/Expected.txt")
    }
    inputs.file("tests/${dirName}/Test.java")

    outputs.file("tests/${dirName}/Out.txt")
    outputs.file("tests/${dirName}/Test.class")

    delete("tests/${dirName}/Out.txt")
    delete("tests/${dirName}/Test.class")
    doLast {
      javaexec {
        workingDir = "tests/${dirName}"
        if (!JavaVersion.current().java9Compatible) {
          jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()
        }
        else if (JavaVersion.current() > JavaVersion.VERSION_11) {
          jvmArgs += [
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED',
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
            '--add-opens',
            'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
          ]
        }

        classpath = sourceSets.test.runtimeClasspath
        classpath += sourceSets.test.output
        mainClass = "${className}"
      }
      if (diff) {
        exec {
          workingDir = "tests/${dirName}"
          executable 'diff'
          args = [
            '-u',
            'Expected.txt',
            'Out.txt'
          ]
        }
      }
    }
  }

  allDataflowTests.dependsOn << taskName
}

// When adding a new test case, don't forget to add the temporary files to `../.gitignore`.
testDataflowAnalysis("busyExpressionTest", "busyexpr", "busyexpr.BusyExpression", true)
testDataflowAnalysis("cfgConstructionTest", "cfgconstruction", "cfgconstruction.CFGConstruction", false)
testDataflowAnalysis("constantPropagationTest", "constant-propagation", "constantpropagation.ConstantPropagation", true)
testDataflowAnalysis("issue3447Test", "issue3447", "livevar.LiveVariable", false)
testDataflowAnalysis("liveVariableTest", "live-variable", "livevar.LiveVariable", true)
testDataflowAnalysis("reachingDefinitionTest", "reachingdef", "reachingdef.ReachingDefinition", true)

test {
  // The tests are run by allDataflowTests.
  exclude '**/*'
}

apply from: rootProject.file('gradle-mvn-push.gradle')

/** Adds information to the publication for uploading the dataflow artifacts to Maven repositories. */
final dataflowPom(publication) {
  sharedPublicationConfiguration(publication)
  publication.from components.java
  // Information that is in all pom files is configured in checker-framework/gradle-mvn-push.gradle.
  publication.pom {
    name = 'Dataflow'
    description = 'Dataflow is a dataflow framework based on the javac compiler.'
    licenses {
      license {
        name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
        url = 'http://www.gnu.org/software/classpath/license.html'
        distribution = 'repo'
      }
    }
  }
}

/**
 * Adds information to the publication for uploading the dataflow-${shadedPkgName} artifacts to Maven repositories.
 * @param shadedPkgName the name of the shaded package to use; also used as part of the artifact name: "dataflow-${shadePkgName}"
 */
final dataflowShadedPom(MavenPublication publication, String shadedPkgName) {
  sharedPublicationConfiguration(publication)

  publication.artifactId = "dataflow-${shadedPkgName}"
  publication.pom {
    name = "Dataflow (${shadedPkgName})"
    description =
        String.join(System.lineSeparator(),
        "dataflow-${shadedPkgName} is a dataflow framework based on the javac compiler.",
        '',
        'It differs from the org.checkerframework:dataflow artifact in two ways.',
        "First, the packages in this artifact have been renamed to org.checkerframework.${shadedPkgName}.*.",
        'Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.')
    licenses {
      license {
        name = 'GNU General Public License, version 2 (GPL2), with the classpath exception'
        url = 'http://www.gnu.org/software/classpath/license.html'
        distribution = 'repo'
      }
    }
    withXml {
      // This should just happen automatically, but I can't figure out how to make it happen with
      // a custom Shadow task.
      asNode().appendNode('dependencies').appendNode('dependency')
          .appendNode('groupId', 'org.checkerframework').parent()
          .appendNode('artifactId', 'checker-qual').parent()
          .appendNode('version', version).parent()
          .appendNode('scope','runtime')
    }
  }
}


publishing {
  publications {
    dataflow(MavenPublication) {
      dataflowPom it
    }

    dataflowShaded(MavenPublication) {
      dataflowShadedPom(it, 'shaded')
      artifact project.tasks.getByName('dataflowshadedJar').archiveFile
      artifact sourcesJar
      artifact javadocJar
    }

    dataflowShadednullaway(MavenPublication) {
      dataflowShadedPom(it, 'nullaway')

      artifact project.tasks.getByName('dataflownullawayJar').archiveFile
      artifact sourcesJar
      artifact javadocJar
    }

    dataflowShadederrorprone(MavenPublication) {
      dataflowShadedPom(it, 'errorprone')

      artifact project.tasks.getByName('dataflowerrorproneJar').archiveFile
      artifact sourcesJar
      artifact javadocJar
    }
  }
}

signing {
  sign publishing.publications.dataflow
  sign publishing.publications.dataflowShaded
  sign publishing.publications.dataflowShadednullaway
  sign publishing.publications.dataflowShadederrorprone
}

publishDataflowPublicationToMavenRepository.dependsOn(signDataflowShadedPublication)
publishDataflowPublicationToMavenRepository.dependsOn(signDataflowShadederrorpronePublication)
publishDataflowPublicationToMavenRepository.dependsOn(signDataflowShadednullawayPublication)

publishDataflowShadedPublicationToMavenRepository.dependsOn(signDataflowPublication)
publishDataflowShadedPublicationToMavenRepository.dependsOn(signDataflowShadederrorpronePublication)
publishDataflowShadedPublicationToMavenRepository.dependsOn(signDataflowShadednullawayPublication)

publishDataflowShadederrorpronePublicationToMavenRepository.dependsOn(signDataflowShadedPublication)
publishDataflowShadederrorpronePublicationToMavenRepository.dependsOn(signDataflowPublication)
publishDataflowShadederrorpronePublicationToMavenRepository.dependsOn(signDataflowShadednullawayPublication)

publishDataflowShadednullawayPublicationToMavenRepository.dependsOn(signDataflowShadederrorpronePublication)
publishDataflowShadednullawayPublicationToMavenRepository.dependsOn(signDataflowShadedPublication)
publishDataflowShadednullawayPublicationToMavenRepository.dependsOn(signDataflowPublication)
