Skip to content

[native_assets_cli] Add DartCApi #1937

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

Closed
wants to merge 1 commit into from
Closed
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
3 changes: 3 additions & 0 deletions .github/workflows/native.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ jobs:
- run: dart pub get -C test_data/no_hook/
if: ${{ matrix.package == 'native_assets_builder' }}

- run: dart pub get -C test_data/use_dart_api/
if: ${{ matrix.package == 'native_assets_builder' }}

- run: dart pub get -C example/build/download_asset/
if: ${{ matrix.package == 'native_assets_cli' }}

Expand Down
56 changes: 56 additions & 0 deletions pkgs/native_assets_builder/test/build_runner/dart_c_api_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2025, 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.

import 'dart:ffi';
import 'dart:io';

import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';

import '../helpers.dart';
import 'helpers.dart';

const Timeout longTimeout = Timeout(Duration(minutes: 5));

void main() async {
test('use_dart_api build', timeout: longTimeout, () async {
await inTempDir((tempUri) async {
await copyTestProjects(targetUri: tempUri);
final packageUri = tempUri.resolve('use_dart_api/');
await runPubGet(
workingDirectory: packageUri,
logger: logger,
);

// Assume we're run from Dart SDK and try to find the include dir.
final includeDirectory = dartExecutable.resolve('../include/');
final versionFile = includeDirectory.resolve('dart_version.h');
final versionContents =
await File(versionFile.toFilePath()).readAsString();
final regex = RegExp(
r'#define DART_API_DL_MAJOR_VERSION (\d+)\s*#define DART_API_DL_MINOR_VERSION (\d+)');
final match = regex.firstMatch(versionContents)!;
final major = int.parse(match.group(1)!);
final minor = int.parse(match.group(2)!);
final dartCApi = DartCApi(
includeDirectory: includeDirectory,
version: Version(major, minor, 0),
);

// Run the build.
final result = (await buildCodeAssets(
packageUri,
dartCApi: dartCApi,
))!;
final codeAsset = CodeAsset.fromEncoded(result.encodedAssets.single);

// Check that we can load the dylib and run the init.
final dylib = DynamicLibrary.open(codeAsset.file!.toFilePath());
final initDartApiDl = dylib.lookupFunction<IntPtr Function(Pointer<Void>),
int Function(Pointer<Void>)>('InitDartApiDL');
final initResult = initDartApiDl(NativeApi.initializeApiDLData);
expect(initResult, 0);
});
});
}
4 changes: 4 additions & 0 deletions pkgs/native_assets_builder/test/build_runner/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Future<BuildResult?> buildCodeAssets(
Uri packageUri, {
String? runPackageName,
List<String>? capturedLogs,
DartCApi? dartCApi,
}) =>
build(
packageUri,
Expand All @@ -63,6 +64,7 @@ Future<BuildResult?> buildCodeAssets(
buildValidator: validateCodeAssetBuildOutput,
applicationAssetValidator: validateCodeAssetInApplication,
runPackageName: runPackageName,
dartCApi: dartCApi,
);

Future<BuildResult?> build(
Expand All @@ -84,6 +86,7 @@ Future<BuildResult?> build(
bool linkingEnabled = false,
required List<String> buildAssetTypes,
Map<String, String>? hookEnvironment,
DartCApi? dartCApi,
}) async {
final targetOS = target?.os ?? OS.current;
final runPackageName_ =
Expand Down Expand Up @@ -122,6 +125,7 @@ Future<BuildResult?> build(
android: targetOS == OS.android
? AndroidCodeConfig(targetNdkApi: targetAndroidNdkApi!)
: null,
dartCApi: dartCApi,
);
}
return inputBuilder;
Expand Down
8 changes: 8 additions & 0 deletions pkgs/native_assets_builder/test_data/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@
- use_all_api/hook/build.dart
- use_all_api/hook/link.dart
- use_all_api/pubspec.yaml
- use_dart_api/ffigen.yaml
- use_dart_api/hook/build.dart
- use_dart_api/lib/src/use_dart_api_bindings_generated.dart
- use_dart_api/lib/use_dart_api.dart
- use_dart_api/pubspec.yaml
- use_dart_api/src/use_dart_api.c
- use_dart_api/src/use_dart_api.h
- use_dart_api/test/use_dart_api_test.dart
- wrong_build_output/hook/build.dart
- wrong_build_output/pubspec.yaml
- wrong_build_output_2/hook/build.dart
Expand Down
11 changes: 11 additions & 0 deletions pkgs/native_assets_builder/test_data/use_dart_api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
An example that uses the [C API of the Dart VM].

The example shows how to pass an object from the Dart heap to native code and
hold on to it via a `PersistentHandle`. For more documentation about handles,
and the other C API features refer to the documentation in the header files.

## Usage

Run tests with `dart --enable-experiment=native-assets test`.

[C API of the Dart VM]: https://github.com/dart-lang/sdk/tree/main/runtime/include
20 changes: 20 additions & 0 deletions pkgs/native_assets_builder/test_data/use_dart_api/ffigen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: NativeAddBindings
description: |
Bindings for `src/use_dart_api.h`.

Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
output: 'lib/src/use_dart_api_bindings_generated.dart'
headers:
entry-points:
- 'src/use_dart_api.h'
include-directives:
- 'src/use_dart_api.h'
preamble: |
// Copyright (c) 2025, 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.
comments:
style: any
length: full
ffi-native:
41 changes: 41 additions & 0 deletions pkgs/native_assets_builder/test_data/use_dart_api/hook/build.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2025, 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.

import 'package:logging/logging.dart';
import 'package:native_assets_cli/code_assets_builder.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';

void main(List<String> arguments) async {
await build(arguments, (input, output) async {
final dartCApi = input.config.code.dartCApi;
if (dartCApi == null) {
throw UnsupportedError(
'This doesn\'t work with access to the Dart C API!',
);
}

final packageName = input.packageName;
final cbuilder = CBuilder.library(
name: packageName,
assetName: 'src/${packageName}_bindings_generated.dart',
sources: [
'src/$packageName.c',
dartCApi.dartApiDlC.toFilePath(),
],
includes: [
dartCApi.includeDirectory.toFilePath(),
],
);
await cbuilder.run(
input: input,
output: output,
logger: Logger('')
..level = Level.ALL
..onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
}),
);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2025, 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.

// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
import 'dart:ffi' as ffi;

@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
external int add(
int a,
int b,
);

@ffi.Native<ffi.IntPtr Function(ffi.Pointer<ffi.Void>)>(symbol: 'InitDartApiDL')
external int InitDartApiDL(
ffi.Pointer<ffi.Void> data,
);

@ffi.Native<ffi.Pointer<ffi.Void> Function(ffi.Handle)>(
symbol: 'NewPersistentHandle')
external ffi.Pointer<ffi.Void> NewPersistentHandle(
Object non_persistent_handle,
);

@ffi.Native<ffi.Handle Function(ffi.Pointer<ffi.Void>)>(
symbol: 'HandleFromPersistent')
external Object HandleFromPersistent(
ffi.Pointer<ffi.Void> persistent_handle,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2025, 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.

export 'src/use_dart_api_bindings_generated.dart';
22 changes: 22 additions & 0 deletions pkgs/native_assets_builder/test_data/use_dart_api/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: use_dart_api
description: Uses some functions from `dart_api_dl.h`.
version: 0.1.0

publish_to: none

environment:
sdk: '>=3.3.0 <4.0.0'

dependencies:
logging: ^1.1.1
# native_assets_cli: ^0.11.0
native_assets_cli:
path: ../../../native_assets_cli/
# native_toolchain_c: ^0.8.0
native_toolchain_c:
path: ../../../native_toolchain_c/

dev_dependencies:
ffigen: ^10.0.0
lints: ^3.0.0
test: ^1.23.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2025, 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 "use_dart_api.h"

int32_t add(int32_t a, int32_t b) { return a + b; }

intptr_t InitDartApiDL(void *data) { return Dart_InitializeApiDL(data); }

void *NewPersistentHandle(Dart_Handle non_persistent_handle) {
return Dart_NewPersistentHandle_DL(non_persistent_handle);
}

Dart_Handle HandleFromPersistent(void *persistent_handle) {
return Dart_HandleFromPersistent_DL(persistent_handle);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2025, 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 <stdint.h>

#include "dart_api_dl.h"

#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif

MYLIB_EXPORT int32_t add(int32_t a, int32_t b);

MYLIB_EXPORT intptr_t InitDartApiDL(void *data);

MYLIB_EXPORT void *NewPersistentHandle(Dart_Handle non_persistent_handle);

MYLIB_EXPORT Dart_Handle HandleFromPersistent(void *persistent_handle);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2025, 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.

import 'dart:ffi';

import 'package:test/test.dart';
import 'package:use_dart_api/use_dart_api.dart';

void main() {
InitDartApiDL(NativeApi.initializeApiDLData);

test('use dart_api_dl.h', () {
const x = 42;
final persistentHandle = NewPersistentHandle(x);
HandleFromPersistent(persistentHandle);
});
}
1 change: 1 addition & 0 deletions pkgs/native_assets_cli/lib/code_assets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export 'src/code_assets/config.dart'
CodeAssetLinkOutputBuilder,
CodeAssetLinkOutputBuilderAdd,
CodeConfig,
DartCApi,
IOSCodeConfig,
MacOSCodeConfig;
export 'src/code_assets/ios_sdk.dart' show IOSSdk;
Expand Down
3 changes: 3 additions & 0 deletions pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ final class CodeAsset {
}..sortOnKey());

static const String type = 'native_code';

@override
String toString() => 'CodeAsset(${encode().encoding})';
}

extension OSLibraryNaming on OS {
Expand Down
Loading
Loading