If I create a solution from C # code, how can I determine which applications were actually created?

I have code that will build (not re reassemble) the whole solution from C #.

The standard build will only compile projects that have really changed.

After the assembly is complete, I would like to know which projects were actually built.

I tried:

1 - We are looking for a changed / unchanged value (or similar) from BuidlResult after completion of the assembly

2 - Attaching a custom registrar and capturing each event, and then viewing messages to see if there is a difference between modified and unchanged projects

I am really surprised that such basic information is not available. For example, it seems logical that the ProjectFinishedEventArgs argument of the Logger ProjectFinished event will contain a boolean or status value. But if he is there, then I did not pay attention to it.

Does anyone know how to determine if msbuild is recompiled or not? I hate resorting to checking the timestamps on the output binaries, but maybe this is what I need to do.

private void DoBuild() { ProjectCollection pc = new ProjectCollection(); BuildLog = new CRMBuildLogger { Parameters = _logfilename }; Dictionary<string, string> globalProperty = new Dictionary<string, string>(); BuildParameters bp = new BuildParameters(pc) { DetailedSummary = true, Loggers = new List<ILogger>() { BuildLog } }; BuildRequestData buildRequest = new BuildRequestData(SolutionFileName, globalProperty, "12.0", new[] { "Build" }, null); BuildLog.BuildResult = BuildManager.DefaultBuildManager.Build(bp, buildRequest); } 
+6
source share
1 answer

I modified my Logger class to give me what I want, but I still hope for a more โ€œnativeโ€ solution. Here it is, if someone finds it useful.

The general idea is to mark the changed file time before building the project, and then mark it again. If it has changed, assume the project has been recompiled.

I started with an MSDN example and changed these methods:

 eventSource_ProjectStarted eventSource_ProjectFinished 

If you start there, then the rest should be pretty clear. I am happy to answer questions if anyone has it.

