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

Placeholder package config reader. #29

Merged
merged 2 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions pkgs/_analyzer_macros/lib/macro_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,21 @@ import 'package:macros/src/executor.dart' as injected;
/// Injected macro implementation for the analyzer.
class AnalyzerMacroImplementation implements injected.MacroImplementation {
final Uri packageConfig;
final Map<String, String> macroImplByName;
final MacroHost _host;

AnalyzerMacroImplementation._(
this.packageConfig, this.macroImplByName, this._host);
AnalyzerMacroImplementation._(this.packageConfig, this._host);

/// Starts the macro host.
///
/// [packageConfig] is the package config of the workspace of the code being
/// edited.
///
/// [macroImplByName] identifies macros, it's a placeholder until we identify
/// macros using package config. Keys are macro annotation qualified names
/// (`uri#name`) and values are macro implementation qualified names.
static Future<AnalyzerMacroImplementation> start({
required Uri packageConfig,
required Map<String, String> macroImplByName,
}) async =>
AnalyzerMacroImplementation._(
packageConfig,
macroImplByName,
await MacroHost.serve(
macroImplByName: macroImplByName,
packageConfig: packageConfig,
queryService: AnalyzerQueryService()));

@override
Expand All @@ -61,7 +53,7 @@ class AnalyzerMacroPackageConfigs implements injected.MacroPackageConfigs {

@override
bool isMacro(Uri uri, String name) =>
_impl._host.isMacro(_impl.packageConfig, QualifiedName('$uri#$name'));
_impl._host.isMacro(QualifiedName('$uri#$name'));
}

class AnalyzerMacroRunner implements injected.MacroRunner {
Expand All @@ -73,8 +65,7 @@ class AnalyzerMacroRunner implements injected.MacroRunner {
injected.RunningMacro run(Uri uri, String name) => AnalyzerRunningMacro.run(
_impl,
QualifiedName('$uri#$name'),
// Look up from the macro name to its implementation.
QualifiedName(_impl.macroImplByName['$uri#$name']!));
_impl._host.lookupMacroImplementation(QualifiedName('$uri#$name'))!);
}

class AnalyzerRunningMacro implements injected.RunningMacro {
Expand Down
7 changes: 1 addition & 6 deletions pkgs/_analyzer_macros/test/analyzer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@ void main() {
AnalysisContextCollection(includedPaths: [directory.path]);
analysisContext = contextCollection.contexts.first;
injected.macroImplementation = await AnalyzerMacroImplementation.start(
packageConfig: Isolate.packageConfigSync!,
macroImplByName: {
'package:_test_macros/declare_x_macro.dart#DeclareX':
'package:_test_macros/declare_x_macro.dart'
'#DeclareXImplementation'
});
packageConfig: Isolate.packageConfigSync!);
});

test('discovers macros, runs them, applies augmentations', () async {
Expand Down
18 changes: 4 additions & 14 deletions pkgs/_cfe_macros/lib/macro_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,21 @@ import 'package:macros/src/executor.dart' as injected;
/// Injected macro implementation for the analyzer.
class CfeMacroImplementation implements injected.MacroImplementation {
final Uri packageConfig;
final Map<String, String> macroImplByName;
final MacroHost _host;

CfeMacroImplementation._(
this.packageConfig, this.macroImplByName, this._host);
CfeMacroImplementation._(this.packageConfig, this._host);

/// Starts the macro host.
///
/// [packageConfig] is the package config of the workspace of the code being
/// edited.
///
/// [macroImplByName] identifies macros, it's a placeholder until we identify
/// macros using package config. Keys are macro annotation qualified names
/// (`uri#name`) and values are macro implementation qualified names.
static Future<CfeMacroImplementation> start({
required Uri packageConfig,
required Map<String, String> macroImplByName,
}) async =>
CfeMacroImplementation._(
packageConfig,
macroImplByName,
await MacroHost.serve(
macroImplByName: macroImplByName,
queryService: CfeQueryService()));
packageConfig: packageConfig, queryService: CfeQueryService()));

@override
injected.MacroPackageConfigs get packageConfigs =>
Expand All @@ -61,7 +52,7 @@ class CfeMacroPackageConfigs implements injected.MacroPackageConfigs {

@override
bool isMacro(Uri uri, String name) =>
_impl._host.isMacro(_impl.packageConfig, QualifiedName('$uri#$name'));
_impl._host.isMacro(QualifiedName('$uri#$name'));
}

class CfeMacroRunner implements injected.MacroRunner {
Expand All @@ -73,8 +64,7 @@ class CfeMacroRunner implements injected.MacroRunner {
injected.RunningMacro run(Uri uri, String name) => CfeRunningMacro.run(
_impl,
QualifiedName('$uri#$name'),
// Look up from the macro name to its implementation.
QualifiedName(_impl.macroImplByName['$uri#$name']!));
_impl._host.lookupMacroImplementation(QualifiedName('$uri#$name'))!);
}

class CfeRunningMacro implements injected.RunningMacro {
Expand Down
14 changes: 1 addition & 13 deletions pkgs/_cfe_macros/test/cfe_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,8 @@ void main() {
tempDir = Directory.systemTemp.createTempSync('cfe_test');

// Inject test macro implementation.
// TODO(davidmorgan): the CFE passes us a mix of absolute file paths and
// package URIs, fix this properly.
final macroFileUri = Directory.current.uri
.resolve('../_test_macros/lib/declare_x_macro.dart');
injected.macroImplementation = await CfeMacroImplementation.start(
packageConfig: Isolate.packageConfigSync!,
macroImplByName: {
'$macroFileUri#DeclareX':
'package:_test_macros/declare_x_macro.dart'
'#DeclareXImplementation',
'package:_test_macros/declare_x_macro.dart#DeclareX':
'package:_test_macros/declare_x_macro.dart'
'#DeclareXImplementation'
});
packageConfig: Isolate.packageConfigSync!);
});

