Skip to content

Commit

Permalink
Address review comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorgan committed Aug 6, 2024
1 parent 17e4220 commit be9d3d7
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 79 deletions.
64 changes: 27 additions & 37 deletions pkgs/_macro_builder/lib/macro_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import 'dart:io';

import 'package:dart_model/dart_model.dart';

import 'src/bootstrap.dart';

/// Builds macros.
///
/// TODO(davidmorgan): add a way to clean up generated files and built output.
Expand All @@ -16,40 +18,20 @@ class MacroBuilder {
/// implements `Macro` from `package:macro`.
///
/// The [packageConfig] must include the macros and all their deps.
///
/// TODO(davidmorgan): figure out builder lifecycle: is it one builder per
/// host, one per workspace, one per build?
/// TODO(davidmorgan): replace `File` packageConfig with a concept of version
/// solve and workspace.
/// TODO(davidmorgan): support for multi-root workspaces.
/// TODO(davidmorgan): support (or decide not to support) in-memory overlay
/// filesystems.
Future<BuiltMacroBundle> build(
File packageConfig, Iterable<QualifiedName> macroImplementations) async {
Uri packageConfig, Iterable<QualifiedName> macroImplementations) async {
final script = createBootstrap(macroImplementations.toList());

return await MacroBuild(packageConfig, script).build();
}

/// Creates the entrypoint script for [macros].
static String createBootstrap(List<QualifiedName> macros) {
final script = StringBuffer();
for (var i = 0; i != macros.length; ++i) {
final macro = macros[i];
// TODO(davidmorgan): pick non-clashing prefixes.
script.writeln("import '${macro.uri}' as m$i;");
}
script.write('''
import 'dart:convert';
import 'package:_macro_client/macro_client.dart';
import 'package:macro_service/macro_service.dart';
void main(List<String> arguments) {
MacroClient.run(
endpoint: HostEndpoint.fromJson(json.decode(arguments[0])),
macros: [''');
for (var i = 0; i != macros.length; ++i) {
final macro = macros[i];
script.write('m$i.${macro.name}()');
if (i != macros.length - 1) script.write(', ');
}
script.writeln(']);');
script.writeln('}');
return script.toString();
}
}

/// A bundle of one or more macros that's ready to execute.
Expand All @@ -61,8 +43,11 @@ class BuiltMacroBundle {
}

