//------------------------------------------------------------------------------ // // Copyright (c) Company. All rights reserved. // //------------------------------------------------------------------------------ using System; using System.ComponentModel.Design; using System.Globalization; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using System.Collections.Generic; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio; using System.Linq; using TestStack.White.UIItems.Finders; using TestStack.White.InputDevices; using TestStack.White.UIItems; using VSLangProj; using System.Runtime.InteropServices; using TestStack.White.WindowsAPI; using System.IO; namespace Tango.BuildExtensions { /// /// Command handler /// internal sealed class TangoBuildCommand { private DTE2 _dte; private IList _projects; private SelectForm _form; private TestStack.White.Application _application; private TestStack.White.UIItems.WindowItems.Window _window; private System.Diagnostics.Process _vsProcess; private const String dalProjectName = "Tango.DAL.Remote"; private const String edmxModelName = "RemoteADO.edmx"; private const String observablesGeneratorProjectName = "Tango.DBObservablesGenerator.CLI"; private const String observablesProjectName = "Tango.BL"; private const String pmrGeneratorProjectName = "Tango.PMRGenerator.CLI"; private const String pmrProjectName = "Tango.PMR"; private const String protoCliProjectName = "Tango.Protobuf.CLI"; #region Redundant /// /// Command ID. /// public const int CommandId = 0x0100; /// /// Command menu group (command set GUID). /// public static readonly Guid CommandSet = new Guid("c03a7b01-8109-4ec5-8f90-858bed027e5d"); /// /// VS Package that provides this command, not null. /// private readonly Package package; /// /// Initializes a new instance of the class. /// Adds our command handlers for menu (commands must exist in the command table file) /// /// Owner package, not null. private TangoBuildCommand(Package package) { if (package == null) { throw new ArgumentNullException("package"); } this.package = package; OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (commandService != null) { var menuCommandID = new CommandID(CommandSet, CommandId); var menuItem = new MenuCommand(this.MenuItemCallback, menuCommandID); commandService.AddCommand(menuItem); } } /// /// Gets the instance of the command. /// public static TangoBuildCommand Instance { get; private set; } /// /// Gets the service provider from the owner package. /// private IServiceProvider ServiceProvider { get { return this.package; } } /// /// Initializes the singleton instance of the command. /// /// Owner package, not null. public static void Initialize(Package package) { Instance = new TangoBuildCommand(package); } /// /// This function is the callback used to execute the command when the menu item is clicked. /// See the constructor to see how the menu item is associated with this function using /// OleMenuCommandService service and MenuCommand class. /// /// Event sender. /// Event args. private void MenuItemCallback(object sender, EventArgs e) { Start(); } #endregion #region Main private void Start() { _form = null; BuildForm buildForm = new BuildForm(); if (buildForm.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; System.Threading.Tasks.Task.Factory.StartNew(() => { _projects = Projects().ToList(); String vsWindowTitle = _dte.DTE.MainWindow.Caption; _vsProcess = System.Diagnostics.Process.GetProcesses().ToList().SingleOrDefault(x => x.MainWindowTitle == vsWindowTitle); _application = TestStack.White.Application.Attach(_vsProcess); _window = _application.GetWindow(vsWindowTitle); OpenProgressForm(); try { if (buildForm.UpdateDataBaseEntities) { UpdateDatabaseEntities(); } if (buildForm.GenerateAndBuildObservables) { GenerateAndBuildObservables(); } if (buildForm.GenerateAutoPmrMessages) { GenerateAutoPMRMessages(); } if (buildForm.UpdateAndBuildPmrMessages) { UpdateAndBuildPmrMessages(); } if (buildForm.BuildSolution) { BuildSolution(); } SetStatusText("Done!"); Wait(1000); CloseProgressForm(); } catch (Exception ex) { CloseProgressForm(); ShowMessage(ex.Message); } }); } private void UpdateDatabaseEntities() { var project = _projects.SingleOrDefault(x => x.Name == dalProjectName); if (project == null) { throw new NullReferenceException("Could not find the Tango solution!"); } var projectItems = project.ProjectItems.OfType().ToList(); SetStatusText("Locating " + edmxModelName + " scheme..."); var items = GetProjectItemsDeep(project); var edmx = GetProjectItemsDeep(project).SingleOrDefault(x => x.Name == edmxModelName); if (edmx == null) { throw new NullReferenceException("Could not locate " + edmxModelName + "!"); } SetStatusText("Expanding diagram..."); edmx.ExpandView(); SetStatusText("Opening edmx diagram window..."); Window win = edmx.Open(EnvDTE.Constants.vsViewKindPrimary); win.Visible = true; SetStatusText("Waiting for edmx diagram window..."); _window.WaitTill(() => _window.Get(SearchCriteria.ByText(edmxModelName + " [Diagram1]")) != null); SetStatusText("Cleaning up edmx scheme..."); _dte.MainWindow.Activate(); _dte.ExecuteCommand("Edit.SelectAll"); Keyboard.Instance.PressSpecialKey(KeyboardInput.SpecialKeys.DELETE); WaitForWindowOpen("Delete Unmapped Tables and Views").PressKey(KeyboardInput.SpecialKeys.RETURN); WaitForWindowClose("Delete Unmapped Tables and Views"); _window.WaitWhileBusy(); SetStatusText("Reinitializing edmx scheme..."); var window = WindowInfo.GetWindow(edmxModelName + " [Diagram1]*"); window.SetActive(); Keyboard.Instance.HoldKey(KeyboardInput.SpecialKeys.SHIFT); Keyboard.Instance.PressSpecialKey(KeyboardInput.SpecialKeys.F10); Keyboard.Instance.LeaveAllKeys(); Wait(100); for (int i = 0; i < 7; i++) { Keyboard.Instance.PressSpecialKey(KeyboardInput.SpecialKeys.DOWN); Wait(10); } Keyboard.Instance.PressSpecialKey(KeyboardInput.SpecialKeys.RETURN); var updateWindow = WaitForWindowOpen("Update Wizard"); _window.WaitWhileBusy(); Wait(1000); updateWindow.PressKey(KeyboardInput.SpecialKeys.SPACE); Wait(50); updateWindow.PressKey(KeyboardInput.SpecialKeys.RETURN); SetStatusText("Generating edmx scheme..."); WaitForWindowClose("Update Wizard"); _window.WaitWhileBusy(); SetStatusText("Saving changes..."); win.Close(vsSaveChanges.vsSaveChangesYes); _window.WaitWhileBusy(); foreach (var template in edmx.ProjectItems.OfType().Where(x => x.Name.EndsWith(".tt"))) { SetStatusText("Running custom tool for " + template.Name + "..."); (template.Object as VSProjectItem).RunCustomTool(); _window.WaitWhileBusy(); } _window.WaitWhileBusy(); SetStatusText("Building project " + dalProjectName + "..."); _dte.Solution.SolutionBuild.BuildProject("Debug", project.FullName, true); if (_dte.Solution.SolutionBuild.LastBuildInfo > 0) { throw new ExternalException(dalProjectName + " failed to build!"); } } private void GenerateAndBuildObservables() { var project = _projects.SingleOrDefault(x => x.Name == observablesGeneratorProjectName); if (project == null) { throw new NullReferenceException("Could not locate project " + observablesGeneratorProjectName); } SetStatusText("Building project " + observablesGeneratorProjectName + "..."); _dte.Solution.SolutionBuild.BuildProject("Debug", project.FullName, true); _dte.Solution.Properties.Item("StartupProject").Value = observablesGeneratorProjectName; SetStatusText("Executing observables generator..."); _dte.ExecuteCommand("Debug.Start"); WaitForWindowOpen("Tango Observables Generator"); WaitForWindowClose("Tango Observables Generator"); var observablesProject = _projects.SingleOrDefault(x => x.Name == observablesProjectName); if (observablesProject == null) { throw new NullReferenceException("Could not locate project " + observablesProjectName); } SetStatusText("Updating " + observablesProjectName + "..."); foreach (var file in Directory.GetFiles(Path.GetDirectoryName(observablesProject.FileName), "*.cs", SearchOption.AllDirectories)) { String parentFolderName = Path.GetFileName(Path.GetDirectoryName(file)); if (parentFolderName != "Debug" && parentFolderName != "Release" && parentFolderName != "obj") { SetStatusText("Adding/Updating file " + Path.GetFileName(file) + "..."); observablesProject.ProjectItems.AddFromFile(file); Wait(10); } } SetStatusText("Building project " + observablesProjectName + "..."); _dte.Solution.SolutionBuild.BuildProject("Debug", observablesProject.FullName, true); if (_dte.Solution.SolutionBuild.LastBuildInfo > 0) { throw new ExternalException(observablesProjectName + " failed to build!"); } } private void GenerateAutoPMRMessages() { var project = _projects.SingleOrDefault(x => x.Name == pmrGeneratorProjectName); if (project == null) { throw new NullReferenceException("Could not locate project " + pmrGeneratorProjectName); } SetStatusText("Building project " + pmrGeneratorProjectName + "..."); _dte.Solution.SolutionBuild.BuildProject("Debug", project.FullName, true); _dte.Solution.Properties.Item("StartupProject").Value = pmrGeneratorProjectName; SetStatusText("Executing PMR generator..."); _dte.ExecuteCommand("Debug.Start"); WaitForWindowOpen("Tango PMR Generator"); WaitForWindowClose("Tango PMR Generator"); } private void UpdateAndBuildPmrMessages() { var protoProject = _projects.SingleOrDefault(x => x.Name == protoCliProjectName); if (protoProject == null) { throw new NullReferenceException("Could not locate project " + protoCliProjectName); } SetStatusText("Building project " + protoCliProjectName + "..."); _dte.Solution.SolutionBuild.BuildProject("Debug", protoProject.FullName, true); if (_dte.Solution.SolutionBuild.LastBuildInfo > 0) { throw new ExternalException(protoCliProjectName + " failed to build!"); } _dte.Solution.Properties.Item("StartupProject").Value = protoCliProjectName; SetStatusText("Executing Tango Proto Compiler..."); _dte.ExecuteCommand("Debug.Start"); WaitForWindowOpen("Tango Protobuf Compiler"); WaitForWindowClose("Tango Protobuf Compiler"); var project = _projects.SingleOrDefault(x => x.Name == pmrProjectName); if (project == null) { throw new NullReferenceException("Could not locate project " + pmrProjectName); } SetStatusText("Updating " + pmrProjectName + "..."); foreach (var file in Directory.GetFiles(Path.GetDirectoryName(project.FileName), "*.cs", SearchOption.AllDirectories)) { String parentFolderName = Path.GetFileName(Path.GetDirectoryName(file)); if (parentFolderName != "Debug" && parentFolderName != "Release" && parentFolderName != "obj") { SetStatusText("Adding/Updating file " + Path.GetFileName(file) + "..."); project.ProjectItems.AddFromFile(file); Wait(10); } } SetStatusText("Building project " + pmrProjectName + "..."); _dte.Solution.SolutionBuild.BuildProject("Debug", project.FullName, true); if (_dte.Solution.SolutionBuild.LastBuildInfo > 0) { throw new ExternalException(pmrProjectName + " failed to build!"); } } private void BuildSolution() { SetStatusText("Building solution..."); _dte.Solution.SolutionBuild.Build(true); if (_dte.Solution.SolutionBuild.LastBuildInfo > 0) { throw new ExternalException("Error building solution!"); } } #endregion #region Solution & Projects public DTE2 GetActiveIDE() { // Get an instance of currently running Visual Studio IDE. DTE2 dte2 = Package.GetGlobalService(typeof(DTE)) as DTE2; _dte = dte2; return dte2; } public IList Projects() { Projects projects = GetActiveIDE().Solution.Projects; List list = new List(); var item = projects.GetEnumerator(); while (item.MoveNext()) { var project = item.Current as Project; if (project == null) { continue; } if (project.Kind == ProjectKinds.vsProjectKindSolutionFolder) { list.AddRange(GetSolutionFolderProjects(project)); } else { list.Add(project); } } return list; } private IEnumerable GetSolutionFolderProjects(Project solutionFolder) { List list = new List(); for (var i = 1; i <= solutionFolder.ProjectItems.Count; i++) { var subProject = solutionFolder.ProjectItems.Item(i).SubProject; if (subProject == null) { continue; } // If this is another solution folder, do a recursive call, otherwise add if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder) { list.AddRange(GetSolutionFolderProjects(subProject)); } else { list.Add(subProject); } } return list; } private List GetProjectItemsDeep(Project project) { List results = new List(); FillProjectItems(project.ProjectItems.OfType().ToList(), results); return results; } private void FillProjectItems(List rootItems, List results) { foreach (var item in rootItems) { results.Add(item); if (item.ProjectItems.Count > 0) { FillProjectItems(item.ProjectItems.OfType().ToList(), results); } } } #endregion #region Notifications private void WriteToConsole(String text) { // Get the output window var outputWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow; // Ensure that the desired pane is visible var paneGuid = Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.GeneralPane_guid; IVsOutputWindowPane pane; outputWindow.CreatePane(paneGuid, "General", 1, 0); outputWindow.GetPane(paneGuid, out pane); // Output the message pane.OutputString(text + Environment.NewLine); } private void ShowMessage(String text) { // Show a message box to prove we were here VsShellUtilities.ShowMessageBox( ServiceProvider, text, "Tango Initializer", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); } #endregion #region Windows API private WindowInfo WaitForWindowOpen(String title) { WindowInfo window = null; do { window = WindowInfo.GetAllWindows().SelectMany(x => x.Children).FirstOrDefault(x => x.Caption == title); } while (window == null); return window; } private void WaitForWindowClose(String title) { while (WindowInfo.GetAllWindows().SelectMany(x => x.Children).ToList().Exists(x => x.Caption == title)) { System.Threading.Thread.Sleep(100); } } #endregion #region Threading private void Wait(int milli) { System.Threading.Thread.Sleep(milli); } #endregion #region Status Form private void OpenProgressForm() { System.Threading.Thread thread = new System.Threading.Thread(() => { _form = new SelectForm(); var handle = _form.Handle; _form.ShowDialog(); }); thread.SetApartmentState(System.Threading.ApartmentState.STA); thread.Start(); while (_form == null || !_form.IsHandleCreated) { } } private void SetStatusText(String text) { _form.Invoke(new Action(() => { _form.SetStatus(text); })); } private void CloseProgressForm() { _form.Invoke(new Action(() => { _form.Close(); _form = null; })); } #endregion } }