我修改了我的記錄器類給我我想要的,但我仍然希望有一個更「原生」的解決方案。在這裏,如果有其他人發現它有用。
總體思路是在項目構建之前記錄文件修改時間,然後再記錄下來。如果更改,則假定項目已重新編譯。
我開始與MSDN example,並修改這些方法:
eventSource_ProjectStarted
eventSource_ProjectFinished
如果你從那裏開始,那麼剩下的應該很清楚。如果有人有問題,我很樂意回答。
更好的是,如果有人可以來這裏回答這個問題,並說「你爲什麼不做X」,那麼我很樂意聽到「X」是什麼。
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's 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's 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;
}
}
請注意,「StudioProject」類是我編寫的類。我不想發佈整件事情,因爲它有很多東西使得我們的本地代碼庫中只有真實的假設。但是,相關的方法(「OutputFile」)就在這裏。它通過項目文件本身進行非常愚蠢的掃描以找出輸出EXE或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;
}
也許你可以嘗試尋找像https://github.com/nagits/BuildVision這樣的東西,看看它是如何完成的。 –
@OmarElabd我對這個項目並不熟悉;但我正在檢查它。看起來很有趣,不管它是否有助於解決這個特定的問題。 – JosephStyons
@OmarElabd只是跟進;我喜歡這個項目,我確信我可以從中學到很多東西。但是,我認爲這不會幫助我解決這個特殊問題。雖然它可以讓你啓動一個構建或重建,但我沒有看到任何它似乎意識到構建是否確實需要重新編譯的地方。如果我還沒注意到,我會繼續尋找。 – JosephStyons