// 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.4.1' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:3.1.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 '3.1.0' id 'checkstyle' id 'com.github.johnrengelman.shadow' version '8.1.1' // NodeJs plugin id "com.github.node-gradle.node" version "3.0.1" id 'idea' id 'com.diffplug.spotless' version '6.20.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' 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 App Engine services task deploy { group = 'deployment' description = 'Deploys all services to App Engine.' } 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.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 ext.createUberJar = { taskName, binaryName, mainClass, List configs = [project.configurations.runtimeClasspath], List srcOutput = [project.sourceSets.main.output], List excludes = [] -> project.tasks.create( taskName, com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { 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 dependencyLicenseReport configuration 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.each { if (it.name != 'dependencyLicenseReport' && it.name != 'integration') { it.resolutionStrategy.activateDependencyLocking() } } } } // Set up all of the deployment projects. if (services.contains(project.path)) { apply from: "${rootDir.path}/appengine_war.gradle" // 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 sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 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') { javadocSource << project.sourceSets.main.allJava javadocClasspath << project.sourceSets.main.runtimeClasspath javadocClasspath << "${buildDir}/generated/sources/annotationProcessor/java/main" javadocDependentTasks << project.tasks.compileJava } } // Force SDK download and deployment to be sequential, otherwise parallel tasks // will fail. For SDK download, they will try to write to the same location to // upgrade gcloud. For deployment, they will try to deploy different services to // the same project at the same time. for (int i = 1; i < services.size(); i++) { project("${services[i]}").downloadCloudSdk .dependsOn(project("${services[i - 1]}").downloadCloudSdk) project("${services[i]}").appengineDeployAll .dependsOn(project("${services[i - 1]}").appengineDeployAll) } // 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. options.addBooleanOption('Xdoclint:all,-missing,-html', 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' } 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 } } }