import groovy.json.JsonOutput import groovy.json.JsonSlurper import org.apache.commons.codec.digest.DigestUtils /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Create common 'regenerate' task sub-tasks can hook into. configure([ project(":lucene:analysis:common"), project(":lucene:core"), project(":lucene:analysis:icu"), project(":lucene:queryparser"), project(":lucene:analysis:kuromoji"), project(":lucene:analysis:nori") ]) { task regenerate() { description "Rerun any code or static data generation tasks." group "generation" } project.ext { // This utility method implements the logic required for "persistent" incremental // source-generating tasks. The idea is simple, the implementation quite complex. // // The idea is that, given source-generating task "sourceTask" we create // a bunch of other tasks that perform checksum generation, validation and sourceTask // skipping; example: // // ${sourceTask}ChecksumLoad // ${sourceTask}ChecksumSave // ${sourceTask}ChecksumCheck (fails if checksums are inconsistent) // maybe${sourceTask} dependsOn [checksum-load, sourceTask, checksum-save] // // Checksums are persisted and computed from sourceTask's inputs/outputs. If the // persisted checksums are identical to current checksums, sourceTask // is skipped (via sourceTask.onlyIf { false }). // // Implementation-wise things get complicated because gradle doesn't have the notion // of "ordered" task execution with respect to task AND its dependencies (we can add // constraints to each node in the execution graph but not node-and-dependencies). // // sourceTask - the task to wrap // extraConfig - a map with extra (optional) configuration options. // andThenTasks: other tasks that should be scheduled to run after source task and // before checksum calculation. wrapWithPersistentChecksums = { Task sourceTask, Map extraConfig = [:] -> def toList = { value -> if (value instanceof List) { return value } else if (value == null) { return [] } else { return [ value ] } } List andThenTasks = toList(extraConfig.get("andThenTasks")) List ignoreWithSource = toList(extraConfig.get("ignoreWithSource")) // Create checksum-loader task. Task checksumLoadTask = tasks.create("${sourceTask.name}ChecksumLoad", { ext { checksumMatch = true } doFirst { // Collect all of task inputs/ outputs. FileCollection allFiles = sourceTask.inputs.files + sourceTask.outputs.files ext.allFiles = allFiles // Compute checksums for root-project relative paths Map actualChecksums = allFiles.files.collectEntries { file -> [ sourceTask.project.rootDir.relativePath(file), file.exists() ? new DigestUtils(DigestUtils.sha1Digest).digestAsHex(file).trim() : "--" ] } ext.actualChecksums = actualChecksums // Load any previously written checksums ext.checksumsFile = project.file("src/generated/checksums/${sourceTask.name}.json") Map savedChecksums = [:] if (checksumsFile.exists()) { savedChecksums = new JsonSlurper().parse(checksumsFile) as Map } ext.savedChecksums = savedChecksums ext.checksumMatch = (savedChecksums.equals(actualChecksums)) } }) Task checksumCheckTask = tasks.create("${sourceTask.name}ChecksumCheck", { dependsOn checksumLoadTask doFirst { if (!checksumLoadTask.checksumMatch) { // This can be made prettier but leave it verbose for now: Map actual = checksumLoadTask.actualChecksums Map expected = checksumLoadTask.savedChecksums def same = actual.intersect(expected) actual = actual - same expected = expected - same throw new GradleException("Checksums mismatch for derived resources; you might have" + " modified a generated source file?:\n" + "Actual:\n ${actual.entrySet().join('\n ')}\n\n" + "Expected:\n ${expected.entrySet().join('\n ')}" ) } } }) check.dependsOn checksumCheckTask Task checksumSaveTask = tasks.create("${sourceTask.name}ChecksumSave", { dependsOn checksumLoadTask doFirst { File checksumsFile = checksumLoadTask.ext.checksumsFile checksumsFile.parentFile.mkdirs() // Recompute checksums for root-project relative paths Map actualChecksums = checksumLoadTask.ext.allFiles.files.collectEntries { file -> [ sourceTask.project.rootDir.relativePath(file), new DigestUtils(DigestUtils.sha1Digest).digestAsHex(file).trim() ] } checksumsFile.setText( JsonOutput.prettyPrint(JsonOutput.toJson(actualChecksums)), "UTF-8") logger.warn("Updated generated file checksums for task ${sourceTask.path}.") } }) Task conditionalTask = tasks.create("${sourceTask.name}IfChanged", { def deps = [ checksumLoadTask, sourceTask, *andThenTasks, checksumSaveTask ].flatten() dependsOn deps mustRunInOrder deps doFirst { if (checksumLoadTask.checksumMatch) { logger.lifecycle("Checksums consistent with sources, skipping task: ${sourceTask.path}") } } }) // Copy the description and group from the source task. project.afterEvaluate { conditionalTask.group sourceTask.group conditionalTask.description sourceTask.description + " (if sources changed)" } // Set conditional execution only if checksum mismatch occurred. if (!gradle.startParameter.isRerunTasks()) { project.afterEvaluate { resolveTaskRefs([sourceTask, *ignoreWithSource, checksumSaveTask]).each { t -> t.configure { logger.info("Making " + t.name + " run only if " + checksumLoadTask.name + " indicates changes") onlyIf { !checksumLoadTask.checksumMatch } } } } } return conditionalTask } } }