Tim asked for some more detail on using Perforce as your SCM for CruiseControl from this post, so here are some sample config files. This post may be too detailed for a general audience…
Perforce Setup – Build Server Workspace
You'll need a dedicated workspace for the build server to use – this will be used by CC.Net to check for changes and to resync. You don't need a separate user account, so it can be created with the build manager as the owner. You'll define the location to monitor in the CC.Net config, so the workspace only needs a view over the root project. So you may create a workspace called build.server with a view of //depot/… //build.server/… (while you're setting this up you can use it from your dev machine with a different root from your normal workspace, then change the host to be the build server when your scripts are finished).
CruiseControl setup - CCNet.config
This sample config uses the build server's workspace to monitor //depot/ProjectX/MAIN/source (assumes the workspace depot is c:\depot):
<cruisecontrol>
<project name="ProjectX">
<sourcecontrol type="p4">
<view>//depot/ProjectX/MAIN/source...</view>
<client>build.server</client>
<applyLabel>true</applyLabel>
<autoGetSource>true</autoGetSource>
</sourcecontrol>
<workingDirectory>C:\depot\ProjectX\MAIN\source</workingDirectory>
<webURL>http://localhost/ccnet </webURL>
<artifactDirectory>C:\buildOutput\ProjectX\artifacts</artifactDirectory>
<modificationDelaySeconds>5</modificationDelaySeconds>
<triggers>
<intervalTrigger name="continuous" seconds="10"/>
</triggers>
<state type="state" directory="C:\buildOutput\ProjectX\artifacts" />
<labeller type="iterationlabeller">
<prefix>1.1</prefix>
<duration>4</duration>
<releaseStartDate>2008/5/29</releaseStartDate>
<separator>.</separator>
</labeller>
<tasks>
<msbuild>
<executable>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable>
<projectFile>build\ProjectX.build</projectFile>
<targets>FullBuild</targets>
<buildArgs>/noconsolelogger /p:Configuration=Debug /verbosity:minimal</buildArgs>
<logger>C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
</msbuild>
</tasks>
</project>
</cruiseControl>
This tells CC to connect to Perforce in the build.server workspace, and monitor all files under //depot/ProjectX/MAIN/source. When it finds a change, it will resynchronise (bringing the build server's copy of the source up to date), determine a label for the build, and start the FullBuild target in the MSBuild file.
If the build is successful, the label is applied to all files in the view, so Perforce is stamped with a label for every successful build (this means at any time we can create a branch for a specific build which we know will work). There's no publishing here, but you typically configure CCNet to issue build failure mails to people/lists named in the project.
For multiple projects in the same CCNet config, you can use the same workspace but specify a different view.
MSBuild setup – projectX.build
The build file specifies targets as entry points. Targets can contain other targets, so typically the build process will be split into component parts which are brought together by composite targets. A sample build file split like this is:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="FullBuild">
<!-- MSBuild extensions required; use installer from Prerequisites-->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<!-- Properties-->
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<OutputDir>bin\$(Configuration)</OutputDir>
<ObjOutputDir>obj\$(Configuration)</ObjOutputDir>
<ProjectDir>..\ProjectX</ProjectDir >
<ProjectTestDir>..\ProjectX.Test</ProjectTestDir >
<ProjectFile>$(ProjectDir)\ProjectX.csproj</ProjectFile >
<TestProjectFile>$(ProjectTestDir)\ProjectX.Test.csproj</TestProjectFile >
<ExcludeTestCategories>InProgress,LongRunning</ExcludeTestCategories>
<Version Condition=" '$(CCNetLabel)' == '' ">1.0.0.0</Version>
<NUnitOutputXmlFile>C:\buildOutput\ProjectX\artifacts\testResults_$(Version).xml</NUnitOutputXmlFile>
</PropertyGroup>
<!-- Default entry point, builds & runs all Continuous tests-->
<Target Name="FullBuild">
<CallTarget Targets="BuildNoTests"/>
<CallTarget Targets="UnitTestContinuous"/>
<CallTarget Targets="Package" />
</Target>
<Target Name="BuildNoTests">
<CallTarget Targets="Build"/>
<CallTarget Targets="PostBuild"/>
</Target>
<ItemGroup>
<OutputDirectory Include="d1">
<DirectoryName>
$(ProjectDir)\$(OutputDir)
</DirectoryName>
</OutputDirectory>
<OutputDirectory Include="d2">
<DirectoryName>
$(ProjectDir)\$(ObjOutputDir)</DirectoryName>
</OutputDirectory>
<OutputDirectory Include="d3">
<DirectoryName>$(ProjectTestDir)\$(OutputDir)</DirectoryName>
</OutputDirectory>
<OutputDirectory Include="d4">
<DirectoryName>$(ProjectTestDir)\$(ObjOutputDir)</DirectoryName>
</OutputDirectory>
</ItemGroup>
<ItemGroup>
<ProjectDirectory Include="p1">
<DirectoryName>$(ProjectDir)</DirectoryName>
</ProjectDirectory>
<ProjectDirectory Include="p2">
<DirectoryName>$(ProjectTestDir)</DirectoryName>
</ProjectDirectory>
</ItemGroup>
<Target Name="CleanDirectories">
<Attrib Directories="%(OutputDirectory.DirectoryName)" ReadOnly="false" ContinueOnError="true"/>
<RemoveDir Directories="%(OutputDirectory.DirectoryName)" ContinueOnError="true"/>
</Target>
<Target Name="SetAssemblyVersions">
<Attrib Files="%(ProjectDirectory.DirectoryName)\Properties\AssemblyInfo.cs" ReadOnly="false"/>
<AssemblyInfo CodeLanguage="CS" OutputFile="%(ProjectDirectory.DirectoryName)\Properties\AssemblyInfo.cs" AssemblyVersion="$(Version)" AssemblyFileVersion="$(Versiom)" />
</Target>
<!--Setup tasks prior to build-->
<Target Name="PreBuild">
<Message Importance="high" Text="In target PreBuild"/>
<!-- Clean the output directories-->
<CallTarget Targets="CleanDirectories"/>
<!--Set assembly versions equal to CCNet version-->
<CallTarget Targets="SetAssemblyVersions"/>
</Target>
<!-- Build projects -->
<Target Name="Build" DependsOnTargets="PreBuild">
<Message Importance="high" Text="In target Build"/>
<MSBuild Projects="$(ProjectFile)">
</MSBuild>
<MSBuild Projects="$(TestProjectFile)">
</MSBuild>
</Target>
<ItemGroup>
<ProjectOutput Include="$(ProjectDir)\$(OutputDir)\*.*"/>
</ItemGroup>
<ItemGroup>
<ProjectTestOutput Include="$(ProjectTestDir)\$(OutputDir)\*.*"/>
</ItemGroup>
<!--Cleanup tasks after build-->
<Target Name="PostBuild" DependsOnTargets="Build">
<Message Importance="high" Text="In target PostBuild"/>
<Copy SourceFiles="@(ProjectOutput)" DestinationFolder="C:\buildOutput\ProjectX\$(Version)"/>
<Copy SourceFiles="@(ProjectTestOutput)" DestinationFolder="C:\buildOutput\ProjectX\Test\$(Version)"/>
</Target>
<!-- Run all tests with the Continuous category-->
<Target Name="UnitTestContinuous">
<Message Importance="high" Text="In target UnitTestContinuous"/>
<CreateItem Include="SmartRename.Test.Dummy.dll">
<Output TaskParameter="Include" ItemName="TestAssembly" />
</CreateItem>
<Message Importance="high" Text="Assemblies: @(TestAssembly)"/>
<NUnit ToolPath="C:\Program Files\NUnit 2.4.6\bin" WorkingDirectory="$(ProjectTestDir)\$(OutputDir)" Assemblies="@(TestAssembly)" ExcludeCategory="$(ExcludeTestCategories)" OutputXmlFile="$(NUnitOutputXmlFile)"/>
</Target>
<!-- Package a& deployment tasks-->
<Target Name="Package" DependsOnTargets="Build">
<Message Importance="high" Text="In target Package"/>
</Target>
</Project>
(note some of the targets are placeholders).
This is more involved and MSBuild is well documented, but the key things to note are:
- Properties defined in the initial PropertyGroup can be overridden by values passed to the MSBuild exe (with /p:name=value;name2=value2); they are referenced in the script with $(name);
- ItemGroup allows you to define an array of items that can be operated on (e.g. in CleanDirectories target); the loop is started and entries accessed with %(GroupName.ElementName);
- Individual items in an ItemGroup can be accessed with @(ItemName);
DependsOnTargets flag will ensure the necessary targets are run before the executing target, but will not invoke the target if it has already been called;
- The SetAssemblyVersions target uses a MSBuild Community Task (http://msbuildtasks.tigris.org/) to update the build number in the projects' AssemblyInfo files - this ensures the built assemblies have the same build number as the Perforce source label (available in $(CCNetLabel) property.