При использовании подхода непрерывной интеграции часто возникает потребность в запуске не просто одного построения, а в запуске последовательности построений.
Это актуально когда код продукта разделён на несколько проектов, и они зависят друг от друга.
В данной статье я хочу рассказать, как это можно сделать, используя TFS сервер.
Пусть у нас есть два проект «Assembly1» и «Assembly2».
Для проекта «Assembly1» настроено два построения: «ClassLibrary1» и «ClassLibrary2».
Для проекта «Assembly2» тоже настроено два построения: «A2.t2» и «A2.t3».
Нам требуется после запуска построения «ClassLibrary1» запустить последовательно «ClassLibrary2» и «A2.t2».
1. Простой и медленный способ (мы так делали раньше).
Настраиваем построения «ClassLibrary2» и «A2.t2» специальным образом:
Указываем построение при check-in.
Добавляем рабочие папки от предыдущего контроллера построения.
Для построения «A2.t2» выполняются аналогичные действия.
Преимущество здесь одно – не требуется модифицировать используемый рабочий процесс для построения.
А вот недостатков больше:
— Чем больше используется «рабочих каталогов», тем дольше осуществляется построение.
— Последовательность построений невозможно запустить вручную, только check-in.
— Последовательность построений может выполниться в произвольном порядке, что не всегда хорошо.
В нашем случае мы не знаем, какое построение выполнится раньше «ClassLibrary2» или «A2.t2».
2. Используем TFS API.
— Создаём новый проект «ClassLibrary».
— Добавляем новый элемент «CodeActivity».
— Создаём два входных параметра «BuildDetail» и «TfsProjectAndBuildDefinition» первый используется для получения и управления BuildServer'ом.
— Разбираем список проектов и построений.
— Данный список состоит из строк, где каждая строка определяет имя проекта, для которого осуществляется построение и имя построения разделённых точкой с запятой.
Ниже приведён код:
using System;
using System.Activities;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.Build.Workflow.Services;
using Microsoft.TeamFoundation.Client;
namespace QueueNewBuilds
{
[BuildActivity(HostEnvironmentOption.Agent)]
public sealed class QueueNewBuild : CodeActivity
{
// The Team Project that the build definition belongs to.
[RequiredArgument]
public InArgument<IBuildDetail> BuildDetail { get; set; }
[RequiredArgument]
public InArgument<String[]> TfsProjectAndBuildDefinition { get; set; }
protected override void Execute(CodeActivityContext context)
{
String[] dirty = context.GetValue(this.TfsProjectAndBuildDefinition);
IBuildDetail buildDetail = context.GetValue(this.BuildDetail);
var pds = Parse(dirty);
//var workspace = buildDetail.BuildDefinition.Workspace;
IBuildServer buildServer = buildDetail.BuildServer;
foreach (var pd in pds)
{
try
{
string message = string.Format("Queue new build "{0}"-"{1}"", pd.TfsProject, pd.BuildDefinition);
context.TrackBuildMessage(message);
IBuildDefinition buildDef = buildServer.GetBuildDefinition(pd.TfsProject, pd.BuildDefinition);
buildServer.QueueBuild(buildDef);
}
catch (Exception ex)
{
string message = string.Format("Queue new build error "{0}"-"{1}", Exception : "{2}"",
pd.TfsProject, pd.BuildDefinition, ex.Message);
context.TrackBuildWarning(message);
}
}
}
private IEnumerable<ProjectDefinition> Parse(string[] dirty)
{
if (dirty == null)
yield break;
foreach (var item in dirty)
{
var t = item.Split(';');
if (t.Length == 2)
{
ProjectDefinition pd = new ProjectDefinition();
pd.TfsProject = t[0].Trim();
pd.BuildDefinition = t[1].Trim();
yield return pd;
}
}
}
class ProjectDefinition
{
public string TfsProject { get; set; }
public string BuildDefinition { get; set; }
}
}
}
Для использования данной «activity» делаем копию «TfvcTemplate.12.xaml»
Модифицируем процесс построения:
Создаём новый аргумент «QueueNewBuild» типа массив строк.
В блоке «Выполение в агенте» создаём переменную «buildDetail» типа «IBuildDetail».
После блока «Try» добавляем «activity» «GetBuildDetail» и «QueueNewBuild».
В блоке «GetBuildDetail» в качестве результата «Result» задаём «buildDetail».
В блоке «QueueNewBuild» в качестве параметра «BuildDetail» задаём значение «buildDetail» полученное на предыдущем шаге и в качестве параметра «TfsProjectAndBuildDefinition» задаём значение «QueueNewBuilds».
Сохраняем изменения, компилируем и добавляем в TFS сервер (данные пункты думаю не имеет смысла расписывать, т.к. они подробно расписаны например тут: www.ewaldhofman.nl/post/2010/05/27/Customize-Team-Build-2010-e28093-Part-7-How-is-the-custom-assembly-found.aspx ).
Для построения «ClassLibrary1» выполняем настройку.
Выбираем модифицированный процесс. У меня он называется «TfvcTemplate.12.2.xaml».
Задаём последовательность построения.
Сохраняем, запускаем построение и наслаждаемся последовательно выполняющимся построением.
Результаты можно обнаружить в лог файле.
Код доступен на Git: github.com/Serg2DFX/QueueNewBuilds/.
Автор: Serg2DFX