commit 43063f1f46c94beaeb22c969c7f33e3c81b87791 Author: wrenge Date: Mon Jun 6 17:14:05 2022 +0300 Initial commit diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bc9852f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ + +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [1.0.0] - 2022-06-06 +- Initial release \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..945dbe3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +./gamectl setup_teamcity_settings \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..43780c7 --- /dev/null +++ b/composer.json @@ -0,0 +1,11 @@ +{ + "name": "bit/taskman_teamcity", + "description": "Teamcity build setup for RND", + "homepage": "https://git.bit5.ru/composer/taskman_teamcity", + "require": { + "php": ">=7.4" + }, + "autoload": { + "classmap": ["teamcity.inc.php"], + } +} diff --git a/teamcity.inc.php b/teamcity.inc.php new file mode 100644 index 0000000..1a76db0 --- /dev/null +++ b/teamcity.inc.php @@ -0,0 +1,22 @@ + 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(UploadArtifactToSlack("Build_Upload_Android", BuildAndroid)) + + //IOS + build(BuildIOS) + build(UploadArtifactToSlack("Build_Upload_IOS", BuildIOS)) + } + } +} + +object BuildAndroid : BuildAndroidType("", "prod/global_android", "+:*") +object BuildIOS : BuildIOSType("", "prod/global_ios", "+:*") + +open class BuildAndroidType(proj : String, env : String, branch: String) : + BuildType({ + name = "Build Android" + + var artifact_rules : String = "*.apk\n*.aab" + artifactRules = artifact_rules + + vcs { + root(GitProjectRepo) + branchFilter = branch + } + + params { + checkbox("build.env.debug", "false", label = "Debug", + description = "Build developer version.", checked = "true") + param("build.env.buildName", "%system.teamcity.buildConfName%") + param("build.env.workDir", "%system.teamcity.build.workingDir%") + param("build.env.artifactRules", artifact_rules) + } + + features { + notifications { + notifierSettings = slackNotifier { + connection = "PROJECT_EXT_2" + sendTo = "#rnd-builds" + messageFormat = verboseMessageFormat { + addBranch = true + addStatusText = true + maximumNumberOfChanges = 10 + } + } + buildFailed = true + buildFinishedSuccessfully = true + } + } + + + var gamectl_environment = if("%build.env.debug%" == "true") env.replace("global", "dev") else env + + steps { + script { + name = "Deps Update" + scriptContent = "./gamectl try_deps_update" + } + script { + name = "Clean" + scriptContent = "./gamectl clean" + } + script { + name = "Produce" + scriptContent = "./gamectl --env=" + gamectl_environment + " build_android" + } + } + + requirements { + equals("teamcity.agent.name", "Default Agent") + } + } + ) +{} + + +open class BuildIOSType(proj : String, env : String, branch : String) : + BuildType({ + name = "Build IOS" + + var artifact_rules : String = "*.ipa" + artifactRules = artifact_rules + + vcs { + root(GitProjectRepo) + branchFilter = branch + } + + params { + checkbox("build.env.debug", "false", label = "Debug", + description = "Build developer version.", checked = "true") + param("build.env.buildName", "%system.teamcity.buildConfName%") + param("build.env.workDir", "%system.teamcity.build.workingDir%") + param("build.env.artifactRules", artifact_rules) + } + + features { + notifications { + notifierSettings = slackNotifier { + connection = "PROJECT_EXT_2" + sendTo = "#rnd-builds" + messageFormat = verboseMessageFormat { + addBranch = true + addStatusText = true + maximumNumberOfChanges = 10 + } + } + buildFailed = true + buildFinishedSuccessfully = true + } + } + + var gamectl_environment = if("%build.env.debug%" == "true") env.replace("global", "dev") else env + + steps { + script { + name = "Deps Update" + scriptContent = "./gamectl try_deps_update" + } + script { + name = "Clean" + scriptContent = "./gamectl clean" + } + script { + name = "Produce" + scriptContent = "./gamectl --env=" + gamectl_environment + " build_ios" + } + } + + requirements { + matches("teamcity.agent.name", "(Default Agent 2|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") +}