2016-01-20 44 views
6

我有代碼將構建(不是re構建)從C#的整個解決方案。如果我從C#代碼構建解決方案,如何知道哪些應用程序實際構建?

標準版本只會編譯實際已更改的項目。

構建完成後,我想知道哪些項目實際構建。

我曾嘗試:

1 - 尋找從BuidlResult構建之後的改變/未改變的值(或類似的)完成

2 - 附加一個custom logger和誘捕每一個事件,然後鑽研的消息,看看是否有任何改變和不變的項目之間的差異

我真的很驚訝,這樣一個基本的信息不容易獲得。例如,Logger的ProjectFinished事件的ProjectFinishedEventArgs參數將包含布爾值或狀態值似乎合乎邏輯。但如果它在那裏,那麼我忽略了它。

有誰知道如何判斷一個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); 
} 
+0

也許你可以嘗試尋找像https://github.com/nagits/BuildVision這樣的東西,看看它是如何完成的。 –

+0

@OmarElabd我對這個項目並不熟悉;但我正在檢查它。看起來很有趣,不管它是否有助於解決這個特定的問題。 – JosephStyons

+0

@OmarElabd只是跟進;我喜歡這個項目,我確信我可以從中學到很多東西。但是,我認爲這不會幫助我解決這個特殊問題。雖然它可以讓你啓動一個構建或重建,但我沒有看到任何它似乎意識到構建是否確實需要重新編譯的地方。如果我還沒注意到,我會繼續尋找。 – JosephStyons

回答

1

我修改了我的記錄器類給我我想要的,但我仍然希望有一個更「原生」的解決方案。在這裏,如果有其他人發現它有用。

總體思路是在項目構建之前記錄文件修改時間,然後再記錄下來。如果更改,則假定項目已重新編譯。

我開始與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; 
} 
相關問題