Introduction
Building Java software on a 2012 Team Foundation Server (TFS) build server might sound crazy at first, but it really works, and is relatively simple to get up and running. Unfortunately, there's a dearth of documentation available on how to configure your Ant builds in conjunction with Windows Workflow build template and project files. This article will bring everything together, and illustrate what it takes to build Java on TFS.
Background
I've been building .NET software on TFS build servers for some time now, and have developed some level of expertise in doing so. When someone asked if I could build Java software on a TFS build server, I thought they were joking. This team is using TFS for source code control, and wanted to know if building in this environment was feasible. After a little research, I found out that it not only was possible, but appears to be in wide use. Emboldened by this new-found information, I set out to take an existing Java solution and get it to build on TFS using Ant. This article shares that experience and offers some ways to make Java builds a little easier to work with in the world of TFS.
Using the code
The team being supported in this effort uses Eclipse as their IDE, and TFS as the source code repository. The team was also using Jenkins for the builds and Oracle WebLogic for deployments. The focus of this article will be on the build, not deployments. For the Eclipse IDE, the Team Explorer Everywhere add-on is required. This allows the developer/build manager to access to the source code, but more importantly, helps with the creation of build definitions. Another prerequisite for this environment is Apache Ant, "a Java library and command-line tool whose mission is to drive processes described in build files as targets and extension points dependent upon each other. The main known usage of Ant is the build of Java applications." Ant targets and tasks allow the builder to compile, assemble, test, and run Java applications. In most Java projects, there is a build folder which will contain a
build.xml file. This build.xml file is what Ant uses to construct the targets specified in the build. Because a build server is being used, the Ant files will need to specify tasks and targets that will be available to the build. There may also be external properties files, which contain build-specific properties that are loaded into the Ant build process at run time. These property files will need to passed to the build server as an argument to the Ant command. More on this soon.
Finally, there is the installation and configuration of the build server. http://msdn.microsoft.com/en-us/library/jj155784.aspx provides a how-to guide, and even steps the user through the creation of a build definition. Use those steps to configure your build server and create your first build definition.
The output of these steps is the creation of a file that TFS needs to perform the build:
TFSBuild.proj. This project file contains the Ant directives that will construct the software. In its generated form, it's a very simplistic MSBuild project file that works in conjunction with the TFS
UpgradeTemplate.xaml file, which is the Windows Workflow file that guides the build process. In order to make the build definition more flexible, some editing of both the
TFSBuild.proj and the UpgradeTemplate.xaml file will need to be performed.
Without customization, the
TFSBuild.proj contains an <ItemGroup>
entry that looks like this:
<ItemGroup>
<AntBuildFile Include="$/MyProject/web/website/build/build.xml">
<Targets></Targets>
<Properties>BinariesRoot=$(BinariesRoot);BuildDefinitionName=$(BuildDefinitionName);
BuildDefinitionUri=$(BuildDefinitionUri);BuildDirectory=$(BuildDirectory);
BuildNumber=$(BuildNumber);DropLocation=$(DropLocation);LogLocation=$(LogLocation);
SourceGetVersion=$(SourceGetVersion);TestResultsRoot=$(TestResultsRoot);
TeamProject=$(TeamProject);WorkspaceName=$(WorkspaceName);WorkspaceOwner=$(WorkspaceOwner)</Properties>
<Lib></Lib>
</AntBuildFile>
<JUnitLogFiles Include="$(BinariesRoot)\**\TEST-*.xml" />
</ItemGroup>
Note that the AntBuildFile
tag has an include parameter for the
build.xml file that was specified during the construction of the build definition. There's a
<Targets>
tag,
too - this is where a build target can be passed to the project file, but it's empty by default. Obviously, this isn't very flexible in this form. The build manager would need to edit
and check in the TFSBuild.proj file every time a different build was desired. Since there can be only one
TFSBuild.proj file, this creates a real problem. Let's look at making three
variable inputs to this process (along with some defaults) that will ease the build manager's pain. Let's break down the following code segment, which is a replacement for the code segment
we just examined above:
<PropertyGroup>
<BuildEnv Condition=" '$(BuildTarget)' == '' ">build-install-jroth</BuildEnv>
<MyProps Condition=" '$(MyProps)' == '' ">build.jroth-gr.properties</MyProps>
<MyBuild Condition=" '$(MyBuild)' == '' ">build.xml</MyBuild>
</PropertyGroup>
<ItemGroup>
<AntBuildFile Include="$(MyBuild)">
<Targets>$(BuildTarget)</Targets>
<Properties>BinariesRoot=$(BinariesRoot);BuildDefinitionName=$(BuildDefinitionName);
BuildDefinitionUri=$(BuildDefinitionUri);BuildDirectory=$(BuildDirectory);
BuildNumber=$(BuildNumber);DropLocation=$(DropLocation);LogLocation=$(LogLocation);
SourceGetVersion=$(SourceGetVersion);TestResultsRoot=$(TestResultsRoot);
TeamProject=$(TeamProject);WorkspaceName=$(WorkspaceName);
WorkspaceOwner=$(WorkspaceOwner)</Properties>
<Lib></Lib>
<PropertyFile>$(BuildDirectory)$(MyProps)</PropertyFile>
</AntBuildFile>
<JUnitLogFiles Include="$(BinariesRoot)\**\TEST-*.xml" />
</ItemGroup>
Note that a <PropertyGroup>
has been created above the <ItemGroup>
from the previous example. Three conditional properties have been defined
for the build target <Targets>
, a new property, the external build property file <PropertyFile>
, and the build.xml file
<AntBuildFile Include="$(MyBuild)">
, respectively. In the <ItemGroup>
, the arguments for the build file, the properties file,
and the and build target are specified using $(MyBuild)
, $(MyProps)
, and $(BuildTarget)
, respectively. But this begs the question: where did
these arguments get defined? How are they passed into this this project file? This is where the UpgradeTemplate.xaml file comes into play.
In Visual Studio 2012, double-click on the Upgrade.Template.xaml file, which will open the scary-looking workflow editor. Don't worry - it's not as bad as it looks.
At the bottom left of that screen, click on "Arguments." Use the line with "Create Argument" to type in the first of the
three arguments above. Repeat for the next 2, until you have three entries: BuildTarget, MyProps, and MyBuild:
Next, click the odd icon on the right side of the Metadata line, and add the
three arguments to this dialog. Provide helpful Display Names for each parameter:
Use the same category for all three. When the build definition is rendered, it will group these arguments under that category header.
Click again on "Arguments" to dismiss that list. Now, find the "Run TfsBuild for Configuration" activity in the workflow, and right-click on it and select Properties.
In the properties dialog for that activity, locate the CommandLineArguments. Click on the ellipsis to open the Expression Editor and construct the following line:
"/p:BuildTarget=" + BuildTarget + ";MyProps=" +
MyProps + ";MyBuild=" + MyBuild + " " + MSBuildArguments
Click OK to save this dialog, then save the template and check it in. Open the build definition, then open the Process tab. Click on "Show details" in the upper right, select the build template, and click the "Refresh" button. Under the new category "Custom" should be the 3 arguments. Provide values for these arguments, and they will be passed into the build process.
These minor changes have now provided a flexible build definition template that can be used to create separate build definitions, one per target. They can also be changed at runtime to use whatever target, build file, or property file desired.
Points of Interest
This only solves the problem of having a single TFSBuild.proj file, which is a great step forward. There are still the issues of structuring the Java builds for the TFS Build server. If there's not a high level of software build maturity, there's likely some work in ensuring the builds are structured properly for building on a remote server.
History
- Original article: 10/8/13