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

feat(ferry_generator): external schema import #606

Merged
merged 11 commits into from
Aug 4, 2024
Merged
30 changes: 19 additions & 11 deletions packages/ferry_generator/lib/graphql_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class GraphqlBuilder implements Builder {
@override
FutureOr<void> build(BuildStep buildStep) async {
SourceNode? schema;
AssetId? _schemaId;
late AssetId _schemaId;
final doc = await readDocument(buildStep, config.sourceExtension);
final docPackage = buildStep.inputId.package;
final docDirPath = p.dirname(buildStep.inputId.path);
Expand Down Expand Up @@ -82,16 +82,20 @@ class GraphqlBuilder implements Builder {
final triStateValueConfig = config.triStateOptionalsConfig;

final schemaOutputAsset =
outputAssetId(_schemaId!, schemaExtension, config.outputDir);
outputAssetId(_schemaId, schemaExtension, config.outputDir);

final schemaUrl = schemaOutputAsset.uri.toString();
final serializerOutputAsset =
AssetId(buildStep.inputId.package, schemaOutputAsset.path);
final serializerUrl = serializerOutputAsset.uri.toString();
final schemaAllocator = GqlAllocator(
buildStep.inputId.uri.toString(),
config.sourceExtension,
outputAssetId(buildStep.inputId, schemaExtension, config.outputDir)
.uri
.toString(),
schemaUrl,
serializerUrl,
config.outputDir,
);

Expand Down Expand Up @@ -123,15 +127,15 @@ class GraphqlBuilder implements Builder {
dataToVarsMode,
),
schemaExtension: buildSchemaLibrary(
doc,
p.basename(generatedFilePath(buildStep.inputId, schemaExtension)),
config.typeOverrides,
config.enumFallbackConfig,
generatePossibleTypesMap: config.shouldGeneratePossibleTypes,
allocator: schemaAllocator,
triStateValueConfig: triStateValueConfig,
generateVarsCreateFactories:
config.shouldGenerateVarsCreateFactories),
doc,
p.basename(generatedFilePath(buildStep.inputId, schemaExtension)),
config.typeOverrides,
config.enumFallbackConfig,
generatePossibleTypesMap: config.shouldGeneratePossibleTypes,
allocator: schemaAllocator,
triStateValueConfig: triStateValueConfig,
generateVarsCreateFactories: config.shouldGenerateVarsCreateFactories,
),
};

for (var entry in libs.entries) {
Expand All @@ -140,11 +144,15 @@ class GraphqlBuilder implements Builder {
final schemaOutputAsset =
outputAssetId(_schemaId, schemaExtension, config.outputDir);

final serialzerOutputAsset =
AssetId(buildStep.inputId.package, schemaOutputAsset.path);

final allocator = GqlAllocator(
buildStep.inputId.uri.toString(),
config.sourceExtension,
generatedAsset.uri.toString(),
schemaOutputAsset.uri.toString(),
serialzerOutputAsset.uri.toString(),
config.outputDir,
);

Expand Down
126 changes: 93 additions & 33 deletions packages/ferry_generator/lib/serializer_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,6 @@ class SerializerBuilder implements Builder {
final nonBuiltClasses =
SplayTreeSet<ClassElement>((a, b) => a.name.compareTo(b.name));

final hasSerializer = (ClassElement c) => c.fields.any((field) =>
field.isStatic &&
field.name == 'serializer' &&
field.type.element?.name == 'Serializer' &&
field.type.element?.source?.uri.toString() ==
'package:built_value/serializer.dart');

final isBuiltValue = (ClassElement c) => c.allSupertypes.any((interface) =>
(interface.element.name == 'Built' ||
interface.element.name == 'EnumClass') &&
interface.element.source.uri.toString() ==
'package:built_value/built_value.dart');

final excludeFileIds = <String, AssetId>{};
for (final excludeGlob in excludeFiles) {
await for (final fileAssetId in buildStep.findAssets(excludeGlob)) {
Expand All @@ -114,17 +101,9 @@ class SerializerBuilder implements Builder {
await for (final input in buildStep.findAssets(generatedFiles)) {
if (excludeFileIds.containsKey(input.path)) continue;
final lib = await buildStep.resolver.libraryFor(input);
lib.units
.expand((cu) => cu.classes)
.where((c) => hasSerializer(c) && isBuiltValue(c))
.forEach(builtClasses.add);

lib.units
.expand((cu) => cu.classes)
.where(
(c) => hasSerializer(c) && !isBuiltValue(c),
)
.forEach(nonBuiltClasses.add);
final classes = extractClassesToGenerateSerializersFor(lib);
builtClasses.addAll(classes.builtClasses);
nonBuiltClasses.addAll(classes.nonBuiltClasses);
}

final additionalSerializers = <Expression>{
Expand All @@ -141,26 +120,107 @@ class SerializerBuilder implements Builder {
),
};

// if the schema is defined in a different package
// we need to import the serializers from that package
// and add them to the serializers of this package
final isExternalSchema = schemaId.package != buildStep.inputId.package;

final externalSerializersExpression = isExternalSchema
? refer('serializers',
_externalSchemaSerializersImport(schemaId, config))
.property('serializers')
: null;

if (isExternalSchema) {
final externalSchemaId =
outputAssetId(schemaId, schemaExtension, config.outputDir);

final externalSchemaLibrary =
await buildStep.resolver.libraryFor(externalSchemaId);

final externalSchemaClasses =
extractClassesToGenerateSerializersFor(externalSchemaLibrary);

builtClasses.addAll(externalSchemaClasses.builtClasses);
nonBuiltClasses.addAll(externalSchemaClasses.nonBuiltClasses);
}

final library = buildSerializerLibrary(
builtClasses,
outputFileName.replaceFirst('.gql.dart', '.gql.g.dart'),
additionalSerializers,
externalSerializers: externalSerializersExpression,
);

final allocator = PickAllocator(
doNotPick: ['package:built_value/serializer.dart'],
include: [
'package:built_collection/built_collection.dart',
'package:ferry_exec/ferry_exec.dart',
...config.typeOverrides.values.map((ref) => ref.url).whereType<String>()
],
);
final allocator = PickAllocator(doNotPick: [
'package:built_value/serializer.dart',
], include: [
'package:built_collection/built_collection.dart',
'package:ferry_exec/ferry_exec.dart',
...config.typeOverrides.values.map((ref) => ref.url).whereType<String>(),
], aliasedImports: {
if (isExternalSchema)
_externalSchemaSerializersImport(schemaId, config):
'_\$external_serializers',
});

final outputId = AssetId(
schemaId.package,
buildStep.inputId.package,
p.joinAll(pathSegments(schemaId)),
);

await writeDocument(outputId, library, allocator, buildStep);
}
}

String _externalSchemaSerializersImport(
AssetId schemaId, BuilderConfig config) {
final outPutId = outputAssetId(schemaId, schemaExtension, config.outputDir);

final serializersPathSegments = outPutId.pathSegments
..removeAt(0)
..removeLast()
..add('serializers.gql.dart');

final outPutPath = p.joinAll(serializersPathSegments);

return 'package:${outPutId.package}/$outPutPath';
}

bool hasSerializer(ClassElement c) => c.fields.any((field) =>
field.isStatic &&
field.name == 'serializer' &&
field.type.element?.name == 'Serializer' &&
field.type.element?.source?.uri.toString() ==
'package:built_value/serializer.dart');

bool isBuiltValue(ClassElement c) => c.allSupertypes.any((interface) =>
(interface.element.name == 'Built' ||
interface.element.name == 'EnumClass') &&
interface.element.source.uri.toString() ==
'package:built_value/built_value.dart');

typedef ClassesToGenerateSerializersFor = ({
Set<ClassElement> builtClasses,
Set<ClassElement> nonBuiltClasses
});

ClassesToGenerateSerializersFor extractClassesToGenerateSerializersFor(
LibraryElement externalSchemaLibrary) {
final builtClasses = externalSchemaLibrary.units
.expand((cu) => cu.classes)
.where((c) => hasSerializer(c) && isBuiltValue(c))
.toSet();

final nonBuiltClasses = externalSchemaLibrary.units
.expand((cu) => cu.classes)
.where(
(c) => hasSerializer(c) && !isBuiltValue(c),
)
.toSet();

return (
builtClasses: builtClasses,
nonBuiltClasses: nonBuiltClasses,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class GqlAllocator implements Allocator {
final String sourceExtension;
final String currentUrl;
final String schemaUrl;
final String serializerUrl;
final String outputDir;

final _imports = <String, int?>{};
Expand All @@ -29,6 +30,7 @@ class GqlAllocator implements Allocator {
this.sourceExtension,
this.currentUrl,
this.schemaUrl,
this.serializerUrl,
this.outputDir,
);

Expand Down Expand Up @@ -69,7 +71,7 @@ class GqlAllocator implements Allocator {
if (uri.fragment == 'schema') {
replacedUrl = schemaUrl;
} else if (uri.fragment == 'serializer') {
replacedUrl = '${p.dirname(schemaUrl)}/serializers.gql.dart';
replacedUrl = '${p.dirname(serializerUrl)}/serializers.gql.dart';
} else {
replacedUrl = outputPath(sourceUrl, outputDir).replaceAll(
RegExp(r'.graphql$'),
Expand Down
13 changes: 11 additions & 2 deletions packages/ferry_generator/lib/src/allocators/pick_allocator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import 'package:code_builder/code_builder.dart';
class PickAllocator implements Allocator {
final List<String> doNotPick;
final List<String> include;
final Map<String, String> aliasedImports;

final Map<String, List<String>?> _imports = {};

PickAllocator({
this.doNotPick = const [],
this.include = const [],
this.aliasedImports = const {},
}) {
for (final url in include) {
_imports[url] = null;
Expand All @@ -30,6 +32,9 @@ class PickAllocator implements Allocator {
} else if (doNotPick.contains(url) || include.contains(url)) {
_imports.putIfAbsent(url, () => null);
return symbol;
} else if (aliasedImports.containsKey(url)) {
final alias = aliasedImports[url]!;
return '$alias.$symbol';
}

_imports.update(url, (symbols) => symbols?..add(symbol),
Expand All @@ -39,9 +44,13 @@ class PickAllocator implements Allocator {
}

@override
Iterable<Directive> get imports => _imports.entries.map(
Iterable<Directive> get imports => _imports.entries
.map(
(u) => u.value == null
? Directive.import(u.key)
: Directive.import(u.key, show: u.value!),
);
)
.followedBy(aliasedImports.entries.map(
(e) => Directive.import(e.key, as: e.value),
));
}
2 changes: 1 addition & 1 deletion packages/ferry_generator/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ topics:
- codegen
dependencies:
gql: '>=0.14.0 <2.0.0'
gql_code_builder: ^0.12.0
gql_code_builder: ^0.13.0
gql_code_builder_serializers: ^0.1.0
gql_tristate_value: ^1.0.0
built_collection: ^5.0.0
Expand Down
4 changes: 4 additions & 0 deletions packages/ferry_test_external_schema/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
pubspec.lock
3 changes: 3 additions & 0 deletions packages/ferry_test_external_schema/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0

- Initial version.
1 change: 1 addition & 0 deletions packages/ferry_test_external_schema/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:pedantic/analysis_options.yaml
24 changes: 24 additions & 0 deletions packages/ferry_test_external_schema/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
targets:
$default:
builders:
ferry_generator|graphql_builder:
enabled: true
options:
schema: ferry_test_graphql2|lib/schema/schema.graphql
tristate_optionals: false
vars_create_factories: false
data_to_json: type_safe
type_overrides:
Date:
name: DateTime

ferry_generator|serializer_builder:
enabled: true
options:
schema: ferry_test_graphql2|lib/schema/schema.graphql
type_overrides:
Date:
name: DateTime
custom_serializers:
- import: 'package:ferry_test_graphql2/date_serializer.dart'
name: DateSerializer

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading