diff --git a/.github/workflows/download_asset.yaml b/.github/workflows/download_asset.yaml new file mode 100644 index 000000000..0d4d21b68 --- /dev/null +++ b/.github/workflows/download_asset.yaml @@ -0,0 +1,129 @@ +name: Prebuilt native assets for package:download_asset + +permissions: + contents: write + +on: + pull_request: + branches: [ main ] + paths: + - .github/workflows/download_asset.yaml + - pkgs/native_assets_cli/example/build/download_asset/ + push: + tags: + - 'download_asset-prebuild-assets-*' + workflow_dispatch: + +jobs: + build-native-libraries: + name: Prebuilt native libraries + + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + + runs-on: ${{ matrix.os }}-latest + + defaults: + run: + working-directory: pkgs/native_assets_cli/example/build/download_asset/ + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + with: + sdk: stable + + - uses: nttld/setup-ndk@afb4c9964b521afb97c864b7d40b11e6911bd410 + with: + ndk-version: r27 + if: ${{ matrix.os == 'linux' }} # Only build on one host. + + - run: dart pub get + + - name: Build Linux host + if: matrix.os == 'ubuntu' + run: | + dart tool/build.dart -oandroid -aarm + dart tool/build.dart -oandroid -aarm64 + dart tool/build.dart -oandroid -aia32 + dart tool/build.dart -oandroid -ariscv64 + dart tool/build.dart -oandroid -ax64 + dart tool/build.dart -olinux -aarm + dart tool/build.dart -olinux -aarm64 + dart tool/build.dart -olinux -aia32 + dart tool/build.dart -olinux -ax64 + + - name: Build MacOS host + if: matrix.os == 'macos' + run: | + dart tool/build.dart -omacos -aarm64 + dart tool/build.dart -omacos -ax64 + dart tool/build.dart -oios -iiphoneos -aarm64 + dart tool/build.dart -oios -iiphonesimulator -aarm64 + dart tool/build.dart -oios -iiphonesimulator -ax64 + + - name: Build Windows host + if: matrix.os == 'windows' + run: | + dart tool/build.dart -owindows -aarm + dart tool/build.dart -owindows -aarm64 + dart tool/build.dart -owindows -aia32 + dart tool/build.dart -owindows -ax64 + + # - name: Upload artifacts + # uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 + # with: + # name: dart-${{matrix.os}}-${{ matrix.compiletype }}-libs + # path: submodules/icu4x/bin + +# check_hashes: +# needs: dart-libs +# runs-on: ubuntu-latest + +# env: +# ICU4X_BUILD_MODE: local + +# steps: +# - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 +# with: +# submodules: true + +# - name: Download binaries +# uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 +# with: +# merge-multiple: true + +# - name: Display structure of downloaded files +# run: ls -R + +# - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 +# with: +# sdk: dev + +# - name: Check hashes of released artifacts +# run: | +# cd pkgs/intl4x +# dart pub get +# dart --enable-experiment=native-assets tool/regenerate_hashes.dart +# git diff --exit-code + +# release: +# needs: dart-libs +# runs-on: ubuntu-latest +# if: github.event_name == 'push' +# steps: +# - name: Download binaries +# uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 + +# - name: Display structure of downloaded files +# run: ls -R + +# - name: Release +# uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 +# with: +# files: '**' +# fail_on_unmatched_files: true +# body: "Build artifacts for `package:intl4x`, based on ICU4X at ref ${{ github.ref_name }}" diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index afcbe00dd..123e92aa1 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -105,6 +105,9 @@ jobs: - run: dart pub get -C test_data/native_dynamic_linking/ if: ${{ matrix.package == 'native_assets_builder' }} + - run: dart pub get -C example/build/download_asset/ + if: ${{ matrix.package == 'native_assets_cli' }} + - run: dart pub get -C example/build/native_dynamic_linking/ if: ${{ matrix.package == 'native_assets_cli' }} diff --git a/pkgs/native_assets_cli/example/build/download_asset/README.md b/pkgs/native_assets_cli/example/build/download_asset/README.md new file mode 100644 index 000000000..b69e66dcc --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/README.md @@ -0,0 +1,14 @@ +An example of a library depending on prebuilt assets which are downloaded in +the build hook. + +## Usage + +Run tests with `dart --enable-experiment=native-assets test`. + +## Code organization + +A typical layout of a package which downloads assets: + +* `hook/build.dart` downloads the prebuilt assets. +* `lib/` contains Dart code which uses the assets. +* `tool/build.dart` prebuilts assets and is exercised from the GitHub CI. diff --git a/pkgs/native_assets_cli/example/build/download_asset/ffigen.yaml b/pkgs/native_assets_cli/example/build/download_asset/ffigen.yaml new file mode 100644 index 000000000..f54281512 --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/ffigen.yaml @@ -0,0 +1,20 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: NativeAddBindings +description: | + Bindings for `src/native_add.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: 'lib/native_add.dart' +headers: + entry-points: + - 'src/native_add.h' + include-directives: + - 'src/native_add.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: diff --git a/pkgs/native_assets_cli/example/build/download_asset/hook/build.dart b/pkgs/native_assets_cli/example/build/download_asset/hook/build.dart new file mode 100644 index 000000000..b4d19f460 --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/hook/build.dart @@ -0,0 +1,27 @@ +// 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/native_assets_cli.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +void main(List args) async { + await build(args, (config, output) async { + final packageName = config.packageName; + final cbuilder = CBuilder.library( + name: packageName, + assetName: '$packageName.dart', + sources: [ + 'src/$packageName.c', + ], + ); + await cbuilder.run( + config: config, + output: output, + logger: Logger('') + ..level = Level.ALL + ..onRecord.listen((record) => print(record.message)), + ); + }); +} diff --git a/pkgs/native_assets_cli/example/build/download_asset/lib/native_add.dart b/pkgs/native_assets_cli/example/build/download_asset/lib/native_add.dart new file mode 100644 index 000000000..f9f8e1e7b --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/lib/native_add.dart @@ -0,0 +1,15 @@ +// 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(symbol: 'add') +external int add( + int a, + int b, +); diff --git a/pkgs/native_assets_cli/example/build/download_asset/lib/src/build_helpers.dart b/pkgs/native_assets_cli/example/build/download_asset/lib/src/build_helpers.dart new file mode 100644 index 000000000..bb62fd968 --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/lib/src/build_helpers.dart @@ -0,0 +1,7 @@ +// 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. + +const androidTargetNdkApi = 30; +const int macOSTargetVersion = 13; +const iOSTargetVersion = 16; diff --git a/pkgs/native_assets_cli/example/build/download_asset/pubspec.yaml b/pkgs/native_assets_cli/example/build/download_asset/pubspec.yaml new file mode 100644 index 000000000..82137dabf --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/pubspec.yaml @@ -0,0 +1,24 @@ +publish_to: none + +name: download_asset +description: Sums two numbers with native code, prebuilt assets. +version: 0.1.0 +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/download_asset + +environment: + sdk: '>=3.3.0 <4.0.0' + +dependencies: + logging: ^1.1.1 + native_assets_cli: ^0.10.0 + # native_assets_cli: + # path: ../../../../native_assets_cli/ + native_toolchain_c: ^0.7.0 + # native_toolchain_c: + # path: ../../../../native_toolchain_c/ + +dev_dependencies: + args: ^2.6.0 + ffigen: ^8.0.2 + lints: ^3.0.0 + test: ^1.21.0 diff --git a/pkgs/native_assets_cli/example/build/download_asset/src/native_add.c b/pkgs/native_assets_cli/example/build/download_asset/src/native_add.c new file mode 100644 index 000000000..91926c2a5 --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/src/native_add.c @@ -0,0 +1,7 @@ +// 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 "native_add.h" + +int32_t add(int32_t a, int32_t b) { return a + b; } diff --git a/pkgs/native_assets_cli/example/build/download_asset/src/native_add.h b/pkgs/native_assets_cli/example/build/download_asset/src/native_add.h new file mode 100644 index 000000000..196445bb5 --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/src/native_add.h @@ -0,0 +1,13 @@ +// 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 + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_cli/example/build/download_asset/test/native_add_test.dart b/pkgs/native_assets_cli/example/build/download_asset/test/native_add_test.dart new file mode 100644 index 000000000..ef7e40c9d --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/test/native_add_test.dart @@ -0,0 +1,12 @@ +// 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:download_asset/native_add.dart'; +import 'package:test/test.dart'; + +void main() { + test('invoke native function', () { + expect(add(24, 18), 42); + }); +} diff --git a/pkgs/native_assets_cli/example/build/download_asset/tool/build.dart b/pkgs/native_assets_cli/example/build/download_asset/tool/build.dart new file mode 100644 index 000000000..5a903eaf8 --- /dev/null +++ b/pkgs/native_assets_cli/example/build/download_asset/tool/build.dart @@ -0,0 +1,123 @@ +// 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:io'; + +import 'package:args/args.dart'; +import 'package:download_asset/src/build_helpers.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/code_assets_builder.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +void main(List args) async { + final ( + os: os, + architecture: architecture, + iOSSdk: iOSSdk, + ) = parseArguments(args); + final config = createBuildConfig(os, architecture, iOSSdk); + final output = BuildOutputBuilder(); + await runBuild(config, output); +} + +({String architecture, String os, String? iOSSdk}) parseArguments( + List args) { + final parser = ArgParser() + ..addOption( + 'architecture', + abbr: 'a', + allowed: Architecture.values.map((a) => a.name), + mandatory: true, + ) + ..addOption( + 'os', + abbr: 'o', + allowed: OS.values.map((a) => a.name), + mandatory: true, + ) + ..addOption( + 'iossdk', + abbr: 'i', + allowed: IOSSdk.values.map((a) => a.type), + help: 'Required if OS is iOS.', + ); + final argResults = parser.parse(args); + + final os = argResults.option('os'); + final architecture = argResults.option('architecture'); + final iOSSdk = argResults.option('iossdk'); + if (os == null || + architecture == null || + (os == OS.iOS.name && iOSSdk == null)) { + print(parser.usage); + exit(1); + } + return ( + os: os, + architecture: architecture, + iOSSdk: iOSSdk, + ); +} + +BuildConfig createBuildConfig( + String osString, + String architecture, + String? iOSSdk, +) { + final packageRoot = Platform.script.resolve('..'); + var targetName = '${osString}_$architecture'; + if (iOSSdk != null) { + targetName += '_$iOSSdk'; + } + final outputDirectory = + packageRoot.resolve('.dart_tool/download_asset/$targetName/'); + final outputDirectoryShared = + packageRoot.resolve('.dart_tool/download_asset/shared/'); + + final os = OS.fromString(osString); + final buildConfigBuilder = BuildConfigBuilder() + ..setupHookConfig( + packageRoot: packageRoot, + packageName: 'download_asset', + buildAssetTypes: [CodeAsset.type], + ) + ..setupBuildConfig(dryRun: false, linkingEnabled: false) + ..setupCodeConfig( + targetArchitecture: Architecture.fromString(architecture), + targetOS: os, + linkModePreference: LinkModePreference.dynamic, + androidConfig: os != OS.android + ? null + : AndroidConfig( + targetNdkApi: androidTargetNdkApi, + ), + iOSConfig: os != OS.iOS + ? null + : IOSConfig( + targetSdk: IOSSdk.fromString(iOSSdk!), + targetVersion: iOSTargetVersion, + ), + macOSConfig: MacOSConfig(targetVersion: macOSTargetVersion)) + ..setupBuildRunConfig( + outputDirectory: outputDirectory, + outputDirectoryShared: outputDirectoryShared); + return BuildConfig(buildConfigBuilder.json); +} + +Future runBuild(BuildConfig config, BuildOutputBuilder output) async { + final cbuilder = CBuilder.library( + name: 'native_add', + assetName: 'native_add.dart', + sources: [ + 'src/native_add.c', + ], + ); + await cbuilder.run( + config: config, + output: output, + logger: Logger('') + ..level = Level.ALL + ..onRecord.listen((record) => print(record.message)), + ); +} diff --git a/pkgs/native_assets_cli/example/build/native_add_library/README.md b/pkgs/native_assets_cli/example/build/native_add_library/README.md index 180cb0295..5a00ac4e6 100644 --- a/pkgs/native_assets_cli/example/build/native_add_library/README.md +++ b/pkgs/native_assets_cli/example/build/native_add_library/README.md @@ -9,8 +9,8 @@ Run tests with `dart --enable-experiment=native-assets test`. A typical layout of a package with native code is: +* `hook/build.dart` implements the CLI that communicates which native assets to + build/bundle with the Dart/Flutter SDK. * `lib/` contains Dart code which uses [`dart:ffi`] and [`package:ffigen`] to call into native code. * `src/` contains C code which is built and then invoked through `dart:ffi`. -* `build.dart` implements the CLI that communicates which native assets - to build/bundle with the Dart/Flutter SDK.