Useful Coursier commands for every dev

The Scala ecosystem has a lot of incredible tools, and for some reason I feel like the we do a poor job at highlighting them as well as showing examples of how useful they can be to us on a daily basis. One of these tools that I believe deserves more attention than it does is Coursier.

Coursier

I often hear people mention Coursier mainly when they're talking about sbt, and how as of sbt 1.3, it ships with Coursier out of the box and utilizes it to fetch dependencies. While that's where the knowledge of Coursier ends for some, I want to show a few use cases of Coursier that I think are both simple and extremely useful for daily things we do as Scala devs.

Coursier CLI

Over the past couple of weeks I've noticed my usage of Coursier CLI spike as I've been either shown or come across new cs commands. cs is the native launcher for Coursier. You can find information on how to install it here along with a nice overview of the few commands I'll show in this post and some others I won't cover. Again, my goal for this isn't to do an in-depth dive into all the ways you can use cs but to give you some practical examples with a few commands that you should be able to start using right away. The following examples are all real scenarios that I came across in the last couple weeks.

resolve

While at work, I was in a conversation in a pull request about how my team didn't love the Java Api of Caffeine, which we were using for some caching. We came across Scaffeine, which is a thin Scala wrapper around the library. Great! However, we try to be mindful of how many dependencies we add, and the question came up of how many transitive dependencies we'd be bringing in by including this. So the question becomes: what is the easiest way to check this? Enter cs resolve. The resolve command is a quick way to print out transitive dependencies of one or more other dependencies. So, I did the following:

❯ cs resolve com.github.blemale:scaffeine_2.12:3.1.0
https://repo1.maven.org/maven2/com/github/blemale/scaffeine_2.12/3.1.0/scaffeine_2.12-3.1.0.pom
  100.0% [##########] 2.6 KiB (7.6 KiB / s)
https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.8/scala-library-2.12.8.pom
  100.0% [##########] 1.6 KiB (26.0 KiB / s)
https://repo1.maven.org/maven2/org/scala-lang/modules/scala-java8-compat_2.12/0.9.0/scala-java8-compat_2.12-0.9.0.pom
  100.0% [##########] 2.3 KiB (14.4 KiB / s)
https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.0/caffeine-2.8.0.pom
  100.0% [##########] 5.2 KiB (25.8 KiB / s)
https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.3/error_prone_annotations-2.3.3.pom
  100.0% [##########] 1.7 KiB (29.5 KiB / s)
https://repo1.maven.org/maven2/org/checkerframework/checker-qual/2.10.0/checker-qual-2.10.0.pom
  100.0% [##########] 2.3 KiB (39.7 KiB / s)
https://repo1.maven.org/maven2/com/google/errorprone/error_prone_parent/2.3.3/error_prone_parent-2.3.3.pom
  100.0% [##########] 5.2 KiB (62.5 KiB / s)
com.github.ben-manes.caffeine:caffeine:2.8.0:default
com.github.blemale:scaffeine_2.12:3.1.0:default
com.google.errorprone:error_prone_annotations:2.3.3:default
org.checkerframework:checker-qual:2.10.0:default
org.scala-lang:scala-library:2.12.8:default
org.scala-lang.modules:scala-java8-compat_2.12:0.9.0:default

As you can see, the full artifacts weren't fetched, only the metadata necessary to be able to list the transitive dependencies, which can be found at the bottom. You can also get a nice tree view of this by passing in the --tree or -t option.

❯ cs resolve -t com.github.blemale:scaffeine_2.12:3.1.0
  Result:
└─ com.github.blemale:scaffeine_2.12:3.1.0
   ├─ com.github.ben-manes.caffeine:caffeine:2.8.0
   │  ├─ com.google.errorprone:error_prone_annotations:2.3.3
   │  └─ org.checkerframework:checker-qual:2.10.0
   ├─ org.scala-lang:scala-library:2.12.8
   └─ org.scala-lang.modules:scala-java8-compat_2.12:0.9.0
      └─ org.scala-lang:scala-library:2.12.4 -> 2.12.8

This is such a quick way to be able to see the dependency tree of a dependency without adding it into your build or looking through build files.

install

Another useful command is the cs install command which allows you to install a launcher for a JVM-based application. The docs do a great job at explaining how this all works, but think of cs install as an apt install or brew install alternative for JVM-based apps. You can install, update, and uninstall apps that you want to use locally just like you would with <enter global package manager name here>. For my situation, I'm a big fan of the mdoc tool, but I normally use it when it's included in a project already. For this situation I just wanted to test something locally without including it in the project. I was reviewing an .adoc document and was curious if mdoc supported them the same way it does .md files. So I simply did the following:

❯ cs install mdoc
...
...
...
Wrote mdoc

This then allowed me full access to the mdoc cli commands, which I could use locally without having to include it in my project to test something out.

complete

Speaking of mdoc, I was using it in another project, and I was curious what the latest version was. I was actually using it in an Ammonite script (to create this website), so I didn't have access to the sbt dependencyUpdates command that I would typically use. I ended up looking on GitHub for the version, which lead me to the site, which lead me to the version. Ironically, I sent in a pr to add in a badge to the readme to make the latest version more discoverable when the author of mdoc, Ólafur Páll Geirsson, introduced me to this gem:

❯ cs complete org.scalameta:mdoc_2.13:
1.3.2
1.3.4
1.3.5
1.3.6
1.4.0-RC2
1.4.0-RC3
2.0.0
2.0.1
2.0.2
2.0.3
2.1.0
2.1.1
2.1.2
2.1.3
2.1.4
2.1.5

cs complete isn't actually on the Coursier website, but you can use it to give you all the options to complete the artifact that you are looking for. This provides such a simple way to explore what versions are cross published and available.

Tell your friends

I want re-iterate that I think there are a ton of great tools in the Scala ecosystem, but not everyone is aware of them. Hopefully, the few real-life examples above illustrate this in a small way. Next time someone mentions Coursier, ask them if they've used any of the cs commands, and show them a couple tips. Let's spread the word about the great tools we have access to.

Cheers.