Skip to content

Instantly share code, notes, and snippets.

@3F
Created June 4, 2015 15:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save 3F/a77129e3978841241927 to your computer and use it in GitHub Desktop.
Save 3F/a77129e3978841241927 to your computer and use it in GitHub Desktop.
Variant 3: based on idea 'Map of projects'. Illustrates solution-wide PRE/POST 'events' for build-operations from Visual Studio IDE (i.e. primary from VS IDE and msbuild.exe as optional)
<?xml version="1.0" encoding="utf-8"?>
<!--
Illustrates solution-wide PRE/POST 'events' for build-operations from Visual Studio IDE (i.e. primary from VS IDE and msbuild.exe as optional)
Alternative for IVsUpdateSolutionEvents2/IVsUpdateSolutionEvents etc.
-
Variant 3: based on idea 'Map of projects'.
Problems for this variant:
* Unavailable projects - for example, if user clicked on 'Unload Project' in Solution Explorer.
However, it's trivial and not related as main bug.. This should be resolved with 'Reload Project'.
-
entry.reg@gmail.com
-->
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Build section -->
<Target Name="_Build" BeforeTargets="Build" DependsOnTargets="ProjectsMap">
<CallTarget Targets="_BuildPRE" Condition="$(ScopeDetectFirst)" />
<CallTarget Targets="_BuildPOST" Condition="$(ScopeDetectLast)" />
</Target>
<Target Name="_BuildPRE">
<!-- ... -->
<Exec Command="calc.exe" ContinueOnError="true" />
</Target>
<Target Name="_BuildPOST">
<!-- ... -->
<Exec Command="notepad.exe" ContinueOnError="true" />
</Target>
<!-- Rebuild section -->
<Target Name="_Rebuild" BeforeTargets="Rebuild" DependsOnTargets="ProjectsMap">
<CallTarget Targets="_RebuildPRE" Condition="$(ScopeDetectFirst)" />
<CallTarget Targets="_RebuildPOST" Condition="$(ScopeDetectLast)" />
</Target>
<Target Name="_RebuildPRE">
<!-- ... -->
</Target>
<Target Name="_RebuildPOST">
<!-- ... -->
</Target>
<!-- Clean section -->
<Target Name="_Clean" BeforeTargets="Clean" DependsOnTargets="ProjectsMap">
<!-- it should be with the reverse order for Clean targets -->
<CallTarget Targets="_CleanPRE" Condition="$(ScopeDetectLast)" />
<CallTarget Targets="_CleanPOST" Condition="$(ScopeDetectFirst)" />
</Target>
<Target Name="_CleanPRE">
<!-- ... -->
</Target>
<Target Name="_CleanPOST">
<!-- ... -->
</Target>
<!-- helpers -->
<Target Name="ProjectsMap">
<ProjectBuildOrder Sln="$(SolutionPath)" OnlyFirst="false">
<Output PropertyName="ProjectBuildOrderString" TaskParameter="Result" />
</ProjectBuildOrder>
<ScopeDetect Map="$(ProjectBuildOrderString)" Current="$(ProjectGuid)" IsFirst="true" >
<Output PropertyName="ScopeDetectFirst" TaskParameter="Result" />
</ScopeDetect>
<ScopeDetect Map="$(ProjectBuildOrderString)" Current="$(ProjectGuid)" IsFirst="false" >
<Output PropertyName="ScopeDetectLast" TaskParameter="Result" />
</ScopeDetect>
</Target>
<UsingTask
TaskName="ProjectBuildOrder"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll">
<ParameterGroup>
<Sln ParameterType="System.String" Required="true" />
<OnlyFirst ParameterType="System.Boolean" Required="true" />
<Result ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.Collections" />
<Using Namespace="System.Collections.Generic" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text" />
<Using Namespace="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
// Patterns from Microsoft.Build.BuildEngine.Shared.SolutionParser !
Regex rProject = new Regex("^Project\\(\"(?<TypeGuid>.*)\"\\)\\s*=\\s*\"(?<Name>.*)\"\\s*,\\s*\"(?<Path>.*)\"\\s*,\\s*\"(?<Guid>.*)\"$");
Regex rProperty = new Regex("^(?<PName>[^=]*)\\s*=\\s*(?<PValue>[^=]*)$");
//Projects with dependencies
Dictionary<string, List<string>> projects = new Dictionary<string, List<string>>();
using(StreamReader reader = new StreamReader(Sln, Encoding.Default))
{
string line;
while((line = reader.ReadLine()) != null)
{
line = line.Trim();
if(!line.StartsWith("Project(", StringComparison.Ordinal)) {
continue;
}
Match m = rProject.Match(line);
if(!m.Success) {
throw new Exception("incorrect line");
}
if(String.Equals("{2150E333-8FDC-42A3-9474-1A3956D46DE8}", m.Groups["TypeGuid"].Value.Trim(), StringComparison.OrdinalIgnoreCase)) {
continue; //SolutionFolder
}
string pGuid = m.Groups["Guid"].Value.Trim();
projects[pGuid] = new List<string>();
while((line = reader.ReadLine()) != null && (line != "EndProject"))
{
line = line.Trim();
if(line.StartsWith("ProjectSection(ProjectDependencies)", StringComparison.Ordinal))
{
for(line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
line = line.Trim();
if(line.StartsWith("EndProjectSection", StringComparison.Ordinal)) {
break;
}
projects[pGuid].Add(rProperty.Match(line).Groups["PName"].Value.Trim());
}
}
}
}
}
// Build order
Queue<string> steps = new Queue<string>(); // it should be with the reverse order for Clean targets
Func<string, bool> h = null;
h = delegate(string id)
{
projects[id].ForEach(dep => h(dep));
if(!steps.Contains(id)) {
steps.Enqueue(id);
}
return true;
};
foreach(KeyValuePair<string, List<string>> project in projects)
{
h(project.Key);
if(!steps.Contains(project.Key)) {
steps.Enqueue(project.Key);
}
}
Result = (OnlyFirst)? steps.Peek() : string.Join(";", steps.ToArray());
]]>
</Code>
</Task>
</UsingTask>
<UsingTask
TaskName="ScopeDetect"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll">
<ParameterGroup>
<Map ParameterType="System.String" Required="true" />
<Current ParameterType="System.String" Required="true" />
<IsFirst ParameterType="System.Boolean" Required="true" />
<Result ParameterType="System.Boolean" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
if(String.IsNullOrEmpty(Map) || String.IsNullOrEmpty(Current)) {
throw new Exception("'Map' or 'Current' arg is empty");
}
string[] items = Map.Split(';');
if(IsFirst && items[0] == Current) {
Result = true;
return _Success;
}
if(!IsFirst && items[items.Length - 1] == Current) {
Result = true;
return _Success;
}
Result = false;
]]>
</Code>
</Task>
</UsingTask>
<!-- remap -->
<Target Name="Build" DependsOnTargets="_Build" />
<Target Name="BuildSelection" DependsOnTargets="_Build" />
<Target Name="BuildOnlyProject" DependsOnTargets="_Build" />
<Target Name="BuildCtx" DependsOnTargets="_Build" />
<Target Name="Rebuild" DependsOnTargets="_Rebuild" />
<Target Name="RebuildSelection" DependsOnTargets="_Rebuild" />
<Target Name="RebuildOnlyProject" DependsOnTargets="_Rebuild" />
<Target Name="RebuildCtx" DependsOnTargets="_Rebuild" />
<Target Name="Clean" DependsOnTargets="_Clean" />
<Target Name="CleanSelection" DependsOnTargets="_Clean" />
<Target Name="CleanOnlyProject" DependsOnTargets="_Clean" />
<Target Name="CleanCtx" DependsOnTargets="_Clean" />
<Target Name="Deploy" DependsOnTargets="_Deploy" />
<Target Name="DeploySelection" DependsOnTargets="_Deploy" />
<Target Name="DeployCtx" DependsOnTargets="_Deploy" />
</Project>
@3F
Copy link
Author

