import groovy.json.JsonBuilder import groovy.json.JsonSlurper import org.apache.tools.ant.filters.ReplaceTokens import org.sonar.build.LicenseReader plugins { id "com.github.hierynomus.license-report" id "com.gradleup.shadow" id "de.undercouch.download" id "org.cyclonedx.bom" } sonar { properties { property 'sonar.projectName', "${projectTitle} :: Application" } } configurations { zipDist zip scanner web shutdowner jdbc_mssql { transitive = false } jdbc_postgresql { transitive = false } jdbc_h2 { transitive = false } bundledPlugin { transitive = false } bundledPlugin_deps { extendsFrom bundledPlugin transitive = true } appLicenses.extendsFrom(api, web, scanner, jdbc_mssql, jdbc_postgresql, jdbc_h2, bundledPlugin_deps) cyclonedx } jar.enabled = false shadowJar { archiveBaseName = 'sonar-application' archiveClassifier = null mergeServiceFiles() zip64 true manifest { attributes('Main-Class': 'org.sonar.application.App') } } dependencies { // please keep list ordered api 'org.slf4j:slf4j-api' api 'org.elasticsearch.client:elasticsearch-rest-high-level-client' api 'org.sonarsource.api.plugin:sonar-plugin-api' api project(':server:sonar-ce') api project(':server:sonar-main') api project(':server:sonar-process') api project(':server:sonar-webserver') api project(':sonar-core') api project(':sonar-plugin-api-impl') compileOnlyApi 'com.github.spotbugs:spotbugs-annotations' scanner project(path: ':sonar-scanner-engine-shaded', configuration: 'shadow') cyclonedx project(path: ':sonar-scanner-engine-shaded') shutdowner project(':sonar-shutdowner') jdbc_h2 'com.h2database:h2' jdbc_mssql 'com.microsoft.sqlserver:mssql-jdbc' jdbc_postgresql 'org.postgresql:postgresql' webapp 'org.sonarsource.sonarqube:webapp-assets' def artifactoryUsername = System.env.'ARTIFACTORY_PRIVATE_USERNAME' ?: (project.hasProperty('artifactoryUsername') ? project.getProperty('artifactoryUsername') : '') def artifactoryPassword = System.env.'ARTIFACTORY_PRIVATE_PASSWORD' ?: (project.hasProperty('artifactoryPassword') ? project.getProperty('artifactoryPassword') : '') if (artifactoryUsername && artifactoryPassword) { zipDist "sonarqube:elasticsearch:${elasticSearchServerVersion}-no-jdk@tar.gz" } else { zipDist "elasticsearch:elasticsearch:${elasticSearchServerVersion}-linux-x86_64@tar.gz" } } // declare dependencies in configuration bundledPlugin to be packaged in lib/extensions apply from: 'bundled_plugins.gradle' //verify if sonar.properties files does not have any external input task verifySonarProperties(type: Verify) { def propertiesFile = file('src/main/assembly/conf/sonar.properties') propertiesFile.withReader { reader -> def line while ((line = reader.readLine()) != null) { if (!line.startsWith('#') && !line.isEmpty()) { throw new GradleException('sonar.properties file by default must not provide any user configuration.') } } } } downloadLicenses { dependencyConfiguration = 'appLicenses' } tasks.register('downloadJres') { doLast { def jresMetadata = new JsonSlurper().parse(file(layout.projectDirectory.dir('src/main/resources/jres-metadata.json').asFile)) jresMetadata.each { jre -> downloadJreFromAdoptium(jre.os, jre.arch, jre.filename, jre.sha256) } } } task zip(type: Zip, dependsOn: [configurations.compileClasspath]) { duplicatesStrategy DuplicatesStrategy.EXCLUDE def archiveDir = "sonarqube-$project.version" dependsOn tasks.downloadJres into("${archiveDir}/jres") { from(layout.buildDirectory.dir('jres')) } if(release) { dependsOn tasks.downloadLicenses into("${archiveDir}/") { from(tasks.downloadLicenses.outputs) { include 'dependency-license.json' filter(LicenseReader) } } } into("${archiveDir}/") { from(file('src/main/assembly')) { exclude 'conf/sonar.properties' exclude 'bin/windows-x86-64/lib/SonarServiceWrapperTemplate.xml' exclude 'bin/windows-x86-64/StartSonar.bat' exclude 'bin/linux-x86-64/sonar.sh' exclude 'bin/macosx-universal-64/sonar.sh' } } ResolvedArtifact elasticSearchArtifact = configurations.zipDist.resolvedConfiguration.resolvedArtifacts.find { it.moduleVersion.id.name == "elasticsearch" } from(tarTree(elasticSearchArtifact.file)) { eachFile { fcd -> def path = fcd.relativePath.segments - fcd.relativeSourcePath.segments + fcd.relativeSourcePath.segments.drop(1) fcd.relativePath = new RelativePath(true, *path) } into("${archiveDir}/elasticsearch") exclude '**/bin/elasticsearch-certgen' exclude '**/bin/elasticsearch-certutil' exclude '**/bin/elasticsearch-create-enrollment-token' exclude '**/bin/elasticsearch-croneval' exclude '**/bin/elasticsearch-env-from-file' exclude '**/bin/elasticsearch-geoip' exclude '**/bin/elasticsearch-keystore' exclude '**/bin/elasticsearch-node' exclude '**/bin/elasticsearch-plugin' exclude '**/bin/elasticsearch-reconfigure-node' exclude '**/bin/elasticsearch-reset-password' exclude '**/bin/elasticsearch-saml-metadata' exclude '**/bin/elasticsearch-service-tokens' exclude '**/bin/elasticsearch-setup-passwords' exclude '**/bin/elasticsearch-shard' exclude '**/bin/elasticsearch-sql-cli' exclude '**/bin/elasticsearch-sql-cli-8.6.1.jar' exclude '**/bin/elasticsearch-syskeygen' exclude '**/bin/elasticsearch-users' exclude '**/jdk/**' exclude '**/lib/tools/ansi-console' exclude '**/lib/tools/geoip-cli' exclude '**/lib/tools/plugin-cli' exclude '**/modules/aggs-matrix-stats/**' exclude '**/modules/blob-cache/**' exclude '**/modules/constant-keyword/**' exclude '**/modules/data-streams/**' exclude '**/modules/frozen-indices/**' exclude '**/modules/inference/**' exclude '**/modules/ingest-attachment/**' exclude '**/modules/ingest-common/**' exclude '**/modules/ingest-geoip/**' exclude '**/modules/ingest-user-agent/**' exclude '**/modules/kibana/**' exclude '**/modules/lang-expression/**' exclude '**/modules/lang-mustache/**' exclude '**/modules/legacy-geo/**' exclude '**/modules/mapper-extras/**' exclude '**/modules/mapper-version/**' exclude '**/modules/ml-package-loader/**' exclude '**/modules/percolator/**' exclude '**/modules/rank-eval/**' exclude '**/modules/rank-rrf/**' exclude '**/modules/repositories-metering-api/**' exclude '**/modules/repository-azure/**' exclude '**/modules/repository-encrypted/**' exclude '**/modules/repository-gcs/**' exclude '**/modules/repository-s3/**' exclude '**/modules/repository-url/**' exclude '**/modules/runtime-fields-common/**' exclude '**/modules/searchable-snapshots/**' exclude '**/modules/search-business-rules/**' exclude '**/modules/snapshot-based-recoveries/**' exclude '**/modules/snapshot-repo-test-kit/**' exclude '**/modules/spatial/**' exclude '**/modules/transform/**' exclude '**/modules/unsigned-long/**' exclude '**/modules/vectors/**' exclude '**/modules/vector-tile/**' exclude '**/modules/wildcard/**' exclude '**/modules/x-pack-aggregate-metric' exclude '**/modules/x-pack-aggregate-metric/**' exclude '**/modules/x-pack-analytics/**' exclude '**/modules/x-pack-async-search/**' exclude '**/modules/x-pack-async/**' exclude '**/modules/x-pack-autoscaling/**' exclude '**/modules/x-pack-ccr/**' exclude '**/modules/x-pack-deprecation/**' exclude '**/modules/x-pack-downsample/**' exclude '**/modules/x-pack-enrich/**' exclude '**/modules/x-pack-ent-search/**' exclude '**/modules/x-pack-eql/**' exclude '**/modules/x-pack-esql/**' exclude '**/modules/x-pack-fleet/**' exclude '**/modules/x-pack-graph/**' exclude '**/modules/x-pack-identity-provider/**' exclude '**/modules/x-pack-ilm/**' exclude '**/modules/x-pack-logstash/**' exclude '**/modules/x-pack-ml/**' exclude '**/modules/x-pack-monitoring/**' exclude '**/modules/x-pack-profiling/**' exclude '**/modules/x-pack-ql/**' exclude '**/modules/x-pack-rollup/**' exclude '**/modules/x-pack-shutdown/**' exclude '**/modules/x-pack-slm/**' exclude '**/modules/x-pack-sql/**' exclude '**/modules/x-pack-stack/**' exclude '**/modules/x-pack-text-structure/**' exclude '**/modules/x-pack-voting-only-node/**' exclude '**/modules/x-pack-watcher/**' exclude '**/modules/x-pack-write-load-forecaster/**' includeEmptyDirs = false } into("${archiveDir}/conf/") { from file('src/main/assembly/conf/sonar.properties') filter(ReplaceTokens, tokens: [ 'searchDefaultHeapSize': '512MB', 'searchJavaOpts' : '-Xmx512m -Xms512m -XX:MaxDirectMemorySize=256m -XX:+HeapDumpOnOutOfMemoryError', 'ceDefaultHeapSize' : '512MB', 'ceJavaOpts' : '-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError', 'webDefaultHeapSize' : '512MB', 'webJavaOpts' : '-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError' ]) } into("${archiveDir}/bin/linux-x86-64/") { from file('src/main/assembly/bin/linux-x86-64/sonar.sh') filter(ReplaceTokens, tokens: [ 'sqversion': version ]) } into("${archiveDir}/bin/macosx-universal-64/") { from file('src/main/assembly/bin/macosx-universal-64/sonar.sh') filter(ReplaceTokens, tokens: [ 'sqversion': version ]) } into("${archiveDir}/bin/windows-x86-64/") { from file('src/main/assembly/bin/windows-x86-64/StartSonar.bat') filter(ReplaceTokens, tokens: [ 'sqversion': version ]) } into("${archiveDir}/bin/windows-x86-64/lib/") { from file('src/main/assembly/bin/windows-x86-64/lib/SonarServiceWrapperTemplate.xml') filter(ReplaceTokens, tokens: [ 'sqversion': version ]) } // Create the empty dir (plugins) required by elasticsearch into("${archiveDir}/elasticsearch/") { // Create the empty dir required by elasticsearch from { new File(buildDir, 'elasticsearch/plugins').mkdirs() "$buildDir/elasticsearch" } } into("${archiveDir}/lib/extensions/") { from configurations.bundledPlugin } into("${archiveDir}/lib/scanner/") { from configurations.scanner } into("${archiveDir}/lib/") { from shadowJar } into("${archiveDir}/web/") { duplicatesStrategy DuplicatesStrategy.FAIL def webappBuildPath = System.getenv('WEBAPP_BUILD_PATH') if (webappBuildPath == null) { from(zipTree(configurations.webapp.singleFile)) } else { assert rootProject.file(webappBuildPath).exists() from(rootProject.fileTree(webappBuildPath)) } if (official) { from project(':private:branding').file('src') } } into("${archiveDir}/lib/jdbc/mssql/") { from configurations.jdbc_mssql } into("${archiveDir}/lib/jdbc/postgresql/") { from configurations.jdbc_postgresql } into("${archiveDir}/lib/jdbc/h2/") { from configurations.jdbc_h2 } into("${archiveDir}/lib/") { from configurations.shutdowner } } // Check the size of the archive zip { mustRunAfter cyclonedxBom doLast { //When the archive size increases due to dependencies, the expected size should be updated as well. //Bump the expected size by at least 10 more megabytes than what is strictly needed, this in conjunction with the //tolerance will allow for some growth in the archive size. def expectedSize = 810_000_000 //We set a tolerance of 15MB to avoid failing the build for small differences in the archive size. def tolerance = 15000000 def minArchiveSize = expectedSize - tolerance def maxArchiveSize = expectedSize + tolerance def archiveSize = archiveFile.get().asFile.length() if (archiveSize < minArchiveSize) throw new GradleException("${archiveFileName.get()} size ($archiveSize) too small. Min is $minArchiveSize") if (archiveSize > maxArchiveSize) throw new GradleException("${destinationDirectory.get()}/${archiveFileName.get()} size ($archiveSize) too large. Max is $maxArchiveSize") } } assemble.dependsOn zip // the script start.sh unpacks OSS distribution into $buildDir/distributions/sonarqube-oss. // This directory should be deleted when the zip is changed. task cleanLocalUnzippedDir(dependsOn: zip) { def unzippedDir = file("$buildDir/distributions/sonarqube-$version") inputs.files(file("$buildDir/distributions/sonar-application-${version}.zip")) outputs.upToDateWhen { true } doLast { println("delete directory ${unzippedDir}") project.delete(unzippedDir) } } assemble.dependsOn cleanLocalUnzippedDir artifacts { zip zip } artifactoryPublish.skip = !deployCommunity def bomFile = layout.buildDirectory.file('reports/bom.json') cyclonedxBom { includeConfigs = ["runtimeClasspath", "web", "shutdowner", "jdbc_mssql", "jdbc_postgresql", "jdbc_h2", "bundledPlugin_deps", "cyclonedx"] outputs.file bomFile } tasks.cyclonedxBom { inputs.files(configurations.runtimeClasspath, configurations.shutdowner, configurations.jdbc_mssql, configurations.jdbc_postgresql, configurations.jdbc_h2, configurations.bundledPlugin_deps, configurations.cyclonedx) } def bomArtifact = artifacts.add('archives', bomFile.get().asFile) { type 'json' classifier 'cyclonedx' builtBy 'cyclonedxBom' } publishing { publications { mavenJava(MavenPublication) { artifact zip } if (enableBom) { mavenJava(MavenPublication) { artifact bomArtifact } } } } String.metaClass.urlEncode = { -> URLEncoder.encode(delegate as String, 'UTF-8') } ext { WINDOWS = 'windows' MAC = 'mac' ALPINE_ADOPTIUM = 'alpine-linux' ALPINE = 'alpine' } /** * Run this task only when you want to update embedded JREs metadata. Please double check the generated file jres-metadata.json before committing. */ tasks.register('updateJresMetadata') { doLast { // download JSON file from Adoptium API URL apiUrl = new URL("https://api.adoptium.net/v3/assets/release_name/eclipse/${jre_release_name.urlEncode()}?heap_size=normal&image_type=jre&project=jdk") def response = new JsonSlurper().parse(apiUrl) def metadataContent = [] def supportedOsAndArch = [[os:'linux', arch:'x64'], [os:'linux', arch:'aarch64'], [os:project.ext.ALPINE_ADOPTIUM, arch:'x64'], [os: project.ext.WINDOWS, arch:'x64'], [os:project.ext.MAC, arch:'x64'], [os:'mac', arch:'aarch64']] for (jreFlavor in supportedOsAndArch) { var candidates = response.binaries.findAll { it.os == jreFlavor.os && it.architecture == jreFlavor.arch } assert candidates.size() == 1, "Expected one JRE package for ${jreFlavor.os} ${jreFlavor.arch} but got ${candidates.size()}" var jre = candidates[0] metadataContent << [ 'id': UUID.randomUUID(), 'filename': jre.package.name, 'sha256': jre.package.checksum, 'javaPath': "${jre_release_name}-jre" + (jreFlavor.os == project.ext.MAC ? '/Contents/Home' : '') + '/bin/java' + (jreFlavor.os == project.ext.WINDOWS ? '.exe' : ''), 'os': jreFlavor.os.replace(project.ext.ALPINE_ADOPTIUM, project.ext.ALPINE), 'arch': jreFlavor.arch ] } def jresMetadata = file(layout.projectDirectory.dir('src/main/resources/jres-metadata.json').asFile) jresMetadata.text = new JsonBuilder(metadataContent).toPrettyString() } } def downloadJreFromAdoptium(os, arch, filename, sha256) { def jreFile = layout.buildDirectory.file("jres/${filename}") download.run { src "https://api.adoptium.net/v3/binary/version/${jre_release_name.urlEncode()}/${os.replace(ext.ALPINE, ext.ALPINE_ADOPTIUM).urlEncode()}/${arch.urlEncode()}/jre/hotspot/normal/eclipse?project=jdk" dest jreFile overwrite false } verifyChecksum.run { src jreFile algorithm 'SHA-256' checksum sha256 } }