// Copyright 2019 The Nomulus Authors. All Rights Reserved. // // Licensed 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. import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent buildscript { if (rootProject.enableDependencyLocking.toBoolean()) { // Lock buildscript dependencies. configurations.classpath { resolutionStrategy.activateDependencyLocking() // log4j has high-profile security vulnerabilities. It's a transitive // dependency used by Gradle itself during build, and not strictly needed. exclude group: 'org.apache.logging.log4j' } } dependencies { classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.5.0' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:5.1.0' classpath 'com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.4.0' classpath 'org.sonatype.aether:aether-api:1.13.1' classpath 'org.sonatype.aether:aether-impl:1.13.1' } } plugins { // Java static analysis plugins. // Re-enable when compatible with Gradle 8 // id 'nebula.lint' version '16.0.2' id 'net.ltgt.errorprone' version '5.1.0' id 'checkstyle' id 'com.gradleup.shadow' version '9.4.0' apply false // NodeJs plugin id "com.github.node-gradle.node" version "7.1.0" id 'idea' id 'com.diffplug.spotless' version '8.4.0' id 'jacoco' id 'com.dorongold.task-tree' version '2.1.0' } dependencyLocking { lockAllConfigurations() } node { download = false version = "22.7.0" } wrapper { distributionType = Wrapper.DistributionType.ALL } apply from: 'dependencies.gradle' apply from: 'dependency_lic.gradle' apply from: 'utils.gradle' // The license-report plugin must run with --no-parallel due to // complex cross-subject references. The `mutex` pattern does not // help because a mutex does not enforce task execution order. // For now we separate checkLicense from build so that the latter may // still take advantage of parallelism, which cuts down the build time // by about 20%. The presubmit and release procedures that want to check // licenses must invoke checkLicense explicitly with the `--no-parallel` // flag. // tasks.build.dependsOn(tasks.checkLicense) // Provide defaults for all of the project properties. // Only do linting if the build is successful. // Re-enable when compatible with Gradle 8 // gradleLint.autoLintAfterFailure = false // Paths to main and test sources. ext.projectRootDir = "${rootDir}" // Tasks to deploy/stage all services task deploy { group = 'deployment' description = 'Deploys all services.' } task stage { group = 'deployment' description = 'Generates application directories for all services.' } def gcpProject = null apply from: "${rootDir.path}/projects.gradle" if (environment == '') { // Keep the project null, this will prevent deployment. Set the // environment to "alpha" because other code needs this property to // explode the war file. environment = 'alpha' } else { gcpProject = projects[environment] if (gcpProject == null) { throw new GradleException("-Penvironment must be one of " + "${projects.keySet()}.") } project(':console-webapp').setProperty('configuration', environment) } rootProject.ext.environment = environment rootProject.ext.gcpProject = gcpProject rootProject.ext.baseDomain = baseDomains[environment] rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox'] // Function to verify that the deployment parameters have been set. def verifyDeploymentParams() { if (prodOrSandboxEnv) { // Do not deploy to prod or sandbox. Print a prominent error in bright red. System.err.println('\033[31;1m-----------------------------------------------------------------') System.err.println('*** DANGER WILL ROBINSON!') System.err.println('*** You may not deploy to production or sandbox from gradle. Do a') System.err.println('*** release from Spinnaker, see deployment playbook.') System.err.println('-----------------------------------------------------------------') throw new GradleException('Aborting. See prominent error above.') } else if (gcpProject == null) { def error = 'You must specify -Penvironment={alpha,crash,qa}' System.err.println("\033[33;1m${error}\033[0m") throw GradleException("Aborting: ${error}") } } // Closure that we can just drop into all of our deployment tasks. rootProject.ext.verifyDeploymentConfig = { doFirst { verifyDeploymentParams() } } // Subproject configuration. // Alias this since it collides with the closure variable name def allowInsecure = allowInsecureProtocol allprojects { // Skip no-op project if (project.name == 'services') return repositories { if (!mavenUrl.isEmpty()) { maven { println "Java dependencies: Using repo ${mavenUrl}..." url = mavenUrl allowInsecureProtocol = allowInsecure == "true" } } else { println "Java dependencies: Using Maven Central..." mavenCentral() google() maven { url = "https://packages.confluent.io/maven/" content { includeGroup "io.confluent" } } } } if (rootProject.enableCrossReferencing.toBoolean()) { gradle.projectsEvaluated { tasks.withType(JavaCompile) { options.fork = true options.forkOptions.executable = file("${System.env.JAVA_HOME}/bin/javac") options.compilerArgs = ["--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"] options.forkOptions.jvmArgs = ["-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", "-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"] } } } } rootProject.ext { pyver = { exe -> try { ext.execInBash( exe + " -c 'import sys; print(sys.hexversion)' 2>/dev/null", "/") as Integer } catch (org.gradle.process.internal.ExecException e) { return -1; } } // Return the path to a usable python3 executable. getPythonExecutable = { // Find a python version greater than 3.7.3 (this is somewhat arbitrary, we // know we'd like at least 3.6, but 3.7.3 is the latest that ships with // Debian so it seems like that should be available anywhere). def MIN_PY_VER = 0x3070300 if (pyver('python') >= MIN_PY_VER) { return 'python' } else if (pyver('/usr/bin/python3') >= MIN_PY_VER) { return '/usr/bin/python3' } else { throw new GradleException("No usable Python version found (build " + "requires at least python 3.7.3)"); } } } task runPresubmits(type: Exec) { args('config/presubmits.py') doFirst { executable getPythonExecutable() } } def javadocSource = [] def javadocClasspath = [] def javadocDependentTasks = [] def services = [':services:default', ':services:backend', ':services:bsa', ':services:tools', ':services:pubapi'] subprojects { // Skip no-op project if (project.name == 'services') return apply plugin: 'com.gradleup.shadow' tasks.configureEach { if (it.class.name.contains('ShadowJar')) { it.zip64 = true } } ext.createUberJar = { taskName, binaryName, mainClass, List configs = [project.configurations.runtimeClasspath], List srcOutput = [project.sourceSets.main.output], List excludes = [] -> project.tasks.create( taskName, project.tasks.shadowJar.class) { zip64 = true mergeServiceFiles() archiveBaseName = binaryName if (mainClass != '') { manifest { attributes 'Main-Class': mainClass } } // Build as a multi-release jar since we've got member jars (e.g., dnsjava // and snakeyaml) that are multi-release. manifest { attributes 'Multi-Release': true } zip64 = true archiveClassifier = '' archiveVersion = '' configurations = configs from srcOutput // Excludes signature files that accompany some dependency jars, like // bonuncycastle. If they are present, only classes from those signed jars are // made available to the class loader. // see https://discuss.gradle.org/t/signing-a-custom-gradle-plugin-thats-downloaded-by-the-build-system-from-github/1365 exclude "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA" exclude excludes // We do seem to get duplicates when constructing uber-jars, either // this is a product of something in gradle 7 or a product of gradle 7 // now giving an error about them when it didn't previously. duplicatesStrategy = DuplicatesStrategy.WARN } } if (rootProject.enableDependencyLocking.toBoolean()) { buildscript { // Lock buildscript dependencies. configurations.classpath { resolutionStrategy.activateDependencyLocking() } } } afterEvaluate { if (rootProject.enableDependencyLocking.toBoolean() && project.name != 'integration') { // The ':integration' project runs server/schema integration tests using // dynamically specified jars with no transitive dependency. Therefore // dependency-locking does not make sense. Furthermore, during // evaluation it resolves the 'testRuntimeOnly' configuration, making it // immutable. Locking activation would trigger an invalid operation // exception. // // For all other projects, due to problem with the gradle-license-report // plugin, the `detached` configurations by this plugin must opt out of // dependency-locking. See dependency_lic.gradle for the reason why. // // To selectively activate dependency locking without hardcoding them // in the 'configurations' block, the following code must run after // project evaluation, when all configurations have been created. configurations.all { if (!it.name.contains('detachedConfiguration')) { it.resolutionStrategy.activateDependencyLocking() } } } } // Set up all of the deployment projects. if (services.contains(project.path)) { // Return early, do not apply the settings below. return } apply from: "${rootDir.path}/java_common.gradle" // When changing Java version here, be sure to update BEAM Java runtime: // search for `flex-template-base-image` and update the parameter value. // There are at least two instances, one in core/build.gradle, one in // release/stage_beam_pipeline.sh // Also need to change: // - base images in Dockerfiles under core, jetty, and proxy. // - Java installation command in the builder image under release. // - cloudbuild-release.yaml under release. java { sourceCompatibility = JavaVersion.VERSION_25 targetCompatibility = JavaVersion.VERSION_25 } project.tasks.test.dependsOn runPresubmits def commonlyExcludedResources = ['**/*.java', '**/BUILD'] project.ext.javaDir = "${project.projectDir}/src/main/java" project.ext.javaTestDir = "${project.projectDir}/src/test/java" project.ext.resourcesSourceDir = "${project.projectDir}/src/main/resources" sourceSets { main { resources { srcDirs += project.ext.javaDir exclude commonlyExcludedResources } } test { resources { srcDirs += project.ext.javaTestDir exclude commonlyExcludedResources } } } // No need to produce javadoc for the jetty subproject, which has no APIs to // expose to users. if (project.name != 'jetty' && !services.contains(project.path)) { javadocSource << project.sourceSets.main.allJava javadocClasspath << { project.sourceSets.main.runtimeClasspath.files } javadocClasspath << "${buildDir}/generated/sources/annotationProcessor/java/main" if (project.tasks.findByName('compileJava')) { javadocDependentTasks << project.tasks.compileJava } if (project.tasks.findByName('processResources')) { javadocDependentTasks << project.tasks.processResources } } } // If "-P verboseTestOutput=true" is passed in, configure all subprojects to dump all of their // output and final test status (pass/fail, errors) for each test class. // // Note that we can't do this in the main subprojects section above because that's evaluated before // the subproject build files and the test tasks haven't been defined yet. We have to do it from // the projectsEvaluted hook, which gets called after the subprojects are configured. if (verboseTestOutput.toBoolean()) { gradle.projectsEvaluated({ subprojects { tasks.withType(Test) { testLogging { events TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT, TestLogEvent.STANDARD_ERROR exceptionFormat TestExceptionFormat.FULL showExceptions true showCauses true showStackTraces true afterSuite { desc, result -> println "Results: ${result.resultType}, " + "${result.successfulTestCount}/${result.testCount} tests " + "passed, ${result.failedTestCount} failures."; } } } } }) } task checkDependenciesDotGradle { doLast { Set depsInUse = [] allprojects { configurations.all { it.dependencies.findAll { it.group != null }.each { // Note: .toString() is required since GString should // not be mixed with Java Strings. depsInUse.add("${it.group}:${it.name}".toString()) } } } def unusedDeps = rootProject.dependencyMap.keySet() .findAll { !depsInUse.contains(it) } .toSorted() if (unusedDeps.isEmpty()) { return } logger.error( "Unused dependencies in dependencies.gradle:\n${unusedDeps.toListString()}") throw new IllegalStateException( "The dependencies.gradle file should only contain direct dependencies.") } } tasks.build.dependsOn(tasks.checkDependenciesDotGradle) rootProject.ext { invokeJavaDiffFormatScript = { action -> def javaHome = project.findProperty('org.gradle.java.home') def javaBin if (javaHome != null) { javaBin = "$javaHome/bin/java" } else { javaBin = ext.execInBash("which java", rootDir) } println("Running the formatting tool with $javaBin") def scriptDir = "${rootDir}/java-format" def workingDir = rootDir def formatDiffScript = "${scriptDir}/google-java-format-git-diff.sh" def pythonExe = getPythonExecutable() return ext.execInBash( "JAVA=${javaBin} PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}") } } // Checks if modified lines in Java source files need reformatting. // Note that this task checks modified Java files in the entire repository. task javaIncrementalFormatCheck { doLast { // We can only do this in a git tree. if (new File("${rootDir}/.git").exists()) { def checkResult = invokeJavaDiffFormatScript("check") if (checkResult == 'true') { throw new IllegalStateException( "Some Java files need to be reformatted. You may use the " + "'javaIncrementalFormatDryRun' task to review\n " + "the changes, or the 'javaIncrementalFormatApply' task " + "to reformat.") } else if (checkResult != 'false') { throw new RuntimeException( "Failed to invoke format check script:\n" + checkResult) } println("Incremental Java format check ok.") } else { println("Omitting format check: not in a git directory.") } } } // Shows how modified lines in Java source files will change after formatting. // Note that this task checks modified Java files in the entire repository. task javaIncrementalFormatDryRun { doLast { println("${invokeJavaDiffFormatScript("show")}") } } tasks.build.dependsOn(tasks.javaIncrementalFormatCheck) // Checks if modified lines in Java source files need reformatting. // Note that this task processes modified Java files in the entire repository. task javaIncrementalFormatApply { doLast { invokeJavaDiffFormatScript("format") } } task javadoc(type: Javadoc) { source javadocSource // Java 11.0.17 has the following bug that affects annotation handling on // package-info.java: // https://bugs.openjdk.org/browse/JDK-8222091 exclude "**/package-info.java" classpath = files(javadocClasspath) destinationDir = file("${buildDir}/docs/javadoc") options.encoding = "UTF-8" // In a lot of places we don't write @return so suppress warnings about that. // We don't report HTML lint errors because XJB-generated POJO files have // incorrect tags (like dangling

