blob: 2f76de7deab23e18262b55b6e04516c1f137e37c [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 'dart:io';
import 'package:ffigen/ffigen.dart' as fg;
import 'package:logging/logging.dart';
import 'package:native_test_helpers/native_test_helpers.dart';
import 'package:path/path.dart' as path;
import 'package:swiftgen/src/util.dart';
import 'package:swiftgen/swiftgen.dart';
import 'package:test/test.dart';
String pkgDir = findPackageRoot('swiftgen').toFilePath();
Future<Target> hostTarget = Target.host();
// There are language features that aren't supported in Swift2ObjC yet, but that
// we want to test in SwiftGen. So for now we write @objc annotated bindings for
// these features. As each of these features is supported, migrate the test to
// stop using hand-written @objc annotations.
// TODO(https://github.com/dart-lang/native/issues/1669): callbacks
// TODO(https://github.com/dart-lang/native/issues/1828): protocols
const objCCompatibleTests = {'callbacks', 'protocols'};
class TestGenerator {
final String name;
late final String testDir;
late final String tempDir;
late final String inputFile;
late final String wrapperFile;
late final String outputFile;
late final String outputObjCFile;
late final String objInputFile;
late final String objWrapperFile;
late final String objObjCFile;
late final String dylibFile;
late final String actualOutputFile;
final bool isObjCCompatible;
TestGenerator(this.name)
: isObjCCompatible = objCCompatibleTests.contains(name) {
testDir = path.absolute(path.join(pkgDir, 'test/integration'));
tempDir = path.join(testDir, 'temp');
inputFile = path.join(testDir, '$name.swift');
wrapperFile = path.join(tempDir, '${name}_wrapper.swift');
outputFile = path.join(tempDir, '${name}_output.dart');
outputObjCFile = path.join(tempDir, '${name}_output.m');
objInputFile = path.join(tempDir, '$name.o');
objWrapperFile = path.join(tempDir, '${name}_wrapper.o');
objObjCFile = path.join(tempDir, '${name}_output_m.o');
dylibFile = path.join(tempDir, '$name.dylib');
actualOutputFile = path.join(testDir, '${name}_bindings.dart');
}
Future<void> generateBindings() async =>
await SwiftGenerator(
target: await hostTarget,
inputs: [
isObjCCompatible
? ObjCCompatibleSwiftFileInput(files: [Uri.file(inputFile)])
: SwiftFileInput(files: [Uri.file(inputFile)]),
],
output: Output(
swiftWrapperFile: isObjCCompatible
? null
: SwiftWrapperFile(path: Uri.file(wrapperFile)),
module: name,
dartFile: Uri.file(outputFile),
objectiveCFile: Uri.file(outputObjCFile),
preamble: '''
// 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.
// coverage:ignore-file
''',
),
ffigen: FfiGeneratorOptions(
objectiveC: fg.ObjectiveC(
interfaces: fg.Interfaces(
include: (decl) => decl.originalName.startsWith('Test'),
),
protocols: fg.Protocols(
include: (decl) => decl.originalName.startsWith('Test'),
),
),
),
).generate(
logger: Logger.root..level = Level.SEVERE,
tempDirectory: Uri.directory(tempDir),
);
Future<void> generateAndVerifyBindings() async {
// Run the generation pipeline. This produces the swift compatability
// wrapper, and the ffigen wrapper.
await generateBindings();
expect(File(inputFile).existsSync(), isTrue);
expect(File(wrapperFile).existsSync(), !isObjCCompatible);
expect(File(outputFile).existsSync(), isTrue);
// The generation pipeline also an obj file as a byproduct.
expect(File(objInputFile).existsSync(), isTrue);
final target = await hostTarget;
await run('swiftc', [
'-c',
inputFile,
if (!isObjCCompatible) wrapperFile,
'-module-name',
name,
'-target',
target.triple,
'-sdk',
path.absolute(target.sdk.toFilePath()),
], tempDir);
final hasOutputObjCFile = File(outputObjCFile).existsSync();
if (hasOutputObjCFile) {
// We also need to compile outputObjCFile to an obj file.
await run('clang', [
'-x',
'objective-c',
'-fobjc-arc',
'-c',
outputObjCFile,
'-fpic',
'-o',
objObjCFile,
], tempDir);
expect(File(objObjCFile).existsSync(), isTrue);
}
// Link all the obj files into a dylib.
await run('clang', [
'-shared',
'-framework',
'Foundation',
objInputFile,
if (!isObjCCompatible) objWrapperFile,
if (hasOutputObjCFile) objObjCFile,
'-o',
dylibFile,
], tempDir);
expect(File(dylibFile).existsSync(), isTrue);
// Expect that the bindings match.
expect(
File(outputFile).readAsStringSync(),
File(actualOutputFile).readAsStringSync(),
);
}
}