Слияние кода завершено, страница обновится автоматически
/*
* Copyright 2024 Thoughtworks, Inc.
*
* 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 com.github.jk1.license.filter.LicenseBundleNormalizer
import com.github.jk1.license.render.JsonReportRenderer
import com.github.jk1.license.task.ReportTask
import com.thoughtworks.go.build.AdoptiumVersion
import com.thoughtworks.go.build.InstallerType
import de.undercouch.gradle.tasks.download.Download
import nl.javadude.gradle.plugins.license.License
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.plugins.ide.idea.model.IdeaLanguageLevel
import java.security.SecureRandom
import java.util.stream.Collectors
plugins {
id 'base'
id 'org.owasp.dependencycheck' version '9.2.0'
id 'com.github.hierynomus.license-base' version '0.16.1'
id 'com.github.jk1.dependency-license-report' version '2.8'
}
apply from: 'dependencies.gradle'
if (file('build-ext.gradle').exists()) {
apply from: 'build-ext.gradle'
}
def script = this
def determineGitRevision = { ->
def process = "git rev-list HEAD --max-count=1".execute(null, project.file(script.buildscript.sourceFile.canonicalFile.parent))
process.waitFor()
return process.text.stripIndent().trim()
}
def determineReleaseRevision = { ->
def process = "git rev-list HEAD --count".execute(null, project.file(script.buildscript.sourceFile.canonicalFile.parent))
process.waitFor()
return process.text.stripIndent().trim()
}
def getVersion = { ... args ->
ByteArrayOutputStream stdout = new ByteArrayOutputStream()
try {
project.exec {
commandLine = args
standardOutput = stdout
errorOutput = stdout
ignoreExitValue = true
}
} catch (Exception ignored) {
return "could not determine version!"
}
return stdout.toString().trim()
}
List<String> partitionFiles(Collection originalFiles) {
def files = originalFiles.unique().sort()
def seed = System.getenv('GO_PIPELINE_COUNTER')?.toInteger() ?: System.getenv('PARTITION_SEED')?.toInteger() ?: new SecureRandom().nextInt().abs()
def totalWorkers = System.getenv('GO_JOB_RUN_COUNT')?.toInteger() ?: 1
def currentWorkerIndex = System.getenv('GO_JOB_RUN_INDEX')?.toInteger() ?: 1
Integer testsPerBucket = (Integer) Math.ceil((double) files.size() / totalWorkers)
rootProject.logger.quiet("Partitioning ${files.size()} files into ${totalWorkers} buckets with approx ${testsPerBucket} files per bucket. Using seed ${seed}.")
rootProject.logger.quiet("To reproduce the test failure, run with:")
rootProject.logger.quiet("PARTITION_SEED=${seed} GO_JOB_RUN_COUNT=${totalWorkers} GO_JOB_RUN_INDEX=${currentWorkerIndex} ./gradlew YOUR_TARGET")
// random shuffle, every agent uses the same seed, so shuffling is predictable
Collections.shuffle(files, new Random(seed))
def allPartitions = files.collate(testsPerBucket)
def currentPartition = allPartitions[currentWorkerIndex - 1]
return (currentPartition ?: []) as List<String>
}
def GO_VERSION_PREVIOUS = '24.2.0'
def GO_VERSION_SEGMENTS = [year: 24, releaseInYear: 3, patch: 0]
def GO_VERSION_NEXT = '24.4.0'
def GO_VERSION = [GO_VERSION_SEGMENTS.year, GO_VERSION_SEGMENTS.releaseInYear, GO_VERSION_SEGMENTS.patch].join('.')
def DIST_VERSION = determineReleaseRevision()
def GIT_REVISION = determineGitRevision()
if (System.getenv().containsKey("GO_SERVER_URL")) {
def separator = "=".multiply(72)
println separator
println "Gradle version: ${gradle.gradleVersion}"
println "JVM: ${System.getProperty('java.version')} (${System.getProperty('java.vm.vendor')} ${System.getProperty('java.vm.version')})"
println "OS: ${System.getProperty('os.name')} ${System.getProperty('os.version')} ${System.getProperty('os.arch')}"
println separator
println("")
println("Tool Versions")
println(separator)
println("node: ${getVersion("node", "--version")}")
println(" git: ${getVersion("git", "--version")}")
println(" hg: ${getVersion("hg", "--quiet", "--version")}")
println(" svn: ${getVersion("svn", "--quiet", "--version")}")
println(" p4: ${getVersion("p4", "-V").readLines().grep(~/Rev.*/).join("")}")
println(" p4d: ${getVersion("p4d", "-V").readLines().grep(~/Rev.*/).join("")}")
println(separator)
println("")
}
ext {
previousVersion = GO_VERSION_PREVIOUS
nextVersion = GO_VERSION_NEXT
goVersionSegments = GO_VERSION_SEGMENTS
goVersion = GO_VERSION
distVersion = DIST_VERSION
fullVersion = DIST_VERSION ? "${GO_VERSION}-${DIST_VERSION}" : GO_VERSION
gitRevision = GIT_REVISION
copyrightYear = Calendar.getInstance().get(Calendar.YEAR)
// Default target version. Can be overridden by sub-projects.
targetJavaVersion = 17
// Version of JVM to compile and test using.
buildJavaVersion = 21
// Version to package with installers and container images by default
packagedJavaVersion = new AdoptiumVersion(
feature: 21,
interim: 0, // set to `null` for the first release of a new featureVersion
update : 3, // set to `null` for the first release of a new featureVersion
patch : null, // set to `null` for most releases. Patch releases are "exceptional".
build : 9
)
}
allprojects {
apply plugin: 'idea'
group = 'com.thoughtworks.go'
version = project.fullVersion
buildDir = "${projectDir}/target"
repositories {
mavenCentral()
maven { url 'https://repo.terracotta.org/maven2/' } // Needed for later ehcache repos
}
if (project.name == 'build-platform') {
return
}
configurations {
// Ensure that we use the jar artifacts from even `java-library` modules; by default they
// resolve to the classes directory instead
compileClasspath {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR))
}
}
}
}
idea {
project {
jdkName = project.buildJavaVersion
targetBytecodeVersion = JavaVersion.toVersion(project.targetJavaVersion)
languageLevel = new IdeaLanguageLevel(project.targetJavaVersion)
}
}
subprojects {
if (!project.childProjects.isEmpty()) {
return
}
apply plugin: 'base'
apply plugin: "com.github.hierynomus.license"
license {
skipExistingHeaders = false
strictCheck = true
ext.year = project.copyrightYear
exclude '**/*.erb'
exclude '**/*.ejs'
exclude '**/*.keep'
exclude '**/*.md'
exclude '**/*.jar'
exclude '**/*.json'
exclude '**/*.html'
exclude '**/*.zip'
exclude '**/*.png'
exclude '**/*.jpg'
exclude '**/*.jpeg'
exclude '**/*.gif'
exclude '**/*.woff'
exclude '**/*.woff2'
exclude '**/*.svg'
exclude '**/*.json'
exclude '**/*.scss.d.ts'
exclude '**/.*.json'
exclude '**/hg.template'
exclude '**/available.toggles'
exclude '**/META-INF/services/*'
exclude '**/node_modules/**'
exclude '**/node-vendor/**'
exclude '**/vendor/**'
exclude '**/javascripts/lib/**'
exclude '**/gems/jruby/**'
exclude '**/tmp/**'
exclude '**/logs/**'
exclude '**/log/**'
exclude '**/robots.txt'
exclude '**/rails/bin/**'
// Code borrowed from other projects
exclude '**/JavaVersion.java'
exclude '**/CommandLine.java'
exclude '**/StreamPumper.java'
exclude '**/StreamConsumer.java'
exclude '**/RuntimeTypeAdapterFactory*.java'
// used by intellij for type hints when editing ftl files, intellij gets confused if it sees a license header in this file
exclude '**/freemarker_implicit.ftl'
header rootProject.file('build-platform/Apache-2.0.txt')
headerDefinitions {
intellijStyleXML {
firstLine = "<!--"
beforeEachLine = ' ~ '
endLine = " -->"
afterEachLine = ''
skipLinePattern = "^<\\?xml.*>\$"
firstLineDetectionPattern = "(\\s|\\t)*<!--.*\$"
lastLineDetectionPattern = ".*-->(\\s|\\t)*\$"
allowBlankLines = true
isMultiline = true
padLines = false
}
intellijStyleFTL {
firstLine = "<#--"
beforeEachLine = ' '
endLine = " -->"
afterEachLine = ''
firstLineDetectionPattern = "(\\s|\\t)*<#--.*\$"
lastLineDetectionPattern = ".*-->(\\s|\\t)*\$"
allowBlankLines = true
isMultiline = true
padLines = false
}
}
mapping {
java = 'SLASHSTAR_STYLE'
groovy = 'SLASHSTAR_STYLE'
scss = 'SLASHSTAR_STYLE'
sass = 'SLASHSTAR_STYLE'
css = 'SLASHSTAR_STYLE'
xml = 'intellijStyleXML'
xsl = 'intellijStyleXML'
xsd = 'intellijStyleXML'
ftl = 'intellijStyleFTL'
ftlh = 'intellijStyleFTL'
ts = 'SLASHSTAR_STYLE'
tsx = 'SLASHSTAR_STYLE'
msx = 'SLASHSTAR_STYLE'
js = 'SLASHSTAR_STYLE'
}
}
final List<String> includeInLicensing = [':server-launcher']
final boolean hasJavaSources = !project.fileTree("src/main/java").files.isEmpty()
if (includeInLicensing.contains(project.path) || hasJavaSources) {
apply plugin: 'com.github.jk1.dependency-license-report'
licenseReport {
renderers = [new JsonReportRenderer("index.json", false)]
excludeGroups = ["com.thoughtworks.go"]
filters = [new LicenseBundleNormalizer()]
outputDir = file("${project.buildDir}/reports/dependency-license")
configurations = ['runtimeClasspath', 'packagingInLibDir']
}
tasks.withType(ReportTask) { ReportTask reportTask ->
reportTask.outputs.cacheIf { true }
project.configurations.each { config ->
if (config.name in project.licenseReport.configurations) {
reportTask.inputs.files(config)
}
}
}
}
if (hasJavaSources) {
apply plugin: 'java-library'
buildDir = "${projectDir}/target"
configurations {
packagingOnly
extractedAtTopLevel { transitive = false }
fatJarConfig
testOutput {
extendsFrom testRuntimeClasspath
transitive = true
}
}
task testJar(type: Jar, description: "Assembles a jar archive containing the test classes.") {
archiveClassifier = 'tests'
from sourceSets.test.output
}
dependencies {
// Force all sub-projects to have managed dependency versions for certain transitive dependencies
implementation platform(project(':build-platform'))
modules {
module("dom4j:dom4j") {
replacedBy("org.dom4j:dom4j", "Upgrade the legacy dom4j in old Hibernate 3.6; dom4j 2 should be sufficiently compatible")
}
module("net.sf.ehcache:ehcache") {
replacedBy("net.sf.ehcache.internal:ehcache-core", "Use the much slimmer core artifact which is fine for our use case")
}
module("commons-logging:commons-logging") {
replacedBy(project.deps.slf4jJcl, "Replace commons-logging implementations with the slf4j apis that redirect logs to slf4j per https://www.slf4j.org/legacy.html")
}
}
components {
// workaround for dom4j Gradle metadata optional dependency issues to avoid it pulling in
// optional dependencies when it should not (https://github.com/dom4j/dom4j/issues/99)
withModule('org.dom4j:dom4j', { details -> details.allVariants { withDependencies { clear() } } })
// workaround for Guava metadata declaring dependencies that are not needed at runtime
// see https://github.com/google/guava/pull/6606
withModule('com.google.guava:guava', { details ->
details.allVariants {
withDependencies {
removeAll {
it.name in [ "jsr305", "checker-qual", "error_prone_annotations", "listenablefuture" ]
}
}
}
})
withModule('com.google.code.gson:gson', { details ->
details.allVariants {
withDependencies {
removeAll {
it.name in [ "error_prone_annotations" ]
}
}
}
})
}
}
}
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
// Hamcrest core/library/all have been merged together: https://hamcrest.org/JavaHamcrest/distributables
if (details.requested.group == 'org.hamcrest' && details.requested.name ==~ /hamcrest-(core|library|all)/) {
details.useTarget project.deps.hamcrest
}
// Javax Annotation has migrated to jakarta annotations
if (details.requested.group == 'javax.annotation' && details.requested.name == 'javax.annotation-api') {
details.useTarget project.deps.jakartaAnnotation
}
// Javax Servlet API has migrated to jakarta Servlet API
if (details.requested.group in ['javax.servlet', 'org.eclipse.jetty.toolchain'] && details.requested.name in ['javax.servlet-api', 'jetty-servlet-api']) {
details.useTarget project.deps.servletApi
}
// Javax Transaction API has migrated to jakarta Transaction API
if (details.requested.group == 'javax.transaction' && details.requested.name == 'jta') {
details.useTarget project.deps.jakartaTransaction
}
}
}
task cleanLogs(type: Delete) {
delete 'logs'
}
task cleanApiOut(type: Delete) {
delete 'out'
}
clean.dependsOn cleanLogs, cleanApiOut
check.dependsOn 'license'
tasks.withType(Jar) { jarTask ->
manifest {
attributes(
'Go-Version': project.version,
'Go-Revision': project.gitRevision,
'Implementation-Title': project.name,
'Implementation-Version': project.version
)
}
includeEmptyDirs false
duplicatesStrategy DuplicatesStrategy.EXCLUDE
if (jarTask.name == 'testJar') {
project.artifacts.add 'testOutput', jarTask
} else {
project.artifacts.add 'archives', jarTask
}
}
tasks.withType(AbstractArchiveTask) {
includeEmptyDirs false
duplicatesStrategy DuplicatesStrategy.EXCLUDE
preserveFileTimestamps = false
reproducibleFileOrder = true
}
tasks.withType(Exec) {
if (logger.isInfoEnabled()) {
standardOutput = System.out
errorOutput = System.err
} else {
standardOutput = new ByteArrayOutputStream()
errorOutput = new ByteArrayOutputStream()
}
}
tasks.withType(GroovyCompile).configureEach {
options.incremental = true
}
tasks.withType(JavaCompile) {
options.warnings = false
options.encoding = "UTF-8"
options.compilerArgs += '-Xlint:-deprecation'
options.release.set(project.targetJavaVersion as Integer)
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(project.buildJavaVersion)
}
}
project.afterEvaluate {
tasks.withType(License) { License thisTask ->
def sourceSetName = thisTask.name.replaceFirst('licenseFormat', '').replaceFirst('license', '')
def mappedSourceSet = sourceSets.find { sourceSet -> sourceSet.name.toLowerCase().equals(sourceSetName.toLowerCase()) }
if (project.path == ':server' && sourceSetName.toLowerCase() == 'test') {
thisTask.source += project.file("rails/")
return
}
if (thisTask.name.toLowerCase().contains('test')) {
thisTask.source -= mappedSourceSet.resources
}
if (project.path == ':test:test-utils') {
thisTask.source -= mappedSourceSet.resources
}
}
}
tasks.withType(Test) { Test testTask ->
dependsOn 'jar'
testTask.useJUnitPlatform()
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(project.buildJavaVersion)
}
def allTestClasses
maxParallelForks = project.hasProperty('maxParallelForks') ? project.maxParallelForks as int : 1
// fixup a tempdir that is predictable and we can clean it up later
def tmpDir = project.file("${System.getProperty('java.io.tmpdir')}/gocd-tests/${new BigInteger(32, new SecureRandom()).toString(32)}")
systemProperty 'java.io.tmpdir', tmpDir
systemProperty 'jdk.attach.allowAttachSelf', 'true'
jvmArgs += testTask.project.name.startsWith("agent") ? InstallerType.agent.jvmModuleOpensArgs : InstallerType.server.jvmModuleOpensArgs
jvmArgs += '-XX:+EnableDynamicAgentLoading' // Needed for byte-buddy-agent (and maybe others?) to avoid warnings/errors on Java 21+
doFirst {
// workaround for https://github.com/unbroken-dome/gradle-testsets-plugin/issues/40
classpath += rootProject.findProject(':test:test-utils').files("resource-include-in-all-projects")
}
defaultCharacterEncoding = "UTF-8"
filter {
failOnNoMatchingTests = true
}
doFirst {
project.logger.info("Setting tmpdir ${tmpDir}")
project.delete(tmpDir)
tmpDir.mkdirs()
List<String> files = []
testTask.testClassesDirs.files.each { File classDir ->
if (classDir.exists()) {
classDir.eachFileRecurse(groovy.io.FileType.FILES) { f ->
def relPath = new File(classDir.toURI().relativize(f.toURI()).toString()).toString().replaceAll(/\$.*/, '.*').replaceAll(/\.class/, '.*')
files << relPath
}
}
}
files = files.unique()
List testsToRun = partitionFiles(files)
allTestClasses = getTestClassesForTask(testsToRun)
List testsToIgnore = files - testsToRun
testsToIgnore.each { f ->
testTask.exclude f
}
testsToRun.each { f ->
testTask.include f
}
}
beforeTest { TestDescriptor test ->
allTestClasses.remove(test.className)
}
beforeSuite { TestDescriptor test ->
allTestClasses.remove(test.className)
}
doLast {
project.logger.info("Deleting tmpdir ${tmpDir}")
project.delete(tmpDir)
def allowTestsToBeSkipped = false
if (project.hasProperty("allowTestsToBeSkipped")) {
allowTestsToBeSkipped = Boolean.parseBoolean(project.property("allowTestsToBeSkipped"))
}
if (!allTestClasses.empty && !allowTestsToBeSkipped) {
throw new GradleException("Missed the following classes to test: ${allTestClasses}".toString())
}
}
testLogging {
showStandardStreams = project.path != ':server'
exceptionFormat 'full'
beforeSuite { suite ->
if (suite.parent) {
logger.quiet("Running ${suite.name}")
}
}
afterSuite { suite, result ->
if (suite.parent) {
logger.quiet("Tests run: ${result.testCount}, Failures: ${result.failedTestCount}, Skipped: ${result.skippedTestCount}, Time elapsed: ${(result.endTime - result.startTime) / 1000.00} sec")
if (result.resultType == TestResult.ResultType.FAILURE) {
logger.quiet("Test ${suite.name} FAILED")
}
} else {
logger.quiet("Total tests run: ${result.testCount}, Failures: ${result.failedTestCount}, Skipped: ${result.skippedTestCount}, Time elapsed: ${(result.endTime - result.startTime) / 1000.00} sec")
}
}
}
}
tasks.withType(Delete) { Delete deleteTask ->
project.afterEvaluate {
idea {
module {
excludeDirs += deleteTask.targetFiles
}
}
}
}
}
task allDependencies {
dependsOn allprojects.collect { "$it.path:dependencies" }
description = "Print dependency tree of all projects"
}
private allCompileTasks(Closure<Boolean> include = { t -> true }) {
allprojects.collect { Project prj ->
prj.afterEvaluate {
compileAll.dependsOn(prj.getTasks().withType(JavaCompile).findAll(include))
compileAll.dependsOn(prj.getTasks().withType(GroovyCompile).findAll(include))
}
}
}
task compileAll { compileAllTask ->
group LifecycleBasePlugin.BUILD_GROUP
allCompileTasks()
}
task prepare {
group LifecycleBasePlugin.BUILD_GROUP
allCompileTasks { Task task -> !task.name.toLowerCase().contains('test') && !task.project.path.toLowerCase().contains('test') }
}
task sparkTest { thisTask ->
group LifecycleBasePlugin.VERIFICATION_GROUP
description = "Run all api tests"
(project(':api').subprojects + project(':spark').subprojects).each { Project subPrj ->
thisTask.dependsOn subPrj.tasks.withType(Test)
}
finalizedBy 'sparkTestJunitHtmlReport'
}
task sparkTestJunitHtmlReport(type: TestReport) {
dependsOn sparkTest
testResults.from(project(':api').subprojects*.test.binaryResultsDirectory.locationOnly)
testResults.from(project(':spark').subprojects*.test.binaryResultsDirectory.locationOnly)
destinationDirectory = file("${project.buildDir}/reports/tests/sparkTest")
}
private void forAllTask(Closure<Task> closure) {
allprojects.each { Project subPrj ->
// afterEvaluate, because the testsset plugin defines tasks much later
subPrj.afterEvaluate {
subPrj.tasks.withType(Test).each { eachTestTask ->
if (!(eachTestTask.path in [':server:integrationTest'])) {
closure.call(eachTestTask)
}
}
}
}
}
task allTests { thisTask ->
group LifecycleBasePlugin.VERIFICATION_GROUP
description = "Run all tests"
forAllTask { Test eachTestTask ->
thisTask.dependsOn eachTestTask
}
finalizedBy 'allTestsJunitHtmlReport'
}
task allTestsJunitHtmlReport(type: TestReport) { TestReport thisTask ->
group LifecycleBasePlugin.VERIFICATION_GROUP
description = "Run all api tests"
forAllTask { Test eachTestTask ->
testResults.from(eachTestTask.binaryResultsDirectory.locationOnly)
eachTestTask.finalizedBy(thisTask)
}
destinationDirectory = file("${project.buildDir}/reports/tests/allTests")
}
static def toCamelCase(String input) {
return input.split("[^a-zA-Z0-9]").collect { it.capitalize() }.join("")
}
static List<String> getTestClassesForTask(List<String> testsToRun) {
return testsToRun.stream()
.filter { testToRun -> !testToRun.matches(".*[/|\\\\].*Abstract.*Test.*") }
.filter { testToRun -> testToRun.endsWith("Test.*") }
.map { testToRun -> testToRun.replaceAll(".\\*", "").replaceAll("[^\\w.*]", ".") }
.collect(Collectors.toList())
}
dependencyCheck {
failBuildOnCVSS = 1
suppressionFile = rootProject.file('build-platform/dependency-check-suppress.xml').toPath().toString()
nvd {
apiKey = System.getenv("NVD_API_KEY") ?: ""
}
analyzers {
nodeAudit.yarnEnabled = false // Doesn't work correctly with Yarn 3+ https://github.com/jeremylong/DependencyCheck/issues/4894
assemblyEnabled = false // Avoid dotnet false positives
retirejs.filterNonVulnerable = true // Reduces effort/memory processing retirejs results
}
}
dependencyCheckAggregate {
doFirst {
project.exec {
commandLine = [ "gem", "install", "bundler-audit", "--no-document" ]
standardOutput = System.out
errorOutput = System.err
}
project.exec {
commandLine = [ "bundle", "audit", "update" ]
standardOutput = System.out
errorOutput = System.err
}
}
}
if (System.getenv().containsKey("GO_SERVER_URL")) {
dependencyCheck {
data {
directory = "${layout.buildDirectory.get()}/dependency-check-data"
}
}
tasks.register('downloadDependencyCheckCache', Download) {
src 'https://nexus.gocd.io/repository/cache/dependency-check-data.zip'
dest layout.buildDirectory.file('dependency-check-data.zip')
onlyIfModified true
}
tasks.register('unzipDependencyCheckCache', Copy) {
dependsOn downloadDependencyCheckCache
from zipTree(downloadDependencyCheckCache.dest)
into layout.buildDirectory.dir("dependency-check-data")
def copyDetails = []
eachFile { copyDetails << it }
doLast {
copyDetails.each { FileCopyDetails details ->
def target = new File(destinationDir, details.path)
if (target.exists()) { target.setLastModified(details.lastModified) }
}
}
}
dependencyCheckUpdate.dependsOn unzipDependencyCheckCache
dependencyCheckAnalyze.dependsOn unzipDependencyCheckCache
dependencyCheckAggregate.dependsOn unzipDependencyCheckCache
tasks.register('uploadDependencyCheckCache', Zip) {
mustRunAfter dependencyCheckUpdate, dependencyCheckAnalyze, dependencyCheckAggregate
from layout.buildDirectory.file("dependency-check-data")
archiveFileName = "dependency-check-data-updated.zip"
destinationDirectory = layout.buildDirectory
doLast {
project.exec {
workingDir = projectDir
commandLine = [
"curl",
"-u", "${System.getenv("NEXUS_CACHE_USERNAME")}:${System.getenv("NEXUS_CACHE_PASSWORD")}",
"--include",
"--upload-file", uploadDependencyCheckCache.archiveFile.get(), 'https://nexus.gocd.io/repository/cache/dependency-check-data.zip'
]
standardOutput = System.out
errorOutput = System.err
ignoreExitValue = true
}
}
}
}
task newSPA {
description = "Helper to create a new SPA. Pass `-PspaName=maintenance_mode"
doFirst {
String spaName = project.spaName
String entityName = toCamelCase(spaName)
String modelsBase = "${project(':server').webAppDir}/WEB-INF/rails/webpack/models/${spaName}"
String pagesBase = "${project(':server').webAppDir}/WEB-INF/rails/webpack/views/pages"
String widgetBase = "${pagesBase}/${spaName}"
String mountPoint = "${project(':server').webAppDir}/WEB-INF/rails/webpack/single_page_apps"
project.delete(modelsBase, widgetBase)
project.mkdir(modelsBase)
project.mkdir(widgetBase)
String licenseHeader =
"""
* Copyright ${project.copyrightYear} Thoughtworks, Inc.
*
* 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.
""".stripIndent().trim()
String javaOrJavaScriptLicencseHeader = "/*\n${licenseHeader.split('\n').collect { eachLine -> " ${eachLine}" }.join("\n")}\n */\n\n"
file("${mountPoint}/${spaName}.tsx").withWriter { out ->
def contents =
"""
import {SinglePageAppBase} from "helpers/spa_base";
import {${entityName}Page} from "views/pages/${spaName}";
export class ${entityName}SPA extends SinglePageAppBase {
constructor() {
super(${entityName}Page);
}
}
//tslint:disable-next-line
new ${entityName}SPA();
"""
out.println(javaOrJavaScriptLicencseHeader + contents.stripIndent().trim() + "\n")
}
file("${pagesBase}/${spaName}.tsx").withWriter { out ->
def contents =
"""
import m from "mithril";
import {${entityName}} from "models/${spaName}/${spaName}";
import {${entityName}Widget} from "views/pages/${spaName}/${spaName}_widget";
import {Page} from "views/pages/page";
interface State {
dummy?: ${entityName};
}
export class ${entityName}Page extends Page<null, State> {
componentToDisplay(vnode: m.Vnode<null, State>): m.Children {
return <${entityName}Widget/>;
}
pageName(): string {
return "SPA Name goes here!";
}
fetchData(vnode: m.Vnode<null, State>): Promise<any> {
// to be implemented
return Promise.resolve();
}
}
"""
out.println(javaOrJavaScriptLicencseHeader + contents.stripIndent().trim() + "\n")
}
file("${widgetBase}/${spaName}_widget.tsx").withWriter { out ->
def contents =
"""
import {MithrilViewComponent} from "jsx/mithril-component";
import m from "mithril";
import {${entityName}} from "models/${spaName}/${spaName}";
interface Attrs {
dummy?: ${entityName};
}
export class ${entityName}Widget extends MithrilViewComponent<Attrs> {
view(vnode: m.Vnode<Attrs>) {
return <div> This is widget</div>;
}
}
"""
out.println(javaOrJavaScriptLicencseHeader + contents.stripIndent().trim() + "\n")
}
file("${modelsBase}/${spaName}.ts").withWriter { out ->
def contents =
"""
interface EmbeddedJSON {
dummy?: boolean;
// to be implemented
}
interface ${entityName}JSON {
_embedded: EmbeddedJSON;
}
export class ${entityName} {
// to be implemented
static fromJSON(data: ${entityName}JSON) {
// to be implemented
}
}
"""
out.println(javaOrJavaScriptLicencseHeader + contents.stripIndent().trim() + "\n")
}
file("spark/spark-spa/src/main/java/com/thoughtworks/go/spark/spa/${entityName}Controller.java").withWriter { out ->
def contents =
"""
package com.thoughtworks.go.spark.spa;
import com.thoughtworks.go.spark.Routes;
import com.thoughtworks.go.spark.SparkController;
import com.thoughtworks.go.spark.spring.SPAAuthenticationHelper;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import spark.TemplateEngine;
import java.util.Map;
import static spark.Spark.*;
public class ${entityName}Controller implements SparkController {
private final SPAAuthenticationHelper authenticationHelper;
private final TemplateEngine engine;
public ${entityName}Controller(SPAAuthenticationHelper authenticationHelper, TemplateEngine engine) {
this.authenticationHelper = authenticationHelper;
this.engine = engine;
}
@Override
public String controllerBasePath() {
return Routes.${entityName}.SPA_BASE;
}
@Override
public void setupRoutes() {
path(controllerBasePath(), () -> {
before("", authenticationHelper::checkAdminUserAnd403);
get("", this::index, engine);
});
}
public ModelAndView index(Request request, Response response) {
Map<String, Object> object = Map.of(
"viewTitle", "${entityName}"
);
return new ModelAndView(object, null);
}
}
"""
out.println(javaOrJavaScriptLicencseHeader + contents.stripIndent().trim() + "\n")
}
addUrlRewriteRule("${spaName} UI")
List<String> lines = file('spark/spark-spa/src/main/java/com/thoughtworks/go/spark/spa/spring/SpaControllers.java').readLines()
def insertionIdx = lines.findIndexOf { l -> (l =~ /^\s*sparkControllers.add/).find() }
lines.add(insertionIdx, "\t\tsparkControllers.add(new ${entityName}Controller(authenticationHelper, templateEngineFactory.create(${entityName}Controller.class, () -> COMPONENT_LAYOUT_PATH)));")
file('spark/spark-spa/src/main/java/com/thoughtworks/go/spark/spa/spring/SpaControllers.java').withWriter { out ->
out.println(lines.join("\n").trim() + "\n")
}
addRoute(entityName, spaName)
}
}
task removeApi {
description = "Helper to create a delete an api module. Pass `-PapiName=roles-config -PapiVersion=v1`"
doFirst {
String apiName = project.apiName
String newProjectName = "api-${apiName}-${project.apiVersion}"
String moduleDir = file("api/${newProjectName}")
String packageName = "com.thoughtworks.go.api${project.apiVersion}.${apiName.replaceAll(~/[^a-zA-Z]/, '')}"
project.delete(moduleDir)
List<String> springConfigContents = file("server/src/main/resources/applicationContext-global.xml").readLines("utf-8")
file("server/src/main/resources/applicationContext-global.xml").withWriter { out ->
def newContents = springConfigContents
.stream()
.filter({ eachLine -> !eachLine.contains("base-package=\"${packageName}\"") })
.collect(Collectors.joining("\n"))
.trim() + "\n"
out.println(newContents)
}
List<String> settingsFileContents = file('settings.gradle').readLines("utf-8")
file('settings.gradle').withWriter { out ->
def newContents = settingsFileContents
.stream()
.filter({ eachLine -> !eachLine.contains("':api:${newProjectName}'") })
.collect(Collectors.joining("\n"))
.trim() + "\n"
out.println(newContents)
}
List<String> gitIgnoreFileContents = file('.gitignore').readLines("utf-8")
file('.gitignore').withWriter { out ->
def newContents = gitIgnoreFileContents
.stream()
.filter({ eachLine -> !eachLine.startsWith("api/${newProjectName}/") })
.collect(Collectors.joining("\n"))
.trim() + "\n"
out.println(newContents)
}
project.exec {
commandLine = ['git', 'add', '-u']
}
project.exec {
commandLine = ['git', 'commit', '-m', "Removed ${apiName} version ${project.apiVersion}"]
}
}
}
task newApi {
description = "Helper to create a new api module. Pass `-PapiName=roles-config -PapiVersion=v1`"
doFirst {
String apiName = project.apiName
String newProjectName = "api-${apiName}-${project.apiVersion}"
String oldProjectName = "api-${apiName}-v${project.apiVersion.replaceFirst('v', '').toInteger() - 1}"
File oldModuleDir = file("api/${oldProjectName}")
project.delete(oldModuleDir)
if (oldModuleDir.exists()) {
throw new GradleException("Please use `upgradeApi` task instead of `newApi`")
}
String moduleDir = file("api/${newProjectName}")
String basePackage = "${moduleDir}/src/main/java/com/thoughtworks/go/api${project.apiVersion}/${apiName.replaceAll(~/[^a-zA-Z]/, '')}"
String testPackage = "${moduleDir}/src/test/groovy/com/thoughtworks/go/api${project.apiVersion}/${apiName.replaceAll(~/[^a-zA-Z]/, '')}"
String packageName = "com.thoughtworks.go.api${project.apiVersion}.${apiName.replaceAll(~/[^a-zA-Z]/, '')}"
String entityClassName = toCamelCase(apiName)
String entityVariableName = entityClassName.uncapitalize()
String controllerClassName = "${entityClassName}Controller${project.apiVersion.capitalize()}"
project.delete(moduleDir)
project.mkdir(moduleDir)
project.mkdir(basePackage)
project.mkdir(testPackage)
file("${moduleDir}/build.gradle").withWriter { out ->
def contents =
"""
/*
* Copyright ${project.copyrightYear} Thoughtworks, Inc.
*
* 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.
*/
plugins {
id 'groovy'
}
dependencies {
implementation project(path: ':api:api-base', configuration: 'runtimeElements')
testImplementation project(path: ':api:api-base', configuration: 'testOutput')
testImplementation project.deps.junit5Api
testRuntimeOnly project.deps.junit5Engine
}
"""
out.println(contents.stripIndent().trim() + "\n")
}
file("${basePackage}/${controllerClassName}.java").withWriter { out ->
def contents =
"""
/*
* Copyright ${project.copyrightYear} Thoughtworks, Inc.
*
* 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.
*/
package ${packageName};
import com.thoughtworks.go.api.ApiController;
import com.thoughtworks.go.api.ApiVersion;
import com.thoughtworks.go.api.CrudController;
import com.thoughtworks.go.api.base.OutputWriter;
import com.thoughtworks.go.api.spring.ApiAuthenticationHelper;
import com.thoughtworks.go.config.exceptions.EntityType;
import com.thoughtworks.go.config.exceptions.HttpException;
import com.thoughtworks.go.server.service.EntityHashingService;
import com.thoughtworks.go.spark.spring.SparkSpringController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import spark.Request;
import spark.Response;
import java.io.IOException;
import java.util.function.Consumer;
import static spark.Spark.*;
@Component
public class ${controllerClassName} extends ApiController implements SparkSpringController, CrudController<${entityClassName}> {
private final ApiAuthenticationHelper apiAuthenticationHelper;
private final EntityHashingService entityHashingService;
@Autowired
public ${controllerClassName}(ApiAuthenticationHelper apiAuthenticationHelper, EntityHashingService entityHashingService) {
super(ApiVersion.${project.apiVersion});
this.apiAuthenticationHelper = apiAuthenticationHelper;
this.entityHashingService = entityHashingService;
}
@Override
public String controllerBasePath() {
return Routes.${entityClassName}.BASE;
}
@Override
public void setupRoutes() {
path(controllerBasePath(), () -> {
// uncomment the line below to set the content type on the base path
// before("", mimeType, this::setContentType);
// uncomment the line below to set the content type on nested routes
// before("/*", mimeType, this::setContentType);
// uncomment for the `index` action
// get("", mimeType, this::index);
// change the line below to enable appropriate security
before("", mimeType, this.apiAuthenticationHelper::checkAdminUserAnd403);
// to be implemented
});
}
// public String index(Request request, Response response) throws IOException {
// ${entityClassName} ${entityVariableName} = fetchEntityFromConfig(request.params(":id"));
// return writerForTopLevelObject(request, response, outputWriter -> ${entityClassName}sRepresenter.toJSON(outputWriter, ${entityVariableName}));
// }
@Override
public String etagFor(${entityClassName} entityFromServer) {
return entityHashingService.hashForEntity(entityFromServer);
}
@Override
public EntityType getEntityType() {
return EntityType.${entityClassName};
}
@Override
public ${entityClassName} doFetchEntityFromConfig(String name) {
return someService.getEntity(name);
}
@Override
public ${entityClassName} buildEntityFromRequestBody(Request req) {
JsonReader jsonReader = GsonTransformer.getInstance().jsonReaderFrom(req.body());
return ${entityClassName}Representer.fromJSON(jsonReader);
}
@Override
public Consumer<OutputWriter> jsonWriter(${entityClassName} ${entityVariableName}) {
return outputWriter -> ${entityClassName}Representer.toJSON(outputWriter, ${entityVariableName});
}
}
"""
out.println(contents.stripIndent().trim() + "\n")
}
file("${testPackage}/${controllerClassName}Test.groovy").withWriter { out ->
def contents =
"""
/*
* Copyright ${project.copyrightYear} Thoughtworks, Inc.
*
* 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.
*/
package ${packageName}
import com.thoughtworks.go.api.spring.ApiAuthenticationHelper
import com.thoughtworks.go.spark.ControllerTrait
import com.thoughtworks.go.spark.SecurityServiceTrait
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.mockito.junit.jupiter.MockitoSettings
import org.mockito.quality.Strictness
@MockitoSettings(strictness = Strictness.LENIENT)
class ${controllerClassName}Test implements SecurityServiceTrait, ControllerTrait<${controllerClassName}> {
@Override
${controllerClassName} createControllerInstance() {
new ${controllerClassName}(new ApiAuthenticationHelper(securityService, goConfigService))
}
@Nested
class Index {
@BeforeEach
void setUp() {
loginAsUser()
}
@Test
void 'test a request'() {
}
@Nested
class Security implements SecurityTestTrait, AdminUserSecurity {
@Override
String getControllerMethodUnderTest() {
return "index"
}
@Override
void makeHttpCall() {
getWithApiHeader(controller.controllerBasePath())
}
}
}
}
"""
out.println(contents.stripIndent().trim() + "\n")
}
addRoute(entityClassName, apiName)
addPackageToSpringClasspathScanning(packageName)
addProjectToSettingsGradle(newProjectName, it)
addProjectToGitIgnore(newProjectName)
}
}
task upgradeApi {
description = "Helper to upgrade an new api module. Pass `-PapiName=roles-config`"
doFirst {
String apiName = project.apiName
List<Integer> allApiVersions = rootProject.project(':api').allprojects
.findAll { Project eachProject -> eachProject.name.startsWith("api-${apiName}-v") }
.collect { Project eachProject -> eachProject.name.replaceFirst("^api-${apiName}-v", "").toInteger() }
Integer currentMaxApiVersion = allApiVersions.max()
if (currentMaxApiVersion == null) {
throw new GradleException("Unable to find any projects with path prefix `:api:api-${apiName}-v`".toString())
}
Project oldProject = rootProject.project("api:api-${apiName}-v${currentMaxApiVersion}")
Integer apiVersion = currentMaxApiVersion + 1
String newProjectName = "api-${apiName}-v${apiVersion}"
File moduleDir = file("api/${newProjectName}")
String oldPackagePrefix = "com.thoughtworks.go.apiv${currentMaxApiVersion}"
String newPackagePrefix = "com.thoughtworks.go.apiv${apiVersion}"
String oldControllerClassSuffix = "ControllerV${currentMaxApiVersion}"
String newControllerClassSuffix = "ControllerV${apiVersion}"
String newPackage = newPackagePrefix;
project.delete(moduleDir)
project.copy {
from oldProject.projectDir
into moduleDir
exclude "out/**/*.*", "target/**/*.*"
filter { String eachLine ->
eachLine
.replace(oldPackagePrefix, newPackagePrefix)
.replace(oldControllerClassSuffix, newControllerClassSuffix)
.replaceAll("ApiVersion\\.v${currentMaxApiVersion}", "ApiVersion.v${apiVersion}")
}
filter(FixCrLfFilter, eol: FixCrLfFilter.CrLf.newInstance("unix"))
eachFile { FileCopyDetails fcd ->
List<String> segments = fcd.relativePath.segments
segments.replaceAll({ String eachSegment ->
if (eachSegment == "apiv${currentMaxApiVersion}") {
return "apiv${apiVersion}"
} else if (eachSegment.contains("${oldControllerClassSuffix}")) {
if (fcd.name.endsWith("${oldControllerClassSuffix}.java")) {
newPackage = new File(oldProject.projectDir, fcd.path).readLines().find { it.startsWith("package ${oldPackagePrefix}") }.replaceAll(/\.apiv[\d]+\./, ".apiv${apiVersion}.").replaceFirst(/^package ([\w\.\-]+);.*$/, '$1')
}
return eachSegment.replace(oldControllerClassSuffix, newControllerClassSuffix)
} else {
return eachSegment
}
})
fcd.relativePath = new RelativePath(!fcd.isDirectory(), (segments as String[]))
}
includeEmptyDirs = false
}
addPackageToSpringClasspathScanning(newPackage)
addProjectToSettingsGradle(newProjectName, it)
addProjectToGitIgnore(newProjectName)
}
}
private void addUrlRewriteRule(String spaName) {
String rule =
"""
<rule>
<name>${spaName}</name>
<from>^/admin/${spaName}(/?)\$</from>
<to last="true">/spark/admin/${spaName}</to>
</rule>
"""
List<String> lines = file("${project(':server').webAppDir}/WEB-INF/urlrewrite.xml").readLines()
int insertionIdx = lines.findIndexOf { l -> (l =~ /^\s*<urlrewrite/).find() }
lines.add(insertionIdx + 1, rule.stripIndent().trim())
file("${project(':server').webAppDir}/WEB-INF/urlrewrite.xml").withWriter { out ->
out.println(lines.join("\n").trim() + "\n")
}
}
private void addRoute(String entityName, String route) {
List<String> lines = file('spark/spark-base/src/main/java/com/thoughtworks/go/spark/Routes.java').readLines()
lines.add(lines.size() - 1, "\tpublic class ${entityName} {public static final String BASE = \"/admin/api/${route}\";}")
file('spark/spark-base/src/main/java/com/thoughtworks/go/spark/Routes.java').withWriter { out ->
out.println(lines.join("\n").trim() + "\n")
}
}
private void addPackageToSpringClasspathScanning(String packageName) {
List<String> lines = file("server/src/main/resources/applicationContext-global.xml").readLines()
int insertionIdx = lines.findIndexOf { l -> (l =~ /^\s*<context:component-scan /).find() }
lines.add(insertionIdx, " <context:component-scan base-package=\"${packageName}\"/>")
file("server/src/main/resources/applicationContext-global.xml").withWriter { out ->
out.println(lines.join("\n").trim() + "\n")
}
}
private Object addProjectToSettingsGradle(String newProjectName, it) {
file('settings.gradle').withWriter { out ->
def contents =
"""
/*
* Copyright ${project.copyrightYear} Thoughtworks, Inc.
*
* 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.
*/
"""
out.println(contents.stripIndent().trim())
out.println("")
out.println("rootProject.name = 'gocd'")
out.println("")
def existingProjects = rootProject.allprojects.findAll { prj -> prj.childProjects.isEmpty() && !prj.path.startsWith(":docker") }.collect {
it.path
}
def newProjects = existingProjects + [":api:${newProjectName}"]
newProjects.sort().unique().each { out.println "include '${it}'" }
out.println("")
out.println("apply from: 'settings-docker.gradle'")
}
}
private void addProjectToGitIgnore(String newProjectName) {
String[] ignoredFiles = ["api/${newProjectName}/out/", "api/${newProjectName}/target/", "api/${newProjectName}/logs/", "api/${newProjectName}/config/"]
rootProject.file('.gitignore').withWriterAppend { out ->
ignoredFiles.each { line ->
out.println(line)
}
}
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )