Skip to content

Commit

Permalink
refactor: move node runtime resolving to NodeRuntime (#57)
Browse files Browse the repository at this point in the history
Move node runtime resolving from ServerNpmResource to NodeRuntime
to be able to re-use NodeRuntime for other purposes in the future.
  • Loading branch information
rchl authored Apr 21, 2021
1 parent d8d20b8 commit d19e6f5
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 68 deletions.
2 changes: 2 additions & 0 deletions st3/lsp_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .activity_indicator import ActivityIndicator
from .api_wrapper_interface import ApiWrapperInterface
from .generic_client_handler import GenericClientHandler
from .node_runtime import NodeRuntime
from .npm_client_handler import NpmClientHandler
from .server_npm_resource import ServerNpmResource
from .server_pip_resource import ServerPipResource
Expand All @@ -15,6 +16,7 @@
'ApiWrapperInterface',
'ClientHandler',
'GenericClientHandler',
'NodeRuntime',
'NpmClientHandler',
'ServerResourceInterface',
'ServerStatus',
Expand Down
74 changes: 70 additions & 4 deletions st3/lsp_utils/node_runtime.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from .activity_indicator import ActivityIndicator
from .helpers import log_and_show_message
from .helpers import parse_version
from .helpers import run_command_sync
from .helpers import SemanticVersion
from .helpers import version_to_string
from contextlib import contextmanager
from LSP.plugin.core.typing import List, Optional, Tuple
from os import path
Expand All @@ -14,10 +16,74 @@

__all__ = ['NodeRuntime', 'NodeRuntimePATH', 'NodeRuntimeLocal']

NODE_VERSION = '12.20.2'
DEFAULT_NODE_VERSION = '12.20.2'
DEFAULT_NODE_VERSION_TUPLE = parse_version(DEFAULT_NODE_VERSION)
NO_NODE_FOUND_MESSAGE = 'Could not start {package_name} due to not being able to find Node.js \
runtime on the PATH. Press the "Install Node.js" button to install Node.js automatically \
(note that it will be installed locally for LSP and will not affect your system otherwise).'


class NodeRuntime:
_node_runtime_resolved = False
_node_runtime = None # Optional[NodeRuntime]
"""
Cached instance of resolved Node.js runtime. This is only done once per-session to avoid unnecessary IO.
"""

@classmethod
def get(
cls, package_name: str, storage_path: str, minimum_version: SemanticVersion = DEFAULT_NODE_VERSION_TUPLE
) -> Optional['NodeRuntime']:
if cls._node_runtime_resolved:
if cls._node_runtime:
cls._check_node_version(cls._node_runtime, minimum_version)
return cls._node_runtime
cls._node_runtime_resolved = True
cls._node_runtime = cls._resolve_node_runtime(package_name, storage_path, minimum_version)
return cls._node_runtime

@classmethod
def _resolve_node_runtime(
cls, package_name: str, storage_path: str, minimum_version: SemanticVersion
) -> Optional['NodeRuntime']:
selected_runtimes = sublime.load_settings('lsp_utils.sublime-settings').get('nodejs_runtime')
for runtime in selected_runtimes:
if runtime == 'system':
node_runtime = NodeRuntimePATH()
if node_runtime.node_exists():
try:
cls._check_node_version(node_runtime, minimum_version)
return node_runtime
except Exception as ex:
message = 'Ignoring system Node.js runtime due to an error. {}'.format(ex)
log_and_show_message('{}: Error: {}'.format(package_name, message))
elif runtime == 'local':
node_runtime = NodeRuntimeLocal(path.join(storage_path, 'lsp_utils', 'node-runtime'))
if not node_runtime.node_exists():
if not sublime.ok_cancel_dialog(NO_NODE_FOUND_MESSAGE.format(package_name=package_name),
'Install Node.js'):
return
try:
node_runtime.install_node()
except Exception as ex:
log_and_show_message('{}: Error: Failed installing a local Node.js runtime:\n{}'.format(
package_name, ex))
return
if node_runtime.node_exists():
try:
cls._check_node_version(node_runtime, minimum_version)
return node_runtime
except Exception as ex:
error = 'Ignoring local Node.js runtime due to an error. {}'.format(ex)
log_and_show_message('{}: Error: {}'.format(package_name, error))

@classmethod
def _check_node_version(cls, node_runtime: 'NodeRuntime', minimum_version: SemanticVersion) -> None:
node_version = node_runtime.resolve_version()
if node_version < minimum_version:
raise Exception('Node.js version requirement failed. Expected minimum: {}, got {}.'.format(
version_to_string(minimum_version), version_to_string(node_version)))

def __init__(self) -> None:
self._node = None # type: Optional[str]
self._npm = None # type: Optional[str]
Expand Down Expand Up @@ -73,7 +139,7 @@ def __init__(self) -> None:


class NodeRuntimeLocal(NodeRuntime):
def __init__(self, base_dir: str, node_version: str = NODE_VERSION):
def __init__(self, base_dir: str, node_version: str = DEFAULT_NODE_VERSION):
super().__init__()
self._base_dir = path.abspath(path.join(base_dir, node_version))
self._node_version = node_version
Expand Down Expand Up @@ -114,7 +180,7 @@ def install_node(self) -> None:
class InstallNode:
'''Command to install a local copy of Node.js'''

def __init__(self, base_dir: str, node_version: str = NODE_VERSION,
def __init__(self, base_dir: str, node_version: str = DEFAULT_NODE_VERSION,
node_dist_url='https://nodejs.org/dist/') -> None:
"""
:param base_dir: The base directory for storing given Node.js runtime version
Expand All @@ -127,7 +193,7 @@ def __init__(self, base_dir: str, node_version: str = NODE_VERSION,
self._node_dist_url = node_dist_url

def run(self) -> None:
print('Installing Node.js {}'.format(self._node_version))
print('[lsp_utils] Installing Node.js {}'.format(self._node_version))
archive, url = self._node_archive()
if not self._node_archive_exists(archive):
self._download_node(url, archive)
Expand Down
68 changes: 4 additions & 64 deletions st3/lsp_utils/server_npm_resource.py
Original file line number Diff line number Diff line change
@@ -1,93 +1,33 @@
from .helpers import log_and_show_message
from .helpers import SemanticVersion
from .helpers import version_to_string
from .node_runtime import NodeRuntime
from .node_runtime import NodeRuntimeLocal
from .node_runtime import NodeRuntimePATH
from .server_resource_interface import ServerResourceInterface
from .server_resource_interface import ServerStatus
from hashlib import md5
from LSP.plugin.core.typing import Dict, Optional
from os import path
from sublime_lib import ResourcePath
import shutil
import sublime

__all__ = ['ServerNpmResource']

NO_NODE_FOUND_MESSAGE = 'Could not start {package_name} due to not being able to find Node.js \
runtime on the PATH. Press the "Install Node.js" button to install Node.js automatically \
(note that it will be installed locally for LSP and will not affect your system otherwise).'


class ServerNpmResource(ServerResourceInterface):
"""
An implementation of :class:`lsp_utils.ServerResourceInterface` implementing server management for
node-based severs. Handles installation and updates of the server in package storage.
"""

_node_runtime_resolved = False
_node_runtime = None # Optional[NodeRuntime]
"""
Cached instance of resolved Node.js runtime. This is only done once per-session to avoid unnecessary IO.
"""

@classmethod
def create(cls, options: Dict) -> Optional['ServerNpmResource']:
package_name = options['package_name']
server_directory = options['server_directory']
server_binary_path = options['server_binary_path']
package_storage = options['package_storage']
minimum_node_version = options['minimum_node_version']
storage_path = options['storage_path']
if not cls._node_runtime_resolved:
cls._node_runtime = cls.resolve_node_runtime(package_name, minimum_node_version, storage_path)
cls._node_runtime_resolved = True
if cls._node_runtime:
return ServerNpmResource(
package_name, server_directory, server_binary_path, package_storage, cls._node_runtime)

@classmethod
def resolve_node_runtime(
cls, package_name: str, minimum_node_version: SemanticVersion, storage_path: str
) -> Optional[NodeRuntime]:
selected_runtimes = sublime.load_settings('lsp_utils.sublime-settings').get('nodejs_runtime')
for runtime in selected_runtimes:
if runtime == 'system':
node_runtime = NodeRuntimePATH()
if node_runtime.node_exists():
try:
cls.check_node_version(node_runtime, minimum_node_version)
return node_runtime
except Exception as ex:
message = 'Ignoring system Node.js runtime due to an error. {}'.format(ex)
log_and_show_message('{}: Error: {}'.format(package_name, message))
elif runtime == 'local':
node_runtime = NodeRuntimeLocal(path.join(storage_path, 'lsp_utils', 'node-runtime'))
if not node_runtime.node_exists():
if not sublime.ok_cancel_dialog(NO_NODE_FOUND_MESSAGE.format(package_name=package_name),
'Install Node.js'):
return
try:
node_runtime.install_node()
except Exception as ex:
log_and_show_message('{}: Error: Failed installing a local Node.js runtime:\n{}'.format(
package_name, ex))
return
if node_runtime.node_exists():
try:
cls.check_node_version(node_runtime, minimum_node_version)
return node_runtime
except Exception as ex:
error = 'Ignoring local Node.js runtime due to an error. {}'.format(ex)
log_and_show_message('{}: Error: {}'.format(package_name, error))

@classmethod
def check_node_version(cls, node_runtime: NodeRuntime, minimum_node_version: SemanticVersion) -> None:
node_version = node_runtime.resolve_version()
if node_version < minimum_node_version:
raise Exception('Node.js version requirement failed. Expected minimum: {}, got {}.'.format(
version_to_string(minimum_node_version), version_to_string(node_version)))
minimum_node_version = options['minimum_node_version']
node_runtime = NodeRuntime.get(package_name, storage_path, minimum_node_version,)
if node_runtime:
return ServerNpmResource(package_name, server_directory, server_binary_path, package_storage, node_runtime)

def __init__(self, package_name: str, server_directory: str, server_binary_path: str,
package_storage: str, node_runtime: NodeRuntime) -> None:
Expand Down

0 comments on commit d19e6f5

Please sign in to comment.