Showing posts with label MsBuild. Show all posts
Showing posts with label MsBuild. Show all posts

Tuesday, 8 October 2013

Structuring Visual Studio Solutions and hooking into your Visual Studio’s build process.


This is the first of a series of posts I will write about a very common problem faced by all sizable projects – how to structure your visual studio projects and resolve inter-dependencies between projects in different visual studio solutions. This is not an issue if you have just one visual studio solution as you can use project references across the board. However, if you have a number of solutions and there a developers working on them simultaneously than the dependencies can sometimes be crippling.

From my experience, the following structure is quite common for most projects.

TypicalPlatform

The Platform Service solution contains common platform specific libraries like configuration, diagnostics, common controls, etc. The Domain Specific services contain libraries specific to the business domain such for example business specific libraries, protocols or schemas. Then there are vertical solutions on top.

In terms of visual studio solutions and project dependences, I would create a solution for each layer and the visual studio projects sitting in top layers are dependent upon projects in the lower layers.

Now that I have explained the context, let’s see different options we have in terms of setting up projects etc. The first and most common method is to set up a “DevBuild” i.e. a build  script that builds all solutions in the order starting from the lower level solutions and culminating with compiling all the “verticals”.

All references within a solution are project references and references across solutions are file references. It’s best practice to reference the assemblies from a common “Assemblies” folder rather than the output path. Typically, you would want to do the copying of all assemblies after a build as a post-build activity. There are different ways of doing it. Below are some of the ones that I have used


1) Adding a custom .targets file in the within the $(MSBuildToolsPath)\Microsoft.Common.Targets\ directory.


This is my favourite and provides a clean “extension” point to add to the Visual Studio Build process. Visual Studio adds two folders within the MSBuild installation directory (C:\Program Files(x86)\MSBuild\12.0 for Visual Studio 2013). Within the directory, there are folders called ImportBefore and ImportAfter. You can write your own MSBuild code and store the file in either of the two folders. If you have extended or hooked into one of the targets run by Visual Studio as part of building a solution, your code will be executed. For example, the following code copies all the assemblies that starts with the name MyCustom and drops them to the common assemblies location for where they can be referenced by other solutions.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="
http://schemas.microsoft.com/developer/msbuild/2003">
    <Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks"/>
    <PropertyGroup>
        <DevelopmentRoot Condition="$(DevelopmentRoot) == ''">C:\Projects\MyCustom\Development\Main</DevelopmentRoot>
        <AssembliesDirectory>$(DevelopmentRoot)\Assemblies</AssembliesDirectory>
    </PropertyGroup>
    <Target Name="CopyAssemblies" AfterTargets="Build" Condition="'$(SkipCopyAssemblies)' != 'true'">            
        <ItemGroup>
            <NetFilesToCopy Include="$(MSBuildProjectDirectory)\**\bin\$(Configuration)\MyCustom*.dll"/>
        </ItemGroup>
        <MSBuild.ExtensionPack.Framework.MsBuildHelper TaskAction="RemoveDuplicateFiles" InputItems1="@(NetFilesToCopy)" Condition="%(NetFilesToCopy.Identity) != ''">
            <Output TaskParameter="OutputItems" ItemName="NetFilesToCopyUnique"/>
        </MSBuild.ExtensionPack.Framework.MsBuildHelper>
        <MakeDir Condition="!Exists($(AssembliesDirectory))" Directories="$(AssembliesDirectory)"/>
        <Microsoft.Build.Tasks.Copy SourceFiles="@(NetFilesToCopyUnique)" DestinationFolder="$(AssembliesDirectory)" Condition="%(NetFilesToCopyUnique.Identity) != ''"/>       
    </Target>
</Project>


 


2) Using Post Build Event command


The post build events have been around since early releases of Visual Studio and allows you to run custom macros at the end of the build process. For me these are rather messy and interfere with your build process especially if you are using Team Build.


3) Visual Studio Add-on


Again a very messy approach but something we have used in the past. You can write your visual studio add-on that traps the compilation event and perform tasks such as copying files to a pre-defined location. I wouldn’t recommend it.


4) Use Visual Studio Solution extension points


When you build a visual studio solutions, behind the scenes, it generates and MSBuild file and uses the generated MSBuild file. In the generated MSBuild file, it includes two files called after.<SolutionName>.sln.targets and before.<SolutionName.sln.targets. If you have files with those names in the same directory and your solution files, it will become part of the build process and provides you an extension point. Sayed Ibrahim Hashimi has detailed it here.

The downside is that the extension points are only used when MSBUILD is called on the solution file (.sln) file from command line. Visual Studio doesn’t support it yet as detailed here.

For me Option 1 is still the best approach if you decide to go down the DevBuld route.

In my next post, I will write about NuGet and how it can be used as an alternative.


Technorati Tags: ,




Thursday, 18 April 2013

CI build, deployment Items and Copy to output directory


As I started to write this blog post, I must admit, I struggled with find an appropriate heading for it. We can put this blog post in the "How I got burnt category" category.

