| /****************************************************************************  | 
| **  | 
| ** Copyright (C) 2018 The Qt Company Ltd.  | 
| ** Contact: https://www.qt.io/licensing/  | 
| **  | 
| ** This file is part of the Qt VS Tools.  | 
| **  | 
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$  | 
| ** Commercial License Usage  | 
| ** Licensees holding valid commercial Qt licenses may use this file in  | 
| ** accordance with the commercial license agreement provided with the  | 
| ** Software or, alternatively, in accordance with the terms contained in  | 
| ** a written agreement between you and The Qt Company. For licensing terms  | 
| ** and conditions see https://www.qt.io/terms-conditions. For further  | 
| ** information use the contact form at https://www.qt.io/contact-us.  | 
| **  | 
| ** GNU General Public License Usage  | 
| ** Alternatively, this file may be used under the terms of the GNU  | 
| ** General Public License version 3 as published by the Free Software  | 
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT  | 
| ** included in the packaging of this file. Please review the following  | 
| ** information to ensure the GNU General Public License requirements will  | 
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html.  | 
| **  | 
| ** $QT_END_LICENSE$  | 
| **  | 
| ****************************************************************************/  | 
|   | 
| using System;  | 
| using System.Collections.Generic;  | 
| using System.IO;  | 
| using System.Linq;  | 
| using Microsoft.VisualStudio;  | 
| using Microsoft.VisualStudio.Debugger.Interop;  | 
| using Microsoft.VisualStudio.Shell;  | 
| using Microsoft.VisualStudio.Shell.Interop;  | 
| using Microsoft.VisualStudio.VCProjectEngine;  | 
| using QtVsTools.Core;  | 
| using QtVsTools.Core.QtMsBuild;  | 
| using QtVsTools.SyntaxAnalysis;  | 
| using static QtVsTools.SyntaxAnalysis.RegExpr;  | 
|   | 
| namespace QtVsTools.Qml.Debug  | 
| {  | 
|     using AD7;  | 
|     using VisualStudio;  | 
|   | 
|     class Launcher : Disposable, IDebugEventCallback2  | 
|     {  | 
|         public static Launcher Instance { get; private set; }  | 
|         IVsDebugger debugger;  | 
|         IVsDebugger4 debugger4;  | 
|   | 
|         HashSet<Guid> _ExcludedProcesses;  | 
|         HashSet<Guid> ExcludedProcesses => _ExcludedProcesses  | 
|             ?? (_ExcludedProcesses = new HashSet<Guid>());  | 
|   | 
|         public static void Initialize()  | 
|         {  | 
|             Instance = new Launcher();  | 
|             Instance.debugger = VsServiceProvider.GetService<IVsDebugger>();  | 
|             Instance.debugger4 = VsServiceProvider.GetService<IVsDebugger, IVsDebugger4>();  | 
|             if (Instance.debugger != null && Instance.debugger4 != null)  | 
|                 Instance.debugger.AdviseDebugEventCallback(Instance);  | 
|         }  | 
|   | 
|         protected override void DisposeManaged()  | 
|         {  | 
|             if (debugger != null)  | 
|                 debugger.UnadviseDebugEventCallback(this);  | 
|         }  | 
|   | 
|         int IDebugEventCallback2.Event(  | 
|             IDebugEngine2 pEngine,  | 
|             IDebugProcess2 pProcess,  | 
|             IDebugProgram2 pProgram,  | 
|             IDebugThread2 pThread,  | 
|             IDebugEvent2 pEvent,  | 
|             ref Guid riidEvent,  | 
|             uint dwAttrib)  | 
|         {  | 
|             if (!QtVsToolsPackage.Instance.Options.QmlDebuggerEnabled)  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             if (riidEvent != typeof(IDebugThreadCreateEvent2).GUID  | 
|                 && riidEvent != typeof(IDebugProgramDestroyEvent2).GUID) {  | 
|                 return VSConstants.S_OK;  | 
|             }  | 
|   | 
|             if (pProcess == null && pProgram.GetProcess(out pProcess) != VSConstants.S_OK)  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             Guid procGuid;  | 
|             if (pProcess.GetProcessId(out procGuid) != VSConstants.S_OK)  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             // Run only once per process  | 
|             if (riidEvent == typeof(IDebugProgramDestroyEvent2).GUID) {  | 
|                 ExcludedProcesses.Remove(procGuid);  | 
|                 return VSConstants.S_OK;  | 
|             } else if (ExcludedProcesses.Contains(procGuid)) {  | 
|                 return VSConstants.S_OK;  | 
|             } else {  | 
|                 ExcludedProcesses.Add(procGuid);  | 
|             }  | 
|   | 
|             if (!(pEvent is IDebugLoadCompleteEvent2 || pEvent is IDebugThreadCreateEvent2))  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             if (pProgram == null)  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             bool native;  | 
|             Guid engineId = GetEngineId(pProgram);  | 
|             if (engineId == NativeEngine.Id)  | 
|                 native = true;  | 
|             else if (engineId == GdbEngine.Id)  | 
|                 native = false;  | 
|             else  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             string execPath;  | 
|             uint procId;  | 
|             if (!GetProcessInfo(pProcess, native, out execPath, out procId))  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             string execCmd;  | 
|             IEnumerable<string> rccItems;  | 
|             if (!GetProjectInfo(execPath, native, out execCmd, out rccItems))  | 
|                 return VSConstants.S_OK;  | 
|   | 
|             LaunchDebug(execPath, execCmd, procId, rccItems);  | 
|             return VSConstants.S_OK;  | 
|         }  | 
|   | 
|         Guid GetEngineId(IDebugProgram2 pProgram)  | 
|         {  | 
|             string engineName;  | 
|             Guid engineGuid;  | 
|             if (pProgram.GetEngineInfo(out engineName, out engineGuid) != VSConstants.S_OK)  | 
|                 return Guid.Empty;  | 
|             return engineGuid;  | 
|         }  | 
|   | 
|         class WslPath  | 
|         {  | 
|             public string Drive;  | 
|             public string Path;  | 
|             public static implicit operator string(WslPath wslPath)  | 
|             {  | 
|                 return string.Format(@"{0}:\{1}", wslPath.Drive, wslPath.Path);  | 
|             }  | 
|         }  | 
|   | 
|         static RegExpr wslPathRegex = new Token("WSLPATH", SkipWs_Disable, StartOfFile  | 
|             & "/mnt/" & new Token("DRIVE", CharWord) & "/" & new Token("PATH", AnyChar.Repeat()))  | 
|         {  | 
|             new Rule<WslPath>  | 
|             {  | 
|                 Update("DRIVE", (WslPath wslPath, string drive) => wslPath.Drive = drive),  | 
|                 Update("PATH", (WslPath wslPath, string path) => wslPath.Path = path),  | 
|             }  | 
|         };  | 
|         static RegExpr.Parser wslPathParser = wslPathRegex.Render();  | 
|   | 
|         bool GetProcessInfo(IDebugProcess2 pProcess, bool native, out string execPath, out uint procId)  | 
|         {  | 
|             execPath = "";  | 
|             procId = 0;  | 
|   | 
|             string fileName;  | 
|             if (pProcess.GetName(enum_GETNAME_TYPE.GN_FILENAME, out fileName) != VSConstants.S_OK)  | 
|                 return false;  | 
|   | 
|             var pProcessId = new AD_PROCESS_ID[1];  | 
|             if (pProcess.GetPhysicalProcessId(pProcessId) != VSConstants.S_OK)  | 
|                 return false;  | 
|   | 
|             if (native) {  | 
|                 execPath = Path.GetFullPath(fileName);  | 
|             } else {  | 
|                 var wslPath = wslPathParser.Parse(fileName)  | 
|                     .GetValues<WslPath>("WSLPATH").FirstOrDefault();  | 
|                 if (wslPath != null)  | 
|                     execPath = Path.GetFullPath(wslPath);  | 
|                 else  | 
|                     execPath = fileName;  | 
|             }  | 
|   | 
|             procId = pProcessId[0].dwProcessId;  | 
|             return true;  | 
|         }  | 
|   | 
|         bool GetProjectInfo(string execPath, bool native, out string execCmd, out IEnumerable<string> rccItems)  | 
|         {  | 
|             execCmd = "";  | 
|             rccItems = null;  | 
|   | 
|             foreach (var project in HelperFunctions.ProjectsInSolution(QtVsToolsPackage.Instance.Dte)) {  | 
|   | 
|                 var vcProject = project.Object as VCProject;  | 
|                 if (vcProject == null)  | 
|                     continue;  | 
|   | 
|                 var vcConfigs = vcProject.Configurations as IVCCollection;  | 
|                 if (vcConfigs == null)  | 
|                     continue;  | 
|                 var activeConfig = project.ConfigurationManager.ActiveConfiguration;  | 
|                 if (activeConfig == null)  | 
|                     continue;  | 
|                 var activeConfigId = string.Format("{0}|{1}",  | 
|                     activeConfig.ConfigurationName, activeConfig.PlatformName);  | 
|                 var vcConfig = vcConfigs.Item(activeConfigId) as VCConfiguration;  | 
|                 if (vcConfig == null)  | 
|                     continue;  | 
|   | 
|                 var props = vcProject as IVCBuildPropertyStorage;  | 
|   | 
|                 var localDebugCommand = props.GetPropertyValue("LocalDebuggerCommand",  | 
|                     vcConfig.Name, "UserFile");  | 
|   | 
|                 var remoteDebugCommand = props.GetPropertyValue("RemoteDebuggerCommand",  | 
|                     vcConfig.Name, "UserFile");  | 
|   | 
|                 string debugCommand = (native || string.IsNullOrEmpty(remoteDebugCommand))  | 
|                     ? localDebugCommand : remoteDebugCommand;  | 
|   | 
|                 bool sameFile = string.Equals(execPath, Path.GetFullPath(debugCommand),  | 
|                     StringComparison.InvariantCultureIgnoreCase);  | 
|   | 
|                 if (!sameFile)  | 
|                     continue;  | 
|   | 
|                 OutputWriteLine(string.Format("Debugging project '{0}'...", vcProject.Name));  | 
|   | 
|                 var qtProject = QtProject.Create(vcProject);  | 
|                 if (qtProject == null) {  | 
|                     OutputWriteLine("DISABLED: Non-Qt project");  | 
|                     return false;  | 
|                 }  | 
|   | 
|                 if (!qtProject.IsQtMsBuildEnabled()) {  | 
|                     OutputWriteLine("DISABLED: Non-Qt/MSBuild project");  | 
|                     return false;  | 
|                 }  | 
|   | 
|                 if (!qtProject.QmlDebug) {  | 
|                     OutputWriteLine("DISABLED: QML debugging disabled in Qt project settings");  | 
|                     return false;  | 
|                 }  | 
|   | 
|                 var execArgs = props.GetPropertyValue(  | 
|                     native ? "LocalDebuggerCommandArguments" : "RemoteDebuggerCommandArguments",  | 
|                     vcConfig.Name, "UserFile");  | 
|                 if (string.IsNullOrEmpty(execArgs)) {  | 
|                     OutputWriteLine("DISABLED: Error reading command line arguments");  | 
|                     return false;  | 
|                 }  | 
|   | 
|                 var cmd = "\"" + execPath + "\" " + execArgs;  | 
|   | 
|                 if (!QmlDebugger.CheckCommandLine(execPath, cmd)) {  | 
|                     OutputWriteLine("DISABLED: Error parsing command line arguments");  | 
|                     return false;  | 
|                 }  | 
|   | 
|                 OutputWriteLine("Starting QML debug session...");  | 
|   | 
|                 execCmd = cmd;  | 
|                 rccItems = ((IVCCollection)vcProject.Files).Cast<VCFile>()  | 
|                     .Where(x => x.ItemType == QtRcc.ItemTypeName)  | 
|                     .Select(x => x.FullPath);  | 
|   | 
|                 return true;  | 
|             }  | 
|   | 
|             OutputWriteLine("DISABLED: Could not identify project being debugged");  | 
|   | 
|             return false;  | 
|         }  | 
|   | 
|         void OutputWriteLine(string msg)  | 
|         {  | 
|             Messages.Print(msg);  | 
|         }  | 
|   | 
|         void LaunchDebug(  | 
|             string execPath,  | 
|             string execCmd,  | 
|             uint procId,  | 
|             IEnumerable<string> rccItems)  | 
|         {  | 
|             var targets = new[] { new VsDebugTargetInfo4  | 
|             {  | 
|                 dlo = (uint)DEBUG_LAUNCH_OPERATION.DLO_CreateProcess,  | 
|                 bstrExe = new Uri(execPath).LocalPath,  | 
|                 bstrArg = execCmd,  | 
|                 bstrOptions = procId.ToString(),  | 
|                 bstrEnv = "QTRCC=" + string.Join(";", rccItems),  | 
|                 guidLaunchDebugEngine = QmlEngine.Id,  | 
|                 LaunchFlags = (uint)__VSDBGLAUNCHFLAGS5.DBGLAUNCH_BreakOneProcess,  | 
|             }};  | 
|   | 
|             var processInfo = new VsDebugTargetProcessInfo[targets.Length];  | 
|             try {  | 
|                 debugger4.LaunchDebugTargets4((uint)targets.Length, targets, processInfo);  | 
|   | 
|             } catch (System.Exception e) {  | 
|                 OutputWriteLine(e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);  | 
|             }  | 
|         }  | 
|     }  | 
| }  |