diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 2aab3d265..84ea83274 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -142,6 +142,9 @@ jobs: - run: dart --enable-experiment=native-assets test working-directory: pkgs/${{ matrix.package }}/example/build/native_dynamic_linking/ + # TODO(https://github.com/dart-lang/native/issues/190): Enable on windows once + # https://github.com/dart-lang/sdk/commit/903eea6bfb8ee405587f0866a1d1e92eea45d29e + # has landed in dev channel. if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change && matrix.os != 'windows' }} - run: dart --enable-experiment=native-assets test diff --git a/pkgs/native_assets_builder/test_data/native_dynamic_linking/hook/build.dart b/pkgs/native_assets_builder/test_data/native_dynamic_linking/hook/build.dart index 79b97a40e..0713eab62 100644 --- a/pkgs/native_assets_builder/test_data/native_dynamic_linking/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/native_dynamic_linking/hook/build.dart @@ -26,9 +26,7 @@ void main(List args) async { sources: [ 'src/math.c', ], - // TODO(https://github.com/dart-lang/native/issues/190): Use specific - // API for linking once available. - flags: config.dynamicLinkingFlags('debug'), + libraries: ['debug'], ), CBuilder.library( name: 'add', @@ -36,13 +34,11 @@ void main(List args) async { sources: [ 'src/add.c', ], - // TODO(https://github.com/dart-lang/native/issues/190): Use specific - // API for linking once available. - flags: config.dynamicLinkingFlags('math'), + libraries: ['math'], ) ]; - // Note: This builders need to be run sequentially because they depend on + // Note: These builders need to be run sequentially because they depend on // each others output. for (final builder in builders) { await builder.run( @@ -53,21 +49,3 @@ void main(List args) async { } }); } - -extension on BuildConfig { - List dynamicLinkingFlags(String libraryName) => switch (targetOS) { - OS.macOS => [ - '-L${outputDirectory.toFilePath()}', - '-l$libraryName', - ], - OS.linux => [ - r'-Wl,-rpath=$ORIGIN', - '-L${outputDirectory.toFilePath()}', - '-l$libraryName', - ], - OS.windows => [ - outputDirectory.resolve('$libraryName.lib').toFilePath(), - ], - _ => throw UnimplementedError('Unsupported OS: $targetOS'), - }; -} diff --git a/pkgs/native_assets_builder/test_data/native_dynamic_linking/src/debug.c b/pkgs/native_assets_builder/test_data/native_dynamic_linking/src/debug.c index 92e42f1d2..b840d62a5 100644 --- a/pkgs/native_assets_builder/test_data/native_dynamic_linking/src/debug.c +++ b/pkgs/native_assets_builder/test_data/native_dynamic_linking/src/debug.c @@ -16,5 +16,7 @@ int debug_printf(const char* format, ...) { int ret = vprintf(format, args); va_end(args); return ret; +#else + return 0; #endif } diff --git a/pkgs/native_assets_cli/example/build/native_dynamic_linking/hook/build.dart b/pkgs/native_assets_cli/example/build/native_dynamic_linking/hook/build.dart index 88b3d2d12..0713eab62 100644 --- a/pkgs/native_assets_cli/example/build/native_dynamic_linking/hook/build.dart +++ b/pkgs/native_assets_cli/example/build/native_dynamic_linking/hook/build.dart @@ -26,9 +26,7 @@ void main(List args) async { sources: [ 'src/math.c', ], - // TODO(https://github.com/dart-lang/native/issues/190): Use specific - // API for linking once available. - flags: config.dynamicLinkingFlags('debug'), + libraries: ['debug'], ), CBuilder.library( name: 'add', @@ -36,13 +34,11 @@ void main(List args) async { sources: [ 'src/add.c', ], - // TODO(https://github.com/dart-lang/native/issues/190): Use specific - // API for linking once available. - flags: config.dynamicLinkingFlags('math'), + libraries: ['math'], ) ]; - // Note: This builders need to be run sequentially because they depend on + // Note: These builders need to be run sequentially because they depend on // each others output. for (final builder in builders) { await builder.run( @@ -53,20 +49,3 @@ void main(List args) async { } }); } - -extension on BuildConfig { - List dynamicLinkingFlags(String libraryName) => switch (targetOS) { - OS.macOS => [ - '-L${outputDirectory.toFilePath()}', - '-l$libraryName', - ], - OS.linux => [ - '-Wl,-rpath=\$ORIGIN/.', - '-L${outputDirectory.toFilePath()}', - '-l$libraryName', - ], - // TODO(https://github.com/dart-lang/native/issues/1415): Enable support - // for Windows once linker flags are supported by CBuilder. - _ => throw UnimplementedError('Unsupported OS: $targetOS'), - }; -} diff --git a/pkgs/native_assets_cli/example/build/native_dynamic_linking/src/debug.c b/pkgs/native_assets_cli/example/build/native_dynamic_linking/src/debug.c index 92e42f1d2..b840d62a5 100644 --- a/pkgs/native_assets_cli/example/build/native_dynamic_linking/src/debug.c +++ b/pkgs/native_assets_cli/example/build/native_dynamic_linking/src/debug.c @@ -16,5 +16,7 @@ int debug_printf(const char* format, ...) { int ret = vprintf(format, args); va_end(args); return ret; +#else + return 0; #endif } diff --git a/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart b/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart index a721b058f..b70e28274 100644 --- a/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart +++ b/pkgs/native_assets_cli/test/example/native_dynamic_linking_test.dart @@ -6,8 +6,9 @@ 'mac-os': Timeout.factor(2), 'windows': Timeout.factor(10), }) -// TODO(https://github.com/dart-lang/native/issues/1415): Enable support -// for Windows once linker flags are supported by CBuilder. +// TODO(https://github.com/dart-lang/native/issues/190): Enable on windows once +// https://github.com/dart-lang/sdk/commit/903eea6bfb8ee405587f0866a1d1e92eea45d29e +// has landed in dev channel. @TestOn('!windows') library; @@ -20,12 +21,6 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() async { - if (Platform.isWindows) { - // TODO(https://github.com/dart-lang/native/issues/1415): Enable support - // for Windows once linker flags are supported by CBuilder. - return; - } - late Uri tempUri; const name = 'native_dynamic_linking'; @@ -51,19 +46,22 @@ void main() async { final configBuilder = BuildConfigBuilder() ..setupHookConfig( - packageRoot: testPackageUri, - packageName: name, - targetOS: OS.current, - buildAssetTypes: [CodeAsset.type], - buildMode: dryRun ? null : BuildMode.debug) + packageRoot: testPackageUri, + packageName: name, + targetOS: OS.current, + buildAssetTypes: [CodeAsset.type], + buildMode: dryRun ? null : BuildMode.debug, + ) ..setupBuildRunConfig( - outputDirectory: outputDirectory, - outputDirectoryShared: outputDirectoryShared) + outputDirectory: outputDirectory, + outputDirectoryShared: outputDirectoryShared, + ) ..setupBuildConfig(linkingEnabled: false, dryRun: dryRun) ..setupCodeConfig( - targetArchitecture: dryRun ? null : Architecture.current, - linkModePreference: LinkModePreference.dynamic, - cCompilerConfig: dryRun ? null : cCompiler); + targetArchitecture: dryRun ? null : Architecture.current, + linkModePreference: LinkModePreference.dynamic, + cCompilerConfig: dryRun ? null : cCompiler, + ); final buildConfigUri = testTempUri.resolve('build_config.json'); await File.fromUri(buildConfigUri) diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index d0edf147c..0d1a8e172 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -4,6 +4,7 @@ https://github.com/dart-lang/native/issues/1611 - Make optimization level configurable. Defaults to `-3s` and `/O3`. https://github.com/dart-lang/native/issues/1267 +- Add `libraries` and `libraryDirectories` to `CTool`. ## 0.6.0 diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index 81d6b8f11..8e99e7ebc 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -53,6 +53,8 @@ class CBuilder extends CTool implements Builder { super.sources = const [], super.includes = const [], super.frameworks = CTool.defaultFrameworks, + super.libraries = const [], + super.libraryDirectories = CTool.defaultLibraryDirectories, @Deprecated( 'Newer Dart and Flutter SDKs automatically add the Dart hook ' 'sources as dependencies.', @@ -76,6 +78,8 @@ class CBuilder extends CTool implements Builder { super.sources = const [], super.includes = const [], super.frameworks = CTool.defaultFrameworks, + super.libraries = const [], + super.libraryDirectories = CTool.defaultLibraryDirectories, @Deprecated( 'Newer Dart and Flutter SDKs automatically add the Dart hook ' 'sources as dependencies.', @@ -132,6 +136,10 @@ class CBuilder extends CTool implements Builder { // ignore: deprecated_member_use_from_same_package for (final source in this.dartBuildFiles) packageRoot.resolve(source), ]; + final libraryDirectories = [ + for (final directory in this.libraryDirectories) + outDir.resolveUri(Uri.file(directory)), + ]; // ignore: deprecated_member_use if (!config.dryRun) { final task = RunCBuilder( @@ -141,6 +149,8 @@ class CBuilder extends CTool implements Builder { sources: sources, includes: includes, frameworks: frameworks, + libraries: libraries, + libraryDirectories: libraryDirectories, dynamicLibrary: type == OutputType.library && linkMode == DynamicLoadingBundled() ? libUri diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart index b79174967..aa804863e 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart @@ -29,6 +29,8 @@ class CLinker extends CTool implements Linker { super.sources = const [], super.includes = const [], super.frameworks = CTool.defaultFrameworks, + super.libraries = const [], + super.libraryDirectories = CTool.defaultLibraryDirectories, @visibleForTesting super.installName, super.flags = const [], super.defines = const {}, @@ -68,6 +70,10 @@ class CLinker extends CTool implements Linker { for (final directory in this.includes) packageRoot.resolveUri(Uri.file(directory)), ]; + final libraryDirectories = [ + for (final directory in this.libraryDirectories) + outDir.resolveUri(Uri.file(directory)), + ]; final task = RunCBuilder( config: config, codeConfig: config.codeConfig, @@ -76,6 +82,8 @@ class CLinker extends CTool implements Linker { sources: sources, includes: includes, frameworks: frameworks, + libraries: libraries, + libraryDirectories: libraryDirectories, dynamicLibrary: linkMode == DynamicLoadingBundled() ? libUri : null, staticLibrary: linkMode == StaticLinking() ? libUri : null, // ignore: invalid_use_of_visible_for_testing_member diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart index 1a977e106..13e9ff0ad 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/ctool.dart @@ -6,15 +6,17 @@ import 'package:meta/meta.dart'; import 'package:native_assets_cli/code_assets.dart'; import 'cbuilder.dart'; +import 'clinker.dart'; import 'language.dart'; import 'optimization_level.dart'; import 'output_type.dart'; +/// Common options for [CBuilder] and [CLinker]. abstract class CTool { /// What kind of artifact to build. final OutputType type; - /// Name of the library or executable to linkg. + /// Name of the library or executable to build or link. /// /// The filename will be decided by [LinkConfig.targetOS] and /// [OSLibraryNaming.libraryFileName] or @@ -50,7 +52,7 @@ abstract class CTool { /// /// Defaults to `['Foundation']`. /// - /// Framworks will not be automatically reported as dependencies of the hook. + /// Frameworks will not be automatically reported as dependencies of the hook. /// Frameworks can be mentioned by name if they are available on the system, /// so the file path is not known. If you're depending on your own frameworks /// report them as dependencies of the hook by calling @@ -58,8 +60,31 @@ abstract class CTool { /// manually. final List frameworks; + /// The default [frameworks]. static const List defaultFrameworks = ['Foundation']; + /// Libraries to link to. + /// + /// In addition to the system default directories, libraries will be searched + /// for in [libraryDirectories]. + /// + /// If you want to link to a library that was built by another [CBuilder] or + /// [CLinker], either leave the default [libraryDirectories] or include `'.'` + /// in the list. + final List libraries; + + /// Directories to search for [libraries], in addition to the system default + /// directories. + /// + /// Resolved against [LinkConfig.outputDirectory]. + /// + /// Defaults to `['.']`, which means the [LinkConfig.outputDirectory] will be + /// searched for libraries. + final List libraryDirectories; + + /// The default [libraryDirectories]. + static const List defaultLibraryDirectories = ['.']; + /// TODO(https://github.com/dart-lang/native/issues/54): Move to [LinkConfig] /// or hide in public API. @visibleForTesting @@ -130,6 +155,8 @@ abstract class CTool { required this.sources, required this.includes, required this.frameworks, + required this.libraries, + required this.libraryDirectories, required this.installName, required this.flags, required this.defines, 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 8342f109c..a4b8197c1 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -28,6 +28,8 @@ class RunCBuilder { final List sources; final List includes; final List frameworks; + final List libraries; + final List libraryDirectories; final Uri? executable; final Uri? dynamicLibrary; final Uri? staticLibrary; @@ -56,6 +58,8 @@ class RunCBuilder { this.sources = const [], this.includes = const [], required this.frameworks, + this.libraries = const [], + this.libraryDirectories = const [], this.executable, this.dynamicLibrary, this.staticLibrary, @@ -298,8 +302,7 @@ class RunCBuilder { if (executable != null) ...[ '-o', outDir.resolveUri(executable!).toFilePath(), - ], - if (dynamicLibrary != null) ...[ + ] else if (dynamicLibrary != null) ...[ '--shared', '-o', outFile!.toFilePath(), @@ -310,6 +313,19 @@ class RunCBuilder { ], ...linkerOptions?.postSourcesFlags(toolInstance.tool, sourceFiles) ?? [], + if (executable != null || dynamicLibrary != null) ...[ + if (config.targetOS case OS.android || OS.linux) + // During bundling code assets are all placed in the same directory. + // Setting this rpath allows the binary to find other code assets + // it is linked against. + if (linkerOptions != null) + '-rpath=\$ORIGIN' + else + '-Wl,-rpath=\$ORIGIN', + for (final directory in libraryDirectories) + '-L${directory.toFilePath()}', + for (final library in libraries) '-l$library', + ], ], logger: logger, captureOutput: false, @@ -344,17 +360,20 @@ class RunCBuilder { ...sources.map((e) => e.toFilePath()), '/link', '/out:${outDir.resolveUri(executable!).toFilePath()}', - ], - if (dynamicLibrary != null) ...[ + ] else if (dynamicLibrary != null) ...[ ...sources.map((e) => e.toFilePath()), '/link', '/DLL', '/out:${outDir.resolveUri(dynamicLibrary!).toFilePath()}', - ], - if (staticLibrary != null) ...[ + ] else if (staticLibrary != null) ...[ '/c', ...sources.map((e) => e.toFilePath()), ], + if (executable != null || dynamicLibrary != null) ...[ + for (final directory in libraryDirectories) + '/LIBPATH:${directory.toFilePath()}', + for (final library in libraries) '$library.lib', + ], ], workingDirectory: outDir, environment: environment, diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart index 3906383e7..d076c7fd9 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart @@ -554,6 +554,110 @@ void main() { 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 = []; + final logger = createCapturingLogger(logMessages); + + final buildConfigBuilder = BuildConfigBuilder() + ..setupHookConfig( + buildAssetTypes: [CodeAsset.type], + packageName: name, + packageRoot: tempUri, + targetOS: OS.current, + buildMode: BuildMode.release, + ) + ..setupBuildConfig( + linkingEnabled: false, + dryRun: false, + ) + ..setupCodeConfig( + targetArchitecture: Architecture.current, + // Ignored by executables. + linkModePreference: LinkModePreference.dynamic, + cCompilerConfig: cCompiler, + ); + buildConfigBuilder.setupBuildRunConfig( + outputDirectory: tempUri, + outputDirectoryShared: tempUri2, + ); + final buildConfig = BuildConfig(buildConfigBuilder.json); + final buildOutput = BuildOutputBuilder(); + + final debugBuilder = CBuilder.library( + name: 'debug', + assetName: 'debug', + includes: [dynamicallyLinkedSrcUri.toFilePath()], + sources: [debugCUri.toFilePath()], + ); + + await debugBuilder.run( + config: buildConfig, + output: buildOutput, + logger: logger, + ); + + final debugLibraryFile = + File.fromUri(tempUri.resolve(OS.current.dylibFileName('debug'))); + final nestedDebugLibraryFile = File.fromUri( + tempUri.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( + config: buildConfig, + 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( + config: buildConfig, + output: buildOutput, + logger: logger, + ); + + final executableUri = tempUri.resolve(OS.current.executableFileName(name)); + expect(await File.fromUri(executableUri).exists(), true); + final result = await runProcess( + executable: executableUri, + logger: logger, + ); + expect(result.exitCode, 0); + }); } Future testDefines({ diff --git a/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/debug.c b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/debug.c new file mode 100644 index 000000000..b840d62a5 --- /dev/null +++ b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/debug.c @@ -0,0 +1,22 @@ +// Copyright (c) 2024, 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 "debug.h" + +#ifdef DEBUG +#include +#include +#endif + +int debug_printf(const char* format, ...) { +#ifdef DEBUG + va_list args; + va_start(args, format); + int ret = vprintf(format, args); + va_end(args); + return ret; +#else + return 0; +#endif +} diff --git a/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/debug.h b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/debug.h new file mode 100644 index 000000000..557cba0d1 --- /dev/null +++ b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/debug.h @@ -0,0 +1,11 @@ +// Copyright (c) 2024, 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. + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int debug_printf(const char * format, ...); diff --git a/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/dynamically_linked.c b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/dynamically_linked.c new file mode 100644 index 000000000..c8cc6d94b --- /dev/null +++ b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/dynamically_linked.c @@ -0,0 +1,9 @@ +// Copyright (c) 2024, 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 "math.h" + +int main() { + return math_add(1, 2) == 3 ? 0 : 1; +} diff --git a/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/math.c b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/math.c new file mode 100644 index 000000000..08313a5bb --- /dev/null +++ b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/math.c @@ -0,0 +1,11 @@ +// Copyright (c) 2024, 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 "debug.h" +#include "math.h" + +int32_t math_add(int32_t a, int32_t b) { + debug_printf("Adding %i and %i.\n", a, b); + return a + b; +} diff --git a/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/math.h b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/math.h new file mode 100644 index 000000000..7d22a65c8 --- /dev/null +++ b/pkgs/native_toolchain_c/test/cbuilder/testfiles/dynamically_linked/src/math.h @@ -0,0 +1,13 @@ +// Copyright (c) 2024, 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 + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t math_add(int32_t a, int32_t b);