diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index 2282ed68a..e62a5d701 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -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` diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index 717ff0c68..fa3313dd2 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -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 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 + /// 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; @@ -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(); } diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart index 1fe1b3540..f68750ee3 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -32,6 +32,8 @@ class RunCBuilder { /// Can be modified with `install_name_tool`. final Uri? installName; + final Map defines; + RunCBuilder({ required this.buildConfig, this.logger, @@ -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] @@ -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, @@ -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', diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml index 1101f60c9..6d4a63b74 100644 --- a/pkgs/native_toolchain_c/pubspec.yaml +++ b/pkgs/native_toolchain_c/pubspec.yaml @@ -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: diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart index 4b6812373..d1f63ff0f 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart @@ -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 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.'), + ); + } + }); } diff --git a/pkgs/native_toolchain_c/test/cbuilder/testfiles/defines/src/defines.c b/pkgs/native_toolchain_c/test/cbuilder/testfiles/defines/src/defines.c new file mode 100644 index 000000000..7bb554b32 --- /dev/null +++ b/pkgs/native_toolchain_c/test/cbuilder/testfiles/defines/src/defines.c @@ -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 + +#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; +}