mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[eel][wsl] IJPL-196973 Compile error open target not source in wsl
- provide the possibility to get IjentApi instance by specific EelDescriptor GitOrigin-RevId: 874542c0261cad5908da1fd10162f66800e97928
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2fa1af099c
commit
cc2954797c
@@ -90,7 +90,7 @@ val localEel: LocalEelApi by lazy {
|
||||
@ApiStatus.Internal
|
||||
fun EelDescriptor.upgradeBlocking(): EelApi = toEelApiBlocking()
|
||||
|
||||
fun EelMachine.toEelApiBlocking(): EelApi = runBlockingMaybeCancellable { toEelApi() }
|
||||
fun EelMachine.toEelApiBlocking(descriptor: EelDescriptor): EelApi = runBlockingMaybeCancellable { toEelApi(descriptor) }
|
||||
|
||||
@ApiStatus.Experimental
|
||||
fun EelDescriptor.toEelApiBlocking(): EelApi {
|
||||
@@ -113,7 +113,10 @@ data object LocalEelMachine : EelMachine {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toEelApi(): EelApi = localEel
|
||||
override suspend fun toEelApi(descriptor: EelDescriptor): EelApi {
|
||||
check(descriptor === LocalEelDescriptor) { "Wrong descriptor: $descriptor for machine: $this" }
|
||||
return localEel
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
|
||||
@@ -52,7 +52,7 @@ interface EelMachine {
|
||||
* Converts this machine into a [EelApi] — starts or reuses a running environment.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
suspend fun toEelApi(): EelApi
|
||||
suspend fun toEelApi(descriptor: EelDescriptor): EelApi
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +136,7 @@ interface EelDescriptor {
|
||||
|
||||
@ApiStatus.Experimental
|
||||
suspend fun toEelApi(): EelApi {
|
||||
return machine.toEelApi()
|
||||
return machine.toEelApi(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,7 +74,7 @@ class EelAbsolutePathTest {
|
||||
override val machine: EelMachine = object : EelMachine {
|
||||
override val name: String = "mock"
|
||||
override val osFamily: EelOsFamily = this@DummyEelDescriptor.osFamily
|
||||
override suspend fun toEelApi(): EelApi {
|
||||
override suspend fun toEelApi(descriptor: EelDescriptor): EelApi {
|
||||
return Assertions.fail<Nothing>()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -14,6 +14,7 @@ import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.diagnostic.runAndLogException
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.platform.eel.EelDescriptor
|
||||
import com.intellij.platform.eel.spawnProcess
|
||||
import com.intellij.platform.eel.provider.utils.copy
|
||||
import com.intellij.platform.eel.provider.utils.asEelChannel
|
||||
@@ -21,8 +22,9 @@ import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.platform.ijent.IjentMissingBinary
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider
|
||||
import com.intellij.platform.ijent.deploy
|
||||
import com.intellij.platform.ijent.createIjentSession
|
||||
import com.intellij.platform.ijent.spi.IjentDeployingStrategy
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
@@ -58,8 +60,8 @@ abstract class AbstractIjentVerificationAction : DumbAwareAction() {
|
||||
try {
|
||||
withModalProgress(modalTaskOwner, e.presentation.text, TaskCancellation.cancellable()) {
|
||||
coroutineScope {
|
||||
val (title, deployingStrategy) = deployingStrategy(this)
|
||||
deployingStrategy.deploy().ijentApi.use { ijent ->
|
||||
val (title, deployingStrategy, descriptor) = deployingStrategy(this)
|
||||
deployingStrategy.createIjentSession<IjentPosixApi>().getIjentInstance(descriptor).use { ijent ->
|
||||
coroutineScope {
|
||||
launch {
|
||||
val info = ijent.ijentProcessInfo
|
||||
@@ -112,7 +114,7 @@ abstract class AbstractIjentVerificationAction : DumbAwareAction() {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract suspend fun deployingStrategy(ijentProcessScope: CoroutineScope): Pair<String, IjentDeployingStrategy>
|
||||
protected abstract suspend fun deployingStrategy(ijentProcessScope: CoroutineScope): Triple<String, IjentDeployingStrategy, EelDescriptor>
|
||||
|
||||
companion object {
|
||||
protected val LOG = logger<AbstractIjentVerificationAction>()
|
||||
|
||||
@@ -1,57 +1,34 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:JvmName("IjentDeployer")
|
||||
|
||||
package com.intellij.platform.ijent
|
||||
|
||||
import com.intellij.platform.ijent.spi.DeployedIjent
|
||||
import com.intellij.platform.eel.EelDescriptor
|
||||
import com.intellij.platform.ijent.spi.IjentDeployingStrategy
|
||||
import com.intellij.platform.ijent.spi.connectToRunningIjent
|
||||
import com.intellij.platform.ijent.spi.createIjentSession
|
||||
|
||||
/**
|
||||
* Starts IJent on some machine, defined by [com.intellij.platform.ijent.spi.IjentDeployingStrategy].
|
||||
*
|
||||
* By default, the IJent executable exits when
|
||||
* the coroutine scope of [IjentProcessMediator] from [IjentDeployingStrategy.createProcess] exits.
|
||||
* [com.intellij.platform.ijent.IjentApi.close] may be used to terminate IJent earlier.
|
||||
*
|
||||
* TODO Either define thrown exceptions or return something like Result.
|
||||
*/
|
||||
suspend fun IjentDeployingStrategy.deploy(): DeployedIjent {
|
||||
val (remotePathToBinary, ijentApi) = doDeploy()
|
||||
return object : DeployedIjent {
|
||||
override val ijentApi: IjentApi = ijentApi
|
||||
override val remotePathToBinary: String = remotePathToBinary
|
||||
interface IjentSession<T : IjentApi> {
|
||||
val isRunning: Boolean
|
||||
val remotePathToBinary: String // TODO Use IjentPath.Absolute.
|
||||
|
||||
suspend fun updateLogLevel()
|
||||
|
||||
fun close()
|
||||
|
||||
fun getIjentInstance(descriptor: EelDescriptor): T
|
||||
|
||||
enum class LogLevel {
|
||||
INFO, DEBUG, TRACE, ALL
|
||||
}
|
||||
}
|
||||
|
||||
/** A specialized version of [com.intellij.platform.ijent.deploy] */
|
||||
suspend fun IjentDeployingStrategy.Posix.deploy(): DeployedIjent.Posix {
|
||||
val (remotePathToBinary, ijentApi) = doDeploy()
|
||||
ijentApi as IjentPosixApi
|
||||
return object : DeployedIjent.Posix {
|
||||
override val ijentApi: IjentPosixApi = ijentApi
|
||||
override val remotePathToBinary: String = remotePathToBinary
|
||||
}
|
||||
}
|
||||
|
||||
/** A specialized version of [com.intellij.platform.ijent.deploy] */
|
||||
suspend fun IjentDeployingStrategy.Windows.deploy(): DeployedIjent.Windows {
|
||||
val (remotePathToBinary, ijentApi) = doDeploy()
|
||||
ijentApi as IjentWindowsApi
|
||||
return object : DeployedIjent.Windows {
|
||||
override val ijentApi: IjentWindowsApi = ijentApi
|
||||
override val remotePathToBinary: String = remotePathToBinary
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun IjentDeployingStrategy.doDeploy(): Pair<String, IjentApi> =
|
||||
suspend fun <T : IjentApi> IjentDeployingStrategy.createIjentSession(): IjentSession<T> =
|
||||
try {
|
||||
val targetPlatform = getTargetPlatform()
|
||||
val targetDescriptor = getTargetDescriptor()
|
||||
val remotePathToBinary = copyFile(IjentExecFileProvider.getInstance().getIjentBinary(targetPlatform))
|
||||
val mediator = createProcess(remotePathToBinary)
|
||||
|
||||
val ijentApi = connectToRunningIjent(getConnectionStrategy(), targetPlatform, targetDescriptor, mediator)
|
||||
remotePathToBinary to ijentApi
|
||||
createIjentSession<T>(getConnectionStrategy(), remotePathToBinary, targetPlatform, mediator)
|
||||
}
|
||||
finally {
|
||||
close()
|
||||
|
||||
@@ -18,8 +18,8 @@ class IjentSessionRegistry(private val coroutineScope: CoroutineScope) {
|
||||
private val counter = AtomicLong()
|
||||
|
||||
private class IjentBundle(
|
||||
val factory: suspend (ijentId: IjentId) -> IjentApi,
|
||||
val deferred: Deferred<IjentApi>?,
|
||||
val factory: suspend (ijentId: IjentId) -> IjentSession<IjentPosixApi>,
|
||||
val deferred: Deferred<IjentSession<IjentPosixApi>>?,
|
||||
val oneOff: Boolean,
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ class IjentSessionRegistry(private val coroutineScope: CoroutineScope) {
|
||||
*/
|
||||
fun register(
|
||||
ijentName: String,
|
||||
launcher: suspend (ijentId: IjentId) -> IjentApi,
|
||||
launcher: suspend (ijentId: IjentId) -> IjentSession<IjentPosixApi>,
|
||||
): IjentId {
|
||||
val ijentId = IjentId("ijent-${counter.getAndIncrement()}-${ijentName.replace(Regex("[^A-Za-z0-9-]"), "-")}")
|
||||
ijents[ijentId] = IjentBundle(launcher, null, oneOff = false)
|
||||
@@ -65,7 +65,7 @@ class IjentSessionRegistry(private val coroutineScope: CoroutineScope) {
|
||||
* An instance of [IjentApi] that has ever thrown [IjentUnavailableException] will never be returned by this function again.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
suspend fun get(ijentId: IjentId): IjentApi {
|
||||
suspend fun get(ijentId: IjentId): IjentSession<IjentPosixApi> {
|
||||
val bundle = ijents.compute(ijentId, @Suppress("NAME_SHADOWING") { ijentId, oldBundle ->
|
||||
require(oldBundle != null) {
|
||||
"Not registered: $ijentId"
|
||||
@@ -73,7 +73,7 @@ class IjentSessionRegistry(private val coroutineScope: CoroutineScope) {
|
||||
|
||||
val oldDeferred = oldBundle.deferred
|
||||
|
||||
val reusedOldDeferred: Deferred<IjentApi>? = when {
|
||||
val reusedOldDeferred: Deferred<IjentSession<IjentPosixApi>>? = when {
|
||||
oldDeferred == null -> null
|
||||
oldBundle.oneOff -> oldDeferred
|
||||
!oldDeferred.isCompleted -> oldDeferred
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ijent.spi
|
||||
|
||||
import com.intellij.platform.ijent.IjentApi
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.IjentWindowsApi
|
||||
|
||||
interface DeployedIjent {
|
||||
val ijentApi: IjentApi
|
||||
|
||||
val remotePathToBinary: String // TODO Use IjentPath.Absolute.
|
||||
|
||||
interface Posix : DeployedIjent {
|
||||
override val ijentApi: IjentPosixApi
|
||||
}
|
||||
|
||||
interface Windows : DeployedIjent {
|
||||
override val ijentApi: IjentWindowsApi
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ijent.spi
|
||||
|
||||
import com.intellij.platform.eel.EelDescriptor
|
||||
import com.intellij.platform.eel.EelPlatform
|
||||
import com.intellij.platform.ijent.deploy
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -28,12 +26,6 @@ interface IjentDeployingStrategy {
|
||||
*/
|
||||
suspend fun getTargetPlatform(): EelPlatform
|
||||
|
||||
/**
|
||||
* Description of the target environment. This descriptor is accepted by IntelliJ Platform API that works with Eel.
|
||||
* todo: merge it with [getTargetPlatform]
|
||||
*/
|
||||
suspend fun getTargetDescriptor(): EelDescriptor
|
||||
|
||||
/**
|
||||
* Defines a set of options for connecting to a running IJent
|
||||
* This step is logically different from deployment,
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
package com.intellij.platform.ijent.spi
|
||||
|
||||
import com.intellij.openapi.components.serviceAsync
|
||||
import com.intellij.platform.eel.EelDescriptor
|
||||
import com.intellij.platform.eel.EelPlatform
|
||||
import com.intellij.platform.ijent.IjentApi
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.IjentSession
|
||||
import com.intellij.platform.ijent.IjentSessionRegistry
|
||||
import com.intellij.platform.ijent.IjentWindowsApi
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Given that there is some IJent process launched, this extension gets handles to stdin+stdout of the process and returns
|
||||
@@ -20,9 +19,9 @@ interface IjentSessionProvider {
|
||||
suspend fun connect(
|
||||
strategy: IjentConnectionStrategy,
|
||||
platform: EelPlatform,
|
||||
descriptor: EelDescriptor,
|
||||
binaryPath: String,
|
||||
mediator: IjentSessionMediator,
|
||||
): IjentApi
|
||||
): IjentSession<*>
|
||||
|
||||
companion object {
|
||||
suspend fun instanceAsync(): IjentSessionProvider = serviceAsync()
|
||||
@@ -45,7 +44,7 @@ sealed class IjentStartupError : RuntimeException {
|
||||
}
|
||||
|
||||
internal class DefaultIjentSessionProvider : IjentSessionProvider {
|
||||
override suspend fun connect(strategy: IjentConnectionStrategy, platform: EelPlatform, descriptor: EelDescriptor, mediator: IjentSessionMediator): IjentApi {
|
||||
override suspend fun connect(strategy: IjentConnectionStrategy, platform: EelPlatform, binaryPath: String, mediator: IjentSessionMediator): IjentSession<*> {
|
||||
throw IjentStartupError.MissingImplPlugin()
|
||||
}
|
||||
}
|
||||
@@ -56,25 +55,8 @@ internal class DefaultIjentSessionProvider : IjentSessionProvider {
|
||||
*
|
||||
* The process terminates automatically only when the IDE exits, or if [IjentApi.close] is called explicitly.
|
||||
*/
|
||||
suspend fun connectToRunningIjent(strategy: IjentConnectionStrategy, platform: EelPlatform, descriptor: EelDescriptor, mediator: IjentSessionMediator): IjentApi {
|
||||
suspend fun <T : IjentApi> createIjentSession(strategy: IjentConnectionStrategy, binaryPath: String, platform: EelPlatform, mediator: IjentSessionMediator): IjentSession<T> {
|
||||
mediator.myExitPolicy = IjentSessionMediator.ProcessExitPolicy.CHECK_CODE
|
||||
return IjentSessionProvider.instanceAsync().connect(strategy, platform, descriptor, mediator)
|
||||
}
|
||||
|
||||
/** A specialized overload of [connectToRunningIjent] */
|
||||
suspend fun connectToRunningIjent(
|
||||
strategy: IjentConnectionStrategy,
|
||||
platform: EelPlatform.Posix,
|
||||
descriptor: EelDescriptor,
|
||||
mediator: IjentSessionMediator,
|
||||
): IjentPosixApi =
|
||||
connectToRunningIjent(strategy, platform as EelPlatform, descriptor, mediator) as IjentPosixApi
|
||||
|
||||
/** A specialized overload of [connectToRunningIjent] */
|
||||
suspend fun connectToRunningIjent(
|
||||
strategy: IjentConnectionStrategy,
|
||||
platform: EelPlatform.Windows,
|
||||
descriptor: EelDescriptor,
|
||||
mediator: IjentSessionMediator,
|
||||
): IjentWindowsApi =
|
||||
connectToRunningIjent(strategy, platform as EelPlatform, descriptor, mediator) as IjentWindowsApi
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return IjentSessionProvider.instanceAsync().connect(strategy, platform, binaryPath, mediator) as IjentSession<T>
|
||||
}
|
||||
+10
-6
@@ -7,8 +7,11 @@ import com.intellij.execution.wsl.WslIjentManager
|
||||
import com.intellij.openapi.components.serviceIfCreated
|
||||
import com.intellij.openapi.progress.Cancellation
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.eel.EelDescriptor
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.platform.ijent.IjentId
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.IjentSession
|
||||
import com.intellij.platform.ijent.IjentSessionRegistry
|
||||
import com.intellij.platform.ijent.spi.IjentThreadPool
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
@@ -37,21 +40,22 @@ class ProductionWslIjentManager(private val scope: CoroutineScope) : WslIjentMan
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getIjentApi(wslDistribution: WSLDistribution, project: Project?, rootUser: Boolean): IjentPosixApi {
|
||||
override suspend fun getIjentApi(descriptor: EelDescriptor?, wslDistribution: WSLDistribution, project: Project?, rootUser: Boolean): IjentPosixApi {
|
||||
val descriptor = (descriptor ?: project?.getEelDescriptor() ?: WslEelDescriptor(wslDistribution)) as WslEelDescriptor
|
||||
|
||||
val ijentSessionRegistry = IjentSessionRegistry.instanceAsync()
|
||||
val ijentId = myCache.computeIfAbsent("""wsl:${wslDistribution.id}${if (rootUser) ":root" else ""}""") { ijentName ->
|
||||
val ijentId = ijentSessionRegistry.register(ijentName) { ijentId ->
|
||||
val ijent = deployAndLaunchIjent(
|
||||
val ijentSession = wslDistribution.createIjentSession(
|
||||
scope,
|
||||
project,
|
||||
ijentId.toString(),
|
||||
wslDistribution,
|
||||
wslCommandLineOptionsModifier = { it.setSudo(rootUser) },
|
||||
)
|
||||
scope.coroutineContext.job.invokeOnCompletion {
|
||||
ijent.close()
|
||||
ijentSession.close()
|
||||
}
|
||||
ijent
|
||||
ijentSession
|
||||
}
|
||||
scope.coroutineContext.job.invokeOnCompletion {
|
||||
ijentSessionRegistry.unregister(ijentId)
|
||||
@@ -59,7 +63,7 @@ class ProductionWslIjentManager(private val scope: CoroutineScope) : WslIjentMan
|
||||
}
|
||||
ijentId
|
||||
}
|
||||
return ijentSessionRegistry.get(ijentId) as IjentPosixApi
|
||||
return ijentSessionRegistry.get(ijentId).getIjentInstance(descriptor)
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
+15
-5
@@ -36,6 +36,7 @@ import java.nio.file.*
|
||||
import java.nio.file.FileSystems.getDefault
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
private val WSLDistribution.roots: Set<String>
|
||||
get() {
|
||||
@@ -47,8 +48,8 @@ private val WSLDistribution.roots: Set<String>
|
||||
return localRoots
|
||||
}
|
||||
|
||||
private suspend fun WSLDistribution.getIjent(): IjentPosixApi {
|
||||
return WslIjentManager.instanceAsync().getIjentApi(this, null, false)
|
||||
private suspend fun WSLDistribution.getIjent(descriptor: EelDescriptor): IjentPosixApi {
|
||||
return WslIjentManager.instanceAsync().getIjentApi(descriptor, this, null, false)
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@@ -230,7 +231,10 @@ class WslEelProvider : EelProvider {
|
||||
false
|
||||
}
|
||||
}
|
||||
WslIjentManager.instanceAsync().getIjentApi(wslPath.distribution, project, false)
|
||||
|
||||
val descriptor = WslEelDescriptor(wslPath.distribution, wslPath.wslRoot)
|
||||
|
||||
WslIjentManager.instanceAsync().getIjentApi(descriptor, wslPath.distribution, project, false)
|
||||
|
||||
(getDefault().provider() as MultiRoutingFileSystemProvider).theOnlyFileSystem.getBackend(wslPath.wslRoot + "\\")
|
||||
}
|
||||
@@ -268,8 +272,12 @@ class WslEelMachine(val distribution: WSLDistribution) : EelMachine {
|
||||
override val osFamily: EelOsFamily = EelOsFamily.Posix
|
||||
override val name: @NonNls String = "WSL: ${distribution.presentableName}"
|
||||
|
||||
override suspend fun toEelApi(): EelApi {
|
||||
return distribution.getIjent()
|
||||
override suspend fun toEelApi(descriptor: EelDescriptor): EelApi {
|
||||
check(descriptor is WslEelDescriptor && descriptor.machine == this) {
|
||||
"Wrong descriptor: $descriptor for machine: $this"
|
||||
}
|
||||
|
||||
return distribution.getIjent(descriptor)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -292,6 +300,8 @@ class WslEelMachine(val distribution: WSLDistribution) : EelMachine {
|
||||
}
|
||||
|
||||
class WslEelDescriptor(val distribution: WSLDistribution, internal val fsRoot: String) : EelPathBoundDescriptor {
|
||||
constructor(distribution: WSLDistribution): this(distribution, distribution.getUNCRootPath().pathString)
|
||||
|
||||
override val rootPath: Path get() = fsRoot.let(::Path)
|
||||
override val machine: EelMachine = WslEelMachine(distribution)
|
||||
|
||||
|
||||
-7
@@ -46,13 +46,6 @@ class WslIjentDeployingStrategy(
|
||||
return computeDetached { commandLine.createProcess() }
|
||||
}
|
||||
|
||||
override suspend fun getTargetDescriptor(): EelDescriptor {
|
||||
val root = project?.basePath?.let {
|
||||
MultiRoutingFileSystem.sanitizeRoot(it)
|
||||
}?.let(WslEelProvider::parsePath)?.first ?: distribution.getUNCRootPath().toString()
|
||||
return WslEelDescriptor(distribution, root)
|
||||
}
|
||||
|
||||
override suspend fun getConnectionStrategy(): IjentConnectionStrategy {
|
||||
return object : IjentConnectionStrategy {
|
||||
override suspend fun canUseVirtualSockets(): Boolean {
|
||||
|
||||
@@ -5,189 +5,27 @@
|
||||
|
||||
package com.intellij.platform.ide.impl.wsl
|
||||
|
||||
import com.intellij.execution.CommandLineUtil.posixQuote
|
||||
import com.intellij.execution.ijent.IjentChildProcessAdapter
|
||||
import com.intellij.execution.ijent.IjentChildPtyProcessAdapter
|
||||
import com.intellij.execution.process.LocalPtyOptions
|
||||
import com.intellij.execution.wsl.WSLCommandLineOptions
|
||||
import com.intellij.execution.wsl.WSLDistribution
|
||||
import com.intellij.execution.wsl.WslIjentManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.platform.eel.EelExecApi
|
||||
import com.intellij.platform.eel.EelProcess
|
||||
import com.intellij.platform.eel.path.EelPath
|
||||
import com.intellij.platform.eel.spawnProcess
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.deploy
|
||||
import com.intellij.platform.ijent.spi.DeployedIjent
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.concurrency.annotations.RequiresBlockingContext
|
||||
import com.intellij.util.suspendingLazy
|
||||
import com.intellij.platform.ijent.IjentSession
|
||||
import com.intellij.platform.ijent.createIjentSession
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
||||
internal suspend fun deployAndLaunchIjent(
|
||||
@ApiStatus.Internal
|
||||
suspend fun WSLDistribution.createIjentSession(
|
||||
parentScope: CoroutineScope,
|
||||
project: Project?,
|
||||
ijentLabel: String,
|
||||
wslDistribution: WSLDistribution,
|
||||
wslCommandLineOptionsModifier: (WSLCommandLineOptions) -> Unit = {},
|
||||
): IjentPosixApi =
|
||||
deployAndLaunchIjentGettingPath(parentScope, project, ijentLabel, wslDistribution, wslCommandLineOptionsModifier).ijentApi
|
||||
|
||||
@VisibleForTesting
|
||||
suspend fun deployAndLaunchIjentGettingPath(
|
||||
parentScope: CoroutineScope,
|
||||
project: Project?,
|
||||
ijentLabel: String,
|
||||
wslDistribution: WSLDistribution,
|
||||
wslCommandLineOptionsModifier: (WSLCommandLineOptions) -> Unit = {},
|
||||
): DeployedIjent.Posix {
|
||||
): IjentSession<IjentPosixApi> {
|
||||
return WslIjentDeployingStrategy(
|
||||
scope = parentScope,
|
||||
ijentLabel = ijentLabel,
|
||||
distribution = wslDistribution,
|
||||
distribution = this,
|
||||
project = project,
|
||||
wslCommandLineOptionsModifier = wslCommandLineOptionsModifier
|
||||
).deploy()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An adapter for [com.intellij.platform.ijent.IjentExecApi.fetchLoginShellEnvVariables] for Java.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
@RequiresBlockingContext
|
||||
fun fetchLoginShellEnv(
|
||||
wslIjentManager: WslIjentManager,
|
||||
wslDistribution: WSLDistribution,
|
||||
project: Project?,
|
||||
rootUser: Boolean,
|
||||
): Map<String, String> =
|
||||
runBlockingCancellable {
|
||||
wslIjentManager.getIjentApi(wslDistribution, project, rootUser).exec.fetchLoginShellEnvVariables()
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a process inside a WSL container defined by [wslDistribution] using IJent.
|
||||
*
|
||||
* [project] is supposed to be used only for showing notifications with appropriate modality. Therefore, it may be almost safely omitted.
|
||||
*
|
||||
* [processBuilder] chosen as a convenient adapter for already written code. The functions uses command line arguments, environment
|
||||
* variables and the working directory defined by [processBuilder].
|
||||
*
|
||||
* The function ignores [ProcessBuilder.redirectInput], [ProcessBuilder.redirectOutput], [ProcessBuilder.redirectError] and similar
|
||||
* methods. Stdin, stdout, and stderr are always piped. The caller MUST drain both [Process.getInputStream] and [Process.getErrorStream].
|
||||
* Otherwise, the remote operating system may suspend the remote process due to buffer overflow.
|
||||
*
|
||||
* [ProcessBuilder.directory] is a Windows path, and the constructor of [java.io.File] can corrupt the path. Therefore,
|
||||
* [WSLCommandLineOptions.getRemoteWorkingDirectory] is preferred over [ProcessBuilder.directory].
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
@RequiresBlockingContext
|
||||
fun runProcessBlocking(
|
||||
wslIjentManager: WslIjentManager,
|
||||
project: Project?,
|
||||
wslDistribution: WSLDistribution,
|
||||
processBuilder: ProcessBuilder,
|
||||
options: WSLCommandLineOptions,
|
||||
ptyOptions: LocalPtyOptions?,
|
||||
): Process = runBlockingCancellable {
|
||||
val ijentApi = wslIjentManager.getIjentApi(wslDistribution, project, options.isSudo)
|
||||
|
||||
val args = processBuilder.command().toMutableList()
|
||||
|
||||
require(!options.isExecuteCommandInDefaultShell) {
|
||||
"This API is not supposed to handle WSLCommandLineOptions.isExecuteCommandInDefaultShell"
|
||||
}
|
||||
|
||||
val shell = suspendingLazy {
|
||||
ijentApi.exec.fetchLoginShellEnvVariables()["SHELL"]
|
||||
?: WSLDistribution.DEFAULT_SHELL
|
||||
}
|
||||
|
||||
val shellInitCommands = options.initShellCommands.asReversed().toMutableList()
|
||||
|
||||
val explicitEnvironmentVariables: Map<String, String>
|
||||
if (options.isExecuteCommandInShell && !options.isPassEnvVarsUsingInterop) {
|
||||
explicitEnvironmentVariables = mapOf()
|
||||
for ((name, value) in processBuilder.environment().entries.sortedBy { (key, _) -> key }) {
|
||||
if (WSLDistribution.ENV_VARIABLE_NAME_PATTERN.matcher(name).matches()) {
|
||||
shellInitCommands += "export ${posixQuote(name)}=${posixQuote(value)}"
|
||||
}
|
||||
else {
|
||||
LOG.debug { "Can not pass environment variable (bad name): '$name'" }
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
explicitEnvironmentVariables = processBuilder.environment()
|
||||
}
|
||||
|
||||
options.remoteWorkingDirectory?.takeIf(String::isNotEmpty)?.let { remoteWorkingDirectory ->
|
||||
// Although there's another and more straightforward way to specify the working directory, this code repeats the logic from
|
||||
// `WSLDistribution.doPatchCommandLine`, just to not break someone's workflow.
|
||||
shellInitCommands += "cd ${posixQuote(remoteWorkingDirectory)}"
|
||||
}
|
||||
|
||||
if (options.isExecuteCommandInShell || shellInitCommands.isNotEmpty()) {
|
||||
// The sequence of the argument should correspond the sequence from `com.intellij.execution.wsl.WSLDistribution#doPatchCommandLine`
|
||||
// Although some argument may be mixed up de-facto, the function is covered by unit tests that check arguments using strict comparison.
|
||||
val shellArgument = (shellInitCommands + args.joinToString(" ", transform = ::posixQuote)).joinToString(" && ")
|
||||
args.clear()
|
||||
args += shell.getValue()
|
||||
if (options.isExecuteCommandInInteractiveShell) {
|
||||
args += "-i"
|
||||
}
|
||||
if (options.isExecuteCommandInLoginShell) {
|
||||
args += "-l"
|
||||
}
|
||||
args += "-c"
|
||||
args += shellArgument
|
||||
}
|
||||
|
||||
val exePath = FileUtil.toSystemIndependentName(args.removeFirst())
|
||||
|
||||
val interactionOptions = when {
|
||||
ptyOptions != null -> with(ptyOptions) { EelExecApi.Pty(initialColumns, initialRows, !consoleMode) }
|
||||
processBuilder.redirectErrorStream() -> EelExecApi.RedirectStdErr(to = EelExecApi.RedirectTo.STDOUT)
|
||||
else -> null
|
||||
}
|
||||
|
||||
val workingDirectory = processBuilder.directory()?.toPath()?.let { windowsWorkingDirectory ->
|
||||
wslDistribution.getWslPath(windowsWorkingDirectory)
|
||||
?: run {
|
||||
LOG.warn("Working directory $windowsWorkingDirectory can't be mapped to WSL distribution ${wslDistribution.id}", Throwable())
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val scope = @OptIn(DelicateCoroutinesApi::class) (wslIjentManager.processAdapterScope)
|
||||
ijentApi.exec.spawnProcess(exePath)
|
||||
.args(args)
|
||||
.env(explicitEnvironmentVariables)
|
||||
.interactionOptions(interactionOptions)
|
||||
.workingDirectory(workingDirectory?.let { EelPath.parse(it, ijentApi.descriptor) })
|
||||
.eelIt()
|
||||
.toProcess(
|
||||
coroutineScope = scope,
|
||||
isPty = interactionOptions != null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun EelProcess.toProcess(
|
||||
coroutineScope: CoroutineScope,
|
||||
isPty: Boolean,
|
||||
): Process =
|
||||
if (isPty)
|
||||
IjentChildPtyProcessAdapter(coroutineScope, this)
|
||||
else
|
||||
IjentChildProcessAdapter(coroutineScope, this)
|
||||
|
||||
private val LOG by lazy { Logger.getInstance("com.intellij.execution.wsl.WslIjentUtil") }
|
||||
).createIjentSession()
|
||||
}
|
||||
@@ -4,7 +4,9 @@ package com.intellij.execution.wsl
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.components.serviceAsync
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.eel.EelDescriptor
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.IjentSession
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -22,9 +24,27 @@ interface WslIjentManager {
|
||||
val processAdapterScope: CoroutineScope
|
||||
|
||||
/**
|
||||
* The returned instance is not supposed to be closed by the caller. [WslIjentManager] closes [IjentApi] by itself during shutdown.
|
||||
* Acquires an [IjentPosixApi] for the given WSL distribution.
|
||||
*
|
||||
* ## Descriptor Resolution
|
||||
* If [descriptor] is `null`, a fallback resolution is performed:
|
||||
* - If [project] is provided, [getEelDescriptor] is called on the project to derive the [EelDescriptor].
|
||||
* - If [project] is also `null`, a default descriptor is created from [wslDistribution] using [WslEelDescriptor].
|
||||
*
|
||||
* This ensures that an [EelDescriptor] is always passed to [IjentSession.getIjentInstance], which may influence
|
||||
* environment configuration or file path resolution.
|
||||
*
|
||||
* ## Lifecycle
|
||||
* - The returned [IjentPosixApi] is managed internally and **must not** be closed by the caller.
|
||||
* - The underlying session is closed automatically when the enclosing coroutine scope completes.
|
||||
*
|
||||
* @param descriptor An optional descriptor pointing to a specific environment path.
|
||||
* @param wslDistribution The target WSL distribution (e.g. Ubuntu, Debian).
|
||||
* @param project An optional project, used as a fallback source for descriptor resolution.
|
||||
* @param rootUser Whether the Ijent should run with root privileges (`sudo`).
|
||||
* @return An initialized [IjentPosixApi] ready for interaction with the WSL environment.
|
||||
*/
|
||||
suspend fun getIjentApi(wslDistribution: WSLDistribution, project: Project?, rootUser: Boolean): IjentPosixApi
|
||||
suspend fun getIjentApi(descriptor: EelDescriptor?, wslDistribution: WSLDistribution, project: Project?, rootUser: Boolean): IjentPosixApi
|
||||
|
||||
@Deprecated(
|
||||
"Use WslIjentAvailabilityService.runWslCommandsViaIjent",
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.intellij.platform.eel.EelExecApi
|
||||
import com.intellij.platform.eel.EelProcess
|
||||
import com.intellij.platform.eel.path.EelPath
|
||||
import com.intellij.platform.eel.spawnProcess
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.concurrency.annotations.RequiresBlockingContext
|
||||
import com.intellij.util.suspendingLazy
|
||||
@@ -37,7 +38,7 @@ fun fetchLoginShellEnv(
|
||||
rootUser: Boolean,
|
||||
): Map<String, String> =
|
||||
runBlockingCancellable {
|
||||
wslIjentManager.getIjentApi(wslDistribution, project, rootUser).exec.fetchLoginShellEnvVariables()
|
||||
wslIjentManager.getIjentApi(null, wslDistribution, project, rootUser).exec.fetchLoginShellEnvVariables()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ fun runProcessBlocking(
|
||||
options: WSLCommandLineOptions,
|
||||
ptyOptions: LocalPtyOptions?,
|
||||
): Process = runBlockingCancellable {
|
||||
val ijentApi = wslIjentManager.getIjentApi(wslDistribution, project, options.isSudo)
|
||||
val ijentApi: IjentPosixApi = wslIjentManager.getIjentApi(null, wslDistribution, project, options.isSudo)
|
||||
|
||||
val args = processBuilder.command().toMutableList()
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.intellij.platform.eel.EelExecApi.ExternalCliEntrypoint
|
||||
import com.intellij.platform.eel.path.EelPath
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.IjentProcessInfo
|
||||
import com.intellij.platform.ijent.IjentSession
|
||||
import com.intellij.platform.ijent.IjentTunnelsPosixApi
|
||||
import com.intellij.platform.ijent.fs.IjentFileSystemPosixApi
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
@@ -485,7 +486,7 @@ class WSLDistributionTest {
|
||||
@DelicateCoroutinesApi
|
||||
override val processAdapterScope: CoroutineScope = scope
|
||||
|
||||
override suspend fun getIjentApi(wslDistribution: WSLDistribution, project: Project?, rootUser: Boolean): IjentPosixApi {
|
||||
override suspend fun getIjentApi(descriptor: EelDescriptor?, wslDistribution: WSLDistribution, project: Project?, rootUser: Boolean): IjentPosixApi {
|
||||
require(wslDistribution == mockWslDistribution) { "$wslDistribution != $mockWslDistribution" }
|
||||
return MockIjentApi(adapter, rootUser)
|
||||
}
|
||||
@@ -526,7 +527,7 @@ private class MockIjentApi(private val adapter: GeneralCommandLine, val rootUser
|
||||
override val name: @NonNls String = "mock"
|
||||
override val osFamily: EelOsFamily = this@MockIjentApi.platform.osFamily
|
||||
|
||||
override suspend fun toEelApi(): EelApi {
|
||||
override suspend fun toEelApi(descriptor: EelDescriptor): EelApi {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ internal class EelTestDescriptor(override val rootPath: Path, val id: String, ov
|
||||
override val machine: EelMachine = object : EelMachine {
|
||||
override val name: @NonNls String = "mock $id"
|
||||
override val osFamily: EelOsFamily get() = this@EelTestDescriptor.osFamily
|
||||
override suspend fun toEelApi(): EelApi = apiProvider()
|
||||
override suspend fun toEelApi(descriptor: EelDescriptor): EelApi = apiProvider()
|
||||
}
|
||||
|
||||
override suspend fun toEelApi(): EelApi {
|
||||
|
||||
+5
-5
@@ -50,13 +50,13 @@ internal suspend fun getCacheTimeout(): Duration? =
|
||||
@Internal
|
||||
internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonService, SimplePersistentStateComponent<MyServiceState>(MyServiceState()) {
|
||||
private val findPythonsMutex = Mutex()
|
||||
private val _cacheImpl: CompletableDeferred<Cache<EelMachine, SystemPython>?> = CompletableDeferred()
|
||||
private val _cacheImpl: CompletableDeferred<Cache<EelDescriptor, SystemPython>?> = CompletableDeferred()
|
||||
private suspend fun cache() = _cacheImpl.await()
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
_cacheImpl.complete(getCacheTimeout()?.let { interval ->
|
||||
Cache<EelMachine, SystemPython>(scope, interval) { eelDescriptor ->
|
||||
Cache<EelDescriptor, SystemPython>(scope, interval) { eelDescriptor ->
|
||||
searchPythonsPhysicallyNoCache(eelDescriptor.toEelApi())
|
||||
}
|
||||
})
|
||||
@@ -68,7 +68,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
|
||||
.getOr(PySystemPythonBundle.message("py.system.python.service.python.is.broken", pythonPath)) { return it }
|
||||
val systemPython = SystemPython(pythonWithLangLevel, null)
|
||||
state.userProvidedPythons.add(pythonPath.pathString)
|
||||
cache()?.get(pythonPath.getEelDescriptor().machine)?.add(systemPython)
|
||||
cache()?.get(pythonPath.getEelDescriptor())?.add(systemPython)
|
||||
return Result.success(systemPython)
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
|
||||
cache.startUpdate()
|
||||
if (forceRefresh) {
|
||||
logger.info("pythons refresh requested")
|
||||
cache.updateCache(eelApi.descriptor.machine) // Update cache and suspend till update finished
|
||||
cache.updateCache(eelApi.descriptor) // Update cache and suspend till update finished
|
||||
}
|
||||
else {
|
||||
cache.get(eelApi.descriptor.machine)
|
||||
cache.get(eelApi.descriptor)
|
||||
}.sorted()
|
||||
} ?: searchPythonsPhysicallyNoCache(eelApi).sorted()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user