Skip to content

Commit

Permalink
- cleaned up objc generation
Browse files Browse the repository at this point in the history
- added ability to generate mock handlers
- added pigeon.dart
  • Loading branch information
gaaclarke committed May 13, 2020
1 parent c41258e commit 9a3793c
Show file tree
Hide file tree
Showing 32 changed files with 416 additions and 42 deletions.
6 changes: 6 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.1.0

* Added pigeon.dart.
* Fixed some Obj-C linter problems.
* Added the ability to generate a mock handler in Dart.

## 0.1.0-experimental.11

* Fixed setting an api to null in Java.
Expand Down
4 changes: 2 additions & 2 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ doesn't need to worry about conflicting versions of Pigeon.

### Rules for defining your communication interface

1) The file should contain no methods or function definitions.
1) The file should contain no method or function definitions.
1) Datatypes are defined as classes with fields of the supported datatypes (see
the supported Datatypes section).
1) Api's should be defined as an `abstract class` with either `HostApi()` or
`FlutterApi()` as metadata. The former being for procedures that are defined
on the host platform and the latter for procedures that are defined in Dart.
1) Method declarations on the Api classes should have one argument and a return
value whose types are defined in the file.
value whose types are defined in the file or be `void`.

## Example

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true
2 changes: 1 addition & 1 deletion packages/pigeon/e2e_tests/test_objc/android/app/.classpath
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-10/"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
12 changes: 7 additions & 5 deletions packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
// Autogenerated from Pigeon (v0.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#import <Foundation/Foundation.h>
@protocol FlutterBinaryMessenger;
Expand Down Expand Up @@ -31,15 +31,17 @@ NS_ASSUME_NONNULL_BEGIN
- (void)search:(ACSearchRequest *)input completion:(void (^)(ACSearchReply *, NSError *))completion;
@end
@protocol ACNestedApi
- (ACSearchReply *)search:(ACNested *)input error:(FlutterError *_Nullable *_Nonnull)error;
- (nullable ACSearchReply *)search:(ACNested *)input error:(FlutterError *_Nullable *_Nonnull)error;
@end

extern void ACNestedApiSetup(id<FlutterBinaryMessenger> binaryMessenger, id<ACNestedApi> api);
extern void ACNestedApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
id<ACNestedApi> _Nullable api);

@protocol ACApi
- (ACSearchReply *)search:(ACSearchRequest *)input error:(FlutterError *_Nullable *_Nonnull)error;
- (nullable ACSearchReply *)search:(ACSearchRequest *)input
error:(FlutterError *_Nullable *_Nonnull)error;
@end

extern void ACApiSetup(id<FlutterBinaryMessenger> binaryMessenger, id<ACApi> api);
extern void ACApiSetup(id<FlutterBinaryMessenger> binaryMessenger, id<ACApi> _Nullable api);

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
// Autogenerated from Pigeon (v0.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#import "dartle.h"
#import <Flutter/Flutter.h>
Expand Down
19 changes: 18 additions & 1 deletion packages/pigeon/e2e_tests/test_objc/lib/dartle.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
// Autogenerated from Pigeon (v0.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
import 'dart:async';
Expand Down Expand Up @@ -128,3 +128,20 @@ class Api {
}
}
}

abstract class MockApi {
SearchReply search(SearchRequest arg);
}

void MockApiSetup(MockApi api) {
{
const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
'dev.flutter.pigeon.Api.search', StandardMessageCodec());
channel.setMockMessageHandler((dynamic message) async {
final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
final SearchRequest input = SearchRequest._fromMap(mapMessage);
final SearchReply output = api.search(input);
return <dynamic, dynamic>{'result': output._toMap()};
});
}
}
2 changes: 1 addition & 1 deletion packages/pigeon/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## message.dart

