build.gradle
Rama develop · réplica de lectura del repositorio original.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.monochromeroad.gradle-plugins:gradle-aws-s3-sync:0.10"
}
}
//plugins {
// //// tool to see tasks dependencies, use: gradlew <mytask> taskTree
// id 'com.dorongold.task-tree' version '1.5'
//}
plugins {
id 'io.github.ben-manes.versions' version '0.67.0'
id 'org.owasp.dependencycheck' version '12.2.2'
}
import com.monochromeroad.gradle.plugin.aws.s3.S3Sync
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
import groovy.json.JsonSlurper
import org.apache.tools.ant.filters.ReplaceTokens
import groovy.xml.XmlSlurper
import groovy.xml.XmlUtil
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
ext.nvdApiKey = findProperty('nvdApiKey') ?: System.getenv('NVD_API_KEY')
ext.convertigoVersion = '8.5.0'
ext.convertigoTag = 'beta'
ext.convertigoCodename = 'astatine'
ext.convertigoCode = 'At'
ext.convertigoRevision = hasProperty('convertigoRevision') ? convertigoRevision : ''
ext.jxBrowserGroup = 'com.teamdev.jxbrowser'
ext.jxBrowserVersion = '9.5.6'
ext.swaggerUiVersion = '5.34.1'
ext.tomcatVersion = '11.0.27'
ext.eclipseVersion = '2026-09'
ext.eclipseBase = '4.41'
ext.studioWebNodeVersion = '26'
ext.sapjco3Version = 'com.convertigo.lib:sapjco3:2026.09.17'
ext.soapuiVersion = '5.9.1'
ext.groovyVersion = '3.0.25'
ext.log4jVersion = '2.26.1'
ext.commonsIo = '2.22.0'
ext.commonsLang3 = '3.21.0'
ext.zstdJniVersion = '1.5.7-25'
ext.brotli4jVersion = '1.24.0'
ext.jacksonVersion = '2.22.4'
ext.xmlSchemaVersion = '1.4.7'
ext.convertigoMajor = convertigoVersion.replaceFirst('(\\d+\\.\\d+)\\..*', '$1')
ext.convertigoEngineAPI = "${convertigoVersion}-SNAPSHOT"
ext.convertigoVersionFull = "${convertigoVersion}${convertigoTag.isEmpty() ? '' : '-' + convertigoTag}"
ext.currentYear = '' + Calendar.getInstance().get(Calendar.YEAR)
// Dev Studio in Eclipse: the providers are used as jars, unless c8o.localProviders links their
// local checkouts as sources (see gradle.properties). -P wins over local.properties (per checkout,
// ignored by git), which wins over the gradle.properties files.
def localProperties = new Properties()
if (file('local.properties').exists()) {
file('local.properties').withInputStream { localProperties.load(it) }
}
def devProperty = { String key ->
def cli = gradle.startParameter.projectProperties
cli.containsKey(key) ? cli[key] : localProperties.containsKey(key) ? localProperties.getProperty(key) : findProperty(key)
}
def eclipseProviderSources = [
couchdb: 'src/com/twinsoft/convertigo/engine/providers/couchdb',
sap: 'src/com/twinsoft/convertigo/engine/providers/sapjco',
plugins: 'src/com/twinsoft/convertigo/engine/plugins'
]
def localProvidersValue = (devProperty('c8o.localProviders') ?: '').toString().trim()
def localProviderNames = localProvidersValue == 'all' ? eclipseProviderSources.keySet().toList() : localProvidersValue.tokenize(', ').unique()
def unknownLocalProviders = localProviderNames - eclipseProviderSources.keySet()
def localProvidersDirValue = (devProperty('c8o.localProvidersDir') ?: '').toString().trim()
def localProvidersDir = localProvidersDirValue ? file(localProvidersDirValue.replaceFirst('^~(?=/|$)', System.getProperty('user.home'))) : rootDir.parentFile
ext.localProviders = (localProviderNames - unknownLocalProviders).collect { name ->
def checkout = new File(localProvidersDir, "convertigo-provider-${name}")
[name: name, path: eclipseProviderSources[name], checkout: checkout, sources: new File(checkout, eclipseProviderSources[name])]
}
ext.localProvidersDescription = localProviders ? "local sources of ${localProviders*.name.join(', ')} from ${localProvidersDir}" : 'jars'
// checked before any Eclipse task runs, so that cleanEclipse does not remove the current files
gradle.taskGraph.whenReady { graph ->
if (!graph.allTasks.any { it.name == 'eclipseProject' }) {
return
}
if (unknownLocalProviders) {
throw new GradleException("Unknown c8o.localProviders ${unknownLocalProviders}: use 'all' or a list of ${eclipseProviderSources.keySet().join(', ')}")
}
def missing = localProviders*.sources.findAll { !it.directory }
if (missing) {
throw new GradleException("c8o.localProviders: missing provider sources ${missing.join(', ')}; clone the providers there or set c8o.localProvidersDir")
}
}
// the source sets exclude the plugins package, which comes from the provider: when it is linked as
// sources, Eclipse compiles it with the excludes of the provider build
ext.applyLocalProvidersToClasspath = { XmlProvider xml ->
def plugins = localProviders.find { it.name == 'plugins' }
if (plugins == null) {
return
}
def providerBuild = new File(plugins.checkout, 'build.gradle')
def providerExcludes = providerBuild.exists() ? (providerBuild.text =~ /exclude\s+['"](com\/twinsoft\/convertigo\/engine\/plugins\/[^'"]+)['"]/).collect { it[1] } : []
def nl = xml.asElement().getElementsByTagName('classpathentry')
for (def i = 0; i < nl.length; i++) {
def entry = nl.item(i)
if (entry.getAttribute('kind') == 'src' && entry.getAttribute('path') == 'src') {
def patterns = entry.getAttribute('excluding').tokenize('|').findAll { it != 'com/twinsoft/convertigo/engine/plugins/**' } + providerExcludes
if (patterns) {
entry.setAttribute('excluding', patterns.join('|'))
} else {
entry.removeAttribute('excluding')
}
}
}
}
apply from: 'headers.gradle'
apply from: 'changelog.gradle'
apply from: 'installers/windows.gradle'
def isDependencyUpdatesTaskRequested = gradle.startParameter.taskNames.any { taskName ->
taskName == 'dependencyUpdates' ||
taskName == 'dependencyUpdatesPatch' ||
taskName.endsWith(':dependencyUpdates') ||
taskName.endsWith(':dependencyUpdatesPatch')
}
if (isDependencyUpdatesTaskRequested) {
gradle.startParameter.parallelProjectExecutionEnabled = false
}
task updateVersion(dependsOn: [':engine:updateProductVersion', 'writeManifest', 'generateDockerfile']) {
group = 'convertigo'
description = 'Run this task after a Convertigo version change.'
}
configurations.all {
// Check for updates every build
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
eclipse {
project {
comment = 'Main project to develop the Convertigo platform'
}
}
allprojects {
apply plugin: 'io.github.ben-manes.versions'
apply plugin: 'org.owasp.dependencycheck'
dependencyCheck {
nvd {
apiKey = rootProject.ext.nvdApiKey
}
}
version = convertigoVersionFull
repositories {
mavenLocal()
maven {
url = "https://m2.convertigo.com"
}
maven {
url = "https://europe-maven.pkg.dev/jxbrowser/releases"
}
mavenCentral()
maven {
url = "https://oss.sonatype.org/content/repositories/snapshots"
}
maven {
url = "https://rapi.tools.ops.smartbear.io/nexus/content/groups/public"
}
}
if (project.name.startsWith('eclipse')) {
apply plugin: 'eclipse'
}
if (project.name.startsWith('eclipse-plugin')) {
apply plugin: 'java'
sourceSets {
main {
java {
exclude '.placeholder'
srcDirs = ['src']
}
resources {
exclude '.placeholder'
srcDirs = ['src']
}
}
}
eclipse {
project {
natures 'org.eclipse.jdt.core.javanature',
'org.eclipse.pde.PluginNature',
'org.eclipse.buildship.core.gradleprojectnature'
buildCommand 'org.eclipse.pde.ManifestBuilder'
buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
}
classpath.file.withXml {
def elt = it.asElement()
def projectPath = projectDir.path.replace('\\', '/') + '/'
def nl = elt.getElementsByTagName('classpathentry')
def hasRequired = false
for (def i = 0; i < nl.length; i++) {
def entry = nl.item(i)
if (entry.getAttribute('kind') == 'lib') {
entry.setAttribute('path', entry.getAttribute('path').replace(projectPath, ''))
}
hasRequired |= entry.getAttribute('path') == 'org.eclipse.pde.core.requiredPlugins'
}
if (!hasRequired) {
def entry = elt.getOwnerDocument().createElement('classpathentry')
entry.setAttribute('kind', 'con')
entry.setAttribute('path', 'org.eclipse.pde.core.requiredPlugins')
elt.appendChild(entry)
}
}
}
configurations {
toSync
}
task syncLib(type: Sync) {
group = 'convertigo'
into 'lib'
from configurations.toSync
rename 'sapjco3.*jar', 'sapjco3.jar'
// optional provider jar, copied by hand (see the Bundle-ClassPath of the studio plugin)
preserve {
include 'convertigo-provider-plugins.jar'
}
}
// the .classpath lists the jars of lib: write it once syncLib has updated them
tasks.named('eclipseClasspath') {
mustRunAfter syncLib
}
}
plugins.withId('java') {
java {
toolchain.languageVersion = JavaLanguageVersion.of(25)
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.release = 25
}
task cleanBin(type: Delete) {
group = 'convertigo'
def f = project.file('bin')
if (f.exists()) {
f.eachFile {
if (it.getName() != 'main') {
println it
delete it
}
}
}
}
}
afterEvaluate {
if (plugins.hasPlugin('maven-publish')) {
publishing {
repositories {
maven {
url = project.properties['c8o.m2.url']
credentials(AwsCredentials) {
accessKey = project.properties['c8o.m2.ak']
secretKey = project.properties['c8o.m2.sk']
}
}
}
}
}
}
}
configurations {
soapuiLightBase
}
dependencies {
soapuiLightBase("com.smartbear.soapui:soapui:${soapuiVersion}") {
transitive = false
}
}
tasks.register('soapuiLightJar', Jar) {
group = 'convertigo'
description = 'Build a SoapUI light jar without explorer UI assets.'
destinationDirectory = layout.buildDirectory.dir('libs')
archiveFileName = "soapui-light-${soapuiVersion}.jar"
from {
configurations.soapuiLightBase.files.collect { zipTree(it) }
}
exclude 'com/eviware/soapui/explorer/**'
}
publishing {
publications {
engineApi (MavenPublication) {
groupId = 'com.convertigo.lib'
artifactId = 'convertigo-engine-api'
version = convertigoEngineAPI
afterEvaluate {
artifact project(':engine').engineApiJar
pom.withXml {
def dependencies = asNode().appendNode('dependencies')
[
'org.apache.logging.log4j:log4j-core:' + log4jVersion,
'org.apache.logging.log4j:log4j-1.2-api:' + log4jVersion,
'com.convertigo.twinsoft:api:2017.12.29',
'com.convertigo.twinsoft:tas:2019.10.16',
'com.convertigo.twinsoft:util:2017.12.29',
'com.fasterxml.jackson.core:jackson-core:' + jacksonVersion,
'commons-io:commons-io:' + commonsIo,
'javax.mail:mail:1.4.7',
'jakarta.servlet:jakarta.servlet-api:6.1.0',
'org.apache.commons:commons-lang3:' + commonsLang3,
'org.apache.httpcomponents:httpclient:4.5.14',
'org.apache.ws.commons.schema:XmlSchema:' + xmlSchemaVersion,
'org.codehaus.jettison:jettison:1.5.7'
].each {
def values = it.split(':')
def dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', values[0])
dependency.appendNode('artifactId', values[1])
dependency.appendNode('version', values[2])
}
}
}
}
soapuiLight (MavenPublication) {
groupId = 'com.smartbear.soapui'
artifactId = 'soapui-light'
version = soapuiVersion
artifact tasks.named('soapuiLightJar')
pom {
name = 'SoapUI light'
description = 'SoapUI jar with jquery assets removed and no transitive dependencies.'
}
pom.withXml {
def dependencies = asNode().appendNode('dependencies')
def dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', 'com.btr')
dependency.appendNode('artifactId', 'proxy-vole')
dependency.appendNode('version', '20131209')
dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', 'commons-lang')
dependency.appendNode('artifactId', 'commons-lang')
dependency.appendNode('version', '2.6')
dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', 'org.apache.xmlbeans')
dependency.appendNode('artifactId', 'xmlbeans')
dependency.appendNode('version', '3.1.1-sb-fixed')
def exclusions = dependency.appendNode('exclusions')
def exclusion = exclusions.appendNode('exclusion')
exclusion.appendNode('groupId', 'stax')
exclusion.appendNode('artifactId', 'stax-api')
dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', 'org.reflections')
dependency.appendNode('artifactId', 'reflections')
dependency.appendNode('version', '0.9.9-RC1')
exclusions = dependency.appendNode('exclusions')
exclusion = exclusions.appendNode('exclusion')
exclusion.appendNode('groupId', 'dom4j')
exclusion.appendNode('artifactId', 'dom4j')
dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', 'org.codehaus.groovy')
dependency.appendNode('artifactId', 'groovy-all')
dependency.appendNode('version', groovyVersion)
exclusions = dependency.appendNode('exclusions')
exclusion = exclusions.appendNode('exclusion')
exclusion.appendNode('groupId', 'jline')
exclusion.appendNode('artifactId', 'jline')
dependency = dependencies.appendNode('dependency')
dependency.appendNode('groupId', 'com.fifesoft')
dependency.appendNode('artifactId', 'rsyntaxtextarea')
dependency.appendNode('version', '2.5.0')
}
}
/*
jxBrowser (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'jxbrowser'
version "${jxBrowserVersion}"
artifact "resources/jxbrowser-${jxBrowserVersion}/jxbrowser-${jxBrowserVersion}.jar"
}
jxBrowserLinux64 (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'jxbrowser-linux64'
version "${jxBrowserVersion}"
artifact "resources/jxbrowser-${jxBrowserVersion}/jxbrowser-linux64-${jxBrowserVersion}.jar"
}
jxBrowserMac (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'jxbrowser-mac'
version "${jxBrowserVersion}"
artifact "resources/jxbrowser-${jxBrowserVersion}/jxbrowser-mac-${jxBrowserVersion}.jar"
}
jxBrowserWin64 (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'jxbrowser-win64'
version "${jxBrowserVersion}"
artifact "resources/jxbrowser-${jxBrowserVersion}/jxbrowser-win64-${jxBrowserVersion}.jar"
}
jxBrowserSwt (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'jxbrowser-swt'
version "${jxBrowserVersion}"
artifact "resources/jxbrowser-${jxBrowserVersion}/jxbrowser-swt-${jxBrowserVersion}.jar"
}
ojdbc (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'ojdbc7'
version '12.1.0.2'
artifact 'resources/ojdbc7.jar'
}
xmlbeans (MavenPublication) {
groupId 'com.convertigo.lib'
artifactId 'xmlbeans'
version '2.6.0'
artifact 'resources/xmlbeans-2.6.0.jar'
}
rhinoTools (MavenPublication) {
groupId 'org.mozilla'
artifactId 'rhino-tools'
version '1.8.0'
artifact 'resources/rhino-tools-1.8.0.jar'
}
*/
}
}
afterEvaluate {
ext.sharedManifest = project(':engine').java.manifest {
attributes(
'Bundle-ManifestVersion': '2',
'Bundle-Vendor': 'Convertigo',
'Bundle-Version': "${convertigoVersion}.qualifier",
'Bundle-RequiredExecutionEnvironment': 'JavaSE-25',
)
}
}
task writeManifest(dependsOn: ['updateEclipseVersion', 'makeSplash']) {
group = 'convertigo'
def wm = getTasksByName 'writeManifest', true
wm.remove tasks['writeManifest']
wm.each { it.dependsOn ':updateEclipseVersion' }
dependsOn wm
}
task buildStudio(type: Exec, dependsOn: ['writeManifest', 'downloadSystemLibraries']) {
group = 'convertigo'
onlyIf { !file('eclipse-repository/target/products').exists() }
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine 'cmd', '/c', 'mvn'
} else {
commandLine 'mvn'
}
args 'install'
}
task updateEclipseProduct(type: Copy, dependsOn: [':checkLicenseDate', 'updateEclipseCategory', ':eclipse-plugin-product:writePluginXml', ':eclipse-plugin-product:writeAboutIni']) {
group = 'convertigo'
def aboutTextFile = file('eclipse-plugin-product/aboutText.txt')
def licenseTextFile = file('legal/dual-license.txt')
inputs.properties(
'currentYear': currentYear,
'convertigoVersionFull': convertigoVersionFull
)
inputs.files aboutTextFile, licenseTextFile
def aboutText = ''
def licenseText = ''
doFirst {
def xml = new XmlSlurper().parseText('<elt/>')
xml.replaceBody aboutTextFile.text
.replace('@CEMS_VERSION@', convertigoVersionFull)
.replace('@CURRENT_YEAR@', currentYear)
aboutText = XmlUtil.serialize(xml).replaceFirst('[\\d\\D]*<elt>([\\d\\D]*)</elt>[\\d\\D]*','$1')
xml.replaceBody licenseTextFile.text
licenseText = XmlUtil.serialize(xml).replaceFirst('[\\d\\D]*<elt>([\\d\\D]*)</elt>[\\d\\D]*','$1')
}
from 'eclipse-repository/Convertigo.product.tpl'
filter {
it.replace('@aboutText@', aboutText)
.replace('@licenseText@', licenseText)
.replace('@convertigoVersion@', convertigoVersion)
.replace('@convertigoMajor@', convertigoMajor)
.replace('@convertigoMode@', convertigoTag.isEmpty() ? 'release' : 'beta')
.replace('@eclipseVersion@', eclipseVersion)
}
into 'eclipse-repository'
rename '.*', 'Convertigo.product'
}
task updateEclipseCategory(type: Copy) {
group = 'convertigo'
inputs.properties(
'convertigoVersion': convertigoVersion
)
from 'eclipse-repository/category.xml.tpl'
filter { it.replace('@convertigoVersion@', convertigoVersion) }
into 'eclipse-repository'
rename '.*', 'category.xml'
}
task updateEclipseVersion(dependsOn: [':updateEclipseProduct', ':eclipse-plugin-product:writeAboutIni', ':eclipse-plugin-product:writePluginXml', ':eclipse-feature:updateFeature']) {
group = 'convertigo'
doFirst {
fileTree(dir: '.', include: '*/pom.xml').files.plus(file('pom.xml')).each {
it.text = it.text.replaceAll('(<!-- SET-VERSION -->\\s*<version>).*(</version>)', "\$1${convertigoVersion}-SNAPSHOT\$2")
}
def base = file('eclipse-base/base.target')
base.text = base.text.replaceFirst('/releases/.+?/', "/releases/${eclipseVersion}/").replaceFirst('/updates/.+?/', "/updates/${eclipseBase}/")
}
}
task buildStudioClean(type: Delete) {
group = 'convertigo'
delete 'eclipse-repository/target/products'
}
task generateEclipseConfiguration(dependsOn: [getTasksByName('cleanEclipse', true), getTasksByName('eclipse', true), getTasksByName('cleanBin', true)]) {
group = 'convertigo'
}
task generateEclipseConfigurationWithManifest(dependsOn: ['writeManifest', 'generateEclipseConfiguration']) {
group = 'convertigo'
finalizedBy 'checkEclipseConfiguration'
}
task checkEclipseConfiguration {
group = 'convertigo'
description = 'Checks that the Eclipse .classpath and .project files only reference existing files.'
doLast {
def errors = []
allprojects.findAll { it.name == 'engine' || it.name.startsWith('eclipse-plugin') }.each { p ->
def classpathFile = p.file('.classpath')
if (classpathFile.exists()) {
def libs = new XmlSlurper().parse(classpathFile).classpathentry.findAll { it.@kind == 'lib' }.collect { it.@path.text() }
libs.findAll { !(new File(it).absolute ? new File(it) : p.file(it)).exists() }.each {
errors << "${p.name}/.classpath: missing library ${it}"
}
// projects whose .classpath mirrors lib (implementation fileTree(dir: 'lib'))
if (p.configurations.findByName('implementation')?.dependencies?.any { it instanceof FileCollectionDependency }) {
p.fileTree(dir: 'lib', include: '*.jar').each {
if (!libs.contains('lib/' + it.name)) {
errors << "${p.name}/.classpath: lib/${it.name} is not listed"
}
}
}
}
def projectFile = p.file('.project')
if (projectFile.exists()) {
new XmlSlurper().parse(projectFile).linkedResources.link.each { link ->
def location = link.location.text()
def uri = link.locationURI.text()
def target = location ? new File(location) : null
def parent = uri =~ /^PARENT-(\d+)-PROJECT_LOC\/(.*)$/
if (parent.matches()) {
target = p.projectDir
parent.group(1).toInteger().times { target = target.parentFile }
target = new File(target, parent.group(2))
} else if (uri.startsWith('file:')) {
target = new File(new URI(uri))
}
if (target != null && !target.exists()) {
errors << "${p.name}/.project: missing linked resource ${link.name.text()} -> ${target}"
}
}
}
}
if (errors) {
throw new GradleException("Eclipse configuration is inconsistent:\n " + errors.join('\n '))
}
logger.lifecycle "Eclipse configuration checked, providers: ${localProvidersDescription}"
}
}
task setupStudioDev(dependsOn: ['generateEclipseConfigurationWithManifest', 'buildStudioWebNpmInstall', 'buildStudioWebBuild']) {
group = 'convertigo'
description = 'Prepares the dev Studio: Eclipse configuration, manifests and admin web build.'
}
task makeSplash(dependsOn: 'convertigo-splash:run') {
group = 'convertigo'
}
// the admin web builds with the Node of nvm on macOS and Linux, and with the Node of the PATH on Windows
def studioWebOnWindows = System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')
def studioWebCommand = { String command ->
studioWebOnWindows ? ['cmd', '/c', command] : ['sh', '-c', ". ~/.nvm/nvm.sh && nvm exec ${studioWebNodeVersion} ${command}"]
}
task buildStudioWebNvmInstall(type: Exec) {
group = 'convertigo'
onlyIf { !studioWebOnWindows }
workingDir 'convertigo-studio-web'
commandLine 'sh'
args '-c', ". ~/.nvm/nvm.sh && nvm install ${studioWebNodeVersion}"
}
task buildStudioWebNpmInstall(type: Exec, dependsOn: ':buildStudioWebNvmInstall') {
group = 'convertigo'
// up-to-date while the lock file is unchanged and node_modules holds its installation
inputs.property('studioWebNodeVersion', studioWebNodeVersion)
inputs.files('convertigo-studio-web/package.json', 'convertigo-studio-web/package-lock.json')
outputs.file('convertigo-studio-web/node_modules/.package-lock.json')
workingDir 'convertigo-studio-web'
commandLine studioWebCommand('npm ci')
}
task buildStudioWebBuild(type: Exec, dependsOn: ':buildStudioWebNvmInstall') {
group = 'convertigo'
mustRunAfter ':buildStudioWebNpmInstall'
// up-to-date while the admin web sources are unchanged and its output is in the studio webapp
// (post-build.js copies it among other files, so a stamp stands for it)
def webapp = file('eclipse-plugin-studio/tomcat/webapps/convertigo')
def stamp = layout.buildDirectory.file('tmp/buildStudioWebBuild/stamp')
inputs.property('studioWebNodeVersion', studioWebNodeVersion)
inputs.files(fileTree('convertigo-studio-web') {
// generated or local files, see convertigo-studio-web/.gitignore
exclude 'node_modules/', 'build/', '.svelte-kit/', '.cache/', 'package/', '.vite/', 'static/monaco/'
exclude 'src/convertigo.utilities.css', 'assets/bezel-sources/', 'vite.config.*.timestamp-*', '.env*', '**/.DS_Store'
exclude 'tests/', 'test-results/', 'playwright-report/'
}).withPropertyName('studioWebSources')
outputs.file(stamp)
outputs.upToDateWhen { ['index.html', '_app', 'admin'].every { new File(webapp, it).exists() } }
doLast {
stamp.get().asFile.text = 'built\n'
}
workingDir 'convertigo-studio-web'
commandLine studioWebCommand('npm run build')
}
task ciFirstStep(dependsOn: [':engine:jar', ':gradle-plugin:publish', ':buildStudioWebNpmInstall', ':buildStudioWebBuild']) {
group = 'convertigo'
}
task generateDockerfile {
group = 'convertigo'
doLast {
def base = file('docker/Dockerfile.tpl').text
base = base.replace('%VERSION%', convertigoVersion)
def generatedDockerfileHeader = '# Generated by ./gradlew generateDockerfile. Do not edit directly.\n'
def generatedScriptHeader = '# Generated by ./gradlew generateDockerfile from docker/docker-entrypoint.sh. Do not edit directly.\n'
def sourceDockerfileHeader = '# Source file for docker/*/Dockerfile, regenerated with ./gradlew generateDockerfile.\n'
def sourceScriptHeader = '# Source file for docker/*/docker-entrypoint.sh, regenerated with ./gradlew generateDockerfile.\n'
def generatedBase = base.replace(sourceDockerfileHeader, '')
def generatedEntryPoint = { String onLaunch ->
file('docker/docker-entrypoint.sh').text
.replace('%ON_LAUNCH%', onLaunch)
.replace(sourceScriptHeader, '')
.replaceFirst('^#!/bin/sh\\n', '#!/bin/sh\n' + generatedScriptHeader)
}
file('docker/default/Dockerfile').text = generatedDockerfileHeader + generatedBase.replace('%BEGIN%', '')
file('docker/default/docker-entrypoint.sh').text = generatedEntryPoint('')
file('docker/default/root-index.html').text = file('docker/root-index.html').text
file('docker/aks/Dockerfile').text = generatedDockerfileHeader + generatedBase.replace('%BEGIN%', file('docker/aks-Dockerfile.tpl').text)
file('docker/aks/docker-entrypoint.sh').text = generatedEntryPoint(file('docker/aks-entrypoint.tpl').text)
file('docker/aks/root-index.html').text = file('docker/root-index.html').text
}
}
def dockerDocsOfficialDir = {
file(findProperty('dockerDocsDir') ?: '../docker-docs')
}
def runDockerDocsOfficialCommand = { File workingDir, List<String> command, Map<String, String> environment = [:] ->
def normalizedCommand = command.collect { it.toString() }
println "\$ ${normalizedCommand.collect { it.contains(' ') ? '"' + it + '"' : it }.join(' ')}"
def processBuilder = new ProcessBuilder(normalizedCommand)
.directory(workingDir)
.redirectErrorStream(true)
processBuilder.environment().putAll(environment)
def process = processBuilder.start()
def output = process.inputStream.getText('UTF-8')
def exitCode = process.waitFor()
if (output) {
print output
}
if (exitCode != 0) {
throw new GradleException("Command failed with exit code ${exitCode}: ${command.join(' ')}")
}
return output
}
def renderDockerDocsOfficialContent = {
def content = []
def inCodeBlock = false
def dockerRunCodeBlock = false
def stripCodeIndent = { String line ->
return line.startsWith('\t') ? line.substring(1) : line.startsWith(' ') ? line.substring(4) : line.substring(3)
}
def normalizeCodeLine = { String line, boolean canReplaceImage ->
def code = stripCodeIndent(line)
if (code == 'FROM convertigo') {
code = 'FROM %%IMAGE%%'
} else if (canReplaceImage || code.trim().replaceFirst(/^\$ /, '').startsWith('docker run ')) {
code = code.replaceFirst(/(^|\s)convertigo(\s*\\?)$/, '$1%%IMAGE%%$2')
}
return code
}
// Docker docs convention: in a console block every command starts with "$ " so the highlighter
// tells commands from their output. The README stays plain markdown; the prompt is added here,
// only in blocks that start with a shell command (not Dockerfile snippets nor option lists).
def shellCodeBlock = false
def continuedCommand = false
def isShellCommand = { String code ->
return code ==~ /^(\$ |[a-z][\w.-]*( |$)|[A-Za-z_][A-Za-z0-9_]*=).*/
}
file('docker/README.md').readLines('UTF-8').each { String line ->
if (line ==~ /^( {3,}|\t).*/) {
if (!inCodeBlock) {
content << '```console'
inCodeBlock = true
dockerRunCodeBlock = stripCodeIndent(line).trim().replaceFirst(/^\$ /, '').startsWith('docker run ')
shellCodeBlock = isShellCommand(stripCodeIndent(line))
continuedCommand = false
}
def code = normalizeCodeLine(line, dockerRunCodeBlock)
if (shellCodeBlock && !continuedCommand && !code.startsWith('$ ') && !code.trim().isEmpty() && !(code ==~ /^\s.*/)) {
code = '$ ' + code
}
continuedCommand = code.trim().endsWith('\\')
content << code
} else {
if (inCodeBlock) {
content << '```'
inCodeBlock = false
dockerRunCodeBlock = false
}
// markdownfmt (Docker docs CI) expects list items as "-<tab>item"
content << line.replaceFirst(/^(\s*)[-*]\s+(?=\S)/, '$1-\t')
if (line == '> [www.convertigo.com](https://www.convertigo.com)') {
content << ''
content << '%%LOGO%%'
}
}
}
if (inCodeBlock) {
content << '```'
}
return content.collect { it.replaceFirst(/[ \t]+$/, '') }.join('\n') + '\n'
}
task generateDockerDocsOfficialContent {
group = 'convertigo'
description = 'Generates the Docker Official Images documentation content from docker/README.md.'
def output = layout.buildDirectory.file('docker-docs/convertigo/content.md')
inputs.file 'docker/README.md'
outputs.file output
outputs.upToDateWhen { false }
doLast {
def outputFile = output.get().asFile
outputFile.parentFile.mkdirs()
outputFile.setText(renderDockerDocsOfficialContent(), 'UTF-8')
}
}
task checkDockerDocsOfficial(dependsOn: 'generateDockerDocsOfficialContent') {
group = 'convertigo'
description = 'Checks that the sibling docker-docs repository is synchronized with docker/README.md.'
doLast {
def target = new File(dockerDocsOfficialDir(), 'convertigo/content.md')
if (!target.isFile()) {
throw new GradleException("Docker docs target not found: ${target}. Use -PdockerDocsDir=path/to/docker-docs if repositories are not siblings.")
}
def generated = layout.buildDirectory.file('docker-docs/convertigo/content.md').get().asFile
if (target.getText('UTF-8') != generated.getText('UTF-8')) {
throw new GradleException("Docker official docs are not synchronized. Run ./gradlew updateDockerDocsOfficial or inspect ${generated}.")
}
}
}
task updateDockerDocsOfficial(dependsOn: 'generateDockerDocsOfficialContent') {
group = 'convertigo'
description = 'Updates ../docker-docs/convertigo/content.md from docker/README.md. Override with -PdockerDocsDir=path.'
doLast {
def docsDir = dockerDocsOfficialDir()
def target = new File(docsDir, 'convertigo/content.md')
if (!new File(docsDir, '.git').directory) {
throw new GradleException("Docker docs repository not found: ${docsDir}. Use -PdockerDocsDir=path/to/docker-docs if repositories are not siblings.")
}
if (!target.isFile()) {
throw new GradleException("Docker docs target not found: ${target}.")
}
def generated = layout.buildDirectory.file('docker-docs/convertigo/content.md').get().asFile
def generatedText = generated.getText('UTF-8')
if (target.getText('UTF-8') != generatedText) {
def statusProcess = new ProcessBuilder('git', 'status', '--porcelain', '--', 'convertigo/content.md')
.directory(docsDir)
.redirectErrorStream(true)
.start()
def statusOutput = statusProcess.inputStream.getText('UTF-8')
def statusExitCode = statusProcess.waitFor()
if (statusExitCode != 0) {
throw new GradleException("Unable to inspect Docker docs git status in ${docsDir}: ${statusOutput.trim()}")
}
if (!statusOutput.trim().isEmpty() && !project.hasProperty('dockerDocsOverwrite')) {
throw new GradleException("Refusing to overwrite locally modified Docker docs file: ${target}. Use -PdockerDocsOverwrite=true to regenerate it anyway.")
}
target.setText(generatedText, 'UTF-8')
}
}
}
task validateDockerDocsOfficial(dependsOn: 'checkDockerDocsOfficial') {
group = 'convertigo'
description = 'Runs Docker official docs validations for the sibling docker-docs repository.'
doLast {
def docsDir = dockerDocsOfficialDir()
if (!new File(docsDir, '.git').directory) {
throw new GradleException("Docker docs repository not found: ${docsDir}. Use -PdockerDocsDir=path/to/docker-docs if repositories are not siblings.")
}
runDockerDocsOfficialCommand(docsDir, ['/usr/bin/env', 'bash', '-c', '''
files="$(
find . \\( -name '*.md' -not -name 'README.md' \\) -exec test -s '{}' ';' -print0 \
| xargs -0 ./markdownfmt.sh -l
)"
if [ -n "$files" ]; then
echo >&2 'Need markdownfmt:'
echo >&2 "$files"
echo >&2
echo "$files" | xargs ./markdownfmt.sh -d >&2
exit 1
fi
'''.stripIndent().trim()])
runDockerDocsOfficialCommand(docsDir, ['/usr/bin/env', 'bash', '-c', './.ci/check-required-files.sh && ./.ci/check-short.sh && ./.ci/check-pr-no-readme.sh'])
runDockerDocsOfficialCommand(docsDir, ['/usr/bin/env', 'bash', '-c', '''
needdiff=
need=()
while IFS= read -r f; do
fdiff="$(./ymlfmt.sh -d "$f")"
if [ -n "$fdiff" ]; then
need+=( "$f" )
needdiff+="$fdiff"$'\\n'
fi
done < <(find . \\( -name '*.yml' -o -name '*.yaml' \\) -not -path './.github/*')
if [ -n "$needdiff" ]; then
echo >&2 'Need ymlfmt:'
printf '%s\\n' "${need[@]}" >&2
echo >&2
printf '%s' "$needdiff" >&2
exit 1
fi
'''.stripIndent().trim()], ['DOCKER_DEFAULT_PLATFORM': findProperty('dockerDocsYmlPlatform') ?: 'linux/amd64'])
runDockerDocsOfficialCommand(docsDir, ['docker', 'run', '--rm',
'-v', "${docsDir.absolutePath}:/src:ro",
'debian:trixie-slim',
'bash', '-c', '''
set -Eeuo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update >/dev/null
apt-get install -y --no-install-recommends ca-certificates curl git jq util-linux coreutils diffutils findutils >/dev/null
arch="$(dpkg --print-architecture)"
case "$arch" in
amd64) bashbrew_asset='bashbrew-amd64' ;;
arm64) bashbrew_asset='bashbrew-arm64v8' ;;
*) echo >&2 "Unsupported bashbrew validation architecture: $arch"; exit 1 ;;
esac
curl -fsSL -o /usr/local/bin/bashbrew "https://github.com/docker-library/bashbrew/releases/download/v0.1.14/$bashbrew_asset"
chmod +x /usr/local/bin/bashbrew
mkdir /work
cp -a /src/. /work/
cd /work
.ci/check-metadata.sh
if ! ./update.sh convertigo >/tmp/update.log 2>&1; then
cat /tmp/update.log >&2
exit 1
fi
tail -80 /tmp/update.log
'''.stripIndent().trim()
])
}
}
task downloadSystemLibFlowViewer(type: Sync) {
group = 'convertigo'
def car = file 'lib_FlowViewer.car'
ext.dir = file 'eclipse-plugin-studio/tomcat/webapps/convertigo/system/projects/'
from 'eclipse-plugin-studio/src'
into ext.dir
doFirst {
new URL('https://github.com/convertigo/c8oprj-lib-flowviewer/releases/download/ci/lib_FlowViewer.car')
.withInputStream{ i -> car.withOutputStream{ it << i }}
from zipTree(car)
exclude 'com/**'
}
doLast {
car.delete()
}
}
task downloadSystemLibraries(/*dependsOn: downloadSystemLibFlowViewer*/) {
group = 'convertigo'
}
task checkLicenseDate {
group = 'convertigo'
def licenseTextFile = file('legal/dual-license.txt')
inputs.properties(
'currentYear': currentYear
)
inputs.files licenseTextFile
outputs.files licenseTextFile
doFirst {
licenseTextFile.text = licenseTextFile.text.replaceAll('-20\\d\\d-', "-${currentYear}-")
}
}
tasks.register('checkStudioJdkUpToDate') {
group = 'convertigo'
description = 'Checks whether installers/nsis/download-jdk.sh uses the latest Temurin LTS patch release.'
inputs.file 'installers/nsis/download-jdk.sh'
doLast {
def failOnOutdated = project.findProperty('failOnOutdatedStudioJdk')?.toString()?.toBoolean() ?: false
def script = file('installers/nsis/download-jdk.sh').text
def releaseMatcher = (script =~ /(?m)^JDK_RELEASE=(.+)$/)
if (!releaseMatcher.find()) {
def msg = "Cannot read JDK_RELEASE from installers/nsis/download-jdk.sh"
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
return
}
def configuredRelease = releaseMatcher.group(1).trim()
def majorMatcher = (configuredRelease =~ /^(\d+)/)
if (!majorMatcher.find()) {
def msg = "Cannot extract JDK major from JDK_RELEASE=${configuredRelease}"
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
return
}
def configuredMajor = majorMatcher.group(1)
try {
def ltsInfo = new JsonSlurper().parse(new URL("https://api.adoptium.net/v3/info/available_releases"))
def latestLts = ltsInfo?.most_recent_lts?.toString()
if (latestLts && latestLts != configuredMajor) {
logger.warn("A newer Temurin LTS major is available (${latestLts}); script is pinned to ${configuredMajor}.")
}
def assetsUrl = "https://api.adoptium.net/v3/assets/latest/${configuredMajor}/hotspot?image_type=jdk&os=windows&architecture=x64"
def assets = new JsonSlurper().parse(new URL(assetsUrl))
if (!(assets instanceof List) || assets.isEmpty()) {
def msg = "Cannot query latest Temurin release for JDK ${configuredMajor}"
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
return
}
def latestReleaseName = assets[0]?.release_name?.toString()
if (!latestReleaseName?.startsWith('jdk-')) {
def msg = "Unexpected Adoptium response: release_name=${latestReleaseName}"
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
return
}
def latestRelease = latestReleaseName.substring(4)
if (configuredRelease == latestRelease) {
logger.lifecycle("Studio JDK is up to date (${configuredRelease}).")
} else {
def msg = "Studio JDK is outdated: configured ${configuredRelease}, latest Temurin ${configuredMajor} is ${latestRelease}."
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
}
} catch (Exception e) {
def msg = "Failed to check Studio JDK update status: ${e.message}"
if (failOnOutdated) {
throw new GradleException(msg, e)
}
logger.warn(msg)
}
}
}
tasks.register('checkSwaggerUiUpToDate') {
group = 'convertigo'
description = 'Checks whether swaggerUiVersion matches the latest swagger-ui GitHub release.'
inputs.property('swaggerUiVersion', swaggerUiVersion)
doLast {
def failOnOutdated = project.findProperty('failOnOutdatedSwaggerUi')?.toString()?.toBoolean() ?: false
def configured = swaggerUiVersion.toString()
try {
def connection = new URL('https://api.github.com/repos/swagger-api/swagger-ui/releases/latest').openConnection()
connection.setRequestProperty('Accept', 'application/vnd.github+json')
connection.setRequestProperty('User-Agent', 'convertigo-gradle')
def release = new JsonSlurper().parse(connection.inputStream)
def latest = release?.tag_name?.toString()?.replaceFirst(/^v/, '')
if (!latest) {
def msg = 'Cannot read the latest swagger-ui release tag from GitHub'
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
return
}
if (configured == latest) {
logger.lifecycle("swagger-ui is up to date (${configured}).")
} else {
def configuredLine = configured.tokenize('.').take(2).join('.')
def latestLine = latest.tokenize('.').take(2).join('.')
def scope = configuredLine == latestLine ? 'patch' : 'minor/major'
def msg = "swagger-ui is outdated (${scope} update): configured ${configured}, latest release is ${latest}. Update swaggerUiVersion in build.gradle and run :engine:unzipSwaggerUI."
if (failOnOutdated) {
throw new GradleException(msg)
}
logger.warn(msg)
}
} catch (Exception e) {
def msg = "Failed to check swagger-ui update status: ${e.message}"
if (failOnOutdated) {
throw new GradleException(msg, e)
}
logger.warn(msg)
}
}
}
def extractVersionLine = { String version ->
def components = ((version ?: '') =~ /\d+/).collect { it.toString() }
if (components.isEmpty()) {
return null
}
while (components.size() < 3) {
components.add('0')
}
return components.take(3)
}
def shouldRejectDependencyUpdate = { selection, boolean samePatchLineOnly ->
def version = selection.candidate.version ?: ''
def lcVersion = version.toLowerCase()
def module = "${selection.candidate.group}:${selection.candidate.module}"
// Reject all pre-releases consistently (case-insensitive).
if (lcVersion.matches('.*(alpha|beta|rc|cr|m[0-9]+|ea|preview|snapshot|-b).*')) {
return true
}
// Keep legacy major lines for risky dependencies.
switch (module) {
case 'commons-collections:commons-collections':
return !version.startsWith('3.')
case 'net.sf.jt400:jt400':
return !version.startsWith('11.')
case 'org.apache.axis2:axis2-saaj':
return !version.startsWith('1.8.')
case 'org.apache.xmlbeans:xmlbeans':
// Internal patched flavor (e.g. 3.1.1-sb-fixed), keep only the exact current version.
return version != selection.currentVersion
case 'com.convertigo.lib:qrcode':
// Internal Jakarta build, do not report the upstream non-Jakarta artifact as an update.
return !version.endsWith('-jakarta')
case 'org.apache.ws.commons.axiom:axiom-api':
case 'org.apache.ws.commons.axiom:axiom-dom':
case 'org.apache.ws.commons.axiom:axiom-impl':
return !version.startsWith('1.')
}
// Tomcat is constrained to major 9.x.
if (selection.candidate.group in ['org.apache.tomcat', 'org.apache.tomcat.embed']) {
def candidateLine = extractVersionLine(version)
def candidateMajor = candidateLine == null ? null : new BigInteger(candidateLine[0])
if (candidateMajor != null && candidateMajor > BigInteger.valueOf(11)) {
return true
}
}
if (samePatchLineOnly) {
def currentLine = extractVersionLine(selection.currentVersion)
def candidateLine = extractVersionLine(version)
if (currentLine != null && candidateLine != null && currentLine.take(2) != candidateLine.take(2)) {
return true
}
}
return false
}
def configureDependencyUpdatesReport = { DependencyUpdatesTask task, boolean samePatchLineOnly ->
if (task.project == rootProject) {
task.finalizedBy(rootProject.tasks.named("checkStudioJdkUpToDate"))
task.finalizedBy(rootProject.tasks.named("checkSwaggerUiUpToDate"))
}
task.revision = 'release'
task.gradleReleaseChannel = 'current'
task.rejectVersionIf {
shouldRejectDependencyUpdate(it, samePatchLineOnly)
}
}
// https://github.com/ben-manes/gradle-versions-plugin
allprojects {
tasks.named("dependencyUpdates", DependencyUpdatesTask).configure { task ->
configureDependencyUpdatesReport(task, false)
}
tasks.register("dependencyUpdatesPatch", DependencyUpdatesTask) { task ->
group = "help"
description = "Displays dependency updates limited to the current patch line."
configureDependencyUpdatesReport(task, true)
}
}
['dependencyUpdates', 'dependencyUpdatesPatch'].each { taskName ->
tasks.named(taskName, DependencyUpdatesTask).configure { task ->
task.dependsOn(subprojects.collect { it.tasks.named(taskName) })
}
}
wrapper {
group = 'convertigo'
gradleVersion = '9.8.2'
}