Skip to content

Commit

Permalink
Swift2objC Public API (#1374)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohammad3id authored Jul 29, 2024
1 parent 7efefec commit bb30bdb
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 37 deletions.
108 changes: 108 additions & 0 deletions pkgs/swift2objc/lib/src/config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'package:path/path.dart' as path;

const defaultTempDirPrefix = 'swift2objc_temp_';
const symbolgraphFileSuffix = '.symbols.json';

class Command {
final String executable;
final List<String> args;

Command({
required this.executable,
required this.args,
});
}

/// Used to configure Swift2ObjC wrapper generation.
class Config {
/// The input to generate a wrapper for.
/// See `FilesInputConfig` and `ModuleInputConfig`;
final InputConfig input;

/// Specify where the wrapper swift file will be output.
final Uri outputFile;

/// 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;

const Config({
required this.input,
required this.outputFile,
this.tempDir,
});
}

/// Used to specify the inputs in the `config` object.
/// See `FilesInputConfig` and `ModuleInputConfig` for concrete implementation;
sealed class InputConfig {
Command get symbolgraphCommand;
}

/// Used to generate a objc wrapper for one or more swift files
class FilesInputConfig implements InputConfig {
/// The swift file(s) to generate a wrapper for
final List<Uri> files;

/// The name of the module files generated by `swiftc in `tempDir`
final String generatedModuleName;

FilesInputConfig({
required this.files,
this.generatedModuleName = 'symbolgraph_module',
});

@override
Command get symbolgraphCommand => Command(
executable: 'swiftc',
args: [
...files.map((uri) => path.absolute(uri.path)),
'-emit-module',
'-emit-symbol-graph',
'-emit-symbol-graph-dir',
'.',
'-module-name',
generatedModuleName
],
);
}

/// Used to generate a objc wrapper for a built-in swift module
/// (e.g, AVFoundation)
class ModuleInputConfig implements InputConfig {
/// The swift module to generate a wrapper for
final String module;

/// The target to generate code for
/// (e.g `x86_64-apple-ios17.0-simulator`)
final String target;

/// The sdk to compile against
/// (e.g `/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sd`)
final Uri sdk;

ModuleInputConfig({
required this.module,
required this.target,
required this.sdk,
});

@override
Command get symbolgraphCommand => Command(
executable: 'swiftc',
args: [
'symbolgraph-extract',
'-module-name',
module,
'-target',
target,
'-sdk',
path.absolute(sdk.path),
'-output-dir',
'.',
],
);
}
64 changes: 64 additions & 0 deletions pkgs/swift2objc/lib/src/generate_wrapper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'dart:io';

import 'package:path/path.dart' as path;

import 'config.dart';
import 'generator/generator.dart';
import 'parser/parser.dart';
import 'transformer/transform.dart';

/// Used to generate the wrapper swift file.
Future<void> generateWrapper(Config config) async {
final Directory tempDir;
final bool deleteTempDirWhenDone;

if (config.tempDir == null) {
tempDir = Directory.systemTemp.createTempSync(defaultTempDirPrefix);
deleteTempDirWhenDone = true;
} else {
tempDir = Directory.fromUri(config.tempDir!);
deleteTempDirWhenDone = false;
}

final input = config.input;

await _generateSymbolgraphJson(
input.symbolgraphCommand,
tempDir,
);

final symbolgraphFileName = switch (input) {
FilesInputConfig() => '${input.generatedModuleName}$symbolgraphFileSuffix',
ModuleInputConfig() => '${input.module}$symbolgraphFileSuffix',
};
final symbolgraphJsonPath = path.join(tempDir.path, symbolgraphFileName);

final declarations = parseAst(symbolgraphJsonPath);
final transformedDeclarations = transform(declarations);
final wrapperCode = generate(transformedDeclarations);

File.fromUri(config.outputFile).writeAsStringSync(wrapperCode);

if (deleteTempDirWhenDone) {
tempDir.deleteSync(recursive: true);
}
}

Future<void> _generateSymbolgraphJson(
Command symbolgraphCommand,
Directory workingDirectory,
) async {
final result = await Process.run(
symbolgraphCommand.executable,
symbolgraphCommand.args,
workingDirectory: workingDirectory.path,
);

if (result.exitCode != 0) {
throw ProcessException(
symbolgraphCommand.executable,
symbolgraphCommand.args,
'Error generating symbol graph \n${result.stdout} \n${result.stderr}',
);
}
}
2 changes: 1 addition & 1 deletion pkgs/swift2objc/lib/src/generator/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ String generate(List<Declaration> declarations) {
return '${[
'import Foundation',
...declarations.map(generateDeclaration),
].join('\n\n')}\n';
].nonNulls.join('\n\n')}\n';
}

String generateDeclaration(Declaration declaration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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:developer';

import '../_core/json.dart';
import '../_core/parsed_symbolgraph.dart';
import '../_core/utils.dart';
Expand All @@ -21,9 +23,8 @@ ParsedRelationsMap parseRelationsMap(Json symbolgraphJson) {
final relationKind = _supportedRelationKindsMap[relationKindString];

if (relationKind == null) {
throw UnimplementedError(
'''Relation of value "$relationKindString" at path ${relationJson["kind"].path} is not implemneted yet''',
);
log('Relation of kind $relationKindString is not supported. Skipping it');
continue;
}

final sourceId = relationJson['source'].get<String>();
Expand Down
5 changes: 2 additions & 3 deletions pkgs/swift2objc/lib/swift2objc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +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/generator/generator.dart' show generate;
export 'src/parser/parser.dart' show parseAst;
export 'src/transformer/transform.dart' show transform;
export 'src/config.dart' show Config, FilesInputConfig, ModuleInputConfig;
export 'src/generate_wrapper.dart';
39 changes: 9 additions & 30 deletions pkgs/swift2objc/test/integration/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:test/test.dart';
void main() {
group('Integration tests', () {
const inputSuffix = '_input.swift';
const symbolSuffix = '_input.symbols.json';
const outputSuffix = '_output.swift';

final thisDir = path.join(Directory.current.path, 'test/integration');
Expand All @@ -29,41 +28,21 @@ void main() {
test(name, () async {
final inputFile = path.join(thisDir, '$name$inputSuffix');
final expectedOutputFile = path.join(thisDir, '$name$outputSuffix');
final symbolFile = path.join(tempDir, '$name$symbolSuffix');
final actualOutputFile = path.join(tempDir, '$name$outputSuffix');

expect(await generateSymbolGraph(inputFile, tempDir), isTrue);
expect(File(symbolFile).existsSync(), isTrue);

final declarations = parseAst(symbolFile);
final transformedDeclarations = transform(declarations);
final actualOutput = generate(transformedDeclarations);
File(actualOutputFile).writeAsStringSync(actualOutput);
await generateWrapper(Config(
input: FilesInputConfig(
files: [Uri.file(inputFile)],
),
outputFile: Uri.file(actualOutputFile),
tempDir: Directory(tempDir).uri,
));

final actualOutput = await File(actualOutputFile).readAsString();
final expectedOutput = File(expectedOutputFile).readAsStringSync();

expect(actualOutput, expectedOutput);
});
}
});
}

Future<bool> generateSymbolGraph(String swiftFile, String outputDir) async {
final result = await Process.run(
'swiftc',
[
swiftFile,
'-emit-module',
'-emit-symbol-graph',
'-emit-symbol-graph-dir',
'.',
],
workingDirectory: outputDir,
);
if (result.exitCode != 0) {
print('Error generating symbol graph');
print(result.stdout);
print(result.stderr);
return false;
}
return true;
}

0 comments on commit bb30bdb

Please sign in to comment.