Skip to content

Commit afcba44

Browse files
committed
Added fortls autoupdate during initialisation
The server will ping PyPi and check if a version is available greater than the currently installed version. If an old version is detected fortls will download the new version to the pip localtion of the current Python distribution. This should allow for fortls to be correctly installed most times. Obviously if running from a directory structure that does not adhere to how pip/conda install packages this might not work.
1 parent 65bfb20 commit afcba44

File tree

4 files changed

+86
-1
lines changed

4 files changed

+86
-1
lines changed

fortls/interface.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ def commandline_args(name: str = "fortls") -> argparse.ArgumentParser:
7676
" as is)"
7777
),
7878
)
79+
parser.add_argument(
80+
"--disable_autoupdate",
81+
action="store_true",
82+
help=(
83+
"fortls automatically checks PyPi for newer version and installs them."
84+
"Use this option to disable the autoupdate feature."
85+
),
86+
)
7987
# XXX: Deprecated, argument not attached to anything. Remove
8088
parser.add_argument(
8189
"--preserve_keyword_order",

fortls/langserver.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
import logging
66
import os
77
import re
8+
import subprocess
9+
import sys
810
import traceback
11+
import urllib.request
912
from multiprocessing import Pool
1013
from pathlib import Path
1114
from typing import Pattern
15+
from urllib.error import URLError
16+
17+
from packaging import version
1218

1319
# Local modules
1420
from fortls.constants import (
@@ -208,6 +214,8 @@ def serve_initialize(self, request):
208214
self._config_logger(request)
209215
self._load_intrinsics()
210216
self._add_source_dirs()
217+
if self._update_version_pypi():
218+
log.log("Please restart the server for new version to activate")
211219

212220
# Initialize workspace
213221
self.workspace_init()
@@ -1486,6 +1494,9 @@ def _load_config_file_general(self, config_dict: dict) -> None:
14861494
)
14871495
self.sync_type: int = 2 if self.incremental_sync else 1
14881496
self.sort_keywords = config_dict.get("sort_keywords", self.sort_keywords)
1497+
self.disable_autoupdate = config_dict.get(
1498+
"disable_autoupdate", self.disable_autoupdate
1499+
)
14891500

14901501
# Autocomplete options -------------------------------------------------
14911502
self.autocomplete_no_prefix = config_dict.get(
@@ -1634,6 +1645,53 @@ def _create_ref_link(self, obj):
16341645
},
16351646
}
16361647

1648+
def _update_version_pypi(self, test: bool = False):
1649+
"""Fetch updates from PyPi for fortls
1650+
1651+
Parameters
1652+
----------
1653+
test : bool, optional
1654+
flag used to override exit checks, only for unittesting, by default False
1655+
"""
1656+
if self.disable_autoupdate:
1657+
return False
1658+
v = version.parse(__version__)
1659+
# Do not run for prerelease and dev release
1660+
if v.is_prerelease and not test:
1661+
return False
1662+
try:
1663+
with urllib.request.urlopen("https://pypi.org/pypi/fortls/json") as resp:
1664+
info = json.loads(resp.read().decode("utf-8"))
1665+
# This is the only reliable way to compare version semantics
1666+
if version.parse(info["info"]["version"]) > v or test:
1667+
self.post_message(
1668+
f"Using fortls {__version__}. A newer version of is"
1669+
" available through PyPi. An attempt will be made to update"
1670+
" the server",
1671+
3,
1672+
)
1673+
# Run pip
1674+
result = subprocess.run(
1675+
[
1676+
sys.executable,
1677+
"-m",
1678+
"pip",
1679+
"install",
1680+
"fortls",
1681+
"--upgrade",
1682+
],
1683+
capture_output=True,
1684+
)
1685+
if result.stdout:
1686+
log.info(result.stdout)
1687+
if result.stderr:
1688+
log.error(result.stderr)
1689+
return True
1690+
# No internet connection exceptions
1691+
except (URLError, KeyError):
1692+
log.warning("Failed to update the fortls Language Server")
1693+
return False
1694+
16371695

16381696
class JSONRPC2Error(Exception):
16391697
def __init__(self, code, message, data=None):

test/test_interface.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
def test_command_line_general_options():
1313
args = parser.parse_args(
1414
"-c config_file.json -n 2 --notify_init --incremental_sync --sort_keywords"
15-
" --debug_log".split()
15+
" --disable_autoupdate --debug_log".split()
1616
)
1717
assert args.config == "config_file.json"
1818
assert args.nthreads == 2
1919
assert args.notify_init
2020
assert args.incremental_sync
2121
assert args.sort_keywords
22+
assert args.disable_autoupdate
2223
assert args.debug_log
2324

2425

@@ -103,6 +104,7 @@ def test_config_file_general_options():
103104
assert server.notify_init
104105
assert server.incremental_sync
105106
assert server.sort_keywords
107+
assert server.disable_autoupdate
106108

107109

108110
def test_config_file_dir_parsing_options():
@@ -164,3 +166,19 @@ def test_config_file_codeactions_options():
164166
server, root = unittest_server_init()
165167
# Code Actions options
166168
assert server.enable_code_actions
169+
170+
171+
def test_version_update_pypi():
172+
from fortls.langserver import LangServer
173+
from fortls.jsonrpc import JSONRPC2Connection, ReadWriter
174+
175+
parser = commandline_args("fortls")
176+
args = parser.parse_args("-c f90_config.json".split())
177+
args = vars(args)
178+
args["disable_autoupdate"] = False
179+
180+
stdin, stdout = sys.stdin.buffer, sys.stdout.buffer
181+
s = LangServer(conn=JSONRPC2Connection(ReadWriter(stdin, stdout)), settings=args)
182+
s.root_path = (Path(__file__).parent / "test_source").resolve()
183+
did_update = s._update_version_pypi(test=True)
184+
assert did_update

test/test_source/f90_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"notify_init": true,
44
"incremental_sync": true,
55
"sort_keywords": true,
6+
"disable_autoupdate": true,
67

78
"source_dirs": ["subdir", "pp/**"],
89
"incl_suffixes": [".FF", ".fpc", ".h", "f20"],

0 commit comments

Comments
 (0)