Skip to content

Commit a74e5a2

Browse files
authored
[ffigen] Add variable substitutions for ObjC SDKs (#2007)
1 parent 2bc6d8d commit a74e5a2

File tree

6 files changed

+131
-36
lines changed

6 files changed

+131
-36
lines changed

pkgs/ffigen/lib/src/config_provider/spec_utils.dart

+4-24
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,10 @@ import '../code_generator/utils.dart';
1818
import '../header_parser/type_extractor/cxtypekindmap.dart';
1919
import '../strings.dart' as strings;
2020
import 'config_types.dart';
21+
import 'utils.dart';
2122

2223
final _logger = Logger('ffigen.config_provider.spec_utils');
2324

24-
/// Replaces the path separators according to current platform.
25-
String _replaceSeparators(String path) {
26-
if (Platform.isWindows) {
27-
return path.replaceAll(p.posix.separator, p.windows.separator);
28-
} else {
29-
return path.replaceAll(p.windows.separator, p.posix.separator);
30-
}
31-
}
32-
33-
/// Replaces the path separators according to current platform, and normalizes .
34-
/// and .. in the path. If a relative path is passed in, it is resolved relative
35-
/// to the config path, and the absolute path is returned.
36-
String normalizePath(String path, String? configFilename) {
37-
final resolveInConfigDir =
38-
(configFilename == null) || p.isAbsolute(path) || path.startsWith('**');
39-
return _replaceSeparators(p.normalize(resolveInConfigDir
40-
? path
41-
: p.absolute(p.join(p.dirname(configFilename), path))));
42-
}
43-
4425
Map<String, LibraryImport> libraryImportsExtractor(
4526
Map<String, String>? typeMap) {
4627
final resultMap = <String, LibraryImport>{};
@@ -276,7 +257,7 @@ YamlHeaders headersExtractor(
276257
for (final key in yamlConfig.keys) {
277258
if (key == strings.entryPoints) {
278259
for (final h in yamlConfig[key]!) {
279-
final headerGlob = normalizePath(h, configFilename);
260+
final headerGlob = normalizePath(substituteVars(h), configFilename);
280261
// Add file directly to header if it's not a Glob but a File.
281262
if (File(headerGlob).existsSync()) {
282263
final osSpecificPath = headerGlob;
@@ -295,9 +276,8 @@ YamlHeaders headersExtractor(
295276
}
296277
if (key == strings.includeDirectives) {
297278
for (final h in yamlConfig[key]!) {
298-
final headerGlob = h;
299-
final fixedGlob = normalizePath(headerGlob, configFilename);
300-
includeGlobs.add(quiver.Glob(fixedGlob));
279+
final headerGlob = normalizePath(substituteVars(h), configFilename);
280+
includeGlobs.add(quiver.Glob(headerGlob));
301281
}
302282
}
303283
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:path/path.dart' as p;
8+
9+
// Replaces the path separators according to current platform.
10+
String _replaceSeparators(String path) {
11+
if (Platform.isWindows) {
12+
return path.replaceAll(p.posix.separator, p.windows.separator);
13+
} else {
14+
return path.replaceAll(p.windows.separator, p.posix.separator);
15+
}
16+
}
17+
18+
/// Replaces the path separators according to current platform, and normalizes .
19+
/// and .. in the path. If a relative path is passed in, it is resolved relative
20+
/// to the config path, and the absolute path is returned.
21+
String normalizePath(String path, String? configFilename) {
22+
final resolveInConfigDir =
23+
(configFilename == null) || p.isAbsolute(path) || path.startsWith('**');
24+
return _replaceSeparators(p.normalize(resolveInConfigDir
25+
? path
26+
: p.absolute(p.join(p.dirname(configFilename), path))));
27+
}
28+
29+
/// Replaces any variable names in the path with the corresponding value.
30+
String substituteVars(String path) {
31+
for (final variable in _variables) {
32+
final key = '\$${variable.key}';
33+
if (path.contains(key)) {
34+
path = path.replaceAll(key, variable.value);
35+
}
36+
}
37+
return path;
38+
}
39+
40+
class _LazyVariable {
41+
_LazyVariable(this.key, this._cmd, this._args);
42+
final String key;
43+
final String _cmd;
44+
final List<String> _args;
45+
String? _value;
46+
String get value => _value ??= firstLineOfStdout(_cmd, _args);
47+
}
48+
49+
final _variables = <_LazyVariable>[
50+
_LazyVariable('XCODE', 'xcode-select', ['-p']),
51+
_LazyVariable('IOS_SDK', 'xcrun', ['--show-sdk-path', '--sdk', 'iphoneos']),
52+
_LazyVariable('MACOS_SDK', 'xcrun', ['--show-sdk-path', '--sdk', 'macosx']),
53+
];
54+
55+
String firstLineOfStdout(String cmd, List<String> args) {
56+
final result = Process.runSync(cmd, args);
57+
assert(result.exitCode == 0);
58+
return (result.stdout as String)
59+
.split('\n')
60+
.where((line) => line.isNotEmpty)
61+
.first;
62+
}

pkgs/ffigen/lib/src/header_parser/parser.dart

+5-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:meta/meta.dart';
1313
import '../code_generator.dart';
1414
import '../code_generator/utils.dart';
1515
import '../config_provider.dart';
16+
import '../config_provider/utils.dart';
1617
import '../strings.dart' as strings;
1718
import '../visitor/apply_config_filters.dart';
1819
import '../visitor/ast.dart';
@@ -159,17 +160,10 @@ List<Binding> parseToBindings(Config c) {
159160
return bindings.toList();
160161
}
161162

162-
List<String> _findObjectiveCSysroot() {
163-
final result = Process.runSync('xcrun', ['--show-sdk-path']);
164-
if (result.exitCode == 0) {
165-
for (final line in (result.stdout as String).split('\n')) {
166-
if (line.isNotEmpty) {
167-
return ['-isysroot', line];
168-
}
169-
}
170-
}
171-
return [];
172-
}
163+
List<String> _findObjectiveCSysroot() => [
164+
'-isysroot',
165+
firstLineOfStdout('xcrun', ['--show-sdk-path'])
166+
];
173167

174168
@visibleForTesting
175169
List<Binding> transformBindings(Config config, List<Binding> bindings) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: SdkVariableTestObjCLibrary
2+
description: 'Tests the ObjC SDK variables'
3+
language: objc
4+
output: 'sdk_variable_bindings.dart'
5+
exclude-all-by-default: true
6+
objc-interfaces:
7+
include:
8+
- NSColorPicker
9+
- UIPickerView
10+
- NSTextList
11+
headers:
12+
entry-points:
13+
- '$XCODE/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/AppKit.framework/Headers/NSColorPicker.h'
14+
- '$IOS_SDK/System/Library/Frameworks/UIKit.framework/Headers/UIPickerView.h'
15+
- '$MACOS_SDK/System/Library/Frameworks/AppKit.framework/Headers/NSTextList.h'
16+
preamble: |
17+
// ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Objective C support is only available on mac.
6+
@TestOn('mac-os')
7+
8+
import 'dart:ffi';
9+
import 'dart:io';
10+
11+
import 'package:test/test.dart';
12+
import '../test_utils.dart';
13+
import 'util.dart';
14+
15+
void main() {
16+
group('SDK variable', () {
17+
late String bindings;
18+
19+
setUpAll(() {
20+
// TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
21+
DynamicLibrary.open('../objective_c/test/objective_c.dylib');
22+
final dylib = File('test/native_objc_test/objc_test.dylib');
23+
verifySetupFile(dylib);
24+
DynamicLibrary.open(dylib.absolute.path);
25+
generateBindingsForCoverage('rename');
26+
bindings = File('test/native_objc_test/sdk_variable_bindings.dart')
27+
.readAsStringSync();
28+
});
29+
30+
test('XCODE', () {
31+
expect(bindings, contains('class NSColorPicker '));
32+
});
33+
34+
test('IOS_SDK', () {
35+
expect(bindings, contains('class UIPickerView '));
36+
});
37+
38+
test('MACOS_SDK', () {
39+
expect(bindings, contains('class NSTextList '));
40+
});
41+
});
42+
}

pkgs/ffigen/test/unit_tests/normalize_path_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import 'dart:io';
66

7-
import 'package:ffigen/src/config_provider/spec_utils.dart';
7+
import 'package:ffigen/src/config_provider/utils.dart';
88
import 'package:path/path.dart' as p;
99
import 'package:test/test.dart';
1010

0 commit comments

Comments
 (0)