/// A single build.
///
/// TODO(davidmorgan): split to interface+implementations as we add different
/// ways to build.
class MacroBuild {
final File packageConfig;
final Uri packageConfig;
final String script;
final Directory workspace =
Directory.systemTemp.createTempSync('macro_builder');
Expand All @@ -76,8 +61,8 @@ class MacroBuild {
/// Throws on failure to build.
Future<BuiltMacroBundle> build() async {
final scriptFile = File.fromUri(workspace.uri.resolve('bin/main.dart'));
scriptFile.parent.createSync(recursive: true);
scriptFile.writeAsStringSync(script.toString());
await scriptFile.create(recursive: true);
await scriptFile.writeAsString(script.toString());

final targetPackageConfig =
File.fromUri(workspace.uri.resolve('.dart_tool/package_config.json'));
Expand All @@ -90,7 +75,11 @@ class MacroBuild {
//
// For now just use the command line.

final result = Process.runSync('dart', ['compile', 'exe', 'bin/main.dart'],
final result = Process.runSync(
// TODO(davidmorgan): this is wrong if run from an AOT-compiled
// executable.
Platform.resolvedExecutable,
['compile', 'exe', 'bin/main.dart', '--output=bin/main.exe'],
workingDirectory: workspace.path);
if (result.exitCode != 0) {
throw StateError('Compile failed: ${result.stderr}');
Expand All @@ -100,11 +89,12 @@ class MacroBuild {
File.fromUri(scriptFile.parent.uri.resolve('main.exe')).path);
}

/// Returns the contents of [pubspec] with relative paths replaced to
/// Returns the contents of [packageConfig] with relative paths replaced to
/// absolute paths, so the pubspec will work from any location.
String _makePackageConfigAbsolute(File pubspec) {
final root = pubspec.parent.parent.absolute.uri;
return pubspec
String _makePackageConfigAbsolute(Uri packageConfig) {
final file = File.fromUri(packageConfig);
final root = file.parent.parent.absolute.uri;
return file
.readAsStringSync()
.replaceAll('"rootUri": "../', '"rootUri": "$root');
}
Expand Down
34 changes: 34 additions & 0 deletions pkgs/_macro_builder/lib/src/bootstrap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// 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';

/// Creates the entrypoint script for [macros].
String createBootstrap(List<QualifiedName> macros) {
final script = StringBuffer();
for (var i = 0; i != macros.length; ++i) {
final macro = macros[i];
// TODO(davidmorgan): pick non-clashing prefixes.
script.writeln("import '${macro.uri}' as m$i;");
}
script.write('''
import 'dart:convert' as convert;
import 'package:_macro_client/macro_client.dart' as macro_client;
import 'package:macro_service/macro_service.dart' as macro_service;
void main(List<String> arguments) {
macro_client.MacroClient.run(
endpoint: macro_service.HostEndpoint.fromJson(
convert.json.decode(arguments[0])),
macros: [''');
for (var i = 0; i != macros.length; ++i) {
final macro = macros[i];
script.write('m$i.${macro.name}()');
if (i != macros.length - 1) script.write(', ');
}
script.writeln(']);');
script.writeln('}');
return script.toString();
}
17 changes: 9 additions & 8 deletions pkgs/_macro_builder/test/macro_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import 'dart:io';
import 'dart:isolate';

import 'package:_macro_builder/macro_builder.dart';
import 'package:_macro_builder/src/bootstrap.dart';
import 'package:dart_model/dart_model.dart';
import 'package:test/test.dart';

void main() {
group(MacroBuilder, () {
test('bootstrap matches golden', () async {
final script = MacroBuilder.createBootstrap([
final script = createBootstrap([
QualifiedName('package:_test_macros/declare_x_macro.dart#DeclareX'),
QualifiedName('package:_test_macros/declare_y_macro.dart#DeclareY'),
QualifiedName(
Expand All @@ -23,14 +24,15 @@ void main() {
import 'package:_test_macros/declare_x_macro.dart' as m0;
import 'package:_test_macros/declare_y_macro.dart' as m1;
import 'package:_more_macros/other_macro.dart' as m2;
import 'dart:convert';
import 'dart:convert' as convert;
import 'package:_macro_client/macro_client.dart';
import 'package:macro_service/macro_service.dart';
import 'package:_macro_client/macro_client.dart' as macro_client;
import 'package:macro_service/macro_service.dart' as macro_service;
void main(List<String> arguments) {
MacroClient.run(
endpoint: HostEndpoint.fromJson(json.decode(arguments[0])),
macro_client.MacroClient.run(
endpoint: macro_service.HostEndpoint.fromJson(
convert.json.decode(arguments[0])),
macros: [m0.DeclareX(), m1.DeclareY(), m2.OtherMacroImplementation()]);
}
''');
Expand All @@ -39,8 +41,7 @@ void main(List<String> arguments) {
test('builds macros', () async {
final builder = MacroBuilder();

final bundle =
await builder.build(File.fromUri(Isolate.packageConfigSync!), [
final bundle = await builder.build(Isolate.packageConfigSync!, [
QualifiedName(
'package:_test_macros/declare_x_macro.dart#DeclareXImplementation')
]);
Expand Down
20 changes: 9 additions & 11 deletions pkgs/_macro_client/lib/macro_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

/// Runs macros, connecting them to a macro host.
///
/// TODO(davidmorgan): handle shutdown and dispose.
/// TODO(davidmorgan): split to multpile implementations depending on
/// transport used to connect to host.
class MacroClient {
final Iterable<Macro> macros;
final Socket socket;

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

// Tell the host which macros are in this bundle.
for (final macro in macros) {
_send(MacroStartedRequest(macroDescription: macro.description).node);
final request = MacroStartedRequest(macroDescription: macro.description);
// TODO(davidmorgan): currently this is JSON with one request per line,
// switch to binary.
socket.writeln(json.encode(request.node));
}
}

Expand All @@ -28,10 +32,4 @@ class MacroClient {
final socket = await Socket.connect('localhost', endpoint.port);
return MacroClient._(macros, socket);
}

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));
}
}
1 change: 1 addition & 0 deletions pkgs/_macro_client/test/macro_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ void main() {
group(MacroClient, () {
test('connects to service', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
addTearDown(serverSocket.close);

unawaited(MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
Expand Down
23 changes: 12 additions & 11 deletions pkgs/_macro_host/lib/macro_host.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// 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:io';
import 'dart:async';

import 'package:_macro_builder/macro_builder.dart';
import 'package:_macro_runner/macro_runner.dart';
Expand All @@ -22,7 +22,7 @@ class MacroHost implements MacroService {

// TODO(davidmorgan): this should be per macro, as part of tracking per-macro
// lifecycle state.
Set<int>? _macroPhases;
Completer<Set<int>>? _macroPhases;

MacroHost._(this.macroServer, this.services) {
services.services.insert(0, this);
Expand All @@ -43,22 +43,22 @@ class MacroHost implements MacroService {
}

/// Whether [name] is a macro according to that package's `pubspec.yaml`.
bool isMacro(File packageConfig, QualifiedName name) {
bool isMacro(Uri packageConfig, QualifiedName name) {
// TODO(language/3728): this is a placeholder, use package config when
// available.
return true;
}

/// Determines which phases the macro implemented at [name] runs in.
Future<Set<int>> queryMacroPhases(
File packageConfig, QualifiedName name) async {
if (_macroPhases != null) return _macroPhases!;
Uri packageConfig, QualifiedName name) async {
// TODO(davidmorgan): track macro lifecycle, correctly run once per macro
// code change including if queried multiple times before response returns.
if (_macroPhases != null) return _macroPhases!.future;
_macroPhases = Completer();
final macroBundle = await macroBuilder.build(packageConfig, [name]);
macroRunner.run(macroBundle: macroBundle, endpoint: macroServer.endpoint);
// TODO(davidmorgan): wait explicitly for the MacroStartedRequest to
// arrive, remove this hard-coded wait.
await Future<void>.delayed(const Duration(seconds: 2));
return _macroPhases!;
macroRunner.start(macroBundle: macroBundle, endpoint: macroServer.endpoint);
return _macroPhases!.future;
}

/// Handle requests that are for the host.
Expand All @@ -68,7 +68,8 @@ class MacroHost implements MacroService {
// that should be passed through to the service that was passed in.
final macroStartedRequest =
MacroStartedRequest.fromJson(request as Map<String, Object?>);
_macroPhases = macroStartedRequest.macroDescription.runsInPhases.toSet();
_macroPhases!
.complete(macroStartedRequest.macroDescription.runsInPhases.toSet());
return MacroStartedResponse();
}

Expand Down
3 changes: 1 addition & 2 deletions pkgs/_macro_host/test/macro_host_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 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:io';
import 'dart:isolate';

import 'package:_macro_host/macro_host.dart';
Expand All @@ -18,7 +17,7 @@ void main() {

final macroName = QualifiedName(
'package:_test_macros/declare_x_macro.dart#DeclareXImplementation');
final packageConfig = File.fromUri(Isolate.packageConfigSync!);
final packageConfig = Isolate.packageConfigSync!;

expect(host.isMacro(packageConfig, macroName), true);
expect(await host.queryMacroPhases(packageConfig, macroName), {2});
Expand Down
4 changes: 2 additions & 2 deletions pkgs/_macro_runner/lib/macro_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import 'package:macro_service/macro_service.dart';
///
/// TODO(davidmorgan): support shutdown/cleanup.
class MacroRunner {
/// Runs [macroBundle] connected to [endpoint].
void run(
/// Starts [macroBundle] connected to [endpoint].
void start(
{required BuiltMacroBundle macroBundle, required HostEndpoint endpoint}) {
Process.run(macroBundle.executablePath, [json.encode(endpoint)]);
}
Expand Down
6 changes: 3 additions & 3 deletions pkgs/_macro_runner/test/macro_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ void main() {
group(MacroRunner, () {
test('runs macros', () async {
final builder = MacroBuilder();
final bundle =
await builder.build(File.fromUri(Isolate.packageConfigSync!), [
final bundle = await builder.build(Isolate.packageConfigSync!, [
QualifiedName(
'package:_test_macros/declare_x_macro.dart#DeclareXImplementation')
]);

final serverSocket = await ServerSocket.bind('localhost', 0);
addTearDown(serverSocket.close);

final runner = MacroRunner();
runner.run(
runner.start(
macroBundle: bundle, endpoint: HostEndpoint(port: serverSocket.port));

expect(
Expand Down
6 changes: 1 addition & 5 deletions pkgs/_macro_server/lib/macro_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MacroServer {
final ServerSocket serverSocket;

MacroServer._(this.service, this.endpoint, this.serverSocket) {
_handleConnections();
serverSocket.forEach(_handleConnection);
}

/// Serves [service].
Expand All @@ -26,10 +26,6 @@ class MacroServer {
service, HostEndpoint(port: serverSocket.port), serverSocket);
}

void _handleConnections() {
serverSocket.forEach(_handleConnection);
}

void _handleConnection(Socket socket) {
// TODO(davidmorgan): currently this is JSON with one request per line,
// switch to binary.
Expand Down
5 changes: 5 additions & 0 deletions pkgs/macro/lib/macro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import 'package:macro_service/macro_service.dart';
/// code.
abstract interface class Macro {
/// Description of the macro.
///
/// TODO(davidmorgan): where possible the macro information should be
/// determined by `macro_builder` and injected in the bootstrap script rather
/// than relying on the user-written macro code to return it.
///
MacroDescription get description;

/// Generate augmentatations and diagnostics for [request].
Expand Down

0 comments on commit be9d3d7

Please sign in to comment.