diff --git a/README.md b/README.md index d4c55c6..266606d 100644 --- a/README.md +++ b/README.md @@ -13,29 +13,33 @@ TfsBuildResultPublisher allows you to create a Fake build, and publish test resu /buildnumber:"MyApplication_Daily_1.0" Options: - -c, --collection=VALUE The collection - -p, --project=VALUE The team project - -b, --builddefinition=VALUE The build definition - -n, --buildnumber=VALUE The build number to assign the build\ - -s, --status=VALUE Status of the build (Succeeded, Failed, Stopped, PartiallySucceeded, default: Succeeded) - -f, --flavor=VALUE Flavor of the build (to track test results against, default: Debug) - -l, --platform=VALUE Platform of the build (to track test results against, AnyCPU) - -t, --target=VALUE Target of the build (shown on build report, default: default) - --localpath=VALUE Local path of solution file. (default: Solutio n.sln) - --serverpath=VALUE Version Control path for solution file. (e.g. $/Solution.sln) - --droplocation=VALUE Location where builds are dropped (default: \\server\drops\) - --buildlog=VALUE Location of build log file. (e.g. \\server\folder\build.log) - --testResults=VALUE Test results file to publish (*.trx, requires MSTest installed) - --create Should the build definition be created if it does not exist - --trigger Instead of creating a manual build, we should trigger the build - --keepForever Does the build participates in the retention policy of the build definition or to keep the build forever - --buildController=VALUE The name of the build controller to use when creating the build definition (default, first controller) - --publishTestRun Creates a test run in Test Manager (requires tcm.exe installed) - --fixTestIds If the .trx file comes from VSTest.Console.exe, the testId's will not be recognised by Test Runs (for associated automation) - --testSuiteId=VALUE The Test Suite to publish the results of the test run to [tcm /suiteId] - --testConfigid=VALUE The Test Configuration to publish the results of the test run to [tcm /configId] - --testRunTitle=VALUE The title of the test run [tcm /title] - --testRunResultOwner=VALUE The result owner of the test run [tcm /resultOwner] + -c, --collection=VALUE The collection + -p, --project=VALUE The team project + -b, --builddefinition=VALUE The build definition + -n, --buildnumber=VALUE The build number to assign the build\ + -s, --status=VALUE Status of the build (Succeeded, Failed, Stopped, PartiallySucceeded, default: Succeeded) + -f, --flavor=VALUE Flavor of the build (to track test results against, default: Debug) + -l, --platform=VALUE Platform of the build (to track test results against, AnyCPU) + -t, --target=VALUE Target of the build (shown on build report, default: default) + --localpath=VALUE Local path of solution file. (default: Solutio n.sln) + --serverpath=VALUE Version Control path for solution file. (e.g. $/Solution.sln) + --droplocation=VALUE Location where builds are dropped (default: \\server\drops\) + --buildlog=VALUE Location of build log file. (e.g. \\server\folder\build.log) + --testResults=VALUE Test results file to publish (*.trx, requires MSTest installed) + --create Should the build definition be created if it does not exist + --trigger Instead of creating a manual build, we should trigger the build + --keepForever Does the build participates in the retention policy of the build definition or to keep the build forever + --buildController=VALUE The name of the build controller to use when creating the build definition (default, first controller) + --publishTestRun Creates a test run in Test Manager (requires tcm.exe installed) + --fixTestIds If the .trx file comes from VSTest.Console.exe, the testId's will not be recognised by Test Runs (for associated automation) + --testSuiteId=VALUE The Test Suite to publish the results of the test run to [tcm /suiteId] + --testConfigid=VALUE The Test Configuration to publish the results of the test run to [tcm /configId] + --testRunTitle=VALUE The title of the test run [tcm /title] + --testRunResultOwner=VALUE The result owner of the test run [tcm /resultOwner] + --changeSets=VALUE The changeset Id's to associate the build with (value is comma delimited: 1,2,3) + --workItems=VALUE The workiem Id's to associate the build with (value is comma delimited: 1,2,3) + --autoAssocChangesetWorkItems=VALUE Whether to auto-associate changeset workitems to the build (true if a value is present) + --buildQueueDisabled=VALUE Whether the build definition's queue is created disabled (true if a value is present) ## References **Original Blog post:** [http://blogs.msdn.com/b/jpricket/archive/2010/02/23/creating-fake-builds-in-tfs-build-2010.aspx](http://blogs.msdn.com/b/jpricket/archive/2010/02/23/creating-fake-builds-in-tfs-build-2010.aspx) diff --git a/TfsCreateBuild/BuildCreator.cs b/TfsCreateBuild/BuildCreator.cs index 5843d92..bc011fd 100644 --- a/TfsCreateBuild/BuildCreator.cs +++ b/TfsCreateBuild/BuildCreator.cs @@ -40,7 +40,8 @@ public int Execute(string[] args) configuration.BuildStatus, configuration.Collection, configuration.BuildLog, configuration.DropPath, configuration.BuildFlavor, configuration.LocalPath, configuration.BuildPlatform, configuration.BuildTarget, configuration.Project, configuration.BuildDefinition, configuration.CreateBuildDefinitionIfNotExists, configuration.BuildController, configuration.BuildNumber, configuration.ServerPath, - configuration.KeepForever); + configuration.KeepForever, configuration.AssociatedChangesetIds, configuration.AssociatedWorkitemIds, configuration.AutoIncludeChangesetWorkItems, + configuration.BuildQueueDisabled); } if (!string.IsNullOrEmpty(configuration.TestResults) && File.Exists(configuration.TestResults)) diff --git a/TfsCreateBuild/BuildTestResultPublisher.cs b/TfsCreateBuild/BuildTestResultPublisher.cs index b8136ac..2a67dc4 100644 --- a/TfsCreateBuild/BuildTestResultPublisher.cs +++ b/TfsCreateBuild/BuildTestResultPublisher.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; namespace TfsBuildResultPublisher { @@ -10,10 +11,12 @@ public bool PublishTestResultsToBuild(string collection, string testResultsFile, { var paths = new[] { + @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe", + @"C:\Program Files\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe", @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\MSTest.exe", @"C:\Program Files\Microsoft Visual Studio 11.0\Common7\IDE\MSTest.exe" }; - var msTest = File.Exists(paths[0]) ? paths[0] : paths[1]; + var msTest = paths.FirstOrDefault(File.Exists); const string argsFormat = "/publish:\"{0}\" /publishresultsfile:\"{1}\" /teamproject:\"{2}\" /publishbuild:\"{3}\" /platform:\"{4}\" /flavor:\"{5}\""; var args = string.Format(argsFormat, collection, testResultsFile, project, buildNumber, buildPlatform, buildFlavour); string stdOut; diff --git a/TfsCreateBuild/Configuration.cs b/TfsCreateBuild/Configuration.cs index c4f910f..8cc88ab 100644 --- a/TfsCreateBuild/Configuration.cs +++ b/TfsCreateBuild/Configuration.cs @@ -29,5 +29,9 @@ public class Configuration public string TeamCityPassword { get; set; } public bool TriggerBuild { get; set; } public bool KeepForever { get; set; } + public int[] AssociatedChangesetIds { get; set; } + public int[] AssociatedWorkitemIds { get; set; } + public bool AutoIncludeChangesetWorkItems { get; set; } + public bool BuildQueueDisabled { get; set; } } } \ No newline at end of file diff --git a/TfsCreateBuild/ConfigurationProvider.cs b/TfsCreateBuild/ConfigurationProvider.cs index 346f8d2..11590a8 100644 --- a/TfsCreateBuild/ConfigurationProvider.cs +++ b/TfsCreateBuild/ConfigurationProvider.cs @@ -45,6 +45,10 @@ public bool TryProvide(string[] args, out Configuration configuration) {"testConfigid=", @"The Test Configuration to publish the results of the test run to [tcm /configId]", v => localConfiguration.TestConfigId = int.Parse(v)}, {"testRunTitle=", @"The title of the test run [tcm /title]", v => localConfiguration.TestRunTitle = v}, {"testRunResultOwner=", @"The result owner of the test run [tcm /resultOwner]", v => localConfiguration.TestRunResultOwner = v}, + {"changeSets=", @"The changeset Id's to associate the build with", v => localConfiguration.AssociatedChangesetIds = Array.ConvertAll(v.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries), int.Parse)}, + {"workItems=", @"The workiem Id's to associate the build with", v => localConfiguration.AssociatedWorkitemIds = Array.ConvertAll(v.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries), int.Parse)}, + {"autoAssocChangesetWorkItems=", @"Whether to auto-associate changeset workitems to the build", v => localConfiguration.AutoIncludeChangesetWorkItems = (v !=null)}, + {"buildQueueDisabled=", @"Whether the build definition's queue is disabled", v => localConfiguration.BuildQueueDisabled = (v !=null)}, }; try diff --git a/TfsCreateBuild/ITfsManualBuildCreator.cs b/TfsCreateBuild/ITfsManualBuildCreator.cs index c9757fb..9485c3c 100644 --- a/TfsCreateBuild/ITfsManualBuildCreator.cs +++ b/TfsCreateBuild/ITfsManualBuildCreator.cs @@ -2,6 +2,6 @@ { public interface ITfsManualBuildCreator { - void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever); + void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever, int[] associatedChangesetIds, int[] associatedWorkitemIds, bool autoIncludeChangesetWorkItems, bool buildQueueDisabled); } } \ No newline at end of file diff --git a/TfsCreateBuild/TfsBuildResultPublisher.csproj b/TfsCreateBuild/TfsBuildResultPublisher.csproj index 9b85582..5a8b044 100644 --- a/TfsCreateBuild/TfsBuildResultPublisher.csproj +++ b/TfsCreateBuild/TfsBuildResultPublisher.csproj @@ -58,6 +58,9 @@ + + + ..\packages\NDesk.Options.0.2.1\lib\NDesk.Options.dll @@ -102,7 +105,9 @@ - + + Designer + Designer diff --git a/TfsCreateBuild/TfsManualBuildCreator.cs b/TfsCreateBuild/TfsManualBuildCreator.cs index 9fb6066..2c2961b 100644 --- a/TfsCreateBuild/TfsManualBuildCreator.cs +++ b/TfsCreateBuild/TfsManualBuildCreator.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.TeamFoundation.Build.Client; using Microsoft.TeamFoundation.Client; +using Microsoft.TeamFoundation.VersionControl.Client; +using Microsoft.TeamFoundation.WorkItemTracking.Client; namespace TfsBuildResultPublisher { public class TfsManualBuildCreator : ITfsManualBuildCreator { - public void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever) + public void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever, int[] associatedChangesetIds, int[] associatedWorkitemIds, bool autoIncludeChangesetWorkItems, bool buildQueueDisabled) { // Get the TeamFoundation Server var tfsCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(collection)); @@ -17,7 +20,7 @@ public void CreateManualBuild(string buildStatus, string collection, string buil // Create a fake definition var definition = CreateOrGetBuildDefinition(buildServer, project, buildDefinition, createBuildDefinitionIfNotExists, - buildController, dropPath); + buildController, dropPath, buildQueueDisabled); // Create the build detail object var buildDetail = definition.CreateManualBuild(buildNumber); @@ -32,6 +35,31 @@ public void CreateManualBuild(string buildStatus, string collection, string buil if (!string.IsNullOrEmpty(buildLog)) buildDetail.LogLocation = buildLog; + WorkItem[] workItems = {}; + if (associatedChangesetIds != null) + { + var service = tfsCollection.GetService(); + var changesets = associatedChangesetIds.Select(changesetId => service.GetChangeset(changesetId, false, false)).ToArray(); + buildProjectNode.Node.Children.AddAssociatedChangesets(changesets); + + if (autoIncludeChangesetWorkItems) + workItems = workItems.Concat(changesets.SelectMany(c => c.WorkItems)).ToArray(); + } + + if (associatedWorkitemIds != null) + { + var service = tfsCollection.GetService(); + workItems = workItems.Concat(associatedWorkitemIds.Select(id => service.GetWorkItem(id))).ToArray(); + } + + buildProjectNode.Node.Children.AddAssociatedWorkItems(workItems); + const string integrationBuild = "Integration Build"; + foreach (var wi in workItems.Where(wi => wi.Fields.Contains(integrationBuild))) + { + wi.Fields[integrationBuild].Value = definition.Name + "/" + buildDetail.BuildNumber; + wi.Save(); + } + buildProjectNode.Save(); // Complete the build by setting the status to succeeded @@ -42,8 +70,8 @@ public void CreateManualBuild(string buildStatus, string collection, string buil private IBuildDefinition CreateOrGetBuildDefinition( IBuildServer buildServer, string project, string buildDefinition, - bool createBuildDefinitionIfNotExists, - string buildController, string dropLocation) + bool createBuildDefinitionIfNotExists, + string buildController, string dropLocation, bool buildQueueDisabled) { try { @@ -56,10 +84,10 @@ private IBuildDefinition CreateOrGetBuildDefinition( } Console.WriteLine("'{0}' does not exist, trying to create build definition", buildDefinition); - return CreateBuildDefinition(buildServer, buildController, project, dropLocation, buildDefinition); + return CreateBuildDefinition(buildServer, buildController, project, dropLocation, buildDefinition, buildQueueDisabled); } - private IBuildDefinition CreateBuildDefinition(IBuildServer buildServer, string buildController, string project, string dropLocation, string buildDefinition) + private IBuildDefinition CreateBuildDefinition(IBuildServer buildServer, string buildController, string project, string dropLocation, string buildDefinition, bool buildQueueDisabled) { var controller = GetBuildController(buildServer, buildController); @@ -72,7 +100,7 @@ private IBuildDefinition CreateBuildDefinition(IBuildServer buildServer, string definition.BuildController = controller; definition.DefaultDropLocation = dropLocation; definition.Description = "Fake build definition used to create fake builds."; - definition.QueueStatus = DefinitionQueueStatus.Enabled; + definition.QueueStatus = buildQueueDisabled ? DefinitionQueueStatus.Disabled : DefinitionQueueStatus.Enabled; definition.Workspace.AddMapping("$/", "c:\\fake", WorkspaceMappingType.Map); definition.Process = processTemplate; definition.Save(); diff --git a/TfsCreateBuild/app.config b/TfsCreateBuild/app.config index e365603..c5015c5 100644 --- a/TfsCreateBuild/app.config +++ b/TfsCreateBuild/app.config @@ -1,3 +1,46 @@ - + + + + +