```dart
import 'package:pigeon/pigeon_lib.dart';
import 'package:pigeon/pigeon.dart';
class SearchRequest {
String query;
Expand Down
5 changes: 4 additions & 1 deletion packages/pigeon/lib/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Method extends Node {
/// Represents a collection of [Method]s that are hosted ona given [location].
class Api extends Node {
/// Parametric constructor for [Api].
Api({this.name, this.location, this.methods});
Api({this.name, this.location, this.methods, this.mockDartHandler});

/// The name of the API.
String name;
Expand All @@ -42,6 +42,9 @@ class Api extends Node {

/// List of methods inside the API.
List<Method> methods;

/// The name of the Dart mock interface to generate to help with testing.
String mockDartHandler;
}

/// Represents a field on a [Class].
Expand Down
30 changes: 26 additions & 4 deletions packages/pigeon/lib/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ if (replyMap == null) {
indent.writeln('');
}

void _writeFlutterApi(Indent indent, Api api) {
void _writeFlutterApi(Indent indent, Api api,
{String Function(Method) channelNameFunc, bool isMockHandler = false}) {
assert(api.location == ApiLocation.flutter);
indent.write('abstract class ${api.name} ');
indent.scoped('{', '}', () {
Expand All @@ -79,11 +80,16 @@ void _writeFlutterApi(Indent indent, Api api) {
indent.writeln('const BasicMessageChannel<dynamic> channel =');
indent.inc();
indent.inc();
final String channelName = channelNameFunc == null
? makeChannelName(api, func)
: channelNameFunc(func);
indent.writeln(
'BasicMessageChannel<dynamic>(\'${makeChannelName(api, func)}\', StandardMessageCodec());');
'BasicMessageChannel<dynamic>(\'$channelName\', StandardMessageCodec());');
indent.dec();
indent.dec();
indent.write('channel.setMessageHandler((dynamic message) async ');
final String messageHandlerSetter =
isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler';
indent.write('channel.$messageHandlerSetter((dynamic message) async ');
indent.scoped('{', '});', () {
final String argType = func.argType;
final String returnType = func.returnType;
Expand All @@ -99,9 +105,16 @@ void _writeFlutterApi(Indent indent, Api api) {
}
if (returnType == 'void') {
indent.writeln('$call;');
if (isMockHandler) {
indent.writeln('return <dynamic, dynamic>{};');
}
} else {
indent.writeln('final $returnType output = $call;');
indent.writeln('return output._toMap();');
const String returnExpresion = 'output._toMap()';
final String returnStatement = isMockHandler
? 'return <dynamic, dynamic>{\'${Keys.result}\': $returnExpresion};'
: 'return $returnExpresion;';
indent.writeln(returnStatement);
}
});
});
Expand Down Expand Up @@ -166,6 +179,15 @@ void generateDart(Root root, StringSink sink) {
for (Api api in root.apis) {
if (api.location == ApiLocation.host) {
_writeHostApi(indent, api);
if (api.mockDartHandler != null) {
final Api mockApi = Api(
name: api.mockDartHandler,
methods: api.methods,
location: ApiLocation.flutter);
_writeFlutterApi(indent, mockApi,
channelNameFunc: (Method func) => makeChannelName(api, func),
isMockHandler: true);
}
} else if (api.location == ApiLocation.flutter) {
_writeFlutterApi(indent, api);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/lib/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'dart:mirrors';
import 'ast.dart';

/// The current version of pigeon.
const String pigeonVersion = '0.1.0-experimental.11';
const String pigeonVersion = '0.1.0';

/// Read all the content from [stdin] to a String.
String readStdin() {
Expand Down
10 changes: 5 additions & 5 deletions packages/pigeon/lib/objc_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {

for (Class klass in root.classes) {
indent.writeln(
'@interface ${_className(options.prefix, klass.name)} : NSObject ');
'@interface ${_className(options.prefix, klass.name)} : NSObject');
for (Field field in klass.fields) {
final HostDatatype hostDatatype = getHostDatatype(
field, root.classes, _objcTypeForDartType,
Expand All @@ -115,20 +115,20 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
final String returnTypeName =
_className(options.prefix, func.returnType);
final String returnType =
func.returnType == 'void' ? 'void' : '$returnTypeName *';
func.returnType == 'void' ? 'void' : 'nullable $returnTypeName *';
if (func.argType == 'void') {
indent.writeln(
'-($returnType)${func.name}:(FlutterError * _Nullable * _Nonnull)error;');
'-($returnType)${func.name}:(FlutterError *_Nullable *_Nonnull)error;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'-($returnType)${func.name}:($argType*)input error:(FlutterError * _Nullable * _Nonnull)error;');
'-($returnType)${func.name}:($argType*)input error:(FlutterError *_Nullable *_Nonnull)error;');
}
}
indent.writeln('@end');
indent.writeln('');
indent.writeln(
'extern void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, id<$apiName> api);');
'extern void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, id<$apiName> _Nullable api);');
indent.writeln('');
} else if (api.location == ApiLocation.flutter) {
indent.writeln('@interface $apiName : NSObject');
Expand Down
1 change: 1 addition & 0 deletions packages/pigeon/lib/pigeon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'pigeon_lib.dart';
25 changes: 15 additions & 10 deletions packages/pigeon/lib/pigeon_lib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ const List<String> _validTypes = <String>[
/// Metadata to mark an API which will be implemented on the host platform.
class HostApi {
/// Parametric constructor for [HostApi].
const HostApi();
const HostApi({this.mockDartHandler});

/// The name of the interface to generate to receive Pigeon messages locally
/// for testing.
final String mockDartHandler;
}

/// Metadata to mark an API which will be implemented in Flutter.
Expand All @@ -56,16 +60,16 @@ class Error {

bool _isApi(ClassMirror classMirror) {
return classMirror.isAbstract &&
(_isHostApi(classMirror) || _isFlutterApi(classMirror));
(_getHostApi(classMirror) != null || _isFlutterApi(classMirror));
}

bool _isHostApi(ClassMirror apiMirror) {
HostApi _getHostApi(ClassMirror apiMirror) {
for (InstanceMirror instance in apiMirror.metadata) {
if (instance.reflectee is HostApi) {
return true;
return instance.reflectee;
}
}
return false;
return null;
}

bool _isFlutterApi(ClassMirror apiMirror) {
Expand Down Expand Up @@ -180,11 +184,12 @@ class Pigeon {
MirrorSystem.getName(declaration.returnType.simpleName));
}
}
root.apis.add(Api()
..name = MirrorSystem.getName(apiMirror.simpleName)
..location =
_isHostApi(apiMirror) ? ApiLocation.host : ApiLocation.flutter
..methods = functions);
final HostApi hostApi = _getHostApi(apiMirror);
root.apis.add(Api(
name: MirrorSystem.getName(apiMirror.simpleName),
location: hostApi != null ? ApiLocation.host : ApiLocation.flutter,
methods: functions,
mockDartHandler: hostApi?.mockDartHandler));
}

final List<Error> validateErrors = _validateAst(root);
Expand Down
44 changes: 44 additions & 0 deletions packages/pigeon/mock_handler_tester/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
10 changes: 10 additions & 0 deletions packages/pigeon/mock_handler_tester/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: e6b697a9df5e9ce933024be3334e86b599c60e71
channel: unknown

project_type: app
3 changes: 3 additions & 0 deletions packages/pigeon/mock_handler_tester/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# mock_handler_tester

A test bed for testing the code generated by `mockDartHandler`.
5 changes: 5 additions & 0 deletions packages/pigeon/mock_handler_tester/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:flutter/material.dart';

void main() {
runApp(Container());
}
19 changes: 19 additions & 0 deletions packages/pigeon/mock_handler_tester/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: mock_handler_tester
description: A testbed for testing mockDartHandler.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
sdk: ">=2.7.0 <3.0.0"

dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.3

dev_dependencies:
flutter_test:
sdk: flutter

flutter:
uses-material-design: true
Loading

0 comments on commit 9a3793c

Please sign in to comment.