How do I build a Team Project with shared code?
One question that comes up a lot in Team System deployments is "how do I share code across team projects"? The easiest way to accomplish this is to set up a Team Project specifically for common, shared code. Here's a simple example of this scenario.
We have one Team Project for ClientApp, and a second Team Project for CommonUtils. ClientApp is a console application that references CommonUtils.
These projects will share a common path on the hard drive:
- c:\tfsprojects\ClientApp
- c:\tfsprojects\CommonUtils
And they'll have workspace mappings to those local hard drive paths:
| Source Control Folder | Local Folder
|
| $/ClientApp | c:\tfsprojects\ClientApp
|
| $/CommonUtils | c:\tfsprojects\CommonUtils
|
CommonUtils is a Class Library project with a post-build event to copy its output to a \binaries folder:
The resulting path is c:\tfsprojects\CommonUtils\CommonUtils\binaries\CommonUtils.dll. The ClientApp project will reference this DLL:
We can now create a Team Build for ClientApp.
However, this build will fail! Team Build cannot figure out the shared DLL reference and thus throws an error. The build output log shows the exact error:
warning MSB3245: Could not resolve this reference. Could not locate the assembly "CommonUtils". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors
However, we can fix this. Thanks to a helpful post by Manish Agarwal, we know how to edit the TFSBuild.proj script to fix this. Check out the clientappbuild build script from Source Control Explorer, and begin editing.
1) add these variables to the PropertyGroup section:
<!-- to be added under PropertyGroup -->
<TfCommand>$(TeamBuildRefPath)\..\tf.exe</TfCommand>
<SkipInitializeWorkspace>true</SkipInitializeWorkspace>
2) add these ItemGroup entries which map both Team Projects and both solutions:
<ItemGroup>
<!-- add one entry for every solution we reference -->
<SolutionToBuild Include="$(SolutionRoot)\CommonUtils\CommonUtils.sln" />
<SolutionToBuild Include="$(SolutionRoot)\ClientApp\ClientApp.sln" />
</ItemGroup>
<ItemGroup>
<!-- add one entry for every Team Project we reference -->
<Map Include="$/ClientApp/ClientApp">
<LocalPath>$(SolutionRoot)\ClientApp</LocalPath>
</Map>
<Map Include="$/CommonUtils/CommonUtils">
<LocalPath>$(SolutionRoot)\CommonUtils</LocalPath>
</Map>
</ItemGroup>
3) hook up the BeforeGet event to retrieve the workspaces for each Team Project, too:
<Target Name="BeforeGet">
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
Name="$(WorkspaceName)" />
<Exec
WorkingDirectory="$(SolutionRoot)"
Command=""$(TfCommand)" workspace /new $(WorkSpaceName) /server:$(TeamFoundationServerUrl)"/>
<Exec
WorkingDirectory="$(SolutionRoot)"
Command=""$(TfCommand)" workfold /unmap /workspace:$(WorkSpaceName) "$(SolutionRoot)""/>
<Exec
WorkingDirectory="$(SolutionRoot)"
Command=""$(TfCommand)" workfold /map /workspace:$(WorkSpaceName) /server:$(TeamFoundationServerUrl) "%(Map.Identity)" "%(Map.LocalPath)""/>
</Target>
Check in the build script, execute it, and now we have a successful Team Build of a Team Project that references a DLL in another Team Project:
And our build output folder contains what we would expect-- the application DLL, and the shared dependency DLL, too:
(Thanks to Eric Cherng for his help in writing this blog post!)