Skip to content

Commit

Permalink
Connect up augmentations from macro output through to host. (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorgan authored Aug 7, 2024
1 parent b3f4c73 commit 693f52e
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 115 deletions.
194 changes: 97 additions & 97 deletions .github/workflows/dart.yml

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion pkgs/_macro_client/lib/macro_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import 'package:macro_service/macro_service.dart';
/// TODO(davidmorgan): split to multpile implementations depending on
/// transport used to connect to host.
class MacroClient {
MacroClient._(Iterable<Macro> macros, Socket socket) {
final RemoteMacroHost host = RemoteMacroHost();
final Iterable<Macro> macros;
final Socket socket;

MacroClient._(this.macros, this.socket) {
// TODO(davidmorgan): negotiation about protocol version goes here.

// Tell the host which macros are in this bundle.
Expand All @@ -24,6 +28,11 @@ class MacroClient {
// switch to binary.
socket.writeln(json.encode(request.node));
}

const Utf8Decoder()
.bind(socket)
.transform(const LineSplitter())
.listen(_handleRequest);
}

/// Runs [macros] for the host at [endpoint].
Expand All @@ -32,4 +41,22 @@ class MacroClient {
final socket = await Socket.connect('localhost', endpoint.port);
return MacroClient._(macros, socket);
}

void _handleRequest(String request) async {
// TODO(davidmorgan): support more than one request type.
final augmentRequest =
AugmentRequest.fromJson(json.decode(request) as Map<String, Object?>);
// TODO(davidmorgan): support multiple macros.
final response = await macros.single.augment(host, augmentRequest);
_send(response.node);
}

void _send(Map<String, Object?> node) {
// TODO(davidmorgan): currently this is JSON with one request per line,
// switch to binary.
socket.writeln(json.encode(node));
}
}

/// [Host] that is connected to a remote macro host.
class RemoteMacroHost implements Host {}
1 change: 1 addition & 0 deletions pkgs/_macro_client/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ dependencies:

dev_dependencies:
_test_macros: any
async: ^2.11.0
dart_flutter_team_lints: ^3.0.0
test: ^1.25.0
21 changes: 21 additions & 0 deletions pkgs/_macro_client/test/macro_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:_macro_client/macro_client.dart';
import 'package:_test_macros/declare_x_macro.dart';
import 'package:async/async.dart';
import 'package:macro_service/macro_service.dart';
import 'package:test/test.dart';

Expand All @@ -23,5 +25,24 @@ void main() {
expect(
serverSocket.first.timeout(const Duration(seconds: 10)), completes);
});

test('sends requests to and from macros', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);

unawaited(MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
macros: [DeclareXImplementation()]));

final socket = await serverSocket.first;

final responses = StreamQueue(
const Utf8Decoder().bind(socket).transform(const LineSplitter()));
final descriptionResponse = await responses.next;
expect(descriptionResponse, '{"macroDescription":{"runsInPhases":[2]}}');

socket.writeln(json.encode(AugmentRequest(phase: 2)));
final augmentResponse = await responses.next;
expect(augmentResponse, '{"augmentations":[{"code":"int get x => 3;"}]}');
});
});
}
28 changes: 26 additions & 2 deletions pkgs/_macro_host/lib/macro_host.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class MacroHost implements MacroService {
// lifecycle state.
Completer<Set<int>>? _macroPhases;

// TODO(davidmorgan): actually match up requests and responses instead of
// this hack.
Completer<AugmentResponse>? _responseCompleter;

MacroHost._(this.macroServer, this.services) {
services.services.insert(0, this);
}
Expand Down Expand Up @@ -61,9 +65,31 @@ class MacroHost implements MacroService {
return _macroPhases!.future;
}

/// Sends [request] to the macro with [name].
Future<AugmentResponse> augment(
QualifiedName name, AugmentRequest request) async {
// TODO(davidmorgan): this just assumes the macro is running, actually
// track macro lifecycle.
macroServer.sendToMacro(name, request);
if (_responseCompleter != null) {
throw StateError('request is already pending');
}
_responseCompleter = Completer<AugmentResponse>();
return await _responseCompleter!.future;
}

/// Handle requests that are for the host.
@override
Future<Object?> handle(Object request) async {
// TODO(davidmorgan): differentiate requests and responses, rather than
// handling as requests.
if (_responseCompleter != null) {
final augmentResponse =
AugmentResponse.fromJson(request as Map<String, Object?>);
_responseCompleter!.complete(augmentResponse);
_responseCompleter = null;
return Object();
}
// TODO(davidmorgan): don't assume the type. Return `null` for types
// that should be passed through to the service that was passed in.
final macroStartedRequest =
Expand All @@ -72,8 +98,6 @@ class MacroHost implements MacroService {
.complete(macroStartedRequest.macroDescription.runsInPhases.toSet());
return MacroStartedResponse();
}

// TODO(davidmorgan): add method here for running macro phases.
}

// TODO(davidmorgan): this is used to handle some requests in the host while
Expand Down
5 changes: 5 additions & 0 deletions pkgs/_macro_host/test/macro_host_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ void main() {

expect(host.isMacro(packageConfig, macroName), true);
expect(await host.queryMacroPhases(packageConfig, macroName), {2});

expect(
await host.augment(macroName, AugmentRequest(phase: 2)),
AugmentResponse(
augmentations: [Augmentation(code: 'int get x => 3;')]));
});
});
}
Expand Down
10 changes: 10 additions & 0 deletions pkgs/_macro_server/lib/macro_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';

