Skip to content

Commit 54344b9

Browse files
authored
Merge pull request #50 from ciuncan/1.0.x
Add support for launching Ammonite REPL from sbt
2 parents 5ec1ea1 + 5ab0165 commit 54344b9

File tree

3 files changed

+159
-22
lines changed

3 files changed

+159
-22
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
target/
22
local.sbt
33
secret/
4+
.metals/
5+
.bloop/
6+
metals.sbt

README.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# sbt-ammonite-classpath
22

3-
**sbt-ammonite-classpath** is an sbt plug-in to export classpath of an sbt project to Ammonite Script, which can be then used in [Ammonite](https://ammonite.io/) or [Almond](http://almond.sh/).
3+
**sbt-ammonite-classpath** is an sbt plug-in to export classpath of an sbt project to Ammonite Script, which can be then used in [Ammonite](https://ammonite.io/) or [Almond](http://almond.sh/). Also supports running Ammonite REPL directly with desired classpath.
44

55
## Usage
66

@@ -18,6 +18,8 @@ object MyObject {
1818
}
1919
```
2020

21+
### Exporting Classpath for Almond or Ammonite
22+
2123
``` bash
2224
$ sbt Compile/fullClasspath/exportToAmmoniteScript && amm --predef target/scala-2.12/fullClasspath-Compile.sc
2325
...
@@ -51,6 +53,69 @@ import $file.$
5153
@ mypackage.MyObject.hello()
5254
Hello, World!
5355
```
56+
57+
### Launching Ammonite REPL
58+
59+
This plugin also supports directly running Ammonite REPL from sbt. Similar to using above scopes you may launch the Ammonite REPL with desired classpath and compile scope as follows:
60+
61+
``` bash
62+
sbt "{scope}:{classpath}::launchAmmoniteRepl"
63+
```
64+
65+
Where **`scope`** can be one of `compile`, `test` and `runtime`, while **`classpath`** can be one of `fullClasspath`, `dependencyClasspath`, `managedClasspath`, `unmanagedClasspath`.
66+
67+
Example:
68+
69+
``` bash
70+
sbt "test:dependencyClasspath::launchAmmoniteRepl"
71+
```
72+
73+
If you would like to run Ammonite REPL with full classpath, you can simply use `launchAmmoniteRepl` task within `compile` (or any other) scope without having to specify classpath task scope:
74+
75+
``` bash
76+
sbt "compile:launchAmmoniteRepl"
77+
# or simply (without scope, compile will be implied)
78+
sbt launchAmmoniteRepl
79+
```
80+
81+
`initialCommands` setting is also supported. If your `initialCommands` or `launchAmmoniteRepl / initialCommands` setting is not appropriate for a given scope, you can override it in one of this plugin's scopes. For example if you would like to only have `import ammonite.ops._` in your Ammonite REPL but not Scala REPL, you can do as follows:
82+
``` scala
83+
...
84+
85+
console / initialCommands := "println(\"Hello Console\")",
86+
87+
Compile / launchAmmoniteRepl / initialCommands += "\nimport ammonite.ops._"
88+
89+
// or simply launchAmmoniteRepl / initialCommands
90+
...
91+
```
92+
93+
When you run `sbt launchAmmoniteRepl`, both commands will be in effect:
94+
95+
``` bash
96+
sbt launchAmmoniteRepl
97+
...
98+
[info] running ammonite.Main --predef /private/tmp/example/target/scala-2.13/fullClasspath-Compile.sc --predef-code "println("Hello Console")
99+
[info] import ammonite.ops._"
100+
Loading...
101+
Hello Console
102+
Welcome to the Ammonite Repl 2.2.0-4-4bd225e (Scala 2.13.3 Java 1.8.0_252)
103+
@ ls! pwd
104+
res2: LsSeq =
105+
".bloop" ".gitignore" ".vscode" "build.sbt" 'target
106+
".git" ".metals" 'LICENCE 'project 'test
107+
".github" ".scalafmt.conf" "README.md" 'src
108+
109+
@
110+
```
111+
112+
By default it will use the `"latest.release"` version of Ammonite, but if you would like to change it, you can override `ammoniteVersion` setting, e.g.:
113+
114+
``` scala
115+
ammoniteVersion := "2.1.4"
116+
Test / ammoniteVersion := "2.2.0"
117+
```
118+
54119
## Related work
55120
56121
[sbt-ammonite](https://github.com/alexarchambault/sbt-ammonite) is an sbt 0.13 plug-in to launch Ammonite. It automatically passes the classpath instead of creating a `sc` file. However, it does not support Almond.

src/main/scala/com/thoughtworks/deeplearning/sbtammoniteclasspath/AmmoniteClasspath.scala

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,106 @@ object AmmoniteClasspath extends AutoPlugin {
1616
object autoImport {
1717
val exportToAmmoniteScript = taskKey[File](
1818
"Export classpath as a .sc file, which can be loaded by another ammonite script or an Almond notebook")
19+
20+
lazy val Ammonite = config("ammonite")
21+
.extend(Compile)
22+
.withDescription("Ammonite config to run REPL, similar to Compile (default) config.")
23+
lazy val AmmoniteTest =
24+
config("ammonite-test")
25+
.extend(Test)
26+
.withDescription("Ammonite config to run REPL, similar to Test config.")
27+
lazy val AmmoniteRuntime = config("ammonite-runtime")
28+
.extend(Runtime)
29+
.withDescription("Ammonite config to run REPL, similar to Runtime config.")
30+
31+
lazy val launchAmmoniteRepl = taskKey[Unit]("Run Ammonite REPL")
32+
33+
lazy val ammoniteVersion = settingKey[String]("Ammonite REPL Version")
1934
}
35+
2036
import autoImport._
2137

22-
override def projectSettings: Seq[Def.Setting[_]] = Seq {
38+
override def globalSettings: Seq[Def.Setting[_]] = Seq(
39+
ammoniteVersion := "latest.release"
40+
)
41+
42+
override def projectSettings: Seq[Def.Setting[_]] =
43+
classpathExportSettings ++
44+
ammoniteRunSettings(Ammonite, Compile) ++
45+
ammoniteRunSettings(AmmoniteTest, Test) ++
46+
ammoniteRunSettings(AmmoniteRuntime, Runtime)
47+
48+
private val allClasspathKeys = Seq(fullClasspath, dependencyClasspath, managedClasspath, unmanagedClasspath)
49+
50+
def classpathExportSettings: Seq[Def.Setting[_]] = {
2351
val configuration = !Each(Seq(Compile, Test, Runtime))
24-
val classpathKey = !Each(Seq(fullClasspath, dependencyClasspath, managedClasspath, unmanagedClasspath))
52+
val classpathKey = !Each(allClasspathKeys)
2553

26-
exportToAmmoniteScript in classpathKey in configuration := {
27-
val code = {
28-
def ammonitePaths = List {
29-
q"_root_.ammonite.ops.Path(${(!Each((classpathKey in configuration).value)).data.toString})"
30-
}
54+
Seq(
55+
configuration / classpathKey / exportToAmmoniteScript := {
56+
val code = {
57+
def ammonitePaths = List {
58+
q"_root_.ammonite.ops.Path(${(!Each((configuration / classpathKey).value)).data.toString})"
59+
}
3160

32-
def mkdirs = List {
33-
val ammonitePath = !Each(ammonitePaths)
34-
q"""
35-
if (!_root_.ammonite.ops.exists($ammonitePath)) {
36-
_root_.ammonite.ops.mkdir($ammonitePath)
61+
def mkdirs = List {
62+
val ammonitePath = !Each(ammonitePaths)
63+
q"""
64+
if (!_root_.ammonite.ops.exists($ammonitePath)) {
65+
_root_.ammonite.ops.mkdir($ammonitePath)
66+
}
67+
"""
3768
}
69+
70+
q"""
71+
..$mkdirs
72+
interp.load.cp(Seq(..$ammonitePaths))
3873
"""
3974
}
40-
41-
q"""
42-
..$mkdirs
43-
interp.load.cp(Seq(..$ammonitePaths))
44-
"""
75+
val file = (configuration / crossTarget).value / s"${classpathKey.key.label}-${configuration.id}.sc"
76+
IO.write(file, code.syntax)
77+
file
4578
}
46-
val file = (crossTarget in configuration).value / s"${classpathKey.key.label}-${configuration.id}.sc"
47-
IO.write(file, code.syntax)
48-
file
49-
}
79+
)
80+
}
81+
82+
def ammoniteRunSettings(ammConf: Configuration, backingConf: Configuration): Seq[Def.Setting[_]] =
83+
inConfig(ammConf)(
84+
Defaults.compileSettings ++
85+
Classpaths.ivyBaseSettings ++
86+
Seq(
87+
libraryDependencies := Seq(("com.lihaoyi" %% "ammonite" % (ammConf / ammoniteVersion).value).cross(CrossVersion.full)),
88+
connectInput := true,
89+
console := runTask(ammConf, backingConf, fullClasspath, ammConf / run / runner).value
90+
) ++
91+
allClasspathKeys.map(classpathKey =>
92+
classpathKey / console := runTask(ammConf, backingConf, classpathKey, ammConf / run / runner).value
93+
)
94+
) ++ Seq(
95+
backingConf / launchAmmoniteRepl := (ammConf / console).value,
96+
backingConf / launchAmmoniteRepl / initialCommands := (ammConf / console / initialCommands).value
97+
) ++
98+
allClasspathKeys.map(classpathKey =>
99+
backingConf / classpathKey / launchAmmoniteRepl := (ammConf / classpathKey / console).value
100+
)
50101

102+
private def runTask(
103+
ammConf: Configuration,
104+
backingConf: Configuration,
105+
classpath: TaskKey[Classpath],
106+
scalaRun: Def.Initialize[Task[ScalaRun]],
107+
): Def.Initialize[Task[Unit]] = {
108+
import Def.parserToInput
109+
val parser = Def.spaceDelimited()
110+
Def.task {
111+
val mainClass = "ammonite.Main"
112+
val args = Seq(
113+
"--predef", (backingConf / classpath / exportToAmmoniteScript).value.absolutePath,
114+
"--predef-code", (backingConf / launchAmmoniteRepl / initialCommands).value
115+
)
116+
val ammoniteOnlyClasspathFiles = sbt.Attributed.data((ammConf / managedClasspath).value)
117+
scalaRun.value.run(mainClass, ammoniteOnlyClasspathFiles, args, streams.value.log).get
118+
}
51119
}
120+
52121
}

0 commit comments

Comments
 (0)