Skip to content
This repository was archived by the owner on Jan 9, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion BuildMonitor/BuildMonitor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
<Compile Include="Domain\Solution.cs" />
<Compile Include="Domain\SolutionBuild.cs" />
<Compile Include="Domain\SolutionBuildData.cs" />
<Compile Include="IntExtensions.cs" />
<Compile Include="LocalData\AnalyseBuildTimes.cs" />
<Compile Include="LocalData\BuildTimes.cs" />
<Compile Include="LocalData\DataAdjuster.cs" />
Expand Down
13 changes: 0 additions & 13 deletions BuildMonitor/IntExtensions.cs

This file was deleted.

2 changes: 1 addition & 1 deletion BuildMonitorPackage/AnalyseBuildTimesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static void Initialize(Package package)
/// <param name="e">Event args.</param>
private void MenuItemCallback(object sender, EventArgs e)
{
var form = new AnalyseBuildTimes(new BuildMonitor.LocalData.AnalyseBuildTimes().Calculate(File.ReadAllText(Settings.RepositoryFilename)).SolutionMonthTable());
var form = new AnalyseBuildTimes(new BuildMonitor.LocalData.AnalyseBuildTimes().Calculate(File.ReadAllText(Settings.Instance.RepositoryPath)).SolutionMonthTable());

form.ShowDialog();
}
Expand Down
72 changes: 26 additions & 46 deletions BuildMonitorPackage/BuildMonitorPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,50 @@
using System.Data.SqlClient;
using System.Data;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell.Settings;

namespace BuildMonitorPackage
{
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[Guid(GuidList.guidBuildMonitorPackagePkgString)]
[PackageRegistration(UseManagedResourcesOnly = true)]
[ProvideAutoLoad("{f1536ef8-92ec-443c-9ed7-fdadf150da82}")]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideOptionPage(typeof(SettingsPage), "Build Monitor", "General", 0, 0, true)]
sealed class BuildMonitorPackage : Package, IVsUpdateSolutionEvents2
{
private DTE dte;
private readonly Monitor monitor;
private readonly DataAdjusterWithLogging dataAdjuster;
private Monitor monitor;
private DataAdjusterWithLogging dataAdjuster;
private BuildMonitor.Domain.Solution solution;

private IVsSolutionBuildManager2 sbm;
private uint updateSolutionEventsCookie;
private OutputWindowPane outputWindowPane;
private SolutionEvents events;
private IVsSolution2 vsSolution;
private OutputWindowWrapper output;

public BuildMonitorPackage()
protected override void Initialize()
{
Settings.CreateRepositoryPathIfNotExist();
base.Initialize();

output = new OutputWindowWrapper(this);

SettingsManager settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider);
WritableSettingsStore settingsStore = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings);
Settings.Instance = new Settings(settingsStore);

var factory = new BuildFactory();
var repository = new BuildRepository(Settings.RepositoryFilename);
var repository = new BuildRepository(Settings.Instance.RepositoryPath);

monitor = new Monitor(factory, repository);
dataAdjuster = new DataAdjusterWithLogging(repository, PrintLine);
}

protected override void Initialize()
{
base.Initialize();
dataAdjuster = new DataAdjusterWithLogging(repository, output.WriteLine);

//if invalid data, adjust it
dataAdjuster.Adjust();


// Get solution build manager
sbm = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2;
if (sbm != null)
Expand All @@ -63,18 +67,18 @@ protected override void Initialize()
events.Opened += Solution_Opened;
GetDTE().Events.BuildEvents.OnBuildBegin += Build_Begin;

PrintLine("Build monitor initialized");
PrintLine("Path to persist data: {0}", Settings.RepositoryFilename);
output.WriteLine("Build monitor initialized");
output.WriteLine("Path to persist data: {0}", Settings.Instance.RepositoryPath);

monitor.SolutionBuildFinished = b =>
{
Print("[{0}] Time Elapsed: {1} \t\t", b.SessionBuildCount, b.SolutionBuildTime.ToTime());
PrintLine("Session build time: {0}\n", b.SessionMillisecondsElapsed.ToTime());
PrintLine("Rebuild All: {0}\n", b.SolutionBuild.IsRebuildAll);
output.Write("[{0}] Time Elapsed: {1} \t\t", b.SessionBuildCount, b.SolutionBuildTime.ToTime());
output.WriteLine("Session build time: {0}\n", b.SessionMillisecondsElapsed.ToTime());
output.WriteLine("Rebuild All: {0}\n", b.SolutionBuild.IsRebuildAll);
//System.Threading.Tasks.Task.Factory.StartNew(() => SaveToDatabase(b));
};

monitor.ProjectBuildFinished = b => PrintLine(" - {0}\t-- {1} --", b.MillisecondsElapsed.ToTime(), b.ProjectName);
monitor.ProjectBuildFinished = b => output.WriteLine(" - {0}\t-- {1} --", b.MillisecondsElapsed.ToTime(), b.ProjectName);
AnalyseBuildTimesCommand.Initialize(this);
}

Expand All @@ -101,34 +105,10 @@ protected override void Initialize()
private void Solution_Opened()
{
solution = new BuildMonitor.Domain.Solution { Name = GetSolutionName() };
PrintLine("\nSolution loaded: \t{0}", solution.Name);
PrintLine("{0}", 60.Times("-"));
}

#region Print to output window pane

private OutputWindowPane GetOutputWindowPane()
{
if (outputWindowPane == null)
{
var outputWindow = (OutputWindow)GetDTE().Windows.Item(EnvDTEConstants.vsWindowKindOutput).Object;
outputWindowPane = outputWindow.OutputWindowPanes.Add("Build monitor");
}
return outputWindowPane;
}

private void Print(string format, params object[] args)
{
GetOutputWindowPane().OutputString(string.Format(format, args));
}

private void PrintLine(string format, params object[] args)
{
Print(format + Environment.NewLine, args);
output.WriteLine("\nSolution loaded: \t{0}", solution.Name);
output.WriteLine(new string('-', 60));
}

#endregion

#region Get objects from vs

private DTE GetDTE()
Expand Down
6 changes: 5 additions & 1 deletion BuildMonitorPackage/BuildMonitorPackage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.CSharp" />
Expand Down Expand Up @@ -182,6 +182,7 @@
<Compile Include="DataAdjusterWithLogging.cs" />
<Compile Include="EnvDTEConstants.cs" />
<Compile Include="Guids.cs" />
<Compile Include="OutputWindowWrapper.cs" />
<Compile Include="Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
Expand All @@ -191,6 +192,9 @@
<Compile Include="BuildMonitorPackage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings.cs" />
<Compile Include="SettingsPage.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources.resx">
Expand Down
45 changes: 45 additions & 0 deletions BuildMonitorPackage/OutputWindowWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using EnvDTE;
using Microsoft.VisualStudio.Shell.Interop;

namespace BuildMonitorPackage {
internal class OutputWindowWrapper {
private readonly OutputWindowPane outputWindowPane;

public OutputWindowWrapper(IServiceProvider serviceContainer)
{
var dte = serviceContainer.GetService(typeof(SDTE)) as DTE;
var outputWindow = (OutputWindow)dte.Windows.Item(EnvDTEConstants.vsWindowKindOutput).Object;

foreach (OutputWindowPane pane in outputWindow.OutputWindowPanes)
{
if (pane.Name == "Build monitor")
{
outputWindowPane = pane;
}
}

if (outputWindowPane == null)
{
outputWindowPane = outputWindow.OutputWindowPanes.Add("Build monitor");
}
}

public void Write(string text) {
outputWindowPane.OutputString(text);
}

public void Write(string format, params object[] args) {
outputWindowPane.OutputString(string.Format(format, args));
}

public void WriteLine(string text) {
Write(text + Environment.NewLine);
}

public void WriteLine(string format, params object[] args)
{
Write(format + Environment.NewLine, args);
}
}
}
115 changes: 97 additions & 18 deletions BuildMonitorPackage/Settings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.VisualStudio.Settings;

