blob: a7ae84d0c52712e890cf81e773b56240fb5a2db2 [file] [log] [blame] [edit]
// 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.
// When users import package:objective_c as a plugin, Flutter builds our native
// code automatically. But we want to be able to run tests using `dart test`, so
// we can't use Flutter's build system. So this script builds a dylib containing
// all that native code.
// ignore_for_file: avoid_print
import 'dart:ffi';
import 'dart:io';
import 'package:args/args.dart';
final cFiles = [
'src/objective_c.c',
'src/include/dart_api_dl.c',
'test/util.c',
].map(_resolve);
final cMain = _resolve('test/main.c');
final objCFiles = [
'src/input_stream_adapter.m',
'src/ns_number.m',
'src/objective_c.m',
'src/objective_c_bindings_generated.m',
'src/observer.m',
'src/protocol.m',
].map(_resolve);
const objCFlags = ['-x', 'objective-c', '-fobjc-arc'];
final outputFile = _resolve('test/objective_c.dylib');
final _repoDir = () {
var path = Platform.script;
while (path.pathSegments.isNotEmpty) {
path = path.resolve('..');
if (Directory(path.resolve('.git').toFilePath()).existsSync()) {
return path;
}
}
throw Exception("Can't find .git dir above ${Platform.script}");
}();
final _pkgDir = _repoDir.resolve('pkgs/objective_c/');
String _resolve(String file) => _pkgDir.resolve(file).toFilePath();
void _runClang(List<String> flags, String output) {
final args = [...flags, '-o', output];
const exec = 'clang';
print('Running: $exec ${args.join(" ")}');
final proc = Process.runSync(exec, args);
print(proc.stdout);
print(proc.stderr);
if (proc.exitCode != 0) {
exitCode = proc.exitCode;
throw Exception('Command failed: $exec ${args.join(" ")}');
}
print('Generated $output');
}
String _buildObject(String input, List<String> flags) {
final output = '$input.o';
_runClang([...flags, '-c', input, '-fpic', '-I', 'src'], output);
return output;
}
void _linkLib(List<String> inputs, String output) =>
_runClang(['-shared', '-undefined', 'dynamic_lookup', ...inputs], output);
void _linkMain(List<String> inputs, String output) =>
_runClang(['-dead_strip', '-fobjc-arc', ...inputs], output);
void main(List<String> arguments) {
final parser = ArgParser();
parser.addFlag('main-thread-dispatcher');
final args = parser.parse(arguments);
final flags = [
if (!args.flag('main-thread-dispatcher')) '-DNO_MAIN_THREAD_DISPATCH',
];
final objFiles = <String>[
for (final src in cFiles) _buildObject(src, flags),
for (final src in objCFiles) _buildObject(src, [...objCFlags, ...flags]),
];
_linkLib(objFiles, outputFile);
// Sanity check that the dylib was created correctly.
final lib = DynamicLibrary.open(outputFile);
lib.lookup('DOBJC_disposeObjCBlockWithClosure'); // objective_c.c
lib.lookup('DOBJC_runOnMainThread'); // objective_c.m
lib.lookup('Dart_InitializeApiDL'); // dart_api_dl.c
lib.lookup('OBJC_CLASS_\$_DOBJCDartProtocol'); // protocol.m
lib.lookup('OBJC_CLASS_\$_DOBJCObservation'); // observer.m
// objective_c_bindings_generated.m
lib.lookup('_ObjectiveCBindings_wrapListenerBlock_ovsamd');
// Sanity check that the executable can find FFI symbols.
_linkMain([...objFiles, cMain], '$cMain.exe');
final result = Process.runSync('$cMain.exe', []);
if (result.exitCode != 0) {
throw Exception('Missing symbols from executable:\n${result.stderr}');
}
}