blob: d2ff5f86c01cef63312d8ca1a38de2888092e94c [file] [log] [blame] [edit]
// Copyright (c) 2025, 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:ffigen/ffigen.dart' as ffigen;
import 'package:swift2objc/swift2objc.dart' as swift2objc;
/// Config options for swiftgen. Takes a Swift API as input, and generates Dart
/// bindings to interop with that API.
///
/// Dart's interop with Swift is built on Objective-C interop. Swift APIs can be
/// accessed through Objective-C, and Dart can access Objective-C using FFIgen.
///
/// Swift -> Objective-C -> Dart
///
/// The Swift -> Objective-C step requires that the Swift API is compatible with
/// Objective-C. If your API is compatible, you can use
/// [ObjCCompatibleSwiftFileInput]. Otherwise, you'll need a wrapper layer that
/// is compatible. This wrapper will be automatically generated if you use
/// [SwiftFileInput] or [SwiftModuleInput]. The generated wrapper must be
/// compiled into your final app or plugin, along with any bindings generated
/// by FFIgen.
class SwiftGenerator {
/// The target OS and SDK version to compile against.
final Target target;
/// The input Swift APIs.
final List<SwiftGenInput> inputs;
/// Determines whether a Swift API included in the generated bindings.
final bool Function(swift2objc.Declaration declaration) include;
/// Configuration for the output files.
final Output output;
/// Configuration for the FFIgen pass.
final FfiGeneratorOptions ffigen;
static bool _defaultInclude(swift2objc.Declaration _) => true;
const SwiftGenerator({
required this.target,
required this.inputs,
this.include = _defaultInclude,
required this.output,
required this.ffigen,
});
}
/// Configuration for the output files.
class Output {
/// Configuration for the Objective-C compatible wrapper API.
///
/// This must be present if the original Swift API is not Objective-C
/// compatible. That is, if you're using [SwiftFileInput] or
/// [SwiftModuleInput]. If all your inputs are [ObjCCompatibleSwiftFileInput]
/// you do not need to set this field.
final SwiftWrapperFile? swiftWrapperFile;
/// The name of the output Swift module.
///
/// The Dart bindings use this to look up the Swift symbols. So it's important
/// that this matches the name of module that the Swift source code, including
/// the wrapper API, is compiled into. For a Flutter plugin, this is simply
/// the plugin name.
final String module;
/// The output Dart file for the generated bindings.
final Uri dartFile;
/// The output Objective-C file for the generated Objective-C bindings.
final Uri objectiveCFile;
/// Extra code inserted at the top of the generated Dart bindings.
final String? preamble;
/// The asset id to use for the @Native annotations.
///
/// If omitted, it will not be generated.
final String? assetId;
const Output({
this.swiftWrapperFile,
required this.module,
required this.dartFile,
required this.objectiveCFile,
this.preamble,
this.assetId,
});
}
/// Configuration for the Objective-C compatible wrapper API.
///
/// This is a generated Swift API that wraps another Swift API and makes it
/// Objective-C compatible.
class SwiftWrapperFile {
/// The output path for the wrapper API.
final Uri path;
/// Extra code inserted at the top of the generated Swift file.
final String? preamble;
const SwiftWrapperFile({required this.path, this.preamble});
}
/// The target defines the OS (ie macOS or iOS), SDK version, and architecture
/// being targeted by the bindings.
///
/// The architecture almost never matters for the purposes of generating Dart
/// bindings. Also, since the bindings include OS version checks, it's usually
/// fine to just target the latest SDK version.
///
/// Typically the only thing to decide is whether you're targeting iOS or macOS.
/// So for most use cases you can simply use either [iOSArm64Latest] or
/// [macOSArm64Latest].
class Target {
/// The target triple, eg x86_64-apple-ios17.0.
final String triple;
/// The path to the iOS/macOS SDK.
final Uri sdk;
const Target({required this.triple, required this.sdk});
/// Returns the [Target] corresponding to the host OS.
static Future<Target> host() async => Target(
triple: await swift2objc.hostTargetTriple,
sdk: await swift2objc.hostSdk,
);
/// Returns the [Target] for the latest installed iOS SDK on arm.
static Future<Target> iOSArmLatest() async => Target(
triple: await swift2objc.iOSArmTargetTripleLatest,
sdk: await swift2objc.iOSSdk,
);
/// Returns the [Target] for the latest installed iOS SDK on arm64.
static Future<Target> iOSArm64Latest() async => Target(
triple: await swift2objc.iOSArm64TargetTripleLatest,
sdk: await swift2objc.iOSSdk,
);
/// Returns the [Target] for the latest installed iOS SDK on x64.
static Future<Target> iOSX64Latest() async => Target(
triple: await swift2objc.iOSX64TargetTripleLatest,
sdk: await swift2objc.iOSSdk,
);
/// Returns the [Target] for the latest installed macOS SDK on arm64.
static Future<Target> macOSArm64Latest() async => Target(
triple: await swift2objc.macOSArm64TargetTripleLatest,
sdk: await swift2objc.macOSSdk,
);
/// Returns the [Target] for the latest installed macOS SDK on x64.
static Future<Target> macOSX64Latest() async => Target(
triple: await swift2objc.macOSX64TargetTripleLatest,
sdk: await swift2objc.macOSSdk,
);
}
/// Describes the inputs to the swiftgen pipeline.
abstract interface class SwiftGenInput {
swift2objc.InputConfig? get swift2ObjCConfig;
Iterable<Uri> get files;
}
/// Input swift files that are already annotated with @objc.
class ObjCCompatibleSwiftFileInput implements SwiftGenInput {
/// A list of paths to the Objective-C compatible Swift files to generate
/// bindings for.
@override
final List<Uri> files;
@override
swift2objc.InputConfig? get swift2ObjCConfig => null;
const ObjCCompatibleSwiftFileInput({required this.files});
}
/// Input swift files that are not necessarily Objective C compatible.
class SwiftFileInput implements SwiftGenInput {
/// A list of paths to the Swift files to generate bindings for.
@override
final List<Uri> files;
/// The name of the temporary module generated while analyzing the input
/// files. The name doesn't matter, and won't appear in generated code. But if
/// your project involves multiple Swift modules, their names must be unique.
final String tempModuleName;
const SwiftFileInput({
required this.files,
this.tempModuleName = 'symbolgraph_module',
});
@override
swift2objc.InputConfig? get swift2ObjCConfig =>
swift2objc.FilesInputConfig(files: files, tempModuleName: tempModuleName);
}
/// A precompiled swift module input.
class SwiftModuleInput implements SwiftGenInput {
/// The name of the module to generate bindings for.
final String module;
const SwiftModuleInput({required this.module});
@override
swift2objc.InputConfig? get swift2ObjCConfig =>
swift2objc.ModuleInputConfig(module: module);
@override
Iterable<Uri> get files => const <Uri>[];
}
/// Selected options from [ffigen.FfiGenerator].
class FfiGeneratorOptions {
/// [ffigen.FfiGenerator.functions]
final ffigen.Functions functions;
/// [ffigen.FfiGenerator.structs]
final ffigen.Structs structs;
/// [ffigen.FfiGenerator.unions]
final ffigen.Unions unions;
/// [ffigen.FfiGenerator.enums]
final ffigen.Enums enums;
/// [ffigen.FfiGenerator.unnamedEnums]
final ffigen.UnnamedEnums unnamedEnums;
/// [ffigen.FfiGenerator.globals]
final ffigen.Globals globals;
/// Configuration for integer types.
final ffigen.Integers integers;
/// [ffigen.FfiGenerator.macros]
final ffigen.Macros macros;
/// [ffigen.FfiGenerator.typedefs]
final ffigen.Typedefs typedefs;
/// [ffigen.FfiGenerator.objectiveC]
final ffigen.ObjectiveC objectiveC;
const FfiGeneratorOptions({
this.functions = ffigen.Functions.excludeAll,
this.structs = ffigen.Structs.excludeAll,
this.unions = ffigen.Unions.excludeAll,
this.enums = ffigen.Enums.excludeAll,
this.unnamedEnums = ffigen.UnnamedEnums.excludeAll,
this.globals = ffigen.Globals.excludeAll,
this.integers = const ffigen.Integers(),
this.macros = ffigen.Macros.excludeAll,
this.typedefs = ffigen.Typedefs.excludeAll,
this.objectiveC = const ffigen.ObjectiveC(),
});
}