without the corresponding open tag. // Starting in Java 25, references to primitives and arrays are forbidden. // The JAXB-generated classes have array references, and we suppress the // error with '-reference'. options.addBooleanOption('Xdoclint:all,-missing,-html,-reference', true) options.addBooleanOption("-allow-script-in-comments",true) options.tags = ["type:a:Generic Type", "error:a:Expected Error", "invariant:a:Guaranteed Property"] } tasks.build.dependsOn(tasks.javadoc) // Task for doing development on core Nomulus. // This fixes code formatting automatically as necessary, builds and tests the // core Nomulus codebase, and runs all presubmits. task coreDev { dependsOn 'javaIncrementalFormatApply' dependsOn 'console-webapp:applyFormatting' dependsOn 'javadoc' dependsOn 'checkDependenciesDotGradle' dependsOn 'checkLicense' dependsOn ':core:check' dependsOn 'assemble' if (gradle.startParameter.parallelProjectExecutionEnabled && gradle.startParameter.taskNames.contains("coreDev")) { throw new GradleException( "ERROR: 'coreDev' cannot run with --parallel due to checkLicense constraints.\n" + "Please run: ./gradlew coreDev --no-parallel" ) } } javadocDependentTasks.each { tasks.javadoc.dependsOn(it) } // Runs the script, which deploys cloud scheduler and tasks based on the config task deployCloudSchedulerAndQueue { doLast { def env = environment if (!prodOrSandboxEnv) { exec { workingDir "${rootDir}/release/builder/" commandLine 'go', 'run', "./deployCloudSchedulerAndQueue.go", "${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml", "${rootDir}/core/src/main/java/google/registry/config/files/tasks/cloud-scheduler-tasks-${env}.xml", "domain-registry-${env}" } exec { workingDir "${rootDir}/release/builder/" commandLine 'go', 'run', "./deployCloudSchedulerAndQueue.go", "${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml", "${rootDir}/core/src/main/java/google/registry/config/files/cloud-tasks-queue.xml", "domain-registry-${env}" } } } } // disable javadoc in subprojects, these will break because they don't have // the correct classpath (see above). gradle.taskGraph.whenReady { graph -> graph.getAllTasks().each { task -> def subprojectJavadoc = (task.path =~ /:.+:javadoc/) if (subprojectJavadoc) { println "Skipping ${task.path} for javadoc (only root javadoc works)" task.enabled = false } } } task fastBuild { group = 'build' description = 'A lightweight build for local dev. Compiles Java, runs standard tests, and checks formatting, but skips Docker images, fragile tests, and the massive Angular console builds. (Do not use this target to verify console changes.)' dependsOn build // Remove the heavy default dependencies specifically for fastBuild gradle.taskGraph.whenReady { graph -> if (graph.hasTask(fastBuild)) { project(':console-webapp').tasks.named('buildConsoleForAll').get().enabled = false project(':jetty').tasks.named('buildNomulusImage').get().enabled = false project(':core').tasks.named('buildToolImage').get().enabled = false project(':core').tasks.named('fragileTest').get().enabled = false project(':jetty').tasks.named('stage').get().enabled = false if (project.tasks.findByName('stage') != null) { project.tasks.named('stage').get().enabled = false } } } }