Skip to content

Commit bbc1ae6

Browse files
committed
feat(idea): call elide install during project sync
Signed-off-by: Dario Valdespino <dvaldespino00@gmail.com>
1 parent fae22a5 commit bbc1ae6

File tree

3 files changed

+114
-152
lines changed

3 files changed

+114
-152
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package dev.elide.intellij.cli
2+
3+
import com.intellij.openapi.project.Project
4+
import com.intellij.util.io.awaitExit
5+
import dev.elide.intellij.Constants
6+
import dev.elide.intellij.service.ElideDistributionResolver
7+
import java.nio.file.Path
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.async
10+
import kotlinx.coroutines.coroutineScope
11+
12+
/**
13+
* Bridge service used to invoke the Elide CLI on a configured distribution. Use [ElideCommandLine.at] to manually set
14+
* the path to the distribution or [ElideCommandLine.resolve] to automatically retrieve the path from the project
15+
* settings.
16+
*/
17+
class ElideCommandLine private constructor(
18+
private val elideHome: Path,
19+
private val workDir: Path? = null,
20+
) {
21+
/**
22+
* Launch the Elide CLI binary as a subprocess and suspend until it finishes executing. The supplied [block] will
23+
* be called after the process is launched, and can be used to interact with it in any way; note that it is not
24+
* necessary to use [awaitExit] in [block], as it is already called before returning.
25+
*
26+
* Calling this method directly is discouraged in favor of command-specific extensions like [install], which provide
27+
* type-safe handling of arguments.
28+
*/
29+
suspend operator fun <R> invoke(
30+
buildCommand: MutableList<String>.() -> Unit,
31+
block: suspend CoroutineScope.(Process) -> R
32+
): R {
33+
val elideBin = elideHome.resolve(Constants.ELIDE_BINARY)
34+
val command = buildList {
35+
add(elideBin.toString())
36+
buildCommand()
37+
}
38+
39+
val process = ProcessBuilder(command)
40+
.also { if (workDir != null) it.directory(workDir.toFile()) }
41+
.start()
42+
43+
return coroutineScope {
44+
val result = async { block(process) }
45+
46+
process.awaitExit()
47+
result.await()
48+
}
49+
}
50+
51+
companion object {
52+
/** Returns an [ElideCommandLine] at the given [elideHome], optionally using [workDir] when invoking commands. */
53+
@JvmStatic fun at(elideHome: Path, workDir: Path? = null): ElideCommandLine {
54+
return ElideCommandLine(elideHome, workDir)
55+
}
56+
57+
/** Returns an [ElideCommandLine] configured according to a linked external project's settings. */
58+
@JvmStatic fun resolve(project: Project, externalProjectPath: String, workDir: Path? = null): ElideCommandLine {
59+
return at(ElideDistributionResolver.getElideHome(project, externalProjectPath), workDir)
60+
}
61+
}
62+
}
63+
64+
/**
65+
* Calls `elide install`, optionally passing [`--with=sources`][withSources] and [`--with=docs`][withDocs], and returns
66+
* the exit code. The [useProcess] block can be used to access the output streams for monitoring the operation.
67+
*/
68+
suspend fun ElideCommandLine.install(
69+
withSources: Boolean = true,
70+
withDocs: Boolean = true,
71+
useProcess: suspend CoroutineScope.(Process) -> Unit = {},
72+
): Int {
73+
return invoke(
74+
buildCommand = {
75+
add("install")
76+
if (withSources) add("--with=sources")
77+
if (withDocs) add("--with=docs")
78+
},
79+
) {
80+
useProcess(it)
81+
it.awaitExit()
82+
}
83+
}

packages/plugin-idea/src/main/kotlin/dev/elide/intellij/project/ElideProjectResolver.kt

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,18 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
2121
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent
2222
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener
2323
import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver
24+
import com.intellij.openapi.progress.coroutineToIndicator
2425
import com.intellij.openapi.progress.runBlockingCancellable
2526
import dev.elide.intellij.Constants
27+
import dev.elide.intellij.cli.ElideCommandLine
28+
import dev.elide.intellij.cli.install
2629
import dev.elide.intellij.manifests.ElideManifestService
2730
import dev.elide.intellij.project.model.ElideProjectModel
2831
import dev.elide.intellij.service.ElideDistributionResolver
2932
import dev.elide.intellij.settings.ElideExecutionSettings
3033
import org.jetbrains.annotations.PropertyKey
3134
import java.nio.file.Path
35+
import kotlinx.coroutines.launch
3236
import kotlin.io.path.Path
3337
import kotlin.io.path.notExists
3438
import elide.tooling.lockfile.LockfileLoader
@@ -65,6 +69,9 @@ class ElideProjectResolver : ExternalSystemProjectResolver<ElideExecutionSetting
6569
listener: ExternalSystemTaskNotificationListener
6670
): DataNode<ProjectData>? {
6771
return runBlockingCancellable {
72+
coroutineToIndicator {
73+
74+
}
6875
LOG.debug("Resolving project at '$projectPath'")
6976

7077
val projectModel = runCatching {
@@ -82,14 +89,35 @@ class ElideProjectResolver : ExternalSystemProjectResolver<ElideExecutionSetting
8289
listener.onStep(id, progressMessage("resolve.steps.parse"))
8390
val manifest = ElideManifestService().parse(manifestPath)
8491

85-
// call the CLI in case the lockfile is outdated
86-
listener.onStep(id, progressMessage("resolve.steps.refresh"))
87-
8892
// configure the project
8993
listener.onStep(id, progressMessage("resolve.steps.configure"))
9094
val loader = buildProjectLoader(projectRoot, settings)
9195
val project = ElideProjectInfo(projectRoot, manifest, null).load(loader)
9296

97+
// call the CLI in case the lockfile is outdated
98+
listener.onStep(id, progressMessage("resolve.steps.refresh"))
99+
val elideHome = settings?.elideHome ?: ElideDistributionResolver.defaultDistributionPath()
100+
val cli = ElideCommandLine.at(elideHome, projectRoot)
101+
102+
val exitCode = cli.install(
103+
withSources = settings?.downloadSources ?: true,
104+
withDocs = settings?.downloadDocs ?: true,
105+
) { process ->
106+
// pass process output on to the IDE
107+
launch {
108+
process.inputStream.bufferedReader().forEachLine {
109+
listener.onTaskOutput(id, "$it\n", true)
110+
}
111+
}
112+
launch {
113+
process.errorStream.bufferedReader().forEachLine {
114+
listener.onTaskOutput(id, "$it\n", false)
115+
}
116+
}
117+
}
118+
119+
if (exitCode != 0) error("Command 'elide install' failed with exit code $exitCode")
120+
93121
// build the project model from the manifest and lockfile
94122
listener.onStep(id, progressMessage("resolve.steps.buildModel"))
95123
ElideProjectModel.buildProjectModel(projectRoot, project)

packages/plugin-idea/src/main/kotlin/dev/elide/intellij/service/ElideCliService.kt

Lines changed: 0 additions & 149 deletions
This file was deleted.

0 commit comments

Comments
 (0)