From e24e764640e1d222bbc268eb83ac8be9e887a520 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Wed, 7 Feb 2024 20:41:40 +0100 Subject: [PATCH] Add generate manifest action --- CHANGELOG.md | 4 + .../Project/AspireHostExtension.cs | 26 +++++++ .../AspireHostProjectPropertyRequest.cs | 13 ++++ .../actions/ManifestGenerationAction.kt | 56 ++++++++++++++ .../aspire/diagram/DiagramService.kt | 2 +- .../aspire/manifest/ManifestService.kt | 76 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 5 ++ .../messages/AspireBundle.properties | 4 + 8 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/dotnet/aspire-plugin/Project/AspireHostExtension.cs create mode 100644 src/dotnet/aspire-plugin/Project/AspireHostProjectPropertyRequest.cs create mode 100644 src/main/kotlin/me/rafaelldi/aspire/actions/ManifestGenerationAction.kt create mode 100644 src/main/kotlin/me/rafaelldi/aspire/manifest/ManifestService.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 17b9c3af..c6b422b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +### Added + +- Generate Aspire manifest action + ## [0.2.3] - 2024-01-22 ### Fixed diff --git a/src/dotnet/aspire-plugin/Project/AspireHostExtension.cs b/src/dotnet/aspire-plugin/Project/AspireHostExtension.cs new file mode 100644 index 00000000..63305420 --- /dev/null +++ b/src/dotnet/aspire-plugin/Project/AspireHostExtension.cs @@ -0,0 +1,26 @@ +using JetBrains.Application; +using JetBrains.ProjectModel; +using JetBrains.ProjectModel.ProjectsHost; +using JetBrains.ProjectModel.Properties; +using JetBrains.RdBackend.Common.Features.ProjectModel.View; +using JetBrains.Util; + +namespace AspirePlugin.Project; + +[ShellComponent] +public class AspireHostExtension : ProjectModelViewPresenterExtension +{ + public override bool TryAddUserData(IProjectMark projectMark, IProject? project, out string name, out string value) + { + var property = project?.GetUniqueRequestedProjectProperty(AspireHostProjectPropertyRequest.IsAspireHost); + if (property.IsNullOrEmpty()) + { + return base.TryAddUserData(projectMark, project, out name, out value); + } + + name = AspireHostProjectPropertyRequest.IsAspireHost; + value = property; + + return true; + } +} \ No newline at end of file diff --git a/src/dotnet/aspire-plugin/Project/AspireHostProjectPropertyRequest.cs b/src/dotnet/aspire-plugin/Project/AspireHostProjectPropertyRequest.cs new file mode 100644 index 00000000..969180bb --- /dev/null +++ b/src/dotnet/aspire-plugin/Project/AspireHostProjectPropertyRequest.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using JetBrains.Application; +using JetBrains.Application.Parts; +using JetBrains.ProjectModel.Properties; + +namespace AspirePlugin.Project; + +[ShellComponent(Instantiation.DemandAnyThreadSafe)] +public class AspireHostProjectPropertyRequest : IProjectPropertiesRequest +{ + public const string IsAspireHost = "IsAspireHost"; + public IEnumerable RequestedProperties => new[] { IsAspireHost }; +} \ No newline at end of file diff --git a/src/main/kotlin/me/rafaelldi/aspire/actions/ManifestGenerationAction.kt b/src/main/kotlin/me/rafaelldi/aspire/actions/ManifestGenerationAction.kt new file mode 100644 index 00000000..a33d50b9 --- /dev/null +++ b/src/main/kotlin/me/rafaelldi/aspire/actions/ManifestGenerationAction.kt @@ -0,0 +1,56 @@ +package me.rafaelldi.aspire.actions + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.platform.backend.workspace.virtualFile +import com.jetbrains.rider.model.RdProjectDescriptor +import com.jetbrains.rider.projectView.nodes.getUserData +import com.jetbrains.rider.projectView.workspace.getProjectModelEntity +import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost +import me.rafaelldi.aspire.manifest.ManifestService + +class ManifestGenerationAction : AnAction() { + override fun actionPerformed(event: AnActionEvent) { + val project = event.project ?: return + val entity = event.dataContext.getProjectModelEntity() ?: return + val descriptor = entity.descriptor as? RdProjectDescriptor ?: return + if (!descriptor.isDotNetCore) return + val isAspireHost = descriptor.getUserData("IsAspireHost") + if (isAspireHost?.equals("true", true) != true) return + val file = entity.url?.virtualFile ?: return + + ManifestService.getInstance(project).generateManifest(file.toNioPath()) + } + + override fun update(event: AnActionEvent) { + val project = event.project + if (project == null) { + event.presentation.isEnabledAndVisible = false + return + } + + val runtime = RiderDotNetActiveRuntimeHost.getInstance(project).dotNetCoreRuntime.value + if (runtime == null) { + event.presentation.isEnabledAndVisible = false + return + } + + val entity = event.dataContext.getProjectModelEntity() + val descriptor = entity?.descriptor + if (descriptor == null || descriptor !is RdProjectDescriptor || !descriptor.isDotNetCore) { + event.presentation.isEnabledAndVisible = false + return + } + + val isAspireHost = descriptor.getUserData("IsAspireHost") + if (isAspireHost?.equals("true", true) != true) { + event.presentation.isEnabledAndVisible = false + return + } + + event.presentation.isEnabledAndVisible = true + } + + override fun getActionUpdateThread() = ActionUpdateThread.BGT +} \ No newline at end of file diff --git a/src/main/kotlin/me/rafaelldi/aspire/diagram/DiagramService.kt b/src/main/kotlin/me/rafaelldi/aspire/diagram/DiagramService.kt index 4a17eff3..95538d5b 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/diagram/DiagramService.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/diagram/DiagramService.kt @@ -65,7 +65,7 @@ class DiagramService(private val project: Project) { initSelectionListeners() initToolbarActions(title) }).thenAccept { - val diagramState = DiagramState(title, it.first) + val diagramState = DiagramState(it.first) diagramState.generateGroups() diagramState.applyChanges() diff --git a/src/main/kotlin/me/rafaelldi/aspire/manifest/ManifestService.kt b/src/main/kotlin/me/rafaelldi/aspire/manifest/ManifestService.kt new file mode 100644 index 00000000..e4324a2f --- /dev/null +++ b/src/main/kotlin/me/rafaelldi/aspire/manifest/ManifestService.kt @@ -0,0 +1,76 @@ +@file:Suppress("UnstableApiUsage") + +package me.rafaelldi.aspire.manifest + +import com.intellij.execution.util.ExecUtil +import com.intellij.ide.util.PsiNavigationSupport +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.rd.util.withUiContext +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.platform.ide.progress.withBackgroundProgress +import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import me.rafaelldi.aspire.AspireBundle +import java.nio.file.Path +import kotlin.io.path.absolutePathString + +@Service(Service.Level.PROJECT) +class ManifestService(private val project: Project, private val scope: CoroutineScope) { + companion object { + fun getInstance(project: Project) = project.service() + private val LOG = logger() + + private const val MANIFEST_FILE_NAME = "aspire-manifest.json" + } + + fun generateManifest(hostProjectPath: Path) { + scope.launch(Dispatchers.Default) { + withBackgroundProgress(project, AspireBundle.message("progress.generating.aspire.manifest")) { + val runtime = RiderDotNetActiveRuntimeHost.getInstance(project).dotNetCoreRuntime.value + if (runtime == null) { + LOG.warn("Unable to find .NET runtime") + return@withBackgroundProgress + } + + val commandLine = runtime.createCommandLine( + listOf( + "msbuild", + "/t:GenerateAspireManifest", + "/p:AspireManifestPublishOutputPath=$MANIFEST_FILE_NAME" + ) + ) + val directoryPath = hostProjectPath.parent + commandLine.workDirectory = directoryPath.toFile() + LOG.debug(commandLine.commandLineString) + + val output = ExecUtil.execAndGetOutput(commandLine) + if (output.checkSuccess(LOG)) { + val manifestPath = directoryPath.resolve(MANIFEST_FILE_NAME) + val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(manifestPath.absolutePathString()) + if (file != null && file.isValid) { + withUiContext { + PsiNavigationSupport.getInstance().createNavigatable(project, file, -1).navigate(true) + } + } + } else { + withUiContext { + Notification( + "Aspire", + AspireBundle.message("notification.manifest.unable.to.generate"), + output.stderr, + NotificationType.WARNING + ) + .notify(project) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index a5bb9a71..0e78f6de 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -48,6 +48,11 @@ + + + diff --git a/src/main/resources/messages/AspireBundle.properties b/src/main/resources/messages/AspireBundle.properties index 18324e74..40380332 100644 --- a/src/main/resources/messages/AspireBundle.properties +++ b/src/main/resources/messages/AspireBundle.properties @@ -8,6 +8,8 @@ action.Aspire.Dashboard.text=Open Aspire Dashboard action.Aspire.Dashboard.description=Open Aspire dashboard in browser action.Aspire.Diagram.text=Show Diagram action.Aspire.Diagram.description=Show distributed traces diagram +action.Aspire.Manifest.text=Generate Aspire Manifest +action.Aspire.Manifest.description=Generate .NET Aspire manifest run.editor.project=Project: run.editor.environment.variables=Environment variables: @@ -20,6 +22,7 @@ configurable.Aspire.show.service=Show projects in the Services tool window configurable.Aspire.collect.telemetry=Collect OpenTelemetry data (experimental) progress.updating.aspire.workload=Updating Aspire workload +progress.generating.aspire.manifest=Generating Aspire Manifest notification.new.version.is.available=A new version of Aspire workload is available notifications.update.aspire.workload=Update Aspire workload @@ -27,6 +30,7 @@ notifications.do.not.check.for.updates=Do not check for updates notifications.aspire.workload.updated=Aspire workload is successfully updated notifications.aspire.workload.update.failed=Aspire workload update failed notification.aspire.workload.update.elevated=Rider needs elevated permissions to update workload +notification.manifest.unable.to.generate=Unable to generate Aspire manifest service.tab.information=Information service.tab.information.name=Name: