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

Configurable outputs for the combining builder #554

Merged
merged 3 commits into from
Aug 19, 2021
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
5 changes: 5 additions & 0 deletions source_gen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.1.0

* Add the `build_extensions` option to `combining_builder`, allowing output
files to be generated into a different directory.

## 1.0.5

* Fix a bug with reviving constant expressions which are fields defined on a
Expand Down
36 changes: 36 additions & 0 deletions source_gen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,41 @@ targets:
If you provide a builder that uses `SharedPartBuilder` and `combining_builder`,
you should document this feature for your users.

### Generating files in different directories

When using shared-part builders which apply the `combining_builder` as part of
the build, the output location for an input file can be changed.
By default, a `.g.dart` file next to the input is generated.

To change this, set the `build_extensions` option on the combining builder. In
jakemac53 marked this conversation as resolved.
Show resolved Hide resolved
the options, `build_extensions` is a map from `String` to `String`, where the
key is matches inputs and the value is a single build output.
For more details on build extensions, see [the docs in the build package][outputs].

For example, you can use these options to generate files under `lib/generated`
with the following build configuration:

```yaml
targets:
$default:
builders:
source_gen|combining_builder:
options:
build_extensions:
'^lib/{{}}.dart': 'lib/generated/{{}}.g.dart'
```

Remember to change the `part` statement in the input to refer to the correct
output file in the other directory.

Note that builder options are part of `source_gen`'s public api! When using
them in a build configuration, always add a dependency on `source_gen` as well:

```yaml
dev_dependencies:
source_gen: ^1.1.0
```

## FAQ

### What is the difference between `source_gen` and [build][]?
Expand Down Expand Up @@ -148,3 +183,4 @@ wraps a single Generator to make a `Builder` which creates Dart library files.
[Trivial example]: https://github.com/dart-lang/source_gen/blob/master/source_gen/test/src/comment_generator.dart
[Full example package]: https://github.com/dart-lang/source_gen/tree/master/example
[example usage]: https://github.com/dart-lang/source_gen/tree/master/example_usage
[outputs]: https://github.com/dart-lang/build/blob/master/docs/writing_a_builder.md#configuring-outputs
62 changes: 56 additions & 6 deletions source_gen/lib/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import 'src/utils.dart';

const _outputExtensions = '.g.dart';
const _partFiles = '.g.part';
const _defaultExtensions = {
'.dart': [_outputExtensions]
};

Builder combiningBuilder([BuilderOptions options = BuilderOptions.empty]) {
final optionsMap = Map<String, dynamic>.from(options.config);
Expand All @@ -29,10 +32,12 @@ Builder combiningBuilder([BuilderOptions options = BuilderOptions.empty]) {
final ignoreForFile = Set<String>.from(
optionsMap.remove('ignore_for_file') as List? ?? <String>[],
);
final buildExtensions = _validatedBuildExtensionsFrom(optionsMap);

final builder = CombiningBuilder(
includePartName: includePartName,
ignoreForFile: ignoreForFile,
buildExtensions: buildExtensions,
);

if (optionsMap.isNotEmpty) {
Expand All @@ -41,6 +46,41 @@ Builder combiningBuilder([BuilderOptions options = BuilderOptions.empty]) {
return builder;
}

Map<String, List<String>> _validatedBuildExtensionsFrom(
Map<String, dynamic> optionsMap) {
final extensionsOption = optionsMap.remove('build_extensions');
if (extensionsOption == null) return _defaultExtensions;

if (extensionsOption is! Map) {
throw ArgumentError(
'Configured build_extensions should be a map from inputs to outputs.');
}

final result = <String, List<String>>{};

for (final entry in extensionsOption.entries) {
final input = entry.key;
if (input is! String || !input.endsWith('.dart')) {
throw ArgumentError('Invalid key in build_extensions option: `$input` '
'should be a string ending with `.dart`');
}

final output = entry.value;
if (output is! String || !output.endsWith('.dart')) {
throw ArgumentError('Invalid output extension `$output`. It should be a '
'string ending with `.dart`');
}

result[input] = [output];
}

if (result.isEmpty) {
throw ArgumentError('Configured build_extensions must not be empty.');
}

return result;
}

PostProcessBuilder partCleanup(BuilderOptions options) =>
const FileDeletingBuilder(['.g.part']);

Expand All @@ -53,9 +93,7 @@ class CombiningBuilder implements Builder {
final Set<String> _ignoreForFile;

@override
Map<String, List<String>> get buildExtensions => const {
'.dart': [_outputExtensions]
};
final Map<String, List<String>> buildExtensions;

/// Returns a new [CombiningBuilder].
///
Expand All @@ -65,6 +103,7 @@ class CombiningBuilder implements Builder {
const CombiningBuilder({
bool? includePartName,
Set<String>? ignoreForFile,
this.buildExtensions = _defaultExtensions,
}) : _includePartName = includePartName ?? false,
_ignoreForFile = ignoreForFile ?? const <String>{};

Expand Down Expand Up @@ -104,8 +143,20 @@ class CombiningBuilder implements Builder {
.where((s) => s.isNotEmpty)
.join('\n\n');
if (assets.isEmpty) return;

final inputLibrary = await buildStep.inputLibrary;
final partOf = nameOfPartial(inputLibrary, buildStep.inputId);
final outputId = buildStep.allowedOutputs.single;
final partOf = nameOfPartial(inputLibrary, buildStep.inputId, outputId);

// Ensure that the input has a correct `part` statement.
final libraryUnit =
await buildStep.resolver.compilationUnitFor(buildStep.inputId);
final part = computePartUrl(buildStep.inputId, outputId);
if (!hasExpectedPartDirective(libraryUnit, part)) {
log.warning('$part must be included as a part directive in '
'the input library with:\n part \'$part\';');
return;
}

final ignoreForFile = _ignoreForFile.isEmpty
? ''
Expand All @@ -117,7 +168,6 @@ part of $partOf;

$assets
''';
await buildStep.writeAsString(
buildStep.inputId.changeExtension(_outputExtensions), output);
await buildStep.writeAsString(outputId, output);
}
}
32 changes: 15 additions & 17 deletions source_gen/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,31 +105,29 @@ class _Builder extends Builder {

if (!_isLibraryBuilder) {
final asset = buildStep.inputId;
final name = nameOfPartial(library, asset);
final name = nameOfPartial(library, asset, outputId);
contentBuffer.writeln();

String part;
if (this is PartBuilder) {
contentBuffer
..write(languageOverrideForLibrary(library))
..writeln('part of $name;');
part = computePartUrl(buildStep.inputId, outputId);
final part = computePartUrl(buildStep.inputId, outputId);

final libraryUnit =
await buildStep.resolver.compilationUnitFor(buildStep.inputId);
final hasLibraryPartDirectiveWithOutputUri =
hasExpectedPartDirective(libraryUnit, part);
if (!hasLibraryPartDirectiveWithOutputUri) {
// TODO: Upgrade to error in a future breaking change?
log.warning('$part must be included as a part directive in '
'the input library with:\n part \'$part\';');
return;
}
} else {
assert(this is SharedPartBuilder);
final finalPartId = buildStep.inputId.changeExtension('.g.dart');
part = computePartUrl(buildStep.inputId, finalPartId);
}

final libraryUnit =
await buildStep.resolver.compilationUnitFor(buildStep.inputId);
final hasLibraryPartDirectiveWithOutputUri = libraryUnit.directives
.whereType<PartDirective>()
.any((e) => e.uri.stringValue == part);
if (!hasLibraryPartDirectiveWithOutputUri) {
// TODO: Upgrade to error in a future breaking change?
log.warning('$part must be included as a part directive in '
'the input library with:\n part \'$part\';');
return;
// For shared-part builders, `part` statements will be checked by the
// combining build step.
}
}

Expand Down
2 changes: 1 addition & 1 deletion source_gen/lib/src/constants/revive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import '../utils.dart';
/// build tool(s) using this library to surface error messages to the user.
Revivable reviveInstance(DartObject object, [LibraryElement? origin]) {
final objectType = object.type;
Element? element = objectType!.aliasElement;
Element? element = objectType!.alias?.element;
if (element == null) {
if (objectType is InterfaceType) {
element = objectType.element;
Expand Down
2 changes: 1 addition & 1 deletion source_gen/lib/src/type_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class UnresolvedAnnotationException implements Exception {
) {
try {
final parsedLibrary = annotatedElement.session!
.getParsedLibraryByElement2(annotatedElement.library!)
.getParsedLibraryByElement(annotatedElement.library!)
as ParsedLibraryResult;
final declaration = parsedLibrary.getElementDeclaration(annotatedElement);
if (declaration == null) {
Expand Down
16 changes: 12 additions & 4 deletions source_gen/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:io';

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
Expand All @@ -21,7 +22,7 @@ import 'package:yaml/yaml.dart';
///
/// This function will return `'VoidFunc'`, unlike [DartType.element.name].
String typeNameOf(DartType type) {
final aliasElement = type.aliasElement;
final aliasElement = type.alias?.element;
if (aliasElement != null) {
return aliasElement.name;
}
Expand All @@ -37,14 +38,21 @@ String typeNameOf(DartType type) {
throw UnimplementedError('(${type.runtimeType}) $type');
}

bool hasExpectedPartDirective(CompilationUnit unit, String part) =>
unit.directives
.whereType<PartDirective>()
.any((e) => e.uri.stringValue == part);

/// Returns a name suitable for `part of "..."` when pointing to [element].
String nameOfPartial(LibraryElement element, AssetId source) {
String nameOfPartial(LibraryElement element, AssetId source, AssetId output) {
if (element.name.isNotEmpty) {
return element.name;
}

final sourceUrl = p.basename(source.uri.toString());
return '\'$sourceUrl\'';
assert(source.package == output.package);
final relativeSourceUri =
p.url.relative(source.path, from: p.dirname(output.path));
return '\'$relativeSourceUri\'';
}

/// Returns a suggested library identifier based on [source] path and package.
Expand Down
6 changes: 3 additions & 3 deletions source_gen/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: source_gen
version: 1.0.5
version: 1.1.0
description: >-
Source code generation builders and utilities for the Dart build system
repository: https://github.com/dart-lang/source_gen
Expand All @@ -8,9 +8,9 @@ environment:
sdk: ">=2.12.0 <3.0.0"

dependencies:
analyzer: ^2.0.0
analyzer: ^2.1.0
async: ^2.5.0
build: ^2.0.0
build: ^2.1.0
dart_style: ^2.0.0
glob: ^2.0.0
meta: ^1.3.0
Expand Down
Loading