Skip to content

Commit bac0d58

Browse files
authored
Add support for defines to CBuilder (#120)
1 parent 6fd8ab3 commit bac0d58

File tree

6 files changed

+203
-11
lines changed

6 files changed

+203
-11
lines changed

pkgs/native_toolchain_c/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.2.1
2+
3+
- Added `defines` for specifying custom defines.
4+
- Added `buildModeDefine` to toggle define for current build mode.
5+
- Added `ndebugDefine` to toggle define of `NDEBUG` for non-debug builds.
6+
17
## 0.2.0
28

39
- **Breaking change** Rename `assetName` to `assetId`

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,48 @@ class CBuilder implements Builder {
5757
@visibleForTesting
5858
final Uri? installName;
5959

60+
/// Definitions of preprocessor macros.
61+
///
62+
/// When the value is `null`, the macro is defined without a value.
63+
final Map<String, String?> defines;
64+
65+
/// Whether to define a macro for the current [BuildMode].
66+
///
67+
/// The macro name is the uppercase name of the build mode and does not have a
68+
/// value.
69+
///
70+
/// Defaults to `true`.
71+
final bool buildModeDefine;
72+
73+
/// Whether to define the standard `NDEBUG` macro when _not_ building with
74+
/// [BuildMode.debug].
75+
///
76+
/// When `NDEBUG` is defined, the C/C++ standard library
77+
/// [`assert` macro in `assert.h`](https://en.wikipedia.org/wiki/Assert.h)
78+
/// becomes a no-op. Other C/C++ code commonly use `NDEBUG` to disable debug
79+
/// features, as well.
80+
///
81+
/// Defaults to `true`.
82+
final bool ndebugDefine;
83+
6084
CBuilder.library({
6185
required this.name,
6286
required this.assetId,
6387
this.sources = const [],
6488
this.dartBuildFiles = const ['build.dart'],
6589
@visibleForTesting this.installName,
90+
this.defines = const {},
91+
this.buildModeDefine = true,
92+
this.ndebugDefine = true,
6693
}) : _type = _CBuilderType.library;
6794

6895
CBuilder.executable({
6996
required this.name,
7097
this.sources = const [],
7198
this.dartBuildFiles = const ['build.dart'],
99+
this.defines = const {},
100+
this.buildModeDefine = true,
101+
this.ndebugDefine = true,
72102
}) : _type = _CBuilderType.executable,
73103
assetId = null,
74104
installName = null;
@@ -112,6 +142,12 @@ class CBuilder implements Builder {
112142
: null,
113143
executable: _type == _CBuilderType.executable ? exeUri : null,
114144
installName: installName,
145+
defines: {
146+
...defines,
147+
if (buildModeDefine) buildConfig.buildMode.name.toUpperCase(): null,
148+
if (ndebugDefine && buildConfig.buildMode != BuildMode.debug)
149+
'NDEBUG': null,
150+
},
115151
);
116152
await task.run();
117153
}

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class RunCBuilder {
3232
/// Can be modified with `install_name_tool`.
3333
final Uri? installName;
3434

35+
final Map<String, String?> defines;
36+
3537
RunCBuilder({
3638
required this.buildConfig,
3739
this.logger,
@@ -40,6 +42,7 @@ class RunCBuilder {
4042
this.dynamicLibrary,
4143
this.staticLibrary,
4244
this.installName,
45+
this.defines = const {},
4346
}) : outDir = buildConfig.outDir,
4447
target = buildConfig.target,
4548
assert([executable, dynamicLibrary, staticLibrary]
@@ -142,11 +145,8 @@ class RunCBuilder {
142145
'-o',
143146
outDir.resolve('out.o').toFilePath(),
144147
],
145-
// TODO(https://github.com/dart-lang/native/issues/50): The defines
146-
// should probably be configurable. That way, the mapping from
147-
// build_mode to defines can be defined in a project-dependent way in
148-
// each project build.dart.
149-
'-D${buildConfig.buildMode.name.toUpperCase()}'
148+
for (final MapEntry(key: name, :value) in defines.entries)
149+
if (value == null) '-D$name' else '-D$name=$value',
150150
],
151151
logger: logger,
152152
captureOutput: false,
@@ -181,11 +181,8 @@ class RunCBuilder {
181181
final result = await runProcess(
182182
executable: compiler.uri,
183183
arguments: [
184-
// TODO(https://github.com/dart-lang/native/issues/50): The defines
185-
// should probably be configurable. That way, the mapping from
186-
// build_mode to defines can be defined in a project-dependent way in
187-
// each project build.dart.
188-
'/D${buildConfig.buildMode.name.toUpperCase()}',
184+
for (final MapEntry(key: name, :value) in defines.entries)
185+
if (value == null) '/D$name' else '/D$name=$value',
189186
if (executable != null) ...[
190187
...sources.map((e) => e.toFilePath()),
191188
'/link',

pkgs/native_toolchain_c/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: native_toolchain_c
22
description: >-
33
A library to invoke the native C compiler installed on the host machine.
4-
version: 0.2.0
4+
version: 0.2.1
55
repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c
66

77
topics:

pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,119 @@ void main() {
127127
);
128128
});
129129
}
130+
131+
for (final buildMode in BuildMode.values) {
132+
for (final enabled in [true, false]) {
133+
test(
134+
'Cbuilder build mode defines ${enabled ? 'enabled' : 'disabled'} for '
135+
'$buildMode',
136+
() => testDefines(
137+
buildMode: buildMode,
138+
buildModeDefine: enabled,
139+
ndebugDefine: enabled,
140+
),
141+
);
142+
}
143+
}
144+
145+
for (final value in [true, false]) {
146+
test(
147+
'Cbuilder define ${value ? 'with' : 'without'} value',
148+
() => testDefines(customDefineWithValue: value),
149+
);
150+
}
151+
}
152+
153+
Future<void> testDefines({
154+
BuildMode buildMode = BuildMode.debug,
155+
bool buildModeDefine = false,
156+
bool ndebugDefine = false,
157+
bool? customDefineWithValue,
158+
}) async {
159+
await inTempDir((tempUri) async {
160+
final definesCUri =
161+
packageUri.resolve('test/cbuilder/testfiles/defines/src/defines.c');
162+
if (!await File.fromUri(definesCUri).exists()) {
163+
throw Exception('Run the test from the root directory.');
164+
}
165+
const name = 'defines';
166+
167+
final buildConfig = BuildConfig(
168+
outDir: tempUri,
169+
packageRoot: tempUri,
170+
targetArchitecture: Architecture.current,
171+
targetOs: OS.current,
172+
buildMode: buildMode,
173+
// Ignored by executables.
174+
linkModePreference: LinkModePreference.dynamic,
175+
cCompiler: CCompilerConfig(
176+
cc: cc,
177+
envScript: envScript,
178+
envScriptArgs: envScriptArgs,
179+
),
180+
);
181+
final buildOutput = BuildOutput();
182+
final cbuilder = CBuilder.executable(
183+
name: name,
184+
sources: [definesCUri.toFilePath()],
185+
defines: {
186+
if (customDefineWithValue != null)
187+
'FOO': customDefineWithValue ? 'BAR' : null,
188+
},
189+
buildModeDefine: buildModeDefine,
190+
ndebugDefine: ndebugDefine,
191+
);
192+
await cbuilder.run(
193+
buildConfig: buildConfig,
194+
buildOutput: buildOutput,
195+
logger: logger,
196+
);
197+
198+
final executableUri =
199+
tempUri.resolve(Target.current.os.executableFileName(name));
200+
expect(await File.fromUri(executableUri).exists(), true);
201+
final result = await runProcess(
202+
executable: executableUri,
203+
logger: logger,
204+
);
205+
expect(result.exitCode, 0);
206+
207+
if (buildModeDefine) {
208+
expect(
209+
result.stdout,
210+
contains('Macro ${buildMode.name.toUpperCase()} is defined: 1'),
211+
);
212+
} else {
213+
expect(
214+
result.stdout,
215+
contains('Macro ${buildMode.name.toUpperCase()} is undefined.'),
216+
);
217+
}
218+
219+
if (ndebugDefine && buildMode != BuildMode.debug) {
220+
expect(
221+
result.stdout,
222+
contains('Macro NDEBUG is defined: 1'),
223+
);
224+
} else {
225+
expect(
226+
result.stdout,
227+
contains('Macro NDEBUG is undefined.'),
228+
);
229+
}
230+
231+
if (customDefineWithValue != null) {
232+
expect(
233+
result.stdout,
234+
contains(
235+
'Macro FOO is defined: ${customDefineWithValue ? 'BAR' : '1'}',
236+
),
237+
);
238+
} else {
239+
expect(
240+
result.stdout,
241+
contains('Macro FOO is undefined.'),
242+
);
243+
}
244+
});
130245
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2023, 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+
#include <stdio.h>
6+
7+
#define STRINGIFY(X) #X
8+
#define MACRO_IS_UNDEFINED(name) printf("Macro " #name " is undefined.\n");
9+
#define MACRO_IS_DEFINED(name) \
10+
printf("Macro " #name " is defined: " STRINGIFY(name) "\n");
11+
12+
int main() {
13+
#ifdef DEBUG
14+
MACRO_IS_DEFINED(DEBUG);
15+
#else
16+
MACRO_IS_UNDEFINED(DEBUG);
17+
#endif
18+
19+
#ifdef RELEASE
20+
MACRO_IS_DEFINED(RELEASE);
21+
#else
22+
MACRO_IS_UNDEFINED(RELEASE);
23+
#endif
24+
25+
#ifdef NDEBUG
26+
MACRO_IS_DEFINED(NDEBUG);
27+
#else
28+
MACRO_IS_UNDEFINED(NDEBUG);
29+
#endif
30+
31+
#ifdef FOO
32+
MACRO_IS_DEFINED(FOO);
33+
#else
34+
MACRO_IS_UNDEFINED(FOO);
35+
#endif
36+
37+
return 0;
38+
}

0 commit comments

Comments
 (0)