diff --git a/jetbrains-core/resources/META-INF/ext-rider.xml b/jetbrains-core/resources/META-INF/ext-rider.xml index 609a2afe2fa..b76b4dfd9f2 100644 --- a/jetbrains-core/resources/META-INF/ext-rider.xml +++ b/jetbrains-core/resources/META-INF/ext-rider.xml @@ -7,6 +7,9 @@ software.aws.toolkits.jetbrains.services.lambda.LambdaHost + + software.aws.toolkits.jetbrains.services.lambda.LambdaPsiHost + diff --git a/jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/execution/local/LocalLambdaRunSettingsEditorPanel.java b/jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/execution/local/LocalLambdaRunSettingsEditorPanel.java index 1d3aa5e969d..216a6fd5d45 100644 --- a/jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/execution/local/LocalLambdaRunSettingsEditorPanel.java +++ b/jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/execution/local/LocalLambdaRunSettingsEditorPanel.java @@ -75,7 +75,10 @@ public LocalLambdaRunSettingsEditorPanel(Project project) { runtime.addActionListener(e -> { int index = runtime.getSelectedIndex(); - if (index < 0) return; + if (index < 0) { + lastSelectedRuntime = null; + return; + } Runtime selectedRuntime = runtime.getItemAt(index); if (selectedRuntime == lastSelectedRuntime) return; lastSelectedRuntime = selectedRuntime; diff --git a/jetbrains-core/src/software/aws/toolkits/jetbrains/utils/CachingAsyncEvaluator.kt b/jetbrains-core/src/software/aws/toolkits/jetbrains/utils/CachingAsyncEvaluator.kt index 616566068a4..aa15954ddb7 100644 --- a/jetbrains-core/src/software/aws/toolkits/jetbrains/utils/CachingAsyncEvaluator.kt +++ b/jetbrains-core/src/software/aws/toolkits/jetbrains/utils/CachingAsyncEvaluator.kt @@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit abstract class CachingAsyncEvaluator { companion object { - private const val EVALUATE_BLOCKING_TIMEOUT_MS = 500 + private const val EVALUATE_BLOCKING_TIMEOUT_MS = 1000 } private val logger: Logger = LoggerFactory.getLogger(this::class.java) diff --git a/jetbrains-core/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/CreateRunConfiguration.kt b/jetbrains-core/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/CreateRunConfiguration.kt index 49206e00af3..c6081013f59 100644 --- a/jetbrains-core/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/CreateRunConfiguration.kt +++ b/jetbrains-core/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/CreateRunConfiguration.kt @@ -104,12 +104,12 @@ fun samRunConfiguration(project: Project): LocalLambdaRunConfiguration { return runConfiguration } -fun preWarmSamVersionCache(path: String?) { +fun preWarmSamVersionCache(path: String?, timeoutMs: Int = TEST_EVALUATE_BLOCKING_TIMEOUT_MS) { path ?: throw InvalidParameterException("Test SAM CLI executable path is not set") - SamVersionCache.evaluateBlocking(path, TEST_EVALUATE_BLOCKING_TIMEOUT_MS) + SamVersionCache.evaluateBlocking(path, timeoutMs) } -fun preWarmLambdaHandlerValidation(project: Project, runtime: Runtime, handler: String) { +fun preWarmLambdaHandlerValidation(project: Project, runtime: Runtime, handler: String, timeoutMs: Int = TEST_EVALUATE_BLOCKING_TIMEOUT_MS) { val handlerValidator = project.service() - handlerValidator.evaluateBlocking(LambdaHandlerValidator.LambdaEntry(project, runtime, handler), TEST_EVALUATE_BLOCKING_TIMEOUT_MS) + handlerValidator.evaluateBlocking(LambdaHandlerValidator.LambdaEntry(project, runtime, handler), timeoutMs) } diff --git a/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Lambda/LambdaPsiHost.cs b/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Lambda/LambdaPsiHost.cs new file mode 100644 index 00000000000..ad2c010ee5f --- /dev/null +++ b/jetbrains-rider/ReSharper.AWS/src/AWS.Psi/Lambda/LambdaPsiHost.cs @@ -0,0 +1,28 @@ +using JetBrains.ProjectModel; +using JetBrains.ReSharper.Host.Features; +using JetBrains.Rider.Model; + +namespace AWS.Psi.Lambda +{ + [SolutionComponent] + public class LambdaPsiHost + { + private readonly LambdaPsiModel myModel; + + public LambdaPsiHost(ISolution solution) + { + myModel = solution.GetProtocolSolution().GetLambdaPsiModel(); + + myModel.IsHandlerExists.Set((lifetime, handlerExistRequest) => + { + var className = handlerExistRequest.ClassName; + var methodName = handlerExistRequest.MethodName; + var projectId = handlerExistRequest.ProjectId; + + var backendPsiHelperModel = solution.GetProtocolSolution().GetBackendPsiHelperModel(); + return backendPsiHelperModel.IsMethodExists.Handler.Invoke( + lifetime, new MethodExistingRequest(className, methodName, "", projectId)); + }); + } + } +} diff --git a/jetbrains-rider/protocol.gradle b/jetbrains-rider/protocol.gradle index 6008a5d81f6..26bee6094ec 100644 --- a/jetbrains-rider/protocol.gradle +++ b/jetbrains-rider/protocol.gradle @@ -3,14 +3,15 @@ def protocolGroup = 'protocol' -ext.csGeneratedOutput = new File(resharperPluginPath, "src/AWS.Daemon/Protocol") +ext.csDaemonGeneratedOutput = new File(resharperPluginPath, "src/AWS.Daemon/Protocol") +ext.csPsiGeneratedOutput = new File(resharperPluginPath, "src/AWS.Psi/Protocol") + ext.ktGeneratedOutput = new File(projectDir, "src/software/aws/toolkits/jetbrains/protocol") -task generateModel(type: tasks.getByName("rdgen").class) { - group = protocolGroup - description = 'Generates protocol models' +ext.modelDir = new File(projectDir, "protocol/model") - def modelDir = new File(projectDir, "protocol/model") +task generateDaemonModel(type: tasks.getByName("rdgen").class) { + def daemonModelSource = new File(modelDir, "daemon").canonicalPath // NOTE: classpath is evaluated lazily, at execution time, because it comes from the unzipped // intellij SDK, which is extracted in afterEvaluate @@ -26,8 +27,8 @@ task generateModel(type: tasks.getByName("rdgen").class) { "$rdLibDirectory/rider-model.jar" } - sources modelDir.canonicalPath - packages = "protocol.model" + sources daemonModelSource + packages = "protocol.model.daemon" generator { language = "kotlin" @@ -42,21 +43,66 @@ task generateModel(type: tasks.getByName("rdgen").class) { transform = "reversed" root = "com.jetbrains.rider.model.nova.ide.IdeRoot" namespace = "JetBrains.Rider.Model" - directory = "$csGeneratedOutput" + directory = "$csDaemonGeneratedOutput" } } } +task generatePsiModel(type: tasks.getByName("rdgen").class) { + def psiModelSource = new File(modelDir, "psi").canonicalPath + + // NOTE: classpath is evaluated lazily, at execution time, because it comes from the unzipped + // intellij SDK, which is extracted in afterEvaluate + params { + verbose = true + hashFolder = "build/rdgen" + + logger.info("Configuring rdgen params") + classpath { + logger.info("Calculating classpath for rdgen, intellij.ideaDependency is: ${intellij.ideaDependency}") + def sdkPath = intellij.ideaDependency.classes + def rdLibDirectory = new File(sdkPath, "lib/rd").canonicalFile + + "$rdLibDirectory/rider-model.jar" + } + sources psiModelSource + packages = "protocol.model.psi" + + generator { + language = "kotlin" + transform = "asis" + root = "com.jetbrains.rider.model.nova.ide.IdeRoot" + namespace = "com.jetbrains.rider.model" + directory = "$ktGeneratedOutput" + } + + generator { + language = "csharp" + transform = "reversed" + root = "com.jetbrains.rider.model.nova.ide.IdeRoot" + namespace = "JetBrains.Rider.Model" + directory = "$csPsiGeneratedOutput" + } + } +} + +task generateModel(type: tasks.getByName("rdgen").class) { + group = protocolGroup + description = 'Generates protocol models' + + dependsOn generateDaemonModel, generatePsiModel +} + task cleanProtocolModels { group = protocolGroup description = 'Clean up generated protocol models' - if (csGeneratedOutput.isDirectory()) { - csGeneratedOutput.deleteDir() - } + def protocolOutDirs = [ ktGeneratedOutput, csDaemonGeneratedOutput, csPsiGeneratedOutput ] - if (ktGeneratedOutput.isDirectory()) { - ktGeneratedOutput.deleteDir() + for (dir in protocolOutDirs) { + if (dir.isDirectory()) { + dir.deleteDir() + } } } diff --git a/jetbrains-rider/protocol/model/LambdaModel.kt b/jetbrains-rider/protocol/model/daemon/LambdaModel.kt similarity index 97% rename from jetbrains-rider/protocol/model/LambdaModel.kt rename to jetbrains-rider/protocol/model/daemon/LambdaModel.kt index aea390c92c4..ee96b5f632e 100644 --- a/jetbrains-rider/protocol/model/LambdaModel.kt +++ b/jetbrains-rider/protocol/model/daemon/LambdaModel.kt @@ -1,7 +1,7 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package protocol.model +package protocol.model.daemon import com.jetbrains.rd.generator.nova.Ext import com.jetbrains.rd.generator.nova.doc diff --git a/jetbrains-rider/protocol/model/psi/LambdaPsiModel.kt b/jetbrains-rider/protocol/model/psi/LambdaPsiModel.kt new file mode 100644 index 00000000000..a2c04b7d54d --- /dev/null +++ b/jetbrains-rider/protocol/model/psi/LambdaPsiModel.kt @@ -0,0 +1,29 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package protocol.model.psi + +import com.jetbrains.rd.generator.nova.Ext +import com.jetbrains.rd.generator.nova.async +import com.jetbrains.rd.generator.nova.call +import com.jetbrains.rd.generator.nova.doc +import com.jetbrains.rd.generator.nova.field +import com.jetbrains.rd.generator.nova.PredefinedType.bool +import com.jetbrains.rd.generator.nova.PredefinedType.int +import com.jetbrains.rd.generator.nova.PredefinedType.string +import com.jetbrains.rider.model.nova.ide.SolutionModel + +@Suppress("unused") +object LambdaPsiModel : Ext(SolutionModel.Solution) { + + private val HandlerExistRequest = structdef { + field("className", string) + field("methodName", string) + field("projectId", int) + } + + init { + call("isHandlerExists", HandlerExistRequest, bool).async + .doc("Check whether handler with specified name exists for a partucular project") + } +} diff --git a/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/LambdaPsiHost.kt b/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/LambdaPsiHost.kt new file mode 100644 index 00000000000..9455cd54406 --- /dev/null +++ b/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/LambdaPsiHost.kt @@ -0,0 +1,15 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.lambda + +import com.intellij.openapi.project.Project +import com.jetbrains.rdclient.util.idea.LifetimedProjectComponent +import com.jetbrains.rider.model.lambdaPsiModel +import com.jetbrains.rider.projectView.solution + +@Suppress("ComponentNotRegistered") +class LambdaPsiHost(project: Project) : LifetimedProjectComponent(project) { + + val model = project.solution.lambdaPsiModel +} diff --git a/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/dotnet/DotNetLambdaHandlerResolver.kt b/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/dotnet/DotNetLambdaHandlerResolver.kt index 293bcb89f5c..8022b892b9b 100644 --- a/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/dotnet/DotNetLambdaHandlerResolver.kt +++ b/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/dotnet/DotNetLambdaHandlerResolver.kt @@ -3,33 +3,26 @@ package software.aws.toolkits.jetbrains.services.lambda.dotnet -import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.NavigatablePsiElement import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope import com.jetbrains.rd.framework.impl.RpcTimeouts -import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rd.util.threading.SpinWait -import com.jetbrains.rider.model.MethodExistingRequest import com.jetbrains.rider.model.backendPsiHelperModel import com.jetbrains.rider.model.publishableProjectsModel +import com.jetbrains.rider.model.HandlerExistRequest +import com.jetbrains.rider.model.MethodExistingRequest import com.jetbrains.rider.projectView.ProjectModelViewHost import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.run.configurations.method.getProjectModeId -import com.jetbrains.rider.util.idea.application +import com.jetbrains.rider.util.idea.getComponent import software.aws.toolkits.jetbrains.services.lambda.LambdaHandlerResolver +import software.aws.toolkits.jetbrains.services.lambda.LambdaPsiHost import software.aws.toolkits.jetbrains.services.lambda.dotnet.element.RiderLambdaHandlerFakePsiElement -import java.time.Duration class DotNetLambdaHandlerResolver : LambdaHandlerResolver { - companion object { - private const val handlerValidationTimeoutMs = 2000L - private const val findMethodTimeoutMs = 10000L - } - override fun version(): Int = 1 override fun findPsiElements( @@ -58,24 +51,7 @@ class DotNetLambdaHandlerResolver : LambdaHandlerResolver { val type = handlerParts[1] val methodName = handlerParts[2] - var isMethodExists = false - - if (application.isDispatchThread) { - isMethodExists = isMethodExists(project, assemblyName, type, methodName) - } else { - var isCompleted = false - application.invokeLater { - isMethodExists = isMethodExists(project, assemblyName, type, methodName) - isCompleted = true - } - - SpinWait.spinUntil(Lifetime.Eternal, Duration.ofMillis(handlerValidationTimeoutMs)) { - ProgressManager.checkCanceled() - isCompleted - } - } - - return isMethodExists + return isMethodExists(project, assemblyName, type, methodName) } fun getFieldIdByHandlerName(project: Project, handler: String): Int { @@ -97,7 +73,7 @@ class DotNetLambdaHandlerResolver : LambdaHandlerResolver { methodName = methodName, targetFramework = "", projectId = projectModelViewHost.getProjectModeId(projectToProcess.projectFilePath)), - timeouts = RpcTimeouts(findMethodTimeoutMs, findMethodTimeoutMs) + timeouts = RpcTimeouts.default ) return fileIdResponse?.fileId ?: -1 @@ -108,12 +84,12 @@ class DotNetLambdaHandlerResolver : LambdaHandlerResolver { val projects = project.solution.publishableProjectsModel.publishableProjects.values.toList() val projectToProcess = projects.find { it.projectName == assemblyName } ?: return false - return project.solution.backendPsiHelperModel.isMethodExists - .sync(MethodExistingRequest( - className = type, - methodName = methodName, - targetFramework = "", - projectId = projectModelViewHost.getProjectModeId(projectToProcess.projectFilePath) - ), RpcTimeouts.default) + val model = project.getComponent().model + + return model.isHandlerExists.sync(HandlerExistRequest( + className = type, + methodName = methodName, + projectId = projectModelViewHost.getProjectModeId(projectToProcess.projectFilePath) + )) } } diff --git a/jetbrains-rider/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/DotNetLocalLambdaRunConfigurationTest.kt b/jetbrains-rider/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/DotNetLocalLambdaRunConfigurationTest.kt new file mode 100644 index 00000000000..f3a918f87ff --- /dev/null +++ b/jetbrains-rider/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/DotNetLocalLambdaRunConfigurationTest.kt @@ -0,0 +1,96 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.lambda.execution.local + +import com.intellij.execution.configurations.RuntimeConfigurationError +import com.intellij.testFramework.runInEdtAndWait +import com.jetbrains.rider.test.asserts.shouldNotBeNull +import org.testng.annotations.Test +import software.aws.toolkits.jetbrains.settings.SamSettings + +class DotNetLocalLambdaRunConfigurationTest : LambdaRunConfigurationTestBase() { + + override fun getSolutionDirectoryName(): String = "SamHelloWorldApp" + + @Test + fun testHandler_ValidHandler() { + preWarmSamVersionCache(SamSettings.getInstance().executablePath) + preWarmLambdaHandlerValidation(handler = defaultHandler) + + runInEdtAndWait { + val runConfiguration = createHandlerBasedRunConfiguration( + handler = defaultHandler + ) + runConfiguration.shouldNotBeNull() + runConfiguration.checkConfiguration() + } + } + + @Test( + expectedExceptions = [ RuntimeConfigurationError::class ], + expectedExceptionsMessageRegExp = "Cannot find handler 'HelloWorld::HelloWorld.Function::HandlerDoesNoteExist' in project.") + fun testHandler_NonExistingMethodName() { + val nonExistingHandler = "HelloWorld::HelloWorld.Function::HandlerDoesNoteExist" + preWarmSamVersionCache(SamSettings.getInstance().executablePath) + preWarmLambdaHandlerValidation(handler = nonExistingHandler) + + runInEdtAndWait { + val runConfiguration = createHandlerBasedRunConfiguration( + handler = nonExistingHandler + ) + runConfiguration.shouldNotBeNull() + runConfiguration.checkConfiguration() + } + } + + @Test( + expectedExceptions = [ RuntimeConfigurationError::class ], + expectedExceptionsMessageRegExp = "Cannot find handler 'HelloWorld::HelloWorld.UnknownFunction::FunctionHandler' in project.") + fun testHandler_NonExistingTypeName() { + val nonExistingHandler = "HelloWorld::HelloWorld.UnknownFunction::FunctionHandler" + preWarmSamVersionCache(SamSettings.getInstance().executablePath) + preWarmLambdaHandlerValidation(handler = nonExistingHandler) + + runInEdtAndWait { + val runConfiguration = createHandlerBasedRunConfiguration( + handler = nonExistingHandler + ) + runConfiguration.shouldNotBeNull() + runConfiguration.checkConfiguration() + } + } + + @Test( + expectedExceptions = [ RuntimeConfigurationError::class ], + expectedExceptionsMessageRegExp = "Cannot find handler 'Fake' in project.") + fun testHandler_InvalidHandlerString() { + val invalidHandler = "Fake" + preWarmSamVersionCache(SamSettings.getInstance().executablePath) + preWarmLambdaHandlerValidation(handler = invalidHandler) + + runInEdtAndWait { + val runConfiguration = createHandlerBasedRunConfiguration( + handler = invalidHandler + ) + runConfiguration.shouldNotBeNull() + runConfiguration.checkConfiguration() + } + } + + @Test( + expectedExceptions = [ RuntimeConfigurationError::class ], + expectedExceptionsMessageRegExp = "Must specify a handler.") + fun testHandler_HandlerNotSet() { + preWarmSamVersionCache(SamSettings.getInstance().executablePath) + preWarmLambdaHandlerValidation() + + runInEdtAndWait { + val runConfiguration = createHandlerBasedRunConfiguration( + handler = null + ) + runConfiguration.shouldNotBeNull() + runConfiguration.checkConfiguration() + } + } +} diff --git a/jetbrains-rider/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/LambdaRunConfigurationTestBase.kt b/jetbrains-rider/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/LambdaRunConfigurationTestBase.kt new file mode 100644 index 00000000000..9f2b0b29cf3 --- /dev/null +++ b/jetbrains-rider/tst/software/aws/toolkits/jetbrains/services/lambda/execution/local/LambdaRunConfigurationTestBase.kt @@ -0,0 +1,51 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.lambda.execution.local + +import com.jetbrains.rider.test.base.BaseTestWithSolution +import org.testng.annotations.AfterMethod +import org.testng.annotations.BeforeMethod +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.services.lambda.model.Runtime +import software.aws.toolkits.jetbrains.core.credentials.MockCredentialsManager +import software.aws.toolkits.jetbrains.services.lambda.sam.SamCommonTestUtils +import software.aws.toolkits.jetbrains.settings.SamSettings + +abstract class LambdaRunConfigurationTestBase : BaseTestWithSolution() { + + companion object { + protected const val HANDLER_EVALUATE_TIMEOUT_MS = 10000 + } + + protected val mockId = "MockCredsId" + protected val mockCreds = AwsBasicCredentials.create("Access", "ItsASecret") + + protected val runtime = Runtime.DOTNETCORE2_1 + protected val defaultHandler = "HelloWorld::HelloWorld.Function::FunctionHandler" + protected val defaultInput = "inputText" + + @BeforeMethod + fun setUpCredentialsManager() { + val validSam = SamCommonTestUtils.makeATestSam(SamCommonTestUtils.getMinVersionAsJson()).toString() + SamSettings.getInstance().savedExecutablePath = validSam + + MockCredentialsManager.getInstance().addCredentials(mockId, mockCreds) + } + + @AfterMethod + fun resetCredentialsManager() { + MockCredentialsManager.getInstance().reset() + } + + protected fun createHandlerBasedRunConfiguration(handler: String? = defaultHandler, input: String? = defaultInput) = + createHandlerBasedRunConfiguration( + project = project, + runtime = runtime, + handler = handler, + input = input, + credentialsProviderId = mockId) + + protected fun preWarmLambdaHandlerValidation(handler: String = defaultHandler) = + preWarmLambdaHandlerValidation(project, runtime, handler, HANDLER_EVALUATE_TIMEOUT_MS) +}