Skip to content

Commit 43ad99c

Browse files
authored
[native_assets_cli] Add example for downloading assets (#1860)
Add an example for how to download assets in a hook which are built on GitHub CI. Design approach: * `tool/build.dart` uses `package:native_toolchain_c`. * GitHub action that publishes artifacts on a tag (not the package release tag). * `hook/build.dart` downloads the artifacts. * `tool/generate_asset_hashes.dart` generates hashes that are used in `hook/build.dart` to check download integrity. GitHub actions mostly cargo culted from: * https://github.com/dart-lang/i18n/blob/83e8d08568e26888dc0252c89f5d1faf92052e44/.github/workflows/intl4x_artifacts.yml Closes: #157
1 parent a646680 commit 43ad99c

File tree

19 files changed

+615
-2
lines changed

19 files changed

+615
-2
lines changed

.github/workflows/native.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ jobs:
105105
- run: dart pub get -C test_data/native_dynamic_linking/
106106
if: ${{ matrix.package == 'native_assets_builder' }}
107107

108+
- run: dart pub get -C example/build/download_asset/
109+
if: ${{ matrix.package == 'native_assets_cli' }}
110+
108111
- run: dart pub get -C example/build/native_dynamic_linking/
109112
if: ${{ matrix.package == 'native_assets_cli' }}
110113

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# A workflow that goes together with the example package:download_asset inside
2+
# package:native_assets_cli.
3+
name: package_download_asset
4+
5+
permissions:
6+
contents: write
7+
8+
on:
9+
pull_request:
10+
branches: [ main ]
11+
paths:
12+
- .github/workflows/package_download_asset.yaml
13+
- pkgs/native_assets_cli/example/build/download_asset/
14+
push:
15+
tags:
16+
- 'download_asset-prebuild-assets-*'
17+
workflow_dispatch:
18+
19+
jobs:
20+
build:
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
os: [ubuntu, macos, windows]
25+
26+
runs-on: ${{ matrix.os }}-latest
27+
28+
defaults:
29+
run:
30+
working-directory: pkgs/native_assets_cli/example/build/download_asset/
31+
32+
steps:
33+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
34+
35+
- uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
36+
with:
37+
sdk: stable
38+
39+
- uses: nttld/setup-ndk@afb4c9964b521afb97c864b7d40b11e6911bd410
40+
with:
41+
ndk-version: r27
42+
if: ${{ matrix.os == 'ubuntu' }} # Only build on one host.
43+
44+
- name: Install native toolchains
45+
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
46+
if: ${{ matrix.os == 'ubuntu' }}
47+
48+
- run: dart pub get
49+
50+
# Keep this list consistent with pkgs/native_assets_cli/example/build/download_asset/lib/src/hook_helpers/target_versions.dart
51+
- name: Build Linux host
52+
if: matrix.os == 'ubuntu'
53+
run: |
54+
dart tool/build.dart -oandroid -aarm
55+
dart tool/build.dart -oandroid -aarm64
56+
dart tool/build.dart -oandroid -aia32
57+
dart tool/build.dart -oandroid -ariscv64
58+
dart tool/build.dart -oandroid -ax64
59+
dart tool/build.dart -olinux -aarm
60+
dart tool/build.dart -olinux -aarm64
61+
dart tool/build.dart -olinux -aia32
62+
dart tool/build.dart -olinux -ariscv64
63+
dart tool/build.dart -olinux -ax64
64+
65+
- name: Build MacOS host
66+
if: matrix.os == 'macos'
67+
run: |
68+
dart tool/build.dart -omacos -aarm64
69+
dart tool/build.dart -omacos -ax64
70+
dart tool/build.dart -oios -iiphoneos -aarm64
71+
dart tool/build.dart -oios -iiphonesimulator -aarm64
72+
dart tool/build.dart -oios -iiphonesimulator -ax64
73+
74+
- name: Build Windows host
75+
if: matrix.os == 'windows'
76+
run: |
77+
dart tool/build.dart -owindows -aarm
78+
dart tool/build.dart -owindows -aarm64
79+
dart tool/build.dart -owindows -aia32
80+
dart tool/build.dart -owindows -ax64
81+
82+
- name: Upload artifacts
83+
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
84+
with:
85+
name: ${{ matrix.os }}-host
86+
path: |
87+
pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**/*.dll
88+
pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**/*.dylib
89+
pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**/*.so
90+
if-no-files-found: error
91+
92+
release:
93+
needs: build
94+
runs-on: ubuntu-latest
95+
96+
defaults:
97+
run:
98+
working-directory: pkgs/native_assets_cli/example/build/download_asset/
99+
100+
steps:
101+
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
102+
with:
103+
submodules: true
104+
105+
- name: Download assets
106+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
107+
with:
108+
merge-multiple: true
109+
path: pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/
110+
111+
- name: Display structure of downloaded assets
112+
run: ls -R .dart_tool/download_asset/
113+
114+
- name: Release
115+
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8
116+
if: startsWith(github.ref, 'refs/tags/download_asset-prebuild-assets')
117+
with:
118+
files: 'pkgs/native_assets_cli/example/build/download_asset/.dart_tool/download_asset/**'
119+
fail_on_unmatched_files: true
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
An example of a library depending on prebuilt assets which are downloaded in
2+
the build hook.
3+
4+
## Usage
5+
6+
Run tests with `dart --enable-experiment=native-assets test`.
7+
8+
## Code organization
9+
10+
A typical layout of a package which downloads assets:
11+
12+
* `tool/build.dart` prebuilts assets and is exercised from a GitHub workflow.
13+
* A [GitHub workflow](../../../../../.github/workflows/package_download_asset.yaml) that builds assets.
14+
* `hook/build.dart` downloads the prebuilt assets.
15+
* `lib/` contains Dart code which uses the assets.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Run with `flutter pub run ffigen --config ffigen.yaml`.
2+
name: NativeAddBindings
3+
description: |
4+
Bindings for `src/native_add.h`.
5+
6+
Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
7+
output: 'lib/native_add.dart'
8+
headers:
9+
entry-points:
10+
- 'src/native_add.h'
11+
include-directives:
12+
- 'src/native_add.h'
13+
preamble: |
14+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
15+
// for details. All rights reserved. Use of this source code is governed by a
16+
// BSD-style license that can be found in the LICENSE file.
17+
comments:
18+
style: any
19+
length: full
20+
ffi-native:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:download_asset/src/hook_helpers/c_build.dart';
8+
import 'package:download_asset/src/hook_helpers/download.dart';
9+
import 'package:download_asset/src/hook_helpers/hashes.dart';
10+
import 'package:native_assets_cli/code_assets_builder.dart';
11+
import 'package:native_assets_cli/native_assets_cli.dart';
12+
13+
void main(List<String> args) async {
14+
// TODO(https://github.com/dart-lang/native/issues/39): Use user-defines to
15+
// control this instead.
16+
const localBuild = false;
17+
18+
await build(args, (input, output) async {
19+
// ignore: dead_code
20+
if (localBuild) {
21+
await runBuild(input, output);
22+
} else {
23+
final targetOS = input.config.code.targetOS;
24+
final targetArchitecture = input.config.code.targetArchitecture;
25+
final iOSSdk =
26+
targetOS == OS.iOS ? input.config.code.iOS.targetSdk : null;
27+
final outputDirectory = Directory.fromUri(input.outputDirectory);
28+
final file = await downloadAsset(
29+
targetOS,
30+
targetArchitecture,
31+
iOSSdk,
32+
outputDirectory,
33+
);
34+
final fileHash = await hashAsset(file);
35+
final expectedHash = assetHashes[createTargetName(
36+
targetOS.name,
37+
targetArchitecture.name,
38+
iOSSdk?.type,
39+
)];
40+
if (fileHash != expectedHash) {
41+
throw Exception('File $file was not downloaded correctly. '
42+
'Found hash $fileHash, expected $expectedHash.');
43+
}
44+
output.assets.code.add(CodeAsset(
45+
package: input.packageName,
46+
name: 'native_add.dart',
47+
linkMode: DynamicLoadingBundled(),
48+
os: targetOS,
49+
architecture: targetArchitecture,
50+
file: file.uri,
51+
));
52+
}
53+
});
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// AUTO GENERATED FILE, DO NOT EDIT.
6+
//
7+
// Generated by `package:ffigen`.
8+
// ignore_for_file: type=lint
9+
import 'dart:ffi' as ffi;
10+
11+
@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
12+
external int add(
13+
int a,
14+
int b,
15+
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:logging/logging.dart';
6+
import 'package:native_assets_cli/code_assets_builder.dart';
7+
import 'package:native_toolchain_c/native_toolchain_c.dart';
8+
9+
Future<void> runBuild(BuildInput input, BuildOutputBuilder output) async {
10+
final name = createTargetName(
11+
input.config.code.targetOS.name,
12+
input.config.code.targetArchitecture.name,
13+
input.config.code.targetOS == OS.iOS
14+
? input.config.code.iOS.targetSdk.type
15+
: null,
16+
);
17+
final cbuilder = CBuilder.library(
18+
name: name,
19+
assetName: 'native_add.dart',
20+
sources: [
21+
'src/native_add.c',
22+
],
23+
);
24+
await cbuilder.run(
25+
input: input,
26+
output: output,
27+
logger: Logger('')
28+
..level = Level.ALL
29+
..onRecord.listen((record) => print(record.message)),
30+
);
31+
}
32+
33+
String createTargetName(String osString, String architecture, String? iOSSdk) {
34+
var targetName = 'native_add_${osString}_$architecture';
35+
if (iOSSdk != null) {
36+
targetName += '_$iOSSdk';
37+
}
38+
return targetName;
39+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:crypto/crypto.dart';
8+
import 'package:native_assets_cli/code_assets_builder.dart';
9+
10+
import 'c_build.dart';
11+
import 'version.dart';
12+
13+
Uri downloadUri(String target) => Uri.parse(
14+
'https://github.com/dart-lang/native/releases/download/$version/$target');
15+
16+
Future<File> downloadAsset(
17+
OS targetOS,
18+
Architecture targetArchitecture,
19+
IOSSdk? iOSSdk,
20+
Directory outputDirectory,
21+
) async {
22+
final targetName = targetOS.dylibFileName(createTargetName(
23+
targetOS.name,
24+
targetArchitecture.name,
25+
iOSSdk?.type,
26+
));
27+
final uri = downloadUri(targetName);
28+
final request = await HttpClient().getUrl(uri);
29+
final response = await request.close();
30+
if (response.statusCode != 200) {
31+
throw ArgumentError('The request to $uri failed.');
32+
}
33+
final library = File.fromUri(outputDirectory.uri.resolve(targetName));
34+
await library.create();
35+
await response.pipe(library.openWrite());
36+
return library;
37+
}
38+
39+
Future<String> hashAsset(File assetFile) async {
40+
// TODO(dcharkes): Should this be a strong hash to not only check for download
41+
// integrity but also safeguard against tampering? This would protected
42+
// against the case where the binary hoster is compromised but pub is not
43+
// compromised.
44+
final fileHash = md5.convert(await assetFile.readAsBytes()).toString();
45+
return fileHash;
46+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// THIS FILE IS AUTOGENERATED. TO UPDATE, RUN
6+
//
7+
// dart --enable-experiment=native-assets tool/generate_asset_hashes.dart
8+
//
9+
10+
const assetHashes = <String, String>{
11+
'libnative_add_android_arm.so': '2c38f3edc805a399dad866d619f9157d',
12+
'libnative_add_android_arm64.so': 'c4f0d8c4c50d1e83592e499e7434b967',
13+
'libnative_add_android_ia32.so': 'e3277144d97bd2c54beee581ed7e6665',
14+
'libnative_add_android_riscv64.so': '8c2576cbe75c9a23f2532ff895f94f76',
15+
'libnative_add_android_x64.so': '9a7bec53e1591091669ecd2bd20911d1',
16+
'libnative_add_ios_arm64_iphoneos.dylib': '1bf1473cacb7fd2778fc5bb28f0b61a2',
17+
'libnative_add_ios_arm64_iphonesimulator.dylib':
18+
'cdddffc0787e6a3a846affcb05fac3a8',
19+
'libnative_add_ios_x64_iphonesimulator.dylib':
20+
'4aea6d631350540d50452ac2bdd1a422',
21+
'libnative_add_linux_arm.so': '1a5b9e4b459e13ee85c148582b9b2252',
22+
'libnative_add_linux_arm64.so': '2b3d736e0c0ac1e1537bc43bd9b82cad',
23+
'libnative_add_linux_ia32.so': 'ed6e130e53fa18eab5572ed106cdaab1',
24+
'libnative_add_linux_riscv64.so': '7fa82325ba7803a0443ca27e3300e7f9',
25+
'libnative_add_linux_x64.so': 'f7af8d1547cdfb150a73d513a7957999',
26+
'libnative_add_macos_arm64.dylib': 'f0804ff4b55126996c180114f25ca5bd',
27+
'libnative_add_macos_x64.dylib': 'b62263803dceb3c23508cb909b5a5583',
28+
'native_add_windows_arm64.dll': '7aa6f5e0275ba1b94cd5b7356f89ef04',
29+
'native_add_windows_ia32.dll': 'ab57b5504d92b5b5dc09732d990fd5e7',
30+
'native_add_windows_x64.dll': 'be9ba2125800aa2e3481a759b1845a50',
31+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
const androidTargetNdkApi = 30;
6+
const int macOSTargetVersion = 13;
7+
const iOSTargetVersion = 16;

0 commit comments

Comments
 (0)