By Eugene Zakhareyev
August 2006 (last updated January 7, 2008)
The article discusses issues related to Team Foundation Server functionality, namely
- Extending Team Explorer functionality related to Team Build
- Fundamentals of TFS Version Control object model
The knowledge of Team Foundation Server version control basics is presumed.
Introduction
With the release of Visual Studio 2005 Team System, Visual Studio become much more
than development environment. One of the reasons is tight integration with Team
Foundation Server in VS IDE, so that many TFS specific tasks are integrated into
VSTS. One of such tasks is definition and execution of builds as part of Team Build
functionality. The article will discuss creation of Visual Studio 2005 add-in that will
extend the functionality of Team Explorer in order to provide integrated Team Build
Types editing experience.
Build types management in Visual Studio 2005
The Team Build Types are defined for specific Team Projects and are in fact Microsoft
Build Engine scripts created for purpose of building one or more solutions or projects
stored in TFS source code repository. The build is executed by running that script
using Team Build engine on specified computer; the build results are stored in TFS
database. Team Foundation Build functionality review (except for Team Build Types editing)
is out of scope of the article; refer to "Managing Builds with Team Build" and "Build System Customization" on MSDN.
User is able to define new build type by using "New Team Build Creation Wizard",
accessible through "Build->New Team Build Type" menu (after selecting
Team Project in Team Explorer) or by right-clicking Build Types project's node and
selecting "New Team Build Type" context menu in Team Explorer. Using the
wizard, initial options for the build type are set throughout several stages and
upon clicking Finish, new Team Build Type is created.
The build type definition consists of three files, TFSBuild.proj, TFSBuild.rsp and
WorkspaceMapping.xml. Upon creation of the build type, those files are created and
placed in TFS source code control repository, under server path "$/[Team Project
Name]/TeamBuildTypes/[Build type name]".
While creating new Team Build Type through the wizard effectively hides from the
users all inner workings related to files format and storage of the files in source
code control repository, those files (especially TFSBuild.proj MSBuild script) will
probably need to be edited for all but most trivial projects manually.
Currently, Team Explorer does not provide single-click "Edit Build Type" functionality.
The user is obliged to find the build files in Source Control Explorer and perform
manual operations (as described in comments in created build type file):
<!-- TO EDIT BUILD
TYPE DEFINITION
To edit the build type, you will need to edit this
file which was generated by the Create New Build Type wizard.
This file is under source control and needs to be checked out before making
any changes.
The file is available at -$/{TeamProjectName}/TeamBuildTypes/{BuildTypeName}
where you will need to replace TeamProjectName and
BuildTypeName with your Team Project and Build Type
name that you created
Checkout the file
1. Open Source Control Explorer by selecting View
-> Other Windows -> Source Control Explorer
2. Ensure that your current workspace has a mapping
for the $/{TeamProjectName}/TeamBuildTypes folder and that you have done a "Get
Latest Version" on that folder
3. Browse through the folders to {TeamProjectName}->TeamBuildTypes->{BuildTypeName}
folder
4. From the list of files available in this folder,
right click on TfsBuild.Proj. Select 'Check Out For Edit...'
Make the required changes to the file and save
Checkin the file
1. Right click on the TfsBuild.Proj file selected
in Step 3 above and select 'Checkin Pending Changes'
2. Use the pending checkin dialog to save your changes
to the source control
Once the file is checked in with the modifications,
all future builds using this build type will use the
modified settings
-->
This article will describe steps and code required for creation of Visual Studio
2005 add-in that will allow editing of Team Build Types with single click from Team Explorer,
as well as committing the changed files to source control, so that steps described
above will become unnecessary.
Note: In Visual Studio Team Foundation Server 2008, the build type files may be located not only under
"$/[Team Project Name]/TeamBuildTypes" but elsewhere under Team Project folder. Thus while the logic above
applies to all TFS 2005 build types, it will be incorrect for TFS 2008 build types located in custom folders.
How to create add-in
The additional commands for selected build type in Team Explorer will be provided
through Visual Studio add-in. To create add-in the following steps should be followed:
- From the Visual Studio "File->New->Project?" menu expand the "Other Project
Types" from the Project types tree
- Select Extensiblity under the "Other Project
Types" node and then select Visual Studio Add-in. Press OK
- Press the Next button
on the first page of appeared wizard
- Choose Create an Add-in using Visual C# and press the Next button
- Check the "Microsoft Visual Studio 2005" check box. Uncheck all others (you may
have more items than what is shown below) and press the Next button
- Name your add-in and give it an appropriate description and then press Next
- Uncheck "Would you like to create a command bar UI for the Add-in" and press Next.
- Press Finish
The add-in created will be without commands and any useful functionality. Now, as
the add-in will use Team Foundation Object Model and some Visual Studio extensibility
techniques, it is supposed that TFS Team Explorer is installed at the machine as
well as Visual Studio 2005 SDK (additional information about SDK may be accessed
here).
The add-in will require references to the following TFS assemblies (located at Visual
Studio's PrivateAssemblies path similar to C:\Program Files\Microsoft Visual Studio
8\Common7\IDE\PrivateAssemblies)
- Microsoft.VisualStudio.TeamFoundation.dll
- Microsoft.TeamFoundation.Common.dll
- Microsoft.TeamFoundation.Client.dll
- Microsoft.TeamFoundation.VersionControl.Client.dll
The assemblies should be added using Browse in "Add Reference" window, as private
assemblies are not listed by Visual Studio.
Additionally, the add-in will use the following Visual Studio SDK assemblies (located
at VS SDK assemblies path similar to C:\Program Files\Visual Studio 2005 SDK\2006.06\VisualStudioIntegration\Common\Assemblies)
using .NET in Add References window:
- Microsoft.VisualStudio.Shell.Interop
- Microsoft.VisualStudio.OLE.Interop
To provide additional menus on Build Type context menu, the named commands must
be created:
- Checkout - to check out the build types files for edit to local disk drive
- Edit - to open build types on local disk for editing
- Checkin - to check in changed build types files into repository
- Undo - undo changes to build types files in repository
- Configure - to configure add-in
The created commands must be added to appropriate command bar (to "Build Type" command
bar for commands appearing when right-clicking build types and "Tools" command bar
for configuration command).
The VS automation methods used for creation of commands are out of scope of current
article; the details may be viewed in add-in source code.
Accessing selected build type
Now that the commands are created, one needs to obtain the build type to edit together
with additional information required to access TFS version control (viz. TFS server
name and selected project name).
To retrieve selected build type node in Team Explorer as well as Team Foundation
server context data, the following code will be used:
private
bool GetTfsSelected(out
string serverName, out
string projectName, out
string buildTypeName)
{
serverName =
null;
projectName =
null;
buildTypeName =
null;
// get Team Explorer service
OleInterop.IServiceProvider
serviceProvider = (OleInterop.IServiceProvider)_applicationObject;
Guid guid = new
Guid(typeof(IVsTeamExplorer).GUID.ToString());
IntPtr ptr;
int result = serviceProvider.QueryService(ref
guid, ref guid, out
ptr);
if (result != 0)
throw new
ApplicationException("Failed to get Team Explorer
service");
IVsTeamExplorer explorer = (IVsTeamExplorer)Marshal.GetObjectForIUnknown(ptr);
// explorer is not open yet
if (explorer.TeamExplorerUIHierarchy == null)
return false;
// get TFS server node
object value;
result = explorer.TeamExplorerUIHierarchy.GetProperty((uint)
Microsoft.VisualStudio.VSConstants.VSITEMID_ROOT,
(int)__VSHPROPID.VSHPROPID_Name,
out value);
// if failed to get hierarchy root
if (result != 0)
throw new
ApplicationException("Failed to get TFS server node
name");
serverName = value as string;
// get project name
ProjectContext projectContext = explorer.GetProjectContext();
// if no selected project
if (projectContext.ProjectName == null)
return false;
projectName = projectContext.ProjectName;
// get selected item id
uint itemID;
IVsMultiItemSelect multiSelect;
result = explorer.TeamExplorerWindow.GetCurrentSelection(out ptr, out itemID,
out multiSelect);
// if no selection
if (result != 0)
return false;
// get selected node name (we suppose that menu is visible only on builds)
IVsHierarchy hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(ptr);
result = hierarchy.GetProperty(itemID,
(int)__VSHPROPID.VSHPROPID_Name,
out value);
if (result != 0)
return false;
buildTypeName = value as string;
return true;
}
The code performs the following tasks:
- Gets IVsTeamExplorer service using OLE.Interop
- Uses IVsTeamExplorer TeamExplorerUIHierarchy property to retrieve root node name
(which is team server name)
- Uses IVsTeamExplorer GetProjectContext method to retrieve selected project context
(to get Team Project name)
- Uses IVSTeamExplorer TeamExplorerWindow property to get currently selected hierarchy
node and then its name. As relevant add-in commands are always called from selected
build type node it may be suggested that selected node name is selected build type
name
Thus after performing the method TFS server name, Team Project name and build type
name become available in add-in for usage with TFS Version Control.
Working with version control
Now, that the commands are created and initial build type data are available one
needs to implement logic for the command execution and correct status display (so
that Check In command will appear only if build types files are checked out etc.)
For all build type operations special class called BuildTypeEditor will be used.
The follwing class diagram describes class methods and properties.
The class constructor will receive TFS server name to initialize all required TFS
object model objects and then will use Select method before any operation on build
type is performed.
In constructor, new instance of TeamFoundationServer is created using factory. It
may be safely supposed that authentication will not be required (as commands are
called through Team Explorer). After team server instance is created, new instance
of VersionControlServer is created. VersionControlServer is a class that provides
all Version Control related functionality in TFS object model, and it will be used
for version control operations.
Select method will simply set internal members to define scope for any subsequent
operations. In order to edit build type it is required first to retrieve build type
files from TFS source code repository to local disk drive. File operations in TFS,
such as Get latest version or Check out are all performed in context of user local
workspace. Workspace is essentially a set of mappings between item server path in
source code control repository and local path at user's workstation. Thus in order
to get and check out files for edit, workspace needs to be available. Add-in will
create and maintain single workspace for all files operations named BuildTypesWorkspace.
Let's have a look at the following code snippet:
// get workspace for local build files editing
Workspace[] workspaces = _serverVc.QueryWorkspaces(WorkspaceName,
UserName,
ComputerName);
Workspace workspace = null;
Debug.Assert(workspaces.Length < 2);
// either create or take found workspace
if (workspaces.Length == 0)
workspace = _serverVc.CreateWorkspace(WorkspaceName, UserName, WorkspaceComment);
else
workspace = workspaces[0];
// check - if project is not mapped, then map it
if (!workspace.IsServerPathMapped(ProjectServerPath))
workspace.Map(ProjectServerPath, ProjectLocalPath);
The code performs following tasks:
- Gets array of workspaces according to specified criteria (workspace name, user name
and computer name) using QueryWorkspaces method of VersionControlServer object.
As exact criteria are provided (workspace name, user name and computer name), the
array will either contain single workspace (if one exists at current computer) or
empty array
- If workspace does not exist, it is created using CreateWorkspace method with same
parameters values as in search call before
- Once there is valid workspace, it is possible to check whether currently selected
team project is mapped to local path on current computer using Workspace class IsServerPathMapped
method. If it is not, Workspace.Map method is used to create new mapping (the mapping
will be created of team project server path to local directory; as all build types
are located at TeamBuildTypes folder under the project folder, one mapping is sufficient
for all build types).
The properties used in the method calls such as UserName and ProjectLocalPath are
defined in BuildTypeEditor class. Of special interest are properties UserName, ProjectServerPath
and ComputerName.
string UserName
{
get
{
return _serverTfs.AuthenticatedUserName;
}
}
string ProjectServerPath
{
get
{
return String.Format("$/{0}", ProjectName);
}
}
string ComputerName
{
get
{
return Workstation.Current.Name;
}
}
UserName property uses TeamFoundationServer property AuthenticatedUserName to get
currently logged in user name. ProjectServerPath uses selected Team Project name
to make up its source code control repository path (based on the fact that all projects
are located directly under the root path $/). Computer name uses Workstation class
static property Current to retrieve current computer name.
Once there exists valid workspace with required Team Project mapping, it is possible
to get build types files to local disk. The following code is used:
// Check if get may be performed without problems
GetRequest request = new
GetRequest(BuildTypeServerPath,
RecursionType.OneLevel,
VersionSpec.Latest);
GetStatus status = workspace.Get(request,
GetOptions.Preview | GetOptions.GetAll);
// if status is not empty - will not proceed and let the user
fix it
if (!status.NoActionNeeded && (status.NumFailures
> 0 || status.NumConflicts > 0 ||
status.NumWarnings
> 0))
return false;
// get the files latest version locally
status
= workspace.Get(request, GetOptions.GetAll);
// if failed - let the user fix it
if (status.NumFailures != 0)
return false;
// pend edit with exclusive lock
int result = workspace.PendEdit(new
string[] { BuildTypeServerPath },
RecursionType.OneLevel,
null, LockLevel.CheckOut);
return (result >= 3);
The code performs the following tasks:
- GetRequest is created with parameters that mean "Get latest versions of all files
immediately under build type server path to local disk as specified by workspace
mapping". After that get operation is initiated on workspace using one of the method
Get overloads. The options for the operations are set to GetAll (meaning that all
files, not those that server thinks are outdated are to be retrieved) and Preview
(meaning not to actually get files but preview the operation expected results).
Preview flag is useful to detect situation where files are present locally and some
of them are writable
- Get status is examined to see whether there are some errors or warnings; in case
there are some operation is cancelled to let user decide what to do. All source
code control operations are logged in TFS Source Control Output pane in Visual Studio,
so detailed message is available to user
- If status is OK, Get request is initiated without Preview option, and files are
retrieved from server to local disk drive according to the workspace mapping
- Once the files are available locally, it is possible to check out those files (or
in TFS terminology to "pend an edit" on the file). The operation is performed by
using Workspace class PendEdit method (one of its many overloads) and giving it
path, recursion option, empty file type filter and lock level (meaning "check out
all files immediately under build type server path and make them writable on local
disk as specified by workspace mapping and set exclusive lock on those files").
Exclusive lock is set in order to prevent build type editing by other user as we
do not expect this to be a concurrent activity
So after executing code above, editable latest version of the build type files is
available at local disk.
Let's forget for the moment that the files after checkout are to be open for editing
and concentrate on check in activities that will be required once the files are
modified. The following code snippet may be used both for check in or changes undo
(depending on passed flag value). In that snippet most of error handling is removed
for clarity (it is supposed that check out succeeded before CommitEdit is called):
// find build type editing workspace
Workspace[] workspaces =_serverVc.QueryWorkspaces(WorkspaceName, UserName,
ComputerName);
if (workspaces.Length != 1)
throw new
ApplicationException(String.Format("Workspace {0} not found", WorkspaceName));
// check that server path mapping exists
Workspace workspace = workspaces[0];
if (!workspace.IsServerPathMapped(BuildTypeServerPath))
throw new
ApplicationException(String.Format("Path {0} is not mapped in {1}",
BuildTypeServerPath,WorkspaceName));
// get pending changes
PendingSet[] sets = workspace.QueryPendingSets(new
string[] { BuildTypeServerPath },
RecursionType.OneLevel, WorkspaceName, UserName,
false);
if (sets.Length != 1)
throw new
ApplicationException(String.Format("No pending edit changes in path {0},
mapped in
{1}", BuildTypeServerPath, WorkspaceName));
// undo or check in changes
if (undo)
workspace.Undo(sets[0].PendingChanges);
else
workspace.CheckIn(sets[0].PendingChanges, CheckinComment);
After the "check out" code snippet walkthrough, most of the code is fairly straightforward
- Workspace where build type files where checked out is found
- All pending files in the workspace under build type server folder are retrieved
by using Workspace class QueryPendingSets method (with almost same parameters specified
for PendEdit method; the last parameters specifies that we are not going to use
the pending sets returned for files downloads). The return value should contain
exactly one PendingSet (with build type files checked out at previous stage)
- Once the pending set is available, it is possible either to undo changes or to check
in them. To undo changes Undo method of Workspace class is used and pending changes
array returned in PendingSet is used as a parameter to specify changes to be undone.
When changes are undone, local files will be replaced and all edits to them will
be forgone. To check in changes, CheckIn method of Workspace class will be invoked,
again passing in PendingChanges array and check in comment
Assembling it all together
Now that it is clear how selected build type files are retrieved locally and how
version control operations are to be performed, it is time to plug that into add-in.
The commands behavior (enabled, visible etc.) is controlled by QueryStatus
method and command execution logic is invoked in Exec method (both methods
add-in implements as part of IDTCommandTarget interface inheritance).
I will not linger on QueryStatus add-in method implementation (code is
available in article sources), suffice it to say that logic implemented displays
command corresponding to build files status: "Check Out for Edit" available for
build type that was not checked out for edit locally and "Check In Changes/Undo
Changes" available for build type that is being edited locally. The implementation
uses QueryPendingSets methods discussed above.
Exec method will bring together all pieces of code we have discussed.
First it is required to retrieve TFS server, Team Project and selected build type
using code described in "Accessing selected build type data".
Secondly, depending on the command invoked the build types files will be either
checked out or checked in (using code described in "Working with version control").
In case that files are being checked out, the .proj file will be opened either in
Visual Studio or in user-configured application (see Configure command in source
code).
Conclusion
In current article I have tried to illustrate the power and elegancy of TFS Version
Control object model and overall ease with which one may extend TFS functionality
in Visual Studio. The object model methods and classes used illustrate basic functionality
of Version Control and approach to object model more advanced uses. You may download
source code
for this article as well as
add-in MSI installation package (please note that newer version of add-in
is available). Code snippets described in the article
generally leave out error handling code so for whole picture please view complete
source code version. See also Team Build Sidekick product page for updates.
Update from November 5, 2006
Version 1.1 of Team Build Sidekick add-in was renamed to Team Foundation Sidekick
add-in and in addition to functionality described in the above article that is related
to Team Build provides additional features augumenting Source Control Explorer functionality.
You may wish to obtain source code for the newer version together with version 1.1
MSI installation.
Update from January 7, 2008
Team Foundation Sidekick add-in application is no longer supported (the source code is still available for educational purposes).
The functionality previously available in add-in is now part of Team Foundation Sidekicks 2.0.
About the author
Eugene Zakhareyev is a principal consultant at Attrice Corporation in San-Jose,
California. His primary focus is SCM and development methodologies using Visual
Studio Team System. He may be contacted through his email.
|