/// <summary> /// Establishes a connection to the Raspberry and ensures that the Raspberry has /// the target SDK, <b>vsdbg</b> installed and also handles uploading of the project /// binaries. /// </summary> /// <param name="connectionInfo">The connection info.</param> /// <param name="targetSdk">The target SDK.</param> /// <param name="projectProperties">The project properties.</param> /// <param name="projectSettings">The project's Raspberry debug settings.</param> /// <returns>The <see cref="Connection"/> or <c>null</c> if there was an error.</returns> public static async Task <Connection> InitializeConnectionAsync(ConnectionInfo connectionInfo, Sdk targetSdk, ProjectProperties projectProperties, ProjectSettings projectSettings) { Covenant.Requires <ArgumentNullException>(connectionInfo != null, nameof(connectionInfo)); Covenant.Requires <ArgumentNullException>(targetSdk != null, nameof(targetSdk)); Covenant.Requires <ArgumentNullException>(projectProperties != null, nameof(projectProperties)); Covenant.Requires <ArgumentNullException>(projectSettings != null, nameof(projectSettings)); var connection = await Connection.ConnectAsync(connectionInfo, projectSettings : projectSettings); // .NET Core only supports Raspberry models 3 and 4. if (!connection.PiStatus.RaspberryModel.StartsWith("Raspberry Pi 3 Model") && !connection.PiStatus.RaspberryModel.StartsWith("Raspberry Pi 4 Model")) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); MessageBoxEx.Show( $"Your [{connection.PiStatus.RaspberryModel}] is not supported. .NET Core requires a Raspberry Model 3 or 4.", $"Raspberry Not Supported", MessageBoxButtons.OK, MessageBoxIcon.Error); connection.Dispose(); return(null); } // Ensure that the SDK is installed. if (!await connection.InstallSdkAsync(targetSdk.Version)) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); MessageBoxEx.Show( $"Cannot install the .NET SDK [v{targetSdk.Version}] on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", "SDK Installation Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); connection.Dispose(); return(null); } // Ensure that the debugger is installed. if (!await connection.InstallDebuggerAsync()) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); MessageBoxEx.Show( $"Cannot install the VSDBG debugger on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", "Debugger Installation Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); connection.Dispose(); return(null); } // Upload the program binaries. if (!await connection.UploadProgramAsync(projectProperties.Name, projectProperties.AssemblyName, projectProperties.PublishFolder)) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); MessageBoxEx.Show( $"Cannot upload the program binaries to the Raspberry.\r\n\r\nCheck the Debug Output for more details.", "Debugger Installation Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); connection.Dispose(); return(null); } return(connection); }
//-------------------------------------------------------------------- // 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(); if (string.IsNullOrEmpty(project.FullName)) { // We'll see this for unsupported Visual Studio projects and will just // return project properties indicating this. return(new ProjectProperties() { Name = project.Name, FullPath = project.FullName, Configuration = null, IsNetCore = false, SdkVersion = null, OutputFolder = null, OutputFileName = null, IsExecutable = false, AssemblyName = null, DebugEnabled = false, DebugConnectionName = null, CommandLineArgs = new List <string>(), EnvironmentVariables = new Dictionary <string, string>(), IsSupportedSdkVersion = false, IsRaspberryCompatible = false, IsAspNet = false, AspPort = 0, AspLaunchBrowser = false, AspRelativeBrowserUri = null }); } var projectFolder = Path.GetDirectoryName(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); // 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 // // 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.SdkGoodCatalog.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);; } } sdkName = targetSdk?.Name; // 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 = sdkName == null ? null : PackageHelper.SdkGoodCatalog.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; // We need to jump through some hoops to obtain the project GUID. var solutionService = RaspberryDebuggerPackage.Instance.SolutionService; Covenant.Assert(solutionService.GetProjectOfUniqueName(project.UniqueName, out var hierarchy) == VSConstants.S_OK); Covenant.Assert(solutionService.GetGuidOfProject(hierarchy, out var projectGuid) == VSConstants.S_OK); // Return the properties. return(new ProjectProperties() { Name = project.Name, FullPath = project.FullName, Guid = projectGuid, 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 }); }