Files
Radixor/gradle/paicehusk-benchmarks.gradle
Leo Galambos e7800b29c9 feat!: modularize stemmer models and release infrastructure
Move bundled stemmer dictionaries from the core artifact into independently
versioned model modules. Add model discovery and explicit model-loading APIs,
a standard model aggregate, a model BOM, and dedicated model and catalog
release workflows.

Add full PoliMorf integration, model provenance and licensing validation,
streaming model-input verification, strict dependency verification, consumer
resolution tests, Configuration Cache compatibility, and expanded JMH,
quality, documentation, and release checks.

Upgrade the CycloneDX and JMH Gradle plugins and remove Gradle 10 and Java
compiler deprecations.

BREAKING CHANGE: The core Radixor artifact no longer contains bundled stemmer
dictionaries. Applications must add the required model artifacts, the standard
model aggregate, or model dependencies managed through the Radixor model BOM.
2026-07-22 23:33:28 +02:00

225 lines
7.9 KiB
Groovy

import org.gradle.plugins.ide.eclipse.model.SourceFolder
def paicehuskVersion = 'master'
def paicehuskArchiveName = "paice-husk-stemmer-${paicehuskVersion}.zip"
def paicehuskDownloadUrl = "https://github.com/Hopper262/paice-husk-stemmer/archive/refs/heads/${paicehuskVersion}.zip"
def paicehuskDownloadFile = layout.buildDirectory.file("third-party/paicehusk/${paicehuskArchiveName}")
def paicehuskExtractDirectory = layout.buildDirectory.dir('third-party/paicehusk/source')
def paicehuskArchiveDirectory = paicehuskExtractDirectory.map { it.dir('paice-husk-stemmer-master') }
def paicehuskJavaFile = paicehuskArchiveDirectory.map { it.file('paicehusk_java.java') }
def paicehuskRulesFile = paicehuskArchiveDirectory.map { it.file('paicehusk_rules.txt') }
def paicehuskGeneratedSourceDirectory = layout.buildDirectory.dir('generated/sources/paicehusk')
def paicehuskGeneratedStemmerFile = paicehuskGeneratedSourceDirectory.map { it.file('org/egothor/stemmer/benchmark/PaiceHuskLancasterStemmer.java') }
def paicehuskGeneratedSourcePath = provider {
project.relativePath(paicehuskGeneratedSourceDirectory.get().asFile)
}
def paicehuskSourceEclipseClasspathAttributes = [
gradle_scope : 'jmh',
gradle_used_by_scope: 'jmh',
test : 'true'
]
def paicehuskIsAbsolutePath = { String path ->
path.startsWith('/') || path ==~ /^[A-Za-z]:[\\\/].*/
}
def paicehuskGeneratedNotice = '''
/**
* Generated at benchmark execution time from upstream
* https://github.com/Hopper262/paice-husk-stemmer .
*
* This source copy is compiled only for the JMH benchmark source set and is
* not committed as production code.
*/
'''
def escapeForJava = { final String text ->
return text.replace('\\\\', '\\\\\\\\')
.replace('\"', '\\\"')
}
def toRuleLines = { final File rulesFile ->
final List<String> lines = rulesFile.readLines('UTF-8')
final StringBuilder ruleLines = new StringBuilder()
for (int index = 0; index < lines.size(); index++) {
final String line = lines.get(index)
ruleLines.append(' "')
ruleLines.append(escapeForJava(line))
ruleLines.append('"')
if (index < lines.size() - 1) {
ruleLines.append(',')
}
ruleLines.append('\n')
}
return ruleLines.toString()
}
def paicehuskEngineInsertion = { final String ruleLines ->
return """
public static final String[] RULE_LINES = {
${ruleLines}
};
private static final java.util.HashMap RULES = createRulesFromEmbeddedRules();
/**
* Creates benchmark stemmer instance.
*/
public PaiceHuskLancasterStemmer() {
}
/**
* Applies Paice/Husk stemming to one token.
*
* @param token input token
* @return stemmed token
*/
public String stem(final String token) {
if (token == null) {
return null;
}
return stemWord(token, RULES, null);
}
/**
* Loads bundled rule lines directly from the generated benchmark source.
*
* @return initialized rule map
*/
private static java.util.HashMap createRulesFromEmbeddedRules() {
try {
final java.io.File ruleFile = java.io.File.createTempFile("paicehusk-rules", ".txt");
ruleFile.deleteOnExit();
try (java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.FileWriter(ruleFile))) {
for (String line : RULE_LINES) {
writer.println(line);
}
}
return loadRules(ruleFile.getAbsolutePath());
} catch (Exception exception) {
throw new IllegalStateException("Unable to initialize benchmark Paice/Husk rules.", exception);
}
}
"""
}
def transformPaiceHuskSource = { final File sourceFile, final File rulesFile, final File targetFile ->
if (!sourceFile.exists()) {
throw new GradleException("Paice/Husk Java source was not available at ${sourceFile}.")
}
if (!rulesFile.exists()) {
throw new GradleException("Paice/Husk rule file was not available at ${rulesFile}.")
}
final String sourceText = sourceFile.getText('UTF-8')
String transformedText = sourceText
transformedText = 'package org.egothor.stemmer.benchmark;' + '\n\n' + transformedText
transformedText = transformedText.replaceFirst(/(?m)^\s*class\s+PaiceHusk\s*\{/, 'public final class PaiceHuskLancasterStemmer {')
transformedText = transformedText.replace('new Character(rule.letter)', 'Character.valueOf(rule.letter)')
transformedText = transformedText.replace('new Character(stem.charAt(stem.length() - 1))',
'Character.valueOf(stem.charAt(stem.length() - 1))')
transformedText = transformedText.replaceFirst(/(?m)^(\s*)static HashMap loadRules\(/,
'$1@SuppressWarnings("unchecked")\n$1static HashMap loadRules(')
final int packageEnd = transformedText.indexOf('\n', transformedText.indexOf('package org.egothor.stemmer.benchmark;'))
if (packageEnd >= 0) {
transformedText = transformedText.substring(0, packageEnd + 1) + '\n' + paicehuskGeneratedNotice + transformedText.substring(packageEnd + 1)
}
final String marker = '\n} // end class PaiceHusk'
final int markerIndex = transformedText.lastIndexOf(marker)
if (markerIndex < 0) {
throw new GradleException("Unexpected Paice/Husk source structure at ${sourceFile}.")
}
final String replacement = paicehuskEngineInsertion(toRuleLines(rulesFile))
transformedText = transformedText.substring(0, markerIndex) + '\n' + replacement + '\n}'
targetFile.parentFile.mkdirs()
targetFile.text = transformedText
}
tasks.register('downloadPaiceHuskStemmer') {
group = 'build setup'
description = 'Downloads the upstream Paice/Husk benchmark source for dynamic extraction.'
outputs.file(paicehuskDownloadFile)
doLast {
final File targetFile = paicehuskDownloadFile.get().asFile
targetFile.parentFile.mkdirs()
if (!targetFile.exists()) {
new URL(paicehuskDownloadUrl).withInputStream { inputStream ->
targetFile.withOutputStream { outputStream ->
outputStream << inputStream
}
}
}
}
}
tasks.register('extractPaiceHuskStemmer', Copy) {
group = 'build setup'
description = 'Extracts the upstream Paice/Husk benchmark archive.'
dependsOn(tasks.named('downloadPaiceHuskStemmer'))
from(zipTree(paicehuskDownloadFile))
into(paicehuskExtractDirectory)
}
tasks.register('generatePaiceHuskLancasterStemmer') {
group = 'build setup'
description = 'Generates PaiceHuskLancasterStemmer into a benchmark-only generated source directory.'
dependsOn(tasks.named('extractPaiceHuskStemmer'))
inputs.files(paicehuskJavaFile, paicehuskRulesFile)
outputs.file(paicehuskGeneratedStemmerFile)
doLast {
transformPaiceHuskSource(paicehuskJavaFile.get().asFile, paicehuskRulesFile.get().asFile, paicehuskGeneratedStemmerFile.get().asFile)
}
}
sourceSets {
jmh {
java {
srcDir(paicehuskGeneratedSourceDirectory)
}
}
}
tasks.named('compileJmhJava') {
dependsOn(tasks.named('generatePaiceHuskLancasterStemmer'))
}
eclipse {
classpath {
file {
whenMerged { classpath ->
String generatedPath = paicehuskGeneratedSourcePath.get()
classpath.entries.removeAll { entry ->
entry.hasProperty('path') && (
entry.path == generatedPath ||
paicehuskIsAbsolutePath(entry.path)
)
}
SourceFolder paicehuskEntry = new SourceFolder(generatedPath, null)
paicehuskEntry.output = 'bin/jmh'
paicehuskSourceEclipseClasspathAttributes.each { String name, String value ->
paicehuskEntry.entryAttributes[name] = value
}
classpath.entries.add(paicehuskEntry)
}
}
}
}