Initial commit

This commit is contained in:
wrenge 2022-06-06 17:14:05 +03:00
commit 43063f1f46
5 changed files with 616 additions and 0 deletions

9
CHANGELOG.md Normal file
View File

@ -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

1
README.md Normal file
View File

@ -0,0 +1 @@
./gamectl setup_teamcity_settings

11
composer.json Normal file
View File

@ -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"],
}
}

22
teamcity.inc.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace taskman;
use Exception;
task('setup_teamcity_settings', function($args = [])
{
setup_teamcity_settings($args);
});
function setup_teamcity_settings($args = array())
{
global $GAME_ROOT;
$cmd = "git config --get remote.origin.url";
$url = str_replace("\n", "", shell_exec($cmd));
$name = "\"git: ${url}\"";
taskman_propset("TEAM_CITY_SETTINGS_NAME_HERE", $name);
taskman_propset("TEAM_CITY_SETTINGS_URL_HERE", "\"$url\"");
gen_build_file(__DIR__ . "/teamcity/settings.kts.in",
"$GAME_ROOT/.teamcity/settings.kts");
}

573
teamcity/settings.kts.in Normal file
View File

@ -0,0 +1,573 @@
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(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<Pair<Stage, DependencySettings>>()
}
class Single(val buildType: BuildType) : Stage()
class Parallel : Stage() {
val buildTypes = arrayListOf<BuildType>()
val sequences = arrayListOf<Sequence>()
}
class Sequence : Stage() {
val stages = arrayListOf<Stage>()
}
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<Stage, DependencySettings>? = 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<Stage, DependencySettings>) {
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<Single, DependencySettings>) {
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<Parallel, DependencySettings>) {
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<Sequence, DependencySettings>) {
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<Single, DependencySettings>) {
stage.buildType.dependencies.dependency(dependency.first.buildType) {
snapshot(dependency.second)
}
}
fun singleDependsOnParallel(stage: Single, dependency: Pair<Parallel, DependencySettings>) {
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<Sequence, DependencySettings>) {
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<Single, DependencySettings>) {
stage.buildTypes.forEach { buildType ->
val single = Single(buildType)
singleDependsOnSingle(single, dependency)
}
stage.sequences.forEach { sequence ->
sequenceDependsOnSingle(sequence, dependency)
}
}
fun parallelDependsOnParallel(stage: Parallel, dependency: Pair<Parallel, DependencySettings>) {
stage.buildTypes.forEach { buildType ->
singleDependsOnParallel(Single(buildType), dependency)
}
stage.sequences.forEach { sequence ->
sequenceDependsOnParallel(sequence, dependency)
}
}
fun parallelDependsOnSequence(stage: Parallel, dependency: Pair<Sequence, DependencySettings>) {
stage.buildTypes.forEach { buildType ->
singleDependsOnSequence(Single(buildType), dependency)
}
stage.sequences.forEach { sequence ->
sequenceDependsOnSequence(sequence, dependency)
}
}
fun sequenceDependsOnSingle(stage: Sequence, dependency: Pair<Single, DependencySettings>) {
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<Parallel, DependencySettings>) {
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<Sequence, DependencySettings>) {
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 <code>stage</code> 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")
}