// ReSharper disable InconsistentNaming
namespace BuildMonitorPackage
Expand All @@ -9,37 +11,114 @@ public static class OptionKey
public const string SolutionId = "bm_solution_id";
}

public static class Settings
{
public static string RepositoryFilename
public class Settings {

private static readonly string DefaultRepositoryPath = Path.Combine("%ApplicationData%", ApplicationFolderName, JsonFileName);

private string rawRepositoryPath = DefaultRepositoryPath;

private const string ApplicationFolderName = "Build Monitor";

private const string JsonFileName = "buildtimes.json";

private readonly WritableSettingsStore settingsStore;

private const string CollectionPath = "BuildMonitor";

public static Settings Instance { get; set; }

public Settings(WritableSettingsStore settingsStore) {
this.settingsStore = settingsStore;
LoadSettings();
CreateApplicationFolderIfNotExist();
}

public string RawRepositoryPath
{
get =>
string.IsNullOrWhiteSpace(BuildMonitorRepositoryFilename)
? DefaultRepositoryFilename
: BuildMonitorRepositoryFilename;
get => rawRepositoryPath;
set
{
if (rawRepositoryPath != value)
{
rawRepositoryPath = value;
SaveSettings();
}
}
}

public static void CreateRepositoryPathIfNotExist()
public string RepositoryPath => ExpandPath(rawRepositoryPath);

private void CreateApplicationFolderIfNotExist()
{
if (!Directory.Exists(RepositoryPath))
string folder = Path.GetDirectoryName(RepositoryPath);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(RepositoryPath);
Directory.CreateDirectory(folder);
}
if (!File.Exists(RepositoryFilename))
if (!File.Exists(RepositoryPath))
{
File.Create(RepositoryFilename).Dispose();
using (var f = File.Create(RepositoryPath)){}
}
}

static string RepositoryPath =>
Path.GetDirectoryName(RepositoryFilename);
private void LoadSettings() {
try
{
RawRepositoryPath = settingsStore.GetString(CollectionPath, "RepositoryPath", DefaultRepositoryPath);
}
catch (Exception ex)
{
Debug.Fail(ex.Message);
}
}

static string BuildMonitorRepositoryFilename =>
Environment.GetEnvironmentVariable("BuildMonitorRepositoryFilename");
private void SaveSettings() {
try
{
if (!settingsStore.CollectionExists(CollectionPath))
{
settingsStore.CreateCollection(CollectionPath);
}

static string DefaultRepositoryFilename =>
$"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\Build Monitor\\buildtimes.json";
settingsStore.SetString(CollectionPath, "RepositoryPath", rawRepositoryPath);
}
catch (Exception ex)
{
Debug.Fail(ex.Message);
}
}

/// <summary>
/// Expands a path possibly starting with a <see cref="Environment.SpecialFolder"/>
/// to a full path.
/// </summary>
private static string ExpandPath(string path)
{
if (!path.StartsWith("%"))
{
return path;
}

int splitIndex = path.IndexOf("%", 1, StringComparison.InvariantCulture) + 1;
string maybeSpecialFolder = path.Substring(0, splitIndex).Trim('%');
string rest = path.Substring(splitIndex);
while (rest.StartsWith(Path.DirectorySeparatorChar.ToString()))
{
// The remaining path cannot start with a rooted path as that
// will "break" Path.Combine.
rest = rest.Substring(1);
}

foreach (var @enum in Enum.GetNames(typeof(Environment.SpecialFolder)))
{
if (@enum.Equals(maybeSpecialFolder, StringComparison.InvariantCultureIgnoreCase))
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), rest);
}
}

return path;
}
}
}
// ReSharper restore InconsistentNaming
Loading