Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build macros, run them, serve a (test) service, host, connect to it #20

Merged
merged 2 commits into from
Aug 6, 2024

Conversation

davidmorgan
Copy link
Contributor

@davidmorgan davidmorgan commented Jul 22, 2024

Flesh out much of the end to end functionality:

  • Build macros (with just "dart compile exe")
  • Run macros (with Process)
  • Serve them (TCP/IP)
  • Connect to them
  • First message passed macro->host: which phases to run in

Next after this: add first introspection and augmentation RPCs/capability; then it will be an actual e2e macro.

@davidmorgan davidmorgan marked this pull request as draft July 22, 2024 09:38
@davidmorgan davidmorgan force-pushed the macro-builder branch 7 times, most recently from 0501a0f to 3c4a927 Compare July 23, 2024 07:50
@davidmorgan davidmorgan changed the title Build macros. Build macros, run them, serve a (test) service, host, connect to it Jul 23, 2024
@davidmorgan davidmorgan force-pushed the macro-builder branch 2 times, most recently from a5287c5 to 81605eb Compare July 23, 2024 12:37
@davidmorgan davidmorgan marked this pull request as ready for review July 24, 2024 14:04
@@ -2,22 +2,110 @@
// 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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we separate out an interface here, and then have an implementation which imports dart:io? It might anyways make sense to have an abstraction here, for kernel versions or a version which uses the front end as a library (this might live in the SDK, under pkg/front_end).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure this will end up with multiple implementations; added a TODO.

Some of them may come from code already in the analyzer and/or CFE.

// to by `macroImplementations` then pass them to `MacroClient.run` in
// `package:_macro_client`.
return BuiltMacroBundle();
File packageConfig, Iterable<QualifiedName> macroImplementations) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the interface probably shouldn't expose any dart:io types - I would make the package config be something provided up front and stored in a field (also not part of the interface probably, just an implementation specific thing).

Otherwise, there is no need for a class at all here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the key question here is about the scope of MacroBuilder: is it one builder for the host, one for the workspace, one for the workspace at a particular time ... wider scope allows more optimization, by which I mean reuse of (resident?) compilers and compile artifacts across builds.

I agree that File does not seem like the right concept, what we are really talking about here is the version solve and workspace that we need to do a build: the builder is going to have to care about both and about changes to both. There are questions of file overlays when using the analyzer and multiroot file systems.

So a lot to unpack here :) which is why I have it at the simplest+wrong thing currently. I'll add a pile of TODOs for all the above.

Copy link
Contributor

@jakemac53 jakemac53 Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw I don't think the version solve or workspace do matter - those are both encapsulated by the package config. And the package config is the only thing the compiler or analyzer should care about. It is the only unifying concept across all our environments.

I just don't think we want File (or any dart:io type) in the public API. Even if this was just a URI I probably would be happy :).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably do have to end up worrying about version solve + workspace in order to handle finding prebuilt macros and changes to macros; it's not needed for now though.

Sure, Uri works; done. Thanks.

}

/// Creates the entrypoint script for [macros].
static String createBootstrap(List<QualifiedName> macros) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be public?

If we want to unit test it, I would put it in a different library under lib/src.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the "private" packages, starting with an underscore, I put implementation directly under lib instead of lib/src; my expectation is that we end up folding them entirely into some package's src so adding lib/src now just seemed like noise.

So I'm thinking of this entire file as private.

Happy to do something else though, as you like.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think there is a difference here worth encapsulating.

If I understand correctly, MacroBuilder will be public (ie: accessible to the CFE/Analyzer). But this function is not intended to be used by either, and should ideally not be exposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, moved out to lib/src as you originally suggested. Thanks :)

