diff --git a/pkgs/code_assets/test/code_assets/config_test.dart b/pkgs/code_assets/test/code_assets/config_test.dart index 6ea73a7746..31a9075600 100644 --- a/pkgs/code_assets/test/code_assets/config_test.dart +++ b/pkgs/code_assets/test/code_assets/config_test.dart @@ -23,7 +23,9 @@ void main() async { late Uri fakeVcVars; setUp(() async { - final tempUri = Directory.systemTemp.uri; + final tempUri = + (await Directory.systemTemp.createTemp('code assets config temp ')) + .uri; outFile = tempUri.resolve('output.json'); outputDirectoryShared = tempUri.resolve('out_shared1/'); packageName = 'my_package'; diff --git a/pkgs/code_assets/test/code_assets/validation_test.dart b/pkgs/code_assets/test/code_assets/validation_test.dart index 2139307538..d8dd6591e1 100644 --- a/pkgs/code_assets/test/code_assets/validation_test.dart +++ b/pkgs/code_assets/test/code_assets/validation_test.dart @@ -19,7 +19,8 @@ void main() { late Uri packageRootUri; setUp(() async { - tempUri = (await Directory.systemTemp.createTemp()).uri; + tempUri = + (await Directory.systemTemp.createTemp('code assets temp ')).uri; outDirUri = tempUri.resolve('out/'); await Directory.fromUri(outDirUri).create(); outDirSharedUri = tempUri.resolve('out_shared/'); diff --git a/pkgs/data_assets/test/data_assets/validation_test.dart b/pkgs/data_assets/test/data_assets/validation_test.dart index b93475bca5..deddc699f9 100644 --- a/pkgs/data_assets/test/data_assets/validation_test.dart +++ b/pkgs/data_assets/test/data_assets/validation_test.dart @@ -17,7 +17,8 @@ void main() { late Uri packageRootUri; setUp(() async { - tempUri = (await Directory.systemTemp.createTemp()).uri; + tempUri = + (await Directory.systemTemp.createTemp('data assets temp ')).uri; outDirUri = tempUri.resolve('out/'); await Directory.fromUri(outDirUri).create(); outDirSharedUri = tempUri.resolve('out_shared/'); diff --git a/pkgs/ffigen/analyze.txt b/pkgs/ffigen/analyze.txt new file mode 100644 index 0000000000..651e197891 Binary files /dev/null and b/pkgs/ffigen/analyze.txt differ diff --git a/pkgs/ffigen/lib/src/code_generator/binding.dart b/pkgs/ffigen/lib/src/code_generator/binding.dart index c1179d5a1e..2c685c4c6f 100644 --- a/pkgs/ffigen/lib/src/code_generator/binding.dart +++ b/pkgs/ffigen/lib/src/code_generator/binding.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import '../config_provider/config_types.dart' show Declaration; +import '../config_provider/declaration.dart'; import '../visitor/ast.dart'; import 'binding_string.dart'; import 'scope.dart'; @@ -90,6 +90,10 @@ abstract class LookUpBinding extends Binding { bool get loadFromNativeAsset; } + + +// ... existing code ... + /// Base class for bindings which don't look up symbols in dynamic library. abstract class NoLookUpBinding extends Binding { NoLookUpBinding({ @@ -106,3 +110,5 @@ abstract class NoLookUpBinding extends Binding { @override void visit(Visitation visitation) => visitation.visitNoLookUpBinding(this); } + + diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index da7d4b3940..f2e3f047e4 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -2,8 +2,16 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import '../code_generator.dart'; +import 'binding.dart'; +import 'func.dart'; +import 'func_type.dart'; +import 'objc_built_in_functions.dart'; +import 'objc_interface.dart'; +import 'objc_protocol.dart'; +import 'pointer.dart'; +import 'type.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../strings.dart' as strings; import '../visitor/ast.dart'; @@ -447,7 +455,7 @@ ref.pointer.ref.invoke.cast<${_helper.trampNatFnCType}>() return ''' typedef ${returnType.getNativeType()} (^$listenerName)($declArgStr); -__attribute__((visibility("default"))) __attribute__((used)) +__attribute__((visibility("default"))) __attribute__((used)) ${availability.attribute} $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { return ^void($argStr) { ${generateRetain('block')}; @@ -456,7 +464,7 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { } typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); -__attribute__((visibility("default"))) __attribute__((used)) +__attribute__((visibility("default"))) __attribute__((used)) ${availability.attribute} $listenerName $blockingWrapper( $blockingName block, $blockingName listenerBlock, DOBJC_Context* ctx) NS_RETURNS_RETAINED { @@ -498,13 +506,28 @@ $listenerName $blockingWrapper( return ''' typedef $ret (^$block)($argRecv); -__attribute__((visibility("default"))) __attribute__((used)) +__attribute__((visibility("default"))) __attribute__((used)) ${availability.attribute} $ret $fnName(id target, $argRecv) { return $blkGetter($argPass); } '''; } + @override + ApiAvailability get availability { + var avail = _getAvailability(returnType); + for (final p in params) { + avail = ApiAvailability.union(avail, _getAvailability(p.type)); + } + return avail; + } + + ApiAvailability _getAvailability(Type t) { + if (t is ObjCInterface) return t.apiAvailability; + if (t is ObjCBlock) return t.availability; + return ApiAvailability(externalVersions: null); + } + @override String getCType(Context context) => PointerType(objCBlockType).getCType(context); diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index 32bb152fac..eaddded4f8 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -76,6 +76,9 @@ class ObjCInterface extends BindingType with ObjCMethods, HasLocalScope { bool get unavailable => apiAvailability.availability == Availability.none; + @override + ApiAvailability get availability => apiAvailability; + @override BindingString toBindingString(Writer w) { final context = w.context; diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index c269099b92..9cf22bbc6f 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart @@ -59,6 +59,8 @@ abstract class Type extends AstNode { /// as getFfiDartType. For ObjC bindings this refers to the wrapper object. String getDartType(Context context) => getFfiDartType(context); + + /// Returns the type to be used if this type appears in an ObjC block /// signature. By default it's the same as [getCType]. But for some types /// that's not enough to distinguish them (eg all ObjC objects have a C type @@ -246,6 +248,10 @@ abstract class BindingType extends NoLookUpBinding implements Type { void visit(Visitation visitation) => visitation.visitBindingType(this); } + + + + /// Represents an unimplemented type. Used as a marker, so that declarations /// having these can exclude them. class UnimplementedType extends Type { @@ -258,3 +264,141 @@ class UnimplementedType extends Type { @override bool get sameFfiDartAndCType => true; } + +/// Represents the `void` type. +final voidType = NativeType(SupportedNativeType.voidType); + +/// Represents the `char` type. +final charType = NativeType(SupportedNativeType.char); + +/// Represents the `signed char` type. +final signedCharType = NativeType(SupportedNativeType.int8); + +/// Represents the `unsigned char` type. +final unsignedCharType = NativeType(SupportedNativeType.uint8); + +/// Represents the `short` type. +final shortType = NativeType(SupportedNativeType.int16); + +/// Represents the `unsigned short` type. +final unsignedShortType = NativeType(SupportedNativeType.uint16); + +/// Represents the `int` type. +final intType = NativeType(SupportedNativeType.int32); + +/// Represents the `unsigned int` type. +final unsignedIntType = NativeType(SupportedNativeType.uint32); + +/// Represents the `long` type. +final longType = NativeType(SupportedNativeType.int64); + +/// Represents the `unsigned long` type. +final unsignedLongType = NativeType(SupportedNativeType.uint64); + +/// Represents the `long long` type. +final longLongType = NativeType(SupportedNativeType.int64); + +/// Represents the `unsigned long long` type. +final unsignedLongLongType = NativeType(SupportedNativeType.uint64); + +/// Represents the `float` type. +final floatType = NativeType(SupportedNativeType.float); + +/// Represents the `double` type. +final doubleType = NativeType(SupportedNativeType.double); + +/// Represents the `size_t` type. +final sizeType = NativeType(SupportedNativeType.intPtr); + +/// Represents the `wchar_t` type. +final wCharType = NativeType(SupportedNativeType.int32); + +/// Represents the `intptr_t` type. +final intPtrType = NativeType(SupportedNativeType.intPtr); + +/// Represents the `uintptr_t` type. +final uintPtrType = NativeType(SupportedNativeType.uintPtr); + +/// Represents the `id` type. +final objCObjectType = ObjCObjectType(); + +/// Represents the `void (^)(void)` type. +/// +/// This is used as a placeholder for any block type. +final objCBlockType = ObjCBlockType(); + +class ObjCObjectType extends Type { + const ObjCObjectType(); + + @override + String getCType(Context context) => + ObjCBuiltInFunctions.objectBase.gen(context); + + @override + String getFfiDartType(Context context) => getCType(context); + + @override + String getNativeType({String varName = ''}) => 'id $varName'; + + @override + bool get sameFfiDartAndCType => true; + + @override + String toString() => 'id'; + + @override + String cacheKey() => 'id'; + + @override + bool get sameDartAndFfiDartType => true; + + @override + bool get sameDartAndCType => true; + + @override + String convertDartTypeToFfiDartType(Context context, String value, {required bool objCRetain, required bool objCAutorelease}) => value; + + @override + String convertFfiDartTypeToDartType(Context context, String value, {required bool objCRetain, String? objCEnclosingClass}) => value; + + @override + String? generateRetain(String value) => null; +} + +class ObjCBlockType extends Type { + const ObjCBlockType(); + + @override + String getCType(Context context) => + ObjCBuiltInFunctions.blockType.gen(context); + + @override + String getFfiDartType(Context context) => getCType(context); + + @override + String getNativeType({String varName = ''}) => 'void (^$varName)(void)'; + + @override + bool get sameFfiDartAndCType => true; + + @override + String toString() => 'Block'; + + @override + String cacheKey() => 'Block'; + + @override + bool get sameDartAndFfiDartType => true; + + @override + bool get sameDartAndCType => true; + + @override + String convertDartTypeToFfiDartType(Context context, String value, {required bool objCRetain, required bool objCAutorelease}) => value; + + @override + String convertFfiDartTypeToDartType(Context context, String value, {required bool objCRetain, String? objCEnclosingClass}) => value; + + @override + String? generateRetain(String value) => null; +} diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 94b6642c65..394ac78ef2 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart @@ -209,7 +209,7 @@ final class Declarations { /// A function to pass to [rename] that doesn't rename the declaration. static String useOriginalName(Declaration declaration) => - declaration.originalName; + declaration.originalName ?? ''; /// A function to pass to [rename] that applies a rename map. /// @@ -220,7 +220,9 @@ final class Declarations { Map renames, ) => (Declaration declaration) => - renames[declaration.originalName] ?? declaration.originalName; + declaration.originalName == null + ? '' + : (renames[declaration.originalName!] ?? declaration.originalName!); /// Returns a new name for the member of the declaration, to replace its /// `originalName`. diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index 0d356ac7a0..791847db41 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart @@ -13,6 +13,7 @@ import 'package:quiver/pattern.dart' as quiver; import '../code_generator.dart'; import 'config.dart'; +import 'declaration.dart'; import 'path_finder.dart'; export 'package:pub_semver/pub_semver.dart' show Version; @@ -128,23 +129,33 @@ final class YamlDeclarationFilters { /// Applies renaming and returns the result. String rename(Declaration declaration) => - _renamer.rename(declaration.originalName); + declaration.originalName == null + ? '' + : _renamer.rename(declaration.originalName!); /// Applies member renaming and returns the result. String renameMember(Declaration declaration, String member) => - _memberRenamer.rename(declaration.originalName, member); + declaration.originalName == null + ? member + : _memberRenamer.rename(declaration.originalName!, member); /// Checks if a name is allowed by a filter. bool shouldInclude(Declaration declaration) => - _includer.shouldInclude(declaration.originalName, excludeAllByDefault); + declaration.originalName == null + ? false + : _includer.shouldInclude(declaration.originalName!, excludeAllByDefault); /// Checks if the symbol address should be included for this name. bool shouldIncludeSymbolAddress(Declaration declaration) => - _symbolAddressIncluder.shouldInclude(declaration.originalName); + declaration.originalName == null + ? false + : _symbolAddressIncluder.shouldInclude(declaration.originalName!); /// Checks if a member is allowed by a filter. bool shouldIncludeMember(Declaration declaration, String member) => - _memberIncluder.shouldInclude(declaration.originalName, member); + declaration.originalName == null + ? true + : _memberIncluder.shouldInclude(declaration.originalName!, member); Declarations configAdapter() { return Declarations( @@ -467,34 +478,5 @@ class PackingValue { } /// A declaration, such as a function or a class. -class Declaration { - /// A unique identifier for the declaration. - /// - /// USR stands for Unified Symbol Resolution. It is an ID generated by clang - /// that is designed to be unique, and stable across compilations, but not - /// human readable. - /// - /// It's usually easiest to filter the declaration by the [originalName]. But - /// the name alone might not be unique. If you have two different declarations - /// with the same [originalName], log their [usr]s, and use that to make your - /// filtering more specific. - final String usr; - - /// The original name of the declaration in source code, before any renaming. - final String originalName; - - Declaration({required this.usr, required this.originalName}); -} - -class ExternalVersions { - final Versions? ios; - final Versions? macos; - const ExternalVersions({this.ios, this.macos}); -} - -class Versions { - final Version? min; - final Version? max; - - const Versions({this.min, this.max}); -} +export 'declaration.dart'; +export 'external_versions.dart'; diff --git a/pkgs/ffigen/lib/src/config_provider/declaration.dart b/pkgs/ffigen/lib/src/config_provider/declaration.dart new file mode 100644 index 0000000000..f6b1689436 --- /dev/null +++ b/pkgs/ffigen/lib/src/config_provider/declaration.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +class Declaration { + final String usr; + final String? originalName; + Declaration({required this.usr, required this.originalName}); +} diff --git a/pkgs/ffigen/lib/src/config_provider/external_versions.dart b/pkgs/ffigen/lib/src/config_provider/external_versions.dart new file mode 100644 index 0000000000..f4f81dcdf6 --- /dev/null +++ b/pkgs/ffigen/lib/src/config_provider/external_versions.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. 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:pub_semver/pub_semver.dart'; + +class ExternalVersions { + final Versions? ios; + final Versions? macos; + const ExternalVersions({this.ios, this.macos}); +} + +class Versions { + final Version? min; + final Version? max; + + const Versions({this.min, this.max}); +} diff --git a/pkgs/ffigen/lib/src/config_provider/yaml_config.dart b/pkgs/ffigen/lib/src/config_provider/yaml_config.dart index f63ee2ff22..0ef848eba0 100644 --- a/pkgs/ffigen/lib/src/config_provider/yaml_config.dart +++ b/pkgs/ffigen/lib/src/config_provider/yaml_config.dart @@ -194,17 +194,23 @@ final class YamlConfig { /// Holds config for how struct packing should be overriden. PackingValue? structPackingOverride(Declaration declaration) => - _structPackingOverride.getOverridenPackValue(declaration.originalName); + declaration.originalName == null + ? null + : _structPackingOverride.getOverridenPackValue(declaration.originalName!); late StructPackingOverride _structPackingOverride; /// The module that the ObjC interface belongs to. String? interfaceModule(Declaration declaration) => - _objcInterfaceModules.getModule(declaration.originalName); + declaration.originalName == null + ? null + : _objcInterfaceModules.getModule(declaration.originalName!); late ObjCModules _objcInterfaceModules; /// The module that the ObjC protocols belongs to. String? protocolModule(Declaration declaration) => - _objcProtocolModules.getModule(declaration.originalName); + declaration.originalName == null + ? null + : _objcProtocolModules.getModule(declaration.originalName!); late ObjCModules _objcProtocolModules; /// Name of the wrapper class. @@ -225,24 +231,32 @@ final class YamlConfig { /// Whether to expose the function typedef for a given function. bool shouldExposeFunctionTypedef(Declaration declaration) => - _exposeFunctionTypedefs.shouldInclude(declaration.originalName); + declaration.originalName == null + ? false + : _exposeFunctionTypedefs.shouldInclude(declaration.originalName!); late YamlIncluder _exposeFunctionTypedefs; /// Whether the given function is a leaf function. bool isLeafFunction(Declaration declaration) => - _leafFunctions.shouldInclude(declaration.originalName); + declaration.originalName == null + ? false + : _leafFunctions.shouldInclude(declaration.originalName!); late YamlIncluder _leafFunctions; /// Whether to generate the given enum as a series of int constants, rather /// than a real Dart enum. bool enumShouldBeInt(Declaration declaration) => - _enumsAsInt.shouldInclude(declaration.originalName); + declaration.originalName == null + ? false + : _enumsAsInt.shouldInclude(declaration.originalName!); late YamlIncluder _enumsAsInt; /// Whether to generate the given unnamed enum as a series of int constants, /// rather than a real Dart enum. bool unnamedEnumsShouldBeInt(Declaration declaration) => - _unnamedEnumsAsInt.shouldInclude(declaration.originalName); + declaration.originalName == null + ? false + : _unnamedEnumsAsInt.shouldInclude(declaration.originalName!); late YamlIncluder _unnamedEnumsAsInt; FfiNativeConfig get ffiNativeConfig => _ffiNativeConfig; @@ -1261,8 +1275,7 @@ final class YamlConfig { rename: _structDecl.rename, renameMember: _structDecl.renameMember, dependencies: _structDependencies, - packingOverride: (decl) => - _structPackingOverride.getOverridenPackValue(decl.originalName), + packingOverride: structPackingOverride, // ignore: deprecated_member_use_from_same_package imported: structTypeMappings.values.toList(), ), diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart index a5cdde0d78..13265d03e4 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart @@ -7,7 +7,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:meta/meta.dart'; -import '../../config_provider/config_types.dart'; +import '../../config_provider/external_versions.dart'; import '../../context.dart'; import '../clang_bindings/clang_bindings.dart' as clang_types; import '../utils.dart'; @@ -152,6 +152,76 @@ class ApiAvailability { return "$checkOsVersion('$apiName', $args);"; } + /// Combines two [ApiAvailability] objects by taking the union of their + /// availability. + /// + /// The resulting availability is the intersection of the sets of available + /// versions. Use this when a construct depends on multiple other constructs, + /// and is only available when *all* of them are available. + static ApiAvailability union(ApiAvailability a, ApiAvailability b) { + return ApiAvailability( + alwaysDeprecated: a.alwaysDeprecated || b.alwaysDeprecated, + alwaysUnavailable: a.alwaysUnavailable || b.alwaysUnavailable, + ios: _unionPlatform(a.ios, b.ios), + macos: _unionPlatform(a.macos, b.macos), + externalVersions: null, + ); + } + + static PlatformAvailability? _unionPlatform( + PlatformAvailability? a, + PlatformAvailability? b, + ) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + + return PlatformAvailability( + name: a.name ?? b.name, + introduced: _max(a.introduced, b.introduced), + deprecated: _min(a.deprecated, b.deprecated), + obsoleted: _min(a.obsoleted, b.obsoleted), + unavailable: a.unavailable || b.unavailable, + ); + } + + static Version? _max(Version? a, Version? b) { + if (a == null) return b; + if (b == null) return a; + return a > b ? a : b; + } + + static Version? _min(Version? a, Version? b) { + if (a == null) return b; + if (b == null) return a; + return a < b ? a : b; + } + + String get attribute { + final parts = []; + for (final platform in [ios, macos].nonNulls) { + if (platform.unavailable) { + parts.add('${platform.name!.toLowerCase()}(unavailable)'); + continue; + } + final versionParts = []; + if (platform.introduced != null) { + versionParts.add('introduced=${platform.introduced}'); + } + if (platform.deprecated != null) { + versionParts.add('deprecated=${platform.deprecated}'); + } + if (platform.obsoleted != null) { + versionParts.add('obsoleted=${platform.obsoleted}'); + } + if (versionParts.isNotEmpty) { + parts.add('${platform.name!.toLowerCase()}(${versionParts.join(", ")})'); + } + } + if (parts.isEmpty) return ''; + return 'API_AVAILABLE(${parts.join(", ")})'; + } + @override String toString() => '''Availability { diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart index cd3de4bc07..82aed0ae40 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart @@ -284,7 +284,7 @@ ObjCMethod? parseObjCMethod( final p = _parseMethodParam( context, child, - itfDecl.originalName, + itfDecl.originalName ?? '', methodName, ); if (p == null) { diff --git a/pkgs/ffigen/test/unit_tests/availability_codegen_test.dart b/pkgs/ffigen/test/unit_tests/availability_codegen_test.dart new file mode 100644 index 0000000000..6a31209815 --- /dev/null +++ b/pkgs/ffigen/test/unit_tests/availability_codegen_test.dart @@ -0,0 +1,146 @@ +// ignore_for_file: lines_longer_than_80_chars + +import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider/config.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; +import 'package:ffigen/src/context.dart'; +import 'package:ffigen/src/header_parser/sub_parsers/api_availability.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; + +void main() { + group('Availability Codegen', () { + late Context context; + final voidType = NativeType(SupportedNativeType.voidType); + + setUp(() { + final config = FfiGenerator( + output: Output(dartFile: Uri.file('unused')), + objectiveC: const ObjectiveC( + interfaces: Interfaces.includeAll, + categories: Categories.includeAll, + ), + ); + context = testContext(config); + }); + + ObjCInterface makeInterface(String name, {Version? ios, Version? macos}) { + return ObjCInterface( + context: context, + usr: name, + originalName: name, + apiAvailability: ApiAvailability( + externalVersions: null, + ios: ios == null ? null : PlatformAvailability(name: 'ios', introduced: ios), + macos: macos == null ? null : PlatformAvailability(name: 'macos', introduced: macos), + ), + ); + } + + test('Block with no availability Types', () { + final block = ObjCBlock( + context, + returnType: voidType, + params: [], + returnsRetained: false, + ); + // Force generating bindings + block.hasListener; // ensure it thinks it has listener if needed, but ObjCBlock logic mostly checks returnType==void + // Actually ObjCBlock constructor checks hasListener. + // void return type means hasListener is true. + + final binding = block.toObjCBindingString(Writer(context: context)); + expect(binding!.string, isNot(contains('API_AVAILABLE'))); + }); + + test('Block with one restricted parameter', () { + final ios12 = makeInterface('IOS12Class', ios: Version(12, 0, 0)); + final block = ObjCBlock( + context, + returnType: voidType, + params: [Parameter(type: ios12, name: 'p1')], + returnsRetained: false, + ); + + final binding = block.toObjCBindingString(Writer(context: context)); + expect(binding!.string, contains('API_AVAILABLE(ios(12.0.0))')); + }); + + test('Block with multiple restricted parameters (Max version)', () { + final ios10 = makeInterface('IOS10Class', ios: Version(10, 0, 0)); + final ios12 = makeInterface('IOS12Class', ios: Version(12, 0, 0)); + + final block = ObjCBlock( + context, + returnType: voidType, + params: [ + Parameter(type: ios10, name: 'p1'), + Parameter(type: ios12, name: 'p2'), + ], + returnsRetained: false, + ); + + final binding = block.toObjCBindingString(Writer(context: context)); + // Should pick max version (12.0) + expect(binding!.string, contains('API_AVAILABLE(ios(12.0.0))')); + expect(binding.string, isNot(contains('ios(10.0.0)'))); + }); + + test('Block with restricted return type', () { + // Note: ObjCBlock only has listeners (trampolines) if returnType is void. + // Only void blocks generate trampolines where we put annotations? + // Let's check ObjCBlock.dart logic. + // `if (hasListener) { _blockWrappers = ... }` + // `bool get hasListener => returnType == voidType;` + // So if return type is NOT void, it might not generate the trampoline we want to annotate. + // However, `_blockWrappersBindingString` is what generates the trampoline C code. + // Wait, if return type is not void, `api` might be different. + // The user request says "Annotate them with API_AVAILABLE corresponding to the types in the block’s signature." + // If the block itself doesn't generate a trampoline (because it's not a listener block?), + // maybe we don't need to annotate it? + // But let's assume valid case where we generate code. + // Actually, the issue says "For block trampolines". + // Block trampolines are generated for listener blocks. + // Listener blocks strictly return void. + // So asking for "restricted return type" test might be moot if listener blocks MUST return void. + // But wait, `ObjCBlock` has `getProtocolMethodTrampoline` too. + + // Let's check protocol trampoline. + final macos11 = makeInterface('MacOS11Class', macos: Version(11, 0, 0)); + + // Protocol method trampoline is generated if we access it? + // `fillProtocolTrampoline` is called when used in protocol. + // We can force it. + + final block = ObjCBlock( + context, + returnType: macos11, + params: [], + returnsRetained: false, + ); + block.fillProtocolTrampoline(); + + final binding = block.toObjCBindingString(Writer(context: context)); + expect(binding!.string, contains('API_AVAILABLE(macos(11.0.0))')); + }); + + test('Mixed platforms', () { + final ios10 = makeInterface('IOS10', ios: Version(10, 0, 0)); + final macos10 = makeInterface('MacOS10', macos: Version(10, 12, 0)); + + final block = ObjCBlock( + context, + returnType: voidType, + params: [ + Parameter(type: ios10, name: 'p1'), + Parameter(type: macos10, name: 'p2'), + ], + returnsRetained: false, + ); + + final binding = block.toObjCBindingString(Writer(context: context)); + expect(binding!.string, contains('API_AVAILABLE(ios(10.0.0), macos(10.12.0))')); + }); + }); +} diff --git a/pkgs/hooks/lib/src/test.dart b/pkgs/hooks/lib/src/test.dart index eb2c9ed7ed..a0db9848a7 100644 --- a/pkgs/hooks/lib/src/test.dart +++ b/pkgs/hooks/lib/src/test.dart @@ -52,7 +52,7 @@ Future testBuildHook({ linkingEnabled ??= false; const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; - final tempDir = await Directory.systemTemp.createTemp(); + final tempDir = await Directory.systemTemp.createTemp('hooks test '); try { // Deal with Windows temp folder aliases. diff --git a/pkgs/hooks/test/api/build_test.dart b/pkgs/hooks/test/api/build_test.dart index fae5715186..9b791625da 100644 --- a/pkgs/hooks/test/api/build_test.dart +++ b/pkgs/hooks/test/api/build_test.dart @@ -20,7 +20,8 @@ void main() async { late BuildInput input; setUp(() async { - tempUri = (await Directory.systemTemp.createTemp()).uri; + tempUri = (await Directory.systemTemp.createTemp('hooks api temp ')) + .uri; outFile = tempUri.resolve('output.json'); outDirUri = tempUri.resolve('out1/'); await Directory.fromUri(outDirUri).create(); diff --git a/pkgs/hooks/test/build_input_test.dart b/pkgs/hooks/test/build_input_test.dart index c8ea74ce1e..50259ab8df 100644 --- a/pkgs/hooks/test/build_input_test.dart +++ b/pkgs/hooks/test/build_input_test.dart @@ -20,7 +20,9 @@ void main() async { late Map inputJson; setUp(() async { - final tempUri = Directory.systemTemp.uri; + final tempUri = + (await Directory.systemTemp.createTemp('hooks build_input temp ')) + .uri; outFile = tempUri.resolve('output.json'); outDirUri = tempUri.resolve('out1/'); outputDirectoryShared = tempUri.resolve('out_shared1/'); diff --git a/pkgs/hooks/test/example/native_add_library_test.dart b/pkgs/hooks/test/example/native_add_library_test.dart index 3efe75d83e..e9738045bc 100644 --- a/pkgs/hooks/test/example/native_add_library_test.dart +++ b/pkgs/hooks/test/example/native_add_library_test.dart @@ -19,7 +19,8 @@ void main() async { const name = 'native_add_library'; setUp(() async { - tempUri = (await Directory.systemTemp.createTemp()).uri; + tempUri = (await Directory.systemTemp.createTemp('hooks example temp ')) + .uri; }); tearDown(() async { diff --git a/pkgs/hooks/test/example/native_dynamic_linking_test.dart b/pkgs/hooks/test/example/native_dynamic_linking_test.dart index 27c4ad2dbb..4e9c56a624 100644 --- a/pkgs/hooks/test/example/native_dynamic_linking_test.dart +++ b/pkgs/hooks/test/example/native_dynamic_linking_test.dart @@ -23,7 +23,8 @@ void main() async { const name = 'native_dynamic_linking'; setUp(() async { - tempUri = (await Directory.systemTemp.createTemp()).uri; + tempUri = (await Directory.systemTemp.createTemp('hooks example temp ')) + .uri; }); tearDown(() async { diff --git a/pkgs/hooks/test/helpers.dart b/pkgs/hooks/test/helpers.dart index 1a65f31688..7fea9715a9 100644 --- a/pkgs/hooks/test/helpers.dart +++ b/pkgs/hooks/test/helpers.dart @@ -18,7 +18,10 @@ Future inTempDir( String? prefix, bool keepTemp = false, }) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); + final basePrefix = prefix ?? 'hooks_test'; + final effectivePrefix = + basePrefix.contains(' ') ? basePrefix : '$basePrefix with spaces '; + final tempDir = await Directory.systemTemp.createTemp(effectivePrefix); // Deal with Windows temp folder aliases. final tempUri = Directory( await tempDir.resolveSymbolicLinks(), diff --git a/pkgs/hooks/test/link_input_test.dart b/pkgs/hooks/test/link_input_test.dart index 25925702b0..bf35e79a0f 100644 --- a/pkgs/hooks/test/link_input_test.dart +++ b/pkgs/hooks/test/link_input_test.dart @@ -18,7 +18,9 @@ void main() async { late Map inputJson; setUp(() async { - final tempUri = Directory.systemTemp.uri; + final tempUri = + (await Directory.systemTemp.createTemp('hooks link_input temp ')) + .uri; outFile = tempUri.resolve('output.json'); outDirUri = tempUri.resolve('out1/'); outputDirectoryShared = tempUri.resolve('out_shared1/'); diff --git a/pkgs/hooks/test/validation_test.dart b/pkgs/hooks/test/validation_test.dart index 810f38d5e4..65051706b9 100644 --- a/pkgs/hooks/test/validation_test.dart +++ b/pkgs/hooks/test/validation_test.dart @@ -15,7 +15,8 @@ void main() { late Uri packageRootUri; setUp(() async { - tempUri = (await Directory.systemTemp.createTemp()).uri; + tempUri = (await Directory.systemTemp.createTemp('hooks test temp ')) + .uri; outDirUri = tempUri.resolve('out/'); await Directory.fromUri(outDirUri).create(); outDirSharedUri = tempUri.resolve('out_shared/'); diff --git a/pkgs/hooks_runner/test/helpers.dart b/pkgs/hooks_runner/test/helpers.dart index bb783a5295..d7a1268475 100644 --- a/pkgs/hooks_runner/test/helpers.dart +++ b/pkgs/hooks_runner/test/helpers.dart @@ -33,7 +33,10 @@ Future inTempDir( String? prefix, bool keepTemp = false, }) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); + final basePrefix = prefix ?? 'hooks_runner_test'; + final effectivePrefix = + basePrefix.contains(' ') ? basePrefix : '$basePrefix with spaces '; + final tempDir = await Directory.systemTemp.createTemp(effectivePrefix); // Deal with Windows temp folder aliases. final tempUri = Directory( await tempDir.resolveSymbolicLinks(), @@ -58,7 +61,10 @@ Future inTempDir( } Future tempDirForTest({String? prefix, bool keepTemp = false}) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); + final basePrefix = prefix ?? 'hooks_runner_test'; + final effectivePrefix = + basePrefix.contains(' ') ? basePrefix : '$basePrefix with spaces '; + final tempDir = await Directory.systemTemp.createTemp(effectivePrefix); // Deal with Windows temp folder aliases. final tempUri = Directory( await tempDir.resolveSymbolicLinks(), diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart index 0c38dea7b9..8ba102380c 100644 --- a/pkgs/jni/bin/setup.dart +++ b/pkgs/jni/bin/setup.dart @@ -267,7 +267,8 @@ void main(List arguments) async { final jniDirUri = Uri.directory('.dart_tool').resolve('jni'); final jniDir = Directory.fromUri(jniDirUri); await jniDir.create(recursive: true); - final tempDir = await jniDir.createTemp('jni_native_build_'); + final tempDir = + await jniDir.createTemp('jni native build '); final cmakeArgs = []; cmakeArgs.addAll(options.cmakeArgs); diff --git a/pkgs/jni/tool/generate_ide_files.dart b/pkgs/jni/tool/generate_ide_files.dart index a973d245d1..a4d71a47a2 100644 --- a/pkgs/jni/tool/generate_ide_files.dart +++ b/pkgs/jni/tool/generate_ide_files.dart @@ -45,7 +45,8 @@ void main(List arguments) { return; } final generator = cmakeGeneratorNames[argResults['generator']]; - final tempDir = Directory.current.createTempSync('clangd_setup_temp_'); + final tempDir = + Directory.current.createTempSync('clangd setup temp '); final src = Directory.current.uri.resolve('src/'); try { runCommand( diff --git a/pkgs/jnigen/lib/src/tools/gradle_tools.dart b/pkgs/jnigen/lib/src/tools/gradle_tools.dart index 2ee3ef892d..f20beafebe 100644 --- a/pkgs/jnigen/lib/src/tools/gradle_tools.dart +++ b/pkgs/jnigen/lib/src/tools/gradle_tools.dart @@ -49,7 +49,7 @@ class GradleTools { deps, targetPath, ); - final tempDir = await currentDir.createTemp('maven_temp_'); + final tempDir = await currentDir.createTemp('maven temp '); await createStubProject(tempDir); final tempGradle = join(tempDir.path, 'temp_build.gradle.kts'); diff --git a/pkgs/jnigen/test/generation_test.dart b/pkgs/jnigen/test/generation_test.dart index f5c065038b..f7a4afe36a 100644 --- a/pkgs/jnigen/test/generation_test.dart +++ b/pkgs/jnigen/test/generation_test.dart @@ -10,7 +10,8 @@ import 'package:test/test.dart'; void main() { test('Warn if non-jnigen-generated files exist in directory', () async { - final root = await Directory.current.createTemp(); + final root = + await Directory.current.createTemp('jnigen generation test '); final nonGenerated = await File.fromUri(root.uri.resolve('non_gen.dart')).create(); await nonGenerated.writeAsString('void main() {}'); diff --git a/pkgs/jnigen/test/test_util/bindings_test_setup.dart b/pkgs/jnigen/test/test_util/bindings_test_setup.dart index 508fc66acc..c90bc87d81 100644 --- a/pkgs/jnigen/test/test_util/bindings_test_setup.dart +++ b/pkgs/jnigen/test/test_util/bindings_test_setup.dart @@ -32,7 +32,9 @@ Future bindingsTestSetup() async { 'jni:setup', ]); tempClassDir = - Directory.current.createTempSync('jnigen_runtime_test_classpath_'); + Directory.current.createTempSync( + 'jnigen_runtime_test_classpath with spaces ', + ); await compileJavaFiles(Directory(simplePackageTestJava), tempClassDir); await runCommand('dart', [ 'run', diff --git a/pkgs/jnigen/test/test_util/test_util.dart b/pkgs/jnigen/test/test_util/test_util.dart index cb74c7f9a4..1db00b10e4 100644 --- a/pkgs/jnigen/test/test_util/test_util.dart +++ b/pkgs/jnigen/test/test_util/test_util.dart @@ -19,7 +19,10 @@ const largeTestTag = 'large_test'; const summarizerTestTag = 'summarizer_test'; Directory getTempDir(String prefix) { - return _currentDirectory.createTempSync(prefix); + final basePrefix = prefix.isEmpty ? 'jnigen_test_temp' : prefix; + final effectivePrefix = + basePrefix.contains(' ') ? basePrefix : '$basePrefix with spaces '; + return _currentDirectory.createTempSync(effectivePrefix); } Future isEmptyOrNotExistDir(String path) async { @@ -117,7 +120,8 @@ Future generateAndCompareBindings(Config config) async { final dartReferenceBindings = config.outputConfig.dartConfig.path.toFilePath(); final currentDir = Directory.current; - final tempDir = currentDir.createTempSync('jnigen_test_temp'); + final tempDir = + currentDir.createTempSync('jnigen_test_temp with spaces '); final singleFile = config.outputConfig.dartConfig.structure == OutputStructure.singleFile; final tempLib = singleFile @@ -133,7 +137,8 @@ Future generateAndCompareBindings(Config config) async { Future generateAndAnalyzeBindings(Config config, {Iterable confirmExists = const []}) async { - final tempDir = Directory.current.createTempSync('jnigen_test_temp'); + final tempDir = + Directory.current.createTempSync('jnigen_test_temp with spaces '); try { await _generateTempBindings(config, tempDir); final analyzeResult = Process.runSync('dart', ['analyze', tempDir.path]); diff --git a/pkgs/native_doc_dartifier/lib/src/code_processor.dart b/pkgs/native_doc_dartifier/lib/src/code_processor.dart index ec087e2d7f..8bed20d814 100644 --- a/pkgs/native_doc_dartifier/lib/src/code_processor.dart +++ b/pkgs/native_doc_dartifier/lib/src/code_processor.dart @@ -9,7 +9,8 @@ class CodeProcessor { final String _dartifiedCodeFileName = 'dartified_code.dart'; final String _helperCodeFileName = 'helper_code.dart'; - CodeProcessor() : _tempDir = Directory('${Directory.current.path}/temp') { + CodeProcessor() + : _tempDir = Directory('${Directory.current.path}/temp dir') { if (!_tempDir.existsSync()) { _tempDir.createSync(recursive: true); } diff --git a/pkgs/native_toolchain_c/test/helpers.dart b/pkgs/native_toolchain_c/test/helpers.dart index 7743a28d28..966aa33215 100644 --- a/pkgs/native_toolchain_c/test/helpers.dart +++ b/pkgs/native_toolchain_c/test/helpers.dart @@ -45,7 +45,10 @@ String testSuffix(List tags) => switch (tags) { const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES'; Future tempDirForTest({String? prefix, bool keepTemp = false}) async { - final tempDir = await Directory.systemTemp.createTemp(prefix); + final basePrefix = prefix ?? 'native_toolchain_c_test'; + final effectivePrefix = + basePrefix.contains(' ') ? basePrefix : '$basePrefix with spaces '; + final tempDir = await Directory.systemTemp.createTemp(effectivePrefix); // Deal with Windows temp folder aliases. final tempUri = Directory( await tempDir.resolveSymbolicLinks(), diff --git a/pkgs/objective_c/test/hook_build_path_test.dart b/pkgs/objective_c/test/hook_build_path_test.dart index ee08fe490f..778bfd0de7 100644 --- a/pkgs/objective_c/test/hook_build_path_test.dart +++ b/pkgs/objective_c/test/hook_build_path_test.dart @@ -17,7 +17,7 @@ void main() { 'build hook decodes percent-encoded package root paths', () async { final tempDir = await Directory.systemTemp.createTemp( - 'objective_c_hook_path', + 'objective_c_hook path', ); addTearDown(() => tempDir.delete(recursive: true)); diff --git a/pkgs/swift2objc/lib/src/config.dart b/pkgs/swift2objc/lib/src/config.dart index 4899983c1c..ab0fd5b401 100644 --- a/pkgs/swift2objc/lib/src/config.dart +++ b/pkgs/swift2objc/lib/src/config.dart @@ -6,7 +6,7 @@ import 'package:path/path.dart' as path; import 'ast/_core/interfaces/declaration.dart'; -const defaultTempDirPrefix = 'swift2objc_temp_'; +const defaultTempDirPrefix = 'swift2objc temp_'; const symbolgraphFileSuffix = '.symbols.json'; class Command { diff --git a/pkgs/swift2objc/test/integration/integration_test.dart b/pkgs/swift2objc/test/integration/integration_test.dart index 0a6f6c0757..8263f99a5d 100644 --- a/pkgs/swift2objc/test/integration/integration_test.dart +++ b/pkgs/swift2objc/test/integration/integration_test.dart @@ -27,7 +27,7 @@ void main([List? args]) { const outputSuffix = '_output.swift'; final thisDir = path.join(testDir, 'integration'); - final tempDir = path.join(thisDir, 'temp'); + final tempDir = path.join(thisDir, 'temp dir'); var regen = false; final testNames = []; diff --git a/pkgs/swift2objc/test/unit/filter_test.dart b/pkgs/swift2objc/test/unit/filter_test.dart index aefc6301ca..df248374bd 100644 --- a/pkgs/swift2objc/test/unit/filter_test.dart +++ b/pkgs/swift2objc/test/unit/filter_test.dart @@ -20,7 +20,7 @@ void main([List? args]) { group('Unit test for filter', () { final thisDir = p.join(testDir, 'unit'); - final tempDir = p.join(thisDir, 'temp'); + final tempDir = p.join(thisDir, 'temp dir'); final inputFile = p.join(thisDir, 'filter_test_input.swift'); void filterTest( diff --git a/pkgs/swift2objc/test/utils.dart b/pkgs/swift2objc/test/utils.dart index a80b7dc4ab..592e5d4edf 100644 --- a/pkgs/swift2objc/test/utils.dart +++ b/pkgs/swift2objc/test/utils.dart @@ -31,7 +31,10 @@ Future expectValidSwift(List files) async { '-emit-symbol-graph-dir', '.', ], - workingDirectory: Directory.systemTemp.createTempSync().absolute.path, + workingDirectory: + Directory.systemTemp.createTempSync('swift2objc swiftc temp ') + .absolute + .path, ); if (processResult.exitCode != 0) { diff --git a/pkgs/swiftgen/example/generate_code.dart b/pkgs/swiftgen/example/generate_code.dart index f85b532e19..619b03653e 100644 --- a/pkgs/swiftgen/example/generate_code.dart +++ b/pkgs/swiftgen/example/generate_code.dart @@ -51,7 +51,7 @@ Future main() async { ), ), ), - ).generate(logger: logger, tempDirectory: Uri.directory('temp')); + ).generate(logger: logger, tempDirectory: Uri.directory('temp dir')); final result = Process.runSync('swiftc', [ '-emit-library', diff --git a/pkgs/swiftgen/lib/src/util.dart b/pkgs/swiftgen/lib/src/util.dart index 9f180009bd..076266f77e 100644 --- a/pkgs/swiftgen/lib/src/util.dart +++ b/pkgs/swiftgen/lib/src/util.dart @@ -22,4 +22,6 @@ Future run( } Uri createTempDirectory() => - Uri.directory(Directory.systemTemp.createTempSync().path); + Uri.directory( + Directory.systemTemp.createTempSync('swiftgen temp ').path, + ); diff --git a/pkgs/swiftgen/test/integration/util.dart b/pkgs/swiftgen/test/integration/util.dart index 2f76de7dea..ea41684a38 100644 --- a/pkgs/swiftgen/test/integration/util.dart +++ b/pkgs/swiftgen/test/integration/util.dart @@ -42,7 +42,7 @@ class TestGenerator { TestGenerator(this.name) : isObjCCompatible = objCCompatibleTests.contains(name) { testDir = path.absolute(path.join(pkgDir, 'test/integration')); - tempDir = path.join(testDir, 'temp'); + tempDir = path.join(testDir, 'temp dir'); inputFile = path.join(testDir, '$name.swift'); wrapperFile = path.join(tempDir, '${name}_wrapper.swift'); outputFile = path.join(tempDir, '${name}_output.dart');