Where parallels cross

Interesting bits of life

Programming in scala with a custom sbt with ensime

TL;DR

Having a project that needs a custom sbt executable requires a bit of setup to make Scala development more pleasing in these occasions.

Introduction

Imagine you work in Scala and you build your project with sbt. It may occur that your client has some special environment, and the simple way to hide the configuration complexity is to use a slightly configured sbt. Say that you come up with an executable called sbt.sh like the following:

#!/bin/bash

SBT_OPTS="someCoolOptions"
java $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "$@"

Note that the shebang is necessary, otherwise sbt-mode will not run your custom sbt.

Now: how do we make ensime consider this executable the one to use for this project?

The discoveries

We are lucky (and grateful) that the maintainers of ensime and sbt-mode provided two variables that allow to define the executable to use: respectively ensime-sbt-command and sbt:program-name.

The hack

The idea is to reassign those variables if a custom sbt executable exists in the project. For simplicity I assumed that the executable is not present in any parent directory of the project root directory. Given that the dir directory in the following function is the directory from where we start ensime, we can find the file through:

(defun get-to-parent-until (fn dir)
  "Returns the first directory from DIR that satisfies FN or NIL."
  (let ((current dir))
    (while (not (or (equalp current "/")
                    (funcall fn current)))
      (setq current (expand-file-name (concat current "/.."))))
    (if (funcall fn current)
        current
      nil)))

This function returns the first directory that satisfies the fn predicate.

With this we can obtain the executable path and assign the variables:

(defun set-sbt-program ()
  "Sets ensime ans sbt program name to local if it exists"
  (interactive)
  (let* ((sbt-dir (get-to-parent-until
                   #'(lambda (d)
                       (file-exists-p  (concat d "/sbt")))
                   default-directory))
         (exec (if sbt-dir (concat sbt-dir "/sbt") "sbt")))
    (setq ensime-sbt-command exec
          sbt:program-name exec)))

Finally we can set a hook to run this function every time we use ensime:

(add-hook 'ensime-mode-hook #'set-sbt-program)

Conclusion

I have shown how to automate some boring configuration for ensime. I added this to the sbt-mode repository for users reference: https://github.com/ensime/emacs-sbt-mode/issues/129.

Comments

comments powered by Disqus