script.writeln("import '${macro.uri}' as m$i;");
}
script.write('''
import 'dart:convert';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you might want to prefix all these, to be safe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done.

Future<BuiltMacroBundle> build() async {
final scriptFile = File.fromUri(workspace.uri.resolve('bin/main.dart'));
scriptFile.parent.createSync(recursive: true);
scriptFile.writeAsStringSync(script.toString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it is a bit weird to be using sync I/O within an async function like this, I would just use async

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

class MacroRunner {
void run(BuiltMacroBundle macro, HostEndpoint endpoint) {}
/// Runs [macroBundle] connected to [endpoint].
void run(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a bit odd to not have this return a Future - maybe just call it start? It also seems like it should just be a top level method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename done.

I expect it needs to be a class for the shutdown/cleanup TODO :)

}

void _handleConnections() {
serverSocket.forEach(_handleConnection);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be inlined above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@override
Future<Object> handle(Object request) async {
_macroStartedRequestsController.add(request as MacroStartedRequest);
return MacroStartedResponse() as Object;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this cast necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension types don't implement Object unless it's explicitly stated :)

This gets resolved properly when we add union types for requests+responses in PR #22, so we don't use Object and use the union type.

@@ -12,7 +12,7 @@ class DeclareX {
// TODO(davidmorgan): this is a placeholder; make it do something, test it.
class DeclareXImplementation implements Macro {
@override
MacroDescription get description => MacroDescription(runsInPhases: [3]);
MacroDescription get description => MacroDescription(runsInPhases: [2]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems wrong for users to be creating this description, with explicit phase numbers like this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added a TODO on the Macro interface that MacroDescription should be populated (as far as possible) by macro_builder which can figure out the information and inject it in the bootstrap script, rather than asking the user macro code to do it.

Copy link
Contributor Author

@davidmorgan davidmorgan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my general reply to your email about these PRs, as to why there are a lot of TODOs open instead of addressed: tl;dr so we can figure out where to reuse code instead of writing new.

Thanks, PTAL.

script.writeln("import '${macro.uri}' as m$i;");
}
script.write('''
import 'dart:convert';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done.

/// Throws on failure to build.
Future<BuiltMacroBundle> build() async {
final scriptFile = File.fromUri(workspace.uri.resolve('bin/main.dart'));
scriptFile.parent.createSync(recursive: true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Future<BuiltMacroBundle> build() async {
final scriptFile = File.fromUri(workspace.uri.resolve('bin/main.dart'));
scriptFile.parent.createSync(recursive: true);
scriptFile.writeAsStringSync(script.toString());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

//
// For now just use the command line.

final result = Process.runSync('dart', ['compile', 'exe', 'bin/main.dart'],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done; although the resolved executable is also not always correct, if this is used from an AOT-compiled binary. Added a TODO as well :)

//
// For now just use the command line.

final result = Process.runSync('dart', ['compile', 'exe', 'bin/main.dart'],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

class MacroRunner {
void run(BuiltMacroBundle macro, HostEndpoint endpoint) {}
/// Runs [macroBundle] connected to [endpoint].
void run(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename done.

I expect it needs to be a class for the shutdown/cleanup TODO :)

}

void _handleConnections() {
serverSocket.forEach(_handleConnection);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@override
Future<Object> handle(Object request) async {
_macroStartedRequestsController.add(request as MacroStartedRequest);
return MacroStartedResponse() as Object;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension types don't implement Object unless it's explicitly stated :)

This gets resolved properly when we add union types for requests+responses in PR #22, so we don't use Object and use the union type.

@@ -12,7 +12,7 @@ class DeclareX {
// TODO(davidmorgan): this is a placeholder; make it do something, test it.
class DeclareXImplementation implements Macro {
@override
MacroDescription get description => MacroDescription(runsInPhases: [3]);
MacroDescription get description => MacroDescription(runsInPhases: [2]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added a TODO on the Macro interface that MacroDescription should be populated (as far as possible) by macro_builder which can figure out the information and inject it in the bootstrap script, rather than asking the user macro code to do it.

}

/// Creates the entrypoint script for [macros].
static String createBootstrap(List<QualifiedName> macros) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think there is a difference here worth encapsulating.

If I understand correctly, MacroBuilder will be public (ie: accessible to the CFE/Analyzer). But this function is not intended to be used by either, and should ideally not be exposed.

final MacroServer macroServer;
final ListOfServices services;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Fine to follow up in later prs

@davidmorgan davidmorgan merged commit b3f4c73 into dart-lang:main Aug 6, 2024
39 checks passed
@davidmorgan davidmorgan deleted the macro-builder branch August 6, 2024 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants