Skip to content

Commit 6f8bc43

Browse files
authored
Merge pull request #2690 from dotty-staging/ide-cross
Fixes for sbt-dotty
2 parents 7e9ea25 + 4d05747 commit 6f8bc43

File tree

3 files changed

+151
-90
lines changed

3 files changed

+151
-90
lines changed

project/Build.scala

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ object Build {
9191
bootstrapFromPublishedJars := false,
9292

9393

94-
// Override `launchIDE` from sbt-dotty to use the language-server and
94+
// Override `runCode` from sbt-dotty to use the language-server and
9595
// vscode extension from the source repository of dotty instead of a
9696
// published version.
97-
launchIDE := (run in `dotty-language-server`).dependsOn(prepareIDE).toTask("").value
97+
runCode := (run in `dotty-language-server`).toTask("").value
9898
)
9999

100100
// Only available in vscode-dotty
@@ -882,7 +882,7 @@ object Build {
882882

883883

884884
sbtPlugin := true,
885-
version := "0.1.1",
885+
version := "0.1.2",
886886
ScriptedPlugin.scriptedSettings,
887887
ScriptedPlugin.sbtTestDirectory := baseDirectory.value / "sbt-test",
888888
ScriptedPlugin.scriptedBufferLog := false,
@@ -920,19 +920,15 @@ object Build {
920920
.start()
921921
.waitFor()
922922
if (exitCode != 0)
923-
throw new FeedbackProvidedException {
924-
override def toString = "'npm run update-all' in vscode-dotty failed"
925-
}
923+
throw new MessageOnlyException("'npm run update-all' in vscode-dotty failed")
926924
}
927925
val tsc = baseDirectory.value / "node_modules" / ".bin" / "tsc"
928926
val exitCodeTsc = new java.lang.ProcessBuilder(tsc.getAbsolutePath, "--pretty", "--project", baseDirectory.value.getAbsolutePath)
929927
.inheritIO()
930928
.start()
931929
.waitFor()
932930
if (exitCodeTsc != 0)
933-
throw new FeedbackProvidedException {
934-
override def toString = "tsc in vscode-dotty failed"
935-
}
931+
throw new MessageOnlyException("tsc in vscode-dotty failed")
936932

937933
// Currently, vscode-dotty depends on daltonjorge.scala for syntax highlighting,
938934
// this is not automatically installed when starting the extension in development mode
@@ -942,9 +938,7 @@ object Build {
942938
.start()
943939
.waitFor()
944940
if (exitCodeInstall != 0)
945-
throw new FeedbackProvidedException {
946-
override def toString = "Installing dependency daltonjorge.scala failed"
947-
}
941+
throw new MessageOnlyException("Installing dependency daltonjorge.scala failed")
948942

949943
sbt.inc.Analysis.Empty
950944
},
@@ -955,9 +949,7 @@ object Build {
955949
.start()
956950
.waitFor()
957951
if (exitCode != 0)
958-
throw new FeedbackProvidedException {
959-
override def toString = "vsce package failed"
960-
}
952+
throw new MessageOnlyException("vsce package failed")
961953

962954
baseDirectory.value / s"dotty-${version.value}.vsix"
963955
},
@@ -968,9 +960,7 @@ object Build {
968960
.start()
969961
.waitFor()
970962
if (exitCode != 0)
971-
throw new FeedbackProvidedException {
972-
override def toString = "vsce unpublish failed"
973-
}
963+
throw new MessageOnlyException("vsce unpublish failed")
974964
},
975965
publish := {
976966
val exitCode = new java.lang.ProcessBuilder("vsce", "publish")
@@ -979,9 +969,7 @@ object Build {
979969
.start()
980970
.waitFor()
981971
if (exitCode != 0)
982-
throw new FeedbackProvidedException {
983-
override def toString = "vsce publish failed"
984-
}
972+
throw new MessageOnlyException("vsce publish failed")
985973
},
986974
run := Def.inputTask {
987975
val inputArgs = spaceDelimited("<arg>").parsed
@@ -994,9 +982,7 @@ object Build {
994982
.start()
995983
.waitFor()
996984
if (exitCode != 0)
997-
throw new FeedbackProvidedException {
998-
override def toString = "Running Visual Studio Code failed"
999-
}
985+
throw new MessageOnlyException("Running Visual Studio Code failed")
1000986
}.dependsOn(compile in Compile).evaluated
1001987
)
1002988

sbt-dotty/src/dotty/tools/sbtplugin/DottyIDEPlugin.scala

Lines changed: 139 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import DottyPlugin.autoImport._
1414

1515
object DottyIDEPlugin extends AutoPlugin {
1616
// Adapted from scala-reflect
17-
private[this] def distinctBy[A, B](xs: Seq[A])(f: A => B): Seq[A] = {
17+
private def distinctBy[A, B](xs: Seq[A])(f: A => B): Seq[A] = {
1818
val buf = new mutable.ListBuffer[A]
1919
val seen = mutable.Set[B]()
2020
xs foreach { x =>
@@ -27,46 +27,162 @@ object DottyIDEPlugin extends AutoPlugin {
2727
buf.toList
2828
}
2929

30-
private def inAllDottyConfigurations[A](key: TaskKey[A], state: State): Task[Seq[A]] = {
31-
val struct = Project.structure(state)
32-
val settings = struct.data
33-
struct.allProjectRefs.flatMap { projRef =>
34-
val project = Project.getProjectForReference(projRef, struct).get
30+
private def isDottyVersion(version: String) =
31+
version.startsWith("0.")
32+
33+
34+
/** Return a new state derived from `state` such that scalaVersion returns `newScalaVersion` in all
35+
* projects in `projRefs` (`state` is returned if no setting needed to be updated).
36+
*/
37+
private def updateScalaVersion(state: State, projRefs: Seq[ProjectRef], newScalaVersion: String): State = {
38+
val extracted = Project.extract(state)
39+
val settings = extracted.structure.data
40+
41+
if (projRefs.forall(projRef => scalaVersion.in(projRef).get(settings).get == newScalaVersion))
42+
state
43+
else {
44+
def matchingSetting(setting: Setting[_]) =
45+
setting.key.key == scalaVersion.key &&
46+
setting.key.scope.project.fold(ref => projRefs.contains(ref), ifGlobal = true, ifThis = true)
47+
48+
val newSettings = extracted.session.mergeSettings.collect {
49+
case setting if matchingSetting(setting) =>
50+
scalaVersion in setting.key.scope := newScalaVersion
51+
}
52+
val newSession = extracted.session.appendRaw(newSettings)
53+
BuiltinCommands.reapply(newSession, extracted.structure, state)
54+
}
55+
}
56+
57+
/** Setup to run in all dotty projects.
58+
* Return a triplet of:
59+
* (1) A version of dotty
60+
* (2) A list of dotty projects
61+
* (3) A state where `scalaVersion` is set to (1) in all projects in (2)
62+
*/
63+
private def dottySetup(state: State): (String, Seq[ProjectRef], State) = {
64+
val structure = Project.structure(state)
65+
val settings = structure.data
66+
67+
// FIXME: this function uses `sorted` to order versions but this is incorrect,
68+
// we need an Ordering for version numbers, like the one in Coursier.
69+
70+
val (dottyVersions, dottyProjRefs) =
71+
structure.allProjectRefs.flatMap { projRef =>
72+
val version = scalaVersion.in(projRef).get(settings).get
73+
if (isDottyVersion(version))
74+
Some((version, projRef))
75+
else
76+
crossScalaVersions.in(projRef).get(settings).get.filter(isDottyVersion).sorted.lastOption match {
77+
case Some(v) =>
78+
Some((v, projRef))
79+
case _ =>
80+
None
81+
}
82+
}.unzip
83+
84+
if (dottyVersions.isEmpty)
85+
throw new MessageOnlyException("No Dotty project detected")
86+
else {
87+
val dottyVersion = dottyVersions.sorted.last
88+
val dottyState = updateScalaVersion(state, dottyProjRefs, dottyVersion)
89+
(dottyVersion, dottyProjRefs, dottyState)
90+
}
91+
}
92+
93+
/** Run `task` in state `state` */
94+
private def runTask[T](task: Task[T], state: State): T = {
95+
val extracted = Project.extract(state)
96+
val structure = extracted.structure
97+
val (_, result) =
98+
EvaluateTask.withStreams(structure, state) { streams =>
99+
EvaluateTask.runTask(task, state, streams, structure.index.triggers,
100+
EvaluateTask.extractedTaskConfig(extracted, structure, state))(
101+
EvaluateTask.nodeView(state, streams, Nil)
102+
)
103+
}
104+
result match {
105+
case Value(v) =>
106+
v
107+
case Inc(i) =>
108+
throw i
109+
}
110+
}
111+
112+
/** Run task `key` in all configurations in all projects in `projRefs`, using state `state` */
113+
private def runInAllConfigurations[T](key: TaskKey[T], projRefs: Seq[ProjectRef], state: State): Seq[T] = {
114+
val structure = Project.structure(state)
115+
val settings = structure.data
116+
val joinedTask = projRefs.flatMap { projRef =>
117+
val project = Project.getProjectForReference(projRef, structure).get
35118
project.configurations.flatMap { config =>
36-
isDotty.in(projRef, config).get(settings) match {
37-
case Some(true) =>
38-
key.in(projRef, config).get(settings)
39-
case _ =>
40-
None
41-
}
119+
key.in(projRef, config).get(settings)
42120
}
43121
}.join
122+
123+
runTask(joinedTask, state)
44124
}
45125

46126
private val projectConfig = taskKey[Option[ProjectConfig]]("")
47-
private val configureIDE = taskKey[Unit]("Generate IDE config files")
48-
private val compileForIDE = taskKey[Unit]("Compile all projects supported by the IDE")
49-
private val runCode = taskKey[Unit]("")
50127

51128
object autoImport {
52-
val prepareIDE = taskKey[Unit]("Prepare for IDE launch")
53-
val launchIDE = taskKey[Unit]("Run Visual Studio Code on this project")
129+
val runCode = taskKey[Unit]("Start VSCode, usually called from launchIDE")
130+
val launchIDE = taskKey[Unit]("Configure and run VSCode on this project")
54131
}
55132

56133
import autoImport._
57134

58135
override def requires: Plugins = plugins.JvmPlugin
59136
override def trigger = allRequirements
60137

138+
def configureIDE = Command.command("configureIDE") { origState =>
139+
val (dottyVersion, projRefs, dottyState) = dottySetup(origState)
140+
val configs0 = runInAllConfigurations(projectConfig, projRefs, dottyState).flatten
141+
142+
// Drop configurations that do not define their own sources, but just
143+
// inherit their sources from some other configuration.
144+
val configs = distinctBy(configs0)(_.sourceDirectories.deep)
145+
146+
// Write the version of the Dotty Language Server to use in a file by itself.
147+
// This could be a field in the JSON config file, but that would require all
148+
// IDE plugins to parse JSON.
149+
val dlsVersion = dottyVersion
150+
.replace("-nonbootstrapped", "") // The language server is only published bootstrapped
151+
val dlsBinaryVersion = dlsVersion.split("\\.").take(2).mkString(".")
152+
val pwArtifact = new PrintWriter(".dotty-ide-artifact")
153+
try {
154+
pwArtifact.println(s"ch.epfl.lamp:dotty-language-server_${dlsBinaryVersion}:${dlsVersion}")
155+
} finally {
156+
pwArtifact.close()
157+
}
158+
159+
val mapper = new ObjectMapper
160+
mapper.writerWithDefaultPrettyPrinter()
161+
.writeValue(new File(".dotty-ide.json"), configs.toArray)
162+
163+
origState
164+
}
165+
166+
def compileForIDE = Command.command("compileForIDE") { origState =>
167+
val (dottyVersion, projRefs, dottyState) = dottySetup(origState)
168+
runInAllConfigurations(compile, projRefs, dottyState)
169+
170+
origState
171+
}
172+
61173
override def projectSettings: Seq[Setting[_]] = Seq(
62174
// Use Def.derive so `projectConfig` is only defined in the configurations where the
63175
// tasks/settings it depends on are defined.
64176
Def.derive(projectConfig := {
65177
if (sources.value.isEmpty) None
66178
else {
179+
// Not needed to generate the config, but this guarantees that the
180+
// generated config is usable by an IDE without any extra compilation
181+
// step.
182+
val _ = compile.value
183+
67184
val id = s"${thisProject.value.id}/${configuration.value.name}"
68185
val compilerVersion = scalaVersion.value
69-
.replace("-nonbootstrapped", "") // The language server is only published bootstrapped
70186
val compilerArguments = scalacOptions.value
71187
val sourceDirectories = unmanagedSourceDirectories.value ++ managedSourceDirectories.value
72188
val depClasspath = Attributed.data(dependencyClasspath.value)
@@ -85,61 +201,20 @@ object DottyIDEPlugin extends AutoPlugin {
85201
)
86202

87203
override def buildSettings: Seq[Setting[_]] = Seq(
88-
configureIDE := {
89-
val log = streams.value.log
90-
91-
val configs0 = state.flatMap(s =>
92-
inAllDottyConfigurations(projectConfig, s)
93-
).value.flatten
94-
// Drop configurations who do not define their own sources, but just
95-
// inherit their sources from some other configuration.
96-
val configs = distinctBy(configs0)(_.sourceDirectories.deep)
97-
98-
if (configs.isEmpty) {
99-
log.error("No Dotty project detected")
100-
} else {
101-
// If different versions of Dotty are used by subprojects, choose the latest one
102-
// FIXME: use a proper version number Ordering that knows that "0.1.1-M1" < "0.1.1"
103-
val ideVersion = configs.map(_.compilerVersion).sorted.last
104-
// Write the version of the Dotty Language Server to use in a file by itself.
105-
// This could be a field in the JSON config file, but that would require all
106-
// IDE plugins to parse JSON.
107-
val pwArtifact = new PrintWriter(".dotty-ide-artifact")
108-
pwArtifact.println(s"ch.epfl.lamp:dotty-language-server_0.1:${ideVersion}")
109-
pwArtifact.close()
110-
111-
val mapper = new ObjectMapper
112-
mapper.writerWithDefaultPrettyPrinter()
113-
.writeValue(new File(".dotty-ide.json"), configs.toArray)
114-
}
115-
},
116-
117-
compileForIDE := {
118-
val _ = state.flatMap(s =>
119-
inAllDottyConfigurations(compile, s)
120-
).value
121-
},
204+
commands ++= Seq(configureIDE, compileForIDE),
122205

123206
runCode := {
124207
val exitCode = new ProcessBuilder("code", "--install-extension", "lampepfl.dotty")
125208
.inheritIO()
126209
.start()
127210
.waitFor()
128211
if (exitCode != 0)
129-
throw new FeedbackProvidedException {
130-
override def toString = "Installing the Dotty support for VSCode failed"
131-
}
212+
throw new MessageOnlyException("Installing the Dotty support for VSCode failed")
132213

133214
new ProcessBuilder("code", baseDirectory.value.getAbsolutePath)
134215
.inheritIO()
135216
.start()
136-
},
137-
138-
prepareIDE := {
139-
val x1 = configureIDE.value
140-
val x2 = compileForIDE.value
141-
},
142-
143-
launchIDE := runCode.dependsOn(prepareIDE).value
144-
)
217+
}
218+
219+
) ++ addCommandAlias("launchIDE", ";configureIDE;runCode")
145220
}

sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ object DottyPlugin extends AutoPlugin {
1414
// - if this was a settingKey, then this would evaluate even if you don't use it.
1515
def dottyLatestNightlyBuild: Option[String] = {
1616
println("Fetching latest Dotty nightly version (requires an internet connection)...")
17-
val Version = """ <version>(0.1\..*-bin.*)</version>""".r
17+
val Version = """ <version>(0.2\..*-bin.*)</version>""".r
1818
val latest = scala.io.Source
1919
.fromURL(
20-
"http://repo1.maven.org/maven2/ch/epfl/lamp/dotty_0.1/maven-metadata.xml")
20+
"http://repo1.maven.org/maven2/ch/epfl/lamp/dotty_0.2/maven-metadata.xml")
2121
.getLines()
2222
.collect { case Version(version) => version }
2323
.toSeq

0 commit comments

Comments
 (0)