import jetbrains.buildServer.configs.kotlin.v2019_2.* import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.slackConnection import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.notifications /* The settings script is an entry point for defining a TeamCity project hierarchy. The script should contain a single call to the project() function with a Project instance or an init function as an argument. VcsRoots, BuildTypes, Templates, and subprojects can be registered inside the project using the vcsRoot(), buildType(), template(), and subProject() methods respectively. To debug settings scripts in command-line, run the mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate command and attach your debugger to the port 8000. To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View -> Tool Windows -> Maven Projects), find the generate task node (Plugins -> teamcity-configs -> teamcity-configs:generate), the 'Debug' option is available in the context menu for the task. */ version = "2020.2" project { vcsRoot(GitProjectRepo) cleanup { keepRule { id = "KEEP_RULE_1" keepAtLeast = days(3) dataToKeep = everything() applyPerEachBranch = true preserveArtifactsDependencies = true } } features { slackConnection { id = "PROJECT_EXT_2" displayName = "Slack" botToken = "zxxf1ef05c471abaea287f16830919d229f61360973870b6e853ace38bf62e5ec066b2bdef118e22d099ad99fe1343aad4a9f24ef83589597df775d03cbe80d301b" clientId = "141599046048.1771998951381" clientSecret = "zxx87b200290c3fc5ac692d3612fdb1ab75faab4e67de5dfbd2e779f4ac295144e0775d03cbe80d301b" } } sequence { parallel { //Android build(BuildAndroid) build(BuildAndroidDev) build(UploadArtifactToSlack("Build_Upload_Android", BuildAndroid)) //IOS build(BuildIOS) build(BuildIOSDev) build(UploadArtifactToSlack("Build_Upload_IOS", BuildIOS)) } } } object BuildAndroid : BuildAndroidType("", "prod/global_android") object BuildAndroidDev : BuildAndroidType("Dev", "dev/global_android") object BuildAndroidCh : BuildAndroidType("Ch", "prod/china_android") object BuildIOS : BuildIOSType("", "prod/global_ios") object BuildIOSDev : BuildIOSType("Dev", "dev/global_ios") object BuildIOSCh : BuildIOSType("Ch", "prod/china_ios") open class BuildAndroidType(name_suffix : String, environment : String) : BuildType({ name = "Build Android " + name_suffix artifactRules = "%build.env.artifactRules%" vcs { root(GitProjectRepo) branchFilter = "+:*" } params { param("build.env.buildName", "%system.teamcity.buildConfName%") param("build.env.workDir", "%system.teamcity.build.workingDir%") param("build.env.artifactRules", "artifacts/android/*.apk\nartifacts/android/*.aab") } features { notifications { notifierSettings = slackNotifier { connection = "PROJECT_EXT_2" sendTo = "#rnd-builds" messageFormat = verboseMessageFormat { addBranch = true addStatusText = true maximumNumberOfChanges = 10 } } buildFailed = true buildFinishedSuccessfully = true } } steps { script { name = "Deps Update" scriptContent = "./gamectl deps_update" } script { name = "Clean" scriptContent = "./gamectl clean" } script { name = "Produce" scriptContent = "./gamectl --env=" + environment + " build_android" } } requirements { matches("teamcity.agent.name", "(Default Agent|Default Agent 2)") } } ) {} open class BuildIOSType(name_suffix : String, environment : String) : BuildType({ name = "Build IOS " + name_suffix artifactRules = "%build.env.artifactRules%" vcs { root(GitProjectRepo) branchFilter = "+:*" } params { param("build.env.buildName", "%system.teamcity.buildConfName%") param("build.env.workDir", "%system.teamcity.build.workingDir%") param("build.env.artifactRules", "artifacts/ios/*.ipa") } features { notifications { notifierSettings = slackNotifier { connection = "PROJECT_EXT_2" sendTo = "#rnd-builds" messageFormat = verboseMessageFormat { addBranch = true addStatusText = true maximumNumberOfChanges = 10 } } buildFailed = true buildFinishedSuccessfully = true } } steps { script { name = "Deps Update" scriptContent = "./gamectl deps_update" } script { name = "Clean" scriptContent = "./gamectl clean" } script { name = "Produce" scriptContent = "./gamectl --env=" + environment + " build_ios" } } requirements { equals("teamcity.agent.name", "Default Agent 3") } }) {} open class UploadArtifactToSlack(build_id : String, build_deps : BuildType) : BuildType({ id(build_id) name = build_id.replace("_", " ") vcs { root(GitProjectRepo) } val project_name : String = "%system.teamcity.projectName%" val deps_build_name : String = build_deps.depParamRefs["build.env.buildName"].ref val deps_build_number : String = build_deps.depParamRefs.buildNumber.ref val deps_build_directory : String = build_deps.depParamRefs["build.env.workDir"].ref steps { script { name = "Upload" scriptContent = """ #!/bin/bash init_message="Artifacts For ${project_name} / ${deps_build_name} #${deps_build_number} %0A:tada:" auth="Bearer xoxb-141599046048-1775293613715-4ipxfXXSdFZaPHuoTL3ms8TT" main_thread=${'$'}(curl -d "text=${'$'}init_message" -d "channel=C01P5JB13SM" -H "Authorization:${'$'}auth" -X POST https://slack.com/api/chat.postMessage)\; thread_id=${'$'}(echo ${'$'}main_thread | sed 's/.*"ts":"\([^"]*\)".*/\1/')\; artifacts=${'$'}(find ${deps_build_directory} -maxdepth 1 \( -name "*.apk" -o -name "*.aab" \))\; for item in ${'$'}artifacts\; do curl -F file=@${'$'}item -F channels=C01P5JB13SM -F thread_ts=${'$'}thread_id -H "Authorization:${'$'}auth" https://slack.com/api/files.upload done """.trimIndent() } } dependencies { artifacts(build_deps){ artifactRules = build_deps.depParamRefs["build.env.artifactRules"].ref } } }) object PublishArifactToGoogle : BuildType({ name = "Publish Android" vcs { root(GitProjectRepo) } }) object PublishArifactToTestFlight : BuildType({ name = "Publish IOS" vcs { root(GitProjectRepo) } }) object GitProjectRepo : GitVcsRoot({ name = %(TEAM_CITY_SETTINGS_NAME_HERE)% url = %(TEAM_CITY_SETTINGS_URL_HERE)% agentCleanPolicy = AgentCleanPolicy.NEVER branch = "master" branchSpec = "+:*" authMethod = password { userName = "game_ci" password = "zxx47498c67380e2c74005e14d1ad544585" } }) ///////////////////////////////////////////////////////////////// typealias DependencySettings = SnapshotDependency.() -> Unit abstract class Stage { var dependencySettings: DependencySettings = {} val dependencies = mutableListOf>() } class Single(val buildType: BuildType) : Stage() class Parallel : Stage() { val buildTypes = arrayListOf() val sequences = arrayListOf() } class Sequence : Stage() { val stages = arrayListOf() } fun Parallel.build(bt: BuildType, block: BuildType.() -> Unit = {}): BuildType { bt.apply(block) buildTypes.add(bt) return bt } fun Parallel.build(block: BuildType.() -> Unit): BuildType { val bt = BuildType().apply(block) buildTypes.add(bt) return bt } fun Parallel.sequence(block: Sequence.() -> Unit): Sequence { val sequence = Sequence().apply(block) buildDependencies(sequence) sequences.add(sequence) return sequence } fun Sequence.sequence(block: Sequence.() -> Unit): Sequence { val sequence = Sequence().apply(block) buildDependencies(sequence) stages.add(sequence) return sequence } fun Sequence.parallel(block: Parallel.() -> Unit): Parallel { val parallel = Parallel().apply(block) stages.add(parallel) return parallel } fun Sequence.build(bt: BuildType, block: BuildType.() -> Unit = {}): BuildType { bt.apply(block) stages.add(Single(bt)) return bt } fun Sequence.build(block: BuildType.() -> Unit): BuildType { val bt = BuildType().apply(block) stages.add(Single(bt)) return bt } fun Project.sequence(block: Sequence.() -> Unit): Sequence { val sequence = Sequence().apply(block) buildDependencies(sequence) registerBuilds(sequence) return sequence } fun buildDependencies(sequence: Sequence) { sequence.dependencies.forEach { stageDependsOnStage(sequence, it) } var previous: Pair? = null for (stage in sequence.stages) { if (previous != null) { stageDependsOnStage(stage, Pair(previous.first, stage.dependencySettings)) } stage.dependencies.forEach { dependency -> stageDependsOnStage(stage, dependency) } val dependencySettings = previous?.second ?: {} previous = Pair(stage, dependencySettings) } } fun stageDependsOnStage(stage: Stage, dependency: Pair) { val s = dependency.first val d = dependency.second if (s is Single) { stageDependsOnSingle(stage, Pair(s, d)) } if (s is Parallel) { stageDependsOnParallel(stage, Pair(s, d)) } if (s is Sequence) { stageDependsOnSequence(stage, Pair(s, d)) } } fun stageDependsOnSingle(stage: Stage, dependency: Pair) { if (stage is Single) { singleDependsOnSingle(stage, dependency) } if (stage is Parallel) { parallelDependsOnSingle(stage, dependency) } if (stage is Sequence) { sequenceDependsOnSingle(stage, dependency) } } fun stageDependsOnParallel(stage: Stage, dependency: Pair) { if (stage is Single) { singleDependsOnParallel(stage, dependency) } if (stage is Parallel) { parallelDependsOnParallel(stage, dependency) } if (stage is Sequence) { sequenceDependsOnParallel(stage, dependency) } } fun stageDependsOnSequence(stage: Stage, dependency: Pair) { if (stage is Single) { singleDependsOnSequence(stage, dependency) } if (stage is Parallel) { parallelDependsOnSequence(stage, dependency) } if (stage is Sequence) { sequenceDependsOnSequence(stage, dependency) } } fun singleDependsOnSingle(stage: Single, dependency: Pair) { stage.buildType.dependencies.dependency(dependency.first.buildType) { snapshot(dependency.second) } } fun singleDependsOnParallel(stage: Single, dependency: Pair) { dependency.first.buildTypes.forEach { buildType -> val dep = Pair(Single(buildType), dependency.second) singleDependsOnSingle(stage, dep) } dependency.first.sequences.forEach { sequence -> val dep = Pair(sequence, dependency.second) singleDependsOnSequence(stage, dep) } } fun singleDependsOnSequence(stage: Single, dependency: Pair) { dependency.first.stages.lastOrNull()?.let { lastStage -> if (lastStage is Single) { singleDependsOnSingle(stage, Pair(lastStage, dependency.second)) } if (lastStage is Parallel) { singleDependsOnParallel(stage, Pair(lastStage, dependency.second)) } if (lastStage is Sequence) { singleDependsOnSequence(stage, Pair(lastStage, dependency.second)) } } } fun parallelDependsOnSingle(stage: Parallel, dependency: Pair) { stage.buildTypes.forEach { buildType -> val single = Single(buildType) singleDependsOnSingle(single, dependency) } stage.sequences.forEach { sequence -> sequenceDependsOnSingle(sequence, dependency) } } fun parallelDependsOnParallel(stage: Parallel, dependency: Pair) { stage.buildTypes.forEach { buildType -> singleDependsOnParallel(Single(buildType), dependency) } stage.sequences.forEach { sequence -> sequenceDependsOnParallel(sequence, dependency) } } fun parallelDependsOnSequence(stage: Parallel, dependency: Pair) { stage.buildTypes.forEach { buildType -> singleDependsOnSequence(Single(buildType), dependency) } stage.sequences.forEach { sequence -> sequenceDependsOnSequence(sequence, dependency) } } fun sequenceDependsOnSingle(stage: Sequence, dependency: Pair) { stage.stages.firstOrNull()?.let { firstStage -> if (firstStage is Single) { singleDependsOnSingle(firstStage, dependency) } if (firstStage is Parallel) { parallelDependsOnSingle(firstStage, dependency) } if (firstStage is Sequence) { sequenceDependsOnSingle(firstStage, dependency) } } } fun sequenceDependsOnParallel(stage: Sequence, dependency: Pair) { stage.stages.firstOrNull()?.let { firstStage -> if (firstStage is Single) { singleDependsOnParallel(firstStage, dependency) } if (firstStage is Parallel) { parallelDependsOnParallel(firstStage, dependency) } if (firstStage is Sequence) { sequenceDependsOnParallel(firstStage, dependency) } } } fun sequenceDependsOnSequence(stage: Sequence, dependency: Pair) { stage.stages.firstOrNull()?.let { firstStage -> if (firstStage is Single) { singleDependsOnSequence(firstStage, dependency) } if (firstStage is Parallel) { parallelDependsOnSequence(firstStage, dependency) } if (firstStage is Sequence) { sequenceDependsOnSequence(firstStage, dependency) } } } fun Project.registerBuilds(sequence: Sequence) { sequence.stages.forEach { if (it is Single) { buildType(it.buildType) } if (it is Parallel) { it.buildTypes.forEach { build -> buildType(build) } it.sequences.forEach { seq -> registerBuilds(seq) } } if(it is Sequence) { registerBuilds(it) } } } fun Project.build(bt: BuildType, block: BuildType.() -> Unit = {}): BuildType { bt.apply(block) buildType(bt) return bt } fun Project.build(block: BuildType.() -> Unit): BuildType { val bt = BuildType().apply(block) buildType(bt) return bt } fun BuildType.produces(artifacts: String) { artifactRules = artifacts } fun BuildType.requires(bt: BuildType, artifacts: String, settings: ArtifactDependency.() -> Unit = {}) { dependencies.artifacts(bt) { artifactRules = artifacts settings() } } fun BuildType.dependsOn(bt: BuildType, settings: SnapshotDependency.() -> Unit = {}) { dependencies.dependency(bt) { snapshot(settings) } } /** * !!!WARNING!!! * * This method works as expected only if the stage is already populated */ fun BuildType.dependsOn(stage: Stage, dependencySettings: DependencySettings = {}) { val single = Single(this) single.dependsOn(stage, dependencySettings) //TODO: does it really work? } fun Stage.dependsOn(bt: BuildType, dependencySettings: DependencySettings = {}) { val stage = Single(bt) dependsOn(stage, dependencySettings) } fun Stage.dependsOn(stage: Stage, dependencySettings: DependencySettings = {}) { dependencies.add(Pair(stage, dependencySettings)) } fun Stage.dependencySettings(dependencySettings: DependencySettings = {}) { this.dependencySettings = dependencySettings } fun BuildType.dependencySettings(dependencySettings: DependencySettings = {}) { throw IllegalStateException("dependencySettings can only be used with parallel {} or sequence {}. Please use dependsOn instead") }