From f271c2c7682fa1687b41fcd96560d64a88598133 Mon Sep 17 00:00:00 2001 From: Ches Martin Date: Sun, 4 Dec 2016 02:47:35 +0700 Subject: [PATCH 1/3] Pass a ProjectConfig to launcher, not a raw path Types > strings. Minor aesthetic renaming, bump bootstrap sbt-coursier. --- ensime_shared/ensime.py | 3 ++- ensime_shared/launcher.py | 38 +++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/ensime_shared/ensime.py b/ensime_shared/ensime.py index 40ded98..6ad8a5b 100644 --- a/ensime_shared/ensime.py +++ b/ensime_shared/ensime.py @@ -111,7 +111,8 @@ def create_client(self, config_path): """ server_v2 = self.using_server_v2() editor = Editor(self._vim) - launcher = EnsimeLauncher(self._vim, config_path, server_v2) + config = ProjectConfig(config_path) + launcher = EnsimeLauncher(self._vim, config, server_v2) if server_v2: return EnsimeClientV2(editor, self._vim, launcher) else: diff --git a/ensime_shared/launcher.py b/ensime_shared/launcher.py index 0193e2e..03582ef 100644 --- a/ensime_shared/launcher.py +++ b/ensime_shared/launcher.py @@ -11,7 +11,7 @@ from string import Template -from ensime_shared.config import BOOTSTRAPS_ROOT, ProjectConfig +from ensime_shared.config import BOOTSTRAPS_ROOT from ensime_shared.errors import InvalidJavaPathError from ensime_shared.util import catch, Util @@ -57,14 +57,14 @@ def http_port(self): class EnsimeLauncher(object): ENSIME_V1 = '1.0.0' ENSIME_V2 = '2.0.0-SNAPSHOT' - SBT_VERSION = '0.13.12' + SBT_VERSION = '0.13.13' + SBT_COURSIER_COORDS = ('io.get-coursier', 'sbt-coursier', '1.0.0-M15') - def __init__(self, vim, config_path, server_v2, base_dir=BOOTSTRAPS_ROOT): + def __init__(self, vim, config, server_v2, base_dir=BOOTSTRAPS_ROOT): self.vim = vim + self.config = config self.server_v2 = server_v2 self.ensime_version = self.ENSIME_V2 if server_v2 else self.ENSIME_V1 - self._config_path = os.path.abspath(config_path) - self.config = ProjectConfig(self._config_path) self.scala_minor = self.config['scala-version'][:4] self.base_dir = os.path.abspath(base_dir) self.classpath_file = os.path.join(self.base_dir, @@ -83,8 +83,8 @@ def launch(self): return self.start_process(classpath) if classpath else None def load_classpath(self): - if not os.path.exists(self.classpath_file): - if not self.generate_classpath(): + if not self.isinstalled(): + if not self.install(): # This should probably be an exception? return None classpath = "{}:{}/lib/tools.jar".format( @@ -115,8 +115,8 @@ def start_process(self, classpath): args = ( [java, "-cp", classpath] + - [a for a in java_flags if a != ""] + - ["-Densime.config={}".format(self._config_path), + [a for a in java_flags if a] + + ["-Densime.config={}".format(self.config.filepath), "org.ensime.server.Server"]) process = subprocess.Popen( args, @@ -129,13 +129,21 @@ def start_process(self, classpath): def on_stop(): log.close() null.close() - with catch(Exception, lambda e: None): + with catch(Exception): os.remove(pid_path) return EnsimeProcess(cache_dir, process, log_path, on_stop) - def generate_classpath(self): + # TODO: should maybe check if the build.sbt matches spec (versions, etc.) + def isinstalled(self): + """Returns whether ENSIME server for this launcher is installed.""" + return os.path.exists(self.classpath_file) + + def install(self): + """Installs ENSIME server with a bootstrap sbt project and generates its classpath.""" project_dir = os.path.dirname(self.classpath_file) + sbt_plugin = """addSbtPlugin("{0}" % "{1}" % "{2}")""" + Util.mkdir_p(project_dir) Util.mkdir_p(os.path.join(project_dir, "project")) Util.write_file( @@ -146,14 +154,14 @@ def generate_classpath(self): "sbt.version={}".format(self.SBT_VERSION)) Util.write_file( os.path.join(project_dir, "project", "plugins.sbt"), - """addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M11")""") + sbt_plugin.format(*self.SBT_COURSIER_COORDS)) # Synchronous update of the classpath via sbt # see https://github.com/ensime/ensime-vim/issues/29 cd_cmd = "cd {}".format(project_dir) sbt_cmd = "sbt -Dsbt.log.noformat=true -batch saveClasspath" - inside_nvim = int(self.vim.eval("has('nvim')")) - if inside_nvim: + + if int(self.vim.eval("has('nvim')")): import tempfile import re tmp_dir = tempfile.gettempdir() @@ -234,7 +242,7 @@ def reorder_classpath(self, classpath_file): """Reorder classpath and put monkeys-jar in the first place.""" success = False - with catch((IOError, OSError), lambda e: None): + with catch((IOError, OSError)): with open(classpath_file, "r") as f: classpath = f.readline() From 272ba494d172f830aab9448d8d650b67f7d2896a Mon Sep 17 00:00:00 2001 From: Ches Martin Date: Fri, 9 Dec 2016 14:08:53 +0800 Subject: [PATCH 2/3] Support launching ENSIME server preinstalled by build tool ensime-sbt now installs the server and puts all the relevant jar paths for launching into .ensime, so we don't have to be bothered with doing the installation. This adds support for launching this way, keeping the bootstrap server installation around for an upgrade period. Closes #353. --- ensime_shared/client.py | 10 +- ensime_shared/config.py | 2 +- ensime_shared/ensime.py | 11 +- ensime_shared/errors.py | 4 + ensime_shared/launcher.py | 222 ++++++++++++++++++++++----- test/resources/test-bootstrap.conf | 7 + test/resources/test-server-jars.conf | 7 + test/test_launcher.py | 127 +++++++++++++++ 8 files changed, 339 insertions(+), 51 deletions(-) create mode 100644 test/resources/test-bootstrap.conf create mode 100644 test/resources/test-server-jars.conf create mode 100644 test/test_launcher.py diff --git a/ensime_shared/client.py b/ensime_shared/client.py index 857851b..0fe7657 100644 --- a/ensime_shared/client.py +++ b/ensime_shared/client.py @@ -193,8 +193,9 @@ def lazy_initialize_ensime(): self.log.debug(str(inspect.stack())) self.log.debug('setup(quiet=%s, bootstrap_server=%s) called by %s()', quiet, bootstrap_server, called_by) - no_classpath = not os.path.exists(self.launcher.classpath_file) - if not bootstrap_server and no_classpath: + + installed = self.launcher.strategy.isinstalled() + if not installed and not bootstrap_server: if not quiet: scala = self.launcher.config.get('scala-version') msg = feedback["prompt_server_install"].format(scala_version=scala) @@ -261,6 +262,7 @@ def reconnect(e): def connect_ensime_server(self): """Start initial connection with the server.""" self.log.debug('connect_ensime_server: in') + server_v2 = isinstance(self, EnsimeClientV2) def disable_completely(e): if e: @@ -272,12 +274,12 @@ def disable_completely(e): self.number_try_connection -= 1 if not self.ensime_server: port = self.ensime.http_port() - uri = "websocket" if self.launcher.server_v2 else "jerky" + uri = "websocket" if server_v2 else "jerky" self.ensime_server = gconfig["ensime_server"].format(port, uri) with catch(Exception, disable_completely): from websocket import create_connection # Use the default timeout (no timeout). - options = {"subprotocols": ["jerky"]} if self.launcher.server_v2 else {} + options = {"subprotocols": ["jerky"]} if server_v2 else {} self.log.debug("About to connect to %s with options %s", self.ensime_server, options) self.ws = create_connection(self.ensime_server, **options) diff --git a/ensime_shared/config.py b/ensime_shared/config.py index 43e18fb..ca7a41e 100644 --- a/ensime_shared/config.py +++ b/ensime_shared/config.py @@ -7,7 +7,7 @@ from ensime_shared.util import Util -BOOTSTRAPS_ROOT = os.path.join(os.environ['HOME'], '.config/ensime-vim/') +BOOTSTRAPS_ROOT = os.path.join(os.environ['HOME'], '.config', 'ensime-vim') """Default directory where ENSIME server bootstrap projects will be created.""" LOG_FORMAT = '%(levelname)-8s <%(asctime)s> (%(filename)s:%(lineno)d) - %(message)s' diff --git a/ensime_shared/ensime.py b/ensime_shared/ensime.py index 6ad8a5b..3410f19 100644 --- a/ensime_shared/ensime.py +++ b/ensime_shared/ensime.py @@ -50,8 +50,9 @@ def __init__(self, vim): self._vim = vim self.clients = {} + @property def using_server_v2(self): - """Whether user has configured the plugin to use ENSIME v2 protocol.""" + """bool: Whether user has configured the plugin to use ENSIME v2 protocol.""" return bool(self.get_setting('server_v2', 0)) def get_setting(self, key, default): @@ -109,11 +110,11 @@ def create_client(self, config_path): This will launch the ENSIME server for the project as a side effect. """ - server_v2 = self.using_server_v2() - editor = Editor(self._vim) config = ProjectConfig(config_path) - launcher = EnsimeLauncher(self._vim, config, server_v2) - if server_v2: + editor = Editor(self._vim) + launcher = EnsimeLauncher(self._vim, config) + + if self.using_server_v2: return EnsimeClientV2(editor, self._vim, launcher) else: return EnsimeClientV1(editor, self._vim, launcher) diff --git a/ensime_shared/errors.py b/ensime_shared/errors.py index 7d5823c..eb74222 100644 --- a/ensime_shared/errors.py +++ b/ensime_shared/errors.py @@ -10,6 +10,10 @@ def __init__(self, errno, msg, filename, *args): super(InvalidJavaPathError, self).__init__(errno, msg, filename, *args) +class LaunchError(RuntimeError): + """Raised when ensime-vim cannot launch the ENSIME server.""" + + class Error(object): """Represents an error in source code reported by ENSIME.""" diff --git a/ensime_shared/launcher.py b/ensime_shared/launcher.py index 03582ef..009c9a2 100644 --- a/ensime_shared/launcher.py +++ b/ensime_shared/launcher.py @@ -1,7 +1,6 @@ # coding: utf-8 import errno -import fnmatch import os import shutil import signal @@ -9,10 +8,12 @@ import subprocess import time +from abc import ABCMeta, abstractmethod +from fnmatch import fnmatch from string import Template from ensime_shared.config import BOOTSTRAPS_ROOT -from ensime_shared.errors import InvalidJavaPathError +from ensime_shared.errors import InvalidJavaPathError, LaunchError from ensime_shared.util import catch, Util @@ -36,6 +37,7 @@ def aborted(self): return not (self.__stopped_manually or self.is_running()) def is_running(self): + # What? If there's no process, it's running? This is mad confusing. return self.process is None or self.process.poll() is None def is_ready(self): @@ -55,50 +57,103 @@ def http_port(self): class EnsimeLauncher(object): - ENSIME_V1 = '1.0.0' - ENSIME_V2 = '2.0.0-SNAPSHOT' - SBT_VERSION = '0.13.13' - SBT_COURSIER_COORDS = ('io.get-coursier', 'sbt-coursier', '1.0.0-M15') + """Launches ENSIME processes, installing the server if needed.""" - def __init__(self, vim, config, server_v2, base_dir=BOOTSTRAPS_ROOT): - self.vim = vim + def __init__(self, vim, config, base_dir=BOOTSTRAPS_ROOT): self.config = config - self.server_v2 = server_v2 - self.ensime_version = self.ENSIME_V2 if server_v2 else self.ENSIME_V1 - self.scala_minor = self.config['scala-version'][:4] - self.base_dir = os.path.abspath(base_dir) - self.classpath_file = os.path.join(self.base_dir, - self.scala_minor, - self.ensime_version, - 'classpath') - self._migrate_legacy_bootstrap_location() + # If an ENSIME assembly jar is in place, it takes launch precedence + assembly = AssemblyJar(config, base_dir) + + if assembly.isinstalled(): + self.strategy = assembly + elif self.config.get('ensime-server-jars'): + self.strategy = DotEnsimeLauncher(config) + else: + self.strategy = SbtBootstrap(vim, config, base_dir) + + self._remove_legacy_bootstrap() + + # Design musing: we could return a Boolean success value then encapsulate + # and expose more lifecycle control through EnsimeLauncher, instead of + # pushing up an EnsimeProcess and leaving callers with the responsibilities + # of dealing with that. EnsimeClient needs a bunch of (worthwhile) refactoring + # before this could happen, though. def launch(self): + # This is legacy -- what is it really accomplishing? cache_dir = self.config['cache-dir'], process = EnsimeProcess(cache_dir, None, None, lambda: None) if process.is_ready(): return process - classpath = self.load_classpath() - return self.start_process(classpath) if classpath else None - - def load_classpath(self): - if not self.isinstalled(): - if not self.install(): # This should probably be an exception? + if not self.strategy.isinstalled(): + if not self.strategy.install(): # TODO: This should be an exception return None - classpath = "{}:{}/lib/tools.jar".format( - Util.read_file(self.classpath_file), self.config['java-home']) + return self.strategy.launch() + + @staticmethod + def _remove_legacy_bootstrap(): + """Remove bootstrap projects from old path, they'd be really stale by now.""" + home = os.environ['HOME'] + old_base_dir = os.path.join(home, '.config', 'classpath_project_ensime') + if os.path.isdir(old_base_dir): + shutil.rmtree(old_base_dir, ignore_errors=True) + + +class LaunchStrategy: + """A strategy for how to install and launch the ENSIME server. - # Allow override with a local development server jar, see: - # http://ensime.github.io/contributing/#manual-qa-testing - for x in os.listdir(self.base_dir): - if fnmatch.fnmatch(x, "ensime_" + self.scala_minor + "*-assembly.jar"): - classpath = os.path.join(self.base_dir, x) + ":" + classpath + Newer build tool versions like sbt-ensime since 1.12.0 may support + installing the server and publishing the jar locations in ``.ensime`` + so that clients don't need to handle installation. Strategies exist to + support older versions and build tools that haven't caught up to this. - return classpath + Args: + config (ProjectConfig): Configuration for the server instance's project. + """ + __metaclass__ = ABCMeta - def start_process(self, classpath): + def __init__(self, config): + self.config = config + + @abstractmethod + def isinstalled(self): + """Whether ENSIME has been installed satisfactorily for the launcher.""" + raise NotImplementedError + + @abstractmethod + def install(self): + """Installs ENSIME server if needed. + + Returns: + bool: Whether the installation completed successfully. + """ + raise NotImplementedError + + @abstractmethod + def launch(self): + """Launches a server instance for the configured project. + + Returns: + EnsimeProcess: A process handle for the launched server. + + Raises: + LaunchError: If server can't be launched according to the strategy. + """ + raise NotImplementedError + + def _start_process(self, classpath): + """Given a classpath prepared for running ENSIME, spawns a server process + in a way that is otherwise agnostic to how the strategy installs ENSIME. + + Args: + classpath (str): Colon-separated classpath string suitable for passing + as an argument to ``java -cp``. + + Returns: + EnsimeProcess: A process handle for the launched server. + """ cache_dir = self.config['cache-dir'] java_flags = self.config['java-flags'] @@ -134,9 +189,102 @@ def on_stop(): return EnsimeProcess(cache_dir, process, log_path, on_stop) + +class AssemblyJar(LaunchStrategy): + """Launches an ENSIME assembly jar if found in ``~/.config/ensime-vim`` (or + base_dir). This is intended for ad hoc local development builds, or behind- + the-firewall corporate installs. See: + + http://ensime.github.io/contributing/#manual-qa-testing + """ + + def __init__(self, config, base_dir): + super(AssemblyJar, self).__init__(config) + self.base_dir = os.path.realpath(base_dir) + self.jar_path = None + self.toolsjar = os.path.join(config['java-home'], 'lib', 'tools.jar') + + def isinstalled(self): + scala_minor = self.config['scala-version'][:4] + for fname in os.listdir(self.base_dir): + if fnmatch(fname, "ensime_" + scala_minor + "*-assembly.jar"): + self.jar_path = os.path.join(self.base_dir, fname) + return True + + return False + + def install(self): + # Nothing to do for this strategy, server is built in the jar + return True + + def launch(self): + if not self.isinstalled(): + raise LaunchError('ENSIME assembly jar not found in {}'.format(self.base_dir)) + + classpath = [self.jar_path, self.toolsjar] + self.config['scala-compiler-jars'] + return self._start_process(':'.join(classpath)) + + +class DotEnsimeLauncher(LaunchStrategy): + """Launches a pre-installed ENSIME via jar paths in ``.ensime``.""" + + def __init__(self, config): + super(DotEnsimeLauncher, self).__init__(config) + server_jars = self.config['ensime-server-jars'] + compiler_jars = self.config['scala-compiler-jars'] + + # Order is important so that monkeys takes precedence + self.classpath = server_jars + compiler_jars + + def isinstalled(self): + return all([os.path.exists(jar) for jar in self.classpath]) + + def install(self): + # Nothing to do, the build tool has done it if we're in this strategy + return True + + def launch(self): + if not self.isinstalled(): + raise LaunchError('Some jars reported by .ensime do not exist: {}' + .format(self.classpath)) + return self._start_process(':'.join(self.classpath)) + + +class SbtBootstrap(LaunchStrategy): + """Install ENSIME via sbt with a bootstrap project. + + This strategy is intended for versions of sbt-ensime prior to 1.12.0 + and other build tools that don't install ENSIME & report its jar paths. + + Support for this installation method will be dropped after users and build + tools have some time to catch up. Consider it deprecated. + """ + ENSIME_V1 = '1.0.0' + SBT_VERSION = '0.13.13' + SBT_COURSIER_COORDS = ('io.get-coursier', 'sbt-coursier', '1.0.0-M15') + + def __init__(self, vim, config, base_dir): + super(SbtBootstrap, self).__init__(config) + self.vim = vim + self.ensime_version = self.ENSIME_V1 + self.scala_minor = self.config['scala-version'][:4] + self.base_dir = os.path.realpath(base_dir) + self.toolsjar = os.path.join(self.config['java-home'], 'lib', 'tools.jar') + self.classpath_file = os.path.join(self.base_dir, + self.scala_minor, + self.ensime_version, + 'classpath') + + def launch(self): + if not self.isinstalled(): + raise LaunchError('Bootstrap classpath file does not exist at {}' + .format(self.classpath_file)) + + classpath = Util.read_file(self.classpath_file) + ':' + self.toolsjar + return self._start_process(classpath) + # TODO: should maybe check if the build.sbt matches spec (versions, etc.) def isinstalled(self): - """Returns whether ENSIME server for this launcher is installed.""" return os.path.exists(self.classpath_file) def install(self): @@ -263,11 +411,3 @@ def reorder_classpath(self, classpath_file): success = True return success - - @staticmethod - def _migrate_legacy_bootstrap_location(): - """Moves an old ENSIME installer root to tidier location.""" - home = os.environ['HOME'] - old_base_dir = os.path.join(home, '.config/classpath_project_ensime') - if os.path.isdir(old_base_dir): - shutil.move(old_base_dir, BOOTSTRAPS_ROOT) diff --git a/test/resources/test-bootstrap.conf b/test/resources/test-bootstrap.conf new file mode 100644 index 0000000..1f9d2be --- /dev/null +++ b/test/resources/test-bootstrap.conf @@ -0,0 +1,7 @@ +( + :name "testing" + :scala-version "2.11.8" + + :java-home "/fake/opt/java" + :scala-compiler-jars ("/fake/cache/scala-compiler-2.11.8.jar" "/fake/cache/scala-library-2.11.8.jar") +) diff --git a/test/resources/test-server-jars.conf b/test/resources/test-server-jars.conf new file mode 100644 index 0000000..789a249 --- /dev/null +++ b/test/resources/test-server-jars.conf @@ -0,0 +1,7 @@ +( + :name "testing" + :scala-version "2.11.8" + :java-home "/fake/opt/java" + :scala-compiler-jars ("/fake/cache/scala-compiler-2.11.8.jar" "/fake/cache/scala-library-2.11.8.jar") + :ensime-server-jars ("/fake/cache/monkeys_2.11-1.0.0.jar" "/fake/cache/server_2.11-1.0.0.jar") +) diff --git a/test/test_launcher.py b/test/test_launcher.py new file mode 100644 index 0000000..74fdb0e --- /dev/null +++ b/test/test_launcher.py @@ -0,0 +1,127 @@ +# coding: utf-8 + +import pytest +from mock import patch +from py import path + +from ensime_shared.config import ProjectConfig +from ensime_shared.errors import LaunchError +from ensime_shared.launcher import (AssemblyJar, DotEnsimeLauncher, + EnsimeLauncher, SbtBootstrap) + +CONFROOT = path.local(__file__).dirpath() / 'resources' + + +def test_determines_launch_strategy(tmpdir, vim): + base_dir = tmpdir.strpath + bootstrap_conf = config('test-bootstrap.conf') + + launcher = EnsimeLauncher(vim, config('test-server-jars.conf'), base_dir) + assert isinstance(launcher.strategy, DotEnsimeLauncher) + + launcher = EnsimeLauncher(vim, bootstrap_conf, base_dir) + assert isinstance(launcher.strategy, SbtBootstrap) + + create_stub_assembly_jar(base_dir, bootstrap_conf) + launcher = EnsimeLauncher(vim, bootstrap_conf, base_dir) + assert isinstance(launcher.strategy, AssemblyJar) + + +class TestAssemblyJarStrategy: + @pytest.fixture + def strategy(self, tmpdir): + return AssemblyJar(config('test-bootstrap.conf'), base_dir=tmpdir.strpath) + + @pytest.fixture + def assemblyjar(self, strategy): + create_stub_assembly_jar(strategy.base_dir, strategy.config) + + def test_isinstalled_if_jar_file_present(self, strategy): + assert not strategy.isinstalled() + self.assemblyjar(strategy) + assert strategy.isinstalled() + + def test_launch_constructs_classpath(self, strategy, assemblyjar): + assert strategy.isinstalled() + with patch.object(strategy, '_start_process', autospec=True) as start: + strategy.launch() + + assert start.call_count == 1 + args, _kwargs = start.call_args + classpath = args[0].split(':') + assert classpath == [strategy.jar_path, + strategy.toolsjar, + ] + strategy.config['scala-compiler-jars'] + + def test_launch_raises_when_not_installed(self, strategy): + assert not strategy.isinstalled() + with pytest.raises(LaunchError) as excinfo: + strategy.launch() + assert 'assembly jar not found' in str(excinfo.value) + + +class TestDotEnsimeStrategy: + @pytest.fixture + def strategy(self): + return DotEnsimeLauncher(config('test-server-jars.conf')) + + def test_adds_server_jars_to_classpath(self, strategy): + server_jars = strategy.config['ensime-server-jars'] + assert all([jar in strategy.classpath for jar in server_jars]) + + def test_isinstalled_if_jars_present(self, strategy): + assert not strategy.isinstalled() + # Stub the existence of the server+compiler jars + with patch('os.path.exists', return_value=True): + assert strategy.isinstalled() + + def test_launch_constructs_classpath(self, strategy): + with patch.object(strategy, '_start_process', autospec=True) as start: + with patch.object(strategy, 'isinstalled', return_value=True): + strategy.launch() + + assert start.call_count == 1 + args, _kwargs = start.call_args + classpath = args[0].split(':') + assert classpath == strategy.classpath + + def test_launch_raises_when_not_installed(self, strategy): + assert not strategy.isinstalled() + with pytest.raises(LaunchError) as excinfo: + strategy.launch() + assert 'Some jars reported by .ensime do not exist' in str(excinfo.value) + + +class TestSbtBootstrapStrategy: + """ + Minimally tested because unit testing this would be obnoxious and brittle... + """ + + @pytest.fixture + def strategy(self, tmpdir, vim): + conf = config('test-bootstrap.conf') + return SbtBootstrap(vim, conf, base_dir=tmpdir.strpath) + + def test_isinstalled_if_classpath_file_present(self, strategy): + assert not strategy.isinstalled() + + def test_launch_raises_when_not_installed(self, strategy): + assert not strategy.isinstalled() + with pytest.raises(LaunchError) as excinfo: + strategy.launch() + assert 'Bootstrap classpath file does not exist' in str(excinfo.value) + + +# ----------------------------------------------------------------------- +# - Helpers - +# ----------------------------------------------------------------------- + +def config(conffile): + return ProjectConfig(CONFROOT.join(conffile).strpath) + + +def create_stub_assembly_jar(indir, projectconfig): + """Touches assembly jar file path in indir and returns the path.""" + scala_minor = projectconfig['scala-version'][:4] + name = 'ensime_{}-assembly.jar'.format(scala_minor) + return path.local(indir).ensure(name).realpath From 50036bbcd168e3f2411150db77f5f6fe8cdc787b Mon Sep 17 00:00:00 2001 From: Ches Martin Date: Tue, 3 Jan 2017 01:03:10 +0700 Subject: [PATCH 3/3] Fix a lint error that breaks the build CI infrastructure has been under maintenance so #359 didn't get checked. --- ensime_shared/typecheck.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ensime_shared/typecheck.py b/ensime_shared/typecheck.py index f17997d..a33f0a7 100644 --- a/ensime_shared/typecheck.py +++ b/ensime_shared/typecheck.py @@ -16,7 +16,9 @@ def buffer_typechecks(self, call_id, payload): def buffer_typechecks_and_display(self, call_id, payload): """Adds typecheck events to the buffer, and displays them right away. - This is currently used as a workaround for issue https://github.com/ensime/ensime-server/issues/1616 + + This is a workaround for this issue: + https://github.com/ensime/ensime-server/issues/1616 """ self.buffer_typechecks(call_id, payload) self.editor.display_notes(self.buffered_notes)