/// <summary> /// Initializes the package right after it's been "sited" into the fully-initialized Visual Studio IDE. /// </summary> protected override void Initialize() { Logging.WriteLine("Initializing UnrealVS extension..."); // Grab the MenuCommandService MenuCommandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; // Get access to Visual Studio's DTE object. This object has various hooks into the Visual Studio // shell that are useful for writing extensions. DTE = (DTE)GetGlobalService(typeof(DTE)); Logging.WriteLine("DTE version " + DTE.Version); // Get selection manager and register to receive events SelectionManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection; SelectionManager.AdviseSelectionEvents(this, out SelectionEventsHandle); // Get solution and register to receive events SolutionManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) as IVsSolution2; UpdateUnrealLoadedStatus(); SolutionManager.AdviseSolutionEvents(this, out SolutionEventsHandle); // Grab the solution build manager. We need this in order to change certain things about the Visual // Studio environment, like what the active startup project is // Get solution build manager SolutionBuildManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2; SolutionBuildManager.AdviseUpdateSolutionEvents(this, out UpdateSolutionEventsHandle); // Create our command-line editor CommandLineEditor = new CommandLineEditor(); // Create our startup project selector StartupProjectSelector = new StartupProjectSelector(); // Create 'BuildStartupProject' instance BuildStartupProject = new BuildStartupProject(); // Create 'CompileSingleFile' instance CompileSingleFile = new CompileSingleFile(); // Create 'GenerateProjectFiles' tools GenerateProjectFiles = new GenerateProjectFiles(); // Create Batch Builder tools BatchBuilder = new BatchBuilder(); // Create the project menu quick builder QuickBuilder = new QuickBuild(); // Call parent implementation base.Initialize(); if (DTE.Solution.IsOpen) { StartTicker(); } }
private void BuildJobSetsCollectionOnCollectionChanged(object sender, EventArgs eventArgs) { Logging.WriteLine("BuildJobSet Collection changed"); if (SetCombo.SelectedItem == null) { SetCombo.SelectedItem = _state._BuildJobSetsCollection[0]; } }
/// Called by combo control to query the text to display or to apply newly-entered text private void ComboHandler(object Sender, EventArgs Args) { try { var OleArgs = (OleMenuCmdEventArgs)Args; string InString = OleArgs.InValue as string; if (InString != null) { // New text set on the combo - set the command line property DesiredCommandLine = null; CommitCommandLineText(InString); } else if (OleArgs.OutValue != IntPtr.Zero) { string EditingString = null; if (OleArgs.InValue != null) { object[] InArray = OleArgs.InValue as object[]; if (InArray != null && 0 < InArray.Length) { EditingString = InArray.Last() as string; } } string TextToDisplay = string.Empty; if (EditingString != null) { // The control wants to put EditingString in the box TextToDisplay = DesiredCommandLine = EditingString; } else { // This is always hit at the end of interaction with the combo if (DesiredCommandLine != null) { TextToDisplay = DesiredCommandLine; DesiredCommandLine = null; CommitCommandLineText(TextToDisplay); } else { TextToDisplay = MakeCommandLineComboText(); } } Marshal.GetNativeVariantForObject(TextToDisplay, OleArgs.OutValue); } } catch (Exception ex) { Exception AppEx = new ApplicationException("CommandLineEditor threw an exception in ComboHandler()", ex); Logging.WriteLine(AppEx.ToString()); throw AppEx; } }
/// <summary> /// Returns all the .uprojects found under the solution root folder. /// </summary> public static IDictionary <string, string> GetUProjects() { var Folder = GetSolutionFolder(); if (string.IsNullOrEmpty(Folder)) { return(new Dictionary <string, string>()); } if (Folder != CachedUProjectRootFolder) { Logging.WriteLine("GetUProjects: recaching uproject paths..."); DateTime Start = DateTime.Now; CachedUProjectRootFolder = Folder; CachedUProjectPaths = EnumerateProjects(new DirectoryInfo(Folder)).Select(x => x.FullName); CachedUProjects = null; TimeSpan TimeTaken = DateTime.Now - Start; Logging.WriteLine(string.Format("GetUProjects: EnumerateProjects took {0} sec", TimeTaken.TotalSeconds)); foreach (string CachedUProjectPath in CachedUProjectPaths) { Logging.WriteLine(String.Format("GetUProjects: found {0}", CachedUProjectPath)); } Logging.WriteLine(" DONE"); } if (CachedUProjects == null) { Logging.WriteLine("GetUProjects: recaching uproject names..."); var ProjectPaths = UnrealVSPackage.Instance.GetLoadedProjectPaths(); var ProjectNames = (from path in ProjectPaths select Path.GetFileNameWithoutExtension(path)).ToArray(); var CodeUProjects = from UProjectPath in CachedUProjectPaths let ProjectName = Path.GetFileNameWithoutExtension(UProjectPath) where ProjectNames.Any(name => string.Compare(name, ProjectName, StringComparison.OrdinalIgnoreCase) == 0) select new { Name = ProjectName, FilePath = UProjectPath }; CachedUProjects = new Dictionary <string, string>(); foreach (var UProject in CodeUProjects) { if (!CachedUProjects.ContainsKey(UProject.Name)) { CachedUProjects.Add(UProject.Name, UProject.FilePath); } } Logging.WriteLine(" DONE"); } return(CachedUProjects); }
/// IDispose pattern lets us clean up our stuff! protected override void Dispose(bool disposing) { if (Ticker != null && Ticker.IsAlive) { Thread.Sleep(TickPeriod + TickPeriod); if (Ticker.IsAlive) { Logging.WriteLine("WARNING: Force aborting Ticker thread"); Ticker.Abort(); } } base.Dispose(disposing); // Clean up singleton instance PrivateInstance = null; CommandLineEditor = null; StartupProjectSelector = null; BatchBuilder = null; QuickBuilder = null; if (CompileSingleFile != null) { CompileSingleFile.Dispose(); CompileSingleFile = null; } // No longer want solution events if (SolutionEventsHandle != 0) { SolutionManager.UnadviseSolutionEvents(SolutionEventsHandle); SolutionEventsHandle = 0; } SolutionManager = null; // No longer want selection events if (SelectionEventsHandle != 0) { SelectionManager.UnadviseSelectionEvents(SelectionEventsHandle); SelectionEventsHandle = 0; } SelectionManager = null; // No longer want update solution events if (UpdateSolutionEventsHandle != 0) { SolutionBuildManager.UnadviseUpdateSolutionEvents(UpdateSolutionEventsHandle); UpdateSolutionEventsHandle = 0; } SolutionBuildManager = null; Logging.WriteLine("Closing UnrealVS extension"); Logging.Close(); }
/** Methods */ /// <summary> /// Package constructor. The package is being created but Visual Studio isn't fully initialized yet, so /// it's NOT SAFE to call into Visual Studio services from here. Do that in Initialize() instead. /// </summary> public UnrealVSPackage() { // Register this key string so the package can save command line data to solution options files. // See OnLoadOptions() & OnSaveOptions() AddOptionKey(CommandLineOptionKey); AddOptionKey(BatchBuildSetsOptionKey); // Setup singleton instance PrivateInstance = this; Logging.Initialize(ExtensionName, VersionString); Logging.WriteLine("Loading UnrealVS extension package..."); }
/// <summary> /// Called from the package class when there are options to be read out of the solution file. /// </summary> /// <param name="Stream">The stream to load the option data from.</param> public void LoadOptions(Stream Stream) { try { _BuildJobSetsCollection.Clear(); using (BinaryReader Reader = new BinaryReader(Stream)) { int SetCount = Reader.ReadInt32(); for (int SetIdx = 0; SetIdx < SetCount; SetIdx++) { BuildJobSet LoadedSet = new BuildJobSet(); LoadedSet.Name = Reader.ReadString(); int JobCount = Reader.ReadInt32(); for (int JobIdx = 0; JobIdx < JobCount; JobIdx++) { Utils.SafeProjectReference ProjectRef = new Utils.SafeProjectReference { FullName = Reader.ReadString(), Name = Reader.ReadString() }; string Config = Reader.ReadString(); string Platform = Reader.ReadString(); BuildJob.BuildJobType JobType; if (Enum.TryParse(Reader.ReadString(), out JobType)) { LoadedSet.BuildJobs.Add(new BuildJob(ProjectRef, Config, Platform, JobType)); } } _BuildJobSetsCollection.Add(LoadedSet); } } if (_BuildJobSetsCollection.Count == 0) { BuildJobSet DefaultItem = new BuildJobSet { Name = "UntitledSet" }; _BuildJobSetsCollection.Add(DefaultItem); } StateLoaded?.Invoke(this, null); } catch (Exception ex) { Exception AppEx = new ApplicationException("BatchBuilder failed to load options from .suo", ex); Logging.WriteLine(AppEx.ToString()); throw AppEx; } }
/// <summary> /// Called when a solution loads. Caches the solution build configs and sets the platform menus' visibility. /// Only platforms found in the laoded solution's list are shown. /// </summary> private void OnSolutionChanged() { CacheBuildConfigs(); ActivePlaformMenus.Clear(); Logging.WriteLine("Updating solution menu state, currently active config platforms are: " + string.Join(", ", SolutionConfigPlatforms)); foreach (var SubMenu in SubMenus) { if (SolutionConfigPlatforms.Any(Platform => string.Compare(Platform, SubMenu.Name, StringComparison.InvariantCultureIgnoreCase) == 0)) { ActivePlaformMenus.Add(SubMenu.Name, AllPlaformMenus[SubMenu.Name]); } } }
/// <summary> /// Returns all the .uprojects found under the solution root folder. /// </summary> public static IDictionary <string, string> GetUProjects() { var Folder = GetSolutionFolder(); if (string.IsNullOrEmpty(Folder)) { return(new Dictionary <string, string>()); } if (Folder != CachedUProjectRootFolder) { Logging.WriteLine("GetUProjects: recaching uproject paths..."); CachedUProjectRootFolder = Folder; CachedUProjectPaths = Directory.GetFiles(Folder, "*." + UProjectExtension, SearchOption.AllDirectories); CachedUProjects = null; Logging.WriteLine(" DONE"); } if (CachedUProjects == null) { Logging.WriteLine("GetUProjects: recaching uproject names..."); var ProjectPaths = UnrealVSPackage.Instance.GetLoadedProjectPaths(); var ProjectNames = (from path in ProjectPaths select Path.GetFileNameWithoutExtension(path)).ToArray(); var CodeUProjects = from UProjectPath in CachedUProjectPaths let ProjectName = Path.GetFileNameWithoutExtension(UProjectPath) where ProjectNames.Any(name => string.Compare(name, ProjectName, StringComparison.OrdinalIgnoreCase) == 0) select new { Name = ProjectName, FilePath = UProjectPath }; CachedUProjects = new Dictionary <string, string>(); foreach (var UProject in CodeUProjects) { if (!CachedUProjects.ContainsKey(UProject.Name)) { CachedUProjects.Add(UProject.Name, UProject.FilePath); } } Logging.WriteLine(" DONE"); } return(CachedUProjects); }
/// <summary> /// Enumerate projects under the given directory /// </summary> /// <param name="SolutionDir">Base directory to enumerate</param> /// <returns>List of project files</returns> static List <FileInfo> EnumerateProjects(DirectoryInfo SolutionDir) { // Enumerate all the projects in the same directory as the solution. If there's one here, we don't need to consider any other. List <FileInfo> ProjectFiles = new List <FileInfo>(SolutionDir.EnumerateFiles("*.uproject")); if (ProjectFiles.Count == 0) { // Build a list of all the parent directories for projects. This includes the UE4 root, plus any directories referenced via .uprojectdirs files. List <DirectoryInfo> ParentProjectDirs = new List <DirectoryInfo>(); ParentProjectDirs.Add(SolutionDir); // Read all the .uprojectdirs files foreach (FileInfo ProjectDirsFile in SolutionDir.EnumerateFiles("*.uprojectdirs")) { foreach (string Line in File.ReadAllLines(ProjectDirsFile.FullName)) { string TrimLine = Line.Trim().Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).Trim(Path.DirectorySeparatorChar); if (TrimLine.Length > 0 && !TrimLine.StartsWith(";")) { try { ParentProjectDirs.Add(new DirectoryInfo(Path.Combine(SolutionDir.FullName, TrimLine))); } catch (Exception Ex) { Logging.WriteLine(String.Format("EnumerateProjects: Exception trying to resolve project directory '{0}': {1}", TrimLine, Ex.Message)); } } } } // Add projects in any subfolders of the parent directories HashSet <string> CheckedParentDirs = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase); foreach (DirectoryInfo ParentProjectDir in ParentProjectDirs) { if (CheckedParentDirs.Add(ParentProjectDir.FullName) && ParentProjectDir.Exists) { foreach (DirectoryInfo ProjectDir in ParentProjectDir.EnumerateDirectories()) { ProjectFiles.AddRange(ProjectDir.EnumerateFiles("*.uproject")); } } } } return(ProjectFiles); }
/// <summary> /// Overrides Package.OnSaveOptions() /// Invoked by the Package class when there are options to be saved to the solution file. /// </summary> /// <param name="key">The name of the option key to save.</param> /// <param name="stream">The stream to save the option data to.</param> protected override void OnSaveOptions(string key, Stream stream) { try { if (0 == string.Compare(key, CommandLineOptionKey)) { Logging.WriteLine("Saving CommandLineEditor options"); CommandLineEditor.SaveOptions(stream); } else if (0 == string.Compare(key, BatchBuildSetsOptionKey)) { Logging.WriteLine("Saving BatchBuilder options"); BatchBuilder.SaveOptions(stream); } } catch (Exception Ex) { // Couldn't save options Exception AppEx = new ApplicationException("OnSaveOptions() failed with key " + key, Ex); Logging.WriteLine(AppEx.ToString()); throw AppEx; } }
/// <summary> /// Launches a program /// </summary> /// <param name="ProgramFile">Path to the program to run</param> /// <param name="Arguments">Command-line arguments</param> /// <param name="OnExit">Optional callback whent he program exits</param> /// <param name="OutputHandler">If supplied, std-out and std-error will be redirected to this function and no shell window will be created</param> /// <returns>The newly-created process, or null if it wasn't able to start. Exceptions are swallowed, but a debug output string is emitted on errors</returns> public System.Diagnostics.Process LaunchProgram(string ProgramFile, string Arguments, EventHandler OnExit = null, DataReceivedEventHandler OutputHandler = null, bool bWaitForCompletion = false) { // Create the action's process. ProcessStartInfo ActionStartInfo = new ProcessStartInfo(); ActionStartInfo.FileName = ProgramFile; ActionStartInfo.Arguments = Arguments; if (OutputHandler != null) { ActionStartInfo.RedirectStandardInput = true; ActionStartInfo.RedirectStandardOutput = true; ActionStartInfo.RedirectStandardError = true; // True to use a DOS box to run the program in, otherwise false to run directly. In order to redirect // output, UseShellExecute must be disabled ActionStartInfo.UseShellExecute = false; // Don't show the DOS box, since we're redirecting everything ActionStartInfo.CreateNoWindow = true; } Logging.WriteLine(String.Format("Executing: {0} {1}", ActionStartInfo.FileName, ActionStartInfo.Arguments)); System.Diagnostics.Process ActionProcess; try { ActionProcess = new System.Diagnostics.Process(); ActionProcess.StartInfo = ActionStartInfo; if (OnExit != null) { ActionProcess.EnableRaisingEvents = true; ActionProcess.Exited += OnExit; } if (ActionStartInfo.RedirectStandardOutput) { ActionProcess.EnableRaisingEvents = true; ActionProcess.OutputDataReceived += OutputHandler; ActionProcess.ErrorDataReceived += OutputHandler; } // Launch the program ActionProcess.Start(); if (ActionStartInfo.RedirectStandardOutput) { ActionProcess.BeginOutputReadLine(); ActionProcess.BeginErrorReadLine(); } if (bWaitForCompletion) { while ((ActionProcess != null) && (!ActionProcess.HasExited)) { ActionProcess.WaitForExit(50); } } } catch (Exception Ex) { // Couldn't launch program Logging.WriteLine("Couldn't launch program: " + ActionStartInfo.FileName); Logging.WriteLine("Exception: " + Ex.Message); ActionProcess = null; } return(ActionProcess); }
public StartupProjectSelector() { _bIsSolutionOpened = UnrealVSPackage.Instance.DTE.Solution.IsOpen; // Create the handlers for our commands { // StartupProjectCombo var StartupProjectComboCommandId = new CommandID(GuidList.UnrealVSCmdSet, StartupProjectComboId); var StartupProjectComboCommand = new OleMenuCommand(StartupProjectComboHandler, StartupProjectComboCommandId); StartupProjectComboCommand.BeforeQueryStatus += (sender, args) => StartupProjectComboCommand.Enabled = _bIsSolutionOpened; UnrealVSPackage.Instance.MenuCommandService.AddCommand(StartupProjectComboCommand); // StartupProjectComboList var StartupProjectComboListCommandId = new CommandID(GuidList.UnrealVSCmdSet, StartupProjectComboListId); var StartupProjectComboListCommand = new OleMenuCommand(StartupProjectComboListHandler, StartupProjectComboListCommandId); StartupProjectComboListCommand.BeforeQueryStatus += (sender, args) => StartupProjectComboListCommand.Enabled = _bIsSolutionOpened; UnrealVSPackage.Instance.MenuCommandService.AddCommand(StartupProjectComboListCommand); StartupProjectComboCommands = new[] { StartupProjectComboCommand, StartupProjectComboListCommand }; } // Register for events that we care about UnrealVSPackage.Instance.OnStartupProjectChanged += OnStartupProjectChanged; UnrealVSPackage.Instance.OnSolutionOpened += delegate { Logging.WriteLine("Opened solution " + UnrealVSPackage.Instance.DTE.Solution.FullName); _bIsSolutionOpened = true; UpdateStartupProjectCombo(); UpdateStartupProjectList(Utils.GetAllProjectsFromDTE()); }; UnrealVSPackage.Instance.OnSolutionClosing += delegate { Logging.WriteLine("Closing solution"); _bIsSolutionOpened = false; UpdateStartupProjectCombo(); _CachedStartupProjects.Clear(); }; UnrealVSPackage.Instance.OnProjectOpened += delegate(Project OpenedProject) { if (_bIsSolutionOpened) { Logging.WriteLine("Opened project node " + OpenedProject.Name); UpdateStartupProjectList(OpenedProject); } else { Logging.WriteLine("Opened project node " + OpenedProject.Name + " with the solution CLOSED"); } }; UnrealVSPackage.Instance.OnProjectClosed += delegate(Project ClosedProject) { Logging.WriteLine("Closed project node " + ClosedProject.Name); RemoveFromStartupProjectList(ClosedProject); }; UnrealVSPackage.Instance.OptionsPage.OnOptionsChanged += OnOptionsChanged; UpdateStartupProjectList(Utils.GetAllProjectsFromDTE()); UpdateStartupProjectCombo(); }
/// <summary> /// Helper to check the properties of a project and determine whether it can be run in VS. /// Projects that return true can be run in the debugger by pressing the usual Start Debugging (F5) command. /// </summary> public static bool IsProjectSuitable(Project Project) { try { Logging.WriteLine("IsProjectExecutable: Attempting to determine if project " + Project.Name + " is executable"); var ConfigManager = Project.ConfigurationManager; if (ConfigManager == null) { return(false); } var ActiveProjectConfig = Project.ConfigurationManager.ActiveConfiguration; if (ActiveProjectConfig != null) { Logging.WriteLine( "IsProjectExecutable: ActiveProjectConfig=\"" + ActiveProjectConfig.ConfigurationName + "|" + ActiveProjectConfig.PlatformName + "\""); } else { Logging.WriteLine("IsProjectExecutable: Warning - ActiveProjectConfig is null!"); } bool IsSuitable = false; if (Project.Kind.Equals(GuidList.VCSharpProjectKindGuidString, StringComparison.OrdinalIgnoreCase)) { // C# project // Chris.Wood //Property StartActionProp = GetProjectConfigProperty(Project, null, "StartAction"); //if (StartActionProp != null) //{ // prjStartAction StartAction = (prjStartAction)StartActionProp.Value; // if (StartAction == prjStartAction.prjStartActionProject) // { // // Project starts the project's output file when run // Property OutputTypeProp = GetProjectProperty(Project, "OutputType"); // if (OutputTypeProp != null) // { // prjOutputType OutputType = (prjOutputType)OutputTypeProp.Value; // if (OutputType == prjOutputType.prjOutputTypeWinExe || // OutputType == prjOutputType.prjOutputTypeExe) // { // IsSuitable = true; // } // } // } // else if (StartAction == prjStartAction.prjStartActionProgram || // StartAction == prjStartAction.prjStartActionURL) // { // // Project starts an external program or a URL when run - assume it has been set deliberately to something executable // IsSuitable = true; // } //} IsSuitable = true; } else if (Project.Kind.Equals(GuidList.VCProjectKindGuidString, StringComparison.OrdinalIgnoreCase)) { // C++ project SolutionConfiguration SolutionConfig = UnrealVSPackage.Instance.DTE.Solution.SolutionBuild.ActiveConfiguration; SolutionContext ProjectSolutionCtxt = SolutionConfig.SolutionContexts.Item(Project.UniqueName); // Get the correct config object from the VCProject string ActiveConfigName = string.Format( "{0}|{1}", ProjectSolutionCtxt.ConfigurationName, ProjectSolutionCtxt.PlatformName); // Get the VS version-specific VC project object. VCProject VCProject = new VCProject(Project, ActiveConfigName); if (VCProject != null) { // Sometimes the configurations is null. if (VCProject.Configurations != null) { var VCConfigMatch = VCProject.Configurations.FirstOrDefault(VCConfig => VCConfig.Name == ActiveConfigName); if (VCConfigMatch != null) { if (VCConfigMatch.DebugAttach) { // Project attaches to a running process IsSuitable = true; } else { // Project runs its own process if (VCConfigMatch.DebugFlavor == DebuggerFlavor.Remote) { // Project debugs remotely if (VCConfigMatch.DebugRemoteCommand.Length != 0) { // An remote program is specified to run IsSuitable = true; } } else { // Local debugger if (VCConfigMatch.DebugCommand.Length != 0 && VCConfigMatch.DebugCommand != "$(TargetPath)") { // An external program is specified to run IsSuitable = true; } else { // No command so the project runs the target file if (VCConfigMatch.ConfigType == ConfigType.Application) { IsSuitable = true; } else if (VCConfigMatch.ConfigType == ConfigType.Generic) { // Makefile if (VCConfigMatch.NMakeToolOutput.Length != 0) { string Ext = Path.GetExtension(VCConfigMatch.NMakeToolOutput); if (!IsLibraryFileExtension(Ext)) { IsSuitable = true; } } } } } } } } } } else { // @todo: support other project types Logging.WriteLine("IsProjectExecutable: Unrecognised 'Kind' in project " + Project.Name + " guid=" + Project.Kind); } return(IsSuitable); } catch (Exception ex) { Exception AppEx = new ApplicationException("IsProjectExecutable() failed", ex); Logging.WriteLine(AppEx.ToString()); throw AppEx; } }