3F commented Jun 7, 2015

use env var for caching results from ProjectsMap and similar... for a quick optimization

@knocte
Copy link

knocte commented Jan 8, 2016

I don't understand this.

@ytkachov
Copy link

Your solution is genious but it does not take into consideration projects' dependencies estableshed in project file. It could be easily corrected. Do you want me to send this fix?

@3F
Copy link
Author

3F commented May 14, 2019

@ytkachov

Your solution is genious but it does not take into consideration projects' dependencies estableshed in project file. It could be easily corrected. Do you want me to send this fix?

Why not :)

btw, this is old draft that was finally implemented in https://github.com/3F/vsSolutionBuildEvent for that period.

However, part of this solution now is core of LProjectDependencies's logic in the new MvsSln: https://github.com/3F/MvsSln
Here's map example:
MvsSln_v2.0_Map.png

I mean, I remember some fixes for this, but anyway, your PR is welcomed for mentioned project too.

Thanks!

@ytkachov
Copy link

@3F

I forked this gist and and made relevant chages in it but do not know how to make pull request for it. Thank you for your excellent work.

@3F
Copy link
Author

3F commented May 15, 2019

@ytkachov

hmm, I don't use Gist so often (my today's sandbox repo on github is better way for this). More probably your changes will be just avaialble from /forks page o_O ie. to integrate this manually. Still lot of inconceivable behaviors from this service by my view (at least they finally added notifications for the past ~5 years <_<).

But if you want to contribute for related MvsSln, you need of course to push your committed changes as PR into: https://github.com/3F/MvsSln

Thank you for your excellent work.

You're welcome. Glad that was really helpful :)
I recommend you also consider to use an independent toolset like vssbe & mvssln
https://github.com/3F

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment