Skip to content

Instantly share code, notes, and snippets.

@ytkachov
Forked from 3F/Sample.sln.msbuild.targets
Last active January 19, 2023 22:14
Show Gist options
  • Save ytkachov/ba4e43975d49f59e2d36836cca10b903 to your computer and use it in GitHub Desktop.
Save ytkachov/ba4e43975d49f59e2d36836cca10b903 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>
<Reference Include="System.Xml"/>
<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" />
<Using Namespace="System.Xml" />
<Code Type="Fragment" Language="cs">
<![CDATA[
string solutionfolder = Path.GetDirectoryName(Sln);
// 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
}
if (String.Equals("{D954291E-2A0B-460D-934E-DC6B0785DB48}", m.Groups["TypeGuid"].Value.Trim(), StringComparison.OrdinalIgnoreCase))
{
continue; // SharedCodeProject
}
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());
}
}
}
// find dependent projects in project file itself
string prjpath = Path.Combine(solutionfolder, m.Groups["Path"].Value.Trim());
List<string> refprjs = new List<string>();
XmlDocument prj = new XmlDocument();
XmlReaderSettings readerSettings = new XmlReaderSettings();
//readerSettings.DtdProcessing = DtdProcessing.Ignore;
// Load the .etp project file thru the XML reader
using (XmlReader xmlReader = XmlReader.Create(prjpath, readerSettings))
{
prj.Load(xmlReader);
}
XmlNamespaceManager ns = new XmlNamespaceManager(prj.NameTable);
ns.AddNamespace("p", "http://schemas.microsoft.com/developer/msbuild/2003");
XmlNodeList refprojects = prj.DocumentElement.SelectNodes(@"descendant::p:ProjectReference/p:Project", ns);
foreach (XmlNode referenceNode in refprojects)
refprjs.Add(referenceNode.InnerText.ToUpper());
projects[pGuid].AddRange(refprjs);
}
}
// 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)
{
if (projects.ContainsKey(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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment