| // 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. | |
| @OnPlatform({'mac-os': Timeout.factor(2), 'windows': Timeout.factor(10)}) | |
| library; | |
| import 'dart:ffi'; | |
| import 'dart:io'; | |
| import 'package:code_assets/code_assets.dart'; | |
| import 'package:hooks/hooks.dart'; | |
| import 'package:native_toolchain_c/native_toolchain_c.dart'; | |
| import 'package:native_toolchain_c/src/utils/run_process.dart'; | |
| import 'package:process/process.dart'; | |
| import 'package:test/test.dart'; | |
| import '../helpers.dart'; | |
| void main() { | |
| test('Language.toString', () { | |
| expect(Language.c.toString(), 'c'); | |
| expect(Language.cpp.toString(), 'c++'); | |
| }); | |
| final targetOS = OS.current; | |
| final macOSConfig = targetOS == OS.macOS | |
| ? MacOSCodeConfig(targetVersion: defaultMacOSVersion) | |
| : null; | |
| for (final pic in [null, true, false]) { | |
| final picTag = switch (pic) { | |
| null => 'auto_pic', | |
| true => 'pic', | |
| false => 'no_pic', | |
| }; | |
| for (final buildMode in BuildMode.values) { | |
| final suffix = testSuffix([buildMode, picTag]); | |
| test('CBuilder executable$suffix', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final helloWorldCUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/hello_world/src/hello_world.c', | |
| ); | |
| if (!await File.fromUri(helloWorldCUri).exists()) { | |
| throw Exception('Run the test from the root directory.'); | |
| } | |
| const name = 'hello_world'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final cbuilder = CBuilder.executable( | |
| name: name, | |
| sources: [helloWorldCUri.toFilePath()], | |
| pie: pic, | |
| buildMode: buildMode, | |
| ); | |
| await cbuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ); | |
| final executableUri = buildInput.outputDirectory.resolve( | |
| OS.current.executableFileName(name), | |
| ); | |
| expect(await File.fromUri(executableUri).exists(), true); | |
| final result = await runProcess( | |
| executable: executableUri, | |
| logger: logger, | |
| processManager: const LocalProcessManager(), | |
| ); | |
| expect(result.exitCode, 0); | |
| if (buildMode == BuildMode.debug) { | |
| expect(result.stdout.trim(), startsWith('Running in debug mode.')); | |
| } | |
| expect(result.stdout.trim(), endsWith('Hello world.')); | |
| final compilerInvocation = logMessages.singleWhere( | |
| (message) => message.contains(helloWorldCUri.toFilePath()), | |
| ); | |
| switch ((buildInput.config.code.targetOS, pic)) { | |
| case (OS.windows, _) || (_, null): | |
| expect(compilerInvocation, isNot(contains('-fPIC'))); | |
| expect(compilerInvocation, isNot(contains('-fPIE'))); | |
| expect(compilerInvocation, isNot(contains('-fno-PIC'))); | |
| expect(compilerInvocation, isNot(contains('-fno-PIE'))); | |
| case (_, true): | |
| expect(compilerInvocation, contains('-fPIE')); | |
| case (_, false): | |
| expect(compilerInvocation, contains('-fno-PIC')); | |
| expect(compilerInvocation, contains('-fno-PIE')); | |
| } | |
| }); | |
| } | |
| for (final buildCodeAssets in [true, false]) { | |
| final suffix = testSuffix([picTag]); | |
| test('CBuilder dylib$suffix', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final addCUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/add/src/add.c', | |
| ); | |
| const name = 'add'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false); | |
| if (buildCodeAssets) { | |
| buildInputBuilder.addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| } | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final cbuilder = CBuilder.library( | |
| sources: [addCUri.toFilePath()], | |
| name: name, | |
| assetName: name, | |
| pic: pic, | |
| buildMode: .release, | |
| ); | |
| await cbuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ); | |
| final dylibUri = buildInput.outputDirectory.resolve( | |
| OS.current.dylibFileName(name), | |
| ); | |
| expect(await File.fromUri(dylibUri).exists(), equals(buildCodeAssets)); | |
| if (buildCodeAssets) { | |
| final dylib = openDynamicLibraryForTest(dylibUri.toFilePath()); | |
| final add = dylib | |
| .lookupFunction< | |
| Int32 Function(Int32, Int32), | |
| int Function(int, int) | |
| >('add'); | |
| expect(add(1, 2), 3); | |
| final compilerInvocation = logMessages.singleWhere( | |
| (message) => message.contains(addCUri.toFilePath()), | |
| ); | |
| switch ((buildInput.config.code.targetOS, pic)) { | |
| case (OS.windows, _) || (_, null): | |
| expect(compilerInvocation, isNot(contains('-fPIC'))); | |
| expect(compilerInvocation, isNot(contains('-fPIE'))); | |
| expect(compilerInvocation, isNot(contains('-fno-PIC'))); | |
| expect(compilerInvocation, isNot(contains('-fno-PIE'))); | |
| case (_, true): | |
| expect(compilerInvocation, contains('-fPIC')); | |
| case (_, false): | |
| expect(compilerInvocation, contains('-fno-PIC')); | |
| expect(compilerInvocation, contains('-fno-PIE')); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| for (final buildMode in BuildMode.values) { | |
| for (final enabled in [true, false]) { | |
| final suffix = testSuffix([buildMode, enabled ? 'enabled' : 'disabled']); | |
| test( | |
| 'CBuilder build mode defines$suffix', | |
| () => testDefines( | |
| buildMode: buildMode, | |
| buildModeDefine: enabled, | |
| ndebugDefine: enabled, | |
| ), | |
| ); | |
| } | |
| } | |
| for (final value in [true, false]) { | |
| final suffix = testSuffix([value ? 'with_value' : 'without_value']); | |
| test( | |
| 'CBuilder define$suffix', | |
| () => testDefines(customDefineWithValue: value), | |
| ); | |
| } | |
| test('CBuilder flags', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| 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.'); | |
| } | |
| final forcedIncludeCUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/defines/src/forcedInclude.c', | |
| ); | |
| const name = 'defines'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final flag = switch (buildInput.config.code.targetOS) { | |
| OS.windows => '/DFOO=USER_FLAG', | |
| _ => '-DFOO=USER_FLAG', | |
| }; | |
| final cbuilder = CBuilder.executable( | |
| name: name, | |
| sources: [definesCUri.toFilePath()], | |
| forcedIncludes: [forcedIncludeCUri.toFilePath()], | |
| flags: [flag], | |
| buildMode: .release, | |
| ); | |
| await cbuilder.run(input: buildInput, output: buildOutput, logger: logger); | |
| final executableUri = buildInput.outputDirectory.resolve( | |
| OS.current.executableFileName(name), | |
| ); | |
| expect(await File.fromUri(executableUri).exists(), true); | |
| final result = await runProcess( | |
| executable: executableUri, | |
| logger: logger, | |
| processManager: const LocalProcessManager(), | |
| ); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout, contains('Macro FOO is defined: USER_FLAG')); | |
| // Check the forced include is added. | |
| expect(result.stdout, contains('Macro FIFOO is defined: "QuotedFIFOO"')); | |
| final compilerInvocation = logMessages.singleWhere( | |
| (message) => message.contains(definesCUri.toFilePath()), | |
| ); | |
| expect(compilerInvocation, contains(flag)); | |
| }); | |
| test('CBuilder includes', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final includeDirectoryUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/includes/include', | |
| ); | |
| final includesHUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/includes/include/includes.h', | |
| ); | |
| final includesCUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/includes/src/includes.c', | |
| ); | |
| const name = 'includes'; | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutputBuilder = BuildOutputBuilder(); | |
| final cbuilder = CBuilder.library( | |
| name: name, | |
| assetName: name, | |
| includes: [includeDirectoryUri.toFilePath()], | |
| sources: [includesCUri.toFilePath()], | |
| buildMode: .release, | |
| ); | |
| await cbuilder.run( | |
| input: buildInput, | |
| output: buildOutputBuilder, | |
| logger: logger, | |
| ); | |
| final buildOutput = buildOutputBuilder.build(); | |
| expect(buildOutput.dependencies, contains(includesHUri)); | |
| final dylibUri = buildInput.outputDirectory.resolve( | |
| OS.current.dylibFileName(name), | |
| ); | |
| final dylib = openDynamicLibraryForTest(dylibUri.toFilePath()); | |
| final x = dylib.lookup<Int>('x'); | |
| expect(x.value, 42); | |
| }); | |
| test('CBuilder std', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); | |
| const name = 'add'; | |
| const std = 'c99'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final targetOS = OS.current; | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final stdFlag = switch (buildInput.config.code.targetOS) { | |
| OS.windows => '/std:$std', | |
| _ => '-std=$std', | |
| }; | |
| final cbuilder = CBuilder.library( | |
| sources: [addCUri.toFilePath()], | |
| name: name, | |
| assetName: name, | |
| std: std, | |
| buildMode: .release, | |
| ); | |
| await cbuilder.run(input: buildInput, output: buildOutput, logger: logger); | |
| final dylibUri = buildInput.outputDirectory.resolve( | |
| OS.current.dylibFileName(name), | |
| ); | |
| final dylib = openDynamicLibraryForTest(dylibUri.toFilePath()); | |
| final add = dylib | |
| .lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>( | |
| 'add', | |
| ); | |
| expect(add(1, 2), 3); | |
| final compilerInvocation = logMessages.singleWhere( | |
| (message) => message.contains(addCUri.toFilePath()), | |
| ); | |
| expect(compilerInvocation, contains(stdFlag)); | |
| }); | |
| test('CBuilder compile c++', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final helloWorldCppUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/hello_world_cpp/src/hello_world_cpp.cc', | |
| ); | |
| if (!await File.fromUri(helloWorldCppUri).exists()) { | |
| throw Exception('Run the test from the root directory.'); | |
| } | |
| const name = 'hello_world_cpp'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final targetOS = OS.current; | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final defaultStdLibLinkFlag = switch (buildInput.config.code.targetOS) { | |
| OS.windows => null, | |
| OS.linux => '-l stdc++', | |
| OS.macOS => '-l c++', | |
| _ => throw UnimplementedError(), | |
| }; | |
| final cbuilder = CBuilder.executable( | |
| name: name, | |
| sources: [helloWorldCppUri.toFilePath()], | |
| language: .cpp, | |
| buildMode: .release, | |
| ); | |
| await cbuilder.run(input: buildInput, output: buildOutput, logger: logger); | |
| final executableUri = buildInput.outputDirectory.resolve( | |
| OS.current.executableFileName(name), | |
| ); | |
| expect(await File.fromUri(executableUri).exists(), true); | |
| final result = await runProcess( | |
| executable: executableUri, | |
| logger: logger, | |
| processManager: const LocalProcessManager(), | |
| ); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout.trim(), endsWith('Hello world.')); | |
| if (defaultStdLibLinkFlag != null) { | |
| final compilerInvocation = logMessages.singleWhere( | |
| (message) => message.contains(helloWorldCppUri.toFilePath()), | |
| ); | |
| expect(compilerInvocation, contains(defaultStdLibLinkFlag)); | |
| } | |
| }); | |
| test('CBuilder cppLinkStdLib', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final helloWorldCppUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/hello_world_cpp/src/hello_world_cpp.cc', | |
| ); | |
| if (!await File.fromUri(helloWorldCppUri).exists()) { | |
| throw Exception('Run the test from the root directory.'); | |
| } | |
| const name = 'hello_world_cpp'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final targetOS = OS.current; | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final cbuilder = CBuilder.executable( | |
| name: name, | |
| sources: [helloWorldCppUri.toFilePath()], | |
| language: .cpp, | |
| cppLinkStdLib: 'stdc++', | |
| buildMode: .release, | |
| ); | |
| if (buildInput.config.code.targetOS == OS.windows) { | |
| await expectLater( | |
| () => cbuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ), | |
| throwsArgumentError, | |
| ); | |
| } else { | |
| await cbuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ); | |
| final executableUri = buildInput.outputDirectory.resolve( | |
| OS.current.executableFileName(name), | |
| ); | |
| expect(await File.fromUri(executableUri).exists(), true); | |
| final result = await runProcess( | |
| executable: executableUri, | |
| logger: logger, | |
| processManager: const LocalProcessManager(), | |
| ); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout.trim(), endsWith('Hello world.')); | |
| final compilerInvocation = logMessages.singleWhere( | |
| (message) => message.contains(helloWorldCppUri.toFilePath()), | |
| ); | |
| expect(compilerInvocation, contains('-l stdc++')); | |
| } | |
| }); | |
| test('CBuilder libraries and libraryDirectories', () async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| final dynamicallyLinkedSrcUri = packageUri.resolve( | |
| 'test/cbuilder/testfiles/dynamically_linked/src/', | |
| ); | |
| final dynamicallyLinkedCUri = dynamicallyLinkedSrcUri.resolve( | |
| 'dynamically_linked.c', | |
| ); | |
| final debugCUri = dynamicallyLinkedSrcUri.resolve('debug.c'); | |
| final mathCUri = dynamicallyLinkedSrcUri.resolve('math.c'); | |
| if (!await File.fromUri(dynamicallyLinkedCUri).exists()) { | |
| throw Exception('Run the test from the root directory.'); | |
| } | |
| const name = 'dynamically_linked'; | |
| final logMessages = <String>[]; | |
| final logger = createCapturingLogger(logMessages); | |
| final targetOS = OS.current; | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: macOSConfig, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final debugBuilder = CBuilder.library( | |
| name: 'debug', | |
| assetName: 'debug', | |
| includes: [dynamicallyLinkedSrcUri.toFilePath()], | |
| sources: [debugCUri.toFilePath()], | |
| buildMode: .release, | |
| ); | |
| await debugBuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ); | |
| final debugLibraryFile = File.fromUri( | |
| buildInput.outputDirectory.resolve(OS.current.dylibFileName('debug')), | |
| ); | |
| final nestedDebugLibraryFile = File.fromUri( | |
| buildInput.outputDirectory | |
| .resolve('debug/') | |
| .resolve(OS.current.dylibFileName('debug')), | |
| ); | |
| await nestedDebugLibraryFile.parent.create(recursive: true); | |
| await debugLibraryFile.rename(nestedDebugLibraryFile.path); | |
| final mathBuilder = CBuilder.library( | |
| name: 'math', | |
| assetName: 'math', | |
| includes: [dynamicallyLinkedSrcUri.toFilePath()], | |
| sources: [mathCUri.toFilePath()], | |
| libraries: ['debug'], | |
| libraryDirectories: ['debug'], | |
| ); | |
| await mathBuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ); | |
| await nestedDebugLibraryFile.rename(debugLibraryFile.path); | |
| final executableBuilder = CBuilder.executable( | |
| name: name, | |
| includes: [dynamicallyLinkedSrcUri.toFilePath()], | |
| sources: [dynamicallyLinkedCUri.toFilePath()], | |
| libraries: ['math'], | |
| ); | |
| await executableBuilder.run( | |
| input: buildInput, | |
| output: buildOutput, | |
| logger: logger, | |
| ); | |
| final executableUri = buildInput.outputDirectory.resolve( | |
| OS.current.executableFileName(name), | |
| ); | |
| expect(await File.fromUri(executableUri).exists(), true); | |
| final result = await runProcess( | |
| executable: executableUri, | |
| logger: logger, | |
| processManager: const LocalProcessManager(), | |
| ); | |
| expect(result.exitCode, 0); | |
| }); | |
| } | |
| Future<void> testDefines({ | |
| BuildMode buildMode = BuildMode.debug, | |
| bool buildModeDefine = false, | |
| bool ndebugDefine = false, | |
| bool? customDefineWithValue, | |
| }) async { | |
| final tempUri = await tempDirForTest(); | |
| final tempUri2 = await tempDirForTest(); | |
| 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 targetOS = OS.current; | |
| final buildInputBuilder = BuildInputBuilder() | |
| ..setupShared( | |
| packageName: name, | |
| packageRoot: tempUri, | |
| outputFile: tempUri.resolve('output.json'), | |
| outputDirectoryShared: tempUri2, | |
| ) | |
| ..config.setupBuild(linkingEnabled: false) | |
| ..addExtension( | |
| CodeAssetExtension( | |
| targetOS: targetOS, | |
| macOS: targetOS == OS.macOS | |
| ? MacOSCodeConfig(targetVersion: defaultMacOSVersion) | |
| : null, | |
| targetArchitecture: Architecture.current, | |
| // Ignored by executables. | |
| linkModePreference: LinkModePreference.dynamic, | |
| cCompiler: cCompiler, | |
| ), | |
| ); | |
| final buildInput = buildInputBuilder.build(); | |
| final buildOutput = BuildOutputBuilder(); | |
| final cbuilder = CBuilder.executable( | |
| name: name, | |
| sources: [definesCUri.toFilePath()], | |
| defines: { | |
| if (customDefineWithValue != null) | |
| 'FOO': customDefineWithValue ? 'BAR' : null, | |
| }, | |
| buildModeDefine: buildModeDefine, | |
| ndebugDefine: ndebugDefine, | |
| buildMode: buildMode, | |
| ); | |
| await cbuilder.run(input: buildInput, output: buildOutput, logger: logger); | |
| final executableUri = buildInput.outputDirectory.resolve( | |
| OS.current.executableFileName(name), | |
| ); | |
| expect(await File.fromUri(executableUri).exists(), true); | |
| final result = await runProcess( | |
| executable: executableUri, | |
| logger: logger, | |
| processManager: const LocalProcessManager(), | |
| ); | |
| 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 != .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.')); | |
| } | |
| } |