From aecc7145063571c0c85305c1cd84710aded07a1c Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Fri, 15 Nov 2024 10:45:35 +0100 Subject: [PATCH 01/13] Rename FunctionsVersionMsBuildService --- .../legacy/PublishableProjectModelExtensions.kt | 8 ++++---- .../msbuild/AzureFunctionsVersionInspection.kt | 2 +- .../coreTools/FunctionCoreToolsInfoProvider.kt | 8 ++++---- ...rvice.kt => FunctionsVersionMsBuildService.kt} | 15 +++++++++++---- 4 files changed, 20 insertions(+), 13 deletions(-) rename PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/{FunctionCoreToolsMsBuildService.kt => FunctionsVersionMsBuildService.kt} (58%) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/PublishableProjectModelExtensions.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/PublishableProjectModelExtensions.kt index be0939ea354..294e38c9f9d 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/PublishableProjectModelExtensions.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/PublishableProjectModelExtensions.kt @@ -12,7 +12,7 @@ import com.intellij.openapi.project.Project import com.jetbrains.rider.model.PublishableProjectModel import com.jetbrains.rider.model.projectModelTasks import com.jetbrains.rider.projectView.solution -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsMsBuildService +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettingsService import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionWorkerRuntime import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem @@ -69,9 +69,9 @@ suspend fun PublishableProjectModel.getFunctionStack( .getFunctionLocalSettings(this) val workerRuntime = functionLocalSettings?.values?.workerRuntime ?: FunctionWorkerRuntime.DOTNET_ISOLATED val azureFunctionVersion = withContext(Dispatchers.EDT) { - FunctionCoreToolsMsBuildService - .getInstance() - .requestAzureFunctionsVersion(project, this@getFunctionStack.projectFilePath) + FunctionsVersionMsBuildService + .getInstance(project) + .requestAzureFunctionsVersion(this@getFunctionStack.projectFilePath) ?.trimStart('v', 'V') ?: "4" } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/codeInspection/msbuild/AzureFunctionsVersionInspection.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/codeInspection/msbuild/AzureFunctionsVersionInspection.kt index 2b5e758d24a..d846fc5d29e 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/codeInspection/msbuild/AzureFunctionsVersionInspection.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/codeInspection/msbuild/AzureFunctionsVersionInspection.kt @@ -14,7 +14,7 @@ import com.intellij.psi.xml.XmlElementType import com.intellij.psi.xml.XmlTag import com.intellij.xml.util.XmlUtil import com.microsoft.azure.toolkit.intellij.legacy.function.FUNCTIONS_CORE_TOOLS_KNOWN_SUPPORTED_VERSIONS -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION class AzureFunctionsVersionInspection : XmlSuppressableInspectionTool() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : XmlElementVisitor() { diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt index b3830569a48..39b29e63f5e 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt @@ -12,7 +12,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.registry.Registry import com.intellij.util.concurrency.ThreadingAssertions -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION import com.microsoft.azure.toolkit.intellij.legacy.function.isFunctionCoreToolsExecutable import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver @@ -33,9 +33,9 @@ class FunctionCoreToolsInfoProvider { projectFilePath: String, allowDownload: Boolean ): Pair? { - val azureFunctionsVersion = FunctionCoreToolsMsBuildService - .getInstance() - .requestAzureFunctionsVersion(project, projectFilePath) + val azureFunctionsVersion = FunctionsVersionMsBuildService + .getInstance(project) + .requestAzureFunctionsVersion(projectFilePath) if (azureFunctionsVersion == null) { LOG.warn("Could not determine project MSBuild property '${PROPERTY_AZURE_FUNCTIONS_VERSION}'") diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsMsBuildService.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionsVersionMsBuildService.kt similarity index 58% rename from PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsMsBuildService.kt rename to PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionsVersionMsBuildService.kt index da0af6edfc3..abf13ec6966 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsMsBuildService.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionsVersionMsBuildService.kt @@ -11,14 +11,21 @@ import com.jetbrains.rider.azure.model.AzureFunctionsVersionRequest import com.jetbrains.rider.azure.model.functionAppDaemonModel import com.jetbrains.rider.projectView.solution -@Service -class FunctionCoreToolsMsBuildService { +@Service(Service.Level.PROJECT) +class FunctionsVersionMsBuildService(private val project: Project) { companion object { - fun getInstance() = service() + fun getInstance(project: Project) = project.service() const val PROPERTY_AZURE_FUNCTIONS_VERSION = "AzureFunctionsVersion" } - suspend fun requestAzureFunctionsVersion(project: Project, projectFilePath: String) = + /** + * Requests the version of Azure Functions runtime for a given project. + * + * @param projectFilePath The file path of the project for which the Azure Functions version is requested. + * + * @return The value of `AzureFunctionsVersion` MSBuild property. + */ + suspend fun requestAzureFunctionsVersion(projectFilePath: String) = project.solution .functionAppDaemonModel .getAzureFunctionsVersion From 0f0aff3b36812a4c774024d7a9cc1c909f6c13dd Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Fri, 15 Nov 2024 10:50:50 +0100 Subject: [PATCH 02/13] Add a service to work with the tooling feed --- .../toolingFeed/FunctionToolingFeedFilter.kt | 58 +++++ .../toolingFeed/FunctionsToolingFeed.kt | 50 +++++ .../FunctionsToolingFeedService.kt | 202 ++++++++++++++++++ .../toolingFeed/FunctionsToolingRelease.kt | 11 + 4 files changed, 321 insertions(+) create mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionToolingFeedFilter.kt create mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeed.kt create mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt create mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionToolingFeedFilter.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionToolingFeedFilter.kt new file mode 100644 index 00000000000..faa1fce1d90 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionToolingFeedFilter.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +package com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed + +import com.intellij.openapi.util.SystemInfo +import com.intellij.util.system.CpuArch + +data class FunctionToolingFeedFilter( + val os: String, + val architectures: List, + val sizes: List +) + +fun getReleaseFilterForCurrentSystem() = when { + SystemInfo.isWindows && CpuArch.isIntel64() -> FunctionToolingFeedFilter( + "Windows", + listOf("x64"), + listOf("minified", "full") + ) + + SystemInfo.isWindows && CpuArch.isArm64() -> FunctionToolingFeedFilter( + "Windows", + listOf("arm64", "x64"), + listOf("minified", "full") + ) + + SystemInfo.isWindows -> FunctionToolingFeedFilter( + "Windows", + listOf("x86"), + listOf("minified", "full") + ) + + SystemInfo.isMac && CpuArch.isArm64() -> FunctionToolingFeedFilter( + "MacOS", + listOf("arm64", "x64"), + listOf("full") + ) + + SystemInfo.isMac -> FunctionToolingFeedFilter( + "MacOS", + listOf("x64"), + listOf("full") + ) + + SystemInfo.isLinux -> FunctionToolingFeedFilter( + "Linux", + listOf("x64"), + listOf("full") + ) + + else -> FunctionToolingFeedFilter( + "Unknown", + listOf("x64"), + listOf("full") + ) +} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeed.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeed.kt new file mode 100644 index 00000000000..8cb70143a44 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeed.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +package com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ReleaseFeed( + @SerialName("tags") + val tags: Map, + @SerialName("releases") + val releases: Map +) + +@Serializable +data class Tag( + @SerialName("release") + val release: String?, + @SerialName("releaseQuality") + val releaseQuality: String?, + @SerialName("hidden") + val hidden: Boolean +) + +@Serializable +data class Release( + @SerialName("templates") + val templates: String?, + @SerialName("coreTools") + val coreTools: List +) + +@Serializable +data class CoreToolsRelease( + @SerialName("OS") + val os: String?, + @SerialName("Architecture") + val architecture: String?, + @SerialName("downloadLink") + val downloadLink: String?, + @SerialName("sha2") + val sha2: String?, + @SerialName("size") + val size: String?, + @SerialName("default") + val default: Boolean +) \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt new file mode 100644 index 00000000000..70469b7c1f6 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +@file:OptIn(ExperimentalSerializationApi::class) + +package com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.trace +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.registry.Registry +import com.intellij.util.io.ZipUtil +import com.intellij.util.net.ssl.CertificateManager +import com.jetbrains.rd.util.concurrentMapOf +import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.core.isEmpty +import io.ktor.utils.io.core.readBytes +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import java.io.File +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.exists + +@Service(Service.Level.APP) +class FunctionsToolingFeedService : Disposable { + companion object { + fun getInstance(): FunctionsToolingFeedService = service() + private val LOG = logger() + } + + private val releaseCache = concurrentMapOf() + + private val client = HttpClient(CIO) { + engine { + https { + trustManager = CertificateManager.getInstance().trustManager + } + } + install(ContentNegotiation) { + json(Json { + explicitNulls = false + ignoreUnknownKeys = true + allowTrailingComma = true + }) + } + } + + /** + * Downloads and saves the Azure Functions tooling release feed if the release cache is empty. + * + * @return Result wrapping any exception encountered during the execution. + */ + suspend fun downloadAndSaveReleaseFeed() = kotlin.runCatching { + if (releaseCache.isNotEmpty()) return@runCatching + + val feed = getReleaseFeed() + val releaseTags = feed.tags + .toSortedMap() + .filterValues { !it.releaseQuality.isNullOrEmpty() && !it.release.isNullOrEmpty() } + val releaseFilter = getReleaseFilterForCurrentSystem() + + for ((releaseTagName, releaseTag) in releaseTags) { + val releaseFromTag = releaseTag.release ?: continue + val release = feed.releases[releaseFromTag] ?: continue + val coreToolsRelease = release.findCoreToolsRelease(releaseFilter) ?: continue + LOG.debug("Release for Azure core tools version ${releaseTagName.lowercase()}: ${releaseTag.release}; ${coreToolsRelease.downloadLink}") + + val releaseKey = releaseTagName.lowercase() + releaseCache.putIfAbsent( + releaseKey, + FunctionsToolingRelease(releaseKey, releaseFromTag, coreToolsRelease.downloadLink ?: "") + ) + } + } + + /** + * Retrieves the file system path for the latest available Azure Functions tooling release based on the provided Azure Functions runtime version. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to get the latest tooling release path. + * @return The path to the latest Azure Functions tooling release, or null if the release could not be determined. + */ + fun getPathForLatestFunctionsToolingRelease(azureFunctionsVersion: String): Path? { + val toolingRelease = getLatestFunctionsToolingRelease(azureFunctionsVersion) ?: return null + val downloadRoot = getReleaseDownloadRoot() + return downloadRoot.resolve(toolingRelease.functionsVersion).resolve(toolingRelease.coreToolsVersion) + } + + /** + * Downloads the latest Azure Functions tooling release for the specified Azure Functions runtime version. + * + * This method fetches the release information, determines if the release has already been downloaded, + * and if not, it downloads the release, extracts it to the appropriate directory and cleans up any temporary files. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to download the latest tooling release. + * @return A Result wrapping the path to the latest Azure Functions tooling release. + */ + suspend fun downloadLatestFunctionsToolingRelease(azureFunctionsVersion: String) = kotlin.runCatching { + downloadAndSaveReleaseFeed().getOrThrow() + + val toolingRelease = getLatestFunctionsToolingRelease(azureFunctionsVersion) + ?: error("Unable to obtain latest tooling release") + val toolingReleasePath = getPathForLatestFunctionsToolingRelease(azureFunctionsVersion) + ?: error(IllegalStateException("Unable to obtain a path of the latest tooling release")) + val funcExecutablePath = toolingReleasePath.resolve("func.exe") //todo: for mac and linux + if (funcExecutablePath.exists()) { + LOG.trace { "The release $toolingRelease is already downloaded" } + return@runCatching toolingReleasePath + } + + val tempFile = FileUtil.createTempFile( + File(FileUtil.getTempDirectory()), + "AzureFunctions-${toolingRelease.functionsVersion}-${toolingRelease.coreToolsVersion}", + ".zip", + true, + true + ) + + withContext(Dispatchers.IO) { + client.prepareGet(toolingRelease.coreToolsArtifactUrl).execute { httpResponse -> + val channel: ByteReadChannel = httpResponse.body() + while (!channel.isClosedForRead) { + val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) + while (!packet.isEmpty) { + val bytes = packet.readBytes() + tempFile.appendBytes(bytes) + } + } + } + } + + if (!toolingReleasePath.exists()) { + toolingReleasePath.createDirectories() + } + + ZipUtil.extract(tempFile.toPath(), toolingReleasePath, null, true) + + if (tempFile.exists()) tempFile.delete() + + return@runCatching toolingReleasePath + } + + private suspend fun getReleaseFeed(): ReleaseFeed { + val feedUrl = getReleaseFeedUrl() + val response = withContext(Dispatchers.IO) { + client.get(feedUrl) + } + return response.body() + } + + private fun getReleaseFeedUrl() = Registry.get("azure.function_app.core_tools.feed.url").asString() + + + private fun getLatestFunctionsToolingRelease(azureFunctionsVersion: String): FunctionsToolingRelease? { + val toolingRelease = releaseCache[azureFunctionsVersion.lowercase()] + if (toolingRelease == null) { + LOG.warn("Could not determine Azure Functions core tools release for version: '$azureFunctionsVersion'") + return null + } + + return toolingRelease + } + + private fun getReleaseDownloadRoot(): Path { + val settings = AzureFunctionSettings.getInstance() + return Path(settings.functionDownloadPath) + } + + private fun Release.findCoreToolsRelease(releaseFilter: FunctionToolingFeedFilter) = + coreTools + .asSequence() + .filter { + it.os.equals(releaseFilter.os, ignoreCase = true) && !it.downloadLink.isNullOrEmpty() + } + .sortedWith( + compareBy { + releaseFilter.architectures.indexOfFirst { architecture -> + it.architecture.equals(architecture, ignoreCase = true) + }.let { rank -> if (rank >= 0) rank else 9999 } + }.thenBy { + releaseFilter.sizes.indexOfFirst { size -> + it.size.equals(size, ignoreCase = true) + }.let { rank -> if (rank >= 0) rank else 9999 } + }) + .firstOrNull() + + override fun dispose() = client.close() +} diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt new file mode 100644 index 00000000000..f3924d693c2 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +package com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed + +data class FunctionsToolingRelease( + val functionsVersion: String, + val coreToolsVersion: String, //todo: is it a tag? + val coreToolsArtifactUrl: String +) \ No newline at end of file From a0258b50b7dca857cd596a3bf10c1fed189a6169 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Fri, 15 Nov 2024 15:41:22 +0100 Subject: [PATCH 03/13] Move all function core tools folder logic to the new manager class --- .../function/coreTools/FileExtensions.kt | 5 +- .../coreTools/FunctionCoreToolsManager2.kt | 111 ++++++++++++++++++ .../templates/AzureProjectTemplateType.kt | 2 +- .../templates/FunctionTemplateManager.kt | 51 +++----- .../templates/FunctionTemplateProvider.kt | 7 +- .../InstallFunctionProjectTemplateType.kt | 2 +- .../ReloadFunctionTemplateActivity.kt | 2 +- 7 files changed, 140 insertions(+), 40 deletions(-) create mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt index 7ab90336efe..aa7a1b15dc0 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt @@ -5,5 +5,8 @@ package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools import java.io.File +import java.nio.file.Path +import kotlin.io.path.nameWithoutExtension -fun File.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) \ No newline at end of file +fun File.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) +fun Path.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt new file mode 100644 index 00000000000..b66222265c3 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.trace +import com.intellij.openapi.util.SystemInfo +import com.intellij.util.text.VersionComparatorUtil +import com.microsoft.azure.toolkit.intellij.legacy.function.isFunctionCoreToolsExecutable +import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings +import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver +import java.nio.file.Path +import kotlin.io.path.* +import kotlin.sequences.lastOrNull +import kotlin.sequences.sortedWith + +@Service(Service.Level.APP) +class FunctionCoreToolsManager2 { + companion object { + fun getInstance(): FunctionCoreToolsManager2 = service() + private val LOG = logger() + } + + /** + * Retrieves the path to the Azure Function core tools folder for a specified Azure Function runtime version. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to get the folder. + * @return The path to the Azure Function core tools folder for the specified Azure Function runtime version, or null if not found. + */ + fun getFunctionCoreToolsPathForVersion(azureFunctionsVersion: String): Path? { + val settings = AzureFunctionSettings.getInstance() + val coreToolsPathEntries = settings.azureCoreToolsPathEntries + val coreToolsPathFromSettings = coreToolsPathEntries + .firstOrNull { it.functionsVersion.equals(azureFunctionsVersion, ignoreCase = true) } + ?.coreToolsPath + ?.let(::resolveCoreToolsPathFromSettings) + if (coreToolsPathFromSettings?.exists() == true) { + LOG.trace { "Get Azure Function core tools path from the settings: $coreToolsPathFromSettings" } + return coreToolsPathFromSettings + } + + val coreToolsRootFolder = settings.functionDownloadPath + val coreToolsPathForVersion = Path(settings.functionDownloadPath).resolve(azureFunctionsVersion) + if (coreToolsPathForVersion.notExists()) { + LOG.info("Unable to find any downloaded core tools in the folder $coreToolsRootFolder for version $azureFunctionsVersion") + return null + } + + LOG.trace { "Get Azure Function core tools path from the download folder: $coreToolsPathForVersion" } + return findCoreToolsPathWithLatestTag(coreToolsPathForVersion) + } + + private fun resolveCoreToolsPathFromSettings(coreToolsPathValue: String): Path? { + if (coreToolsPathValue.isEmpty()) return null + + if (isFunctionCoreToolsExecutable(coreToolsPathValue)) { + val coreToolsPathFromEnvironment = FunctionCliResolver.resolveFunc()?.let(::Path) ?: return null + LOG.trace { "Resolved core tools path from environment: $coreToolsPathFromEnvironment" } + return patchCoreToolsPath(coreToolsPathFromEnvironment) + } else { + val coreToolsPathFromSettings = Path(coreToolsPathValue) + LOG.trace { "Resolved core tools path from settings: $coreToolsPathFromSettings" } + return patchCoreToolsPath(coreToolsPathFromSettings) + } + } + + private fun patchCoreToolsPath(funcCoreToolsPath: Path): Path { + val normalizedPath = if (funcCoreToolsPath.isRegularFile() && funcCoreToolsPath.isFunctionCoreTools()) { + funcCoreToolsPath.parent + } else { + funcCoreToolsPath + } + if (!SystemInfo.isWindows) return normalizedPath + + // Chocolatey and NPM have shim executables that are not .NET (and not debuggable). + // If it's a Chocolatey install or NPM install, rewrite the path to the tools path + // where the func executable is located. + // + // Logic is similar to com.microsoft.azure.toolkit.intellij.function.runner.core.FunctionCliResolver.resolveFunc() + val chocolateyPath = normalizedPath.resolve("../lib/azure-functions-core-tools/tools").normalize() + if (chocolateyPath.exists()) { + LOG.info("Functions core tools path $normalizedPath is Chocolatey-installed. Rewriting path to $chocolateyPath") + return chocolateyPath + } + + val npmPath = normalizedPath.resolve("../node_modules/azure-functions-core-tools/bin").normalize() + if (npmPath.exists()) { + LOG.info("Functions core tools path $normalizedPath is NPM-installed. Rewriting path to $npmPath") + return npmPath + } + + return normalizedPath + } + + private fun findCoreToolsPathWithLatestTag(coreToolsPathForVersion: Path): Path? { + val latestTagFolderForVersion = coreToolsPathForVersion + .listDirectoryEntries() + .asSequence() + .filter { it.isDirectory() } + .sortedWith { first, second -> VersionComparatorUtil.compare(first.name, second.name) } + .lastOrNull { it.exists() } + + LOG.trace { "The latest tag folder from $coreToolsPathForVersion is $latestTagFolderForVersion" } + + return latestTagFolderForVersion + } +} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/AzureProjectTemplateType.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/AzureProjectTemplateType.kt index 3755dc6654f..efe85a7cd18 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/AzureProjectTemplateType.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/AzureProjectTemplateType.kt @@ -20,7 +20,7 @@ class AzureProjectTemplateType : PredefinedProjectTemplateType() { override val icon = IntelliJAzureIcons.getIcon("/icons/FunctionApp/TemplateAzureFunc.svg") override val order = 90 override val shouldHide: Boolean - get() = !FunctionTemplateManager.getInstance().areRegistered() + get() = !FunctionTemplateManager.getInstance().areAzureFunctionTemplatesInstalled() override fun acceptableForTemplate(projectTemplate: RdProjectTemplate): Boolean { return projectTemplate.hasClassification("Azure Functions") diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt index e6f189f6aad..7b47bbe263f 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt @@ -10,8 +10,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.util.concurrency.ThreadingAssertions import com.jetbrains.rider.projectView.projectTemplates.providers.RiderProjectTemplateProvider import com.microsoft.azure.toolkit.intellij.legacy.function.FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsInfoProvider -import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager2 import java.nio.file.Path import kotlin.io.path.* @@ -25,37 +24,38 @@ class FunctionTemplateManager { private val netIsolatedPath = Path("net-isolated") - fun areRegistered(): Boolean { - val coreToolFolder = getCoreToolFolder() ?: return false + fun areAzureFunctionTemplatesInstalled(): Boolean { + val functionCoreToolsFolder = FunctionCoreToolsManager2 + .getInstance() + .getFunctionCoreToolsPathForVersion(FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION) + ?: return false return RiderProjectTemplateProvider .getUserTemplateSources() - .any { (isFunctionProjectTemplate(it.toPath(), coreToolFolder)) && it.exists() } + .any { (isFunctionProjectTemplate(it.toPath(), functionCoreToolsFolder)) && it.exists() } } - suspend fun tryReload(allowDownload: Boolean) { + fun reloadAzureFunctionTemplates() { ThreadingAssertions.assertBackgroundThread() - // Determine core tools info for the latest supported Azure Functions version - val toolsInfoProvider = FunctionCoreToolsInfoProvider.getInstance() - val coreToolsInfo = toolsInfoProvider - .retrieveForVersion(FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION, allowDownload) + val functionCoreToolsFolder = FunctionCoreToolsManager2 + .getInstance() + .getFunctionCoreToolsPathForVersion(FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION) ?: return - removePreviousTemplates(coreToolsInfo.coreToolsPath) + removePreviousTemplates(functionCoreToolsFolder.parent) - // Add available templates val templateFolders = listOf( - coreToolsInfo.coreToolsPath.resolve("templates"), - coreToolsInfo.coreToolsPath.resolve("templates/net6-isolated"), - coreToolsInfo.coreToolsPath.resolve("templates/net-isolated") + functionCoreToolsFolder.resolve("templates"), + functionCoreToolsFolder.resolve("templates/net6-isolated"), + functionCoreToolsFolder.resolve("templates/net-isolated") ).filter { it.exists() } for (templateFolder in templateFolders) { try { val templateFiles = templateFolder .listDirectoryEntries() - .filter { isFunctionProjectTemplate(it, coreToolsInfo.coreToolsPath) } + .filter { isFunctionProjectTemplate(it, functionCoreToolsFolder) } LOG.debug("Found ${templateFiles.size} function template(s) in $templateFolder") @@ -68,20 +68,6 @@ class FunctionTemplateManager { } } - private fun getCoreToolFolder(): Path? { - val settings = AzureFunctionSettings.getInstance() - val toolPathEntries = settings.azureCoreToolsPathEntries - val toolPathFromConfiguration = toolPathEntries - .firstOrNull { - it.functionsVersion.equals(FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION, ignoreCase = true) - } - ?.coreToolsPath - ?: return null - - return if (toolPathFromConfiguration.isNotEmpty()) Path(toolPathFromConfiguration).parent - else Path(settings.functionDownloadPath) - } - private fun isFunctionProjectTemplate(path: Path?, coreToolPath: Path): Boolean { if (path == null) return false if ( @@ -92,12 +78,13 @@ class FunctionTemplateManager { return path.startsWith(coreToolPath) } - private fun removePreviousTemplates(coreToolsPath: Path) { + private fun removePreviousTemplates(functionCoreToolsFolder: Path) { val templateSources = RiderProjectTemplateProvider .getUserTemplateSources() .map { it.toPath() } + templateSources.forEach { - if (it.startsWith(coreToolsPath)) { + if (it.startsWith(functionCoreToolsFolder)) { RiderProjectTemplateProvider.removeUserTemplateSource(it.toFile()) } else if (it.contains(netIsolatedPath)) { val index = it.lastIndexOf(netIsolatedPath) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateProvider.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateProvider.kt index ea559235ac5..34a1179eb33 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateProvider.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateProvider.kt @@ -25,11 +25,10 @@ class FunctionTemplateProvider : ProjectTemplateProvider { val templateManager = FunctionTemplateManager.getInstance() - if (!templateManager.areRegistered()) { + if (!templateManager.areAzureFunctionTemplatesInstalled()) { runWithModalProgressBlocking(DummyProject.getInstance(), "Reloading Azure templates...") { - templateManager.tryReload(false) - - if (!templateManager.areRegistered()) { + templateManager.reloadAzureFunctionTemplates() + if (!templateManager.areAzureFunctionTemplatesInstalled()) { result.set(setOf(InstallFunctionProjectTemplateType())) } } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionProjectTemplateType.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionProjectTemplateType.kt index 6b8371cfbe1..55352704a67 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionProjectTemplateType.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionProjectTemplateType.kt @@ -50,7 +50,7 @@ class InstallFunctionProjectTemplateType : ProjectTemplateType { override fun getComponent(): JComponent { return InstallFunctionToolComponent { runWithModalProgressBlocking(DummyProject.getInstance(), "Reloading Azure templates...") { - FunctionTemplateManager.getInstance().tryReload(false) + FunctionTemplateManager.getInstance().reloadAzureFunctionTemplates() } context.reloadTemplates.fire() }.getView() diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt index ef1d0e6f399..7ed8964b356 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.withContext class ReloadFunctionTemplateActivity: ProjectActivity { override suspend fun execute(project: Project) { withContext(Dispatchers.Default) { - FunctionTemplateManager.getInstance().tryReload(true) +// FunctionTemplateManager.getInstance().tryReload() } } } \ No newline at end of file From 8bf04c121087d6f48a97afde2325bc0461bdf859 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Fri, 15 Nov 2024 16:26:40 +0100 Subject: [PATCH 04/13] Download function core tooling from the new service --- .../coreTools/FunctionCoreToolsManager2.kt | 24 +++++++++++++++ .../templates/InstallFunctionToolComponent.kt | 12 ++++---- .../FunctionsToolingFeedService.kt | 30 ++++++++----------- .../toolingFeed/FunctionsToolingRelease.kt | 4 +-- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt index b66222265c3..b319bb5b995 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.util.SystemInfo import com.intellij.util.text.VersionComparatorUtil import com.microsoft.azure.toolkit.intellij.legacy.function.isFunctionCoreToolsExecutable import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings +import com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed.FunctionsToolingFeedService import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver import java.nio.file.Path import kotlin.io.path.* @@ -54,6 +55,29 @@ class FunctionCoreToolsManager2 { return findCoreToolsPathWithLatestTag(coreToolsPathForVersion) } + /** + * Downloads the latest Azure Function core tools release for the specified Azure Functions runtime version. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to download the latest core tools release. + * @return The path to the downloaded Azure Function core tools, or null if the download was unsuccessful. + */ + suspend fun downloadLatestFunctionCoreToolsForVersion(azureFunctionsVersion: String): Path? { + val downloadLatestReleaseResult = FunctionsToolingFeedService + .getInstance() + .downloadLatestFunctionsToolingRelease(azureFunctionsVersion) + + val latestReleasePath = downloadLatestReleaseResult.getOrNull() + if (latestReleasePath == null) { + LOG.warn( + "Unable to download the latest Azure Function core tooling release for version $azureFunctionsVersion", + downloadLatestReleaseResult.exceptionOrNull() + ) + return null + } + + return latestReleasePath + } + private fun resolveCoreToolsPathFromSettings(coreToolsPathValue: String): Path? { if (coreToolsPathValue.isEmpty()) return null diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt index 0beaa2bd14f..b50c3316b3e 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt @@ -2,7 +2,7 @@ * Copyright 2018-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -@file:Suppress("UnstableApiUsage") +@file:Suppress("UnstableApiUsage", "DialogTitleCapitalization") package com.microsoft.azure.toolkit.intellij.legacy.function.templates @@ -15,7 +15,7 @@ import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBInsets import com.jetbrains.rider.ui.components.base.Viewable import com.microsoft.azure.toolkit.intellij.legacy.function.FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsInfoProvider +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager2 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.swing.JComponent @@ -39,11 +39,9 @@ class InstallFunctionToolComponent(reloadTemplates: Runnable) : Viewable + client.prepareGet(toolingRelease.artifactUrl).execute { httpResponse -> val channel: ByteReadChannel = httpResponse.body() while (!channel.isClosedForRead) { val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) @@ -164,7 +154,6 @@ class FunctionsToolingFeedService : Disposable { private fun getReleaseFeedUrl() = Registry.get("azure.function_app.core_tools.feed.url").asString() - private fun getLatestFunctionsToolingRelease(azureFunctionsVersion: String): FunctionsToolingRelease? { val toolingRelease = releaseCache[azureFunctionsVersion.lowercase()] if (toolingRelease == null) { @@ -180,6 +169,11 @@ class FunctionsToolingFeedService : Disposable { return Path(settings.functionDownloadPath) } + private fun getPathForLatestFunctionsToolingRelease(toolingRelease: FunctionsToolingRelease): Path { + val downloadRoot = getReleaseDownloadRoot() + return downloadRoot.resolve(toolingRelease.functionsVersion).resolve(toolingRelease.releaseTag) + } + private fun Release.findCoreToolsRelease(releaseFilter: FunctionToolingFeedFilter) = coreTools .asSequence() diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt index f3924d693c2..20a5e6592fe 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingRelease.kt @@ -6,6 +6,6 @@ package com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed data class FunctionsToolingRelease( val functionsVersion: String, - val coreToolsVersion: String, //todo: is it a tag? - val coreToolsArtifactUrl: String + val releaseTag: String, + val artifactUrl: String ) \ No newline at end of file From 1b7114b2ca150f8088bf025da4fefa0e501ca022 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Mon, 18 Nov 2024 13:34:17 +0100 Subject: [PATCH 05/13] Use new methods to get azure core tools while running --- .../coreTools/FunctionCoreToolsManager2.kt | 19 ++++++++ .../runner/localRun/FunctionNetCoreRuntime.kt | 8 ++-- .../localRun/FunctionRunExecutorFactory.kt | 46 ++++++++++++++----- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt index b319bb5b995..ee0e98b3e58 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt @@ -26,6 +26,25 @@ class FunctionCoreToolsManager2 { private val LOG = logger() } + /** + * Retrieves the path to the Azure Function core tools for a specified Azure Function runtime version + * or downloads the latest core tools if not available. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to get or download the core tools. + * @return The path to the Azure Function core tools for the specified Azure Function runtime version, + * or null if the path cannot be determined or the download fails. + */ + suspend fun getFunctionCoreToolsPathOrDownloadForVersion(azureFunctionsVersion: String): Path? { + val existingCoreToolsPath = getFunctionCoreToolsPathForVersion(azureFunctionsVersion) + if (existingCoreToolsPath != null) { + LOG.trace { "Found existing core tools path: $existingCoreToolsPath" } + return existingCoreToolsPath + } + + LOG.trace { "Existing core tools aren't found, downloading the latest one" } + return downloadLatestFunctionCoreToolsForVersion(azureFunctionsVersion) + } + /** * Retrieves the path to the Azure Function core tools folder for a specified Azure Function runtime version. * diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt index 3b0ce41233f..952c001183a 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt @@ -15,13 +15,13 @@ import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.DotNetRuntime import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntimeType -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsInfo import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionWorkerRuntime import kotlinx.coroutines.ExperimentalCoroutinesApi +import java.nio.file.Path import kotlin.io.path.absolutePathString class FunctionNetCoreRuntime( - private val coreToolsInfo: FunctionCoreToolsInfo, + private val functionCoreToolsExecutablePath: Path, private val workerRuntime: FunctionWorkerRuntime, private val lifetime: Lifetime ) : DotNetRuntime(DotNetCoreRuntimeType) { @@ -32,7 +32,7 @@ class FunctionNetCoreRuntime( if (commandLine.parametersList.parametersCount > 0 && commandLine.parametersList[0] != exePath) { commandLine.parametersList.prepend(exePath) } - commandLine.exePath = coreToolsInfo.coreToolsExecutable.absolutePathString() + commandLine.exePath = functionCoreToolsExecutablePath.absolutePathString() } } @@ -69,7 +69,7 @@ class FunctionNetCoreRuntime( activeDotnetRuntime, dotNetExecutable, executionEnvironment, - coreToolsInfo.coreToolsExecutable.absolutePathString() + activeDotnetRuntime.cliExePath ) } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt index da07182602e..8bf56e2cda6 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt @@ -22,7 +22,9 @@ import com.intellij.notification.NotificationAction import com.intellij.notification.NotificationType import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.trace import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SystemInfo import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rider.azure.model.AzureFunctionWorkerModel import com.jetbrains.rider.azure.model.AzureFunctionWorkerModelRequest @@ -36,8 +38,9 @@ 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.msNet.MsNetRuntime -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsInfo -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsInfoProvider +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager2 +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettings @@ -63,11 +66,28 @@ class FunctionRunExecutorFactory( environment: ExecutionEnvironment, lifetime: Lifetime ): RunProfileState { - val coreToolsInfoResult = FunctionCoreToolsInfoProvider - .getInstance() - .retrieveForProject(project, parameters.projectFilePath, true) - ?: throw CantRunException("Can't run Azure Functions host. No Azure Functions Core Tools information could be determined") - val (azureFunctionsVersion, coreToolsInfo) = coreToolsInfoResult + val azureFunctionsRuntimeVersion = FunctionsVersionMsBuildService + .getInstance(project) + .requestAzureFunctionsVersion(parameters.projectFilePath) + if (azureFunctionsRuntimeVersion == null) { + LOG.warn("Could not determine project MSBuild property '${PROPERTY_AZURE_FUNCTIONS_VERSION}'") + throw CantRunException("Can't run Azure Functions host. Could not determine project MSBuild property '${PROPERTY_AZURE_FUNCTIONS_VERSION}'") + } + + val functionCoreToolsPath = withContext(Dispatchers.Default) { + FunctionCoreToolsManager2 + .getInstance() + .getFunctionCoreToolsPathOrDownloadForVersion(azureFunctionsRuntimeVersion) + } + if (functionCoreToolsPath == null) { + LOG.warn("Unable to find or download Function core tools for the project '${parameters.projectFilePath}'") + throw CantRunException("Can't run Azure Functions host. Unable to find locally or download Function core tools") + } + + val functionCoreToolsExecutablePath = + if (SystemInfo.isWindows) functionCoreToolsPath.resolve("func.exe") + else functionCoreToolsPath.resolve("func") + LOG.trace { "Function core tools executable path: $functionCoreToolsExecutablePath" } val projectFilePath = Path(parameters.projectFilePath) val functionLocalSettings = withContext(Dispatchers.Default) { @@ -76,7 +96,7 @@ class FunctionRunExecutorFactory( .getFunctionLocalSettings(projectFilePath) } - val dotNetExecutable = getDotNetExecutable(coreToolsInfo, functionLocalSettings) + val dotNetExecutable = getDotNetExecutable(functionCoreToolsExecutablePath, functionLocalSettings) ?: throw CantRunException("Can't run Azure Functions host. Unable to create .NET executable") LOG.debug("Patching host.json file to reflect run configuration parameters") @@ -91,10 +111,10 @@ class FunctionRunExecutorFactory( } LOG.debug { "Worker runtime: $workerRuntime" } - val runtimeToExecute = if (azureFunctionsVersion.equals("v1", ignoreCase = true)) { + val runtimeToExecute = if (azureFunctionsRuntimeVersion.equals("v1", ignoreCase = true)) { MsNetRuntime() } else { - FunctionNetCoreRuntime(coreToolsInfo, workerRuntime, lifetime) + FunctionNetCoreRuntime(functionCoreToolsExecutablePath, workerRuntime, lifetime) } LOG.debug { "Configuration will be executed on ${runtimeToExecute.javaClass.name}" } @@ -106,7 +126,7 @@ class FunctionRunExecutorFactory( } private suspend fun getDotNetExecutable( - coreToolsInfo: FunctionCoreToolsInfo, + functionCoreToolsExecutablePath: Path, functionLocalSettings: FunctionLocalSettings? ): DotNetExecutable? { val runnableProjects = project.solution.runnableProjectsModel.projects.valueOrNull @@ -120,7 +140,7 @@ class FunctionRunExecutorFactory( return null } - val coreToolsExecutablePath = coreToolsInfo.coreToolsExecutable.absolutePathString() + val coreToolsExecutablePath = functionCoreToolsExecutablePath.absolutePathString() val projectOutput = runnableProject .projectOutputs @@ -164,6 +184,8 @@ class FunctionRunExecutorFactory( .getInstance(project) .processEnvironment(runParameters, projectProcessOptions) + LOG.trace { "Function executable: $executableParameters" } + return DotNetExecutable( executableParameters.executablePath ?: coreToolsExecutablePath, executableParameters.tfm ?: projectOutput?.tfm, From b032cc1b4ef8b1a7ec514d21fe79d0a2883673d0 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Mon, 18 Nov 2024 13:37:55 +0100 Subject: [PATCH 06/13] Remove previous classes and rename --- .../coreTools/FunctionCoreToolsInfo.kt | 9 - .../FunctionCoreToolsInfoProvider.kt | 174 --------- .../coreTools/FunctionCoreToolsManager.kt | 357 ++++++------------ .../coreTools/FunctionCoreToolsManager2.kt | 154 -------- .../{FileExtensions.kt => PathExtensions.kt} | 2 - .../localRun/FunctionRunExecutorFactory.kt | 4 +- .../templates/FunctionTemplateManager.kt | 6 +- .../templates/InstallFunctionToolComponent.kt | 4 +- 8 files changed, 115 insertions(+), 595 deletions(-) delete mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfo.kt delete mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt delete mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt rename PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/{FileExtensions.kt => PathExtensions.kt} (76%) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfo.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfo.kt deleted file mode 100644 index e7153277cf7..00000000000 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfo.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. - */ - -package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools - -import java.nio.file.Path - -data class FunctionCoreToolsInfo(val coreToolsPath: Path, val coreToolsExecutable: Path) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt deleted file mode 100644 index 39b29e63f5e..00000000000 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsInfoProvider.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. - */ - -package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools - -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.debug -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.SystemInfo -import com.intellij.openapi.util.registry.Registry -import com.intellij.util.concurrency.ThreadingAssertions -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION -import com.microsoft.azure.toolkit.intellij.legacy.function.isFunctionCoreToolsExecutable -import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings -import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.io.File - -@Service -class FunctionCoreToolsInfoProvider { - companion object { - fun getInstance(): FunctionCoreToolsInfoProvider = service() - - private val LOG = logger() - } - - suspend fun retrieveForProject( - project: Project, - projectFilePath: String, - allowDownload: Boolean - ): Pair? { - val azureFunctionsVersion = FunctionsVersionMsBuildService - .getInstance(project) - .requestAzureFunctionsVersion(projectFilePath) - - if (azureFunctionsVersion == null) { - LOG.warn("Could not determine project MSBuild property '${PROPERTY_AZURE_FUNCTIONS_VERSION}'") - return null - } - - LOG.debug { "Azure Functions version: $azureFunctionsVersion" } - - val coreToolsInfo = withContext(Dispatchers.Default) { - retrieveForVersion(azureFunctionsVersion, allowDownload) - } - if (coreToolsInfo == null) { - LOG.warn("Could not determine Azure Functions Core Tools information") - return null - } - - LOG.debug { "Core tools executable: ${coreToolsInfo.coreToolsExecutable}" } - - return azureFunctionsVersion to coreToolsInfo - } - - suspend fun retrieveForVersion( - azureFunctionsVersion: String, - allowDownload: Boolean - ): FunctionCoreToolsInfo? { - ThreadingAssertions.assertBackgroundThread() - - val coreToolsFromConfiguration = retrieveFromConfiguration(azureFunctionsVersion) - if (coreToolsFromConfiguration != null) return coreToolsFromConfiguration - - LOG.info("Could not determine Azure Core Tools path from configuration") - - val coreToolsFromFeed = retrieveFromFeed(azureFunctionsVersion, allowDownload) - if (coreToolsFromFeed != null) return coreToolsFromFeed - - LOG.info("Could not determine Azure Core Tools path from feed") - - return null - } - - private fun retrieveFromConfiguration(azureFunctionsVersion: String): FunctionCoreToolsInfo? { - val settings = AzureFunctionSettings.getInstance() - val toolsPathEntries = settings.azureCoreToolsPathEntries - LOG.debug("Azure Core Tools path entries: ${toolsPathEntries.joinToString { "${it.functionsVersion}: ${it.coreToolsPath}" }}") - - val toolsPathFromConfiguration = toolsPathEntries - .firstOrNull { it.functionsVersion.equals(azureFunctionsVersion, ignoreCase = true) } - ?.coreToolsPath - ?: return null - - if (toolsPathFromConfiguration.isEmpty()) return null - - // If the configuration is func/func.cmd/func.exe, try and determine the full path from the environment - if (isFunctionCoreToolsExecutable(toolsPathFromConfiguration)) { - val toolsPathFromEnvironment = FunctionCliResolver.resolveFunc()?.let { resolveFromPath(File(it)) } - if (toolsPathFromEnvironment == null) { - LOG.warn("Azure Functions Core Tools path is set to '$toolsPathFromConfiguration' in configuration, but could not be resolved.") - } - - return toolsPathFromEnvironment - } - - return resolveFromPath(File(toolsPathFromConfiguration)) - } - - private suspend fun retrieveFromFeed( - azureFunctionsVersion: String, - allowDownload: Boolean - ): FunctionCoreToolsInfo? { - val coreToolsPathFromFeed = FunctionCoreToolsManager - .getInstance() - .demandCoreToolsPathForVersion( - azureFunctionsVersion, - Registry.get("azure.function_app.core_tools.feed.url").asString(), - allowDownload - ) ?: return null - - return resolveFromPath(coreToolsPathFromFeed) - } - - private fun resolveFromPath(funcCoreToolsPath: File): FunctionCoreToolsInfo? { - val patchedPath = patchCoreToolsPath(funcCoreToolsPath) - - val executablePath = if (SystemInfo.isWindows) { - patchedPath.resolve("func.exe") - } else { - patchedPath.resolve("func") - } - - if (!executablePath.exists()) { - return null - } - - if (!executablePath.canExecute()) { - LOG.warn("Updating executable flag for $executablePath...") - try { - executablePath.setExecutable(true) - } catch (s: SecurityException) { - LOG.error("Failed setting executable flag for $executablePath", s) - } - } - - return FunctionCoreToolsInfo(patchedPath.toPath(), executablePath.toPath()) - } - - private fun patchCoreToolsPath(funcCoreToolsPath: File): File { - val normalizedPath = normalizeCoreToolsPath(funcCoreToolsPath) - if (!SystemInfo.isWindows) return normalizedPath - - // Chocolatey and NPM have shim executables that are not .NET (and not debuggable). - // If it's a Chocolatey install or NPM install, rewrite the path to the tools path - // where the func executable is located. - // - // Logic is similar to com.microsoft.azure.toolkit.intellij.function.runner.core.FunctionCliResolver.resolveFunc() - val chocolateyPath = normalizedPath.resolve("../lib/azure-functions-core-tools/tools").normalize() - if (chocolateyPath.exists()) { - LOG.info("Functions core tools path ${normalizedPath.path} is Chocolatey-installed. Rewriting path to ${chocolateyPath.path}") - return chocolateyPath - } - - val npmPath = normalizedPath.resolve("../node_modules/azure-functions-core-tools/bin").normalize() - if (npmPath.exists()) { - LOG.info("Functions core tools path ${normalizedPath.path} is NPM-installed. Rewriting path to ${npmPath.path}") - return npmPath - } - - return normalizedPath - } - - private fun normalizeCoreToolsPath(path: File) = - if (path.isFile && path.isFunctionCoreTools()) { - path.parentFile - } else { - path - } -} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt index fc512d78dc4..300675e9568 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt @@ -1,295 +1,154 @@ /* - * Copyright 2018-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -@file:Suppress("UnstableApiUsage") - package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.trace import com.intellij.openapi.util.SystemInfo -import com.intellij.openapi.util.io.FileUtil -import com.intellij.openapi.util.registry.Registry -import com.intellij.util.io.HttpRequests -import com.intellij.util.io.ZipUtil -import com.intellij.util.system.CpuArch import com.intellij.util.text.VersionComparatorUtil -import com.jetbrains.rd.util.concurrentMapOf +import com.microsoft.azure.toolkit.intellij.legacy.function.isFunctionCoreToolsExecutable import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings -import java.io.File -import java.io.IOException -import java.net.UnknownHostException - -@Service +import com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed.FunctionsToolingFeedService +import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver +import java.nio.file.Path +import kotlin.io.path.* +import kotlin.sequences.lastOrNull +import kotlin.sequences.sortedWith + +@Service(Service.Level.APP) class FunctionCoreToolsManager { companion object { fun getInstance(): FunctionCoreToolsManager = service() - private val LOG = logger() } - private val fixedReleases = mapOf( - ) - - private val releaseCache = concurrentMapOf() - - suspend fun demandCoreToolsPathForVersion( - azureFunctionsVersion: String, - releaseFeedUrl: String, - allowDownload: Boolean - ): File? { - val downloadRoot = resolveDownloadRoot() - - if (allowDownload && Registry.`is`("azure.function_app.core_tools.feed.enabled")) { - ensureReleaseCacheFromFeed(releaseFeedUrl) - - val coreToolsPath = resolveDownloadInfoForRelease(azureFunctionsVersion, downloadRoot) - ?.let { ensureReleaseDownloaded(it) } - - if (coreToolsPath != null) return coreToolsPath + /** + * Retrieves the path to the Azure Function core tools for a specified Azure Function runtime version + * or downloads the latest core tools if not available. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to get or download the core tools. + * @return The path to the Azure Function core tools for the specified Azure Function runtime version, + * or null if the path cannot be determined or the download fails. + */ + suspend fun getFunctionCoreToolsPathOrDownloadForVersion(azureFunctionsVersion: String): Path? { + val existingCoreToolsPath = getFunctionCoreToolsPathForVersion(azureFunctionsVersion) + if (existingCoreToolsPath != null) { + LOG.trace { "Found existing core tools path: $existingCoreToolsPath" } + return existingCoreToolsPath } - val coreToolsPath = tryResolveExistingCoreToolsPath(azureFunctionsVersion, downloadRoot) - if (coreToolsPath != null) return coreToolsPath - - return null + LOG.trace { "Existing core tools aren't found, downloading the latest one" } + return downloadLatestFunctionCoreToolsForVersion(azureFunctionsVersion) } - private fun resolveDownloadRoot(): File { + /** + * Retrieves the path to the Azure Function core tools folder for a specified Azure Function runtime version. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to get the folder. + * @return The path to the Azure Function core tools folder for the specified Azure Function runtime version, or null if not found. + */ + fun getFunctionCoreToolsPathForVersion(azureFunctionsVersion: String): Path? { val settings = AzureFunctionSettings.getInstance() - val downloadRoot = File(settings.functionDownloadPath) - - if (!downloadRoot.exists()) { - try { - downloadRoot.mkdir() - } catch (e: Exception) { - LOG.error("Error while creating download root: ${downloadRoot.path}", e) - } + val coreToolsPathEntries = settings.azureCoreToolsPathEntries + val coreToolsPathFromSettings = coreToolsPathEntries + .firstOrNull { it.functionsVersion.equals(azureFunctionsVersion, ignoreCase = true) } + ?.coreToolsPath + ?.let(::resolveCoreToolsPathFromSettings) + if (coreToolsPathFromSettings?.exists() == true) { + LOG.trace { "Get Azure Function core tools path from the settings: $coreToolsPathFromSettings" } + return coreToolsPathFromSettings } - return downloadRoot - } - - private suspend fun ensureReleaseCacheFromFeed(releaseFeedUrl: String) { - if (releaseCache.isNotEmpty()) return - - val releaseFilter = getReleaseFilter() - - LOG.debug("Azure Core Tools release filter: $releaseFilter") - - try { - val feed = FunctionCoreToolsReleaseFeedService.getInstance().getReleaseFeed(releaseFeedUrl) - - val releaseTags = feed.tags - .toSortedMap() - .filterValues { !it.releaseQuality.isNullOrEmpty() && !it.release.isNullOrEmpty() } - - for ((releaseTagName, releaseTag) in releaseTags) { - val releaseFromTag = fixedReleases[releaseTagName] ?: releaseTag.release ?: continue - val release = feed.releases[releaseFromTag] ?: continue - - val releaseCoreTools = getReleaseCoreTool(release, releaseFilter) ?: continue - - LOG.debug("Release for Azure Core Tools version ${releaseTagName.lowercase()}: ${releaseTag.release}; ${releaseCoreTools.downloadLink}") - - val releaseKey = releaseTagName.lowercase() - releaseCache.putIfAbsent( - releaseKey, - FunctionCoreToolsRelease(releaseKey, releaseFromTag, releaseCoreTools.downloadLink ?: "") - ) - } - } catch (e: UnknownHostException) { - LOG.warn("Could not download from Azure Functions Core Tools release feed URL at $releaseFeedUrl: $e") - } catch (e: IOException) { - LOG.warn("Could not download from Azure Functions Core Tools release feed URL at $releaseFeedUrl: $e") + val coreToolsRootFolder = settings.functionDownloadPath + val coreToolsPathForVersion = Path(settings.functionDownloadPath).resolve(azureFunctionsVersion) + if (coreToolsPathForVersion.notExists()) { + LOG.info("Unable to find any downloaded core tools in the folder $coreToolsRootFolder for version $azureFunctionsVersion") + return null } - } - - private fun getReleaseFilter() = when { - SystemInfo.isWindows && CpuArch.isIntel64() -> AzureCoreToolsFeedReleaseFilter( - "Windows", - listOf("x64"), - listOf("minified", "full") - ) - - SystemInfo.isWindows && CpuArch.isArm64() -> AzureCoreToolsFeedReleaseFilter( - "Windows", - listOf("arm64", "x64"), - listOf("minified", "full") - ) - - SystemInfo.isWindows -> AzureCoreToolsFeedReleaseFilter( - "Windows", - listOf("x86"), - listOf("minified", "full") - ) - SystemInfo.isMac && CpuArch.isArm64() -> AzureCoreToolsFeedReleaseFilter( - "MacOS", - listOf("arm64", "x64"), - listOf("full") - ) - - SystemInfo.isMac -> AzureCoreToolsFeedReleaseFilter( - "MacOS", - listOf("x64"), - listOf("full") - ) - - SystemInfo.isLinux -> AzureCoreToolsFeedReleaseFilter( - "Linux", - listOf("x64"), - listOf("full") - ) - - else -> AzureCoreToolsFeedReleaseFilter( - "Unknown", - listOf("x64"), - listOf("full") - ) + LOG.trace { "Get Azure Function core tools path from the download folder: $coreToolsPathForVersion" } + return findCoreToolsPathWithLatestTag(coreToolsPathForVersion) } - private fun getReleaseCoreTool(release: Release, releaseFilter: AzureCoreToolsFeedReleaseFilter) = - release.coreTools - .asSequence() - .filter { - it.os.equals(releaseFilter.os, ignoreCase = true) && - !it.downloadLink.isNullOrEmpty() - } - .sortedWith( - compareBy { - releaseFilter.architectures.indexOfFirst { architecture -> - it.architecture.equals(architecture, ignoreCase = true) - }.let { rank -> if (rank >= 0) rank else 9999 } - }.thenBy { - releaseFilter.sizes.indexOfFirst { size -> - it.size.equals(size, ignoreCase = true) - }.let { rank -> if (rank >= 0) rank else 9999 } - }) - .firstOrNull() - - private fun resolveDownloadInfoForRelease( - azureFunctionsVersion: String, - downloadRoot: File - ): FunctionCoreToolsDownloadInfo? { - val releaseInfo = releaseCache[azureFunctionsVersion.lowercase()] - if (releaseInfo == null) { - LOG.warn("Could not determine Azure Functions Core Tools release. Azure Functions version: '$azureFunctionsVersion'") + /** + * Downloads the latest Azure Function core tools release for the specified Azure Functions runtime version. + * + * @param azureFunctionsVersion The version of Azure Functions runtime for which to download the latest core tools release. + * @return The path to the downloaded Azure Function core tools, or null if the download was unsuccessful. + */ + suspend fun downloadLatestFunctionCoreToolsForVersion(azureFunctionsVersion: String): Path? { + val downloadLatestReleaseResult = FunctionsToolingFeedService + .getInstance() + .downloadLatestFunctionsToolingRelease(azureFunctionsVersion) + + val latestReleasePath = downloadLatestReleaseResult.getOrNull() + if (latestReleasePath == null) { + LOG.warn( + "Unable to download the latest Azure Function core tooling release for version $azureFunctionsVersion", + downloadLatestReleaseResult.exceptionOrNull() + ) return null } - val downloadFolderForTag = downloadRoot.resolve(releaseInfo.functionsVersion) - val downloadFolderForTagRelease = downloadFolderForTag.resolve(releaseInfo.coreToolsVersion) - - LOG.debug( - "Found Azure Functions Core Tools release from feed. " + - "Azure Functions version: '${releaseInfo.functionsVersion}'; " + - "Core Tools Version: ${releaseInfo.coreToolsVersion}; " + - "Expected download path: ${downloadFolderForTagRelease.path}" - ) - - return FunctionCoreToolsDownloadInfo( - downloadFolderForTag, - downloadFolderForTagRelease, - releaseInfo - ) + return latestReleasePath } - private fun ensureReleaseDownloaded(downloadInfo: FunctionCoreToolsDownloadInfo): File? { - if (downloadInfo.downloadFolderForTagAndRelease.exists()) { - return downloadInfo.downloadFolderForTagAndRelease - } - downloadRelease(downloadInfo) - - if (downloadInfo.downloadFolderForTagAndRelease.exists()) { - return downloadInfo.downloadFolderForTagAndRelease + private fun resolveCoreToolsPathFromSettings(coreToolsPathValue: String): Path? { + if (coreToolsPathValue.isEmpty()) return null + + if (isFunctionCoreToolsExecutable(coreToolsPathValue)) { + val coreToolsPathFromEnvironment = FunctionCliResolver.resolveFunc()?.let(::Path) ?: return null + LOG.trace { "Resolved core tools path from environment: $coreToolsPathFromEnvironment" } + return patchCoreToolsPath(coreToolsPathFromEnvironment) + } else { + val coreToolsPathFromSettings = Path(coreToolsPathValue) + LOG.trace { "Resolved core tools path from settings: $coreToolsPathFromSettings" } + return patchCoreToolsPath(coreToolsPathFromSettings) } - - return null } - private fun downloadRelease(downloadInfo: FunctionCoreToolsDownloadInfo) { - val tempFile = FileUtil.createTempFile( - File(FileUtil.getTempDirectory()), - "AzureFunctions-${downloadInfo.release.functionsVersion}-${downloadInfo.release.coreToolsVersion}", - ".zip", - true, - true - ) - - HttpRequests - .request(downloadInfo.release.coreToolsArtifactUrl) - .saveToFile(tempFile, null) - - try { - if (downloadInfo.downloadFolderForTag.exists()) { - downloadInfo.downloadFolderForTag.deleteRecursively() - } - downloadInfo.downloadFolderForTagAndRelease.mkdir() - } catch (e: Exception) { - LOG.error("Error while removing directory ${downloadInfo.downloadFolderForTag.path}", e) + private fun patchCoreToolsPath(funcCoreToolsPath: Path): Path { + val normalizedPath = if (funcCoreToolsPath.isRegularFile() && funcCoreToolsPath.isFunctionCoreTools()) { + funcCoreToolsPath.parent + } else { + funcCoreToolsPath } - - try { - ZipUtil.extract(tempFile.toPath(), downloadInfo.downloadFolderForTagAndRelease.toPath(), null, true) - } catch (e: Exception) { - LOG.error( - "Error while extracting ${tempFile.path} to ${downloadInfo.downloadFolderForTagAndRelease.path}", - e - ) - } - - try { - if (tempFile.exists()) tempFile.delete() - } catch (e: Exception) { - LOG.error("Error while removing temporary file ${tempFile.path}", e) + if (!SystemInfo.isWindows) return normalizedPath + + // Chocolatey and NPM have shim executables that are not .NET (and not debuggable). + // If it's a Chocolatey install or NPM install, rewrite the path to the tools path + // where the func executable is located. + // + // Logic is similar to com.microsoft.azure.toolkit.intellij.function.runner.core.FunctionCliResolver.resolveFunc() + val chocolateyPath = normalizedPath.resolve("../lib/azure-functions-core-tools/tools").normalize() + if (chocolateyPath.exists()) { + LOG.info("Functions core tools path $normalizedPath is Chocolatey-installed. Rewriting path to $chocolateyPath") + return chocolateyPath } - } - - private fun tryResolveExistingCoreToolsPath(azureFunctionsVersion: String, downloadRoot: File): File? { - val downloadFolderForTag = downloadRoot.resolve(azureFunctionsVersion.lowercase()) - if (!downloadFolderForTag.exists()) return null - val downloadFolderForTagRelease = downloadFolderForTag.listFiles(File::isDirectory) - ?.asSequence() - ?.sortedWith { first, second -> VersionComparatorUtil.compare(first?.name, second?.name) } - ?.lastOrNull { it.exists() && it.listFiles { file -> file.isFunctionCoreTools() }?.any() == true } - - if (downloadFolderForTagRelease != null) { - LOG.debug( - "Found existing Azure Functions Core Tools path. " + - "Azure Functions version: '${azureFunctionsVersion.lowercase()}'; " + - "Download path: ${downloadFolderForTagRelease.path}" - ) - - return downloadFolderForTagRelease + val npmPath = normalizedPath.resolve("../node_modules/azure-functions-core-tools/bin").normalize() + if (npmPath.exists()) { + LOG.info("Functions core tools path $normalizedPath is NPM-installed. Rewriting path to $npmPath") + return npmPath } - LOG.warn( - "Could not determine existing Azure Functions Core Tools path. " + - "Azure Functions version: '${azureFunctionsVersion.lowercase()}'" - ) - - return null + return normalizedPath } - private data class AzureCoreToolsFeedReleaseFilter( - val os: String, - val architectures: List, - val sizes: List - ) + private fun findCoreToolsPathWithLatestTag(coreToolsPathForVersion: Path): Path? { + val latestTagFolderForVersion = coreToolsPathForVersion + .listDirectoryEntries() + .asSequence() + .filter { it.isDirectory() } + .sortedWith { first, second -> VersionComparatorUtil.compare(first.name, second.name) } + .lastOrNull { it.exists() } - private data class FunctionCoreToolsRelease( - val functionsVersion: String, - val coreToolsVersion: String, - val coreToolsArtifactUrl: String - ) + LOG.trace { "The latest tag folder from $coreToolsPathForVersion is $latestTagFolderForVersion" } - private data class FunctionCoreToolsDownloadInfo( - val downloadFolderForTag: File, - val downloadFolderForTagAndRelease: File, - val release: FunctionCoreToolsRelease - ) + return latestTagFolderForVersion + } } \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt deleted file mode 100644 index ee0e98b3e58..00000000000 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager2.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. - */ - -package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools - -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.diagnostic.trace -import com.intellij.openapi.util.SystemInfo -import com.intellij.util.text.VersionComparatorUtil -import com.microsoft.azure.toolkit.intellij.legacy.function.isFunctionCoreToolsExecutable -import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings -import com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed.FunctionsToolingFeedService -import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver -import java.nio.file.Path -import kotlin.io.path.* -import kotlin.sequences.lastOrNull -import kotlin.sequences.sortedWith - -@Service(Service.Level.APP) -class FunctionCoreToolsManager2 { - companion object { - fun getInstance(): FunctionCoreToolsManager2 = service() - private val LOG = logger() - } - - /** - * Retrieves the path to the Azure Function core tools for a specified Azure Function runtime version - * or downloads the latest core tools if not available. - * - * @param azureFunctionsVersion The version of Azure Functions runtime for which to get or download the core tools. - * @return The path to the Azure Function core tools for the specified Azure Function runtime version, - * or null if the path cannot be determined or the download fails. - */ - suspend fun getFunctionCoreToolsPathOrDownloadForVersion(azureFunctionsVersion: String): Path? { - val existingCoreToolsPath = getFunctionCoreToolsPathForVersion(azureFunctionsVersion) - if (existingCoreToolsPath != null) { - LOG.trace { "Found existing core tools path: $existingCoreToolsPath" } - return existingCoreToolsPath - } - - LOG.trace { "Existing core tools aren't found, downloading the latest one" } - return downloadLatestFunctionCoreToolsForVersion(azureFunctionsVersion) - } - - /** - * Retrieves the path to the Azure Function core tools folder for a specified Azure Function runtime version. - * - * @param azureFunctionsVersion The version of Azure Functions runtime for which to get the folder. - * @return The path to the Azure Function core tools folder for the specified Azure Function runtime version, or null if not found. - */ - fun getFunctionCoreToolsPathForVersion(azureFunctionsVersion: String): Path? { - val settings = AzureFunctionSettings.getInstance() - val coreToolsPathEntries = settings.azureCoreToolsPathEntries - val coreToolsPathFromSettings = coreToolsPathEntries - .firstOrNull { it.functionsVersion.equals(azureFunctionsVersion, ignoreCase = true) } - ?.coreToolsPath - ?.let(::resolveCoreToolsPathFromSettings) - if (coreToolsPathFromSettings?.exists() == true) { - LOG.trace { "Get Azure Function core tools path from the settings: $coreToolsPathFromSettings" } - return coreToolsPathFromSettings - } - - val coreToolsRootFolder = settings.functionDownloadPath - val coreToolsPathForVersion = Path(settings.functionDownloadPath).resolve(azureFunctionsVersion) - if (coreToolsPathForVersion.notExists()) { - LOG.info("Unable to find any downloaded core tools in the folder $coreToolsRootFolder for version $azureFunctionsVersion") - return null - } - - LOG.trace { "Get Azure Function core tools path from the download folder: $coreToolsPathForVersion" } - return findCoreToolsPathWithLatestTag(coreToolsPathForVersion) - } - - /** - * Downloads the latest Azure Function core tools release for the specified Azure Functions runtime version. - * - * @param azureFunctionsVersion The version of Azure Functions runtime for which to download the latest core tools release. - * @return The path to the downloaded Azure Function core tools, or null if the download was unsuccessful. - */ - suspend fun downloadLatestFunctionCoreToolsForVersion(azureFunctionsVersion: String): Path? { - val downloadLatestReleaseResult = FunctionsToolingFeedService - .getInstance() - .downloadLatestFunctionsToolingRelease(azureFunctionsVersion) - - val latestReleasePath = downloadLatestReleaseResult.getOrNull() - if (latestReleasePath == null) { - LOG.warn( - "Unable to download the latest Azure Function core tooling release for version $azureFunctionsVersion", - downloadLatestReleaseResult.exceptionOrNull() - ) - return null - } - - return latestReleasePath - } - - private fun resolveCoreToolsPathFromSettings(coreToolsPathValue: String): Path? { - if (coreToolsPathValue.isEmpty()) return null - - if (isFunctionCoreToolsExecutable(coreToolsPathValue)) { - val coreToolsPathFromEnvironment = FunctionCliResolver.resolveFunc()?.let(::Path) ?: return null - LOG.trace { "Resolved core tools path from environment: $coreToolsPathFromEnvironment" } - return patchCoreToolsPath(coreToolsPathFromEnvironment) - } else { - val coreToolsPathFromSettings = Path(coreToolsPathValue) - LOG.trace { "Resolved core tools path from settings: $coreToolsPathFromSettings" } - return patchCoreToolsPath(coreToolsPathFromSettings) - } - } - - private fun patchCoreToolsPath(funcCoreToolsPath: Path): Path { - val normalizedPath = if (funcCoreToolsPath.isRegularFile() && funcCoreToolsPath.isFunctionCoreTools()) { - funcCoreToolsPath.parent - } else { - funcCoreToolsPath - } - if (!SystemInfo.isWindows) return normalizedPath - - // Chocolatey and NPM have shim executables that are not .NET (and not debuggable). - // If it's a Chocolatey install or NPM install, rewrite the path to the tools path - // where the func executable is located. - // - // Logic is similar to com.microsoft.azure.toolkit.intellij.function.runner.core.FunctionCliResolver.resolveFunc() - val chocolateyPath = normalizedPath.resolve("../lib/azure-functions-core-tools/tools").normalize() - if (chocolateyPath.exists()) { - LOG.info("Functions core tools path $normalizedPath is Chocolatey-installed. Rewriting path to $chocolateyPath") - return chocolateyPath - } - - val npmPath = normalizedPath.resolve("../node_modules/azure-functions-core-tools/bin").normalize() - if (npmPath.exists()) { - LOG.info("Functions core tools path $normalizedPath is NPM-installed. Rewriting path to $npmPath") - return npmPath - } - - return normalizedPath - } - - private fun findCoreToolsPathWithLatestTag(coreToolsPathForVersion: Path): Path? { - val latestTagFolderForVersion = coreToolsPathForVersion - .listDirectoryEntries() - .asSequence() - .filter { it.isDirectory() } - .sortedWith { first, second -> VersionComparatorUtil.compare(first.name, second.name) } - .lastOrNull { it.exists() } - - LOG.trace { "The latest tag folder from $coreToolsPathForVersion is $latestTagFolderForVersion" } - - return latestTagFolderForVersion - } -} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt similarity index 76% rename from PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt rename to PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt index aa7a1b15dc0..92a864144f2 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FileExtensions.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt @@ -4,9 +4,7 @@ package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools -import java.io.File import java.nio.file.Path import kotlin.io.path.nameWithoutExtension -fun File.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) fun Path.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt index 8bf56e2cda6..06c29a016f4 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt @@ -38,7 +38,7 @@ 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.msNet.MsNetRuntime -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager2 +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds @@ -75,7 +75,7 @@ class FunctionRunExecutorFactory( } val functionCoreToolsPath = withContext(Dispatchers.Default) { - FunctionCoreToolsManager2 + FunctionCoreToolsManager .getInstance() .getFunctionCoreToolsPathOrDownloadForVersion(azureFunctionsRuntimeVersion) } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt index 7b47bbe263f..ccf350e8645 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/FunctionTemplateManager.kt @@ -10,7 +10,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.util.concurrency.ThreadingAssertions import com.jetbrains.rider.projectView.projectTemplates.providers.RiderProjectTemplateProvider import com.microsoft.azure.toolkit.intellij.legacy.function.FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager2 +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager import java.nio.file.Path import kotlin.io.path.* @@ -25,7 +25,7 @@ class FunctionTemplateManager { private val netIsolatedPath = Path("net-isolated") fun areAzureFunctionTemplatesInstalled(): Boolean { - val functionCoreToolsFolder = FunctionCoreToolsManager2 + val functionCoreToolsFolder = FunctionCoreToolsManager .getInstance() .getFunctionCoreToolsPathForVersion(FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION) ?: return false @@ -38,7 +38,7 @@ class FunctionTemplateManager { fun reloadAzureFunctionTemplates() { ThreadingAssertions.assertBackgroundThread() - val functionCoreToolsFolder = FunctionCoreToolsManager2 + val functionCoreToolsFolder = FunctionCoreToolsManager .getInstance() .getFunctionCoreToolsPathForVersion(FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION) ?: return diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt index b50c3316b3e..5e7c8b917bd 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/InstallFunctionToolComponent.kt @@ -15,7 +15,7 @@ import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBInsets import com.jetbrains.rider.ui.components.base.Viewable import com.microsoft.azure.toolkit.intellij.legacy.function.FUNCTIONS_CORE_TOOLS_LATEST_SUPPORTED_VERSION -import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager2 +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.swing.JComponent @@ -39,7 +39,7 @@ class InstallFunctionToolComponent(reloadTemplates: Runnable) : Viewable Date: Mon, 18 Nov 2024 13:41:01 +0100 Subject: [PATCH 07/13] Refactoring --- .../runner/localRun/FunctionNetCoreRuntime.kt | 2 ++ .../FunctionIsolatedDebugProfileState.kt | 13 ++++++------- .../{ => profileStates}/FunctionRunProfileState.kt | 2 +- .../localRun/{ => runners}/FunctionDebugRunner.kt | 3 ++- .../localRun/{ => runners}/FunctionProgramRunner.kt | 5 +++-- .../azure-intellij-plugin-appservice-dotnet.xml | 4 ++-- 6 files changed, 16 insertions(+), 13 deletions(-) rename PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/{ => profileStates}/FunctionIsolatedDebugProfileState.kt (95%) rename PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/{ => profileStates}/FunctionRunProfileState.kt (98%) rename PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/{ => runners}/FunctionDebugRunner.kt (91%) rename PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/{ => runners}/FunctionProgramRunner.kt (78%) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt index 952c001183a..be5962feee8 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionNetCoreRuntime.kt @@ -16,6 +16,8 @@ import com.jetbrains.rider.runtime.DotNetRuntime import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntimeType import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionWorkerRuntime +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.profileStates.FunctionIsolatedDebugProfileState +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.profileStates.FunctionRunProfileState import kotlinx.coroutines.ExperimentalCoroutinesApi import java.nio.file.Path import kotlin.io.path.absolutePathString diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionIsolatedDebugProfileState.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/profileStates/FunctionIsolatedDebugProfileState.kt similarity index 95% rename from PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionIsolatedDebugProfileState.kt rename to PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/profileStates/FunctionIsolatedDebugProfileState.kt index d25f5be1bf9..20c4161a399 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionIsolatedDebugProfileState.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/profileStates/FunctionIsolatedDebugProfileState.kt @@ -1,10 +1,8 @@ /* - * Copyright 2018-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -@file:Suppress("UnstableApiUsage") - -package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun +package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.profileStates import com.intellij.execution.CantRunException import com.intellij.execution.ExecutionResult @@ -34,6 +32,7 @@ import com.jetbrains.rider.run.dotNetCore.toCPUKind import com.jetbrains.rider.run.msNet.MsNetAttachProfileState import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.DotNetRuntime +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionHostDebugLauncher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -70,7 +69,7 @@ class FunctionIsolatedDebugProfileState( val processExecutablePath = ParametersListUtil.parse(targetProcess.commandLine).firstOrNull() val processArchitecture = getPlatformArchitecture(lifetime, pid, environment.project) val processTargetFramework = processExecutablePath?.let { - DebuggerHelperHost + DebuggerHelperHost.Companion .getInstance(environment.project) .getAssemblyTargetFramework(it, lifetime) } @@ -96,7 +95,7 @@ class FunctionIsolatedDebugProfileState( } private suspend fun launchFunctionHostWaitingForDebugger(environment: ExecutionEnvironment): Pair { - val launcher = FunctionHostDebugLauncher.getInstance(environment.project) + val launcher = FunctionHostDebugLauncher.Companion.getInstance(environment.project) val (executionResult, pid) = withBackgroundProgress(environment.project, "Waiting for Azure Functions host to start...") { withContext(Dispatchers.Default) { @@ -162,7 +161,7 @@ class FunctionIsolatedDebugProfileState( private suspend fun getPlatformArchitecture(lifetime: Lifetime, pid: Int, project: Project): PlatformArchitecture { if (SystemInfo.isWindows) { - return DebuggerHelperHost + return DebuggerHelperHost.Companion .getInstance(project) .getProcessArchitecture(lifetime, pid) } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunProfileState.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/profileStates/FunctionRunProfileState.kt similarity index 98% rename from PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunProfileState.kt rename to PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/profileStates/FunctionRunProfileState.kt index b126ea4ae68..080a59cf53d 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunProfileState.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/profileStates/FunctionRunProfileState.kt @@ -2,7 +2,7 @@ * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun +package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.profileStates import com.intellij.execution.DefaultExecutionResult import com.intellij.execution.ExecutionResult diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionDebugRunner.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/runners/FunctionDebugRunner.kt similarity index 91% rename from PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionDebugRunner.kt rename to PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/runners/FunctionDebugRunner.kt index e9e5ac4f283..1a21b803af8 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionDebugRunner.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/runners/FunctionDebugRunner.kt @@ -2,7 +2,7 @@ * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun +package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.runners import com.intellij.execution.configurations.RunProfile import com.intellij.execution.configurations.RunProfileState @@ -11,6 +11,7 @@ import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.ui.RunContentDescriptor import com.jetbrains.rider.debugger.DotNetDebugRunner import com.jetbrains.rider.run.configurations.RequiresPreparationRunProfileState +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionRunConfiguration class FunctionDebugRunner : DotNetDebugRunner() { companion object { diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionProgramRunner.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/runners/FunctionProgramRunner.kt similarity index 78% rename from PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionProgramRunner.kt rename to PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/runners/FunctionProgramRunner.kt index b3402b77d52..6dea4718d4e 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionProgramRunner.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/runners/FunctionProgramRunner.kt @@ -1,12 +1,13 @@ /* - * Copyright 2018-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun +package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.runners import com.intellij.execution.configurations.RunProfile import com.intellij.execution.executors.DefaultRunExecutor import com.jetbrains.rider.debugger.DotNetProgramRunner +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionRunConfiguration class FunctionProgramRunner : DotNetProgramRunner() { companion object { diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml index 0fb0d12de26..0753375eb76 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml @@ -27,9 +27,9 @@ implementation="com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionRunConfigurationType"/> + implementation="com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.runners.FunctionProgramRunner"/> + implementation="com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.runners.FunctionDebugRunner"/> From d5459d1c893023ab200810a64c4c1931fd240f3c Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Mon, 18 Nov 2024 14:13:02 +0100 Subject: [PATCH 08/13] Fix the searching for the existing core tools --- .../function/coreTools/FunctionCoreToolsManager.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt index 300675e9568..ebcf29f3ec7 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt @@ -143,9 +143,14 @@ class FunctionCoreToolsManager { val latestTagFolderForVersion = coreToolsPathForVersion .listDirectoryEntries() .asSequence() - .filter { it.isDirectory() } - .sortedWith { first, second -> VersionComparatorUtil.compare(first.name, second.name) } - .lastOrNull { it.exists() } + .filter { it.isDirectory() && it.exists() } + .sortedWith { first, second -> -1 * VersionComparatorUtil.compare(first.name, second.name) } + .firstOrNull { + val coreToolExecutablePath = + if (SystemInfo.isWindows) it.resolve("func.exe") + else it.resolve("func") + coreToolExecutablePath.exists() + } LOG.trace { "The latest tag folder from $coreToolsPathForVersion is $latestTagFolderForVersion" } From e7ba42a4d3a81d34de2d9073378bd7738a7fc798 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Mon, 18 Nov 2024 15:04:02 +0100 Subject: [PATCH 09/13] Remove folders after unsuccessful download --- .../coreTools/FunctionCoreToolsManager.kt | 5 +- .../function/coreTools/PathExtensions.kt | 6 +- .../localRun/FunctionRunExecutorFactory.kt | 6 +- .../FunctionsToolingFeedService.kt | 86 +++++++++++-------- 4 files changed, 59 insertions(+), 44 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt index ebcf29f3ec7..31d1390c483 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt @@ -16,7 +16,6 @@ import com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed.Function import com.microsoft.azure.toolkit.lib.appservice.utils.FunctionCliResolver import java.nio.file.Path import kotlin.io.path.* -import kotlin.sequences.lastOrNull import kotlin.sequences.sortedWith @Service(Service.Level.APP) @@ -146,9 +145,7 @@ class FunctionCoreToolsManager { .filter { it.isDirectory() && it.exists() } .sortedWith { first, second -> -1 * VersionComparatorUtil.compare(first.name, second.name) } .firstOrNull { - val coreToolExecutablePath = - if (SystemInfo.isWindows) it.resolve("func.exe") - else it.resolve("func") + val coreToolExecutablePath = it.resolveFunctionCoreToolsExecutable() coreToolExecutablePath.exists() } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt index 92a864144f2..c9197e9f7e1 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/PathExtensions.kt @@ -4,7 +4,11 @@ package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools +import com.intellij.openapi.util.SystemInfo import java.nio.file.Path import kotlin.io.path.nameWithoutExtension -fun Path.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) \ No newline at end of file +fun Path.isFunctionCoreTools() = nameWithoutExtension.equals("func", ignoreCase = true) +fun Path.resolveFunctionCoreToolsExecutable(): Path = + if (SystemInfo.isWindows) resolve("func.exe") + else resolve("func") \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt index 06c29a016f4..d1723a7dfc8 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt @@ -24,7 +24,6 @@ import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.trace import com.intellij.openapi.project.Project -import com.intellij.openapi.util.SystemInfo import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rider.azure.model.AzureFunctionWorkerModel import com.jetbrains.rider.azure.model.AzureFunctionWorkerModelRequest @@ -41,6 +40,7 @@ import com.jetbrains.rider.runtime.msNet.MsNetRuntime import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionCoreToolsManager import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.FunctionsVersionMsBuildService.Companion.PROPERTY_AZURE_FUNCTIONS_VERSION +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.resolveFunctionCoreToolsExecutable import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettings @@ -84,9 +84,7 @@ class FunctionRunExecutorFactory( throw CantRunException("Can't run Azure Functions host. Unable to find locally or download Function core tools") } - val functionCoreToolsExecutablePath = - if (SystemInfo.isWindows) functionCoreToolsPath.resolve("func.exe") - else functionCoreToolsPath.resolve("func") + val functionCoreToolsExecutablePath = functionCoreToolsPath.resolveFunctionCoreToolsExecutable() LOG.trace { "Function core tools executable path: $functionCoreToolsExecutablePath" } val projectFilePath = Path(parameters.projectFilePath) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt index eff0d2ce6f8..40536106fe0 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt @@ -2,7 +2,7 @@ * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ -@file:OptIn(ExperimentalSerializationApi::class) +@file:OptIn(ExperimentalSerializationApi::class, ExperimentalPathApi::class) package com.microsoft.azure.toolkit.intellij.legacy.function.toolingFeed @@ -11,12 +11,12 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.trace -import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.registry.Registry import com.intellij.util.io.ZipUtil import com.intellij.util.net.ssl.CertificateManager import com.jetbrains.rd.util.concurrentMapOf +import com.microsoft.azure.toolkit.intellij.legacy.function.coreTools.resolveFunctionCoreToolsExecutable import com.microsoft.azure.toolkit.intellij.legacy.function.settings.AzureFunctionSettings import io.ktor.client.* import io.ktor.client.call.* @@ -33,8 +33,10 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import java.io.File import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.Path import kotlin.io.path.createDirectories +import kotlin.io.path.deleteRecursively import kotlin.io.path.exists @Service(Service.Level.APP) @@ -44,6 +46,8 @@ class FunctionsToolingFeedService : Disposable { private val LOG = logger() } + private val fixedReleases = mapOf() + private val releaseCache = concurrentMapOf() private val client = HttpClient(CIO) { @@ -72,11 +76,11 @@ class FunctionsToolingFeedService : Disposable { val feed = getReleaseFeed() val releaseTags = feed.tags .toSortedMap() - .filterValues { !it.releaseQuality.isNullOrEmpty() && !it.release.isNullOrEmpty() } + .filterValues { !it.releaseQuality.isNullOrEmpty() && !it.release.isNullOrEmpty() && !it.hidden } val releaseFilter = getReleaseFilterForCurrentSystem() for ((releaseTagName, releaseTag) in releaseTags) { - val releaseFromTag = releaseTag.release ?: continue + val releaseFromTag = fixedReleases[releaseTagName] ?: releaseTag.release ?: continue val release = feed.releases[releaseFromTag] ?: continue val coreToolsRelease = release.findCoreToolsRelease(releaseFilter) ?: continue LOG.debug("Release for Azure core tools version ${releaseTagName.lowercase()}: ${releaseTag.release}; ${coreToolsRelease.downloadLink}") @@ -98,50 +102,62 @@ class FunctionsToolingFeedService : Disposable { * @param azureFunctionsVersion The version of Azure Functions runtime for which to download the latest tooling release. * @return A Result wrapping the path to the latest Azure Functions tooling release. */ - suspend fun downloadLatestFunctionsToolingRelease(azureFunctionsVersion: String) = kotlin.runCatching { - downloadAndSaveReleaseFeed().getOrThrow() + suspend fun downloadLatestFunctionsToolingRelease(azureFunctionsVersion: String): Result { + downloadAndSaveReleaseFeed().onFailure { exception -> + return Result.failure(exception) + } val toolingRelease = getLatestFunctionsToolingRelease(azureFunctionsVersion) - ?: error("Unable to obtain latest tooling release") + if (toolingRelease == null) { + return Result.failure(IllegalStateException("Unable to obtain latest tooling release")) + } val toolingReleasePath = getPathForLatestFunctionsToolingRelease(toolingRelease) - val funcExecutablePath = - if (SystemInfo.isWindows) toolingReleasePath.resolve("func.exe") - else toolingReleasePath.resolve("func") - if (funcExecutablePath.exists()) { + val coreToolsExecutablePath = toolingReleasePath.resolveFunctionCoreToolsExecutable() + if (coreToolsExecutablePath.exists()) { LOG.trace { "The release $toolingRelease is already downloaded" } - return@runCatching toolingReleasePath + return Result.success(toolingReleasePath) } - val tempFile = FileUtil.createTempFile( - File(FileUtil.getTempDirectory()), - "AzureFunctions-${toolingRelease.functionsVersion}-${toolingRelease.releaseTag}", - ".zip", - true, - true - ) - - withContext(Dispatchers.IO) { - client.prepareGet(toolingRelease.artifactUrl).execute { httpResponse -> - val channel: ByteReadChannel = httpResponse.body() - while (!channel.isClosedForRead) { - val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) - while (!packet.isEmpty) { - val bytes = packet.readBytes() - tempFile.appendBytes(bytes) + try { + val tempFile = FileUtil.createTempFile( + File(FileUtil.getTempDirectory()), + "AzureFunctions-${toolingRelease.functionsVersion}-${toolingRelease.releaseTag}", + ".zip", + true, + true + ) + + LOG.trace { "Created a temporary file: ${tempFile.absolutePath}" } + + withContext(Dispatchers.IO) { + client.prepareGet(toolingRelease.artifactUrl).execute { httpResponse -> + val channel: ByteReadChannel = httpResponse.body() + while (!channel.isClosedForRead) { + val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) + while (!packet.isEmpty) { + val bytes = packet.readBytes() + tempFile.appendBytes(bytes) + } } } } - } - if (!toolingReleasePath.exists()) { - toolingReleasePath.createDirectories() - } + LOG.trace { "Downloaded core tooling archive to the ${tempFile.absolutePath}" } - ZipUtil.extract(tempFile.toPath(), toolingReleasePath, null, true) + if (!toolingReleasePath.exists()) { + toolingReleasePath.createDirectories() + } - if (tempFile.exists()) tempFile.delete() + ZipUtil.extract(tempFile.toPath(), toolingReleasePath, null, true) - return@runCatching toolingReleasePath + if (tempFile.exists()) tempFile.delete() + + return Result.success(toolingReleasePath) + } catch (e: Exception) { + toolingReleasePath.deleteRecursively() + LOG.warn("Unable to download Function core tools for the runtime version $azureFunctionsVersion") + return Result.failure(e) + } } private suspend fun getReleaseFeed(): ReleaseFeed { From c660b52593b078819552e02acdab835896625a89 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Mon, 18 Nov 2024 15:05:19 +0100 Subject: [PATCH 10/13] Remove ReloadFunctionTemplateActivity --- .../ReloadFunctionTemplateActivity.kt | 18 ------------------ ...azure-intellij-plugin-appservice-dotnet.xml | 2 -- 2 files changed, 20 deletions(-) delete mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt deleted file mode 100644 index 7ed8964b356..00000000000 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/templates/ReloadFunctionTemplateActivity.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2018-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. - */ - -package com.microsoft.azure.toolkit.intellij.legacy.function.templates - -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.ProjectActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class ReloadFunctionTemplateActivity: ProjectActivity { - override suspend fun execute(project: Project) { - withContext(Dispatchers.Default) { -// FunctionTemplateManager.getInstance().tryReload() - } - } -} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml index 0753375eb76..3717a912af8 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml @@ -12,8 +12,6 @@ - From 79d61788faf4119a1cb2e3f88c9872a8bdb2874a Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Tue, 19 Nov 2024 11:37:07 +0100 Subject: [PATCH 11/13] Create an activity to update and clean-up core tools folders --- .../coreTools/FunctionCoreToolsManager.kt | 118 ++++++++++++++++-- .../UpdateFunctionCoreToolsActivity.kt | 18 +++ .../FunctionsToolingFeedService.kt | 25 +++- ...zure-intellij-plugin-appservice-dotnet.xml | 2 + 4 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/UpdateFunctionCoreToolsActivity.kt diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt index 31d1390c483..50fd7946994 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/FunctionCoreToolsManager.kt @@ -2,6 +2,8 @@ * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. */ +@file:OptIn(ExperimentalPathApi::class) + package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools import com.intellij.openapi.components.Service @@ -23,6 +25,8 @@ class FunctionCoreToolsManager { companion object { fun getInstance(): FunctionCoreToolsManager = service() private val LOG = logger() + + private const val CORE_TOOLING_FOLDERS_COUNT = 5 } /** @@ -62,10 +66,14 @@ class FunctionCoreToolsManager { return coreToolsPathFromSettings } - val coreToolsRootFolder = settings.functionDownloadPath - val coreToolsPathForVersion = Path(settings.functionDownloadPath).resolve(azureFunctionsVersion) + val coreToolsDownloadFolder = settings.functionDownloadPath + if (coreToolsDownloadFolder.isEmpty()) { + LOG.info("Unable to find any downloaded core tools because tool download path is not set up") + return null + } + val coreToolsPathForVersion = Path(coreToolsDownloadFolder).resolve(azureFunctionsVersion) if (coreToolsPathForVersion.notExists()) { - LOG.info("Unable to find any downloaded core tools in the folder $coreToolsRootFolder for version $azureFunctionsVersion") + LOG.info("Unable to find any downloaded core tools in the folder $coreToolsDownloadFolder for version $azureFunctionsVersion") return null } @@ -96,6 +104,100 @@ class FunctionCoreToolsManager { return latestReleasePath } + /** + * Updates the Azure Function core tools to the latest versions managed by Rider. + * + * This method retrieves the current core tools path settings and determines the versions + * managed by Rider. It then checks if these versions already exist in the specified + * core tools download directory. If they do, it retrieves the tooling releases + * for these versions and updates the core tools accordingly. After updating, + * it cleans up any unnecessary core tools for the specified versions. + */ + suspend fun updateFunctionCoreTools() { + val settings = AzureFunctionSettings.getInstance() + val coreToolsPathEntries = settings.azureCoreToolsPathEntries + val versionsManagedByRider = coreToolsPathEntries + .filter { it.coreToolsPath.isEmpty() } + .map { it.functionsVersion } + .toMutableList() + versionsManagedByRider.add("v0") + + val coreToolsDownloadFolder = settings.functionDownloadPath + if (coreToolsDownloadFolder.isEmpty()) { + LOG.trace { "Unable to update core tools because tool download path is not set up" } + } + val coreToolsDownloadFolderPath = Path(coreToolsDownloadFolder) + val versionsToUpdate = versionsManagedByRider.filter { + coreToolsDownloadFolderPath.resolve(it).exists() + } + if (versionsToUpdate.isEmpty()) return + + val toolingReleases = FunctionsToolingFeedService + .getInstance() + .getFunctionsToolingReleaseForVersions(versionsToUpdate) + if (toolingReleases == null) { + LOG.trace { "Unable to get tooling releases for versions: ${versionsToUpdate.joinToString()}" } + return + } + + for (toolingRelease in toolingReleases) { + updateFunctionCoreToolsForVersion( + toolingRelease.functionsVersion, + toolingRelease.releaseTag, + coreToolsDownloadFolderPath + ) + cleanUpCoreToolsForVersion(toolingRelease.functionsVersion, coreToolsDownloadFolderPath) + } + } + + private suspend fun updateFunctionCoreToolsForVersion( + functionsVersion: String, + releaseTag: String, + coreToolsDownloadFolder: Path + ) { + val coreToolsPath = coreToolsDownloadFolder.resolve(functionsVersion).resolve(releaseTag) + if (coreToolsPath.exists() && coreToolsPath.resolveFunctionCoreToolsExecutable().exists()) { + LOG.trace { "Core tools with tag $releaseTag already exists" } + return + } + + FunctionsToolingFeedService + .getInstance() + .downloadLatestFunctionsToolingRelease(functionsVersion) + .onFailure { + LOG.warn("Unable to update core tools for version $functionsVersion", it) + } + } + + private fun cleanUpCoreToolsForVersion(functionsVersion: String, coreToolsDownloadFolder: Path) { + val coreToolsPathForVersion = coreToolsDownloadFolder.resolve(functionsVersion) + val tagFolders = coreToolsPathForVersion.listAllTagFolders() + + for (tagFolder in tagFolders) { + runCatching { + if (!tagFolder.resolveFunctionCoreToolsExecutable().exists()) { + LOG.trace { "Core tools folder $tagFolder is probably empty, removing it" } + tagFolder.deleteRecursively() + } + }.onFailure { + LOG.trace(it) + } + } + + val tagFoldersWithoutEmpty = coreToolsPathForVersion.listAllTagFolders().toList() + if (tagFoldersWithoutEmpty.size <= CORE_TOOLING_FOLDERS_COUNT) return + + val folderCountToDelete = tagFoldersWithoutEmpty.size - CORE_TOOLING_FOLDERS_COUNT + for (tagFolderToDelete in tagFoldersWithoutEmpty.takeLast(folderCountToDelete)) { + runCatching { + LOG.trace("Removing core tools folder $tagFolderToDelete") + tagFolderToDelete.deleteRecursively() + }.onFailure { + LOG.trace(it) + } + } + } + private fun resolveCoreToolsPathFromSettings(coreToolsPathValue: String): Path? { if (coreToolsPathValue.isEmpty()) return null @@ -140,10 +242,7 @@ class FunctionCoreToolsManager { private fun findCoreToolsPathWithLatestTag(coreToolsPathForVersion: Path): Path? { val latestTagFolderForVersion = coreToolsPathForVersion - .listDirectoryEntries() - .asSequence() - .filter { it.isDirectory() && it.exists() } - .sortedWith { first, second -> -1 * VersionComparatorUtil.compare(first.name, second.name) } + .listAllTagFolders() .firstOrNull { val coreToolExecutablePath = it.resolveFunctionCoreToolsExecutable() coreToolExecutablePath.exists() @@ -153,4 +252,9 @@ class FunctionCoreToolsManager { return latestTagFolderForVersion } + + private fun Path.listAllTagFolders() = listDirectoryEntries() + .asSequence() + .filter { it.isDirectory() && it.exists() } + .sortedWith { first, second -> -1 * VersionComparatorUtil.compare(first.name, second.name) } } \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/UpdateFunctionCoreToolsActivity.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/UpdateFunctionCoreToolsActivity.kt new file mode 100644 index 00000000000..9e2601e4545 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/coreTools/UpdateFunctionCoreToolsActivity.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +package com.microsoft.azure.toolkit.intellij.legacy.function.coreTools + +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class UpdateFunctionCoreToolsActivity: ProjectActivity { + override suspend fun execute(project: Project) { + withContext(Dispatchers.Default) { + FunctionCoreToolsManager.getInstance().updateFunctionCoreTools() + } + } +} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt index 40536106fe0..5bfa38d5a0b 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt @@ -104,14 +104,18 @@ class FunctionsToolingFeedService : Disposable { */ suspend fun downloadLatestFunctionsToolingRelease(azureFunctionsVersion: String): Result { downloadAndSaveReleaseFeed().onFailure { exception -> + LOG.warn("Unable to download Function tooling release feed", exception) return Result.failure(exception) } val toolingRelease = getLatestFunctionsToolingRelease(azureFunctionsVersion) if (toolingRelease == null) { - return Result.failure(IllegalStateException("Unable to obtain latest tooling release")) + return Result.failure(IllegalStateException("Unable to obtain latest function tooling release")) } val toolingReleasePath = getPathForLatestFunctionsToolingRelease(toolingRelease) + if (toolingReleasePath == null) { + return Result.failure(IllegalStateException("Unable to path to download function tooling release")) + } val coreToolsExecutablePath = toolingReleasePath.resolveFunctionCoreToolsExecutable() if (coreToolsExecutablePath.exists()) { LOG.trace { "The release $toolingRelease is already downloaded" } @@ -160,6 +164,15 @@ class FunctionsToolingFeedService : Disposable { } } + suspend fun getFunctionsToolingReleaseForVersions(azureFunctionsVersions: List): List? { + downloadAndSaveReleaseFeed().onFailure { exception -> + LOG.warn("Unable to download Function tooling release feed", exception) + return null + } + + return azureFunctionsVersions.mapNotNull { getLatestFunctionsToolingRelease(it) } + } + private suspend fun getReleaseFeed(): ReleaseFeed { val feedUrl = getReleaseFeedUrl() val response = withContext(Dispatchers.IO) { @@ -180,14 +193,16 @@ class FunctionsToolingFeedService : Disposable { return toolingRelease } - private fun getReleaseDownloadRoot(): Path { + private fun getReleaseDownloadRoot(): Path? { val settings = AzureFunctionSettings.getInstance() - return Path(settings.functionDownloadPath) + val coreToolsDownloadFolder = settings.functionDownloadPath + return if (coreToolsDownloadFolder.isNotEmpty()) Path(coreToolsDownloadFolder) + else null } - private fun getPathForLatestFunctionsToolingRelease(toolingRelease: FunctionsToolingRelease): Path { + private fun getPathForLatestFunctionsToolingRelease(toolingRelease: FunctionsToolingRelease): Path? { val downloadRoot = getReleaseDownloadRoot() - return downloadRoot.resolve(toolingRelease.functionsVersion).resolve(toolingRelease.releaseTag) + return downloadRoot?.resolve(toolingRelease.functionsVersion)?.resolve(toolingRelease.releaseTag) } private fun Release.findCoreToolsRelease(releaseFilter: FunctionToolingFeedFilter) = diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml index 3717a912af8..ee66c5b624b 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/resources/META-INF/azure-intellij-plugin-appservice-dotnet.xml @@ -14,6 +14,8 @@ implementation="com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.MigrateBuildProjectTaskActivity"/> + From 6ff9b9ce26dee59584b5f0986201a82da7a8803b Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Tue, 19 Nov 2024 13:41:54 +0100 Subject: [PATCH 12/13] Set core tools file permissions --- .../FunctionsToolingFeedService.kt | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt index 5bfa38d5a0b..314674c6ca5 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/toolingFeed/FunctionsToolingFeedService.kt @@ -11,6 +11,7 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.trace +import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.registry.Registry import com.intellij.util.io.ZipUtil @@ -32,12 +33,16 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import java.io.File +import java.nio.file.Files import java.nio.file.Path +import java.nio.file.attribute.PosixFilePermission import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.Path import kotlin.io.path.createDirectories import kotlin.io.path.deleteRecursively import kotlin.io.path.exists +import kotlin.io.path.isExecutable +import kotlin.io.path.setPosixFilePermissions @Service(Service.Level.APP) class FunctionsToolingFeedService : Disposable { @@ -148,13 +153,16 @@ class FunctionsToolingFeedService : Disposable { LOG.trace { "Downloaded core tooling archive to the ${tempFile.absolutePath}" } - if (!toolingReleasePath.exists()) { + if (!toolingReleasePath.exists()) toolingReleasePath.createDirectories() - } ZipUtil.extract(tempFile.toPath(), toolingReleasePath, null, true) - if (tempFile.exists()) tempFile.delete() + if (tempFile.exists()) + tempFile.delete() + + if (!coreToolsExecutablePath.isExecutable() && !SystemInfo.isWindows) + setExecutablePermissionsForCoreTools(coreToolsExecutablePath) return Result.success(toolingReleasePath) } catch (e: Exception) { @@ -223,5 +231,15 @@ class FunctionsToolingFeedService : Disposable { }) .firstOrNull() + private fun setExecutablePermissionsForCoreTools(coreToolsExecutable: Path) { + coreToolsExecutable.setPosixFilePermissions( + setOf( + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE + ) + ) + } + override fun dispose() = client.close() } From b76335509e4c42cb196d10e4a5b3d83f82e5fdef Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Tue, 19 Nov 2024 13:53:12 +0100 Subject: [PATCH 13/13] Update changelog --- PluginsAndFeatures/azure-toolkit-for-rider/CHANGELOG.md | 4 ++++ PluginsAndFeatures/azure-toolkit-for-rider/gradle.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/CHANGELOG.md b/PluginsAndFeatures/azure-toolkit-for-rider/CHANGELOG.md index eec1eaa007b..219e38aa184 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/CHANGELOG.md +++ b/PluginsAndFeatures/azure-toolkit-for-rider/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +### Changed + +- Reimplement Function core tools management + ## [4.3.4] - 2024-11-13 ### Fixed diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/gradle.properties b/PluginsAndFeatures/azure-toolkit-for-rider/gradle.properties index a3551e0b8b6..3c77ffa70ac 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/gradle.properties +++ b/PluginsAndFeatures/azure-toolkit-for-rider/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.jetbrains pluginName = azure-toolkit-for-rider pluginRepositoryUrl = https://github.com/JetBrains/azure-tools-for-intellij # SemVer format -> https://semver.org -pluginVersion = 4.3.4 +pluginVersion = 4.3.5 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 243