Skip to content

Commit 0d79bd6

Browse files
committed
Version segregate jetbrains-ultimate files for 2025.3
- CodeCatalystGatewayClientCustomizer: GatewayClientCustomizationProvider API removed - DevEnvStatusWatcher: UnattendedStatusUtil API removed, use fallback values - GoHelper: Go debugger APIs changed, throw UnsupportedOperationException - NodeJsDebugSupport: NodeJS debugger APIs changed, stub implementation src-242-252: Original working implementations src-253+: Stubbed/disabled implementations with TODOs
1 parent 02e01ef commit 0d79bd6

File tree

8 files changed

+367
-0
lines changed

8 files changed

+367
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.remoteDev.caws
5+
6+
// TODO: GatewayClientCustomizationProvider removed in 2025.3 - investigate new Gateway customization APIs
7+
/*
8+
import com.intellij.openapi.extensions.ExtensionNotApplicableException
9+
import com.jetbrains.rdserver.unattendedHost.customization.controlCenter.GatewayClientCustomizationProvider
10+
import icons.AwsIcons
11+
import software.aws.toolkits.jetbrains.utils.isCodeCatalystDevEnv
12+
import software.aws.toolkits.resources.message
13+
14+
class CodeCatalystGatewayClientCustomizer : GatewayClientCustomizationProvider {
15+
init {
16+
if (!isCodeCatalystDevEnv()) {
17+
throw ExtensionNotApplicableException.create()
18+
}
19+
}
20+
21+
override fun getIcon() = AwsIcons.Logos.AWS_SMILE_SMALL
22+
23+
override fun getTitle() = message("caws.gateway.title")
24+
}
25+
*/
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.remoteDev.caws
5+
6+
import com.intellij.openapi.project.Project
7+
import com.intellij.openapi.startup.StartupActivity
8+
import com.intellij.openapi.ui.MessageDialogBuilder
9+
import kotlinx.coroutines.delay
10+
import kotlinx.coroutines.launch
11+
import kotlinx.coroutines.runBlocking
12+
import kotlinx.coroutines.withContext
13+
import software.amazon.awssdk.services.codecatalyst.CodeCatalystClient
14+
import software.aws.toolkits.core.utils.error
15+
import software.aws.toolkits.core.utils.getLogger
16+
import software.aws.toolkits.core.utils.info
17+
import software.aws.toolkits.jetbrains.core.awsClient
18+
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext
19+
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineUiContext
20+
import software.aws.toolkits.jetbrains.core.coroutines.projectCoroutineScope
21+
import software.aws.toolkits.jetbrains.core.credentials.sono.CodeCatalystCredentialManager
22+
import software.aws.toolkits.jetbrains.services.caws.CawsConstants
23+
import software.aws.toolkits.jetbrains.services.caws.envclient.CawsEnvironmentClient
24+
import software.aws.toolkits.jetbrains.services.caws.envclient.models.UpdateActivityRequest
25+
import software.aws.toolkits.jetbrains.utils.isCodeCatalystDevEnv
26+
import software.aws.toolkits.jetbrains.utils.notifyError
27+
import software.aws.toolkits.resources.message
28+
import java.time.Instant
29+
import java.time.temporal.ChronoUnit
30+
31+
class DevEnvStatusWatcher : StartupActivity {
32+
33+
companion object {
34+
private val LOG = getLogger<DevEnvStatusWatcher>()
35+
}
36+
37+
override fun runActivity(project: Project) {
38+
if (!isCodeCatalystDevEnv()) {
39+
return
40+
}
41+
val connection = CodeCatalystCredentialManager.getInstance(project).getConnectionSettings()
42+
?: error("Failed to fetch connection settings from Dev Environment")
43+
val envId = System.getenv(CawsConstants.CAWS_ENV_ID_VAR) ?: error("envId env var null")
44+
val org = System.getenv(CawsConstants.CAWS_ENV_ORG_NAME_VAR) ?: error("space env var null")
45+
val projectName = System.getenv(CawsConstants.CAWS_ENV_PROJECT_NAME_VAR) ?: error("project env var null")
46+
val client = connection.awsClient<CodeCatalystClient>()
47+
val coroutineScope = projectCoroutineScope(project)
48+
coroutineScope.launch(getCoroutineBgContext()) {
49+
val initialEnv = client.getDevEnvironment {
50+
it.id(envId)
51+
it.spaceName(org)
52+
it.projectName(projectName)
53+
}
54+
val inactivityTimeout = initialEnv.inactivityTimeoutMinutes()
55+
if (inactivityTimeout == 0) {
56+
LOG.info { "Dev environment inactivity timeout is 0, not monitoring" }
57+
return@launch
58+
}
59+
val inactivityTimeoutInSeconds = inactivityTimeout * 60
60+
61+
// TODO: Re-enable when Gateway APIs are available in 2025.3
62+
// val jbActivityStatus = GatewayConnectionUtil.getInstance().getSecondsSinceLastControllerActivity()
63+
val jbActivityStatus = 0L // Temporary fallback
64+
notifyBackendOfActivity((getActivityTime(jbActivityStatus).toString()))
65+
var secondsSinceLastControllerActivity = jbActivityStatus
66+
67+
while (true) {
68+
val response = checkHeartbeat(secondsSinceLastControllerActivity, inactivityTimeoutInSeconds, project)
69+
if (response.first) return@launch
70+
delay(30000)
71+
secondsSinceLastControllerActivity = response.second
72+
}
73+
}
74+
}
75+
76+
// This function returns a Pair The first value is a boolean indicating if the API returned the last recorded activity.
77+
// If inactivity tracking is disabled or if the value returned by the API is unparseable, the heartbeat is not sent
78+
// The second value indicates the seconds since last activity as recorded by JB in the most recent run
79+
fun checkHeartbeat(
80+
secondsSinceLastControllerActivity: Long,
81+
inactivityTimeoutInSeconds: Int,
82+
project: Project,
83+
): Pair<Boolean, Long> {
84+
val lastActivityTime = getJbRecordedActivity()
85+
86+
if (lastActivityTime < secondsSinceLastControllerActivity) {
87+
// update the API in case of any activity
88+
notifyBackendOfActivity((getActivityTime(lastActivityTime).toString()))
89+
}
90+
91+
val lastRecordedActivityTime = getLastRecordedApiActivity()
92+
if (lastRecordedActivityTime == null) {
93+
LOG.error { "Couldn't retrieve last recorded activity from API" }
94+
return Pair(true, lastActivityTime)
95+
}
96+
val durationRecordedSinceLastActivity = Instant.now().toEpochMilli().minus(lastRecordedActivityTime.toLong())
97+
val secondsRecordedSinceLastActivity = durationRecordedSinceLastActivity / 1000
98+
99+
if (secondsRecordedSinceLastActivity >= (inactivityTimeoutInSeconds - 300)) {
100+
try {
101+
val inactivityDurationInMinutes = secondsRecordedSinceLastActivity / 60
102+
val ans = runBlocking {
103+
val continueWorking = withContext(getCoroutineUiContext()) {
104+
return@withContext MessageDialogBuilder.okCancel(
105+
message("caws.devenv.continue.working.after.timeout.title"),
106+
message("caws.devenv.continue.working.after.timeout", inactivityDurationInMinutes)
107+
).ask(project)
108+
}
109+
return@runBlocking continueWorking
110+
}
111+
112+
if (ans) {
113+
notifyBackendOfActivity(getActivityTime().toString())
114+
}
115+
} catch (e: Exception) {
116+
val preMessage = "Error while checking if Dev Environment should continue working"
117+
LOG.error(e) { preMessage }
118+
notifyError(preMessage, e.message.toString())
119+
}
120+
}
121+
return Pair(false, lastActivityTime)
122+
}
123+
124+
fun getLastRecordedApiActivity(): String? = CawsEnvironmentClient.getInstance().getActivity()?.timestamp
125+
126+
// TODO: Re-enable when Gateway APIs are available in 2025.3
127+
// Original: GatewayConnectionUtil.getInstance().getSecondsSinceLastControllerActivity()
128+
private val fallbackActivityTime = 0L
129+
130+
fun getJbRecordedActivity(): Long = fallbackActivityTime
131+
132+
fun notifyBackendOfActivity(timestamp: String = Instant.now().toEpochMilli().toString()) {
133+
val request = UpdateActivityRequest(
134+
timestamp = timestamp
135+
)
136+
CawsEnvironmentClient.getInstance().putActivityTimestamp(request)
137+
}
138+
139+
private fun getActivityTime(secondsSinceLastActivity: Long = 0): Long = Instant.now().minus(secondsSinceLastActivity, ChronoUnit.SECONDS).toEpochMilli()
140+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.lambda.go
5+
6+
// TODO: Re-enable when Go plugin APIs are available in 2025.3
7+
// import com.goide.dlv.DlvDebugProcessUtil
8+
import com.goide.execution.GoRunUtil
9+
import com.intellij.openapi.application.runReadAction
10+
import com.intellij.openapi.project.Project
11+
import com.intellij.openapi.roots.ProjectFileIndex
12+
import com.intellij.openapi.vfs.VirtualFile
13+
import com.intellij.xdebugger.XDebugProcess
14+
import com.intellij.xdebugger.XDebugProcessStarter
15+
import com.intellij.xdebugger.XDebugSession
16+
import software.aws.toolkits.jetbrains.utils.execution.steps.Context
17+
import java.nio.file.Files
18+
19+
/**
20+
* "Light" ides like Goland do not rely on marking folders as source root, so infer it based on
21+
* the go.mod file. This function is based off of the similar PackageJsonUtil#findUpPackageJson
22+
*
23+
* @throws IllegalStateException If the contentRoot cannot be located
24+
*/
25+
fun inferSourceRoot(project: Project, virtualFile: VirtualFile): VirtualFile? {
26+
val projectFileIndex = ProjectFileIndex.getInstance(project)
27+
val contentRoot = runReadAction {
28+
projectFileIndex.getContentRootForFile(virtualFile)
29+
}
30+
31+
return contentRoot?.let { root ->
32+
var file = virtualFile.parent
33+
while (file != null) {
34+
if ((file.isDirectory && file.children.any { !it.isDirectory && it.name == "go.mod" })) {
35+
return file
36+
}
37+
// If we go up to the root and it's still not found, stop going up and mark source root as
38+
// not found, since it will fail to build
39+
if (file == root) {
40+
return null
41+
}
42+
file = file.parent
43+
}
44+
return null
45+
}
46+
}
47+
48+
object GoDebugHelper {
49+
// TODO see https://youtrack.jetbrains.com/issue/GO-10775 for "Debugger disconnected unexpectedly" when the lambda finishes
50+
fun createGoDebugProcess(
51+
@Suppress("UNUSED_PARAMETER") debugHost: String,
52+
@Suppress("UNUSED_PARAMETER") debugPorts: List<Int>,
53+
@Suppress("UNUSED_PARAMETER") context: Context,
54+
): XDebugProcessStarter = object : XDebugProcessStarter() {
55+
override fun start(session: XDebugSession): XDebugProcess {
56+
// TODO: Re-enable when Go plugin APIs are available in 2025.3
57+
// val process = DlvDebugProcessUtil.createDlvDebugProcess(session, DlvDisconnectOption.KILL, null, true)
58+
throw UnsupportedOperationException("Go debugging temporarily disabled in 2025.3 - Go plugin APIs moved")
59+
}
60+
}
61+
62+
fun copyDlv(): String {
63+
// This can take a target platform, but that pulls directly from GOOS, so we have to walk back up the file tree
64+
// either way. Goland comes with mac/window/linux dlv since it supports remote debugging, so it is always safe to
65+
// pull the linux one
66+
val dlvFolder = GoRunUtil.getBundledDlv(null)?.parentFile?.parentFile?.resolve("linux")
67+
?: throw IllegalStateException("Packaged Devle debugger is not found!")
68+
val directory = Files.createTempDirectory("goDebugger")
69+
Files.copy(dlvFolder.resolve("dlv").toPath(), directory.resolve("dlv"))
70+
// Delve that comes packaged with the IDE does not have the executable flag set
71+
directory.resolve("dlv").toFile().setExecutable(true)
72+
return directory.toAbsolutePath().toString()
73+
}
74+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.lambda.nodejs
5+
6+
import com.google.common.collect.BiMap
7+
import com.google.common.collect.HashBiMap
8+
import com.intellij.execution.runners.ExecutionEnvironment
9+
import com.intellij.execution.ui.ExecutionConsole
10+
import com.intellij.javascript.debugger.LocalFileSystemFileFinder
11+
import com.intellij.javascript.debugger.RemoteDebuggingFileFinder
12+
import com.intellij.openapi.fileTypes.FileType
13+
import com.intellij.openapi.fileTypes.PlainTextFileType
14+
import com.intellij.openapi.project.Project
15+
import com.intellij.openapi.util.io.FileUtil
16+
import com.intellij.openapi.vfs.VirtualFile
17+
import com.intellij.psi.PsiElement
18+
import com.intellij.psi.PsiFile
19+
import com.intellij.xdebugger.XDebugProcess
20+
import com.intellij.xdebugger.XDebugProcessStarter
21+
import com.intellij.xdebugger.XDebugSession
22+
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProviderBase
23+
import compat.com.intellij.lang.javascript.JavascriptLanguage
24+
import org.jetbrains.io.LocalFileFinder
25+
import software.aws.toolkits.core.lambda.LambdaRuntime
26+
import software.aws.toolkits.jetbrains.services.PathMapping
27+
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.ImageDebugSupport
28+
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.RuntimeDebugSupport
29+
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.SamRunningState
30+
import software.aws.toolkits.jetbrains.utils.execution.steps.Context
31+
import javax.swing.JComponent
32+
import javax.swing.JLabel
33+
34+
class NodeJsRuntimeDebugSupport : RuntimeDebugSupport {
35+
override suspend fun createDebugProcess(
36+
context: Context,
37+
environment: ExecutionEnvironment,
38+
state: SamRunningState,
39+
debugHost: String,
40+
debugPorts: List<Int>,
41+
): XDebugProcessStarter = NodeJsDebugUtils.createDebugProcess(state, debugHost, debugPorts)
42+
}
43+
44+
abstract class NodeJsImageDebugSupport : ImageDebugSupport {
45+
override fun supportsPathMappings(): Boolean = true
46+
override val languageId = JavascriptLanguage.id
47+
override suspend fun createDebugProcess(
48+
context: Context,
49+
environment: ExecutionEnvironment,
50+
state: SamRunningState,
51+
debugHost: String,
52+
debugPorts: List<Int>,
53+
): XDebugProcessStarter = NodeJsDebugUtils.createDebugProcess(state, debugHost, debugPorts)
54+
55+
override fun containerEnvVars(debugPorts: List<Int>): Map<String, String> = mapOf(
56+
"NODE_OPTIONS" to "--inspect-brk=0.0.0.0:${debugPorts.first()} --max-http-header-size 81920"
57+
)
58+
}
59+
60+
class NodeJs16ImageDebug : NodeJsImageDebugSupport() {
61+
override val id: String = LambdaRuntime.NODEJS16_X.toString()
62+
override fun displayName() = LambdaRuntime.NODEJS16_X.toString().capitalize()
63+
}
64+
65+
class NodeJs18ImageDebug : NodeJsImageDebugSupport() {
66+
override val id: String = LambdaRuntime.NODEJS18_X.toString()
67+
override fun displayName() = LambdaRuntime.NODEJS18_X.toString().capitalize()
68+
}
69+
70+
class NodeJs20ImageDebug : NodeJsImageDebugSupport() {
71+
override val id: String = LambdaRuntime.NODEJS20_X.toString()
72+
override fun displayName() = LambdaRuntime.NODEJS20_X.toString().capitalize()
73+
}
74+
75+
object NodeJsDebugUtils {
76+
private const val NODE_MODULES = "node_modules"
77+
78+
// Noop editors provider for disabled NodeJS debugging in 2025.3
79+
private class NoopXDebuggerEditorsProvider : XDebuggerEditorsProviderBase() {
80+
override fun getFileType(): FileType = PlainTextFileType.INSTANCE
81+
override fun createExpressionCodeFragment(project: Project, text: String, context: PsiElement?, isPhysical: Boolean): PsiFile? = null
82+
}
83+
84+
fun createDebugProcess(
85+
state: SamRunningState,
86+
@Suppress("UNUSED_PARAMETER") debugHost: String,
87+
@Suppress("UNUSED_PARAMETER") debugPorts: List<Int>,
88+
): XDebugProcessStarter = object : XDebugProcessStarter() {
89+
override fun start(session: XDebugSession): XDebugProcess {
90+
val mappings = createBiMapMappings(state.pathMappings)
91+
92+
@Suppress("UNUSED_VARIABLE")
93+
val fileFinder = RemoteDebuggingFileFinder(mappings, LocalFileSystemFileFinder())
94+
95+
// STUB IMPLEMENTATION: NodeJS debugging temporarily disabled
96+
return object : XDebugProcess(session) {
97+
override fun getEditorsProvider() = NoopXDebuggerEditorsProvider()
98+
override fun doGetProcessHandler() = null
99+
override fun createConsole() = object : ExecutionConsole {
100+
override fun getComponent(): JComponent = JLabel("NodeJS debugging disabled in 2025.3")
101+
override fun getPreferredFocusableComponent(): JComponent? = null
102+
override fun dispose() {}
103+
}
104+
}
105+
}
106+
}
107+
108+
/**
109+
* Convert [PathMapping] to NodeJs debugger path mapping format.
110+
*
111+
* Docker uses the same project structure for dependencies in the folder node_modules. We map the source code and
112+
* the dependencies in node_modules folder separately as the node_modules might not exist in the local project.
113+
*/
114+
private fun createBiMapMappings(pathMapping: List<PathMapping>): BiMap<String, VirtualFile> {
115+
val mappings = HashBiMap.create<String, VirtualFile>(pathMapping.size)
116+
117+
listOf(".", NODE_MODULES).forEach { subPath ->
118+
pathMapping.forEach {
119+
val remotePath = FileUtil.toCanonicalPath("${it.remoteRoot}/$subPath")
120+
LocalFileFinder.findFile("${it.localRoot}/$subPath")?.let { localFile ->
121+
mappings.putIfAbsent("file://$remotePath", localFile)
122+
}
123+
}
124+
}
125+
126+
return mappings
127+
}
128+
}

0 commit comments

Comments
 (0)