Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: swiftgen prototype #1367

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
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
8 changes: 7 additions & 1 deletion pkgs/ffigen/lib/ffigen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,11 @@
/// https://pub.dev/packages/ffigen for details.
library ffigen;

export 'src/config_provider.dart' show Config, YamlConfig;
export 'src/config_provider.dart' show
Config,
DeclarationFilters,
ExternalVersions,
Language,
Versions,
YamlConfig;
export 'src/ffigen.dart' show FfiGen;
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/config_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
library config_provider;

export 'config_provider/config.dart';
export 'config_provider/config_types.dart';
export 'config_provider/yaml_config.dart';
8 changes: 4 additions & 4 deletions pkgs/swift2objc/lib/src/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ class Config {
/// Specify where the wrapper swift file will be output.
final Uri outputFile;

/// Specify where the wrapper swift file will be output.
final String? preamble;

/// Specify where to output the intermidiate files (i.g the symbolgraph json).
/// If this is null, a teemp directory will be generated in the system temp
/// directory (using `Directory.systemTemp`) and then deleted.
/// Specifying a temp directory would prevent the tool from deleting the
/// intermediate files after generating the wrapper
final Uri? tempDir;

/// Text inserted into the [outputFile] before the generated output.
final String? preamble;

const Config({
required this.input,
required this.outputFile,
Expand Down Expand Up @@ -96,7 +96,7 @@ class ModuleInputConfig implements InputConfig {

@override
Command get symbolgraphCommand => Command(
executable: 'swiftc',
executable: 'swift',
args: [
'symbolgraph-extract',
'-module-name',
Expand Down
2 changes: 1 addition & 1 deletion pkgs/swift2objc/lib/swift2objc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
// 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.

export 'src/config.dart' show Config, FilesInputConfig, ModuleInputConfig;
export 'src/config.dart' show Config, InputConfig, FilesInputConfig, ModuleInputConfig;
export 'src/generate_wrapper.dart';
52 changes: 52 additions & 0 deletions pkgs/swiftgen/bin/swiftgen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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:swiftgen/swiftgen.dart';
import 'package:ffigen/ffigen.dart' as ffigen;
import 'package:pub_semver/pub_semver.dart';

Future<void> main() async {
/*generate(Config(
target: Target(
triple: 'x86_64-apple-macosx10.14',
sdk: Uri.directory('/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'),
),
input: SwiftFileInput(
module: 'SwiftgenTest',
files: [Uri.file('/Users/liama/dev/native/pkgs/swift2objc/test/integration/classes_and_methods_input.swift')],
),
tempDir: Uri.directory('temp'),
outputModule: 'SwiftgenTestWrapper',
objcSwiftFile: Uri.file('SwiftgenTestWrapper.swift'),
ffigen: FfiGenConfig(
output: Uri.file('SwiftgenTestWrapper.dart'),
outputObjC: Uri.file('SwiftgenTestWrapper.m'),
externalVersions: ffigen.ExternalVersions(
ios: ffigen.Versions(min: Version(12, 0, 0)),
macos: ffigen.Versions(min: Version(10, 14, 0)),
),
),
));*/
generate(Config(
target: Target(
triple: 'x86_64-apple-macosx14.0',
sdk: Uri.directory('/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'),
),
input: SwiftModuleInput(
module: 'AVFoundation',
),
tempDir: Uri.directory('temp'),
objcSwiftPreamble: 'import AVFoundation',
outputModule: 'AVFoundationWrapper',
objcSwiftFile: Uri.file('AVFoundationWrapper.swift'),
ffigen: FfiGenConfig(
output: Uri.file('AVFoundationWrapper.dart'),
outputObjC: Uri.file('AVFoundationWrapper.m'),
externalVersions: ffigen.ExternalVersions(
ios: ffigen.Versions(min: Version(12, 0, 0)),
macos: ffigen.Versions(min: Version(10, 14, 0)),
),
),
));
}
167 changes: 167 additions & 0 deletions pkgs/swiftgen/lib/src/config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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:swift2objc/swift2objc.dart' as swift2objc;
import 'package:ffigen/ffigen.dart' as ffigen;

import 'util.dart';

class Target {
String triple;
Uri sdk;

Target({required this.triple, required this.sdk});

static Future<Target> host() => getHostTarget();
}

abstract interface class ConfigInput {
String get module;
swift2objc.InputConfig asSwift2ObjCConfig(Target target);
Iterable<Uri> get files;
Iterable<String> get compileArgs;
}

class SwiftFileInput implements ConfigInput {
@override
final String module;

@override
final List<Uri> files;

SwiftFileInput({
required this.module,
required this.files,
});

@override
swift2objc.InputConfig asSwift2ObjCConfig(Target target) =>
swift2objc.FilesInputConfig(
files: files,
generatedModuleName: module,
);

@override
Iterable<String> get compileArgs => const <String>[];
}

class SwiftModuleInput implements ConfigInput {
@override
final String module;

SwiftModuleInput({
required this.module,
});

@override
swift2objc.InputConfig asSwift2ObjCConfig(Target target) =>
swift2objc.ModuleInputConfig(
module: module,
target: target.triple,
sdk: target.sdk,
);

@override
Iterable<Uri> get files => const <Uri>[];

@override
Iterable<String> get compileArgs => const <String>[];
}

/// Selected options from the ffigen Config object.
class FfiGenConfig {
/// Output file name.
final Uri output;

/// Output ObjC file name.
final Uri outputObjC;

/// Name of the wrapper class.
final String? wrapperName;

/// Doc comment for the wrapper class.
final String? wrapperDocComment;

/// Header of the generated bindings.
final String? preamble;

/// Declaration filters for Functions.
final ffigen.DeclarationFilters? functionDecl;

/// Declaration filters for Structs.
final ffigen.DeclarationFilters? structDecl;

/// Declaration filters for Unions.
final ffigen.DeclarationFilters? unionDecl;

/// Declaration filters for Enums.
final ffigen.DeclarationFilters? enumClassDecl;

/// Declaration filters for Unnamed enum constants.
final ffigen.DeclarationFilters? unnamedEnumConstants;

/// Declaration filters for Globals.
final ffigen.DeclarationFilters? globals;

/// Declaration filters for Macro constants.
final ffigen.DeclarationFilters? macroDecl;

/// Declaration filters for Typedefs.
final ffigen.DeclarationFilters? typedefs;

/// Declaration filters for Objective C interfaces.
final ffigen.DeclarationFilters? objcInterfaces;

/// Declaration filters for Objective C protocols.
final ffigen.DeclarationFilters? objcProtocols;

/// Minimum target versions for ObjC APIs, per OS. APIs that were deprecated
/// before this version will not be generated.
final ffigen.ExternalVersions externalVersions;

FfiGenConfig({
required this.output,
required this.outputObjC,
this.wrapperName,
this.wrapperDocComment,
this.preamble,
this.functionDecl,
this.structDecl,
this.unionDecl,
this.enumClassDecl,
this.unnamedEnumConstants,
this.globals,
this.macroDecl,
this.typedefs,
this.objcInterfaces,
this.objcProtocols,
this.externalVersions = const ffigen.ExternalVersions(),
});
}

class Config {
final Target target;

// Input. Either a swift file or a module.
final ConfigInput input;

// Intermediates.
final String? objcSwiftPreamble;
final Uri objcSwiftFile;
final Uri tempDir;

// Output file.
final String? outputModule;
final FfiGenConfig ffigen;

Config({
required this.target,
required this.input,
this.objcSwiftPreamble,
required this.objcSwiftFile,
required this.tempDir,
this.outputModule,
required this.ffigen,
});
}
77 changes: 77 additions & 0 deletions pkgs/swiftgen/lib/src/generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 'dart:io';

import 'package:path/path.dart' as p;
import 'package:swift2objc/swift2objc.dart' as swift2objc;
import 'package:ffigen/ffigen.dart' as ffigen;
import 'package:ffigen/src/config_provider/path_finder.dart';
import 'package:logging/logging.dart';

import 'config.dart';
import 'util.dart';

extension _ConfigUtil on Config {
String get absTempDir => p.absolute(tempDir.toFilePath());
String get outModule => outputModule ?? input.module;
String get objcHeader => p.join(absTempDir, '${outModule}.h');
}

Future<void> generate(Config config) async {
Directory(config.absTempDir).createSync(recursive: true);
await _generateObjCSwiftFile(config);
await _generateObjCFile(config);
_generateDartFile(config);
}

Future<void> _generateObjCSwiftFile(Config config) =>
swift2objc.generateWrapper(swift2objc.Config(
input: config.input.asSwift2ObjCConfig(config.target),
outputFile: config.objcSwiftFile,
tempDir: config.tempDir,
preamble: config.objcSwiftPreamble,
));

Future<void> _generateObjCFile(Config config) =>
run('swiftc', [
'-c', p.absolute(config.objcSwiftFile.toFilePath()),
...config.input.files.map((uri) => p.absolute(uri.toFilePath())),
'-module-name', config.outModule,
'-emit-objc-header-path', config.objcHeader,
'-target', config.target.triple,
'-sdk', p.absolute(config.target.sdk.toFilePath()),
...config.input.compileArgs,
], config.absTempDir);

void _generateDartFile(Config config) {
final generator = ffigen.FfiGen(logLevel: Level.SEVERE);
final ffigenConfig = ffigen.Config(
language: ffigen.Language.objc,
output: config.ffigen.output,
outputObjC: config.ffigen.outputObjC,
wrapperName: config.ffigen.wrapperName ?? config.outModule,
wrapperDocComment: config.ffigen.wrapperDocComment,
preamble: config.ffigen.preamble,
functionDecl: config.ffigen.functionDecl ?? ffigen.DeclarationFilters.excludeAll,
structDecl: config.ffigen.structDecl ?? ffigen.DeclarationFilters.excludeAll,
unionDecl: config.ffigen.unionDecl ?? ffigen.DeclarationFilters.excludeAll,
enumClassDecl: config.ffigen.enumClassDecl ?? ffigen.DeclarationFilters.excludeAll,
unnamedEnumConstants: config.ffigen.unnamedEnumConstants ?? ffigen.DeclarationFilters.excludeAll,
globals: config.ffigen.globals ?? ffigen.DeclarationFilters.excludeAll,
macroDecl: config.ffigen.macroDecl ?? ffigen.DeclarationFilters.excludeAll,
typedefs: config.ffigen.typedefs ?? ffigen.DeclarationFilters.excludeAll,
objcInterfaces: config.ffigen.objcInterfaces ?? ffigen.DeclarationFilters.excludeAll,
objcProtocols: config.ffigen.objcProtocols ?? ffigen.DeclarationFilters.excludeAll,
entryPoints: [Uri.file(config.objcHeader)],
compilerOpts: [
...getCStandardLibraryHeadersForMac(),
'-Wno-nullability-completeness',
],
interfaceModuleFunc: (_) => config.outModule,
protocolModuleFunc: (_) => config.outModule,
externalVersions: config.ffigen.externalVersions,
);
generator.run(ffigenConfig);
}
28 changes: 28 additions & 0 deletions pkgs/swiftgen/lib/src/util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 'dart:io';

import 'config.dart';

Future<void> run(
String executable, List<String> arguments, String workingDir) async {
final process = await Process.start(
executable, arguments,
workingDirectory: workingDir);
process.stdout.listen(stdout.add);
process.stderr.listen(stderr.add);
if ((await process.exitCode) != 0) {
throw ProcessException(executable, arguments);
}
}

Future<Target> getHostTarget() async {
return Target(
// TODO: swiftc -print-target-info, target.triple
triple: 'x86_64-apple-macosx14.0',
// TODO: xcrun --show-sdk-path
sdk: Uri.directory('/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'),
);
}
7 changes: 7 additions & 0 deletions pkgs/swiftgen/lib/swiftgen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// 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.

export 'package:ffigen/ffigen.dart' show DeclarationFilters;
export 'src/config.dart' show Config, SwiftFileInput, SwiftModuleInput, Target, FfiGenConfig;
export 'src/generator.dart' show generate;
Loading
Loading