Even better, if someone can come and delete this answer and say โ€œWhy don't you make Xโ€, then I would be very happy to hear what โ€œXโ€ is.

 using System; using System.Collections.Generic; using System.IO; using System.Security; using BuildMan.Classes; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace JustBuild { public struct ProjectOutputTimeStamp { public string ProjectName; public DateTime OutputDateTime_BeforeBuild; public DateTime OutputDateTime_AfterBuild; } public class CRMBuildLogger : Logger { public List<string> Errors = new List<string>(); public List<string> Warnings = new List<string>(); public List<string> Messages = new List<string>(); public List<ProjectOutputTimeStamp> outputs = new List<ProjectOutputTimeStamp>(); public BuildResult BuildResult; /// <summary> /// Initialize is guaranteed to be called by MSBuild at the start of the build /// before any events are raised. /// </summary> public override void Initialize(IEventSource eventSource) { if (null == Parameters) { throw new LoggerException("Log file was not set."); } string[] parameters = Parameters.Split(';'); string logFile = parameters[0]; if (String.IsNullOrEmpty(logFile)) { throw new LoggerException("Log file was not set."); } if (parameters.Length > 1) { throw new LoggerException("Too many parameters passed."); } try { // Open the file streamWriter = new StreamWriter(logFile); } catch (Exception ex) { if ( ex is UnauthorizedAccessException || ex is ArgumentNullException || ex is PathTooLongException || ex is DirectoryNotFoundException || ex is NotSupportedException || ex is ArgumentException || ex is SecurityException || ex is IOException ) { throw new LoggerException("Failed to create log file: " + ex.Message); } // Unexpected failure throw; } // For brevity, we'll only register for certain event types. Loggers can also // register to handle TargetStarted/Finished and other events. if (eventSource == null) return; eventSource.ProjectStarted += eventSource_ProjectStarted; eventSource.MessageRaised += eventSource_MessageRaised; eventSource.WarningRaised += eventSource_WarningRaised; eventSource.ErrorRaised += eventSource_ErrorRaised; eventSource.ProjectFinished += eventSource_ProjectFinished; } void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) { // BuildErrorEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters string line = String.Format(": ERROR {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber); Errors.Add(line); WriteLineWithSenderAndMessage(line, e); } void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) { // BuildWarningEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters string line = String.Format(": Warning {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber); Warnings.Add(line); WriteLineWithSenderAndMessage(line, e); } void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) { // BuildMessageEventArgs adds Importance to BuildEventArgs // Let take account of the verbosity setting we've been passed in deciding whether to log the message if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)) || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)) || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) ) { Messages.Add(e.Message); WriteLineWithSenderAndMessage(String.Empty, e); } } void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) { int idx = IndexOfProjectTimeStamp(e.ProjectFile); DateTime outputfiledatetime = DateTime.MinValue; StudioProject proj = new StudioProject(e.ProjectFile); FileInfo outputFile; if (File.Exists(e.ProjectFile)) { outputFile = new FileInfo(proj.OutputFile()); outputfiledatetime = outputFile.LastWriteTime; } //keep track of the mod date/time of the project output. //if the mod date changes as a result of the build, then that means the project changed. //this is necessary because the MSBuild engine doesn't tell us which projects were actually recompiled during a "build". //see also: http://stackoverflow.com/questions/34903800 ProjectOutputTimeStamp p = new ProjectOutputTimeStamp() { OutputDateTime_BeforeBuild = outputfiledatetime, ProjectName = e.ProjectFile, OutputDateTime_AfterBuild = DateTime.MinValue }; if (-1 == idx) outputs.Add(p); else outputs[idx] = p; WriteLine(String.Empty, e); indent++; } private int IndexOfProjectTimeStamp(string projectname) { for (int i = 0; i < outputs.Count; ++i) if (outputs[i].ProjectName.ToUpper() == projectname.ToUpper()) return i; return -1; } void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) { int idx = IndexOfProjectTimeStamp(e.ProjectFile); DateTime outputfiledatetime = DateTime.MinValue; StudioProject proj = new StudioProject(e.ProjectFile); FileInfo outputFile; if (File.Exists(e.ProjectFile)) { outputFile = new FileInfo(proj.OutputFile()); outputfiledatetime = outputFile.LastWriteTime; } //keep track of the mod date/time of the project output. //if the mod date changes as a result of the build, then that means the project changed. //this is necessary because the MSBuild engine doesn't tell us which projects were actually recompiled during a "build". //see also: http://stackoverflow.com/questions/34903800 ProjectOutputTimeStamp p = outputs[idx]; p.OutputDateTime_AfterBuild = outputfiledatetime; if (-1 < idx) outputs[idx] = p; indent--; WriteLine(String.Empty, e); } public List<string> RecompiledProjects() { //let callers ask "which projects were actually recompiled" and get a list of VBPROJ files. List<string> result = new List<string>(); foreach (ProjectOutputTimeStamp p in outputs) { if(p.OutputDateTime_AfterBuild>p.OutputDateTime_BeforeBuild) result.Add(p.ProjectName); } return result; } /// <summary> /// Write a line to the log, adding the SenderName and Message /// (these parameters are on all MSBuild event argument objects) /// </summary> private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) { if (0 == String.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) { // Well, if the sender name is MSBuild, let leave it out for prettiness WriteLine(line, e); } else { WriteLine(e.SenderName + ": " + line, e); } } /// <summary> /// Just write a line to the log /// </summary> private void WriteLine(string line, BuildEventArgs e) { for (int i = indent; i > 0; i--) { streamWriter.Write("\t"); } streamWriter.WriteLine(line + e.Message); } /// <summary> /// Shutdown() is guaranteed to be called by MSBuild at the end of the build, after all /// events have been raised. /// </summary> public override void Shutdown() { // Done logging, let go of the file streamWriter.Close(); } private StreamWriter streamWriter; private int indent; } } 

Note that the "StudioProject" class is the one I wrote. I donโ€™t want to publish all this because it has a lot of things that make assumptions that would be true only in our local code base. However, the corresponding method ("OutputFile") is here. It does a rather dumb scan through the project file itself to determine the output EXE or DLL.

 public string OutputFile() { if (_ProjectFile == null) return string.Empty; string result = string.Empty; StreamReader reader = new StreamReader(_ProjectFile); string projFolder = new DirectoryInfo(_ProjectFile).Parent?.FullName; bool insideCurrentConfig = false; string configuration = string.Empty; string assemblyName = string.Empty; string outputPath = string.Empty; bool isExe = false; do { string currentLine = reader.ReadLine(); if (currentLine == null) continue; if ((configuration == string.Empty) && (currentLine.Contains("<Configuration"))) configuration = currentLine.Split('>')[1].Split('<')[0]; if (!insideCurrentConfig && !isExe && currentLine.Contains("WinExe")) isExe = true; if ((assemblyName == string.Empty) && (currentLine.Contains("<AssemblyName>"))) assemblyName = currentLine.Split('>')[1].Split('<')[0]; if (configuration != string.Empty && currentLine.Contains("<PropertyGroup") && currentLine.Contains(configuration)) insideCurrentConfig = true; if (insideCurrentConfig && currentLine.Contains("<OutputPath>")) outputPath = currentLine.Split('>')[1].Split('<')[0]; if ((outputPath != null) && (assemblyName != null)) result = projFolder + "\\" + outputPath + assemblyName + (isExe?".exe":".dll"); if (insideCurrentConfig && currentLine.Contains("</PropertyGroup>")) return result; //if we were in the current config, and that config is ending, then we are done. } while (!reader.EndOfStream); return result; } 
+1
source

All Articles