Skip to content

[native_toolchain_c] Add support for configuring preprocessor macro defines #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkgs/native_toolchain_c/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.2.1

- Added `defines` for specifying custom defines.
- Added `buildModeDefine` to toggle define for current build mode.
- Added `ndebugDefine` to toggle define of `NDEBUG` for non-debug builds.

## 0.2.0

- **Breaking change** Rename `assetName` to `assetId`
Expand Down
36 changes: 36 additions & 0 deletions pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,48 @@ class CBuilder implements Builder {
@visibleForTesting
final Uri? installName;

/// Definitions of preprocessor macros.
///
/// When the value is `null`, the macro is defined without a value.
final Map<String, String?> defines;

/// Whether to define a macro for the current [BuildMode].
///
/// The macro name is the uppercase name of the build mode and does not have a
Copy link
Collaborator

@dcharkes dcharkes Sep 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: I found this in some build systems to be the default, but I didn't add documentation here. 🙄

/// value.
///
/// Defaults to `true`.
final bool buildModeDefine;

/// Whether to define the standard `NDEBUG` macro when _not_ building with
/// [BuildMode.debug].
///
/// When `NDEBUG` is defined, the C/C++ standard library
/// [`assert` macro in `assert.h`](https://en.wikipedia.org/wiki/Assert.h)
/// becomes a no-op. Other C/C++ code commonly use `NDEBUG` to disable debug
/// features, as well.
///
/// Defaults to `true`.
final bool ndebugDefine;

CBuilder.library({
required this.name,
required this.assetId,
this.sources = const [],
this.dartBuildFiles = const ['build.dart'],
@visibleForTesting this.installName,
this.defines = const {},
this.buildModeDefine = true,
this.ndebugDefine = true,
}) : _type = _CBuilderType.library;

CBuilder.executable({
required this.name,
this.sources = const [],
this.dartBuildFiles = const ['build.dart'],
this.defines = const {},
this.buildModeDefine = true,
this.ndebugDefine = true,
}) : _type = _CBuilderType.executable,
assetId = null,
installName = null;
Expand Down Expand Up @@ -112,6 +142,12 @@ class CBuilder implements Builder {
: null,
executable: _type == _CBuilderType.executable ? exeUri : null,
installName: installName,
defines: {
...defines,
if (buildModeDefine) buildConfig.buildMode.name.toUpperCase(): null,
if (ndebugDefine && buildConfig.buildMode != BuildMode.debug)
'NDEBUG': null,
},
);
await task.run();
}
Expand Down
17 changes: 7 additions & 10 deletions pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class RunCBuilder {
/// Can be modified with `install_name_tool`.
final Uri? installName;

final Map<String, String?> defines;

RunCBuilder({
required this.buildConfig,
this.logger,
Expand All @@ -40,6 +42,7 @@ class RunCBuilder {
this.dynamicLibrary,
this.staticLibrary,
this.installName,
this.defines = const {},
}) : outDir = buildConfig.outDir,
target = buildConfig.target,
assert([executable, dynamicLibrary, staticLibrary]
Expand Down Expand Up @@ -142,11 +145,8 @@ class RunCBuilder {
'-o',
outDir.resolve('out.o').toFilePath(),
],
// TODO(https://github.com/dart-lang/native/issues/50): The defines
// should probably be configurable. That way, the mapping from
// build_mode to defines can be defined in a project-dependent way in
// each project build.dart.
'-D${buildConfig.buildMode.name.toUpperCase()}'
for (final MapEntry(key: name, :value) in defines.entries)
if (value == null) '-D$name' else '-D$name=$value',
],
logger: logger,
captureOutput: false,
Expand Down Expand Up @@ -181,11 +181,8 @@ class RunCBuilder {
final result = await runProcess(
executable: compiler.uri,
arguments: [
// TODO(https://github.com/dart-lang/native/issues/50): The defines
// should probably be configurable. That way, the mapping from
// build_mode to defines can be defined in a project-dependent way in
// each project build.dart.
'/D${buildConfig.buildMode.name.toUpperCase()}',
for (final MapEntry(key: name, :value) in defines.entries)
if (value == null) '/D$name' else '/D$name=$value',
if (executable != null) ...[
...sources.map((e) => e.toFilePath()),
'/link',
Expand Down
2 changes: 1 addition & 1 deletion pkgs/native_toolchain_c/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: native_toolchain_c
description: >-
A library to invoke the native C compiler installed on the host machine.
version: 0.2.0
version: 0.2.1
repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c

topics:
Expand Down
115 changes: 115 additions & 0 deletions pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,119 @@ void main() {
);
});
}

for (final buildMode in BuildMode.values) {
for (final enabled in [true, false]) {
test(
'Cbuilder build mode defines ${enabled ? 'enabled' : 'disabled'} for '
'$buildMode',
() => testDefines(
buildMode: buildMode,
buildModeDefine: enabled,
ndebugDefine: enabled,
),
);
}
}

for (final value in [true, false]) {
test(
'Cbuilder define ${value ? 'with' : 'without'} value',
() => testDefines(customDefineWithValue: value),
);
}
}

Future<void> testDefines({
BuildMode buildMode = BuildMode.debug,
bool buildModeDefine = false,
bool ndebugDefine = false,
bool? customDefineWithValue,
}) async {
await inTempDir((tempUri) async {
final definesCUri =
packageUri.resolve('test/cbuilder/testfiles/defines/src/defines.c');
if (!await File.fromUri(definesCUri).exists()) {
throw Exception('Run the test from the root directory.');
}
const name = 'defines';

final buildConfig = BuildConfig(
outDir: tempUri,
packageRoot: tempUri,
targetArchitecture: Architecture.current,
targetOs: OS.current,
buildMode: buildMode,
// Ignored by executables.
linkModePreference: LinkModePreference.dynamic,
cCompiler: CCompilerConfig(
cc: cc,
envScript: envScript,
envScriptArgs: envScriptArgs,
),
);
final buildOutput = BuildOutput();
final cbuilder = CBuilder.executable(
name: name,
sources: [definesCUri.toFilePath()],
defines: {
if (customDefineWithValue != null)
'FOO': customDefineWithValue ? 'BAR' : null,
},
buildModeDefine: buildModeDefine,
ndebugDefine: ndebugDefine,
);
await cbuilder.run(
buildConfig: buildConfig,
buildOutput: buildOutput,
logger: logger,
);

final executableUri =
tempUri.resolve(Target.current.os.executableFileName(name));
expect(await File.fromUri(executableUri).exists(), true);
final result = await runProcess(
executable: executableUri,
logger: logger,
);
expect(result.exitCode, 0);

if (buildModeDefine) {
expect(
result.stdout,
contains('Macro ${buildMode.name.toUpperCase()} is defined: 1'),
);
} else {
expect(
result.stdout,
contains('Macro ${buildMode.name.toUpperCase()} is undefined.'),
);
}

if (ndebugDefine && buildMode != BuildMode.debug) {
expect(
result.stdout,
contains('Macro NDEBUG is defined: 1'),
);
} else {
expect(
result.stdout,
contains('Macro NDEBUG is undefined.'),
);
}

if (customDefineWithValue != null) {
expect(
result.stdout,
contains(
'Macro FOO is defined: ${customDefineWithValue ? 'BAR' : '1'}',
),
);
} else {
expect(
result.stdout,
contains('Macro FOO is undefined.'),
);
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2023, 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.

#include <stdio.h>

#define STRINGIFY(X) #X
#define MACRO_IS_UNDEFINED(name) printf("Macro " #name " is undefined.\n");
#define MACRO_IS_DEFINED(name) \
printf("Macro " #name " is defined: " STRINGIFY(name) "\n");

int main() {
#ifdef DEBUG
MACRO_IS_DEFINED(DEBUG);
#else
MACRO_IS_UNDEFINED(DEBUG);
#endif

#ifdef RELEASE
MACRO_IS_DEFINED(RELEASE);
#else
MACRO_IS_UNDEFINED(RELEASE);
#endif

#ifdef NDEBUG
MACRO_IS_DEFINED(NDEBUG);
#else
MACRO_IS_UNDEFINED(NDEBUG);
#endif

#ifdef FOO
MACRO_IS_DEFINED(FOO);
#else
MACRO_IS_UNDEFINED(FOO);
#endif

return 0;
}