teamcity {
version = '9.1'
}
30 November 2016
Tags: gradle teamcity build plugin
The previous post provided a very brief introduction to using the Gradle TeamCity plugin. This is the first post of two on using the plugin and will expand on the plugin’s configuration properties and tasks, introduce agent-side plugin and tools support and a few tips on using the plugin. The second post will cover how to setup multiple TeamCity environments to test and debug a plugin.
The Gradle TeamCity plugin actually consists of 3 plugins and typically each is applied to a separate Gradle project in a multi-project setup to build and package the corresponding component of a TeamCity plugin.
The server-side plugin, com.github.rodm.teamcity-server
adds tasks and dependencies to a Gradle project to build a
server-side plugin archive. This plugin is required to produced the final plugin archive to be deployed to a TeamCity server.
The agent-side plugin, com.github.rodm.teamcity-agent
adds tasks and dependencies to a Gradle project to build an
agent-side plugin archive.
The third plugin is the common plugin, com.github.rodm.teamcity-common
, this plugin only adds a dependency to a
Gradle project to support creating a shared library for use by both the agent-side and server-side components.
We can configure the version of the API to be used by the plugins by setting the version
property in the teamcity
configuration. By default it’s set to '9.0', it can be set to any release or snapshot version of TeamCity but I
would recommend setting the version using only the major and minor numbers.
teamcity {
version = '9.1'
}
We can support changing the version at build time by using a Gradle property. Using a Gradle property to change the API version to build against makes it easy to discover any incompatible API changes.
ext {
teamcityVersion = findProperty('teamcity.version') ?: '9.1'
}
With the above configuration, building the plugin against a newer version of the API can be run by providing an
override to the teamcity.version
property at the command line.
$ ./gradlew -Pteamcity.version=10.0 clean build
The first plugin of the three is the common plugin. This plugin only adds the common-api
dependency to a Gradle
project. The output of the project, a jar file, can then be packaged with both the agent-side and server-side plugins.
apply plugin: 'java'
apply plugin: 'com.github.rodm.teamcity-common'
teamcity {
version = teamcityVersion
}
The example above shows the version
property being set with the value of the extension property teamcityVersion
,
this expects the extension property value to be inherited from the root project.
By default the jar file will contain the project version as part of its name. For a jar file that will be packaged
into a plugin archive file it may not be necessary to keep the version, we can remove the version from the jar name
by setting the version property of the jar
task to an empty string.
jar {
version = ''
}
The next plugin is the agent-side plugin, it adds the dependency agent-api
to a project and the following
tasks:-
generateAgentDescriptor
processAgentDescriptor
agentPlugin
The generateAgentDescriptor
task will use the descriptor defined in the Gradle build file and generate an agent-side
plugin descriptor file in the build directory. The task is disabled if the descriptor is defined to use an external
file.
The processAgentDescriptor
task will use the descriptor file defined in the Gradle build file. It will copy the
descriptor file to the build directory and replace any token in the file with the value defined in the build file.
The agentPlugin
task packages the agent-side jar, any third-party libraries and plugin descriptor into an agent-side
plugin archive, a zip file. The agent-side plugin archive is added to the plugin
configuration so that it can be
used as a dependency by a project building the server-side plugin.
In addition to adding the above tasks the plugin extends the jar
task to output warnings if the Spring Bean
descriptor file references any classes that are not included in the agent-side jar file.
The example below shows the minimum configuration required to create an agent-side plugin descriptor. More descriptor properties supported by the plugin can be found in the examples of the README file.
teamcity {
agent {
descriptor {
pluginDeployment {
useSeparateClassloader = true
}
}
}
}
We can include a shared jar built against the common-api
from another Gradle project by adding it as a dependency.
dependencies {
compile project(':common')
}
By default the agent-side plugin archive name is a based on the name of the root Gradle project with '-agent' and
the project version appended. We can change this by setting the baseName
and version
properties of the agentPlugin
task.
agentPlugin {
baseName = 'pluginName'
version = ''
}
We can include additional jars, native libraries and scripts in the plugin archive. The files to be included can be
defined in one or more files
CopySpec configuration blocks.
teamcity {
agent {
files {
into('lib') {
from('path/to/additional/jars')
}
}
files {
into('bin') {
from('path/to/scripts')
}
}
}
}
The agent-side plugin can also produce a tool plugin. A tool plugin can be used to repackage an existing tool for deployment to TeamCity. The tool is made available to build configurations as a parameter, the parameter is the path to where the tool is installed on each build agent.
A minimal Gradle project to build a tool plugin can apply the agent-side and server-side plugins and use Gradle’s dependency management to download the tool to be repackaged.
The samples directory for the Gradle TeamCity plugin contains an example project,
agent-tool-plugin, that shows Apache Maven 3.3.3 being repackaged as a tool. The build file
shows how the Maven archive is downloaded as a dependency, added to the plugin archive using the files
CopySpec and how the mvn
shell script is set to be executable.
Creating tool plugins is useful for deploying tools to all TeamCity build agents that are not available using the native package manager on the build agent host.
The final plugin is the server-side plugin, it adds the dependency server-api
to the project and the following
tasks:-
generateServerDescriptor
processServerDescriptor
serverPlugin
The generateServerDescriptor
task will use the descriptor defined in the Gradle build file and generate an server-side
plugin descriptor file in the build directory. The task is disabled if the descriptor is defined to use an external
file.
The processServerDescriptor
task will use the descriptor file defined in the Gradle build file. It will copy the
descriptor file to the build directory and replace any token in the file with the value defined in the build file.
An example is shown at the end of this post.
The serverPlugin
task packages the server-side jar, any third-party libraries, the agent-side plugin archive and
plugin descriptor into a server-side plugin archive, a zip file.
A complete set of the descriptor properties supported by the server-side plugin can be found in the examples of the README file.
The server-side plugin, like the agent-side plugin, extends the jar
to output warnings if the Spring Bean descriptor
file references classes that are not included in the server-side jar file.
To include a jar from another project that has been built against the common-api
the same configuration
shown above for the agent-side plugin can be used.
To include the agent-side plugin archive, the output from a project building the agent-side plugin, can be added to
the agent
configuration as shown below.
dependencies {
agent project(path: ':agent', configuration: 'plugin')
}
The server-side plugin like the agent-side plugin can include additional files, jars or native libs, and scripts in
the archive using the files
CopySpec property. The example shown for the agent-side is the
same for the server-side.
The default name for the plugin archive is the name of the root Gradle project, this is typically defined in the
settings.gradle file, and the version property. We can change the name and remove the version from the archive name
by setting the following properties on the serverPlugin
task.
serverPlugin {
baseName = 'pluginName'
version = ''
}
Tokens to be replaced in the plugin descriptor XML file should follow Ant’s style for tokens, this means they should start and end with the '@' character.
<?xml version="1.0" encoding="UTF-8"?>
<teamcity-plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:schemas-jetbrains-com:teamcity-plugin-v1-xml">
<info>
<name>server-plugin</name>
<display-name>server-plugin</display-name>
<version>@VERSION@</version>
<description>TeamCity Example Server Plugin</description>
<vendor>
<name>@VENDOR_NAME@</name>
</vendor>
</info>
<deployment use-separate-classloader="true"/>
</teamcity-plugin>
To replace the tokens in the above file the server-side plugin can be configured, as shown below, to provide a map of the tokens and values.
teamcity {
server {
descriptor = file("${rootDir}/teamcity-plugin.xml")
tokens VERSION: project.version, VENDOR_NAME: 'vendor'
}
}
This post has hopefully provided more detail and some tips on building TeamCity plugins using the Gradle TeamCity plugin. The next post will show how to use the plugin to test and debug a TeamCity plugin.