// 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. 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 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 flags) { final output = '$input.o'; _runClang([...flags, '-c', input, '-fpic', '-I', 'src'], output); return output; } void _linkLib(List inputs, String output) => _runClang(['-shared', '-undefined', 'dynamic_lookup', ...inputs], output); void _linkMain(List inputs, String output) => _runClang(['-dead_strip', '-fobjc-arc', ...inputs], output); void main(List 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 = [ 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}'); } }