[pigeon] Added basic C++ support (#476)
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 6785e20..09de8d6 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.1.0
+
+* [c++] Adds C++ code generator.
+
## 3.0.4
* [objc] Simplified some code output, including avoiding Xcode warnings about
@@ -35,8 +39,8 @@
## 2.0.2
* Fixes Java crash for nullable nested type.
-
-* ## 2.0.1
+
+## 2.0.1
* Adds support for TaskQueues for serial background execution.
diff --git a/packages/pigeon/CONTRIBUTING.md b/packages/pigeon/CONTRIBUTING.md
index 39266c0..2dc8e70 100644
--- a/packages/pigeon/CONTRIBUTING.md
+++ b/packages/pigeon/CONTRIBUTING.md
@@ -23,6 +23,7 @@
* [java_generator.dart](./lib/java_generator.dart) - The Java code generator.
* [objc_generator.dart](./lib/objc_generator.dart) - The Objective-C code
generator (header and source files).
+* [cpp_generator.dart](./lib/cpp_generator.dart) - The C++ code generator.
* [generator_tools.dart](./lib/generator_tools.dart) - Shared code between generators.
* [pigeon_cl.dart](./lib/pigeon_cl.dart) - The top-level function executed by
the command line tool in [bin/][./bin].
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index 149e2c1..1be5647 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -5,8 +5,9 @@
## Supported Platforms
-Currently Pigeon only supports generating Objective-C code for usage on iOS and
-Java code for Android. The Objective-C code is
+Currently Pigeon supports generating Objective-C code for usage on iOS, Java
+code for Android, and has experimental support for C++ for Windows. The
+Objective-C code is
[accessible to Swift](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift)
and the Java code is accessible to Kotlin.
@@ -18,32 +19,39 @@
## Usage
-### Flutter calling into iOS Steps
+1) Add Pigeon as a `dev_dependency`.
+1) Make a ".dart" file outside of your "lib" directory for defining the
+ communication interface.
+1) Run pigeon on your ".dart" file to generate the required Dart and
+ host-language code: `flutter pub get` then `flutter pub run pigeon`
+ with suitable arguments (see [example](./example)).
+1) Add the generated Dart code to `./lib` for compilation.
+1) Implement the host-language code and add it to your build (see below).
+1) Call the generated Dart methods.
-1) Add Pigeon as a dev_dependency.
-1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
-1) Run pigeon on your ".dart" file to generate the required Dart and Objective-C
- code: `flutter pub get` then `flutter pub run pigeon` with suitable arguments
- (see [example](./example)).
-1) Add the generated Dart code to `lib` for compilation.
+### Flutter calling into iOS steps
+
1) Add the generated Objective-C code to your Xcode project for compilation
(e.g. `ios/Runner.xcworkspace` or `.podspec`).
1) Implement the generated iOS protocol for handling the calls on iOS, set it up
as the handler for the messages.
-1) Call the generated Dart methods.
### Flutter calling into Android Steps
-1) Add Pigeon as a dev_dependency.
-1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
-1) Run pigeon on your ".dart" file to generate the required Dart and Java code.
- `flutter pub get` then `flutter pub run pigeon` with suitable arguments
- (see [example](./example)).
-1) Add the generated Dart code to `./lib` for compilation.
-1) Add the generated Java code to your `./android/app/src/main/java` directory for compilation.
-1) Implement the generated Java interface for handling the calls on Android, set it up
- as the handler for the messages.
-1) Call the generated Dart methods.
+1) Add the generated Java code to your `./android/app/src/main/java` directory
+ for compilation.
+1) Implement the generated Java interface for handling the calls on Android, set
+ it up as the handler for the messages.
+
+### Flutter calling into Windows Steps
+
+1) Add the generated C++ code to your `./windows` directory for compilation, and
+ to your `windows/CMakeLists.txt` file.
+1) Implement the generated C++ abstract class for handling the calls on Windows,
+ set it up as the handler for the messages.
+
+**Note:** Windows C++ is experimental while we get more usage and add more
+testing. Not all features may be supported.
### Calling into Flutter from the host platform
@@ -116,6 +124,16 @@
}
```
+```c++
+// C++
+
+/** Generated class from Pigeon that represents a handler of messages from Flutter.*/
+class Api2Host {
+public:
+ virtual void calculate(Value value, flutter::MessageReply<Value> result) = 0;
+}
+```
+
### Null Safety (NNBD)
Pigeon supports generating null-safe code, but it doesn't yet support:
diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart
new file mode 100644
index 0000000..2e59fe9
--- /dev/null
+++ b/packages/pigeon/lib/cpp_generator.dart
@@ -0,0 +1,862 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:pigeon/functional.dart';
+
+import 'ast.dart';
+import 'generator_tools.dart';
+import 'pigeon_lib.dart' show Error;
+
+/// Options that control how C++ code will be generated.
+class CppOptions {
+ /// Creates a [CppOptions] object
+ const CppOptions({
+ this.header,
+ this.namespace,
+ this.copyrightHeader,
+ });
+
+ /// The path to the header that will get placed in the source filed (example:
+ /// "foo.h").
+ final String? header;
+
+ /// The namespace where the generated class will live.
+ final String? namespace;
+
+ /// A copyright header that will get prepended to generated code.
+ final Iterable<String>? copyrightHeader;
+
+ /// Creates a [CppOptions] from a Map representation where:
+ /// `x = CppOptions.fromMap(x.toMap())`.
+ static CppOptions fromMap(Map<String, Object> map) {
+ return CppOptions(
+ header: map['header'] as String?,
+ namespace: map['namespace'] as String?,
+ copyrightHeader: map['copyrightHeader'] as Iterable<String>?,
+ );
+ }
+
+ /// Converts a [CppOptions] to a Map representation where:
+ /// `x = CppOptions.fromMap(x.toMap())`.
+ Map<String, Object> toMap() {
+ final Map<String, Object> result = <String, Object>{
+ if (header != null) 'header': header!,
+ if (namespace != null) 'namespace': namespace!,
+ if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!,
+ };
+ return result;
+ }
+
+ /// Overrides any non-null parameters from [options] into this to make a new
+ /// [CppOptions].
+ CppOptions merge(CppOptions options) {
+ return CppOptions.fromMap(mergeMaps(toMap(), options.toMap()));
+ }
+}
+
+String _getCodecName(Api api) => '${api.name}CodecSerializer';
+
+String _pointerPrefix = 'pointer';
+String _encodablePrefix = 'encodable';
+
+void _writeCodecHeader(Indent indent, Api api, Root root) {
+ final String codecName = _getCodecName(api);
+ indent.write('class $codecName : public flutter::StandardCodecSerializer ');
+ indent.scoped('{', '};', () {
+ indent.scoped(' public:', '', () {
+ indent.writeln('');
+ indent.format('''
+inline static $codecName& GetInstance() {
+\tstatic $codecName sInstance;
+\treturn sInstance;
+}
+''');
+ indent.writeln('$codecName();');
+ });
+ if (getCodecClasses(api, root).isNotEmpty) {
+ indent.writeScoped(' public:', '', () {
+ indent.writeln(
+ 'void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override;');
+ });
+ indent.writeScoped(' protected:', '', () {
+ indent.writeln(
+ 'flutter::EncodableValue ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const override;');
+ });
+ }
+ }, nestCount: 0);
+}
+
+void _writeCodecSource(Indent indent, Api api, Root root) {
+ final String codecName = _getCodecName(api);
+ indent.writeln('$codecName::$codecName() {}');
+ if (getCodecClasses(api, root).isNotEmpty) {
+ indent.write(
+ 'flutter::EncodableValue $codecName::ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const');
+ indent.scoped('{', '}', () {
+ indent.write('switch (type) ');
+ indent.scoped('{', '}', () {
+ for (final EnumeratedClass customClass in getCodecClasses(api, root)) {
+ indent.write('case ${customClass.enumeration}: ');
+ indent.writeScoped('', '', () {
+ indent.writeln(
+ 'return flutter::CustomEncodableValue(${customClass.name}(std::get<flutter::EncodableMap>(ReadValue(stream))));');
+ });
+ }
+ indent.write('default:');
+ indent.writeScoped('', '', () {
+ indent.writeln(
+ 'return flutter::StandardCodecSerializer::ReadValueOfType(type, stream);');
+ });
+ });
+ });
+ indent.writeln('');
+ indent.write(
+ 'void $codecName::WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const');
+ indent.writeScoped('{', '}', () {
+ indent.write(
+ 'if (const flutter::CustomEncodableValue* custom_value = std::get_if<flutter::CustomEncodableValue>(&value))');
+ indent.scoped('{', '}', () {
+ for (final EnumeratedClass customClass in getCodecClasses(api, root)) {
+ indent.write(
+ 'if (custom_value->type() == typeid(${customClass.name}))');
+ indent.scoped('{', '}', () {
+ indent.writeln('stream->WriteByte(${customClass.enumeration});');
+ indent.writeln(
+ 'WriteValue(std::any_cast<${customClass.name}>(*custom_value).ToEncodableMap(), stream);');
+ indent.writeln('return;');
+ });
+ }
+ });
+ indent.writeln(
+ 'flutter::StandardCodecSerializer::WriteValue(value, stream);');
+ });
+ }
+}
+
+void _writeErrorOr(Indent indent) {
+ indent.format('''
+class FlutterError {
+ public:
+\tFlutterError();
+\tFlutterError(const std::string& arg_code)
+\t\t: code(arg_code) {};
+\tFlutterError(const std::string& arg_code, const std::string& arg_message)
+\t\t: code(arg_code), message(arg_message) {};
+\tFlutterError(const std::string& arg_code, const std::string& arg_message, const flutter::EncodableValue& arg_details)
+\t\t: code(arg_code), message(arg_message), details(arg_details) {};
+\tstd::string code;
+\tstd::string message;
+\tflutter::EncodableValue details;
+};
+template<class T> class ErrorOr {
+\tstd::variant<std::unique_ptr<T>, T, FlutterError> v;
+ public:
+\tErrorOr(const T& rhs) { new(&v) T(rhs); }
+\tErrorOr(const FlutterError& rhs) {
+\t\tnew(&v) FlutterError(rhs);
+\t}
+\tstatic ErrorOr<std::unique_ptr<T>> MakeWithUniquePtr(std::unique_ptr<T> rhs) {
+\t\tErrorOr<std::unique_ptr<T>> ret = ErrorOr<std::unique_ptr<T>>();
+\t\tret.v = std::move(rhs);
+\t\treturn ret;
+\t}
+\tbool hasError() const { return std::holds_alternative<FlutterError>(v); }
+\tconst T& value() const { return std::get<T>(v); };
+\tconst FlutterError& error() const { return std::get<FlutterError>(v); };
+ private:
+\tErrorOr() = default;
+\tfriend class ErrorOr;
+};
+''');
+}
+
+void _writeHostApiHeader(Indent indent, Api api) {
+ assert(api.location == ApiLocation.host);
+
+ indent.writeln(
+ '/* Generated class from Pigeon that represents a handler of messages from Flutter. */');
+ indent.write('class ${api.name} ');
+ indent.scoped('{', '};', () {
+ indent.scoped(' public:', '', () {
+ indent.writeln('${api.name}(const ${api.name}&) = delete;');
+ indent.writeln('${api.name}& operator=(const ${api.name}&) = delete;');
+ indent.writeln('virtual ~${api.name}() { };');
+ for (final Method method in api.methods) {
+ final String returnTypeName = method.returnType.isVoid
+ ? 'std::optional<FlutterError>'
+ : 'ErrorOr<${_nullSafeCppTypeForDartType(method.returnType, considerReference: false)}>';
+
+ final List<String> argSignature = <String>[];
+ if (method.arguments.isNotEmpty) {
+ final Iterable<String> argTypes = method.arguments
+ .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+ final Iterable<String> argNames =
+ method.arguments.map((NamedType e) => e.name);
+ argSignature.addAll(
+ map2(argTypes, argNames, (String argType, String argName) {
+ return '$argType $argName';
+ }));
+ }
+ if (method.isAsynchronous) {
+ argSignature.add('std::function<void($returnTypeName reply)> result');
+ indent.writeln(
+ 'virtual void ${method.name}(${argSignature.join(', ')}) = 0;');
+ } else {
+ indent.writeln(
+ 'virtual $returnTypeName ${method.name}(${argSignature.join(', ')}) = 0;');
+ }
+ }
+ indent.addln('');
+ indent.writeln('/** The codec used by ${api.name}. */');
+ indent.writeln('static const flutter::StandardMessageCodec& GetCodec();');
+ indent.writeln(
+ '/** Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`. */');
+ indent.writeln(
+ 'static void SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api);');
+ indent.writeln(
+ 'static flutter::EncodableMap WrapError(std::string_view errorMessage);');
+ indent.writeln(
+ 'static flutter::EncodableMap WrapError(const FlutterError& error);');
+ });
+ indent.scoped(' protected:', '', () {
+ indent.writeln('${api.name}() = default;');
+ });
+ }, nestCount: 0);
+}
+
+void _writeHostApiSource(Indent indent, Api api) {
+ assert(api.location == ApiLocation.host);
+
+ final String codecName = _getCodecName(api);
+ indent.format('''
+/** The codec used by ${api.name}. */
+const flutter::StandardMessageCodec& ${api.name}::GetCodec() {
+\treturn flutter::StandardMessageCodec::GetInstance(&$codecName::GetInstance());
+}
+''');
+ indent.writeln(
+ '/** Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`. */');
+ indent.write(
+ 'void ${api.name}::SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api) ');
+ indent.scoped('{', '}', () {
+ for (final Method method in api.methods) {
+ final String channelName = makeChannelName(api, method);
+ indent.write('');
+ indent.scoped('{', '}', () {
+ indent.writeln(
+ 'auto channel = std::make_unique<flutter::BasicMessageChannel<flutter::EncodableValue>>(');
+ indent.inc();
+ indent.inc();
+ indent.writeln('binary_messenger, "$channelName", &GetCodec());');
+ indent.dec();
+ indent.dec();
+ indent.write('if (api != nullptr) ');
+ indent.scoped('{', '} else {', () {
+ indent.write(
+ 'channel->SetMessageHandler([api](const flutter::EncodableValue& message, const flutter::MessageReply<flutter::EncodableValue>& reply)');
+ indent.scoped('{', '});', () {
+ final String returnTypeName = method.returnType.isVoid
+ ? 'std::optional<FlutterError>'
+ : 'ErrorOr<${_nullSafeCppTypeForDartType(method.returnType, considerReference: false)}>';
+ indent.writeln('flutter::EncodableMap wrapped;');
+ indent.write('try ');
+ indent.scoped('{', '}', () {
+ final List<String> methodArgument = <String>[];
+ if (method.arguments.isNotEmpty) {
+ indent.writeln(
+ 'auto args = std::get<flutter::EncodableList>(message);');
+ enumerate(method.arguments, (int index, NamedType arg) {
+ final String argType = _nullSafeCppTypeForDartType(arg.type);
+ final String argName = _getSafeArgumentName(index, arg);
+
+ final String encodableArgName =
+ '${_encodablePrefix}_$argName';
+ indent.writeln('auto $encodableArgName = args.at($index);');
+ if (!arg.type.isNullable) {
+ indent.write('if ($encodableArgName.IsNull()) ');
+ indent.scoped('{', '}', () {
+ indent.writeln(
+ 'wrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError("$argName unexpectedly null.")));');
+ indent.writeln('reply(wrapped);');
+ indent.writeln('return;');
+ });
+ }
+ indent.writeln(
+ '$argType $argName = std::any_cast<$argType>(std::get<flutter::CustomEncodableValue>($encodableArgName));');
+ methodArgument.add(argName);
+ });
+ }
+
+ String _wrapResponse(String reply, TypeDeclaration returnType) {
+ final bool isReferenceReturnType =
+ _isReferenceType(_cppTypeForDartType(method.returnType));
+ String elseBody = '';
+ final String ifCondition;
+ final String errorGetter;
+ final String prefix = (reply != '') ? '\t' : '';
+ if (returnType.isVoid) {
+ elseBody =
+ '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::EncodableValue()));${indent.newline}';
+ ifCondition = 'output.has_value()';
+ errorGetter = 'value';
+ } else {
+ if (isReferenceReturnType && !returnType.isNullable) {
+ elseBody = '''
+$prefix\tif (!output.value()) {
+$prefix\t\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError("output is unexpectedly null.")));
+$prefix\t} else {
+$prefix\t\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::CustomEncodableValue(*output.value().get())));
+$prefix\t}${indent.newline}''';
+ } else {
+ elseBody =
+ '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::CustomEncodableValue(output.value())));${indent.newline}';
+ }
+ ifCondition = 'output.hasError()';
+ errorGetter = 'error';
+ }
+ return '${prefix}if ($ifCondition) {${indent.newline}'
+ '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError(output.$errorGetter())));${indent.newline}'
+ '$prefix$reply'
+ '$prefix} else {${indent.newline}'
+ '$elseBody'
+ '$prefix$reply'
+ '$prefix}';
+ }
+
+ if (method.isAsynchronous) {
+ methodArgument.add(
+ '[&wrapped, &reply]($returnTypeName output) {${indent.newline}'
+ '${_wrapResponse('\treply(wrapped);${indent.newline}', method.returnType)}'
+ '}',
+ );
+ }
+ final String call =
+ 'api->${method.name}(${methodArgument.join(', ')})';
+ if (method.isAsynchronous) {
+ indent.format('$call;');
+ } else {
+ indent.writeln('$returnTypeName output = $call;');
+ indent.format(_wrapResponse('', method.returnType));
+ }
+ });
+ indent.write('catch (const std::exception& exception) ');
+ indent.scoped('{', '}', () {
+ indent.writeln(
+ 'wrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError(exception.what())));');
+ if (method.isAsynchronous) {
+ indent.writeln('reply(wrapped);');
+ }
+ });
+ if (!method.isAsynchronous) {
+ indent.writeln('reply(wrapped);');
+ }
+ });
+ });
+ indent.scoped(null, '}', () {
+ indent.writeln('channel->SetMessageHandler(nullptr);');
+ });
+ });
+ }
+ });
+}
+
+String _getArgumentName(int count, NamedType argument) =>
+ argument.name.isEmpty ? 'arg$count' : argument.name;
+
+/// Returns an argument name that can be used in a context where it is possible to collide.
+String _getSafeArgumentName(int count, NamedType argument) =>
+ _getArgumentName(count, argument) + '_arg';
+
+void _writeFlutterApiHeader(Indent indent, Api api) {
+ assert(api.location == ApiLocation.flutter);
+ indent.writeln(
+ '/* Generated class from Pigeon that represents Flutter messages that can be called from C++. */');
+ indent.write('class ${api.name} ');
+ indent.scoped('{', '};', () {
+ indent.scoped(' private:', '', () {
+ indent.writeln('flutter::BinaryMessenger* binary_messenger_;');
+ });
+ indent.scoped(' public:', '', () {
+ indent.write('${api.name}(flutter::BinaryMessenger* binary_messenger);');
+ indent.writeln('');
+ indent.writeln('static const flutter::StandardMessageCodec& GetCodec();');
+ for (final Method func in api.methods) {
+ final String returnType = func.returnType.isVoid
+ ? 'void'
+ : _nullSafeCppTypeForDartType(func.returnType);
+ final String callback = 'std::function<void($returnType)>&& callback';
+ if (func.arguments.isEmpty) {
+ indent.writeln('void ${func.name}($callback);');
+ } else {
+ final Iterable<String> argTypes = func.arguments
+ .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+ final Iterable<String> argNames =
+ indexMap(func.arguments, _getSafeArgumentName);
+ final String argsSignature =
+ map2(argTypes, argNames, (String x, String y) => '$x $y')
+ .join(', ');
+ indent.writeln('void ${func.name}($argsSignature, $callback);');
+ }
+ }
+ });
+ }, nestCount: 0);
+}
+
+void _writeFlutterApiSource(Indent indent, Api api) {
+ assert(api.location == ApiLocation.flutter);
+ indent.writeln(
+ '/* Generated class from Pigeon that represents Flutter messages that can be called from C++. */');
+ indent.write(
+ '${api.name}::${api.name}(flutter::BinaryMessenger* binary_messenger)');
+ indent.scoped('{', '}', () {
+ indent.writeln('this->binary_messenger_ = binary_messenger;');
+ });
+ indent.writeln('');
+ final String codecName = _getCodecName(api);
+ indent.format('''
+const flutter::StandardMessageCodec& ${api.name}::GetCodec() {
+\treturn flutter::StandardMessageCodec::GetInstance(&$codecName::GetInstance());
+}
+''');
+ for (final Method func in api.methods) {
+ final String channelName = makeChannelName(api, func);
+ final String returnType = func.returnType.isVoid
+ ? 'void'
+ : _nullSafeCppTypeForDartType(func.returnType);
+ String sendArgument;
+ final String callback = 'std::function<void($returnType)>&& callback';
+ if (func.arguments.isEmpty) {
+ indent.write('void ${api.name}::${func.name}($callback) ');
+ sendArgument = 'flutter::EncodableValue()';
+ } else {
+ final Iterable<String> argTypes = func.arguments
+ .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+ final Iterable<String> argNames =
+ indexMap(func.arguments, _getSafeArgumentName);
+ sendArgument =
+ 'flutter::EncodableList { ${(argNames.map((String arg) => 'flutter::CustomEncodableValue($arg)')).join(', ')} }';
+ final String argsSignature =
+ map2(argTypes, argNames, (String x, String y) => '$x $y').join(', ');
+ indent
+ .write('void ${api.name}::${func.name}($argsSignature, $callback) ');
+ }
+ indent.scoped('{', '}', () {
+ const String channel = 'channel';
+ indent.writeln(
+ 'auto channel = std::make_unique<flutter::BasicMessageChannel<flutter::EncodableValue>>(');
+ indent.inc();
+ indent.inc();
+ indent.writeln('binary_messenger_, "$channelName", &GetCodec());');
+ indent.dec();
+ indent.dec();
+ indent.write(
+ '$channel->Send($sendArgument, [callback](const uint8_t* reply, size_t reply_size)');
+ indent.scoped('{', '});', () {
+ if (func.returnType.isVoid) {
+ indent.writeln('callback();');
+ } else {
+ indent.writeln(
+ 'std::unique_ptr<flutter::EncodableValue> decoded_reply = GetCodec().DecodeMessage(reply, reply_size);');
+ indent.writeln(
+ 'flutter::EncodableValue args = *(flutter::EncodableValue*)(decoded_reply.release());');
+ const String output = 'output';
+
+ final bool isBuiltin =
+ _cppTypeForBuiltinDartType(func.returnType) != null;
+ final String returnTypeName = _cppTypeForDartType(func.returnType);
+ if (func.returnType.isNullable) {
+ indent.writeln('$returnType $output{};');
+ } else {
+ indent.writeln('$returnTypeName $output{};');
+ }
+ final String pointerVariable = '${_pointerPrefix}_$output';
+ if (func.returnType.baseName == 'int') {
+ indent.format('''
+if (const int32_t* $pointerVariable = std::get_if<int32_t>(&args))
+\t$output = *$pointerVariable;
+else if (const int64_t* ${pointerVariable}_64 = std::get_if<int64_t>(&args))
+\t$output = *${pointerVariable}_64;''');
+ } else if (!isBuiltin) {
+ indent.write(
+ 'if (const flutter::EncodableMap* $pointerVariable = std::get_if<flutter::EncodableMap>(&args))');
+ indent.scoped('{', '}', () {
+ indent.writeln('$output = $returnTypeName(*$pointerVariable);');
+ });
+ } else {
+ indent.write(
+ 'if (const $returnTypeName* $pointerVariable = std::get_if<$returnTypeName>(&args))');
+ indent.scoped('{', '}', () {
+ indent.writeln('$output = *$pointerVariable;');
+ });
+ }
+
+ indent.writeln('callback($output);');
+ }
+ });
+ });
+ }
+}
+
+String _makeGetterName(NamedType field) {
+ final String uppercased =
+ field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
+ return 'get$uppercased';
+}
+
+String _makeSetterName(NamedType field) {
+ final String uppercased =
+ field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
+ return 'set$uppercased';
+}
+
+bool _isReferenceType(String dataType) {
+ switch (dataType) {
+ case 'bool':
+ case 'int64_t':
+ case 'double':
+ return false;
+ default:
+ return true;
+ }
+}
+
+String? _cppTypeForBuiltinDartType(TypeDeclaration type) {
+ const Map<String, String> cppTypeForDartTypeMap = <String, String>{
+ 'bool': 'bool',
+ 'int': 'int64_t',
+ 'String': 'std::string',
+ 'double': 'double',
+ 'Uint8List': 'std::vector<uint8_t>',
+ 'Int32List': 'std::vector<int32_t>',
+ 'Int64List': 'std::vector<int64_t>',
+ 'Float64List': 'std::vector<double>',
+ 'Map': 'flutter::EncodableMap',
+ 'List': 'flutter::EncodableList',
+ };
+ if (cppTypeForDartTypeMap.containsKey(type.baseName)) {
+ return cppTypeForDartTypeMap[type.baseName];
+ } else {
+ return null;
+ }
+}
+
+String _cppTypeForDartType(TypeDeclaration type) {
+ return _cppTypeForBuiltinDartType(type) ?? type.baseName;
+}
+
+String _nullSafeCppTypeForDartType(TypeDeclaration type,
+ {bool considerReference = true}) {
+ if (type.isNullable) {
+ return 'std::optional<${_cppTypeForDartType(type)}>';
+ } else {
+ String typeName = _cppTypeForDartType(type);
+ if (_isReferenceType(typeName)) {
+ if (considerReference)
+ typeName = 'const $typeName&';
+ else
+ typeName = 'std::unique_ptr<$typeName>';
+ }
+ return typeName;
+ }
+}
+
+String _getGuardName(String? headerFileName, String? namespace) {
+ String guardName = 'PIGEON_';
+ if (headerFileName != null) {
+ guardName += '${headerFileName.replaceAll('.', '_').toUpperCase()}_';
+ }
+ if (namespace != null) {
+ guardName += '${namespace.toUpperCase()}_';
+ }
+ return guardName + 'H_';
+}
+
+/// Generates the ".h" file for the AST represented by [root] to [sink] with the
+/// provided [options] and [headerFileName].
+void generateCppHeader(
+ String? headerFileName, CppOptions options, Root root, StringSink sink) {
+ final Indent indent = Indent(sink);
+ if (options.copyrightHeader != null) {
+ addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+ }
+ indent.writeln('// $generatedCodeWarning');
+ indent.writeln('// $seeAlsoWarning');
+ indent.addln('');
+ final String guardName = _getGuardName(headerFileName, options.namespace);
+ indent.writeln('#ifndef $guardName');
+ indent.writeln('#define $guardName');
+ indent.writeln('#include <flutter/encodable_value.h>');
+ indent.writeln('#include <flutter/basic_message_channel.h>');
+ indent.writeln('#include <flutter/binary_messenger.h>');
+ indent.writeln('#include <flutter/standard_message_codec.h>');
+ indent.addln('');
+ indent.writeln('#include <map>');
+ indent.writeln('#include <string>');
+ indent.writeln('#include <optional>');
+
+ indent.addln('');
+
+ if (options.namespace != null) {
+ indent.writeln('namespace ${options.namespace} {');
+ }
+
+ indent.addln('');
+ indent.writeln('/* Generated class from Pigeon. */');
+
+ for (final Enum anEnum in root.enums) {
+ indent.writeln('');
+ indent.write('enum class ${anEnum.name} ');
+ indent.scoped('{', '};', () {
+ int index = 0;
+ for (final String member in anEnum.members) {
+ indent.writeln(
+ '$member = $index${index == anEnum.members.length - 1 ? '' : ','}');
+ index++;
+ }
+ });
+ }
+
+ indent.addln('');
+
+ _writeErrorOr(indent);
+
+ for (final Class klass in root.classes) {
+ indent.addln('');
+ indent.writeln(
+ '/* Generated class from Pigeon that represents data sent in messages. */');
+ indent.write('class ${klass.name} ');
+ indent.scoped('{', '};', () {
+ indent.scoped(' public:', '', () {
+ indent.writeln('${klass.name}();');
+ for (final NamedType field in klass.fields) {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+ if (_isReferenceType(hostDatatype.datatype)) {
+ indent.writeln(
+ 'const ${hostDatatype.datatype}& ${_makeGetterName(field)}() const;');
+ indent.writeln(
+ 'void ${_makeSetterName(field)}(const ${hostDatatype.datatype}& setterArg);');
+ } else {
+ indent.writeln(
+ '${hostDatatype.datatype} ${_makeGetterName(field)}() const;');
+ indent.writeln(
+ 'void ${_makeSetterName(field)}(const ${hostDatatype.datatype} setterArg);');
+ }
+ indent.addln('');
+ }
+ });
+
+ indent.scoped(' private:', '', () {
+ indent.writeln('${klass.name}(flutter::EncodableMap map);');
+ indent.writeln('flutter::EncodableMap ToEncodableMap();');
+ for (final Class friend in root.classes) {
+ if (friend != klass &&
+ friend.fields.any(
+ (NamedType element) => element.type.baseName == klass.name)) {
+ indent.writeln('friend class ${friend.name};');
+ }
+ }
+ for (final Api api in root.apis) {
+ // TODO(gaaclarke): Find a way to be more precise with our
+ // friendships.
+ indent.writeln('friend class ${api.name};');
+ indent.writeln('friend class ${_getCodecName(api)};');
+ }
+
+ for (final NamedType field in klass.fields) {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+ indent.writeln('${hostDatatype.datatype} ${field.name}_;');
+ }
+ });
+ }, nestCount: 0);
+ indent.writeln('');
+ }
+
+ for (final Api api in root.apis) {
+ _writeCodecHeader(indent, api, root);
+ indent.addln('');
+ if (api.location == ApiLocation.host) {
+ _writeHostApiHeader(indent, api);
+ } else if (api.location == ApiLocation.flutter) {
+ _writeFlutterApiHeader(indent, api);
+ }
+ }
+
+ if (options.namespace != null) {
+ indent.writeln('} // namespace');
+ }
+
+ indent.writeln('#endif // $guardName');
+}
+
+/// Generates the ".cpp" file for the AST represented by [root] to [sink] with the
+/// provided [options].
+void generateCppSource(CppOptions options, Root root, StringSink sink) {
+ final Set<String> rootClassNameSet =
+ root.classes.map((Class x) => x.name).toSet();
+ final Set<String> rootEnumNameSet =
+ root.enums.map((Enum x) => x.name).toSet();
+ final Indent indent = Indent(sink);
+ if (options.copyrightHeader != null) {
+ addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+ }
+ indent.writeln('// $generatedCodeWarning');
+ indent.writeln('// $seeAlsoWarning');
+ indent.addln('');
+ indent.addln('#undef _HAS_EXCEPTIONS');
+ indent.addln('');
+ indent.writeln('#include <flutter/basic_message_channel.h>');
+ indent.writeln('#include <flutter/binary_messenger.h>');
+ indent.writeln('#include <flutter/standard_message_codec.h>');
+ indent.writeln('#include <map>');
+ indent.writeln('#include <string>');
+ indent.writeln('#include <optional>');
+
+ indent.writeln('#include "${options.header}"');
+
+ indent.addln('');
+
+ indent.addln('');
+
+ if (options.namespace != null) {
+ indent.writeln('namespace ${options.namespace} {');
+ }
+
+ indent.addln('');
+ indent.writeln('/* Generated class from Pigeon. */');
+
+ for (final Class klass in root.classes) {
+ indent.addln('');
+ indent.writeln('/* ${klass.name} */');
+ indent.addln('');
+ for (final NamedType field in klass.fields) {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+ if (_isReferenceType(hostDatatype.datatype)) {
+ indent.writeln(
+ 'const ${hostDatatype.datatype}& ${klass.name}::${_makeGetterName(field)}() const { return ${field.name}_; }');
+ indent.writeln(
+ 'void ${klass.name}::${_makeSetterName(field)}(const ${hostDatatype.datatype}& setterArg) { this->${field.name}_ = setterArg; }');
+ } else {
+ indent.writeln(
+ '${hostDatatype.datatype} ${klass.name}::${_makeGetterName(field)}() const { return ${field.name}_; }');
+ indent.writeln(
+ 'void ${klass.name}::${_makeSetterName(field)}(const ${hostDatatype.datatype} setterArg) { this->${field.name}_ = setterArg; }');
+ }
+
+ indent.addln('');
+ }
+ indent.write('flutter::EncodableMap ${klass.name}::ToEncodableMap() ');
+ indent.scoped('{', '}', () {
+ indent.writeln('flutter::EncodableMap toMapResult;');
+ for (final NamedType field in klass.fields) {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+ String toWriteValue = '';
+ if (!hostDatatype.isBuiltin &&
+ rootClassNameSet.contains(field.type.baseName)) {
+ toWriteValue = '${field.name}_.ToEncodableMap()';
+ } else if (!hostDatatype.isBuiltin &&
+ rootEnumNameSet.contains(field.type.baseName)) {
+ toWriteValue = 'flutter::EncodableValue((int)${field.name}_)';
+ } else {
+ toWriteValue = 'flutter::EncodableValue(${field.name}_)';
+ }
+ indent.writeln(
+ 'toMapResult.insert(std::make_pair(flutter::EncodableValue("${field.name}"), $toWriteValue));');
+ }
+ indent.writeln('return toMapResult;');
+ });
+ indent.writeln('${klass.name}::${klass.name}() {}');
+ indent.write('${klass.name}::${klass.name}(flutter::EncodableMap map) ');
+ indent.scoped('{', '}', () {
+ for (final NamedType field in klass.fields) {
+ final String pointerFieldName = '${_pointerPrefix}_${field.name}';
+ final String encodableFieldName = '${_encodablePrefix}_${field.name}';
+ indent.writeln(
+ 'auto $encodableFieldName = map.at(flutter::EncodableValue("${field.name}"));');
+ if (rootEnumNameSet.contains(field.type.baseName)) {
+ indent.writeln(
+ 'if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))\t${field.name}_ = (${field.type.baseName})*$pointerFieldName;');
+ } else {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+ if (field.type.baseName == 'int') {
+ indent.format('''
+if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))
+\t${field.name}_ = *$pointerFieldName;
+else if (const int64_t* ${pointerFieldName}_64 = std::get_if<int64_t>(&$encodableFieldName))
+\t${field.name}_ = *${pointerFieldName}_64;''');
+ } else if (!hostDatatype.isBuiltin &&
+ root.classes
+ .map((Class x) => x.name)
+ .contains(field.type.baseName)) {
+ indent.write(
+ 'if (const flutter::EncodableMap* $pointerFieldName = std::get_if<flutter::EncodableMap>(&$encodableFieldName))');
+ indent.scoped('{', '}', () {
+ indent.writeln(
+ '${field.name}_ = ${hostDatatype.datatype}(*$pointerFieldName);');
+ });
+ } else {
+ indent.write(
+ 'if (const ${hostDatatype.datatype}* $pointerFieldName = std::get_if<${hostDatatype.datatype}>(&$encodableFieldName))');
+ indent.scoped('{', '}', () {
+ indent.writeln('${field.name}_ = *$pointerFieldName;');
+ });
+ }
+ }
+ }
+ });
+ indent.addln('');
+ }
+
+ for (final Api api in root.apis) {
+ _writeCodecSource(indent, api, root);
+ indent.addln('');
+ if (api.location == ApiLocation.host) {
+ _writeHostApiSource(indent, api);
+
+ indent.addln('');
+ indent.format('''
+flutter::EncodableMap ${api.name}::WrapError(std::string_view errorMessage) {
+\treturn flutter::EncodableMap({
+\t\t{flutter::EncodableValue("${Keys.errorMessage}"), flutter::EncodableValue(std::string(errorMessage).data())},
+\t\t{flutter::EncodableValue("${Keys.errorCode}"), flutter::EncodableValue("Error")},
+\t\t{flutter::EncodableValue("${Keys.errorDetails}"), flutter::EncodableValue()}
+\t});
+}
+flutter::EncodableMap ${api.name}::WrapError(const FlutterError& error) {
+\treturn flutter::EncodableMap({
+\t\t{flutter::EncodableValue("${Keys.errorMessage}"), flutter::EncodableValue(error.message)},
+\t\t{flutter::EncodableValue("${Keys.errorCode}"), flutter::EncodableValue(error.code)},
+\t\t{flutter::EncodableValue("${Keys.errorDetails}"), error.details}
+\t});
+}''');
+ indent.addln('');
+ } else if (api.location == ApiLocation.flutter) {
+ _writeFlutterApiSource(indent, api);
+ }
+ }
+
+ if (options.namespace != null) {
+ indent.writeln('} // namespace');
+ }
+}
+
+/// Validates an AST to make sure the cpp generator supports everything.
+List<Error> validateCpp(CppOptions options, Root root) {
+ final List<Error> result = <Error>[];
+ for (final Class aClass in root.classes) {
+ for (final NamedType field in aClass.fields) {
+ if (!field.type.isNullable) {
+ // TODO(gaaclarke): Add line number and filename.
+ result.add(Error(
+ message:
+ 'unsupported nonnull field "${field.name}" in "${aClass.name}"'));
+ }
+ }
+ }
+ return result;
+}
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 4d92c38..97f62e0 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -8,7 +8,7 @@
import 'ast.dart';
/// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '3.0.4';
+const String pigeonVersion = '3.1.0';
/// Read all the content from [stdin] to a String.
String readStdin() {
@@ -89,11 +89,12 @@
String? end,
Function func, {
bool addTrailingNewline = true,
+ int nestCount = 1,
}) {
if (begin != null) {
_sink.write(begin + newline);
}
- nest(1, func);
+ nest(nestCount, func);
if (end != null) {
_sink.write(str() + end);
if (addTrailingNewline) {
diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart
index d056115..7a26cf3 100644
--- a/packages/pigeon/lib/pigeon.dart
+++ b/packages/pigeon/lib/pigeon.dart
@@ -4,6 +4,7 @@
export 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+export 'cpp_generator.dart' show CppOptions;
export 'dart_generator.dart' show DartOptions;
export 'java_generator.dart' show JavaOptions;
export 'objc_generator.dart' show ObjcOptions;
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index aea6ef7..2fd9583 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -19,6 +19,7 @@
import 'package:analyzer/error/error.dart' show AnalysisError;
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
+import 'package:pigeon/cpp_generator.dart';
import 'package:pigeon/generator_tools.dart';
import 'package:pigeon/java_generator.dart';
@@ -157,6 +158,9 @@
this.objcOptions,
this.javaOut,
this.javaOptions,
+ this.cppHeaderOut,
+ this.cppSourceOut,
+ this.cppOptions,
this.dartOptions,
this.copyrightHeader,
this.oneLanguage,
@@ -187,6 +191,15 @@
/// Options that control how Java will be generated.
final JavaOptions? javaOptions;
+ /// Path to the ".h" C++ file that will be generated.
+ final String? cppHeaderOut;
+
+ /// Path to the ".cpp" C++ file that will be generated.
+ final String? cppSourceOut;
+
+ /// Options that control how C++ will be generated.
+ final CppOptions? cppOptions;
+
/// Options that control how Dart will be generated.
final DartOptions? dartOptions;
@@ -218,6 +231,12 @@
javaOptions: map.containsKey('javaOptions')
? JavaOptions.fromMap((map['javaOptions'] as Map<String, Object>?)!)
: null,
+ cppHeaderOut: map['experimental_cppHeaderOut'] as String?,
+ cppSourceOut: map['experimental_cppSourceOut'] as String?,
+ cppOptions: map.containsKey('experimental_cppOptions')
+ ? CppOptions.fromMap(
+ (map['experimental_cppOptions'] as Map<String, Object>?)!)
+ : null,
dartOptions: map.containsKey('dartOptions')
? DartOptions.fromMap((map['dartOptions'] as Map<String, Object>?)!)
: null,
@@ -240,6 +259,9 @@
if (objcOptions != null) 'objcOptions': objcOptions!.toMap(),
if (javaOut != null) 'javaOut': javaOut!,
if (javaOptions != null) 'javaOptions': javaOptions!.toMap(),
+ if (cppHeaderOut != null) 'experimental_cppHeaderOut': cppHeaderOut!,
+ if (cppSourceOut != null) 'experimental_cppSourceOut': cppSourceOut!,
+ if (cppOptions != null) 'experimental_cppOptions': cppOptions!.toMap(),
if (dartOptions != null) 'dartOptions': dartOptions!.toMap(),
if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!,
if (astOut != null) 'astOut': astOut!,
@@ -468,6 +490,54 @@
List<Error> validate(PigeonOptions options, Root root) => <Error>[];
}
+/// A [Generator] that generates C++ header code.
+class CppHeaderGenerator implements Generator {
+ /// Constructor for [CppHeaderGenerator].
+ const CppHeaderGenerator();
+
+ @override
+ void generate(StringSink sink, PigeonOptions options, Root root) {
+ final CppOptions cppOptions = options.cppOptions ?? const CppOptions();
+ final CppOptions cppOptionsWithHeader = cppOptions.merge(CppOptions(
+ copyrightHeader: options.copyrightHeader != null
+ ? _lineReader(options.copyrightHeader!)
+ : null));
+ generateCppHeader(path.basenameWithoutExtension(options.cppHeaderOut!),
+ cppOptionsWithHeader, root, sink);
+ }
+
+ @override
+ IOSink? shouldGenerate(PigeonOptions options) =>
+ _openSink(options.cppHeaderOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) =>
+ validateCpp(options.cppOptions!, root);
+}
+
+/// A [Generator] that generates C++ source code.
+class CppSourceGenerator implements Generator {
+ /// Constructor for [CppSourceGenerator].
+ const CppSourceGenerator();
+
+ @override
+ void generate(StringSink sink, PigeonOptions options, Root root) {
+ final CppOptions cppOptions = options.cppOptions ?? const CppOptions();
+ final CppOptions cppOptionsWithHeader = cppOptions.merge(CppOptions(
+ copyrightHeader: options.copyrightHeader != null
+ ? _lineReader(options.copyrightHeader!)
+ : null));
+ generateCppSource(cppOptionsWithHeader, root, sink);
+ }
+
+ @override
+ IOSink? shouldGenerate(PigeonOptions options) =>
+ _openSink(options.cppSourceOut);
+
+ @override
+ List<Error> validate(PigeonOptions options, Root root) => <Error>[];
+}
+
dart_ast.Annotation? _findMetadata(
dart_ast.NodeList<dart_ast.Annotation> metadata, String query) {
final Iterable<dart_ast.Annotation> annotations = metadata
@@ -1063,6 +1133,12 @@
..addOption('java_out', help: 'Path to generated Java file (.java).')
..addOption('java_package',
help: 'The package that generated Java code will be in.')
+ ..addOption('experimental_cpp_header_out',
+ help: 'Path to generated C++ header file (.h). (experimental)')
+ ..addOption('experimental_cpp_source_out',
+ help: 'Path to generated C++ classes file (.cpp). (experimental)')
+ ..addOption('cpp_namespace',
+ help: 'The namespace that generated C++ code will be in.')
..addOption('objc_header_out',
help: 'Path to generated Objective-C header file (.h).')
..addOption('objc_prefix',
@@ -1101,6 +1177,11 @@
javaOptions: JavaOptions(
package: results['java_package'],
),
+ cppHeaderOut: results['experimental_cpp_header_out'],
+ cppSourceOut: results['experimental_cpp_source_out'],
+ cppOptions: CppOptions(
+ namespace: results['cpp_namespace'],
+ ),
copyrightHeader: results['copyright_header'],
oneLanguage: results['one_language'],
astOut: results['ast_out'],
@@ -1143,6 +1224,8 @@
<Generator>[
const DartGenerator(),
const JavaGenerator(),
+ const CppHeaderGenerator(),
+ const CppSourceGenerator(),
const DartTestGenerator(),
const ObjcHeaderGenerator(),
const ObjcSourceGenerator(),
@@ -1196,6 +1279,12 @@
ObjcOptions(header: path.basename(options.objcHeaderOut!)))));
}
+ if (options.cppHeaderOut != null) {
+ options = options.merge(PigeonOptions(
+ cppOptions: options.cppOptions!.merge(
+ CppOptions(header: path.basename(options.cppHeaderOut!)))));
+ }
+
for (final Generator generator in safeGenerators) {
final IOSink? sink = generator.shouldGenerate(options);
if (sink != null) {
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
index 4a1e2a0..bf19653 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
index 7f7aa0d..9f0ce12 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
index 0b7e513..9791455 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
index 4ac7d91..dd7c7c9 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
index 2ed1294..a1f8030 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
index 52cf1e5..757d4a9 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
index c398ef2..b5da7e5 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.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, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt b/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
index 9d456d4..0fe0a39 100644
--- a/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
@@ -53,8 +53,38 @@
# directly into the test binary rather than using the DLL.
add_executable(${TEST_RUNNER}
test/pigeon_test.cpp
- # TODO(gaaclarke): Add test/message.g.cpp.
- # TODO(gaaclarke): Add test/message.g.h.
+ test/all_datatypes.g.cpp
+ test/all_datatypes.g.h
+ test/all_void.g.cpp
+ test/all_void.g.h
+ test/async_handlers.g.cpp
+ test/async_handlers.g.h
+ test/message.g.cpp
+ test/message.g.h
+ test/enum.g.cpp
+ test/enum.g.h
+ test/host2flutter.g.cpp
+ test/host2flutter.g.h
+ test/list.g.cpp
+ test/list.g.h
+ test/multiple_arity.g.cpp
+ test/multiple_arity.g.h
+ # Removed until supported by C++ generator.
+ # test/non_null_fields.g.cpp
+ # test/non_null_fields.g.h
+ test/nullable_returns.g.cpp
+ test/nullable_returns.g.h
+ test/primitive.g.cpp
+ test/primitive.g.h
+ test/void_arg_flutter.g.cpp
+ test/void_arg_flutter.g.h
+ test/void_arg_host.g.cpp
+ test/void_arg_host.g.h
+ test/voidflutter.g.cpp
+ test/voidflutter.g.h
+ test/voidhost.g.cpp
+ test/voidhost.g.h
+
${PLUGIN_SOURCES}
)
apply_standard_settings(${TEST_RUNNER})
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
index a74d8e5..e5ab587 100644
--- a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
@@ -10,6 +10,7 @@
#include <memory>
#include <string>
+#include "message.g.h"
#include "windows_unit_tests_plugin.h"
namespace windows_unit_tests {
@@ -19,13 +20,17 @@
using flutter::EncodableMap;
using flutter::EncodableValue;
+using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SetArgPointee;
+using namespace messageTest;
class MockMethodResult : public flutter::MethodResult<> {
public:
+ ~MockMethodResult() = default;
+
MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result),
(override));
MOCK_METHOD(void, ErrorInternal,
@@ -35,6 +40,44 @@
MOCK_METHOD(void, NotImplementedInternal, (), (override));
};
+class MockBinaryMessenger : public flutter::BinaryMessenger {
+ public:
+ ~MockBinaryMessenger() = default;
+
+ MOCK_METHOD(void, Send,
+ (const std::string& channel, const uint8_t* message,
+ size_t message_size, flutter::BinaryReply reply),
+ (override, const));
+ MOCK_METHOD(void, SetMessageHandler,
+ (const std::string& channel,
+ flutter::BinaryMessageHandler handler),
+ (override));
+};
+
+class MockApi : public Api {
+ public:
+ ~MockApi() = default;
+
+ MOCK_METHOD(std::optional<FlutterError>, initialize, (), (override));
+ MOCK_METHOD(ErrorOr<std::unique_ptr<SearchReply>>, search,
+ (const SearchRequest&), (override));
+};
+
+class Writer : public flutter::ByteStreamWriter {
+ public:
+ void WriteByte(uint8_t byte) override { data_.push_back(byte); }
+ void WriteBytes(const uint8_t* bytes, size_t length) override {
+ for (size_t i = 0; i < length; ++i) {
+ data_.push_back(bytes[i]);
+ }
+ }
+ void WriteAlignment(uint8_t alignment) override {
+ while (data_.size() % alignment != 0) {
+ data_.push_back(0);
+ }
+ }
+ std::vector<uint8_t> data_;
+};
} // namespace
TEST(PigeonTests, Placeholder) {
@@ -49,5 +92,58 @@
std::move(result));
}
+TEST(PigeonTests, CallInitialize) {
+ MockBinaryMessenger mock_messenger;
+ MockApi mock_api;
+ flutter::BinaryMessageHandler handler;
+ EXPECT_CALL(
+ mock_messenger,
+ SetMessageHandler("dev.flutter.pigeon.Api.initialize", testing::_))
+ .Times(1)
+ .WillOnce(testing::SaveArg<1>(&handler));
+ EXPECT_CALL(mock_messenger,
+ SetMessageHandler("dev.flutter.pigeon.Api.search", testing::_))
+ .Times(1);
+ EXPECT_CALL(mock_api, initialize());
+ Api::SetUp(&mock_messenger, &mock_api);
+ bool did_call_reply = false;
+ flutter::BinaryReply reply = [&did_call_reply](const uint8_t* data,
+ size_t size) {
+ did_call_reply = true;
+ };
+ handler(nullptr, 0, reply);
+ EXPECT_TRUE(did_call_reply);
+}
+
+TEST(PigeonTests, CallSearch) {
+ MockBinaryMessenger mock_messenger;
+ MockApi mock_api;
+ flutter::BinaryMessageHandler handler;
+ EXPECT_CALL(
+ mock_messenger,
+ SetMessageHandler("dev.flutter.pigeon.Api.initialize", testing::_))
+ .Times(1);
+ EXPECT_CALL(mock_messenger,
+ SetMessageHandler("dev.flutter.pigeon.Api.search", testing::_))
+ .Times(1)
+ .WillOnce(testing::SaveArg<1>(&handler));
+ EXPECT_CALL(mock_api, search(testing::_))
+ .WillOnce(Return(ByMove(ErrorOr<SearchReply>::MakeWithUniquePtr(
+ std::make_unique<SearchReply>()))));
+ Api::SetUp(&mock_messenger, &mock_api);
+ bool did_call_reply = false;
+ flutter::BinaryReply reply = [&did_call_reply](const uint8_t* data,
+ size_t size) {
+ did_call_reply = true;
+ };
+ SearchRequest request;
+ Writer writer;
+ flutter::EncodableList args;
+ args.push_back(flutter::CustomEncodableValue(request));
+ ApiCodecSerializer::GetInstance().WriteValue(args, &writer);
+ handler(writer.data_.data(), writer.data_.size(), reply);
+ EXPECT_TRUE(did_call_reply);
+}
+
} // namespace test
} // namespace windows_unit_tests
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 25ef681..714c09d 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
-version: 3.0.4 # This must match the version in lib/generator_tools.dart
+version: 3.1.0 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart
new file mode 100644
index 0000000..d7cf69f
--- /dev/null
+++ b/packages/pigeon/test/cpp_generator_test.dart
@@ -0,0 +1,88 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:pigeon/ast.dart';
+import 'package:pigeon/cpp_generator.dart';
+import 'package:pigeon/pigeon.dart' show Error;
+import 'package:test/test.dart';
+
+void main() {
+ test('gen one api', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doSomething',
+ arguments: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'Input',
+ isNullable: false,
+ ),
+ name: 'input',
+ offset: null)
+ ],
+ returnType:
+ const TypeDeclaration(baseName: 'Output', isNullable: false),
+ isAsynchronous: false,
+ )
+ ])
+ ], classes: <Class>[
+ Class(name: 'Input', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ ),
+ name: 'input',
+ offset: null)
+ ]),
+ Class(name: 'Output', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: true,
+ ),
+ name: 'output',
+ offset: null)
+ ])
+ ], enums: <Enum>[]);
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppHeader('', const CppOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('class Input'));
+ expect(code, contains('class Output'));
+ expect(code, contains('class Api'));
+ }
+ {
+ final StringBuffer sink = StringBuffer();
+ generateCppSource(const CppOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('Input::Input()'));
+ expect(code, contains('Output::Output'));
+ expect(
+ code,
+ contains(
+ 'void Api::SetUp(flutter::BinaryMessenger* binary_messenger, Api* api)'));
+ }
+ });
+
+ test('doesn\'t support nullable fields', () {
+ final Root root = Root(
+ apis: <Api>[],
+ classes: <Class>[
+ Class(name: 'Foo', fields: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(baseName: 'int', isNullable: false))
+ ])
+ ],
+ enums: <Enum>[],
+ );
+ final List<Error> errors = validateCpp(const CppOptions(), root);
+ expect(errors.length, 1);
+ expect(errors[0].message, contains('foo'));
+ expect(errors[0].message, contains('Foo'));
+ });
+}
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 9fa597a..0d22c08 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -92,6 +92,18 @@
expect(opts.objcSourceOut, equals('foo.m'));
});
+ test('parse args - experimental_cpp_header_out', () {
+ final PigeonOptions opts =
+ Pigeon.parseArgs(<String>['--experimental_cpp_header_out', 'foo.h']);
+ expect(opts.cppHeaderOut, equals('foo.h'));
+ });
+
+ test('parse args - experimental_cpp_source_out', () {
+ final PigeonOptions opts =
+ Pigeon.parseArgs(<String>['--experimental_cpp_source_out', 'foo.cpp']);
+ expect(opts.cppSourceOut, equals('foo.cpp'));
+ });
+
test('parse args - one_language', () {
final PigeonOptions opts = Pigeon.parseArgs(<String>['--one_language']);
expect(opts.oneLanguage, isTrue);
@@ -397,6 +409,26 @@
expect(buffer.toString(), startsWith('// Copyright 2013'));
});
+ test('C++ header generater copyright flag', () {
+ final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
+ const PigeonOptions options = PigeonOptions(
+ cppHeaderOut: 'Foo.h', copyrightHeader: './copyright_header.txt');
+ const CppHeaderGenerator cppHeaderGenerator = CppHeaderGenerator();
+ final StringBuffer buffer = StringBuffer();
+ cppHeaderGenerator.generate(buffer, options, root);
+ expect(buffer.toString(), startsWith('// Copyright 2013'));
+ });
+
+ test('C++ source generater copyright flag', () {
+ final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
+ const PigeonOptions options =
+ PigeonOptions(copyrightHeader: './copyright_header.txt');
+ const CppSourceGenerator cppSourceGenerator = CppSourceGenerator();
+ final StringBuffer buffer = StringBuffer();
+ cppSourceGenerator.generate(buffer, options, root);
+ expect(buffer.toString(), startsWith('// Copyright 2013'));
+ });
+
test('nested enum', () {
const String code = '''
enum NestedEnum { one, two }
diff --git a/packages/pigeon/tool/run_tests.dart b/packages/pigeon/tool/run_tests.dart
index 59a7803..c96c684 100644
--- a/packages/pigeon/tool/run_tests.dart
+++ b/packages/pigeon/tool/run_tests.dart
@@ -204,6 +204,7 @@
{required String input,
String? cppHeaderOut,
String? cppSourceOut,
+ String? cppNamespace,
String? dartOut,
String? dartTestOut,
bool streamOutput = true}) async {
@@ -218,16 +219,22 @@
];
if (cppHeaderOut != null) {
args.addAll(<String>[
- '--objc_header_out', // TODO(gaaclarke): Switch to c++.
+ '--experimental_cpp_header_out',
cppHeaderOut,
]);
}
if (cppSourceOut != null) {
args.addAll(<String>[
- '--objc_source_out', // TODO(gaaclarke): Switch to c++.
+ '--experimental_cpp_source_out',
cppSourceOut,
]);
}
+ if (cppNamespace != null) {
+ args.addAll(<String>[
+ '--cpp_namespace',
+ cppNamespace,
+ ]);
+ }
if (dartOut != null) {
args.addAll(<String>['--dart_out', dartOut]);
}
@@ -254,13 +261,35 @@
Future<int> _runWindowsUnitTests() async {
const String windowsUnitTestsPath = './platform_tests/windows_unit_tests';
- final int generateCode = await _runPigeon(
- input: './pigeons/message.dart',
- cppHeaderOut: '$windowsUnitTestsPath/windows/test/message.g.h',
- cppSourceOut: '$windowsUnitTestsPath/windows/test/message.g.cpp',
- );
- if (generateCode != 0) {
- return generateCode;
+ const List<String> tests = <String>[
+ 'message',
+ 'all_datatypes',
+ 'all_void',
+ 'async_handlers',
+ 'enum',
+ 'host2flutter',
+ 'list',
+ 'multiple_arity',
+ // Removed until supported by C++ generator.
+ // 'non_null_fields',
+ 'nullable_returns',
+ 'primitive',
+ 'void_arg_flutter',
+ 'void_arg_host',
+ 'voidflutter',
+ 'voidhost'
+ ];
+ int generateCode = 0;
+
+ for (final String test in tests) {
+ generateCode = await _runPigeon(
+ input: './pigeons/$test.dart',
+ cppHeaderOut: '$windowsUnitTestsPath/windows/test/$test.g.h',
+ cppSourceOut: '$windowsUnitTestsPath/windows/test/$test.g.cpp',
+ cppNamespace: '${test}Test');
+ if (generateCode != 0) {
+ return generateCode;
+ }
}
final Process compile = await _streamOutput(Process.start(