mirror of https://github.com/AMT-Cheif/drift.git
Expose plugin debug server as cli command
This commit is contained in:
parent
49a1e8253e
commit
ae91b5d526
|
@ -66,13 +66,19 @@ used to construct common queries.
|
|||
## Workflows
|
||||
|
||||
### Debugging the analyzer plugin
|
||||
The analyzer plugin (branded as "SQL IDE" to users) can be run in isolation, which makes
|
||||
debugging easier. Note: Port 9999 has to be free for this to work, but you can change the
|
||||
|
||||
We have an analyzer plugin to support IDE features like auto-complete, navigation, syntax
|
||||
highlighting, outline and folding to users. Normally, analyzer plugins are discovered and
|
||||
loaded by the analysis server, which makes them very annoying to debug.
|
||||
|
||||
However, we found a way to run the plugin in isolation, which makes debugging much easier.
|
||||
Note: Port 9999 has to be free for this to work, but you can change the
|
||||
port defined in the two files below.
|
||||
|
||||
To debug the plugin, do the following:
|
||||
1. In `moor/tools/analyzer_plugin/bin/plugin.dart`, set `useDebuggingVariant` to true.
|
||||
2. Run `moor_generator/lib/plugin.dart` as a regular Dart VM app (this can be debugged).
|
||||
2. Run `moor_generator/bin/moor_generator.dart debug-plugin` as a regular Dart VM app
|
||||
(this can be debugged when started from an IDE).
|
||||
3. (optional) Make sure the analysis server picks up the updated version of the analysis
|
||||
plugin by deleting the `~/.dartServer/.plugin_manager` folder.
|
||||
4. Open a project that uses the plugin, for instance via `code extras/plugin_example`.
|
||||
|
|
|
@ -28,14 +28,15 @@ Note: If you only want to _use_ the plugin and don't care about debugging it, fo
|
|||
from the [user documentation](https://moor.simonbinder.eu/docs/using-sql/sql_ide/).
|
||||
|
||||
After you completed the setup, these steps will open an editor instance that runs the plugin.
|
||||
1. chdir into `moor_generator` and run `lib/plugin.dart`. You can run that file from an IDE if
|
||||
you need debugging capabilities, but starting it from the command line is fine. Keep that
|
||||
script running.
|
||||
2. Open this folder in the code instance
|
||||
3. Wait ~15s, you should start to see some log entries in the output of step 1.
|
||||
1. chdir into `moor_generator` and run `dart bin/moor_generator.dart debug-plugin`.
|
||||
You can run that script from an IDE if you need debugging capabilities, but starting
|
||||
it from the command line is fine. Keep that script running.
|
||||
3. Uncomment the `plugin` lines in `analysis_options.yaml`
|
||||
3. Open this folder in the code instance
|
||||
4. Wait ~15s, you should start to see some log entries in the output of step 1.
|
||||
As soon as they appear, the plugin is ready to go.
|
||||
|
||||
_Note_: `lib/plugin.dart` doesn't support multiple clients. Whenever you close or reload the
|
||||
_Note_: `bin/moor_generator.dart` doesn't support multiple clients. Whenever you close or reload the
|
||||
editor, that script needs to be restarted as well. That script should also be running before
|
||||
starting the analysis server.
|
||||
|
||||
|
@ -43,7 +44,7 @@ starting the analysis server.
|
|||
|
||||
If the plugin doesn't start properly, you can
|
||||
|
||||
1. make sure it was picked up by the analysis server: You set the `dart.analyzerDiagnosticsPort`
|
||||
1. make sure it was picked up by the analysis server: Set the `dart.analyzerDiagnosticsPort`
|
||||
to any port and see some basic information under the "plugins" tab of the website started.
|
||||
2. When setting `dart.analyzerInstrumentationLogFile`, the analysis server will write the
|
||||
exception that caused the plugin to stop
|
|
@ -1,92 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer_plugin/channel/channel.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol.dart';
|
||||
import 'package:analyzer_plugin/starter.dart';
|
||||
import 'package:moor_generator/src/backends/plugin/plugin.dart';
|
||||
|
||||
void start(List<String> args, SendPort sendPort) {
|
||||
ServerPluginStarter(MoorPlugin(PhysicalResourceProvider.INSTANCE))
|
||||
.start(sendPort);
|
||||
}
|
||||
|
||||
class WebSocketPluginServer implements PluginCommunicationChannel {
|
||||
final dynamic address;
|
||||
final int port;
|
||||
|
||||
HttpServer server;
|
||||
|
||||
WebSocket _currentClient;
|
||||
final StreamController<WebSocket> _clientStream =
|
||||
StreamController.broadcast();
|
||||
|
||||
WebSocketPluginServer({dynamic address, this.port = 9999})
|
||||
: address = address ?? InternetAddress.loopbackIPv4 {
|
||||
_init();
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
server = await HttpServer.bind(address, port);
|
||||
print('listening on $address at port $port');
|
||||
server.transform(WebSocketTransformer()).listen(_handleClientAdded);
|
||||
}
|
||||
|
||||
void _handleClientAdded(WebSocket socket) {
|
||||
if (_currentClient != null) {
|
||||
print('ignoring connection attempt because an active client already '
|
||||
'exists');
|
||||
socket.close();
|
||||
} else {
|
||||
print('client connected');
|
||||
_currentClient = socket;
|
||||
_clientStream.add(_currentClient);
|
||||
_currentClient.done.then((_) {
|
||||
print('client disconnected');
|
||||
_currentClient = null;
|
||||
_clientStream.add(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
server?.close(force: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void listen(void Function(Request request) onRequest,
|
||||
{Function onError, void Function() onDone}) {
|
||||
final stream = _clientStream.stream;
|
||||
|
||||
// wait until we're connected
|
||||
stream.firstWhere((socket) => socket != null).then((_) {
|
||||
_currentClient.listen((data) {
|
||||
print('I: $data');
|
||||
onRequest(Request.fromJson(
|
||||
json.decode(data as String) as Map<String, dynamic>));
|
||||
});
|
||||
});
|
||||
stream.firstWhere((socket) => socket == null).then((_) => onDone());
|
||||
}
|
||||
|
||||
@override
|
||||
void sendNotification(Notification notification) {
|
||||
print('N: ${notification.toJson()}');
|
||||
_currentClient?.add(json.encode(notification.toJson()));
|
||||
}
|
||||
|
||||
@override
|
||||
void sendResponse(Response response) {
|
||||
print('O: ${response.toJson()}');
|
||||
_currentClient?.add(json.encode(response.toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the plugin over a websocket service.
|
||||
void main() {
|
||||
MoorPlugin(PhysicalResourceProvider.INSTANCE).start(WebSocketPluginServer());
|
||||
ServerPluginStarter(MoorPlugin.forProduction()).start(sendPort);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:analyzer/file_system/file_system.dart';
|
||||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/context/builder.dart'; // ignore: implementation_imports
|
||||
import 'package:analyzer/src/context/context_root.dart'; // ignore: implementation_imports
|
||||
// ignore: implementation_imports
|
||||
|
@ -47,6 +48,10 @@ class MoorPlugin extends ServerPlugin
|
|||
dartScheduler.start();
|
||||
}
|
||||
|
||||
factory MoorPlugin.forProduction() {
|
||||
return MoorPlugin(PhysicalResourceProvider.INSTANCE);
|
||||
}
|
||||
|
||||
@override
|
||||
final List<String> fileGlobsToAnalyze = const ['*.moor'];
|
||||
@override
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:moor_generator/src/backends/standalone.dart';
|
||||
import 'package:moor_generator/src/cli/commands/debug_plugin.dart';
|
||||
|
||||
import 'commands/identify_databases.dart';
|
||||
|
||||
|
@ -27,7 +28,9 @@ class MoorCli {
|
|||
_runner = CommandRunner(
|
||||
'pub run moor_generator',
|
||||
'CLI utilities for the moor package, currently in an experimental state.',
|
||||
)..addCommand(IdentifyDatabases(this));
|
||||
)
|
||||
..addCommand(IdentifyDatabases(this))
|
||||
..addCommand(DebugPluginCommand(this));
|
||||
|
||||
_analyzerReadyCompleter.complete(_analyzer.init());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer_plugin/channel/channel.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol.dart';
|
||||
import 'package:moor_generator/src/backends/plugin/plugin.dart';
|
||||
|
||||
import '../cli.dart';
|
||||
|
||||
class DebugPluginCommand extends MoorCommand {
|
||||
DebugPluginCommand(MoorCli cli) : super(cli) {
|
||||
argParser.addOption(
|
||||
'port',
|
||||
abbr: 'p',
|
||||
help: 'The port to use when starting the websocket server',
|
||||
defaultsTo: '9999',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => 'debug-plugin';
|
||||
|
||||
@override
|
||||
String get description => 'Start the analyzer plugin on a websocket server';
|
||||
|
||||
@override
|
||||
bool get hidden => true;
|
||||
|
||||
@override
|
||||
void run() {
|
||||
final port = int.tryParse(argResults['port'] as String);
|
||||
if (port == null) {
|
||||
print('Port must be an int');
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
MoorPlugin.forProduction().start(_WebSocketPluginServer(port: port));
|
||||
}
|
||||
}
|
||||
|
||||
class _WebSocketPluginServer implements PluginCommunicationChannel {
|
||||
final dynamic address;
|
||||
final int port;
|
||||
|
||||
HttpServer server;
|
||||
|
||||
WebSocket _currentClient;
|
||||
final StreamController<WebSocket> _clientStream =
|
||||
StreamController.broadcast();
|
||||
|
||||
_WebSocketPluginServer({dynamic address, this.port = 9999})
|
||||
: address = address ?? InternetAddress.loopbackIPv4 {
|
||||
_init();
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
server = await HttpServer.bind(address, port);
|
||||
print('listening on $address at port $port');
|
||||
server.transform(WebSocketTransformer()).listen(_handleClientAdded);
|
||||
}
|
||||
|
||||
void _handleClientAdded(WebSocket socket) {
|
||||
if (_currentClient != null) {
|
||||
print('ignoring connection attempt because an active client already '
|
||||
'exists');
|
||||
socket.close();
|
||||
} else {
|
||||
print('client connected');
|
||||
_currentClient = socket;
|
||||
_clientStream.add(_currentClient);
|
||||
_currentClient.done.then((_) {
|
||||
print('client disconnected');
|
||||
_currentClient = null;
|
||||
_clientStream.add(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
server?.close(force: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void listen(void Function(Request request) onRequest,
|
||||
{Function onError, void Function() onDone}) {
|
||||
final stream = _clientStream.stream;
|
||||
|
||||
// wait until we're connected
|
||||
stream.firstWhere((socket) => socket != null).then((_) {
|
||||
_currentClient.listen((data) {
|
||||
print('I: $data');
|
||||
onRequest(Request.fromJson(
|
||||
json.decode(data as String) as Map<String, dynamic>));
|
||||
});
|
||||
});
|
||||
stream.firstWhere((socket) => socket == null).then((_) => onDone());
|
||||
}
|
||||
|
||||
@override
|
||||
void sendNotification(Notification notification) {
|
||||
print('N: ${notification.toJson()}');
|
||||
_currentClient?.add(json.encode(notification.toJson()));
|
||||
}
|
||||
|
||||
@override
|
||||
void sendResponse(Response response) {
|
||||
print('O: ${response.toJson()}');
|
||||
_currentClient?.add(json.encode(response.toJson()));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue