Motivation
I don’t like having a single large file for a TeamCity project, which is the default when exporting a project. It violates the Single Responsibility Principle (SRP). For maintenance, I would rather find each element of interest — whether a sub-project, template, build step, or vcs root — in its own small file, so that I don’t have to hunt inside a large file. And I would rather add new files than modify existing ones.
Is This a Good Idea?
This note about non-portable DSL explains the basic structure when you want to use multiple files. And yet I never noticed it while hunting in detail for help on this topic a week ago; only just stumbled on it while writing this blog piece. It seems to imply that using multiple files is “non-portable,” but apparently I have been using the portable DSL: “The portable format does not require specifying the uuid”, which I’ve not been doing.
There is a small risk that I could do something drastic and lose my build history without a uuid. Since I also have server backups, I’m not too worried. And in all of my experiments I’ve not been able to find any problems with this approach so far.
Starting Point
The official help page has the following sample settings.kts
file:
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
version = "2020.1"
project {
buildType {
id("HelloWorld")
name = "Hello world"
steps {
script {
scriptContent = "echo 'Hello world!'"
}
}
}
}
File Structure
An approach to splitting this could result in the following structure:
.teamcity directory
|-- _self
|-- buildTypes
|-- EchoHelloWorld.kt
|-- Project.kt
|-- pom.xmls
|-- settings.kts
Some conventions to note here:
- Per the Kotlin Coding
Conventions,
the directory names correspond to packages, the packages are named with
camelCase
rather thanPascalCase
, but the file / class name is inPascalCase
. - Whereas the single file has the Kotlin script extension
.kts
, the individual files have plain.kt
, except forsettings.kts
. - Root-level project files are in the
_self
directory. The TeamCity help pages mention this as_Self
, but I prefer_self
as it reinforces the Kotlin coding convention. - When converting from a single portable script to multiple scripts, be sure to set the package name correctly at the top of each file. Otherwise you will likely trip yourself up with compilation errors, unless you explicitly reference the package name in an import.
The individual files are shown below, not including pom.xml
; there is no
reason to modify it. Note the package imports section, containing both local
packages and the jetbrains
packages.
EchoHelloWorld.kt
package _self.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
object EchoHelloWorld : BuildType ({
id("HelloWorld")
name = "Hello world"
steps {
script {
scriptContent = "echo 'Hello world!'"
}
}
})
Project.kt
I could have named this HelloWorldProject.kt
, but Project.kt
is short, simple,
and unambiguous in the root of the Self
directory.
When bringing the project definition over from the settings file, it needs to be
converted into a class, so we replace project { ...}
with a class declaration
that inherits from the JetBrains Project
class, as you see in the snippet
below. We are creating this in the _self
directory, so we also need to declare
that our class is in the _self
package, and we need to import appropriate
packages from JetBrains. Note that the first import statement came directly
from the settings file, but the second one was not there. We need to add
this second import in order for the file to know what the Project
class is.
package _self
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
object HelloWorldProject : Project({
buildType(_self.buildTypes.EchoHelloWorld)
})
settings.kts
import jetbrains.buildServer.configs.kotlin.v2019_2.*
version = "2020.1"
project(_self.HelloWorldProject)
Enriching with a VCS Root
To further demonstrate, let’s add a new file defining a Git VCS root.
.teamcity directory
|-- _self
|-- buildTypes
|-- EchoHelloWorld.kt
|-- vcsRoots
|-- HelloWorldRepo.kt
|-- Project.kt
|-- pom.xml
|-- settings.kts
HelloWorldRepo.kt
See the previous post’s Managing Secure
Data
section for important information on the accessToken
variable. Note that the
GitHub organization name is specified as a variable — allowing a developer
to test in a fork (substitute user’s username for organization) before
submitting a pull request.
package installer.vcsRoots
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot
object HelloWorldRepo : GitVcsRoot({
name = "Hello-World"
url = "https://github.com/%github.organization%/Hello-World.git"
branch = "%git.branch.default%"
userNameStyle = GitVcsRoot.UserNameStyle.NAME
checkoutSubmodules = GitVcsRoot.CheckoutSubmodules.IGNORE
serverSideAutoCRLF = true
useMirrors = false
authMethod = password {
userName = "%github.username%"
password = "%github.accessToken%"
}
})
Next Steps
Hoping to cover in a future post…
- Templates are just specialized BuildTypes.
- Build Features
- Generate XML for further validation
Posted with : DevOps Tools and Practices, Tech, Software Development Life Cycle