/// Serves a [MacroService].
Expand All @@ -13,6 +15,9 @@ class MacroServer {
final HostEndpoint endpoint;
final ServerSocket serverSocket;

// TODO(davidmorgan): track which socket corresponds to which macro(s).
Socket? _lastSocket;

MacroServer._(this.service, this.endpoint, this.serverSocket) {
serverSocket.forEach(_handleConnection);
}
Expand All @@ -26,7 +31,12 @@ class MacroServer {
service, HostEndpoint(port: serverSocket.port), serverSocket);
}

void sendToMacro(QualifiedName name, AugmentRequest request) async {
_lastSocket!.writeln(json.encode(request.node));
}

void _handleConnection(Socket socket) {
_lastSocket = socket;
// TODO(davidmorgan): currently this is JSON with one request per line,
// switch to binary.
socket
Expand Down
1 change: 1 addition & 0 deletions pkgs/_macro_server/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ environment:
sdk: ^3.6.0-48.0.dev

dependencies:
dart_model: any
macro_service: any

dev_dependencies:
Expand Down
8 changes: 5 additions & 3 deletions pkgs/_test_macros/lib/declare_x_macro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:dart_model/dart_model.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

/// Adds a getter `int get x` to the class.
class DeclareX {
const DeclareX();
}

// TODO(davidmorgan): this is a placeholder; make it do something, test it.
class DeclareXImplementation implements Macro {
@override
MacroDescription get description => MacroDescription(runsInPhases: [2]);

@override
Future<AugmentResponse> augment(Host host, AugmentRequest request) async {
// TODO(davidmorgan): add an implementation.
return AugmentResponse();
// TODO(davidmorgan): still need to pass through the augment target.
return AugmentResponse(
augmentations: [Augmentation(code: 'int get x => 3;')]);
}
}
11 changes: 9 additions & 2 deletions pkgs/dart_model/lib/src/dart_model.g.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
// This file is generated. To make changes edit schemas/*.schema.json
// then run from the repo root: dart tool/model_generator/bin/main.dart

/// An augmentation to Dart code. TODO(davidmorgan): this is a placeholder, add some data.
/// An augmentation to Dart code. TODO(davidmorgan): this is a placeholder.
extension type Augmentation.fromJson(Map<String, Object?> node) {
Augmentation() : this.fromJson({});
Augmentation({
String? code,
}) : this.fromJson({
if (code != null) 'code': code,
});

/// Augmentation code.
String get code => node['code'] as String;
}

/// A metadata annotation.
Expand Down
12 changes: 11 additions & 1 deletion pkgs/macro_service/lib/src/macro_service.g.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// This file is generated. To make changes edit schemas/*.schema.json
// then run from the repo root: dart tool/model_generator/bin/main.dart

import 'package:dart_model/dart_model.dart';

/// A request to a macro to augment some code.
extension type AugmentRequest.fromJson(Map<String, Object?> node) {
AugmentRequest({
Expand All @@ -15,7 +17,15 @@ extension type AugmentRequest.fromJson(Map<String, Object?> node) {

/// Macro's response to an [AugmentRequest]: the resulting augmentations.
extension type AugmentResponse.fromJson(Map<String, Object?> node) {
AugmentResponse() : this.fromJson({});
AugmentResponse({
List<Augmentation>? augmentations,
}) : this.fromJson({
if (augmentations != null) 'augmentations': augmentations,
});

/// The augmentations.
List<Augmentation> get augmentations =>
(node['augmentations'] as List).cast();
}

/// A macro host server endpoint. TODO(davidmorgan): this should be a oneOf supporting different types of connection. TODO(davidmorgan): it's not clear if this belongs in this package! But, where else?
Expand Down
3 changes: 3 additions & 0 deletions pkgs/macro_service/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ resolution: workspace
environment:
sdk: ^3.6.0-48.0.dev

dependencies:
dart_model: any

dev_dependencies:
dart_flutter_team_lints: ^3.0.0
7 changes: 5 additions & 2 deletions schemas/dart_model.schema.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{
"$id": "https://github.com/dart-lang/macros/blob/main/schemas/dart_model.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"oneOf": [
{"$ref": "#/$defs/Model"}
],
"$defs": {
"Augmentation": {
"description": "An augmentation to Dart code. TODO(davidmorgan): this is a placeholder, add some data.",
"description": "An augmentation to Dart code. TODO(davidmorgan): this is a placeholder.",
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Augmentation code."
}
}
},
"MetadataAnnotation": {
Expand Down
10 changes: 8 additions & 2 deletions schemas/macro_service.schema.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"$id": "https://github.com/dart-lang/macros/blob/main/schemas/macro_service.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"oneOf": [
{"$ref": "#/$defs/AugmentRequest"},
Expand All @@ -18,7 +17,14 @@
},
"AugmentResponse": {
"type": "object",
"description": "Macro's response to an [AugmentRequest]: the resulting augmentations."
"description": "Macro's response to an [AugmentRequest]: the resulting augmentations.",
"properties": {
"augmentations": {
"description": "The augmentations.",
"type": "array",
"items": {"$ref": "file:dart_model.schema.json#/$defs/Augmentation"}
}
}
},
"HostEndpoint": {
"type": "object",
Expand Down
39 changes: 35 additions & 4 deletions tool/dart_model_generator/lib/generate_dart_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:io';

import 'package:dart_style/dart_style.dart';
import 'package:json_schema/json_schema.dart';
// ignore: implementation_imports
import 'package:json_schema/src/json_schema/models/ref_provider.dart';

/// Generates `pkgs/dart_model/lib/src/dart_model.g.dart` from
/// `schemas/dart_model.schema.json`, and similarly for `macro_service`.
Expand All @@ -17,18 +20,23 @@ void run() {
File('pkgs/dart_model/lib/src/dart_model.g.dart').writeAsStringSync(
generate(File('schemas/dart_model.schema.json').readAsStringSync()));
File('pkgs/macro_service/lib/src/macro_service.g.dart').writeAsStringSync(
generate(File('schemas/macro_service.schema.json').readAsStringSync()));
generate(File('schemas/macro_service.schema.json').readAsStringSync(),
importDartModel: true));
}

/// Generates and returns code for [schemaJson].
String generate(String schemaJson) {
String generate(String schemaJson,
{bool importDartModel = false, String? dartModelJson}) {
final result = <String>[
'// This file is generated. To make changes edit schemas/*.schema.json',
'// then run from the repo root: '
'dart tool/model_generator/bin/main.dart',
'',
if (importDartModel) "import 'package:dart_model/dart_model.dart';",
];
final schema = JsonSchema.create(schemaJson);
final schema = JsonSchema.create(schemaJson,
refProvider: LocalRefProvider(dartModelJson ??
File('schemas/dart_model.schema.json').readAsStringSync()));
for (final def in schema.defs.entries) {
result.add(_generateExtensionType(def.key, def.value));
}
Expand Down Expand Up @@ -174,7 +182,7 @@ String _readRefNameOrType(JsonSchema schema, String key) {
final typeSchema = schema.schemaMap![key] as Map;
final ref = typeSchema[r'$ref'] as String?;
if (ref != null) {
return ref.substring(r'#/$defs/'.length);
return ref.substring(ref.indexOf(r'#/$defs/') + r'#/$defs/'.length);
} else {
final type = typeSchema['type'] as String;
switch (type) {
Expand Down Expand Up @@ -209,3 +217,26 @@ class PropertyMetadata {
required this.type,
this.elementTypeName});
}

/// Loads referenced schemas.
///
/// No need to connect to servers like the default implementation, just return
/// the one file we know we need.
class LocalRefProvider implements RefProvider<SyncJsonProvider> {
final String dartModelJson;

LocalRefProvider(this.dartModelJson);

@override
bool get isSync => true;

@override
SyncJsonProvider get provide => (String path) {
if (path != 'file:///dart_model.schema.json') {
throw UnsupportedError(
'This provider only loads file:///dart_model.schema.json!'
' Got: $path');
}
return json.decode(dartModelJson) as Map<String, Object?>;
};
}
Loading

0 comments on commit 693f52e

Please sign in to comment.