I did not found any suitable tool that can be used to load dependencies when building .NET project in a TeamCity environment (using NAnt scripts).
Until now all dependencies where stored in VCS together with the source, not so sophisticated.
All our TeamCity projects contains one SDK-configuration that is a zipped file with all output assemblies and executables.
The TeamCity offers URL patterns to access build artifacts. For example:
These two facts trigged me to build a custom NAnt task.
It simply downloads SDK-artifacts from the TeamCity repository, and unpacks the assemblies in the current build environment before the build is started.
I added a “pom”-file to all TeamCity configuration projects that defines the dependencies:
<project>
<dependencyManagement>
<repositories>
<repository>http://teamcity.mycompany.com/guestAuth/repository/download/</repository>
</repositories>
<dependencies>
<dependency>
<groupId>AGroup</groupId>
<artifactId>AProduct</artifactId>
<version>1.1.124.0</version>
</dependency>
<dependency>
<groupId>AGroup</groupId>
<artifactId>BProduct</artifactId>
<version>5.4.42.0</version>
</dependency>
<dependency>
<groupId>BGroup</groupId>
<artifactId>CProduct</artifactId>
<version>2.5.24.0</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
The NAnt task is executed in the build file:
<target name="loadDependencies">
<loaddependencies filename="${Build.Base}\dependencies.xml" target="${Build.Base}\Dependencies"/>
</target>And here is the source if someone is interested:
using System;
using System.Collections.Generic;
namespace MyNAnt.Build.Tasks
{
using System.IO;
using System.Net;
using System.Xml;
using global::NAnt.Core;
using global::NAnt.Core.Attributes;
using ICSharpCode.SharpZipLib.Zip;
[TaskName("loaddependencies")]
public class LoadDependenciesTask : Task
{
// NAnt parameters
[TaskAttribute("filename", Required = true)]
[StringValidator(AllowEmpty = false)]
public string DependencyFile { get; set; }
[TaskAttribute("target", Required = true)]
[StringValidator(AllowEmpty = false)]
public string Target { get; set; }
// Lokal parameters
private List<Dependency> Dependencies { get; set; }
private List<string> Repositories { get; set; }
/// <summary>
/// Executes the NAnt task
/// </summary>
protected override void ExecuteTask()
{
// load all dependency information
LoadDependencies();
// download dependencies from TeamCity
foreach (var dependency in Dependencies)
{
DownloadArtifact(Repositories, dependency.Group, dependency.Name, dependency.Version, Target);
}
}
/// <summary>
/// Reads the dependency list from configuration file
/// </summary>
private void LoadDependencies()
{
var doc = new XmlDocument();
Log(Level.Info, "Loading dependencies from '{0}'.", DependencyFile);
doc.Load(DependencyFile);
// load all repositories to search for assemblies
var repList = new List<string>();
var repElemList = doc.GetElementsByTagName("repository");
foreach (XmlNode repository in repElemList)
{
repList.Add(repository.InnerText);
}
Repositories = repList;
// load all dependency assemblies
var depList = new List<Dependency>();
var depElemList = doc.GetElementsByTagName("dependency");
foreach (XmlNode dependency in depElemList)
{
var item = new Dependency();
if (dependency != null)
{
item.Group = dependency["groupId"].InnerText;
item.Name = dependency["artifactId"].InnerText;
item.Version = dependency["version"].InnerText;
}
depList.Add(item);
}
Dependencies = depList;
}
/// <summary>
/// Downloads and unzip artifact from TeamCity repository
/// </summary>
/// <param name="repositoryList"></param>
/// <param name="group"></param>
/// <param name="name"></param>
/// <param name="version"></param>
/// <param name="destination"></param>
private void DownloadArtifact(List<string> repositoryList, string group, string name, string version, string destination)
{
Log(Level.Info, "Destination folder: '{0}'.", destination);
// create destination folder if it not exist
Directory.CreateDirectory(destination);
foreach (var repository in repositoryList)
{
// URL-example
// http://teamcity.mycompany.com/guestAuth/repository/download/AGroup::AProduct/1.1.124/SDK/AProduct_SDK-1.1.124.zip
var address = repository + group + "::" + name + "/" + version + "/SDK/" + name + "_SDK-" + version + ".zip";
using (var wc = new WebClient())
{
try
{
using (var streamRemote = wc.OpenRead(new Uri(address)))
{
Log(Level.Info, "Found artifact '{0}'.", address);
var zis = new ZipInputStream(streamRemote);
ZipEntry ze;
while ((ze = zis.GetNextEntry()) != null)
{
if (ze.IsDirectory)
{
Directory.CreateDirectory(ze.Name);
}
else
{
var buffer = new byte[2048];
var fileName = Path.GetFileName(ze.Name);
Log(Level.Info, "Unzipping '{0}'.", fileName);
using (
Stream outstream = new FileStream(
destination + "\\" + fileName, FileMode.Create))
{
while (true)
{
var bytes = zis.Read(buffer, 0, 2048);
if (bytes > 0) outstream.Write(buffer, 0, bytes);
else break;
}
}
}
}
return;
}
}
catch (Exception)
{
// ignore exceptions
}
}
}
Log(Level.Error, "ERROR: Artifact '{0}::{1}' with version '{2}' not found!.", group, name, version);
}
}
}