I have a VS2010 solution file with over 20 projects in it, and some of the projects have dependencies on other projects from within the solution.
I also have multiple bu
I thought I'd update my previous answer, as I've spent a lot of time and effort creating my own workaround to this problem. The work around is a bit more comprehensive than simply living with the problem, but I've attempted to both eliminate the issue and insulate ourselves against future shocks like this.
MSBuild has been demoted from working with solutions, configurations or otherwise. MSBuild is just asked to compile projects in isolation. The order this happens is calculated by a Powershell script that parses ours solutions and projects to work out the best Just-In-Time build execution plan.
Key to this (and I think you might find helpful) are the following snippets:
Identifying my solutions
I have a list of all the solutions in my platform, and I essentially iterate over each of these.
$buildPlan = (
@{
solutions = (
@{
name = "DataStorage"
namespace = "Platform.Databases"
},
@{
name = "CoreFramework"
},
@{
namespace = "Platform.Server"
name = "Application1"
},
@{
namespace = "Platform.Server"
name = "Application2"
},
@{
namespace = "Platform.Client"
name = "Application1"
}
)
})
I have some logic that helps to translate this into actual physical paths, but its very bespoke to our needs, so I won't list it here. Sufficed to say, from this list, I can find the .sln file I need to parse.
Parsing the solution file for projects
With each solution, I read the .sln file and attempt to extract all the projects contained within that I will need to build later.
So firstly, identify all projects in my
$solutionContent = Get-Content $solutionFile
$buildConfigurations += Get-Content $solutionFile | Select-String "{([a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12})}\.(Release.*)\|Any CPU\.Build" | % {
New-Object PSObject -Property @{
Name = $_.matches[0].groups[3].value.replace("Release ","");
Guid = $_.matches[0].groups[1].value
}
} | Sort-Object Name,Guid -unique
And then translate this into a nice list of projects that I can iterate over later.
$projectDefinitions = $solutionContent |
Select-String 'Project\(' |
ForEach-Object {
$projectParts = $_ -Split '[,=]' | ForEach-Object { $_.Trim('[ "{}]') };
$configs = ($buildConfigurations | where {$_.Guid -eq $projectParts[3]} | Select-Object Name)
foreach ($config in $configs)
{
$santisiedConfig = if ([string]::IsNullOrEmpty($config.Name)){"Release"}else{$config.Name}
if ($projectParts[1] -match "OurCompanyPrefix.")
{
New-Object PSObject -Property @{
Name = $projectParts[1];
File = $projectParts[2];
Guid = $projectParts[3];
Config = $santisiedConfig
}
}
}
}
Load the Visual Studio project
From my parsing of the solution file, I now have a list of projects per solution, which crucially contains the relative File Path from the solution root to find the project.
$projectDefinition = [xml](Get-Content $csProjectFileName)
$ns = @{ e = "http://schemas.microsoft.com/developer/msbuild/2003" }
$references = @();
1) Identifying external project references
$references += Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:ItemGroup/e:Reference" -Namespace $ns | % {$_.Node} | where {$_.Include -match "OurCompanyPrefix" -and $_.HintPath -notmatch "packages"} | % {$_.Include}
2) Identifying internal project references
$references += Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:ItemGroup/e:ProjectReference" -Namespace $ns | % { $_.Node.Name }
3) Following "Post-Build" events as external references
$references += Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:PropertyGroup/e:PostBuildEvent" -Namespace $ns | where {(!([String]::IsNullOrEmpty($_.Node.InnerText)))} | % {
$postBuildEvents = $_.Node.InnerText.Split("`n")
$projectsReferencedInPostBuildEvents = $postBuildEvents | Select-String "\(SolutionDir\)((\w|\.)*)" | % {$_.Matches[0].Groups[1].Value}
if ($projectsReferencedInPostBuildEvents -ne $null)
{
Write-Output $projectsReferencedInPostBuildEvents | % { $matchedProject = $_; ($releaseConfiguation | ? {$_.File -match $matchedProject}).Name }
}
}
And, since we're at it, get some basic output information too
This is handy when it comes to iterating my list of projects to build, as know where to push the output, or where to find the output of a dependent.
$assemblyName = (Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:PropertyGroup/e:AssemblyName" -Namespace $ns).Node.InnerText
$outputPath = (Select-Xml -Xml $projectDefinition -XPath "//e:Project/e:PropertyGroup[contains(@Condition,'Release|')]/e:OutputPath" -Namespace $ns).Node.InnerText
And at the end of it all
We just need to make sure we don't have any duplicates, so I record just the distinct dependencies of this particular code project:
$dependendents = @();
if ($references -ne $null)
{
$buildAction.project.dependencies += $references | where {(!([string]::IsNullOrEmpty($_))) -and ($_ -match "OurCompanyPrefix\.(.*)")} | % { $_.ToLower()} | Select -unique
}
I would hope this provides you with enough information for parsing your SLN and PROJ files. How you would choose to capture and store this information I think would depend entirely up to you.
I'm in the middle of writing quite an in-depth blog post about this, which will contain all the trimmings and framework I've eluded to above. The post isn't ready yet, but I will be linking to it from an earlier post : http://automagik.piximo.me/2013/02/just-in-time-compilation.html - Since this change by Microsoft nearly derailed this work!
Cheers.