Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 29.1.1

* Fixes `basePath` resolution when an output or directory path shares a string prefix with `basePath`.

## 29.1.0

* [kotlin,swift] Adds support for removing event channel handler registration
Expand Down
10 changes: 9 additions & 1 deletion packages/pigeon/lib/src/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import 'generator.dart';
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
const String pigeonVersion = '29.1.0';
const String pigeonVersion = '29.1.1';

/// Default plugin package name.
const String defaultPluginPackageName = 'dev.flutter.pigeon';
Expand Down Expand Up @@ -84,6 +84,14 @@ String makeRelative(String targetPath, String fromPath) {
return path.posix.relative(absTarget, from: absFrom);
}

/// Returns true if [child] is the same path as or inside [parent].
bool isWithinOrEquals(String parent, String child) {
final String normalizedParent = posixPath(parent);
final String normalizedChild = posixPath(child);
return path.posix.equals(normalizedParent, normalizedChild) ||
path.posix.isWithin(normalizedParent, normalizedChild);
}

/// Read all the content from [stdin] to a String.
String readStdin() {
final bytes = <int>[];
Expand Down
7 changes: 3 additions & 4 deletions packages/pigeon/lib/src/kotlin/jnigen_config_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ class JnigenConfigGenerator extends Generator<InternalJnigenConfigOptions> {
final String rawConfigDir =
(basePath.isNotEmpty &&
configDirSetting.isNotEmpty &&
!configDirSetting.startsWith(basePath))
!isWithinOrEquals(basePath, configDirSetting))
? path.posix.join(basePath, configDirSetting)
: (configDirSetting.isNotEmpty ? configDirSetting : basePath);

final String appDir =
generatorOptions.kotlinOptions.appDirectory ?? generatorOptions.appDirectory ?? '';
final String rawAppDir =
(basePath.isNotEmpty && appDir.isNotEmpty && !appDir.startsWith(basePath))
(basePath.isNotEmpty && appDir.isNotEmpty && !isWithinOrEquals(basePath, appDir))
? path.posix.join(basePath, appDir)
: appDir;

Expand All @@ -86,8 +86,7 @@ class JnigenConfigGenerator extends Generator<InternalJnigenConfigOptions> {
}

final String dartOutPath = generatorOptions.dartOptions.dartOut ?? '';
final String rawDartOut =
(basePath.isNotEmpty && dartOutPath.isNotEmpty && !dartOutPath.startsWith(basePath))
final String rawDartOut = (basePath.isNotEmpty && dartOutPath.isNotEmpty)
? path.posix.join(basePath, dartOutPath)
: dartOutPath;
final String fullDartOut = rawConfigDir.isNotEmpty
Expand Down
6 changes: 4 additions & 2 deletions packages/pigeon/lib/src/pigeon_lib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -909,9 +909,10 @@ ${_argParser.usage}''';
internalOptions.swiftOptions?.configDirectory ?? internalOptions.configDirectory;
final String fullConfigDir =
(internalOptions.basePath != null &&
internalOptions.basePath!.isNotEmpty &&
targetConfigDir != null &&
targetConfigDir.isNotEmpty &&
!targetConfigDir.startsWith(internalOptions.basePath!))
!isWithinOrEquals(internalOptions.basePath!, targetConfigDir))
Comment on lines 911 to +915

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure consistency with other generators and avoid relying on fragile behavior of path.isWithin when called with an empty string, we should explicitly check that internalOptions.basePath is not empty before calling isWithinOrEquals.

            (internalOptions.basePath != null &&
                internalOptions.basePath!.isNotEmpty &&
                targetConfigDir != null &&
                targetConfigDir.isNotEmpty &&
                !isWithinOrEquals(internalOptions.basePath!, targetConfigDir))

? path.join(internalOptions.basePath!, targetConfigDir)
: (targetConfigDir ?? internalOptions.basePath ?? '');
final int exitCode = await _runFfigen(fullConfigDir, internalOptions.input, dartExecutable);
Expand All @@ -936,9 +937,10 @@ ${_argParser.usage}''';
internalOptions.kotlinOptions?.configDirectory ?? internalOptions.configDirectory;
final String fullConfigDir =
(internalOptions.basePath != null &&
internalOptions.basePath!.isNotEmpty &&
targetConfigDir != null &&
targetConfigDir.isNotEmpty &&
!targetConfigDir.startsWith(internalOptions.basePath!))
!isWithinOrEquals(internalOptions.basePath!, targetConfigDir))
Comment on lines 939 to +943

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure consistency with other generators and avoid relying on fragile behavior of path.isWithin when called with an empty string, we should explicitly check that internalOptions.basePath is not empty before calling isWithinOrEquals.

            (internalOptions.basePath != null &&
                internalOptions.basePath!.isNotEmpty &&
                targetConfigDir != null &&
                targetConfigDir.isNotEmpty &&
                !isWithinOrEquals(internalOptions.basePath!, targetConfigDir))

? path.join(internalOptions.basePath!, targetConfigDir)
: (targetConfigDir ?? internalOptions.basePath ?? '');
final bool success = await _runJnigen(
Expand Down
11 changes: 4 additions & 7 deletions packages/pigeon/lib/src/pigeon_lib_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2702,15 +2702,14 @@ String? _findTargetPubspecPath({
}) {
String? pubspecPath;
if (dartOutPath != null && dartOutPath.isNotEmpty) {
final String fullDartOut =
(basePath != null && basePath.isNotEmpty && !dartOutPath.startsWith(basePath))
final String fullDartOut = (basePath != null && basePath.isNotEmpty)
? path.join(basePath, dartOutPath)
: dartOutPath;
pubspecPath = findPubspecPath(File(fullDartOut).parent);
}
if (pubspecPath == null && appDirectory != null && appDirectory.isNotEmpty) {
final String fullAppDir =
(basePath != null && basePath.isNotEmpty && !appDirectory.startsWith(basePath))
(basePath != null && basePath.isNotEmpty && !isWithinOrEquals(basePath, appDirectory))
? path.join(basePath, appDirectory)
: appDirectory;
pubspecPath = findPubspecPath(Directory(fullAppDir));
Expand Down Expand Up @@ -2776,9 +2775,7 @@ String? _checkGeneratedBindingsPubspecVersion({
return null;
}
final String basePath = options.basePath ?? '';
final String fullDartOut = (basePath.isNotEmpty && !dartOutPath.startsWith(basePath))
? path.join(basePath, dartOutPath)
: dartOutPath;
final String fullDartOut = basePath.isNotEmpty ? path.join(basePath, dartOutPath) : dartOutPath;
final bindingsDartFile = File('${path.withoutExtension(fullDartOut)}$generatedFileSuffix');
if (!bindingsDartFile.existsSync()) {
return null;
Expand Down Expand Up @@ -2809,7 +2806,7 @@ String? _checkGeneratedBindingsPubspecVersion({

final String? pubspecPath = _findTargetPubspecPath(
appDirectory: appDirectory,
dartOutPath: fullDartOut,
dartOutPath: dartOutPath,
basePath: options.basePath,
);
if (pubspecPath == null) {
Expand Down
8 changes: 3 additions & 5 deletions packages/pigeon/lib/src/swift/ffigen_config_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,20 @@ import 'package:swiftgen/swiftgen.dart';
final String rawConfigDir =
(basePath.isNotEmpty &&
configDirSetting.isNotEmpty &&
!configDirSetting.startsWith(basePath))
!isWithinOrEquals(basePath, configDirSetting))
? path.posix.join(basePath, configDirSetting)
: (configDirSetting.isNotEmpty ? configDirSetting : basePath);

final String swiftOutPath = generatorOptions.swiftOptions.swiftOut;
final String rawSwiftOut =
(basePath.isNotEmpty && swiftOutPath.isNotEmpty && !swiftOutPath.startsWith(basePath))
final String rawSwiftOut = (basePath.isNotEmpty && swiftOutPath.isNotEmpty)
? path.posix.join(basePath, swiftOutPath)
: swiftOutPath;
final String fullSwiftOut = rawConfigDir.isNotEmpty
? makeRelative(rawSwiftOut, rawConfigDir)
: rawSwiftOut;

final String dartOutPath = generatorOptions.dartOut ?? '';
final String rawDartOut =
(basePath.isNotEmpty && dartOutPath.isNotEmpty && !dartOutPath.startsWith(basePath))
final String rawDartOut = (basePath.isNotEmpty && dartOutPath.isNotEmpty)
? path.posix.join(basePath, dartOutPath)
: dartOutPath;
final String fullDartOut = rawConfigDir.isNotEmpty
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: pigeon
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%3A%22p%3A+pigeon%22
version: 29.1.0 # This must match the version in lib/src/generator_tools.dart
version: 29.1.1 # This must match the version in lib/src/generator_tools.dart

environment:
sdk: ^3.11.0
Expand Down
20 changes: 20 additions & 0 deletions packages/pigeon/test/generator_tools_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,26 @@ void myMethod() {
);
});
});

group('isWithinOrEquals', () {
test('returns true for exact match and child paths', () {
expect(isWithinOrEquals('app', 'app'), isTrue);
expect(isWithinOrEquals('app', 'app/lib/messages.g.dart'), isTrue);
});

test('returns false when child only shares a string prefix with parent', () {
expect(isWithinOrEquals('app', 'app_bindings/lib/messages.g.dart'), isFalse);
expect(isWithinOrEquals('app', 'app_bindings'), isFalse);
});

test('normalizes trailing slashes, relative segments, and Windows separators', () {
expect(isWithinOrEquals('app/', 'app'), isTrue);
expect(isWithinOrEquals('app/', 'app/lib/'), isTrue);
expect(isWithinOrEquals('./app', 'app/lib'), isTrue);
expect(isWithinOrEquals(r'app\sub', r'app\sub\file.dart'), isTrue);
expect(isWithinOrEquals(r'app\sub', r'app\sub_other\file.dart'), isFalse);
});
});
test('deduceClassNameComponent extracts class name component', () {
expect(deduceClassNameComponent('ios/Classes/messages.g.swift'), 'messages');
expect(deduceClassNameComponent('Foo.swift'), 'Foo');
Expand Down
78 changes: 78 additions & 0 deletions packages/pigeon/test/pigeon_lib_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2709,6 +2709,84 @@ dependency_overrides:
},
);

test(
'FfigenConfigGeneratorAdapter and JnigenConfigGeneratorAdapter prepend basePath when paths share a prefix or directory name with basePath',
() {
final root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);

final ffiSink = StringBuffer();
const ffiAdapter = FfigenConfigGeneratorAdapter();
final InternalPigeonOptions ffiOptions = InternalPigeonOptions.fromPigeonOptions(
const PigeonOptions(
input: 'pigeons/messages.dart',
basePath: 'app',
dartOut: 'app/lib/src/messages.g.dart',
swiftOut: 'app_darwin/Messages.g.swift',
swiftOptions: SwiftOptions(useFfi: true, configDirectory: 'app_config'),
),
);
ffiAdapter.generate(ffiSink, ffiOptions, root, FileType.na);
final ffiCode = ffiSink.toString();
expect(ffiCode, contains("dartFile: Uri.file('../app/lib/src/messages.g.ffi.dart')"));
expect(ffiCode, contains("Uri.file('../app_darwin/Messages.g.swift')"));

final jniSink = StringBuffer();
const jniAdapter = JnigenConfigGeneratorAdapter();
final InternalPigeonOptions jniOptions = InternalPigeonOptions.fromPigeonOptions(
const PigeonOptions(
input: 'pigeons/messages.dart',
basePath: 'app',
dartOut: 'app_bindings/lib/src/messages.g.dart',
kotlinOut: 'app_android/Messages.g.kt',
kotlinOptions: KotlinOptions(
useJni: true,
appDirectory: 'app_example',
configDirectory: 'app_config',
),
),
);
jniAdapter.generate(jniSink, jniOptions, root, FileType.na);
final jniCode = jniSink.toString();
expect(jniCode, contains("path: Uri.file('../app_bindings/lib/src/messages.g.jni.dart')"));
expect(jniCode, contains("androidExample: Uri.directory('../app_example')"));
},
);

test(
'checkObjectiveCPubspecVersion resolves dartOut when it shares a string prefix with basePath',
() {
final Directory tempDir = Directory.systemTemp.createTempSync('pigeon_objc_prefix_test_');
try {
final baseDir = Directory('${tempDir.path}/app')..createSync(recursive: true);
File('${baseDir.path}/pubspec.yaml').writeAsStringSync('''
name: my_package
dependencies:
ffi: ^2.0.0
objective_c: ^9.5.0
''');
final libDir = Directory('${baseDir.path}/app_bindings/lib')..createSync(recursive: true);
File('${libDir.path}/messages.g.ffi.dart').writeAsStringSync(r'''
const _$objcVersionCheck = objc.ObjCVersionCheck(9, 6);
''');
final InternalPigeonOptions options = InternalPigeonOptions.fromPigeonOptions(
PigeonOptions(
input: 'foo.dart',
basePath: baseDir.path,
dartOut: 'app_bindings/lib/messages.g.dart',
swiftOut: 'app_bindings/lib/messages.g.swift',
swiftOptions: const SwiftOptions(useFfi: true),
dartPackageName: 'my_package',
),
);
final String? warning = checkObjectiveCPubspecVersion(options);
expect(warning, isNotNull);
expect(warning, contains('require package:objective_c ^9.6.0'));
} finally {
tempDir.deleteSync(recursive: true);
}
},
);

test('FfigenConfigGeneratorAdapter only lists the Setup class for host APIs', () {
final root = Root(
apis: <Api>[
Expand Down
Loading