Skip to content

Commit

Permalink
Placeholder package config reader. (#29)
Browse files Browse the repository at this point in the history
* Placeholder package config reader.

* Use `MacroPackageConfig`.
  • Loading branch information
davidmorgan authored Aug 12, 2024
1 parent 1bfa4b3 commit 0b951a9
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 63 deletions.
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');
}

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

0 comments on commit 0b951a9

Please sign in to comment.