Skip to content
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

[native_assets_cli] Add example for downloading assets #1860

Merged
merged 2 commits into from
Jan 9, 2025
Merged
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 @@ -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' }}

Expand Down
119 changes: 119 additions & 0 deletions .github/workflows/package_download_asset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# A workflow that goes together with the example package:download_asset inside
# package:native_assets_cli.
name: package_download_asset

permissions:
contents: write

on:
pull_request:
branches: [ main ]
paths:
- .github/workflows/package_download_asset.yaml
- pkgs/native_assets_cli/example/build/download_asset/
push:
tags:
- 'download_asset-prebuild-assets-*'
workflow_dispatch:

jobs:
build:
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 == 'ubuntu' }} # Only build on one host.

- name: Install native toolchains
run: sudo apt-get update && sudo apt-get install clang-15 gcc-i686-linux-gnu gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-riscv64-linux-gnu
if: ${{ matrix.os == 'ubuntu' }}

- run: dart pub get

# Keep this list consistent with pkgs/native_assets_cli/example/build/download_asset/lib/src/hook_helpers/target_versions.dart
- 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 -ariscv64
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: ${{ matrix.os }}-host
path: |
pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**/*.dll
pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**/*.dylib
pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**/*.so
if-no-files-found: error

release:
needs: build
runs-on: ubuntu-latest

defaults:
run:
working-directory: pkgs/native_assets_cli/example/build/download_asset/

steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
with:
submodules: true

- name: Download assets
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
with:
merge-multiple: true
path: pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/

- name: Display structure of downloaded assets
run: ls -R .dart_tool/download_asset/

- name: Release
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8
if: startsWith(github.ref, 'refs/tags/download_asset-prebuild-assets')
with:
files: 'pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**'
fail_on_unmatched_files: true
15 changes: 15 additions & 0 deletions pkgs/native_assets_cli/example/build/download_asset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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:

* `tool/build.dart` prebuilts assets and is exercised from a GitHub workflow.
* A [GitHub workflow](../../../../../.github/workflows/package_download_asset.yaml) that builds assets.
* `hook/build.dart` downloads the prebuilt assets.
* `lib/` contains Dart code which uses the assets.
20 changes: 20 additions & 0 deletions pkgs/native_assets_cli/example/build/download_asset/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/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:
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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:download_asset/src/hook_helpers/c_build.dart';
import 'package:download_asset/src/hook_helpers/download.dart';
import 'package:download_asset/src/hook_helpers/hashes.dart';
import 'package:native_assets_cli/code_assets_builder.dart';
import 'package:native_assets_cli/native_assets_cli.dart';

void main(List<String> args) async {
// TODO(https://github.com/dart-lang/native/issues/39): Use user-defines to
// control this instead.
const localBuild = false;

await build(args, (input, output) async {
// ignore: dead_code
if (localBuild) {
await runBuild(input, output);
} else {
final targetOS = input.config.code.targetOS;
final targetArchitecture = input.config.code.targetArchitecture;
final iOSSdk =
targetOS == OS.iOS ? input.config.code.iOS.targetSdk : null;
final outputDirectory = Directory.fromUri(input.outputDirectory);
final file = await downloadAsset(
targetOS,
targetArchitecture,
iOSSdk,
outputDirectory,
);
final fileHash = await hashAsset(file);
final expectedHash = assetHashes[createTargetName(
targetOS.name,
targetArchitecture.name,
iOSSdk?.type,
)];
if (fileHash != expectedHash) {
throw Exception('File $file was not downloaded correctly. '
'Found hash $fileHash, expected $expectedHash.');
}
output.assets.code.add(CodeAsset(
package: input.packageName,
name: 'native_add.dart',
linkMode: DynamicLoadingBundled(),
os: targetOS,
architecture: targetArchitecture,
file: file.uri,
));
}
});
}
Original file line number Diff line number Diff line change
@@ -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<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
external int add(
int a,
int b,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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_toolchain_c/native_toolchain_c.dart';

Future<void> runBuild(BuildInput input, BuildOutputBuilder output) async {
final name = createTargetName(
input.config.code.targetOS.name,
input.config.code.targetArchitecture.name,
input.config.code.targetOS == OS.iOS
? input.config.code.iOS.targetSdk.type
: null,
);
final cbuilder = CBuilder.library(
name: name,
assetName: 'native_add.dart',
sources: [
'src/native_add.c',
],
);
await cbuilder.run(
input: input,
output: output,
logger: Logger('')
..level = Level.ALL
..onRecord.listen((record) => print(record.message)),
);
}

String createTargetName(String osString, String architecture, String? iOSSdk) {
var targetName = 'native_add_${osString}_$architecture';
if (iOSSdk != null) {
targetName += '_$iOSSdk';
}
return targetName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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:crypto/crypto.dart';
import 'package:native_assets_cli/code_assets_builder.dart';

import 'c_build.dart';
import 'version.dart';

Uri downloadUri(String target) => Uri.parse(
'https://github.com/dart-lang/native/releases/download/$version/$target');

Future<File> downloadAsset(
OS targetOS,
Architecture targetArchitecture,
IOSSdk? iOSSdk,
Directory outputDirectory,
) async {
final targetName = targetOS.dylibFileName(createTargetName(
targetOS.name,
targetArchitecture.name,
iOSSdk?.type,
));
final uri = downloadUri(targetName);
final request = await HttpClient().getUrl(uri);
final response = await request.close();
if (response.statusCode != 200) {
throw ArgumentError('The request to $uri failed.');
}
final library = File.fromUri(outputDirectory.uri.resolve(targetName));
await library.create();
await response.pipe(library.openWrite());
return library;
}

Future<String> hashAsset(File assetFile) async {
// TODO(dcharkes): Should this be a strong hash to not only check for download
// integrity but also safeguard against tampering? This would protected
// against the case where the binary hoster is compromised but pub is not
// compromised.
final fileHash = md5.convert(await assetFile.readAsBytes()).toString();
return fileHash;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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.

// THIS FILE IS AUTOGENERATED. TO UPDATE, RUN
//
// dart --enable-experiment=native-assets tool/generate_asset_hashes.dart
//

const assetHashes = <String, String>{
'libnative_add_android_arm.so': '2c38f3edc805a399dad866d619f9157d',
'libnative_add_android_arm64.so': 'c4f0d8c4c50d1e83592e499e7434b967',
'libnative_add_android_ia32.so': 'e3277144d97bd2c54beee581ed7e6665',
'libnative_add_android_riscv64.so': '8c2576cbe75c9a23f2532ff895f94f76',
'libnative_add_android_x64.so': '9a7bec53e1591091669ecd2bd20911d1',
'libnative_add_ios_arm64_iphoneos.dylib': '1bf1473cacb7fd2778fc5bb28f0b61a2',
'libnative_add_ios_arm64_iphonesimulator.dylib':
'cdddffc0787e6a3a846affcb05fac3a8',
'libnative_add_ios_x64_iphonesimulator.dylib':
'4aea6d631350540d50452ac2bdd1a422',
'libnative_add_linux_arm.so': '1a5b9e4b459e13ee85c148582b9b2252',
'libnative_add_linux_arm64.so': '2b3d736e0c0ac1e1537bc43bd9b82cad',
'libnative_add_linux_ia32.so': 'ed6e130e53fa18eab5572ed106cdaab1',
'libnative_add_linux_riscv64.so': '7fa82325ba7803a0443ca27e3300e7f9',
'libnative_add_linux_x64.so': 'f7af8d1547cdfb150a73d513a7957999',
'libnative_add_macos_arm64.dylib': 'f0804ff4b55126996c180114f25ca5bd',
'libnative_add_macos_x64.dylib': 'b62263803dceb3c23508cb909b5a5583',
'native_add_windows_arm64.dll': '7aa6f5e0275ba1b94cd5b7356f89ef04',
'native_add_windows_ia32.dll': 'ab57b5504d92b5b5dc09732d990fd5e7',
'native_add_windows_x64.dll': 'be9ba2125800aa2e3481a759b1845a50',
};
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c, null) 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.

// THIS FILE IS AUTOGENERATED. TO UPDATE, RUN
//
// dart --enable-experiment=native-assets tool/generate_asset_hashes.dart
//

import 'package:native_assets_cli/code_assets_builder.dart';

const supportedTargets = [
(OS.android, Architecture.arm, null),
(OS.android, Architecture.arm64, null),
(OS.android, Architecture.ia32, null),
(OS.android, Architecture.riscv64, null),
(OS.android, Architecture.x64, null),
(OS.iOS, Architecture.arm64, IOSSdk.iPhoneOS),
(OS.iOS, Architecture.arm64, IOSSdk.iPhoneSimulator),
(OS.iOS, Architecture.x64, IOSSdk.iPhoneSimulator),
(OS.linux, Architecture.arm, null),
(OS.linux, Architecture.arm64, null),
(OS.linux, Architecture.ia32, null),
(OS.linux, Architecture.riscv64, null),
(OS.linux, Architecture.x64, null),
(OS.macOS, Architecture.arm64, null),
(OS.macOS, Architecture.x64, null),
(OS.windows, Architecture.arm64, null),
(OS.windows, Architecture.ia32, null),
(OS.windows, Architecture.x64, null),
];
Loading
Loading