446 lines
15 KiB
Groovy
446 lines
15 KiB
Groovy
|
|
plugins {
|
|
id 'com.github.node-gradle.node' version '7.1.0'
|
|
id "java"
|
|
id "war"
|
|
id "org.teavm" version "${teaVmVersion}"
|
|
}
|
|
|
|
import java.security.MessageDigest
|
|
import java.nio.file.Files
|
|
import java.nio.file.Paths
|
|
apply from: "${rootDir}/gradle/libs/class-finder.gradle"
|
|
|
|
configurations {
|
|
withResources {
|
|
canBeResolved = true
|
|
}
|
|
}
|
|
|
|
node {
|
|
download = true
|
|
distBaseUrl = null
|
|
version = "22.18.0"
|
|
}
|
|
|
|
configurations.configureEach {
|
|
exclude group: 'org.ngengine', module: 'jme3-jbullet'
|
|
}
|
|
|
|
dependencies {
|
|
implementation "org.teavm:teavm-jso:${teaVmVersion}"
|
|
implementation "org.teavm:teavm-jso-apis:${teaVmVersion}"
|
|
implementation "org.teavm:teavm-metaprogramming-api:${teaVmVersion}"
|
|
implementation "org.teavm:teavm-classlib:${teaVmVersion}"
|
|
implementation "org.teavm:teavm-tooling:${teaVmVersion}"
|
|
implementation "org.teavm:teavm-core:${teaVmVersion}"
|
|
|
|
implementation "org.ngengine:nge-platform-teavm:${ngePlatformVersion}"
|
|
implementation "org.ngengine:nge-app:${ngeVersion}"
|
|
implementation "org.ngengine:jme3-plugins-json:${ngeVersion}"
|
|
implementation "org.ngengine:nge-web:${ngeVersion}"
|
|
implementation "org.ngengine:nge-web-bullet:${ngeVersion}"
|
|
|
|
implementation project(':app')
|
|
|
|
withResources project(':app')
|
|
withResources "org.ngengine:nge-platform-teavm:${ngePlatformVersion}"
|
|
withResources "org.ngengine:nge-web:${ngeVersion}"
|
|
withResources "org.ngengine:nge-web-bullet:${ngeVersion}"
|
|
}
|
|
|
|
java {
|
|
toolchain {
|
|
languageVersion = JavaLanguageVersion.of(21)
|
|
}
|
|
}
|
|
|
|
sourceSets {
|
|
test {
|
|
resources {
|
|
srcDirs = ['src/test/resources', 'src/main/resources']
|
|
}
|
|
}
|
|
}
|
|
|
|
def binaryExclusions = [
|
|
'**/*.class',
|
|
'**/*.dll',
|
|
'**/*.so',
|
|
'**/*.dylib',
|
|
'**/package.html',
|
|
"**/*.kotlin_builtins",
|
|
"**/build.data",
|
|
"**/*.xml",
|
|
"**/test*.png",
|
|
// '**/*.java'
|
|
]
|
|
|
|
|
|
|
|
def generateHashes(File dir) {
|
|
def hashes = []
|
|
Files.walk(Paths.get(dir.toURI())).filter {
|
|
Files.isRegularFile(it)
|
|
}.forEach { file ->
|
|
def relativePath = dir.toPath().relativize(file).toString()
|
|
if(relativePath.endsWith(".class")) return;
|
|
if(relativePath.endsWith(".java")) return;
|
|
def hash = generateSHA256(file.toFile())
|
|
def size = file.toFile().length()
|
|
hashes << "$hash $size $relativePath"
|
|
}
|
|
return hashes.join('\n')
|
|
}
|
|
|
|
def generateSHA256(file) {
|
|
def messageDigest = MessageDigest.getInstance("SHA-256")
|
|
file.eachByte(4096) { bytes, len ->
|
|
messageDigest.update(bytes, 0, len)
|
|
}
|
|
return messageDigest.digest().collect { String.format("%02x", it) }.join()
|
|
}
|
|
|
|
task copyAllResources {
|
|
|
|
dependsOn configurations.runtimeClasspath.getTaskDependencyFromProjectDependency(true, 'jar')
|
|
dependsOn 'jar'
|
|
|
|
['compileTeavmJava','processTeavmResources','compileJava','processResources']
|
|
.each { tn -> if (tasks.findByName(tn) != null) dependsOn tn }
|
|
|
|
doLast {
|
|
def dest = file("${rootDir}/dist/web")
|
|
dest.mkdirs()
|
|
|
|
def safeCopy = { spec ->
|
|
copy {
|
|
into dest
|
|
from spec
|
|
exclude(*binaryExclusions)
|
|
exclude '**/module-info.class'
|
|
includeEmptyDirs = false
|
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
|
}
|
|
}
|
|
safeCopy('src/main/resources')
|
|
|
|
def runtimeFiles = configurations.runtimeClasspath.resolvedConfiguration.lenientConfiguration.files
|
|
runtimeFiles.each { f ->
|
|
if (!f.exists()) return
|
|
if (f.isDirectory()) {
|
|
safeCopy(f)
|
|
} else if (f.name.endsWith('.jar')) {
|
|
safeCopy(project.zipTree(f))
|
|
}
|
|
}
|
|
|
|
println "Resource aggregation complete. Total files: " + fileTree(dest).matching { exclude(*binaryExclusions) }.files.size()
|
|
|
|
def manifestFile = file("${rootDir}/dist/web/manifest.json")
|
|
if (manifestFile.exists()) {
|
|
def manifest = new groovy.json.JsonSlurper().parseText(manifestFile.text)
|
|
manifest.name = Title
|
|
manifest.short_name = ShortName
|
|
|
|
manifestFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(manifest))
|
|
println "Updated manifest.json: name=$Title, short_name=$ShortName"
|
|
}
|
|
|
|
def outputDir = file("${rootDir}/dist/web/")
|
|
def outputFile = file("${rootDir}/dist/web/resources.index.txt")
|
|
|
|
outputFile.text = generateHashes(outputDir)
|
|
}
|
|
}
|
|
|
|
|
|
def generateClassListFile(File outputFile, Collection<String> classes, Closure<String> lineFormatter) {
|
|
outputFile.parentFile.mkdirs()
|
|
outputFile.text = classes.collect(lineFormatter).join('\n')
|
|
println "Generated class list at ${outputFile.absolutePath}"
|
|
}
|
|
|
|
def generateCodeFromTemplate(File outputFile, String template, Map<String, Object> vars) {
|
|
outputFile.parentFile.mkdirs()
|
|
String result = template
|
|
vars.each { key, value ->
|
|
result = result.replace("{{$key}}", value.toString())
|
|
}
|
|
outputFile.text = result
|
|
println "Generated code at ${outputFile.absolutePath}"
|
|
}
|
|
|
|
task scanClasspathForTeaVM {
|
|
dependsOn configurations.compileClasspath.getTaskDependencyFromProjectDependency(true, 'classes')
|
|
doLast {
|
|
def scanResults = project.scanClasspathForClasses(project)
|
|
|
|
def preserveListFile = file("${buildDir}/generated/teaReflectList.txt")
|
|
generateClassListFile(preserveListFile, scanResults.classes) { className ->
|
|
"**/${className.replace('.', '/')}.java"
|
|
}
|
|
|
|
def reflectionSupplierFile = file("${buildDir}/generated/teaReflect.java")
|
|
def template = """private static void initGenerated() {
|
|
{{classInitializers}}
|
|
}"""
|
|
generateCodeFromTemplate(reflectionSupplierFile, template, [
|
|
classInitializers: scanResults.reflectionClasses.collect {
|
|
" TeaReflectionSupplier.addReflectionClass(\"${it}\");"
|
|
}.join('\n')
|
|
])
|
|
}
|
|
}
|
|
|
|
task updateTeaReflectionSupplier {
|
|
dependsOn scanClasspathForTeaVM
|
|
doLast {
|
|
def reflectionSupplierFile = file("${buildDir}/generated/teaReflect.java")
|
|
def teaReflectionSupplierFile = file("${projectDir}/src/main/java/org/ngengine/platform/TeaReflectionSupplier.java")
|
|
|
|
if (reflectionSupplierFile.exists() && teaReflectionSupplierFile.exists()) {
|
|
def content = teaReflectionSupplierFile.text
|
|
def initGeneratedContent = reflectionSupplierFile.text
|
|
content = content.replaceAll(/(?s)private static void initGenerated\(\)\s*\{.*?\}/, initGeneratedContent)
|
|
teaReflectionSupplierFile.text = content
|
|
println "Updated TeaReflectionSupplier.java with generated initGenerated() method"
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.named('compileJava').configure {
|
|
dependsOn 'updateTeaReflectionSupplier'
|
|
}
|
|
|
|
teavm.js {
|
|
addedToWebApp = false
|
|
mainClass = "org.ngengine.platform.WebLauncher"
|
|
obfuscated = false
|
|
debugInformation = true
|
|
strict= false
|
|
outOfProcess=false
|
|
// optimization=org.teavm.gradle.api.OptimizationLevel.NONE
|
|
sourceMap=true
|
|
moduleType = org.teavm.gradle.api.JSModuleType.ES2015
|
|
|
|
def classesToPreserve=[]
|
|
def preserveListFile = file("${buildDir}/generated/teaReflectList.txt")
|
|
if (preserveListFile.exists()) {
|
|
preserveListFile.readLines().each { line ->
|
|
if (line.trim()) {
|
|
classesToPreserve.add(line.trim())
|
|
}
|
|
}
|
|
}
|
|
|
|
def fqcnToPreserve = new LinkedHashSet<String>()
|
|
|
|
def toFqcn = { File baseDir, File f ->
|
|
def rel = f.path.substring(baseDir.path.length() + 1)
|
|
rel = rel.replaceAll(/\.java$/, '').replaceAll(/\.class$/, '')
|
|
rel.replace('/', '.')
|
|
}
|
|
|
|
rootProject.allprojects.each { prj ->
|
|
def ss = prj.extensions.findByName('sourceSets')
|
|
def mainSS = ss?.findByName('main')
|
|
if (mainSS == null) return
|
|
mainSS.java.srcDirs.each { srcDir ->
|
|
if (!srcDir.exists()) return
|
|
fileTree(srcDir).matching { include classesToPreserve }.forEach { f ->
|
|
def fqcn = toFqcn(srcDir, f)
|
|
fqcnToPreserve.add(fqcn)
|
|
}
|
|
}
|
|
}
|
|
|
|
def rtArtifacts = configurations.runtimeClasspath.resolvedConfiguration.lenientConfiguration.files
|
|
rtArtifacts.each { artifact ->
|
|
if (!artifact.exists()) return
|
|
if (artifact.isDirectory()) {
|
|
fileTree(artifact).matching { include classesToPreserve.collect { it.replace('.java', '.class') } }.forEach { f ->
|
|
def fqcn = toFqcn(artifact, f)
|
|
fqcnToPreserve.add(fqcn)
|
|
}
|
|
} else if (artifact.name.endsWith('.jar')) {
|
|
def classFilePatterns = classesToPreserve.collect { it.replace('.java', '.class') }
|
|
.collect { it.replace('**/', '') }
|
|
|
|
try {
|
|
def jarFile = new java.util.jar.JarFile(artifact)
|
|
def entries = jarFile.entries()
|
|
|
|
while (entries.hasMoreElements()) {
|
|
def entry = entries.nextElement()
|
|
if (entry.isDirectory()) continue
|
|
|
|
def entryName = entry.getName()
|
|
if (classFilePatterns.any { pattern ->
|
|
entryName.endsWith(pattern) || entryName.matches(pattern.replace("*", ".*"))
|
|
}) {
|
|
def fqcn = entryName.replaceAll(/\.class$/, '').replace('/', '.')
|
|
fqcnToPreserve.add(fqcn)
|
|
}
|
|
}
|
|
jarFile.close()
|
|
} catch (Exception e) {
|
|
println "WARNING: Could not process JAR file: ${artifact.name} - ${e.message}"
|
|
}
|
|
}
|
|
}
|
|
|
|
fqcnToPreserve.each {
|
|
preservedClasses.add(it)
|
|
}
|
|
|
|
targetFileName = "webapp.js"
|
|
}
|
|
|
|
|
|
|
|
task buildWebApp {
|
|
dependsOn 'compileJava', 'generateJavaScript', 'copyAllResources'
|
|
doLast {
|
|
def jsDir = file("$buildDir/generated/teavm/js")
|
|
def distDir = file("${rootDir}/dist/web")
|
|
distDir.mkdirs()
|
|
copy {
|
|
from jsDir
|
|
into distDir
|
|
}
|
|
println "Copied web app to: $distDir"
|
|
}
|
|
}
|
|
|
|
|
|
task buildBundledWebApp {
|
|
dependsOn buildWebApp
|
|
doLast {
|
|
def jsDir = file("$rootDir/dist/web")
|
|
def bundledJsDir = file("$rootDir/dist/web-bundle")
|
|
def bundleIgnoreFile = file("$jsDir/bundle-ignore.txt")
|
|
def zipFile = file("$bundledJsDir/ngeapp-bundle.zip")
|
|
def configFile = file("$bundledJsDir/ngeapp.json")
|
|
|
|
bundledJsDir.mkdirs()
|
|
|
|
// Read ignore patterns from bundle-ignore.txt
|
|
def ignorePatterns = []
|
|
if (bundleIgnoreFile.exists()) {
|
|
bundleIgnoreFile.eachLine { line ->
|
|
if (line.trim() && !line.startsWith('//')) ignorePatterns.add(line.trim())
|
|
}
|
|
println "Found ${ignorePatterns.size()} patterns to exclude from bundle"
|
|
} else {
|
|
println "Warning: bundle-ignore.txt not found at ${bundleIgnoreFile}. No files will be excluded from the bundle."
|
|
}
|
|
|
|
|
|
// Create zip with everything except ignored files
|
|
ant.zip(destfile: zipFile) {
|
|
fileset(dir: jsDir) {
|
|
ignorePatterns.each { pattern ->
|
|
if (pattern.endsWith('/')) {
|
|
exclude(name: "${pattern}**")
|
|
} else {
|
|
exclude(name: pattern)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Copy ignored files to bundledjs directory
|
|
if (!ignorePatterns.isEmpty()) {
|
|
println "Copying excluded files to bundled directory..."
|
|
|
|
delete fileTree(bundledJsDir).matching { include '**/*' exclude 'ngeapp-bundle.zip', 'ngeapp-config.json' }
|
|
|
|
fileTree(jsDir).visit { fileDetails ->
|
|
if (fileDetails.isDirectory()) return
|
|
|
|
def relativePath = fileDetails.relativePath.toString()
|
|
def matchesPattern = false
|
|
|
|
for (pattern in ignorePatterns) {
|
|
if (pattern.endsWith('/')) {
|
|
if (relativePath.startsWith(pattern) || relativePath.startsWith(pattern.substring(0, pattern.length() - 1))) {
|
|
matchesPattern = true
|
|
break
|
|
}
|
|
} else {
|
|
if (relativePath == pattern) {
|
|
matchesPattern = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matchesPattern) {
|
|
println "Copying excluded file: $relativePath"
|
|
copy {
|
|
from fileDetails.file
|
|
into new File(bundledJsDir, fileDetails.relativePath.parent.toString())
|
|
}
|
|
}
|
|
}
|
|
println "Finished copying excluded files"
|
|
}
|
|
// Calculate hash of zip file
|
|
def hash = generateSHA256(zipFile)
|
|
|
|
// Set bundle in config file
|
|
def config = [:]
|
|
if (configFile.exists()) {
|
|
try {
|
|
config = new groovy.json.JsonSlurper().parseText(configFile.text)
|
|
println "Updating existing config file: $configFile.path"
|
|
} catch (Exception e) {
|
|
println "Warning: Could not parse existing config file: $e.message"
|
|
config = [:]
|
|
}
|
|
}
|
|
|
|
config.bundle = "ngeapp-bundle.zip"
|
|
config.bundleHash = hash
|
|
configFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(config))
|
|
|
|
println "Created bundle: $zipFile.path"
|
|
println "Bundle hash: $hash"
|
|
println "Config written to: $configFile.path"
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
task npmCi(type: NpmTask) {
|
|
workingDir = file("${projectDir}")
|
|
args = ['ci']
|
|
}
|
|
|
|
task runWebApp(type: NpxTask) {
|
|
dependsOn buildWebApp
|
|
workingDir = file("$rootDir/dist/web")
|
|
command = 'http-server'
|
|
args = [ '.', '-p', '8000']
|
|
}
|
|
|
|
task runBundledWebApp(type: NpxTask) {
|
|
dependsOn buildBundledWebApp
|
|
workingDir = file("$rootDir/dist/web-bundle")
|
|
command = 'http-server'
|
|
args = [ '.', '-p', '8000']
|
|
}
|
|
|
|
task deployWebApp(type: NpxTask) {
|
|
dependsOn buildBundledWebApp
|
|
dependsOn npmCi
|
|
command = 'nostr-deploy-cli'
|
|
args = ['deploy','--skip-setup', '-d', "${rootDir}/dist/web-bundle"]
|
|
workingDir = file("${projectDir}")
|
|
}
|
|
|