Skip to content

Commit 59cb3b2

Browse files
authored
[native_toolchain_c] CBuilder Objective-C clang flags (#1228)
Adds Apple clang flags for compiling Objective-C with the `CBuilder`. Issue: * #1227 Also pins the dev CI to the latest working version: * dart-lang/sdk#56080
1 parent ae5aeca commit 59cb3b2

File tree

7 files changed

+289
-146
lines changed

7 files changed

+289
-146
lines changed

pkgs/native_toolchain_c/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 0.5.0-wip
22

33
- Renamed parameters in `Builder.run`.
4+
- Added `Language.objectiveC`.
45

56
## 0.4.2
67

pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ class Language {
2020
const Language._(this.name);
2121

2222
static const Language c = Language._('c');
23+
2324
static const Language cpp = Language._('c++');
2425

26+
static const Language objectiveC = Language._('objective c');
27+
2528
/// Known values for [Language].
26-
static const List<Language> values = [c, cpp];
29+
static const List<Language> values = [
30+
c,
31+
cpp,
32+
objectiveC,
33+
];
2734

2835
@override
2936
String toString() => name;
@@ -63,6 +70,20 @@ class CBuilder implements Builder {
6370
/// Used to output the [BuildOutput.dependencies].
6471
final List<String> includes;
6572

73+
/// Frameworks to link.
74+
///
75+
/// Only effective if [language] is [Language.objectiveC].
76+
///
77+
/// Defaults to `['Foundation']`.
78+
///
79+
/// Not used to output the [BuildOutput.dependencies], frameworks can be
80+
/// mentioned by name if they are available on the system, so the file path
81+
/// is not known. If you're depending on your own frameworks add them to
82+
/// [BuildOutput.dependencies] manually.
83+
final List<String> frameworks;
84+
85+
static const List<String> _defaultFrameworks = ['Foundation'];
86+
6687
/// The dart files involved in building this artifact.
6788
///
6889
/// Resolved against [BuildConfig.packageRoot].
@@ -155,6 +176,7 @@ class CBuilder implements Builder {
155176
required this.assetName,
156177
this.sources = const [],
157178
this.includes = const [],
179+
this.frameworks = _defaultFrameworks,
158180
required this.dartBuildFiles,
159181
@visibleForTesting this.installName,
160182
this.flags = const [],
@@ -172,6 +194,7 @@ class CBuilder implements Builder {
172194
required this.name,
173195
this.sources = const [],
174196
this.includes = const [],
197+
this.frameworks = _defaultFrameworks,
175198
required this.dartBuildFiles,
176199
this.flags = const [],
177200
this.defines = const {},
@@ -221,6 +244,7 @@ class CBuilder implements Builder {
221244
logger: logger,
222245
sources: sources,
223246
includes: includes,
247+
frameworks: frameworks,
224248
dynamicLibrary: _type == _CBuilderType.library &&
225249
linkMode == DynamicLoadingBundled()
226250
? libUri

pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class RunCBuilder {
2323
final Logger? logger;
2424
final List<Uri> sources;
2525
final List<Uri> includes;
26+
final List<String> frameworks;
2627
final Uri? executable;
2728
final Uri? dynamicLibrary;
2829
final Uri? staticLibrary;
@@ -47,6 +48,7 @@ class RunCBuilder {
4748
this.logger,
4849
this.sources = const [],
4950
this.includes = const [],
51+
required this.frameworks,
5052
this.executable,
5153
this.dynamicLibrary,
5254
this.staticLibrary,
@@ -246,6 +248,12 @@ class RunCBuilder {
246248
if (value == null) '-D$name' else '-D$name=$value',
247249
for (final include in includes) '-I${include.toFilePath()}',
248250
...sourceFiles,
251+
if (language == Language.objectiveC) ...[
252+
for (final framework in frameworks) ...[
253+
'-framework',
254+
framework,
255+
],
256+
],
249257
if (executable != null) ...[
250258
'-o',
251259
outDir.resolveUri(executable!).toFilePath(),

pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_ios_test.dart

Lines changed: 113 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -36,115 +36,126 @@ void main() {
3636

3737
const name = 'add';
3838

39-
for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) {
40-
for (final targetIOSSdk in IOSSdk.values) {
41-
for (final target in targets) {
42-
if (target == Architecture.x64 && targetIOSSdk == IOSSdk.iPhoneOS) {
43-
continue;
44-
}
45-
46-
final libName = OS.iOS.libraryFileName(name, linkMode);
47-
for (final installName in [
48-
null,
49-
if (linkMode == DynamicLoadingBundled())
50-
Uri.file('@executable_path/Frameworks/$libName'),
51-
]) {
52-
test(
53-
'CBuilder $linkMode library $targetIOSSdk $target'
54-
' ${installName ?? ''}'
55-
.trim(), () async {
56-
final tempUri = await tempDirForTest();
57-
final addCUri =
58-
packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
59-
final buildConfig = BuildConfig.build(
60-
outputDirectory: tempUri,
61-
packageName: name,
62-
packageRoot: tempUri,
63-
targetArchitecture: target,
64-
targetOS: OS.iOS,
65-
buildMode: BuildMode.release,
66-
linkModePreference: linkMode == DynamicLoadingBundled()
67-
? LinkModePreference.dynamic
68-
: LinkModePreference.static,
69-
targetIOSSdk: targetIOSSdk,
70-
);
71-
final buildOutput = BuildOutput();
39+
for (final language in [Language.c, Language.objectiveC]) {
40+
for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) {
41+
for (final targetIOSSdk in IOSSdk.values) {
42+
for (final target in targets) {
43+
if (target == Architecture.x64 && targetIOSSdk == IOSSdk.iPhoneOS) {
44+
continue;
45+
}
7246

73-
final cbuilder = CBuilder.library(
74-
name: name,
75-
assetName: name,
76-
sources: [addCUri.toFilePath()],
77-
installName: installName,
78-
dartBuildFiles: ['hook/build.dart'],
79-
);
80-
await cbuilder.run(
81-
config: buildConfig,
82-
output: buildOutput,
83-
logger: logger,
84-
);
47+
final libName = OS.iOS.libraryFileName(name, linkMode);
48+
for (final installName in [
49+
null,
50+
if (linkMode == DynamicLoadingBundled())
51+
Uri.file('@executable_path/Frameworks/$libName'),
52+
]) {
53+
test(
54+
'CBuilder $linkMode $language library $targetIOSSdk $target'
55+
' ${installName ?? ''}'
56+
.trim(), () async {
57+
final tempUri = await tempDirForTest();
58+
final sourceUri = switch (language) {
59+
Language.c =>
60+
packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'),
61+
Language.objectiveC => packageUri.resolve(
62+
'test/cbuilder/testfiles/add_objective_c/src/add.m'),
63+
Language() => throw UnimplementedError(),
64+
};
65+
final buildConfig = BuildConfig.build(
66+
outputDirectory: tempUri,
67+
packageName: name,
68+
packageRoot: tempUri,
69+
targetArchitecture: target,
70+
targetOS: OS.iOS,
71+
buildMode: BuildMode.release,
72+
linkModePreference: linkMode == DynamicLoadingBundled()
73+
? LinkModePreference.dynamic
74+
: LinkModePreference.static,
75+
targetIOSSdk: targetIOSSdk,
76+
);
77+
final buildOutput = BuildOutput();
8578

86-
final libUri = tempUri.resolve(libName);
87-
final objdumpResult = await runProcess(
88-
executable: Uri.file('objdump'),
89-
arguments: ['-t', libUri.path],
90-
logger: logger,
91-
);
92-
expect(objdumpResult.exitCode, 0);
93-
final machine = objdumpResult.stdout
94-
.split('\n')
95-
.firstWhere((e) => e.contains('file format'));
96-
expect(machine, contains(objdumpFileFormat[target]));
79+
final cbuilder = CBuilder.library(
80+
name: name,
81+
assetName: name,
82+
sources: [sourceUri.toFilePath()],
83+
installName: installName,
84+
dartBuildFiles: ['hook/build.dart'],
85+
language: language,
86+
);
87+
await cbuilder.run(
88+
config: buildConfig,
89+
output: buildOutput,
90+
logger: logger,
91+
);
9792

98-
final otoolResult = await runProcess(
99-
executable: Uri.file('otool'),
100-
arguments: ['-l', libUri.path],
101-
logger: logger,
102-
);
103-
expect(otoolResult.exitCode, 0);
104-
if (targetIOSSdk == IOSSdk.iPhoneOS || target == Architecture.x64) {
105-
// The x64 simulator behaves as device, presumably because the
106-
// devices are never x64.
107-
expect(otoolResult.stdout, contains('LC_VERSION_MIN_IPHONEOS'));
108-
expect(otoolResult.stdout, isNot(contains('LC_BUILD_VERSION')));
109-
} else {
110-
expect(otoolResult.stdout,
111-
isNot(contains('LC_VERSION_MIN_IPHONEOS')));
112-
expect(otoolResult.stdout, contains('LC_BUILD_VERSION'));
113-
final platform = otoolResult.stdout
93+
final libUri = tempUri.resolve(libName);
94+
final objdumpResult = await runProcess(
95+
executable: Uri.file('objdump'),
96+
arguments: ['-t', libUri.path],
97+
logger: logger,
98+
);
99+
expect(objdumpResult.exitCode, 0);
100+
final machine = objdumpResult.stdout
114101
.split('\n')
115-
.firstWhere((e) => e.contains('platform'));
116-
const platformIosSimulator = 7;
117-
expect(platform, contains(platformIosSimulator.toString()));
118-
}
102+
.firstWhere((e) => e.contains('file format'));
103+
expect(machine, contains(objdumpFileFormat[target]));
119104

120-
if (linkMode == DynamicLoadingBundled()) {
121-
final libInstallName = await runOtoolInstallName(libUri, libName);
122-
if (installName == null) {
123-
// If no install path is passed, we have an absolute path.
124-
final tempName = tempUri.pathSegments.lastWhere((e) => e != '');
125-
final pathEnding =
126-
Uri.directory(tempName).resolve(libName).toFilePath();
127-
expect(Uri.file(libInstallName).isAbsolute, true);
128-
expect(libInstallName, contains(pathEnding));
129-
final targetInstallName =
130-
'@executable_path/Frameworks/$libName';
131-
await runProcess(
132-
executable: Uri.file('install_name_tool'),
133-
arguments: [
134-
'-id',
135-
targetInstallName,
136-
libUri.toFilePath(),
137-
],
138-
logger: logger,
139-
);
140-
final libInstallName2 =
141-
await runOtoolInstallName(libUri, libName);
142-
expect(libInstallName2, targetInstallName);
105+
final otoolResult = await runProcess(
106+
executable: Uri.file('otool'),
107+
arguments: ['-l', libUri.path],
108+
logger: logger,
109+
);
110+
expect(otoolResult.exitCode, 0);
111+
if (targetIOSSdk == IOSSdk.iPhoneOS ||
112+
target == Architecture.x64) {
113+
// The x64 simulator behaves as device, presumably because the
114+
// devices are never x64.
115+
expect(otoolResult.stdout, contains('LC_VERSION_MIN_IPHONEOS'));
116+
expect(otoolResult.stdout, isNot(contains('LC_BUILD_VERSION')));
143117
} else {
144-
expect(libInstallName, installName.toFilePath());
118+
expect(otoolResult.stdout,
119+
isNot(contains('LC_VERSION_MIN_IPHONEOS')));
120+
expect(otoolResult.stdout, contains('LC_BUILD_VERSION'));
121+
final platform = otoolResult.stdout
122+
.split('\n')
123+
.firstWhere((e) => e.contains('platform'));
124+
const platformIosSimulator = 7;
125+
expect(platform, contains(platformIosSimulator.toString()));
126+
}
127+
128+
if (linkMode == DynamicLoadingBundled()) {
129+
final libInstallName =
130+
await runOtoolInstallName(libUri, libName);
131+
if (installName == null) {
132+
// If no install path is passed, we have an absolute path.
133+
final tempName =
134+
tempUri.pathSegments.lastWhere((e) => e != '');
135+
final pathEnding =
136+
Uri.directory(tempName).resolve(libName).toFilePath();
137+
expect(Uri.file(libInstallName).isAbsolute, true);
138+
expect(libInstallName, contains(pathEnding));
139+
final targetInstallName =
140+
'@executable_path/Frameworks/$libName';
141+
await runProcess(
142+
executable: Uri.file('install_name_tool'),
143+
arguments: [
144+
'-id',
145+
targetInstallName,
146+
libUri.toFilePath(),
147+
],
148+
logger: logger,
149+
);
150+
final libInstallName2 =
151+
await runOtoolInstallName(libUri, libName);
152+
expect(libInstallName2, targetInstallName);
153+
} else {
154+
expect(libInstallName, installName.toFilePath());
155+
}
145156
}
146-
}
147-
});
157+
});
158+
}
148159
}
149160
}
150161
}

0 commit comments

Comments
 (0)