[pigeon] added the ability to add a copyright notice to generated files (#377)

This commit is contained in:
gaaclarke
2021-06-09 09:45:52 -07:00
committed by GitHub
parent 1f021ea652
commit 1ed18e3d74
11 changed files with 333 additions and 76 deletions

View File

@ -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.

View File

@ -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<String>? copyrightHeader;
}
String _escapeForDartSingleQuotedString(String raw) {
@ -209,6 +212,9 @@ void generateDart(DartOptions opt, Root root, StringSink sink) {
final List<String> 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(

View File

@ -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<String> lines, {String? linePrefix}) {
final String prefix = linePrefix ?? '';
for (final String line in lines) {
indent.writeln('$prefix$line');
}
}

View File

@ -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<String>? copyrightHeader;
}
void _writeHostApi(Indent indent, Api api) {
@ -226,6 +230,9 @@ void generateJava(JavaOptions options, Root root, StringSink sink) {
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('');

View File

@ -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<String>? 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 <Foundation/Foundation.h>');
@ -398,6 +405,9 @@ void generateObjcSource(ObjcOptions options, Root root, StringSink sink) {
root.classes.map((Class x) => x.name).toList();
final List<String> 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}"');

View File

@ -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<Error> errors;
}
String _posixify(String input) {
final path.Context context = path.Context(style: path.Style.posix);
return context.fromUri(path.toUri(path.absolute(input)));
}
Iterable<String> _lineReader(String path) sync* {
final String contents = File(path).readAsStringSync();
const LineSplitter lineSplitter = LineSplitter();
final List<String> 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<String> 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<void> _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<Error> _validateAst(Root root) {
final List<Error> result = <Error>[];
final List<String> 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<int> run(List<String> args) async {
/// command-line arguments. The optional parameter [generators] allows you to
/// customize the generators that pigeon will use.
static Future<int> run(List<String> args,
{List<Generator>? generators}) async {
final Pigeon pigeon = Pigeon.setup();
final PigeonOptions options = Pigeon.parseArgs(args);
final List<Generator> safeGenerators = generators ??
<Generator>[
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));
for (final Generator generator in safeGenerators) {
final IOSink? sink = generator.shouldGenerate(options);
if (sink != null) {
generator.generate(sink, options, parseResults.root);
await sink.flush();
}
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!);
}
await _runGenerator(
options.javaOut!,
(StringSink sink) => generateJava(
options.javaOptions ?? JavaOptions(), parseResults.root, sink));
}
} else {
errors.add(Error(message: 'No pigeon classes found, nothing generated.'));

View File

@ -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 \

View File

@ -454,4 +454,20 @@ void main() {
final String code = sink.toString();
expect(code, matches('channel.send[(]null[)]'));
});
Iterable<String> _makeIterable(String string) sync* {
yield string;
}
test('header', () {
final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
final StringBuffer sink = StringBuffer();
generateDart(
DartOptions(
isNullSafe: false, copyrightHeader: _makeIterable('hello world')),
root,
sink);
final String code = sink.toString();
expect(code, startsWith('// hello world'));
});
}

View File

@ -413,4 +413,20 @@ void main() {
expect(code, contains('toMapResult.put("enum1", enum1.index);'));
expect(code, contains('fromMapResult.enum1 = Enum1.values()[(int)enum1];'));
});
Iterable<String> _makeIterable(String string) sync* {
yield string;
}
test('header', () {
final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
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'));
});
}

View File

@ -682,4 +682,36 @@ void main() {
contains(
'[api doSomething:^(ABCOutput *_Nullable output, FlutterError *_Nullable error) {'));
});
Iterable<String> _makeIterable(String string) sync* {
yield string;
}
test('source copyright', () {
final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
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: <Api>[], classes: <Class>[], enums: <Enum>[]);
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'));
});
}

View File

@ -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(<String>['--dart_null_safety']);
expect(results.dartOptions?.isNullSafe, isTrue);
});
test('copyright flag', () {
final PigeonOptions results =
Pigeon.parseArgs(<String>['--copyright_header', 'foobar.txt']);
expect(results.copyrightHeader, 'foobar.txt');
});
test('Dart generater copyright flag', () {
final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
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: <Api>[], classes: <Class>[], enums: <Enum>[]);
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: <Api>[], classes: <Class>[], enums: <Enum>[]);
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: <Api>[], classes: <Class>[], enums: <Enum>[]);
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'));
});
}