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>
@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