test('discovers macros, runs them, applies augmentations', () async {
Expand Down
29 changes: 16 additions & 13 deletions pkgs/_macro_host/lib/macro_host.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,42 @@ import 'package:_macro_server/macro_server.dart';
import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';

import 'src/package_config.dart';

/// Hosts macros: builds them, runs them, serves the macro service.
///
/// Tools that want to support macros, such as the Analyzer and the CFE, can
/// do so by running a `MacroHost` and providing their own `HostService`.
class MacroHost {
final Map<String, String> macroImplByName;
final MacroPackageConfig macroPackageConfig;
final _HostService _hostService;
final MacroServer macroServer;
final MacroBuilder macroBuilder = MacroBuilder();
final MacroRunner macroRunner = MacroRunner();

MacroHost._(this.macroImplByName, this.macroServer, this._hostService);
MacroHost._(this.macroPackageConfig, this.macroServer, this._hostService);

/// Starts a macro host with introspection queries handled by [queryService].
///
/// [macroImplByName] is a map from macro annotation qualified name to macro
/// implementation qualified name, it's needed until this information is
/// available in package configs.
static Future<MacroHost> serve({
required Map<String, String> macroImplByName,
required Uri packageConfig,
required QueryService queryService,
}) async {
final macroPackageConfig = MacroPackageConfig.readFromUri(packageConfig);
final hostService = _HostService(queryService);
final server = await MacroServer.serve(service: hostService);
return MacroHost._(macroImplByName, server, hostService);
return MacroHost._(macroPackageConfig, server, hostService);
}

/// Whether [name] is a macro according to that package's `pubspec.yaml`.
bool isMacro(Uri packageConfig, QualifiedName name) {
// TODO(language/3728): this is a placeholder, use package config when
// available.
return macroImplByName.keys.contains(name.string);
}
bool isMacro(QualifiedName name) => lookupMacroImplementation(name) != null;

/// Checks whether [name] is a macro annotation.
///
/// If so, returns the qualified name of the macro implementation.
///
/// If not, returns `null`.
QualifiedName? lookupMacroImplementation(QualifiedName name) =>
macroPackageConfig.lookupMacroImplementation(name);

/// Determines which phases the macro implemented at [name] runs in.
Future<Set<int>> queryMacroPhases(
Expand Down
84 changes: 84 additions & 0 deletions pkgs/_macro_host/lib/src/package_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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 'dart:io';

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

/// Reads a package config to determine information about macros.
class MacroPackageConfig {
final PackageConfig packageConfig;

MacroPackageConfig({required this.packageConfig});

factory MacroPackageConfig.readFromUri(Uri uri) => MacroPackageConfig(
packageConfig:
PackageConfig.parseBytes(File.fromUri(uri).readAsBytesSync(), uri));

/// Checks whether [name] is a macro annotation.
///
/// If so, returns the qualified name of the macro implementation.
///
/// If not, returns `null`.
///
/// This is a placeholder implementation until `language/3728` is
/// implemented. It expects macros to be marked by a comment in the
/// annotation package `pubspec.yaml` that looks like this:
///
/// ```
/// # macro <annotation name> <implementation name>
/// ```
///
/// For example:
///
/// ```
/// # macro lib/declare_x_macro.dart#DeclareX package:_test_macros/declare_x_macro.dart#DeclareXImplementation
/// ```
QualifiedName? lookupMacroImplementation(QualifiedName name) {
var packageName = name.uri;
if (packageName.startsWith('dart:') ||
packageName.startsWith('org-dartlang-sdk:')) {
return null;
}
// TODO(davidmorgan): error handling when lookup fails.
if (packageName.startsWith('file:')) {
packageName =
packageConfig.toPackageUri(Uri.parse(packageName)).toString();
}
final libraryPathAndName =
'lib/${packageName.substring(packageName.indexOf('/') + 1)}#${name.name}';
if (packageName.startsWith('package:') && packageName.contains('/')) {
packageName = packageName.substring('package:'.length);
packageName = packageName.substring(0, packageName.indexOf('/'));
} else {
// TODO(davidmorgan): support macros outside lib dirs.
throw ArgumentError('Name must start "package:" and have a path: $name');
Copy link
Contributor

Choose a reason for hiding this comment

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

We should have a TODO here to look up the package name by URI in the package config, to support macros defined outside of lib dirs.

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.

}

final matchingPackage = packageConfig[packageName];
if (matchingPackage == null) {
throw StateError('Package "$packageName" not found in package config.');
}

// TODO(language/3728): read macro annotation identifiers from package
// config. Until then, check the pubsec, to simulate what that feature will
// do.
final packageUri = matchingPackage.root;
final pubspecUri = packageUri.resolve('pubspec.yaml');
final lines = File.fromUri(pubspecUri).readAsLinesSync();

final implsByLibraryQualifiedName = <String, String>{};
for (final line in lines) {
if (!line.startsWith('# macro ')) continue;
final items = line.split(' ');
// The rest of the line should be the library qualified name of the
// annotation then the fully qualified name of the implementation.
implsByLibraryQualifiedName[items[2]] = items[3];
}

final result = implsByLibraryQualifiedName[libraryPathAndName];
return result == null ? null : QualifiedName(result);
}
}
1 change: 1 addition & 0 deletions pkgs/_macro_host/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
_macro_server: any
dart_model: any
macro_service: any
package_config: ^2.1.0

dev_dependencies:
_test_macros: any
Expand Down
8 changes: 4 additions & 4 deletions pkgs/_macro_host/test/macro_host_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ void main() {

final queryService = TestQueryService();
final host = await MacroHost.serve(
macroImplByName: {macroName.string: macroImplementation.string},
packageConfig: Isolate.packageConfigSync!,
queryService: queryService);

final packageConfig = Isolate.packageConfigSync!;

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

Expand All @@ -42,12 +42,12 @@ void main() {

final queryService = TestQueryService();
final host = await MacroHost.serve(
macroImplByName: {macroName.string: macroImplementation.string},
packageConfig: Isolate.packageConfigSync!,
queryService: queryService);

final packageConfig = Isolate.packageConfigSync!;

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

Expand Down
41 changes: 41 additions & 0 deletions pkgs/_macro_host/test/package_config_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 'dart:io';
import 'dart:isolate';

import 'package:_macro_host/src/package_config.dart';
import 'package:dart_model/dart_model.dart';
import 'package:test/test.dart';

void main() {
group(MacroPackageConfig, () {
test('can look up macro implementations from package URIs', () async {
final packageConfig =
MacroPackageConfig.readFromUri(Isolate.packageConfigSync!);

expect(
packageConfig
.lookupMacroImplementation(QualifiedName(
'package:_test_macros/declare_x_macro.dart#DeclareX'))!
.string,
'package:_test_macros/declare_x_macro.dart#DeclareXImplementation');
});

test('can look up macro implementations from file URIs', () async {
final packageConfig =
MacroPackageConfig.readFromUri(Isolate.packageConfigSync!);

final sourceFileUri = Directory.current.uri
.resolve('../_test_macros/lib/declare_x_macro.dart');

expect(
packageConfig
.lookupMacroImplementation(
QualifiedName('$sourceFileUri#DeclareX'))!
.string,
'package:_test_macros/declare_x_macro.dart#DeclareXImplementation');
});
});
}
4 changes: 4 additions & 0 deletions pkgs/_test_macros/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ dependencies:
dev_dependencies:
dart_flutter_team_lints: ^3.0.0
test: ^1.25.0

# TODO(language/3728): use the real feature when there is one.
# macro lib/declare_x_macro.dart#DeclareX package:_test_macros/declare_x_macro.dart#DeclareXImplementation
# macro lib/query_class.dart#QueryClass package:_test_macros/query_class.dart#QueryClassImplementation