This repository has been archived by the owner on May 4, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the Java DAP implementation (+LSP uprev) (#258)
This change bundles a pre-built binary of the Java DAP with microsoft/java-debug#379 and replit/java-debug@2a556e5 applied, so that we can specify what host/port it should bind to, in addition to always using localhost as the interface that it binds to. In order for this to function correctly, it also needs to be running a more recent version of the Java LSP, so we will upgrade that too.
- Loading branch information
Showing
9 changed files
with
166 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
#!/usr/bin/env python3 | ||
"""Small wrapper to correctly initialize the Java DAP. | ||
This launches the (normal) Java LSP and then tells it to initialize with the | ||
Java DAP plugin bundle. This causes the DAP plugin to bind to a TCP port, and | ||
once that's done, the communication with the DAP can start. | ||
This is known to be flaky, so this retries until it succeeds. | ||
""" | ||
|
||
import json | ||
import logging | ||
import os | ||
import signal | ||
import subprocess | ||
import sys | ||
import time | ||
|
||
from typing import Any, IO, Dict, List, Optional | ||
|
||
_JAVA_DAP_BUNDLE = '/run_dir/com.microsoft.java.debug.plugin-0.32.0.jar' | ||
|
||
|
||
def _send_lsp_message(msg: Dict[str, Any], lsp: IO[bytes]) -> None: | ||
"""Sends one LSP message.""" | ||
serialized_msg = json.dumps({ | ||
'jsonrpc': '2.0', | ||
**msg, | ||
}) | ||
payload = len(serialized_msg) | ||
lsp.write((f'Content-Length: {len(serialized_msg)}\r\n\r\n' + | ||
serialized_msg).encode('utf-8')) | ||
lsp.flush() | ||
|
||
|
||
def _receive_lsp_message(lsp: IO[bytes]) -> Optional[Dict[str, Any]]: | ||
"""Receives one LSP message.""" | ||
headers = b'' | ||
while not headers.endswith(b'\r\n\r\n'): | ||
byte = lsp.read(1) | ||
if len(byte) == 0: | ||
return None | ||
headers += byte | ||
content_length = 0 | ||
for header in headers.strip().split(b'\r\n'): | ||
name, value = header.split(b':', maxsplit=2) | ||
if name.strip().lower() == b'content-length': | ||
content_length = int(value.strip()) | ||
serialized = b'' | ||
while content_length: | ||
chunk = lsp.read(content_length) | ||
if not chunk: | ||
raise Exception(f'short read: {serialized!r}') | ||
content_length -= len(chunk) | ||
serialized += chunk | ||
return json.loads(serialized) | ||
|
||
|
||
def _run() -> bool: | ||
"""Attempts to start the DAP. Returns whether the caller should retry.""" | ||
with subprocess.Popen(['/usr/bin/run-language-server', '-l', 'java'], | ||
stdout=subprocess.PIPE, | ||
stdin=subprocess.PIPE, | ||
preexec_fn=os.setsid) as dap: | ||
try: | ||
_send_lsp_message( | ||
{ | ||
'id': 1, | ||
'method': 'initialize', | ||
'params': { | ||
'processId': None, | ||
'initializationOptions': { | ||
'bundles': [ | ||
_JAVA_DAP_BUNDLE, | ||
], | ||
}, | ||
'trace': 'verbose', | ||
'capabilities': {}, | ||
}, | ||
}, dap.stdin) | ||
# Wait for the initialize message has been acknowledged. | ||
# This maximizes the probability of success. | ||
while True: | ||
message = _receive_lsp_message(dap.stdout) | ||
if not message: | ||
return True | ||
if message.get('method') == 'window/logMessage': | ||
print(message.get('params', {}).get('message'), | ||
file=sys.stderr) | ||
if message.get('id') == 1: | ||
break | ||
_send_lsp_message( | ||
{ | ||
'id': 2, | ||
'method': 'workspace/executeCommand', | ||
'params': { | ||
'command': 'vscode.java.startDebugSession', | ||
}, | ||
}, dap.stdin) | ||
# Wait for the reply. If the request errored out, exit early to | ||
# send a clear signal to the caller. | ||
while True: | ||
message = _receive_lsp_message(dap.stdout) | ||
if not message: | ||
return True | ||
if message.get('method') == 'window/logMessage': | ||
print(message.get('params', {}).get('message'), | ||
file=sys.stderr) | ||
if message.get('id') == 2: | ||
if 'error' in message: | ||
print(message['error'].get('message'), file=sys.stderr) | ||
# This happens often during the first launch before | ||
# things warm up. | ||
return True | ||
break | ||
# Keep reading to drain the queue. | ||
while True: | ||
message = _receive_lsp_message(dap.stdout) | ||
if not message: | ||
break | ||
if message.get('method') == 'window/logMessage': | ||
print(message.get('params', {}).get('message'), | ||
file=sys.stderr) | ||
except Exception: | ||
logging.exception('failed') | ||
finally: | ||
pgrp = os.getpgid(dap.pid) | ||
os.killpg(pgrp, signal.SIGINT) | ||
return False | ||
|
||
|
||
def _main() -> None: | ||
while True: | ||
retry = _run() | ||
if not retry: | ||
break | ||
|
||
|
||
if __name__ == '__main__': | ||
_main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.