So, we are running our CI builds using TFS 2012 and with very little customisation to the out-of-the-box build template and it all works a treat. However, occasionally our builds are failing with the following error

C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets (3686): Unable to copy file "C:\CI\77\src\...." to "C:\CI\77\bin\....". Access to the path 'C:\CI\77\bin\....' is denied.


Looking closely, there were two traits of the files for which we were getting this error.

  1. The files had the "Copy to Output directory" property set to "Copy Always".

  1. The files were present as an "Deployment Item" in a Test class.

We had a few XAML files, which had to be loaded dynamically in our tests, so the DeploymentItem was needed.

The error happened in the  "_CopyOutOfDateSourceItemsToOutputDirectoryAlways" target in the Microsoft.Common.targets file, which is executed when MSBUILD is attempting to copy all files with the setting "Always Copy to output directory" to the output directory.

The error seems to have happened because the CI Build tried to copy a file after it has been deployed as a deployment item. We did have BuildInParallel attribute set to true. In our case, we realised that the "Copy to output directory" was not needed, so we changed the setting to "Do not copy". In other scenarios where both the conditions are needed, the solution would be to set the BuildInParallel parameter to false. 

Tuesday, 16 April 2013

Clearing Windows Server AppFabric databases


If you have been using Windows Service AppFabric, you would be aware of the two AppFabric databases. The AppFabric Monitoring database and AppFabric Persistence database. The Monitoring database stores events information while the persistence database store information about instance data and metadata.

It is often are requirement to clean up the database, especially if you deploying to a clean environment. The following MSBuild file will help do that.


    <UsingTask TaskFactory="PowershellTaskFactory" TaskName="ResetAppFabricDatabases" AssemblyFile="$(PowerShellAssemblyFile)">
        <ParameterGroup>
            <AppFabAdmins Required="true" ParameterType="System.String"/>
            <AppFabReaders Required="true" ParameterType="System.String"/>
            <AppFabUsers Required="true" ParameterType="System.String"/>
            <MonitoringDbName Required="true" ParameterType="System.String" />
            <MonitoringDbServer Required="true" ParameterType="System.String" />
            <PersistenceDbName Required="true" ParameterType="System.String" />
            <PersistenceDbServer Required="true" ParameterType="System.String" />
            <ConfirmPreference Required="true" ParameterType="System.String" />
        </ParameterGroup>
        <Task>
            <![CDATA[         
                            $executionPolicy = Get-ExecutionPolicy
                            Set-ExecutionPolicy Unrestricted                        
                Import-Module ApplicationServer
                $log.LogMessage([Microsoft.Build.Framework.MessageImportance]"Normal", "Removing persistance database {0} on sql instance {1}.", $PersistenceDbName, $PersistenceDbServer)
                Remove-ASPersistenceSqlDatabase -Force -Server $PersistenceDbServer -Database $PersistenceDbName            
            
                $log.LogMessage([Microsoft.Build.Framework.MessageImportance]"Normal", "Creating persistance database.")
                Initialize-ASPersistenceSqlDatabase -Admins $AppFabAdmins -Readers $AppFabReaders -Users $AppFabUsers -Database $PersistenceDbName -Server $PersistenceDbServer
            
                $log.LogMessage([Microsoft.Build.Framework.MessageImportance]"Normal", "Clearing Monitoring Database {0} on sql instance {1}.", $MonitoringDbName, $MonitoringDbServer)
                Clear-ASMonitoringSqlDatabase -Database $MonitoringDbName -Server $MonitoringDbServer                        
                        
                            Set-ExecutionPolicy $executionPolicy                        
                ]]>
        </Task>
    </UsingTask>


Just call the Target  "ResetAppFabricDatabases" from your project file as shown below.

Please note that you would need to have MSBuildExtensions pack installed on your machine to run this script.

    <Target Name="ClearDownAppFabric">
        <ResetAppFabricDatabases AppFabAdmins="$(Domain)\$(AppFabricAdministratorsGroup)"
                                 AppFabReaders="$(Domain)\$(AppFabricObserversGroup)"
                                 AppFabUsers="$(Domain)\$(AppFabricUsersGroup)"
                                 MonitoringDbName="$(APFMonitoringDatabaseName)"
                                 MonitoringDbServer="$(APFDatabaseServer)"
                                 PersistenceDbName="$(APFPersistenceDatabaseName)"
                                 PersistenceDbServer="$(APFDatabaseServer)"
                                 ConfirmPreference="None"/>
    </Target>

You can download the sample from here

Wednesday, 6 February 2013

An exception occurred while invoking executor 'executor://mstestadapter/v1': Object reference not set to an instance of an object.


I have been meaning to blog about this error for a few days. Only got around to do so now. Recently, after a changeset was checked in, we starting seeing the following exception in our Team Build 

An exception occurred while invoking executor 'executor://mstestadapter/v1': Object reference not set to an instance of an object.

