我有代码将从 C# 构建(而不是重新构建)整个解决方案。
标准构建只会编译实际更改的项目。
构建完成后,我想知道实际构建了哪些项目。
我试过:
1 - 在构建完成后从 BuidlResult 中查找更改/未更改的值(或类似值)
2 - 附加自定义记录器并捕获每个事件,然后仔细查看消息以查看更改和未更改的项目之间是否存在任何差异
我真的很惊讶这样一个基本信息不容易获得。 例如,记录器的 ProjectFinished 事件的 ProjectFinishEventArgs 参数包含布尔值或状态值似乎是合乎逻辑的。 但如果它在那里,那么我就忽略了它。
有谁知道如何判断 msbuild 的产品是否被重新编译? 我讨厌求助于检查输出二进制文件的时间戳,但也许这就是我必须做的。
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);
}
我修改了我的 Logger 类以提供我想要的东西,但我仍然希望有一个更"原生"的解决方案。 在这里,以防其他人发现它有用。
一般的想法是在项目生成之前记下文件的修改时间,并在之后再次记下它。 如果它已更改,则假定项目已重新编译。
我从 MSDN 示例开始,并修改了以下方法:
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"类是我编写的类。 我不想发布整个事情,因为它有很多东西可以做出假设,这些假设只有在我们的本地代码库中才是正确的。 但是,相关方法("输出文件")在这里。 它对项目文件本身进行非常愚蠢的扫描,以找出输出的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;
}