diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f30141..791bdd3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +### Changed + +- Improved handling of environment variables from Aspire host launchSettings file + ## [0.2.1] - 2024-01-05 ### Fixed diff --git a/gradle.properties b/gradle.properties index 31cd0603..87e598e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = me.rafaelldi.aspire pluginName = aspire-plugin pluginRepositoryUrl = https://github.com/rafaelldi/aspire-plugin # SemVer format -> https://semver.org -pluginVersion = 0.2.1 +pluginVersion = 0.2.2 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 233 diff --git a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationFactory.kt b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationFactory.kt index c562389e..ff9d11bd 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationFactory.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationFactory.kt @@ -13,7 +13,7 @@ class AspireHostConfigurationFactory(type: AspireHostConfigurationType) : this, "Aspire Host", AspireHostConfigurationParameters( - project, "", true, DotNetStartBrowserParameters() + project, "", true, hashMapOf(), true, DotNetStartBrowserParameters() ) ) } \ No newline at end of file diff --git a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationParameters.kt b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationParameters.kt index 30548d19..42c4cca7 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationParameters.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationParameters.kt @@ -1,5 +1,6 @@ package me.rafaelldi.aspire.run +import com.intellij.execution.configuration.EnvironmentVariablesComponent import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.execution.configurations.RuntimeConfigurationError @@ -20,11 +21,14 @@ import org.jdom.Element class AspireHostConfigurationParameters( private val project: Project, var projectFilePath: String, + var trackEnvs: Boolean, + var envs: Map, var trackUrl: Boolean, var startBrowserParameters: DotNetStartBrowserParameters ) { companion object { private const val PROJECT_FILE_PATH = "PROJECT_FILE_PATH" + private const val TRACK_ENVS = "TRACK_ENVS" private const val TRACK_URL = "TRACK_URL" } @@ -56,6 +60,9 @@ class AspireHostConfigurationParameters( fun readExternal(element: Element) { projectFilePath = JDOMExternalizerUtil.readField(element, PROJECT_FILE_PATH) ?: "" + val trackEnvsString = JDOMExternalizerUtil.readField(element, TRACK_ENVS) ?: "" + trackEnvs = trackEnvsString != "0" + EnvironmentVariablesComponent.readExternal(element, envs) val trackUrlString = JDOMExternalizerUtil.readField(element, TRACK_URL) ?: "" trackUrl = trackUrlString != "0" startBrowserParameters = DotNetStartBrowserParameters.readExternal(element) @@ -63,6 +70,8 @@ class AspireHostConfigurationParameters( fun writeExternal(element: Element) { JDOMExternalizerUtil.writeField(element, PROJECT_FILE_PATH, projectFilePath) + JDOMExternalizerUtil.writeField(element, TRACK_ENVS, if (trackEnvs) "1" else "0") + EnvironmentVariablesComponent.writeExternal(element, envs) JDOMExternalizerUtil.writeField(element, TRACK_URL, if (trackUrl) "1" else "0") startBrowserParameters.writeExternal(element) } @@ -70,8 +79,10 @@ class AspireHostConfigurationParameters( fun AspireHostConfigurationParameters.setUpFromRunnableProject(project: RunnableProject) { projectFilePath = project.projectFilePath - trackUrl = true val runOptions = project.getRunOptions() + trackEnvs = true + envs = project.environmentVariables.associate { it.key to it.value } + trackUrl = true val startBrowserUrl = runOptions.startBrowserUrl if (startBrowserUrl.isNotEmpty()) { startBrowserParameters.apply { diff --git a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationViewModel.kt b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationViewModel.kt index 6da94506..8c4d87e4 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationViewModel.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostConfigurationViewModel.kt @@ -16,6 +16,7 @@ class AspireHostConfigurationViewModel( private val lifetime: Lifetime, private val runnableProjectsModel: RunnableProjectsModel?, val projectSelector: ProjectSelector, + val environmentVariablesEditor: EnvironmentVariablesEditor, separator: ViewSeparator, val urlEditor: TextEditor, val dotNetBrowserSettingsEditor: BrowserSettingsEditor @@ -23,12 +24,15 @@ class AspireHostConfigurationViewModel( override val controls: List = listOf( projectSelector, + environmentVariablesEditor, separator, urlEditor, dotNetBrowserSettingsEditor ) private var isLoaded = false + + var trackEnvs = true var trackUrl = true init { @@ -44,6 +48,7 @@ class AspireHostConfigurationViewModel( ) } + environmentVariablesEditor.envs.advise(lifetime) { handleEnvValueChange() } urlEditor.text.advise(lifetime) { handleUrlValueChange() } } @@ -53,6 +58,7 @@ class AspireHostConfigurationViewModel( } val runOptions = project.getRunOptions() + environmentVariablesEditor.envs.set(runOptions.environmentVariables) val startBrowserUrl = runOptions.startBrowserUrl if (startBrowserUrl.isNotEmpty()) { urlEditor.defaultValue.value = startBrowserUrl @@ -61,6 +67,14 @@ class AspireHostConfigurationViewModel( } } + private fun handleEnvValueChange() { + projectSelector.project.valueOrNull?.let { + val envs = it.environmentVariables.associate { pair -> pair.key to pair.value }.toSortedMap() + val editorEnvs = environmentVariablesEditor.envs.value.toSortedMap() + trackEnvs = envs == editorEnvs + } + } + private fun handleUrlValueChange() { projectSelector.project.valueOrNull?.let { val runOptions = it.getRunOptions() @@ -68,9 +82,16 @@ class AspireHostConfigurationViewModel( } } - fun reset(projectFilePath: String, trackUrl: Boolean, dotNetStartBrowserParameters: DotNetStartBrowserParameters) { + fun reset( + projectFilePath: String, + trackEnvs: Boolean, + envs: Map, + trackUrl: Boolean, + dotNetStartBrowserParameters: DotNetStartBrowserParameters + ) { isLoaded = false + this.trackEnvs = trackEnvs this.trackUrl = trackUrl runnableProjectsModel?.projects?.adviseOnce(lifetime) { projectList -> @@ -116,6 +137,10 @@ class AspireHostConfigurationViewModel( }?.let { project -> projectSelector.project.set(project) + val effectiveEnvs = + if (trackEnvs) project.environmentVariables.associate { it.key to it.value } else envs + environmentVariablesEditor.envs.set(effectiveEnvs) + val runOptions = project.getRunOptions() val effectiveUrl = if (trackUrl) runOptions.startBrowserUrl else dotNetStartBrowserParameters.url urlEditor.defaultValue.value = effectiveUrl diff --git a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt index 3ae9e022..c97152cc 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt @@ -13,6 +13,9 @@ import com.jetbrains.rider.model.runnableProjectsModel import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.run.configurations.AsyncExecutorFactory import com.jetbrains.rider.run.configurations.project.DotNetProjectConfigurationParameters +import com.jetbrains.rider.run.environment.ExecutableParameterProcessor +import com.jetbrains.rider.run.environment.ExecutableRunParameters +import com.jetbrains.rider.run.environment.ProjectProcessOptions import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost import com.jetbrains.rider.util.NetUtils @@ -20,6 +23,8 @@ import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DEBUG_SESSION_P import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DEBUG_SESSION_TOKEN import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DOTNET_DASHBOARD_OTLP_ENDPOINT_URL import me.rafaelldi.aspire.settings.AspireSettings +import org.jetbrains.concurrency.await +import java.io.File import java.util.* class AspireHostExecutorFactory( @@ -50,13 +55,11 @@ class AspireHostExecutorFactory( } } - private fun getDotNetExecutable(runnableProject: RunnableProject): DotNetExecutable { + private suspend fun getDotNetExecutable(runnableProject: RunnableProject): DotNetExecutable { val projectOutput = runnableProject.projectOutputs.firstOrNull() ?: throw CantRunException("Unable to find project output") - val envs = runnableProject.environmentVariables - .associate { it.key to it.value } - .toMutableMap() + val envs = parameters.envs.toMutableMap() val debugSessionToken = UUID.randomUUID().toString() val debugSessionPort = NetUtils.findFreePort(67800) val openTelemetryProtocolEndpointPort = NetUtils.findFreePort(87800) @@ -65,14 +68,32 @@ class AspireHostExecutorFactory( if (AspireSettings.getInstance().collectTelemetry) envs[DOTNET_DASHBOARD_OTLP_ENDPOINT_URL] = "http://localhost:$openTelemetryProtocolEndpointPort" - return DotNetExecutable( + val processOptions = ProjectProcessOptions( + File(runnableProject.projectFilePath), + File(projectOutput.workingDirectory) + ) + val runParameters = ExecutableRunParameters( projectOutput.exePath, - projectOutput.tfm, projectOutput.workingDirectory, ParametersListUtil.join(projectOutput.defaultArguments), + envs, + true, + projectOutput.tfm + ) + + val params = ExecutableParameterProcessor + .getInstance(project) + .processEnvironment(runParameters, processOptions) + .await() + + return DotNetExecutable( + params.executablePath ?: projectOutput.exePath, + params.tfm ?: projectOutput.tfm, + params.workingDirectoryPath ?: projectOutput.workingDirectory, + params.commandLineArgumentString ?: ParametersListUtil.join(projectOutput.defaultArguments), false, false, - envs, + params.environmentVariables, true, parameters.startBrowserAction, null, diff --git a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostSettingsEditor.kt b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostSettingsEditor.kt index 930efc98..7f4b5017 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostSettingsEditor.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostSettingsEditor.kt @@ -3,12 +3,10 @@ package me.rafaelldi.aspire.run import com.intellij.openapi.project.Project import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rider.run.configurations.ProtocolLifetimedSettingsEditor -import com.jetbrains.rider.run.configurations.controls.ControlViewBuilder -import com.jetbrains.rider.run.configurations.controls.ProjectSelector -import com.jetbrains.rider.run.configurations.controls.TextEditor -import com.jetbrains.rider.run.configurations.controls.ViewSeparator +import com.jetbrains.rider.run.configurations.controls.* import com.jetbrains.rider.run.configurations.controls.startBrowser.BrowserSettingsEditor import com.jetbrains.rider.run.configurations.runnableProjectsModelIfAvailable +import me.rafaelldi.aspire.AspireBundle import javax.swing.JComponent class AspireHostSettingsEditor(private val project: Project) : @@ -19,9 +17,10 @@ class AspireHostSettingsEditor(private val project: Project) : viewModel = AspireHostConfigurationViewModel( lifetime, project.runnableProjectsModelIfAvailable, - ProjectSelector("Project:", "Project"), - ViewSeparator("Open browser"), - TextEditor("URL", "URL", lifetime), + ProjectSelector(AspireBundle.message("run.editor.project"), "Project"), + EnvironmentVariablesEditor(AspireBundle.message("run.editor.environment.variables"), "Environment_variables"), + ViewSeparator(AspireBundle.message("run.editor.open.browser")), + TextEditor(AspireBundle.message("run.editor.url"), "URL", lifetime), BrowserSettingsEditor("") ) return ControlViewBuilder(lifetime, project, "AspireHost").build(viewModel) @@ -32,6 +31,8 @@ class AspireHostSettingsEditor(private val project: Project) : if (selectedProject != null) { configuration.parameters.apply { projectFilePath = selectedProject.projectFilePath + trackEnvs = viewModel.trackEnvs + envs = viewModel.environmentVariablesEditor.envs.value trackUrl = viewModel.trackUrl startBrowserParameters.url = viewModel.urlEditor.text.value startBrowserParameters.browser = viewModel.dotNetBrowserSettingsEditor.settings.value.myBrowser @@ -47,6 +48,8 @@ class AspireHostSettingsEditor(private val project: Project) : configuration.parameters.apply { viewModel.reset( projectFilePath, + trackEnvs, + envs, trackUrl, startBrowserParameters ) diff --git a/src/main/resources/messages/AspireBundle.properties b/src/main/resources/messages/AspireBundle.properties index 7cdb1614..b8cd0b96 100644 --- a/src/main/resources/messages/AspireBundle.properties +++ b/src/main/resources/messages/AspireBundle.properties @@ -7,6 +7,11 @@ action.Aspire.Settings.description=Open plugin settings page action.Aspire.Help.text=Aspire Help action.Aspire.Help.description=Open plugin help +run.editor.project=Project: +run.editor.environment.variables=Environment variables: +run.editor.open.browser=Open browser +run.editor.url=URL + configurable.Aspire=Aspire configurable.Aspire.check.new.version=Check for new versions of the Aspire workload during startup configurable.Aspire.show.service=Show projects in the Services tool window