On the face of it, we couldn't find spot anything obviously wrong in the changeset. The solutions compiled correctly on local machine and all the tests executed successfully when ran from Visual Studio. However, in the team build the unit tests would fail.

Since, we are using TFS 2010 and our test include Visual Studio 2012 functionality, we are using the VSTest activity of TfsBuildExtensions to execute our unit tests. Behind the scenes, the activity executes the vstest.console.exe process passing all the test assemblies as command line parameters. This means that all our unit tests are executed in a single process in a single test run.

Looking closely, we spotted that some of our assemblies were targeted to .Net 4.0 and some of the assemblies were targeted to .Net 4.5 and we were using Entity Framework 5.0 in both these assemblies. Entity framework includes different versions of its library for assemblies targeting .Net 4.0 as compared to .Net 4.5. This is what the problem was. If a version of Entity Framework dll is already loaded to the process, the other version of the library could not be loaded and this is what caused the error. The solution is to run tests in different test runs.

I have create a simple project to demonstrate this issue. You can download from here. The error is very obvious if vstest.console.exe is executed from command line. 

Thursday, 29 April 2010

Missing Reference name while building with Team Build

I recently spent quite a lot of time, uncovering this rather bizarre looking error in our Team Build. We start getting it when I moved one happy perfectly working build from a TSF 2008 build agent running to a new TFS 2010 Build agent. As soon as I ran the build on the new server, I start getting an error in one of the projects

Class1(14,27): error CS0234: The type or namespace name 'Class1' does not exist in the namespace 'namespace1' (are you missing an assembly reference?)

Tried the same build in the original Build agent and it did built without any problems. Ran the build again with diagnostics log. From the log files I spotted that where my project was getting compiled using Csc.exe , it didn’t include reference to a project. So, I checked again where the project being reference was getting built and yes it was. Also, it was built before the project what was referencing it. So on the face of it everything looked good. But it won’t still build!

Next thing was to try building the solution manually using MSBUILD. So wrote something like

Msbuild.exe mysolution.sln
It ran successfully without a hitch. Now, it was getting interesting. But hang on Team Build does use Multi-processor support while invoking msbuild. So, ran msbuild again with a /m switch

Msbuild.exe /m mysolution.sln


Here we go, I got exactly the same problem. So the build work fines when someone is compiling using one processor but not with multi-processor. So, what happened there?

With some support from Microsoft Visual Studio support team, found that it could be a problem that that path of the project file was too long. So looked back and yes it was. The path of the project that the other project was referencing was 262 characters. Reduced the path length by reconfiguring my build agent to build on a shorter named base directory and bang it worked correctly.

So, if you hit by this problem that a build doesn’t work when maxcpucount is greater than one, do check your path lengths.

Monday, 27 July 2009

Custom Output Directory for solutions in Team Build

Basically, the problem is that Team Build creates the output of compiled code in a different location than desktop build or in other words when you compile your project locally on your development machine.
I faced this issue because a new website was introduced in one of our solutions with the same name as another project in the same solution.
The only difference between the two were that they were in different directory structure. For this reason, it built fine on the local machine but once on the build server
using team build, the project compiled later overwrote all the website files of the earlier project.

Now, unlike Team Build v1, Orcas does support building using the same output structure as desktop build.

There is a Team Build property called CustomizableOutDir. If you set it to true, the output binaries are created within the same
folder structure rather than to the Binaries root directory. This post from Aaron Hallberg gives a very good account on the
new property.

I tried using that but since I only had to use it in one of my several solutions, I tried using the format


<SolutionToBuild Include="$(SolutionRoot)\Development\...\Solution1.sln">
<Properties>CustomizableOutDir=true</Properties>
</SolutionToBuild>

but it didn't work. Turned out the property needs to be defined in a global PropertyGroup

<PropertyGroup>
<CustomizableOutDir>true</CustomizableOutDir>
</PropertyGroup>
so once I have defined it all solutions would be build in place. So what to do now?
The above mentioned blog post mentions the use of OutDir propertiy. The OutDir property specifies the directory where Tema Build writes all output files.
Similary, there is another property TeamBuildDir is the output directory where Team Build writes the output files by default.

So, to fix the issue that all my solutions, execept for one, keep on writing to the default output directory, I wrote my buid script as follows with the CustomizableOutDir property set to true.


<SolutionToBuild Include="$(SolutionRoot)\Development\...\Solution0.sln" >
<Properties>OutDir=$(TeamBuildOutDir)</Properties>
</SolutionToBuild>


<SolutionToBuild Include="$(SolutionRoot)\Development\...\Solution1.sln" >
<!-- Since this solution needs to build in place, we don't specify the OutDir property-->
</SolutionToBuild>


<SolutionToBuild Include="$(SolutionRoot)\Development\...\Solution1.sln" >
<Properties>OutDir=$(TeamBuildOutDir)</Properties>
</SolutionToBuild>

I hope this solution is useful for other poeple as well. If it works (or not work) for you, please do not hesitate to leave a comment.