lucene/gradle/generation/regenerate.gradle

192 lines
7.5 KiB
Groovy

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<String, Object> extraConfig = [:] ->
def toList = { value ->
if (value instanceof List) {
return value
} else if (value == null) {
return []
} else {
return [ value ]
}
}
List<Object> andThenTasks = toList(extraConfig.get("andThenTasks"))
List<Object> 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<String, String> 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<String, String> 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<String, String> actual = checksumLoadTask.actualChecksums
Map<String, String> 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<String, String> 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
}
}
}