diff --git a/packages/pigeon/copyright_header.txt b/packages/pigeon/copyright_header.txt new file mode 100644 index 0000000000..0bc9812707 --- /dev/null +++ b/packages/pigeon/copyright_header.txt @@ -0,0 +1,4 @@ +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. + diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index c645ae8619..af090fa019 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -8,10 +8,13 @@ import 'generator_tools.dart'; /// Options that control how Dart code will be generated. class DartOptions { /// Constructor for DartOptions. - DartOptions({this.isNullSafe = true}); + DartOptions({this.isNullSafe = true, this.copyrightHeader}); /// Determines if the generated code has null safety annotations (Dart >=2.12 required). bool isNullSafe; + + /// A copyright header that will get prepended to generated code. + Iterable? copyrightHeader; } String _escapeForDartSingleQuotedString(String raw) { @@ -209,6 +212,9 @@ void generateDart(DartOptions opt, Root root, StringSink sink) { final List customEnumNames = root.enums.map((Enum x) => x.name).toList(); final Indent indent = Indent(sink); + if (opt.copyrightHeader != null) { + addLines(indent, opt.copyrightHeader!, linePrefix: '// '); + } indent.writeln('// $generatedCodeWarning'); indent.writeln('// $seeAlsoWarning'); indent.writeln( diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 9df125ba4f..25c06b750f 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -211,3 +211,11 @@ class Keys { bool isVoid(TypeMirror type) { return MirrorSystem.getName(type.simpleName) == 'void'; } + +/// Adds the [lines] to [indent]. +void addLines(Indent indent, Iterable lines, {String? linePrefix}) { + final String prefix = linePrefix ?? ''; + for (final String line in lines) { + indent.writeln('$prefix$line'); + } +} diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index cc5a64eae6..34e4831fa9 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -24,6 +24,7 @@ class JavaOptions { JavaOptions({ this.className, this.package, + this.copyrightHeader, }); /// The name of the class that will house all the generated classes. @@ -31,6 +32,9 @@ class JavaOptions { /// The package where the generated class will live. String? package; + + /// A copyright header that will get prepended to generated code. + Iterable? copyrightHeader; } void _writeHostApi(Indent indent, Api api) { @@ -226,6 +230,9 @@ void generateJava(JavaOptions options, Root root, StringSink sink) { final Set 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(''); diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 1148ecc041..991b2d6d6a 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -11,6 +11,7 @@ class ObjcOptions { ObjcOptions({ this.header, this.prefix, + this.copyrightHeader, }); /// The path to the header that will get placed in the source filed (example: @@ -19,6 +20,9 @@ class ObjcOptions { /// Prefix that will be appended before all generated classes and protocols. String? prefix; + + /// A copyright header that will get prepended to generated code. + Iterable? copyrightHeader; } String _className(String? prefix, String className) { @@ -171,6 +175,9 @@ void _writeFlutterApiDeclaration(Indent indent, Api api, ObjcOptions options) { /// provided [options]. void generateObjcHeader(ObjcOptions 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.writeln('#import '); @@ -398,6 +405,9 @@ void generateObjcSource(ObjcOptions options, Root root, StringSink sink) { root.classes.map((Class x) => x.name).toList(); final List enumNames = root.enums.map((Enum x) => x.name).toList(); + if (options.copyrightHeader != null) { + addLines(indent, options.copyrightHeader!, linePrefix: '// '); + } indent.writeln('// $generatedCodeWarning'); indent.writeln('// $seeAlsoWarning'); indent.writeln('#import "${options.header}"'); diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 3b80ca6e06..2215747e41 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; import 'dart:io'; import 'dart:mirrors'; @@ -150,6 +151,9 @@ class PigeonOptions { /// Options that control how Dart will be generated. DartOptions? dartOptions = DartOptions(); + + /// Path to a copyright header that will get prepended to generated code. + String? copyrightHeader; } /// A collection of an AST represented as a [Root] and [Error]'s. @@ -167,6 +171,152 @@ class ParseResults { final List errors; } +String _posixify(String input) { + final path.Context context = path.Context(style: path.Style.posix); + return context.fromUri(path.toUri(path.absolute(input))); +} + +Iterable _lineReader(String path) sync* { + final String contents = File(path).readAsStringSync(); + const LineSplitter lineSplitter = LineSplitter(); + final List lines = lineSplitter.convert(contents); + for (final String line in lines) { + yield line; + } +} + +IOSink? _openSink(String? output) { + if (output == null) { + return null; + } + IOSink sink; + File file; + if (output == 'stdout') { + sink = stdout; + } else { + file = File(output); + sink = file.openWrite(); + } + return sink; +} + +/// A generator that will write code to a sink based on the contents of [PigeonOptions]. +abstract class Generator { + /// Returns an [IOSink] instance to be written to if the [Generator] should + /// generate. If it returns `null`, the [Generator] will be skipped. + IOSink? shouldGenerate(PigeonOptions options); + + /// Write the generated code described in [root] to [sink] using the + /// [options]. + void generate(StringSink sink, PigeonOptions options, Root root); +} + +/// A [Generator] that generates Dart source code. +class DartGenerator implements Generator { + /// Constructor for [DartGenerator]. + const DartGenerator(); + + @override + void generate(StringSink sink, PigeonOptions options, Root root) { + final DartOptions dartOptions = options.dartOptions ?? DartOptions(); + dartOptions.copyrightHeader = options.copyrightHeader != null + ? _lineReader(options.copyrightHeader!) + : null; + generateDart(dartOptions, root, sink); + } + + @override + IOSink? shouldGenerate(PigeonOptions options) => _openSink(options.dartOut); +} + +/// A [Generator] that generates Dart test source code. +class DartTestGenerator implements Generator { + /// Constructor for [DartTestGenerator]. + const DartTestGenerator(); + + @override + void generate(StringSink sink, PigeonOptions options, Root root) { + final String mainPath = context.relative( + _posixify(options.dartOut!), + from: _posixify(path.dirname(options.dartTestOut!)), + ); + generateTestDart( + options.dartOptions ?? DartOptions(), + root, + sink, + mainPath, + ); + } + + @override + IOSink? shouldGenerate(PigeonOptions options) { + if (options.dartTestOut != null && options.dartOut != null) { + return _openSink(options.dartTestOut); + } else { + return null; + } + } +} + +/// A [Generator] that generates Objective-C header code. +class ObjcHeaderGenerator implements Generator { + /// Constructor for [ObjcHeaderGenerator]. + const ObjcHeaderGenerator(); + + @override + void generate(StringSink sink, PigeonOptions options, Root root) { + final ObjcOptions objcOptions = options.objcOptions ?? ObjcOptions(); + objcOptions.copyrightHeader = options.copyrightHeader != null + ? _lineReader(options.copyrightHeader!) + : null; + generateObjcHeader(objcOptions, root, sink); + } + + @override + IOSink? shouldGenerate(PigeonOptions options) => + _openSink(options.objcHeaderOut); +} + +/// A [Generator] that generates Objective-C source code. +class ObjcSourceGenerator implements Generator { + /// Constructor for [ObjcSourceGenerator]. + const ObjcSourceGenerator(); + + @override + void generate(StringSink sink, PigeonOptions options, Root root) { + final ObjcOptions objcOptions = options.objcOptions ?? ObjcOptions(); + objcOptions.copyrightHeader = options.copyrightHeader != null + ? _lineReader(options.copyrightHeader!) + : null; + generateObjcSource(objcOptions, root, sink); + } + + @override + IOSink? shouldGenerate(PigeonOptions options) => + _openSink(options.objcSourceOut); +} + +/// A [Generator] that generates Java source code. +class JavaGenerator implements Generator { + /// Constructor for [JavaGenerator]. + const JavaGenerator(); + + @override + void generate(StringSink sink, PigeonOptions options, Root root) { + if (options.javaOptions!.className == null) { + options.javaOptions!.className = + path.basenameWithoutExtension(options.javaOut!); + } + options.javaOptions!.copyrightHeader = options.copyrightHeader != null + ? _lineReader(options.copyrightHeader!) + : null; + generateJava(options.javaOptions ?? JavaOptions(), root, sink); + } + + @override + IOSink? shouldGenerate(PigeonOptions options) => _openSink(options.javaOut); +} + /// Tool for generating code to facilitate platform channels usage. class Pigeon { /// Create and setup a [Pigeon] instance. @@ -356,7 +506,10 @@ options: ..addOption('objc_header_out', help: 'Path to generated Objective-C header file (.h).') ..addOption('objc_prefix', - help: 'Prefix for generated Objective-C classes and protocols.'); + help: 'Prefix for generated Objective-C classes and protocols.') + ..addOption('copyright_header', + help: + 'Path to file with copyright header to be prepended to generated code.'); /// Convert command-line arguments to [PigeonOptions]. static PigeonOptions parseArgs(List args) { @@ -380,23 +533,10 @@ options: package: results['java_package'], ); opts.dartOptions = DartOptions()..isNullSafe = results['dart_null_safety']; + opts.copyrightHeader = results['copyright_header']; return opts; } - static Future _runGenerator( - String output, void Function(IOSink sink) func) async { - IOSink sink; - File file; - if (output == 'stdout') { - sink = stdout; - } else { - file = File(output); - sink = file.openWrite(); - } - func(sink); - await sink.flush(); - } - List _validateAst(Root root) { final List result = []; final List customClasses = @@ -451,17 +591,21 @@ options: } } - static String _posixify(String input) { - final path.Context context = path.Context(style: path.Style.posix); - return context.fromUri(path.toUri(path.absolute(input))); - } - /// The 'main' entrypoint used by the command-line tool. [args] are the - /// command-line arguments. - static Future run(List args) async { + /// command-line arguments. The optional parameter [generators] allows you to + /// customize the generators that pigeon will use. + static Future run(List args, + {List? generators}) async { final Pigeon pigeon = Pigeon.setup(); final PigeonOptions options = Pigeon.parseArgs(args); - + final List safeGenerators = generators ?? + [ + const DartGenerator(), + const JavaGenerator(), + const DartTestGenerator(), + const ObjcHeaderGenerator(), + const ObjcSourceGenerator(), + ]; _executeConfigurePigeon(options); if (options.input == null || options.dartOut == null) { @@ -489,48 +633,12 @@ options: for (final Error err in parseResults.errors) { errors.add(Error(message: err.message, filename: options.input)); } - if (options.dartOut != null) { - await _runGenerator( - options.dartOut!, - (StringSink sink) => generateDart( - options.dartOptions ?? DartOptions(), parseResults.root, sink)); - } - if (options.dartTestOut != null && options.dartOut != null) { - final String mainPath = context.relative( - _posixify(options.dartOut!), - from: _posixify(path.dirname(options.dartTestOut!)), - ); - await _runGenerator( - options.dartTestOut!, - (StringSink sink) => generateTestDart( - options.dartOptions ?? DartOptions(), - parseResults.root, - sink, - mainPath, - ), - ); - } - if (options.objcHeaderOut != null) { - await _runGenerator( - options.objcHeaderOut!, - (StringSink sink) => generateObjcHeader( - options.objcOptions ?? ObjcOptions(), parseResults.root, sink)); - } - if (options.objcSourceOut != null) { - await _runGenerator( - options.objcSourceOut!, - (StringSink sink) => generateObjcSource( - options.objcOptions ?? ObjcOptions(), parseResults.root, sink)); - } - if (options.javaOut != null) { - if (options.javaOptions!.className == null) { - options.javaOptions!.className = - path.basenameWithoutExtension(options.javaOut!); + for (final Generator generator in safeGenerators) { + final IOSink? sink = generator.shouldGenerate(options); + if (sink != null) { + generator.generate(sink, options, parseResults.root); + await sink.flush(); } - await _runGenerator( - options.javaOut!, - (StringSink sink) => generateJava( - options.javaOptions ?? JavaOptions(), parseResults.root, sink)); } } else { errors.add(Error(message: 'No pigeon classes found, nothing generated.')); diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh index 4dc693ef27..f095f692d6 100755 --- a/packages/pigeon/run_tests.sh +++ b/packages/pigeon/run_tests.sh @@ -40,6 +40,7 @@ if [ $java_version == "8" ]; then else javac_bootclasspath= fi +run_pigeon="pub run pigeon --copyright_header ./copyright_header.txt" ############################################################################### # Helper Functions @@ -60,7 +61,7 @@ test_pigeon_ios() { echo "test_pigeon_ios($1)" temp_dir=$(mktmpdir) - pub run pigeon \ + $run_pigeon \ --no-dart_null_safety \ --input $1 \ --dart_out $temp_dir/pigeon.dart \ @@ -88,7 +89,7 @@ test_pigeon_android() { echo "test_pigeon_android($1)" temp_dir=$(mktmpdir) - pub run pigeon \ + $run_pigeon \ --input $1 \ --dart_out $temp_dir/pigeon.dart \ --java_out $temp_dir/Pigeon.java \ @@ -119,12 +120,12 @@ test_pigeon_dart() { temp_dir_1=$(mktmpdir) temp_dir_2=$(mktmpdir) - pub run pigeon \ + $run_pigeon \ --input $1 \ --dart_out $temp_dir_1/pigeon.dart & null_safe_gen_pid=$! - pub run pigeon \ + $run_pigeon \ --no-dart_null_safety \ --input $1 \ --dart_out $temp_dir_2/pigeon.dart & @@ -190,12 +191,12 @@ run_dart_unittests() { } test_running_without_arguments() { - pub run pigeon 1>/dev/null + $run_pigeon 1>/dev/null } run_flutter_unittests() { pushd $PWD - pub run pigeon \ + $run_pigeon \ --input pigeons/flutter_unittests.dart \ --dart_out platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart cd platform_tests/flutter_null_safe_unit_tests @@ -206,7 +207,7 @@ run_flutter_unittests() { run_mock_handler_tests() { pushd $PWD - pub run pigeon \ + $run_pigeon \ --input pigeons/message.dart \ --dart_out mock_handler_tester/test/message.dart \ --dart_test_out mock_handler_tester/test/test.dart @@ -273,13 +274,13 @@ run_objc_compilation_tests() { } run_ios_unittests() { - pub run pigeon \ + $run_pigeon \ --no-dart_null_safety \ --input pigeons/message.dart \ --dart_out /dev/null \ --objc_header_out platform_tests/ios_unit_tests/ios/Runner/messages.h \ --objc_source_out platform_tests/ios_unit_tests/ios/Runner/messages.m - pub run pigeon \ + $run_pigeon \ --no-dart_null_safety \ --input pigeons/async_handlers.dart \ --dart_out /dev/null \ @@ -307,7 +308,7 @@ run_ios_e2e_tests() { DARTLE_M="e2e_tests/test_objc/ios/Runner/dartle.m" DARTLE_DART="e2e_tests/test_objc/lib/dartle.dart" PIGEON_JAVA="e2e_tests/test_objc/android/app/src/main/java/io/flutter/plugins/Pigeon.java" - pub run pigeon \ + $run_pigeon \ --input pigeons/message.dart \ --dart_out $DARTLE_DART \ --objc_header_out $DARTLE_H \ @@ -335,7 +336,7 @@ run_formatter() { run_android_unittests() { pushd $PWD - pub run pigeon \ + $run_pigeon \ --input pigeons/android_unittests.dart \ --dart_out /dev/null \ --java_out platform_tests/android_unit_tests/android/app/src/main/java/com/example/android_unit_tests/Pigeon.java \ diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index fbe34c7cb2..176db319e7 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -454,4 +454,20 @@ void main() { final String code = sink.toString(); expect(code, matches('channel.send[(]null[)]')); }); + + Iterable _makeIterable(String string) sync* { + yield string; + } + + test('header', () { + final Root root = Root(apis: [], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + generateDart( + DartOptions( + isNullSafe: false, copyrightHeader: _makeIterable('hello world')), + root, + sink); + final String code = sink.toString(); + expect(code, startsWith('// hello world')); + }); } diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index b324ee0b37..3c0f9438e7 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -413,4 +413,20 @@ void main() { expect(code, contains('toMapResult.put("enum1", enum1.index);')); expect(code, contains('fromMapResult.enum1 = Enum1.values()[(int)enum1];')); }); + + Iterable _makeIterable(String string) sync* { + yield string; + } + + test('header', () { + final Root root = Root(apis: [], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + final JavaOptions javaOptions = JavaOptions( + className: 'Messages', + copyrightHeader: _makeIterable('hello world'), + ); + generateJava(javaOptions, root, sink); + final String code = sink.toString(); + expect(code, startsWith('// hello world')); + }); } diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index 7008c2f79d..399a7e587b 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -682,4 +682,36 @@ void main() { contains( '[api doSomething:^(ABCOutput *_Nullable output, FlutterError *_Nullable error) {')); }); + + Iterable _makeIterable(String string) sync* { + yield string; + } + + test('source copyright', () { + final Root root = Root(apis: [], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + generateObjcSource( + ObjcOptions( + header: 'foo.h', + prefix: 'ABC', + copyrightHeader: _makeIterable('hello world')), + root, + sink); + final String code = sink.toString(); + expect(code, startsWith('// hello world')); + }); + + test('header copyright', () { + final Root root = Root(apis: [], classes: [], enums: []); + final StringBuffer sink = StringBuffer(); + generateObjcHeader( + ObjcOptions( + header: 'foo.h', + prefix: 'ABC', + copyrightHeader: _makeIterable('hello world')), + root, + sink); + final String code = sink.toString(); + expect(code, startsWith('// hello world')); + }); } diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 98bb2ed672..afd6125955 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:pigeon/ast.dart'; +import 'package:pigeon/java_generator.dart'; import 'package:pigeon/pigeon_lib.dart'; import 'package:test/test.dart'; @@ -263,4 +264,52 @@ void main() { Pigeon.parseArgs(['--dart_null_safety']); expect(results.dartOptions?.isNullSafe, isTrue); }); + + test('copyright flag', () { + final PigeonOptions results = + Pigeon.parseArgs(['--copyright_header', 'foobar.txt']); + expect(results.copyrightHeader, 'foobar.txt'); + }); + + test('Dart generater copyright flag', () { + final Root root = Root(apis: [], classes: [], enums: []); + final PigeonOptions options = PigeonOptions(); + options.copyrightHeader = './copyright_header.txt'; + const DartGenerator dartGenerator = DartGenerator(); + final StringBuffer buffer = StringBuffer(); + dartGenerator.generate(buffer, options, root); + expect(buffer.toString(), startsWith('// Copyright 2013')); + }); + + test('Java generater copyright flag', () { + final Root root = Root(apis: [], classes: [], enums: []); + final PigeonOptions options = PigeonOptions(); + options.javaOut = 'Foo.java'; + options.javaOptions = JavaOptions(); + options.copyrightHeader = './copyright_header.txt'; + const JavaGenerator javaGenerator = JavaGenerator(); + final StringBuffer buffer = StringBuffer(); + javaGenerator.generate(buffer, options, root); + expect(buffer.toString(), startsWith('// Copyright 2013')); + }); + + test('Objc header generater copyright flag', () { + final Root root = Root(apis: [], classes: [], enums: []); + final PigeonOptions options = PigeonOptions(); + options.copyrightHeader = './copyright_header.txt'; + const ObjcHeaderGenerator objcHeaderGenerator = ObjcHeaderGenerator(); + final StringBuffer buffer = StringBuffer(); + objcHeaderGenerator.generate(buffer, options, root); + expect(buffer.toString(), startsWith('// Copyright 2013')); + }); + + test('Objc source generater copyright flag', () { + final Root root = Root(apis: [], classes: [], enums: []); + final PigeonOptions options = PigeonOptions(); + options.copyrightHeader = './copyright_header.txt'; + const ObjcSourceGenerator objcSourceGenerator = ObjcSourceGenerator(); + final StringBuffer buffer = StringBuffer(); + objcSourceGenerator.generate(buffer, options, root); + expect(buffer.toString(), startsWith('// Copyright 2013')); + }); }