/// <summary> /// 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. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event args.</param> #pragma warning disable VSTHRD100 private async void Execute(object sender, EventArgs e) #pragma warning restore VSTHRD100 { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (dte.Solution == null) { return; } var project = PackageHelper.GetStartupProject(dte.Solution); if (project == null) { MessageBoxEx.Show( "Please select a startup project using the Project/Set as Startup project menu or by right clicking a project in the Solution Explorer and enabling this.", "Startup Project not found", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var targetFrameworkMonikers = (string)project.Properties.Item("TargetFrameworkMoniker").Value; var outputType = (int)project.Properties.Item("OutputType").Value; var monikers = targetFrameworkMonikers.Split(','); var isNetCore = monikers[0] == ".NETCoreApp"; var sdkVersion = monikers[1].StartsWith("Version=v") ? monikers[1].Substring("Version=v".Length) : null; if (!isNetCore || outputType != 1 /* EXE */ || sdkVersion == null || SemanticVersion.Parse(sdkVersion) < SemanticVersion.Parse("3.1")) { MessageBoxEx.Show( "Raspberry debugging is not supported by this project type. Only .NET Core applications targeting .NET Core 3.1 or greater are supported.", "Unsupported Project Type", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var raspberryProjects = PackageHelper.ReadRaspberryProjects(dte.Solution); var projectSettings = raspberryProjects[project.UniqueName]; var settingsDialog = new SettingsDialog(projectSettings); if (settingsDialog.ShowDialog() == DialogResult.OK) { PackageHelper.WriteRaspberryProjects(dte.Solution, raspberryProjects); } }
//-------------------------------------------------------------------- // Static members /// <summary> /// Creates a <see cref="ProjectProperties"/> instance holding the /// necessary properties from a <see cref="Project"/>. This must /// be called on a UI thread. /// </summary> /// <param name="solution">The current solution.</param> /// <param name="project">The source project.</param> /// <returns>The cloned <see cref="ProjectProperties"/>.</returns> public static ProjectProperties CopyFrom(Solution solution, Project project) { Covenant.Requires <ArgumentNullException>(solution != null, nameof(solution)); Covenant.Requires <ArgumentNullException>(project != null, nameof(project)); ThreadHelper.ThrowIfNotOnUIThread(); var projectFolder = Path.GetDirectoryName(project.FullName); var projectFile = File.ReadAllText(project.FullName); var isNetCore = true; var netVersion = (SemanticVersion)null; var sdkName = (string)null; // Read the properties we care about from the project. var targetFrameworkMonikers = (string)project.Properties.Item("TargetFrameworkMoniker").Value; var outputType = (int)project.Properties.Item("OutputType").Value; var monikers = targetFrameworkMonikers.Split(','); isNetCore = monikers[0] == ".NETCoreApp"; // Extract the version from the moniker. This looks like: "Version=v5.0" var versionRegex = new Regex(@"(?<version>[0-9\.]+)$"); netVersion = SemanticVersion.Parse(versionRegex.Match(monikers[1]).Groups["version"].Value); #if DOESNT_WORK // The [dotnet --info] command doesn't work as I expected because it doesn't // appear to examine the project file when determining the SDK version./ // // https://github.com/nforgeio/RaspberryDebugger/issues/16 // We're going to execute [dotnet --info] in the project directory, to obtain the // SDK version which will be on the second line which will look something like: // // Version: 3.1.301 // // The cool thing is that this will honor any [global.json] files in the solution. var orgDirectory = Environment.CurrentDirectory; Environment.CurrentDirectory = projectFolder; try { var response = NeonHelper.ExecuteCapture("dotnet", new object[] { "--info" }); // Note that we'll stick with the version we extracted from the TargetFrameworkMoniker // above on the off chance that this call fails. if (response.ExitCode == 0) { using (var reader = new StringReader(response.OutputText)) { var versionLine = reader.Lines().Skip(1).Take(1).FirstOrDefault().Trim(); Covenant.Assert(versionLine != null); Covenant.Assert(versionLine.StartsWith("Version:")); sdkName = versionLine.Split(':')[1]; } } } finally { Environment.CurrentDirectory = orgDirectory; } #else // So, we're just going to use the latest known SDK from our catalog instead. // This isn't ideal but should work fine for the vast majority of people. var targetSdk = (Sdk)null; var targetSdkVersion = (SemanticVersion)null; foreach (var sdkItem in PackageHelper.SdkCatalog.Items .Where(item => item.IsStandalone && item.Architecture == SdkArchitecture.ARM32)) { var sdkVersion = SemanticVersion.Parse(sdkItem.Version); if (sdkVersion.Major != netVersion.Major || sdkVersion.Minor != netVersion.Minor) { continue; } if (targetSdkVersion == null || sdkVersion > targetSdkVersion) { targetSdkVersion = sdkVersion; targetSdk = new Sdk(sdkItem.Name, sdkItem.Version);; } } if (targetSdk == null) { // I don't believe we'll ever see this because the project shouldn't build // when the required SDK isn't present. Log.Error($"Unable to locate an SDK for [{targetFrameworkMonikers}]."); Covenant.Assert(false, $"Unable to locate an SDK for [{targetFrameworkMonikers}]."); } sdkName = targetSdk.Name; #endif // Load [Properties/launchSettings.json] if present to obtain the command line // arguments and environment variables as well as the target connection. Note // that we're going to use the profile named for the project and ignore any others. // // The launch settings for Console vs. WebApps are a bit different. WebApps include // a top-level "iisSettings"" property and two profiles: "IIS Express" and the // profile with the project name. We're going to use the presence of the "iisSettings" // property to determine that we're dealing with a WebApp and we'll do some additional // processing based off of the project profile: // // 1. Launch the browser if [launchBrowser=true] // 2. Extract the site port number from [applicationUrl] // 3. Have the app listen on all IP addresses by adding this environment // variable when we : // // ASPNETCORE_SERVER.URLS=http://0.0.0.0:<port> var launchSettingsPath = Path.Combine(projectFolder, "Properties", "launchSettings.json"); var commandLineArgs = new List <string>(); var environmentVariables = new Dictionary <string, string>(); var isAspNet = false; var aspPort = 0; var aspLaunchBrowser = false; var aspRelativeBrowserUri = "/"; if (File.Exists(launchSettingsPath)) { var settings = JObject.Parse(File.ReadAllText(launchSettingsPath)); var profiles = settings.Property("profiles"); if (profiles != null) { foreach (var profile in ((JObject)profiles.Value).Properties()) { if (profile.Name == project.Name) { var profileObject = (JObject)profile.Value; var environmentVariablesObject = (JObject)profileObject.Property("environmentVariables")?.Value; commandLineArgs = ParseArgs((string)profileObject.Property("commandLineArgs")?.Value); if (environmentVariablesObject != null) { foreach (var variable in environmentVariablesObject.Properties()) { environmentVariables[variable.Name] = (string)variable.Value; } } // Extract additional settings for ASPNET projects. if (settings.Property("iisSettings") != null) { isAspNet = true; // Note that we're going to fall back to port 5000 if there are any // issues parsing the application URL. const int fallbackPort = 5000; var jProperty = profileObject.Property("applicationUrl"); if (jProperty != null && jProperty.Value.Type == JTokenType.String) { try { var uri = new Uri((string)jProperty.Value); aspPort = uri.Port; if (!NetHelper.IsValidPort(aspPort)) { aspPort = fallbackPort; } } catch { aspPort = fallbackPort; } } else { aspPort = fallbackPort; } jProperty = profileObject.Property("launchBrowser"); if (jProperty != null && jProperty.Value.Type == JTokenType.Boolean) { aspLaunchBrowser = (bool)jProperty.Value; } } } else if (profile.Name == "IIS Express") { // For ASPNET apps, this profile may include a "launchUrl" which // specifies the absolute or relative URI to display in a debug // browser launched during debugging. // // We're going to normalize this as a relative URI and save it // so we'll be able to launch the browser on the correct page. var profileObject = (JObject)profile.Value; var jProperty = profileObject.Property("launchUrl"); if (jProperty != null && jProperty.Value.Type == JTokenType.String) { var launchUri = (string)jProperty.Value; if (!string.IsNullOrEmpty(launchUri)) { try { var uri = new Uri(launchUri, UriKind.RelativeOrAbsolute); if (uri.IsAbsoluteUri) { aspRelativeBrowserUri = uri.PathAndQuery; } else { aspRelativeBrowserUri = launchUri; } } catch { // We'll fall back to "/" for any URI parsing errors. aspRelativeBrowserUri = "/"; } } } } } } } // Get the target Raspberry from the debug settings. var projects = PackageHelper.ReadRaspberryProjects(solution); var projectSettings = projects[project.UniqueName]; var debugEnabled = projectSettings.EnableRemoteDebugging; var debugConnectionName = projectSettings.RemoteDebugTarget; // Determine whether the referenced .NET Core SDK is currently supported. var sdk = PackageHelper.SdkCatalog.Items.SingleOrDefault(item => SemanticVersion.Parse(item.Name) == SemanticVersion.Parse(sdkName) && item.Architecture == SdkArchitecture.ARM32); var isSupportedSdkVersion = sdk != null; // Determine whether the project is Raspberry compatible. var isRaspberryCompatible = isNetCore && outputType == 1 && // 1=EXE isSupportedSdkVersion; // Return the properties. return(new ProjectProperties() { Name = project.Name, FullPath = project.FullName, Configuration = project.ConfigurationManager.ActiveConfiguration.ConfigurationName, IsNetCore = isNetCore, SdkVersion = sdk?.Version, OutputFolder = Path.Combine(projectFolder, project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString()), OutputFileName = (string)project.Properties.Item("OutputFileName").Value, IsExecutable = outputType == 1, // 1=EXE AssemblyName = project.Properties.Item("AssemblyName").Value.ToString(), DebugEnabled = debugEnabled, DebugConnectionName = debugConnectionName, CommandLineArgs = commandLineArgs, EnvironmentVariables = environmentVariables, IsSupportedSdkVersion = isSupportedSdkVersion, IsRaspberryCompatible = isRaspberryCompatible, IsAspNet = isAspNet, AspPort = aspPort, AspLaunchBrowser = aspLaunchBrowser, AspRelativeBrowserUri = aspRelativeBrowserUri }); }