From 1a6faf502c01c598ce8ed6c77ea22c29774dbf34 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 8 May 2024 06:31:23 -0700 Subject: [PATCH] Add link.dart hook (#827) Support a `hook/link.dart` script, which executes after kernel compilation. The SDK changes to make this work are in https://dart-review.googlesource.com/c/sdk/+/338380. The link script receives as input a list of assets produced by `hook/build.dart` scripts. In a `hook/build.dart`, users can specify if an asset should be sent to a link script, and give the name of the package containing that script. All assets destined for a particular link script are collected and given as input. In https://github.com/dart-lang/native/pull/1117, the link scripts get as additional input tree shaking information collected during kernel compilation, to enable link scripts to remove or add assets based on usage of symbols in Dart code. ---
Contribution guidelines:
- See our [contributor guide](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md) for general expectations for PRs. - Larger or significant changes should be discussed in an issue before creating a PR. - Contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`. - Most changes should add an entry to the changelog and may need to [rev the pubspec package version](https://github.com/dart-lang/sdk/wiki/External-Package-Maintenance#making-a-change). - Changes to packages require [corresponding tests](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md#Testing). Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.
--- .github/workflows/native.yaml | 37 +- pkgs/native_assets_builder/CHANGELOG.md | 3 +- .../lib/native_assets_builder.dart | 3 + .../lib/src/build_runner/build_planner.dart | 4 + .../lib/src/build_runner/build_runner.dart | 526 +++++++-------- .../lib/src/model/build_result.dart | 17 + .../lib/src/model/dry_run_result.dart | 19 + .../lib/src/model/hook_result.dart | 71 +++ .../lib/src/model/link_result.dart | 17 + .../src/package_layout/package_layout.dart | 20 +- pkgs/native_assets_builder/pubspec.yaml | 13 +- .../test/build_runner/build_planner_test.dart | 7 +- .../test/build_runner/helpers.dart | 123 ++-- .../test/build_runner/link_test.dart | 96 +++ pkgs/native_assets_builder/test/helpers.dart | 8 +- .../native_assets_builder/test_data/README.md | 2 +- .../test_data/add_asset_link/.gitignore | 4 + .../test_data/add_asset_link/README.md | 1 + .../add_asset_link/bin/add_asset_link.dart | 9 + .../test_data/add_asset_link/hook/build.dart | 31 + .../test_data/add_asset_link/hook/link.dart | 23 + .../add_asset_link/lib/add_asset_link.dart | 5 + .../lib/src/add_asset_link.dart | 9 + .../lib/src/add_asset_link_bindings.dart | 12 + .../test_data/add_asset_link/pubspec.yaml | 22 + .../test_data/add_asset_link/src/native_add.c | 9 + .../test_data/add_asset_link/src/native_add.h | 13 + .../test_data/complex_link/.gitignore | 4 + .../test_data/complex_link/assets/data_0.json | 0 .../test_data/complex_link/assets/data_1.json | 0 .../complex_link/bin/complex_link.dart | 9 + .../test_data/complex_link/hook/build.dart | 22 + .../test_data/complex_link/hook/link.dart | 15 + .../test_data/complex_link/pubspec.yaml | 21 + .../assets/data_helper_0.json | 0 .../assets/data_helper_1.json | 0 .../assets/data_helper_2.json | 0 .../assets/data_helper_3.json | 0 .../complex_link_helper/hook/build.dart | 34 + .../lib/complex_link_helper.dart | 7 + .../complex_link_helper/pubspec.yaml | 19 + .../test_data/cyclic_package_1/pubspec.yaml | 6 +- .../test_data/cyclic_package_2/pubspec.yaml | 6 +- .../test_data/drop_dylib_link/.gitignore | 4 + .../test_data/drop_dylib_link/README.md | 1 + .../drop_dylib_link/bin/drop_dylib_link.dart | 15 + .../test_data/drop_dylib_link/hook/build.dart | 48 ++ .../test_data/drop_dylib_link/hook/link.dart | 18 + .../drop_dylib_link/lib/drop_dylib_link.dart | 5 + .../lib/src/drop_dylib_link.dart | 15 + .../lib/src/drop_dylib_link_bindings.dart | 19 + .../test_data/drop_dylib_link/pubspec.yaml | 22 + .../drop_dylib_link/src/native_add.c | 9 + .../drop_dylib_link/src/native_add.h | 13 + .../drop_dylib_link/src/native_multiply.c | 9 + .../drop_dylib_link/src/native_multiply.h | 13 + .../test_data/manifest.yaml | 40 ++ .../test_data/native_add/pubspec.yaml | 12 +- .../native_add_add_source/pubspec.yaml | 12 +- .../test_data/native_subtract/pubspec.yaml | 12 +- .../package_reading_metadata/pubspec.yaml | 6 +- .../package_with_metadata/pubspec.yaml | 6 +- .../test_data/simple_link/assets/data_0.json | 0 .../test_data/simple_link/assets/data_1.json | 0 .../test_data/simple_link/assets/data_2.json | 0 .../test_data/simple_link/assets/data_3.json | 0 .../simple_link/bin/simple_link.dart | 7 + .../test_data/simple_link/hook/build.dart | 22 + .../test_data/simple_link/hook/link.dart | 14 + .../test_data/simple_link/pubspec.yaml | 19 + .../wrong_build_output/hook/build.dart | 4 +- .../test_data/wrong_build_output/pubspec.yaml | 6 +- .../wrong_build_output_2/hook/build.dart | 4 +- .../wrong_build_output_2/pubspec.yaml | 6 +- .../wrong_build_output_3/hook/build.dart | 4 +- .../wrong_build_output_3/pubspec.yaml | 6 +- .../wrong_namespace_asset/pubspec.yaml | 6 +- pkgs/native_assets_cli/CHANGELOG.md | 4 + pkgs/native_assets_cli/README.md | 2 +- pkgs/native_assets_cli/example/README.md | 13 +- .../native_assets_cli/example/build/README.md | 9 + .../example/{ => build}/local_asset/README.md | 0 .../{ => build}/local_asset/data/asset.txt | 0 .../{ => build}/local_asset/hook/build.dart | 0 .../{ => build}/local_asset/pubspec.yaml | 8 +- .../{ => build}/native_add_app/README.md | 0 .../native_add_app/bin/native_add_app.dart | 0 .../{ => build}/native_add_app/pubspec.yaml | 2 +- .../test/native_add_library_test.dart | 0 .../{ => build}/native_add_library/README.md | 0 .../native_add_library/ffigen.yaml | 0 .../native_add_library/hook/build.dart | 0 .../lib/native_add_library.dart | 0 .../native_add_library/pubspec.yaml | 14 +- .../src/native_add_library.c | 0 .../src/native_add_library.h | 0 .../test/native_add_library_test.dart | 0 .../{ => build}/use_dart_api/README.md | 0 .../{ => build}/use_dart_api/ffigen.yaml | 0 .../{ => build}/use_dart_api/hook/build.dart | 0 .../src/use_dart_api_bindings_generated.dart | 0 .../use_dart_api/lib/use_dart_api.dart | 0 .../{ => build}/use_dart_api/manifest.yaml | 0 .../{ => build}/use_dart_api/pubspec.yaml | 12 +- .../{ => build}/use_dart_api/src/dart_api.h | 0 .../use_dart_api/src/dart_api_dl.c | 0 .../use_dart_api/src/dart_api_dl.h | 0 .../use_dart_api/src/dart_embedder_api.h | 0 .../use_dart_api/src/dart_native_api.h | 0 .../use_dart_api/src/dart_tools_api.h | 0 .../use_dart_api/src/dart_version.h | 0 .../src/internal/dart_api_dl_impl.h | 0 .../use_dart_api/src/use_dart_api.c | 0 .../use_dart_api/src/use_dart_api.h | 0 .../use_dart_api/test/use_dart_api_test.dart | 0 pkgs/native_assets_cli/example/link/README.md | 9 + .../app_with_asset_treeshaking/.gitignore | 3 + .../app_with_asset_treeshaking/CHANGELOG.md | 3 + .../analysis_options.yaml | 30 + .../bin/app_with_asset_treeshaking.dart | 10 + .../lib/app_with_asset_treeshaking.dart | 9 + .../app_with_asset_treeshaking/pubspec.yaml | 17 + .../link/package_with_assets/.gitignore | 7 + .../link/package_with_assets/CHANGELOG.md | 3 + .../assets/unused_asset.json | 3 + .../assets/used_asset.json | 3 + .../link/package_with_assets/hook/build.dart | 24 + .../link/package_with_assets/hook/link.dart | 13 + .../lib/package_with_assets.dart | 13 + .../link/package_with_assets/pubspec.yaml | 20 + .../lib/native_assets_cli.dart | 6 +- .../lib/native_assets_cli_internal.dart | 6 +- pkgs/native_assets_cli/lib/src/api/build.dart | 4 +- .../lib/src/api/build_config.dart | 90 +-- .../lib/src/api/build_output.dart | 31 +- .../lib/src/api/data_asset.dart | 13 +- .../lib/src/api/hook_config.dart | 104 +++ pkgs/native_assets_cli/lib/src/api/link.dart | 45 ++ .../lib/src/api/link_config.dart | 101 +++ .../lib/src/api/link_output.dart | 61 ++ .../lib/src/model/asset.dart | 9 +- .../lib/src/model/build_config.dart | 602 ++++-------------- .../lib/src/model/build_config_CHANGELOG.md | 6 + .../lib/src/model/build_output_CHANGELOG.md | 6 + .../lib/src/model/data_asset.dart | 27 +- .../lib/src/model/dependencies.dart | 4 +- .../native_assets_cli/lib/src/model/hook.dart | 20 + .../lib/src/model/hook_config.dart | 477 ++++++++++++++ .../{build_output.dart => hook_output.dart} | 128 ++-- .../lib/src/model/link_config.dart | 146 +++++ .../lib/src/model/native_code_asset.dart | 18 +- .../lib/src/model/resource_identifiers.dart | 202 ++++++ .../native_assets_cli/lib/src/utils/json.dart | 14 +- pkgs/native_assets_cli/pubspec.yaml | 3 +- .../test/api/asset_test.dart | 1 - .../test/api/build_config_test.dart | 15 +- .../test/api/build_test.dart | 7 +- .../test/api/link_config_test.dart | 179 ++++++ .../test/example/local_asset_test.dart | 6 +- .../test/example/native_add_library_test.dart | 6 +- pkgs/native_assets_cli/test/helpers.dart | 23 + .../test/model/asset_test.dart | 128 ++-- .../test/model/build_config_test.dart | 312 +++------ .../test/model/build_output_test.dart | 288 ++++----- .../test/model/checksum_test.dart | 84 +++ .../test/model/link_config_test.dart | 476 ++++++++++++++ .../test/model/resource_data.dart | 123 ++++ .../test/model/resource_identifiers_test.dart | 31 + pkgs/native_toolchain_c/README.md | 3 +- .../lib/src/cbuilder/cbuilder.dart | 39 +- .../lib/src/cbuilder/run_cbuilder.dart | 76 ++- .../lib/src/native_toolchain/recognizer.dart | 12 +- pkgs/native_toolchain_c/pubspec.yaml | 8 +- .../test/cbuilder/cbuilder_test.dart | 2 +- .../test/cbuilder/compiler_resolver_test.dart | 10 +- pkgs/native_toolchain_c/test/helpers.dart | 4 + .../test/tool/tool_resolver_test.dart | 6 +- 177 files changed, 4258 insertions(+), 1559 deletions(-) create mode 100644 pkgs/native_assets_builder/lib/src/model/build_result.dart create mode 100644 pkgs/native_assets_builder/lib/src/model/dry_run_result.dart create mode 100644 pkgs/native_assets_builder/lib/src/model/hook_result.dart create mode 100644 pkgs/native_assets_builder/lib/src/model/link_result.dart create mode 100644 pkgs/native_assets_builder/test/build_runner/link_test.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/.gitignore create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/README.md create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.c create mode 100644 pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.h create mode 100644 pkgs/native_assets_builder/test_data/complex_link/.gitignore create mode 100644 pkgs/native_assets_builder/test_data/complex_link/assets/data_0.json create mode 100644 pkgs/native_assets_builder/test_data/complex_link/assets/data_1.json create mode 100644 pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart create mode 100644 pkgs/native_assets_builder/test_data/complex_link/hook/build.dart create mode 100644 pkgs/native_assets_builder/test_data/complex_link/hook/link.dart create mode 100644 pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_0.json create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_1.json create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_2.json create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_3.json create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart create mode 100644 pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/README.md create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.c create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.h create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.c create mode 100644 pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.h create mode 100644 pkgs/native_assets_builder/test_data/simple_link/assets/data_0.json create mode 100644 pkgs/native_assets_builder/test_data/simple_link/assets/data_1.json create mode 100644 pkgs/native_assets_builder/test_data/simple_link/assets/data_2.json create mode 100644 pkgs/native_assets_builder/test_data/simple_link/assets/data_3.json create mode 100644 pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart create mode 100644 pkgs/native_assets_builder/test_data/simple_link/hook/build.dart create mode 100644 pkgs/native_assets_builder/test_data/simple_link/hook/link.dart create mode 100644 pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml create mode 100644 pkgs/native_assets_cli/example/build/README.md rename pkgs/native_assets_cli/example/{ => build}/local_asset/README.md (100%) rename pkgs/native_assets_cli/example/{ => build}/local_asset/data/asset.txt (100%) rename pkgs/native_assets_cli/example/{ => build}/local_asset/hook/build.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/local_asset/pubspec.yaml (68%) rename pkgs/native_assets_cli/example/{ => build}/native_add_app/README.md (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_app/bin/native_add_app.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_app/pubspec.yaml (87%) rename pkgs/native_assets_cli/example/{ => build}/native_add_app/test/native_add_library_test.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/README.md (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/ffigen.yaml (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/hook/build.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/lib/native_add_library.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/pubspec.yaml (56%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/src/native_add_library.c (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/src/native_add_library.h (100%) rename pkgs/native_assets_cli/example/{ => build}/native_add_library/test/native_add_library_test.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/README.md (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/ffigen.yaml (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/hook/build.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/lib/src/use_dart_api_bindings_generated.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/lib/use_dart_api.dart (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/manifest.yaml (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/pubspec.yaml (56%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_api.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_api_dl.c (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_api_dl.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_embedder_api.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_native_api.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_tools_api.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/dart_version.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/internal/dart_api_dl_impl.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/use_dart_api.c (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/src/use_dart_api.h (100%) rename pkgs/native_assets_cli/example/{ => build}/use_dart_api/test/use_dart_api_test.dart (100%) create mode 100644 pkgs/native_assets_cli/example/link/README.md create mode 100644 pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore create mode 100644 pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md create mode 100644 pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml create mode 100644 pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart create mode 100644 pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart create mode 100644 pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/.gitignore create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart create mode 100644 pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml create mode 100644 pkgs/native_assets_cli/lib/src/api/hook_config.dart create mode 100644 pkgs/native_assets_cli/lib/src/api/link.dart create mode 100644 pkgs/native_assets_cli/lib/src/api/link_config.dart create mode 100644 pkgs/native_assets_cli/lib/src/api/link_output.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/hook.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/hook_config.dart rename pkgs/native_assets_cli/lib/src/model/{build_output.dart => hook_output.dart} (58%) create mode 100644 pkgs/native_assets_cli/lib/src/model/link_config.dart create mode 100644 pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart create mode 100644 pkgs/native_assets_cli/test/api/link_config_test.dart create mode 100644 pkgs/native_assets_cli/test/model/checksum_test.dart create mode 100644 pkgs/native_assets_cli/test/model/link_config_test.dart create mode 100644 pkgs/native_assets_cli/test/model/resource_data.dart create mode 100644 pkgs/native_assets_cli/test/model/resource_identifiers_test.dart diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index f4dd54ad7..1aa5e0e96 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -80,13 +80,34 @@ jobs: - run: dart pub get -C test_data/package_with_metadata/ if: ${{ matrix.package == 'native_assets_builder' }} - - run: dart pub get -C example/native_add_app/ + - run: dart pub get -C test_data/simple_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/complex_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/complex_link_helper/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/drop_dylib_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/add_asset_link/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C example/build/native_add_app/ + if: ${{ matrix.package == 'native_assets_cli' }} + + - run: dart pub get -C example/build/native_add_library/ + if: ${{ matrix.package == 'native_assets_cli' }} + + - run: dart pub get -C example/build/use_dart_api/ if: ${{ matrix.package == 'native_assets_cli' }} - - run: dart pub get -C example/native_add_library/ + - run: dart pub get -C example/link/package_with_assets/ if: ${{ matrix.package == 'native_assets_cli' }} - - run: dart pub get -C example/use_dart_api/ + - run: dart pub get -C example/link/app_with_asset_treeshaking/ if: ${{ matrix.package == 'native_assets_cli' }} - run: dart analyze --fatal-infos @@ -103,23 +124,23 @@ jobs: if: ${{ matrix.sdk == 'stable' }} - run: dart --enable-experiment=native-assets test - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: dart --enable-experiment=native-assets run - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: dart --enable-experiment=native-assets build bin/native_add_app.dart - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: ./native_add_app.exe - working-directory: pkgs/${{ matrix.package }}/example/native_add_app/bin/native_add_app/ + working-directory: pkgs/${{ matrix.package }}/example/build/native_add_app/bin/native_add_app/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - run: dart --enable-experiment=native-assets test - working-directory: pkgs/${{ matrix.package }}/example/use_dart_api/ + working-directory: pkgs/${{ matrix.package }}/example/build/use_dart_api/ if: ${{ matrix.package == 'native_assets_cli' && matrix.sdk == 'dev' && !matrix.breaking-change }} - name: Install coverage diff --git a/pkgs/native_assets_builder/CHANGELOG.md b/pkgs/native_assets_builder/CHANGELOG.md index d79e1a4a4..5e00b7615 100644 --- a/pkgs/native_assets_builder/CHANGELOG.md +++ b/pkgs/native_assets_builder/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.6.1 +## 0.7.0-wip - Fix test. +- Add support for `hook/link.dart`. ## 0.6.0 diff --git a/pkgs/native_assets_builder/lib/native_assets_builder.dart b/pkgs/native_assets_builder/lib/native_assets_builder.dart index 9dfa400af..d3d92db0b 100644 --- a/pkgs/native_assets_builder/lib/native_assets_builder.dart +++ b/pkgs/native_assets_builder/lib/native_assets_builder.dart @@ -3,5 +3,8 @@ // BSD-style license that can be found in the LICENSE file. export 'package:native_assets_builder/src/build_runner/build_runner.dart'; +export 'package:native_assets_builder/src/model/build_result.dart'; +export 'package:native_assets_builder/src/model/dry_run_result.dart'; export 'package:native_assets_builder/src/model/kernel_assets.dart'; +export 'package:native_assets_builder/src/model/link_result.dart'; export 'package:native_assets_builder/src/package_layout/package_layout.dart'; diff --git a/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart b/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart index 0df0cbff3..e6e129518 100644 --- a/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart +++ b/pkgs/native_assets_builder/lib/src/build_runner/build_planner.dart @@ -83,6 +83,10 @@ class NativeAssetsBuildPlanner { } } +/// A graph of package dependencies. +/// +/// This is encoded as a mapping from package name to list of package +/// dependencies. class PackageGraph { final Map> map; diff --git a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart index 8de8c5566..9ed7b51c8 100644 --- a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart +++ b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart @@ -9,6 +9,10 @@ import 'package:logging/logging.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:package_config/package_config.dart'; +import '../model/build_result.dart'; +import '../model/dry_run_result.dart'; +import '../model/hook_result.dart'; +import '../model/link_result.dart'; import '../package_layout/package_layout.dart'; import '../utils/run_process.dart'; import 'build_planner.dart'; @@ -33,7 +37,7 @@ class NativeAssetsBuildRunner { /// This method is invoked by launchers such as dartdev (for `dart run`) and /// flutter_tools (for `flutter run` and `flutter build`). /// - /// If provided, only native assets of all transitive dependencies of + /// If provided, only assets of all transitive dependencies of /// [runPackageName] are built. Future build({ required LinkModePreferenceImpl linkModePreference, @@ -47,39 +51,90 @@ class NativeAssetsBuildRunner { PackageLayout? packageLayout, String? runPackageName, Iterable? supportedAssetTypes, - }) async { - packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory); - final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; - final List buildPlan; - final PackageGraph packageGraph; - if (packagesWithNativeAssets.length <= 1 && runPackageName == null) { - buildPlan = packagesWithNativeAssets; - packageGraph = PackageGraph({ - for (final p in packagesWithNativeAssets) p.name: [], - }); - } else { - final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( - rootPackageRoot: packageLayout.rootPackageRoot, - packagesWithNativeAssets: packagesWithNativeAssets, - dartExecutable: Uri.file(Platform.resolvedExecutable), - logger: logger, + }) async => + _run( + hook: Hook.build, + linkModePreference: linkModePreference, + target: target, + workingDirectory: workingDirectory, + buildMode: buildMode, + cCompilerConfig: cCompilerConfig, + targetIOSSdk: targetIOSSdk, + targetAndroidNdkApi: targetAndroidNdkApi, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + runPackageName: runPackageName, + supportedAssetTypes: supportedAssetTypes, ); - final (plan, planSuccess) = planner.plan( + + /// [workingDirectory] is expected to contain `.dart_tool`. + /// + /// This method is invoked by launchers such as dartdev (for `dart run`) and + /// flutter_tools (for `flutter run` and `flutter build`). + /// + /// If provided, only assets of all transitive dependencies of + /// [runPackageName] are linked. + Future link({ + required Target target, + required Uri workingDirectory, + required BuildModeImpl buildMode, + CCompilerConfigImpl? cCompilerConfig, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + required bool includeParentEnvironment, + PackageLayout? packageLayout, + Uri? resourceIdentifiers, + String? runPackageName, + Iterable? supportedAssetTypes, + required BuildResult buildResult, + }) async => + _run( + hook: Hook.link, + linkModePreference: LinkModePreferenceImpl.dynamic, + target: target, + workingDirectory: workingDirectory, + buildMode: buildMode, + cCompilerConfig: cCompilerConfig, + targetIOSSdk: targetIOSSdk, + targetAndroidNdkApi: targetAndroidNdkApi, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, runPackageName: runPackageName, + resourceIdentifiers: resourceIdentifiers, + supportedAssetTypes: supportedAssetTypes, + buildResult: buildResult, ); - if (!planSuccess) { - return _BuildResultImpl( - assets: [], - dependencies: [], - success: false, - ); - } - buildPlan = plan; - packageGraph = planner.packageGraph; + + /// The common method for running building or linking of assets. + Future _run({ + required Hook hook, + required LinkModePreferenceImpl linkModePreference, + required Target target, + required Uri workingDirectory, + required BuildModeImpl buildMode, + CCompilerConfigImpl? cCompilerConfig, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + required bool includeParentEnvironment, + PackageLayout? packageLayout, + Uri? resourceIdentifiers, + String? runPackageName, + Iterable? supportedAssetTypes, + BuildResult? buildResult, + }) async { + assert(hook == Hook.link || buildResult == null); + + packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory); + final packagesWithAssets = await packageLayout.packagesWithAssets(hook); + final (buildPlan, packageGraph, planSuccess) = await _plannedPackages( + packagesWithAssets, + packageLayout, + runPackageName, + ); + final hookResult = HookResult.failure(); + if (!planSuccess) { + return hookResult; } - final assets = []; - final dependencies = []; final metadata = {}; var success = true; for (final package in buildPlan) { @@ -89,41 +144,106 @@ class NativeAssetsBuildRunner { targetMetadata: metadata, ); final config = await _cliConfig( - packageName: package.name, - packageRoot: packageLayout.packageRoot(package.name), - target: target, - buildMode: buildMode, - linkMode: linkModePreference, - buildParentDir: packageLayout.dartToolNativeAssetsBuilder, - dependencyMetadata: dependencyMetadata, - cCompilerConfig: cCompilerConfig, - targetIOSSdk: targetIOSSdk, - targetAndroidNdkApi: targetAndroidNdkApi, - supportedAssetTypes: supportedAssetTypes, + package, + packageLayout, + target, + buildMode, + linkModePreference, + dependencyMetadata, + cCompilerConfig, + targetIOSSdk, + targetAndroidNdkApi, + supportedAssetTypes, + hook, + resourceIdentifiers, + buildResult, ); - final ( - packageAssets, - packageDependencies, - packageMetadata, - packageSuccess, - ) = await _buildPackageCached( + + final (buildOutput, packageSuccess) = await _runHookForPackageCached( + hook, config, packageLayout.packageConfigUri, workingDirectory, includeParentEnvironment, + resourceIdentifiers, ); - assets.addAll(packageAssets); - dependencies.addAll(packageDependencies); + hookResult.add(buildOutput); success &= packageSuccess; - if (packageMetadata != null) { - metadata[config.packageName] = packageMetadata; - } + + metadata[config.packageName] = buildOutput.metadata; } - return _BuildResultImpl( - assets: assets, - dependencies: dependencies..sort(_uriCompare), - success: success, + + return hookResult.withSuccess(success); + } + + Future _cliConfig( + Package package, + PackageLayout packageLayout, + Target target, + BuildModeImpl buildMode, + LinkModePreferenceImpl linkModePreference, + DependencyMetadata? dependencyMetadata, + CCompilerConfigImpl? cCompilerConfig, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + Iterable? supportedAssetTypes, + Hook hook, + Uri? resourceIdentifiers, + BuildResult? buildResult, + ) async { + final buildDirName = HookConfigImpl.checksum( + packageName: package.name, + packageRoot: package.root, + targetOS: target.os, + targetArchitecture: target.architecture, + buildMode: buildMode, + linkModePreference: linkModePreference, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + dependencyMetadata: dependencyMetadata, + targetAndroidNdkApi: targetAndroidNdkApi, + supportedAssetTypes: supportedAssetTypes, + hook: hook, ); + final outDirUri = + packageLayout.dartToolNativeAssetsBuilder.resolve('$buildDirName/out/'); + final outDir = Directory.fromUri(outDirUri); + if (!await outDir.exists()) { + // TODO(https://dartbug.com/50565): Purge old or unused folders. + await outDir.create(recursive: true); + } + + if (hook == Hook.link) { + return LinkConfigImpl( + outputDirectory: outDirUri, + packageName: package.name, + packageRoot: package.root, + targetOS: target.os, + targetArchitecture: target.architecture, + buildMode: buildMode, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + targetAndroidNdkApi: targetAndroidNdkApi, + resourceIdentifierUri: resourceIdentifiers, + assets: buildResult!.assetsForLinking[package.name] ?? [], + supportedAssetTypes: supportedAssetTypes, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + } else { + return BuildConfigImpl( + outputDirectory: outDirUri, + packageName: package.name, + packageRoot: package.root, + targetOS: target.os, + targetArchitecture: target.architecture, + buildMode: buildMode, + linkModePreference: linkModePreference, + targetIOSSdk: targetIOSSdk, + cCompiler: cCompilerConfig, + dependencyMetadata: dependencyMetadata, + targetAndroidNdkApi: targetAndroidNdkApi, + ); + } } /// [workingDirectory] is expected to contain `.dart_tool`. @@ -143,30 +263,17 @@ class NativeAssetsBuildRunner { Iterable? supportedAssetTypes, }) async { packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory); - final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; - final List buildPlan; - if (packagesWithNativeAssets.length <= 1 && runPackageName == null) { - buildPlan = packagesWithNativeAssets; - } else { - final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( - rootPackageRoot: packageLayout.rootPackageRoot, - packagesWithNativeAssets: packagesWithNativeAssets, - dartExecutable: Uri.file(Platform.resolvedExecutable), - logger: logger, - ); - final (plan, planSuccess) = planner.plan( - runPackageName: runPackageName, - ); - if (!planSuccess) { - return _DryRunResultImpl( - assets: [], - success: false, - ); - } - buildPlan = plan; + final packagesWithBuild = + await packageLayout.packagesWithAssets(Hook.build); + final (buildPlan, _, planSuccess) = await _plannedPackages( + packagesWithBuild, + packageLayout, + runPackageName, + ); + final hookResult = HookResult.failure(); + if (!planSuccess) { + return hookResult; } - final assets = []; var success = true; for (final package in buildPlan) { final config = await _cliConfigDryRun( @@ -177,95 +284,89 @@ class NativeAssetsBuildRunner { buildParentDir: packageLayout.dartToolNativeAssetsBuilder, supportedAssetTypes: supportedAssetTypes, ); - final (packageAssets, _, _, packageSuccess) = await _buildPackage( + final (buildOutput, packageSuccess) = await _runHookForPackage( + Hook.build, config, packageLayout.packageConfigUri, workingDirectory, includeParentEnvironment, - dryRun: true, + null, ); - for (final asset in packageAssets) { + for (final asset in buildOutput.assets) { switch (asset) { case NativeCodeAssetImpl _: if (asset.architecture != null) { // Backwards compatibility, if an architecture is provided use it. - assets.add(asset); + hookResult.assets.add(asset); } else { // Dry run does not report architecture. Dart VM branches on OS // and Target when looking up assets, so populate assets for all // architectures. for (final architecture in asset.os.architectures) { - assets.add(asset.copyWith( - architecture: architecture, - )); + hookResult.assets + .add(asset.copyWith(architecture: architecture)); } } case DataAssetImpl _: - assets.add(asset); + hookResult.assets.add(asset); } } success &= packageSuccess; } - return _DryRunResultImpl( - assets: assets, - success: success, - ); + return hookResult.withSuccess(success); } - Future<_PackageBuildRecord> _buildPackageCached( - BuildConfigImpl config, + Future<_PackageBuildRecord> _runHookForPackageCached( + Hook hook, + HookConfigImpl config, Uri packageConfigUri, Uri workingDirectory, bool includeParentEnvironment, + Uri? resources, ) async { - final packageName = config.packageName; final outDir = config.outputDirectory; if (!await Directory.fromUri(outDir).exists()) { await Directory.fromUri(outDir).create(recursive: true); } - final buildOutput = await BuildOutputImpl.readFromFile(outDir: outDir); - final lastBuilt = buildOutput?.timestamp.roundDownToSeconds() ?? - DateTime.fromMillisecondsSinceEpoch(0); - final dependencies = buildOutput?.dependenciesModel; - final lastChange = await dependencies?.lastModified() ?? DateTime.now(); - - if (lastBuilt.isAfter(lastChange)) { - logger.info('Skipping build for $packageName in $outDir. ' - 'Last build on $lastBuilt, last input change on $lastChange.'); - // All build flags go into [outDir]. Therefore we do not have to check - // here whether the config is equal. - final assets = buildOutput!.assets; - final dependencies = buildOutput.dependencies; - final metadata = buildOutput.metadataModel; - return (assets, dependencies, metadata, true); + final hookOutput = HookOutputImpl.readFromFile(file: config.outputFile); + if (hookOutput != null) { + final lastBuilt = hookOutput.timestamp.roundDownToSeconds(); + final lastChange = await hookOutput.dependenciesModel.lastModified(); + + if (lastBuilt.isAfter(lastChange)) { + logger + .info('Skipping ${hook.name} for ${config.packageName} in $outDir. ' + 'Last build on $lastBuilt, last input change on $lastChange.'); + // All build flags go into [outDir]. Therefore we do not have to check + // here whether the config is equal. + return (hookOutput, true); + } } - return await _buildPackage( + return await _runHookForPackage( + hook, config, packageConfigUri, workingDirectory, includeParentEnvironment, - dryRun: false, + resources, ); } - Future<_PackageBuildRecord> _buildPackage( - BuildConfigImpl config, + Future<_PackageBuildRecord> _runHookForPackage( + Hook hook, + HookConfigImpl config, Uri packageConfigUri, Uri workingDirectory, - bool includeParentEnvironment, { - required bool dryRun, - }) async { - final outDir = config.outputDirectory; - final configFile = outDir.resolve('../config.json'); - final buildHook = config.packageRoot.resolve('hook/').resolve('build.dart'); - final buildHookLegacy = config.packageRoot.resolve('build.dart'); + bool includeParentEnvironment, + Uri? resources, + ) async { + final configFile = config.configFile; final configFileContents = config.toJsonString(); logger.info('config.json contents: $configFileContents'); await File.fromUri(configFile).writeAsString(configFileContents); - final buildOutputFile = - File.fromUri(outDir.resolve(BuildOutputImpl.fileNameV1_1_0)); + final buildOutputFile = File.fromUri(config.outputFile); if (await buildOutputFile.exists()) { // Ensure we'll never read outdated build results. await buildOutputFile.delete(); @@ -273,11 +374,9 @@ class NativeAssetsBuildRunner { final arguments = [ '--packages=${packageConfigUri.toFilePath()}', - if (await File.fromUri(buildHook).exists()) - buildHook.toFilePath() - else - buildHookLegacy.toFilePath(), + config.script.toFilePath(), '--config=${configFile.toFilePath()}', + if (resources != null) resources.toFilePath(), ]; final result = await runProcess( workingDirectory: workingDirectory, @@ -298,7 +397,7 @@ class NativeAssetsBuildRunner { logger.severe( ''' Building native assets for package:${config.packageName} failed. -build.dart returned with exit code: ${result.exitCode}. +${config.script} returned with exit code: ${result.exitCode}. To reproduce run: $commandString stderr: @@ -311,20 +410,27 @@ ${result.stdout} } try { - final buildOutput = await BuildOutputImpl.readFromFile(outDir: outDir); - final assets = buildOutput?.assets ?? []; - success &= validateAssetsPackage(assets, config.packageName); - final dependencies = buildOutput?.dependencies ?? []; - final metadata = dryRun ? null : buildOutput?.metadataModel; - return (assets, dependencies, metadata, success); + final buildOutput = + HookOutputImpl.readFromFile(file: config.outputFile) ?? + HookOutputImpl(); + //As a link.dart can pipe through assets from other packages. + if (hook == Hook.build) { + success &= validateAssetsPackage( + buildOutput.assets, + config.packageName, + ); + } + return (buildOutput, success); } on FormatException catch (e) { logger.severe(''' Building native assets for package:${config.packageName} failed. -build_output.json contained a format error. +${config.outputName} contained a format error. + +Contents: ${File.fromUri(config.outputFile).readAsStringSync()}. ${e.message} '''); success = false; - return ([], [], const Metadata({}), false); + return (HookOutputImpl(), false); // TODO(https://github.com/dart-lang/native/issues/109): Stop throwing // type errors in native_assets_cli, release a new version of that package // and then remove this. @@ -332,14 +438,14 @@ ${e.message} } on TypeError { logger.severe(''' Building native assets for package:${config.packageName} failed. -build_output.json contained a format error. +${config.outputName} contained a type error. + +Contents: ${File.fromUri(config.outputFile).readAsStringSync()}. '''); success = false; - return ([], [], const Metadata({}), false); + return (HookOutputImpl(), false); } finally { if (!success) { - final buildOutputFile = - File.fromUri(outDir.resolve(BuildOutputImpl.fileNameV1_1_0)); if (await buildOutputFile.exists()) { await buildOutputFile.delete(); } @@ -347,53 +453,6 @@ build_output.json contained a format error. } } - static Future _cliConfig({ - required String packageName, - required Uri packageRoot, - required Target target, - IOSSdkImpl? targetIOSSdk, - int? targetAndroidNdkApi, - required BuildModeImpl buildMode, - required LinkModePreferenceImpl linkMode, - required Uri buildParentDir, - CCompilerConfigImpl? cCompilerConfig, - DependencyMetadata? dependencyMetadata, - Iterable? supportedAssetTypes, - }) async { - final buildDirName = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: packageRoot, - targetOS: target.os, - targetArchitecture: target.architecture, - buildMode: buildMode, - linkModePreference: linkMode, - targetIOSSdk: targetIOSSdk, - cCompiler: cCompilerConfig, - dependencyMetadata: dependencyMetadata, - targetAndroidNdkApi: targetAndroidNdkApi, - supportedAssetTypes: supportedAssetTypes, - ); - final outDirUri = buildParentDir.resolve('$buildDirName/out/'); - final outDir = Directory.fromUri(outDirUri); - if (!await outDir.exists()) { - // TODO(https://dartbug.com/50565): Purge old or unused folders. - await outDir.create(recursive: true); - } - return BuildConfigImpl( - outDir: outDirUri, - packageName: packageName, - packageRoot: packageRoot, - targetOS: target.os, - targetArchitecture: target.architecture, - buildMode: buildMode, - linkModePreference: linkMode, - targetIOSSdk: targetIOSSdk, - cCompiler: cCompilerConfig, - dependencyMetadata: dependencyMetadata, - targetAndroidNdkApi: targetAndroidNdkApi, - ); - } - static Future _cliConfigDryRun({ required String packageName, required Uri packageRoot, @@ -409,7 +468,7 @@ build_output.json contained a format error. await outDir.create(recursive: true); } return BuildConfigImpl.dryRun( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRoot, targetOS: targetOS, @@ -436,84 +495,51 @@ build_output.json contained a format error. bool validateAssetsPackage(Iterable assets, String packageName) { final invalidAssetIds = assets .map((a) => a.id) - .where((n) => !n.startsWith('package:$packageName/')) + .where((id) => !id.startsWith('package:$packageName/')) .toSet() .toList() ..sort(); - final success = invalidAssetIds.isEmpty; - if (!success) { + if (invalidAssetIds.isNotEmpty) { logger.severe( '`package:$packageName` declares the following assets which do not ' 'start with `package:$packageName/`: ${invalidAssetIds.join(', ')}.', ); + return false; + } else { + return true; } - return success; } -} - -typedef _PackageBuildRecord = ( - Iterable, - Iterable dependencies, - Metadata?, - bool success, -); - -/// The result from a [NativeAssetsBuildRunner.dryRun]. -abstract interface class DryRunResult { - /// The native assets for all [Target]s for the build or dry run. - List get assets; - /// Whether all builds completed without errors. - /// - /// All error messages are streamed to [NativeAssetsBuildRunner.logger]. - bool get success; -} - -final class _DryRunResultImpl implements DryRunResult { - @override - final List assets; - - @override - final bool success; - - _DryRunResultImpl({ - required this.assets, - required this.success, - }); -} - -/// The result from a [NativeAssetsBuildRunner.build]. -abstract class BuildResult implements DryRunResult { - /// All the files used for building the native assets of all packages. - /// - /// This aggregated list can be used to determine whether the - /// [NativeAssetsBuildRunner] needs to be invoked again. The - /// [NativeAssetsBuildRunner] determines per package with native assets - /// if it needs to run the build again. - List get dependencies; + Future<(List plan, PackageGraph dependencyGraph, bool success)> + _plannedPackages( + List packagesWithNativeAssets, + PackageLayout packageLayout, + String? runPackageName, + ) async { + if (packagesWithNativeAssets.length <= 1 && runPackageName == null) { + final dependencyGraph = PackageGraph({ + for (final p in packagesWithNativeAssets) p.name: [], + }); + return (packagesWithNativeAssets, dependencyGraph, true); + } else { + final planner = await NativeAssetsBuildPlanner.fromRootPackageRoot( + rootPackageRoot: packageLayout.rootPackageRoot, + packagesWithNativeAssets: packagesWithNativeAssets, + dartExecutable: Uri.file(Platform.resolvedExecutable), + logger: logger, + ); + final (plan, planSuccess) = planner.plan( + runPackageName: runPackageName, + ); + return (plan, planner.packageGraph, planSuccess); + } + } } -final class _BuildResultImpl implements BuildResult { - @override - final List assets; - - @override - final List dependencies; - - @override - final bool success; - - _BuildResultImpl({ - required this.assets, - required this.dependencies, - required this.success, - }); -} +typedef _PackageBuildRecord = (HookOutputImpl, bool success); extension on DateTime { DateTime roundDownToSeconds() => DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch - millisecondsSinceEpoch % const Duration(seconds: 1).inMilliseconds); } - -int _uriCompare(Uri u1, Uri u2) => u1.toString().compareTo(u2.toString()); diff --git a/pkgs/native_assets_builder/lib/src/model/build_result.dart b/pkgs/native_assets_builder/lib/src/model/build_result.dart new file mode 100644 index 000000000..9857989ad --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/build_result.dart @@ -0,0 +1,17 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli_internal.dart'; + +/// The result of a build, executing the build.dart hooks from all packages in +/// the dependency tree of the entry point application. +abstract class BuildResult { + List get dependencies; + + bool get success; + + List get assets; + + Map> get assetsForLinking; +} diff --git a/pkgs/native_assets_builder/lib/src/model/dry_run_result.dart b/pkgs/native_assets_builder/lib/src/model/dry_run_result.dart new file mode 100644 index 000000000..e451d44e8 --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/dry_run_result.dart @@ -0,0 +1,19 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli_internal.dart'; + +import '../../native_assets_builder.dart'; + +/// Similar to a [BuildResult], but for the case of a dry run, where not assets +/// are actually produced. +abstract interface class DryRunResult { + /// The native assets for all [Target]s for the dry run. + List get assets; + + /// Whether all builds completed without errors. + /// + /// All error messages are streamed to [NativeAssetsBuildRunner.logger]. + bool get success; +} diff --git a/pkgs/native_assets_builder/lib/src/model/hook_result.dart b/pkgs/native_assets_builder/lib/src/model/hook_result.dart new file mode 100644 index 000000000..1aed91f2b --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/hook_result.dart @@ -0,0 +1,71 @@ +// 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. + +import 'package:collection/collection.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; + +import '../../native_assets_builder.dart'; + +/// The result from a [NativeAssetsBuildRunner.build] or +/// [NativeAssetsBuildRunner.link]. +final class HookResult implements BuildResult, DryRunResult, LinkResult { + @override + final List assets; + + @override + final Map> assetsForLinking; + + @override + final List dependencies; + + @override + final bool success; + + HookResult._({ + required this.assets, + required this.assetsForLinking, + required this.dependencies, + required this.success, + }); + + HookResult.failure() + : this._( + assets: [], + assetsForLinking: {}, + dependencies: [], + success: false, + ); + + void add(HookOutputImpl buildOutput) { + assets.addAll(buildOutput.assets); + final mergedMaps = mergeMaps( + assetsForLinking, + buildOutput.assetsForLinking, + value: (assets1, assets2) { + final twoInOne = assets1.where((asset) => assets2.contains(asset)); + final oneInTwo = assets2.where((asset) => assets1.contains(asset)); + if (twoInOne.isNotEmpty || oneInTwo.isNotEmpty) { + throw ArgumentError( + 'Found assets with same IDs, ${[...oneInTwo, ...twoInOne]}'); + } + return [ + ...assets1, + ...assets2, + ]; + }, + ); + assetsForLinking.addAll(mergedMaps); + dependencies.addAll(buildOutput.dependencies); + dependencies.sort(_uriCompare); + } + + HookResult withSuccess(bool success) => HookResult._( + assets: assets, + assetsForLinking: assetsForLinking, + dependencies: dependencies, + success: success, + ); +} + +int _uriCompare(Uri u1, Uri u2) => u1.toString().compareTo(u2.toString()); diff --git a/pkgs/native_assets_builder/lib/src/model/link_result.dart b/pkgs/native_assets_builder/lib/src/model/link_result.dart new file mode 100644 index 000000000..90f3c580b --- /dev/null +++ b/pkgs/native_assets_builder/lib/src/model/link_result.dart @@ -0,0 +1,17 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli_internal.dart'; + +import 'build_result.dart'; + +/// Similar to a [BuildResult], but for `link.dart` hooks instead of +/// `build.dart` hooks. +abstract interface class LinkResult { + List get assets; + + List get dependencies; + + bool get success; +} diff --git a/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart b/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart index d190d397f..85cbf1ccf 100644 --- a/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart +++ b/pkgs/native_assets_builder/lib/src/package_layout/package_layout.dart @@ -4,6 +4,7 @@ import 'dart:io'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:package_config/package_config.dart'; /// Directory layout for dealing with native assets. @@ -86,27 +87,36 @@ class PackageLayout { /// All packages in [packageConfig] with native assets. /// /// Whether a package has native assets is defined by whether it contains - /// a `hook/build.dart`. + /// a `hook/build.dart` or `hook/link.dart`. /// /// For backwards compatibility, a toplevel `build.dart` is also supported. // TODO(https://github.com/dart-lang/native/issues/823): Remove fallback when // everyone has migrated. (Probably once we stop backwards compatibility of // the protocol version pre 1.2.0 on some future version.) - late final Future> packagesWithNativeAssets = () async { + Future> packagesWithAssets(Hook hook) async => switch (hook) { + Hook.build => _packagesWithBuildAssets ??= + await _packagesWithHook(hook), + Hook.link => _packagesWithLinkAssets ??= await _packagesWithHook(hook), + }; + + List? _packagesWithBuildAssets; + List? _packagesWithLinkAssets; + + Future> _packagesWithHook(Hook hook) async { final result = []; for (final package in packageConfig.packages) { final packageRoot = package.root; if (packageRoot.scheme == 'file') { if (await File.fromUri( - packageRoot.resolve('hook/').resolve('build.dart'), + packageRoot.resolve('hook/').resolve(hook.scriptName), ).exists() || await File.fromUri( - packageRoot.resolve('build.dart'), + packageRoot.resolve(hook.scriptName), ).exists()) { result.add(package); } } } return result; - }(); + } } diff --git a/pkgs/native_assets_builder/pubspec.yaml b/pkgs/native_assets_builder/pubspec.yaml index 341d035b2..7dd3ec07d 100644 --- a/pkgs/native_assets_builder/pubspec.yaml +++ b/pkgs/native_assets_builder/pubspec.yaml @@ -1,18 +1,21 @@ name: native_assets_builder description: >- - This package is the backend that invokes `build.dart` hooks. -version: 0.6.1 + This package is the backend that invokes build hooks. +version: 0.7.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_builder +publish_to: none + environment: sdk: '>=3.3.0 <4.0.0' dependencies: + collection: ^1.18.0 graphs: ^2.3.1 logging: ^1.2.0 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../native_assets_cli/ package_config: ^2.1.0 yaml: ^3.1.2 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart b/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart index 9669c17da..b4a03a30d 100644 --- a/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart +++ b/pkgs/native_assets_builder/test/build_runner/build_planner_test.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:native_assets_builder/native_assets_builder.dart'; import 'package:native_assets_builder/src/build_runner/build_planner.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:test/test.dart'; import '../helpers.dart'; @@ -37,7 +38,7 @@ void main() async { final packageLayout = await PackageLayout.fromRootPackageRoot(nativeAddUri); final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; + await packageLayout.packagesWithAssets(Hook.build); final planner = NativeAssetsBuildPlanner( packageGraph: graph, @@ -62,7 +63,7 @@ void main() async { final packageLayout = await PackageLayout.fromRootPackageRoot(nativeAddUri); final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; + await packageLayout.packagesWithAssets(Hook.build); final nativeAssetsBuildPlanner = await NativeAssetsBuildPlanner.fromRootPackageRoot( rootPackageRoot: nativeAddUri, @@ -89,7 +90,7 @@ void main() async { final packageLayout = await PackageLayout.fromRootPackageRoot(nativeAddUri); final packagesWithNativeAssets = - await packageLayout.packagesWithNativeAssets; + await packageLayout.packagesWithAssets(Hook.build); final nativeAssetsBuildPlanner = await NativeAssetsBuildPlanner.fromRootPackageRoot( rootPackageRoot: nativeAddUri, diff --git a/pkgs/native_assets_builder/test/build_runner/helpers.dart b/pkgs/native_assets_builder/test/build_runner/helpers.dart index 6dab3951e..f541840c8 100644 --- a/pkgs/native_assets_builder/test/build_runner/helpers.dart +++ b/pkgs/native_assets_builder/test/build_runner/helpers.dart @@ -40,38 +40,30 @@ Future build( List? capturedLogs, PackageLayout? packageLayout, String? runPackageName, -}) async { - StreamSubscription? subscription; - if (capturedLogs != null) { - subscription = - logger.onRecord.listen((event) => capturedLogs.add(event.message)); - } +}) async => + await runWithLog(capturedLogs, () async { + final result = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).build( + buildMode: BuildModeImpl.release, + linkModePreference: linkModePreference, + target: Target.current, + workingDirectory: packageUri, + cCompilerConfig: cCompilerConfig, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + runPackageName: runPackageName, + ); - final result = await NativeAssetsBuildRunner( - logger: logger, - dartExecutable: dartExecutable, - ).build( - buildMode: BuildModeImpl.release, - linkModePreference: linkModePreference, - target: Target.current, - workingDirectory: packageUri, - cCompilerConfig: cCompilerConfig, - includeParentEnvironment: includeParentEnvironment, - packageLayout: packageLayout, - runPackageName: runPackageName, - ); - if (result.success) { - await expectAssetsExist(result.assets.cast()); - } + if (result.success) { + await expectAssetsExist(result.assets); + } - if (subscription != null) { - await subscription.cancel(); - } - - return result; -} + return result; + }); -Future dryRun( +Future link( Uri packageUri, Logger logger, Uri dartExecutable, { @@ -80,23 +72,40 @@ Future dryRun( bool includeParentEnvironment = true, List? capturedLogs, PackageLayout? packageLayout, -}) async { + required BuildResult buildResult, +}) async => + await runWithLog(capturedLogs, () async { + final result = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).link( + buildMode: BuildModeImpl.release, + target: Target.current, + workingDirectory: packageUri, + cCompilerConfig: cCompilerConfig, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + buildResult: buildResult, + ); + + if (result.success) { + await expectAssetsExist(result.assets); + } + + return result; + }); + +Future runWithLog( + List? capturedLogs, + Future Function() f, +) async { StreamSubscription? subscription; if (capturedLogs != null) { subscription = logger.onRecord.listen((event) => capturedLogs.add(event.message)); } - final result = await NativeAssetsBuildRunner( - logger: logger, - dartExecutable: dartExecutable, - ).dryRun( - linkModePreference: linkModePreference, - targetOS: Target.current.os, - workingDirectory: packageUri, - includeParentEnvironment: includeParentEnvironment, - packageLayout: packageLayout, - ); + final result = await f(); if (subscription != null) { await subscription.cancel(); @@ -105,15 +114,33 @@ Future dryRun( return result; } -Future expectAssetsExist(List assets) async { +Future dryRun( + Uri packageUri, + Logger logger, + Uri dartExecutable, { + LinkModePreferenceImpl linkModePreference = LinkModePreferenceImpl.dynamic, + CCompilerConfigImpl? cCompilerConfig, + bool includeParentEnvironment = true, + List? capturedLogs, + PackageLayout? packageLayout, +}) async => + runWithLog(capturedLogs, () async { + final result = await NativeAssetsBuildRunner( + logger: logger, + dartExecutable: dartExecutable, + ).dryRun( + linkModePreference: linkModePreference, + targetOS: Target.current.os, + workingDirectory: packageUri, + includeParentEnvironment: includeParentEnvironment, + packageLayout: packageLayout, + ); + return result; + }); + +Future expectAssetsExist(List assets) async { for (final asset in assets) { - final uri = asset.file!; - expect( - uri.toFilePath(), - contains('${Platform.pathSeparator}.dart_tool${Platform.pathSeparator}' - 'native_assets_builder${Platform.pathSeparator}')); - final file = File.fromUri(uri); - expect(file, exists); + expect(File.fromUri(asset.file!), exists); } } diff --git a/pkgs/native_assets_builder/test/build_runner/link_test.dart b/pkgs/native_assets_builder/test/build_runner/link_test.dart new file mode 100644 index 000000000..fdd8e1aa7 --- /dev/null +++ b/pkgs/native_assets_builder/test/build_runner/link_test.dart @@ -0,0 +1,96 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart' as cli; +import 'package:native_assets_cli/src/api/asset.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; +import 'helpers.dart'; + +const Timeout longTimeout = Timeout(Duration(minutes: 5)); + +void main() async { + test( + 'simple_link linking', + timeout: longTimeout, + () async { + await inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('simple_link/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet( + workingDirectory: packageUri, + logger: logger, + ); + + final buildResult = await build( + packageUri, + logger, + dartExecutable, + ); + expect(buildResult.assets.length, 0); + + final linkResult = await link( + packageUri, + logger, + dartExecutable, + buildResult: buildResult, + ); + expect(linkResult.assets.length, 2); + }); + }, + ); + + test( + 'complex_link linking', + timeout: longTimeout, + () async { + await inTempDir((tempUri) async { + // From package:complex_link_helper + const builtHelperAssets = ['data_helper_0', 'data_helper_1']; + const helperAssetsForLinking = ['data_helper_2', 'data_helper_3']; + // From package:complex_link + const mainAssetsForLinking = ['data_0', 'data_1']; + final assetsForLinking = [ + ...helperAssetsForLinking, + ...mainAssetsForLinking, + ]; + final linkedAssets = assetsForLinking.skip(1); + + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('complex_link/'); + + // First, run `pub get`, we need pub to resolve our dependencies. + await runPubGet(workingDirectory: packageUri, logger: logger); + + final buildResult = await build( + packageUri, + logger, + dartExecutable, + ); + expect(buildResult.success, true); + expect(_getNames(buildResult.assets), orderedEquals(builtHelperAssets)); + expect( + _getNames(buildResult.assetsForLinking['complex_link']!), + orderedEquals(assetsForLinking), + ); + + final linkResult = await link( + packageUri, + logger, + dartExecutable, + buildResult: buildResult, + ); + expect(linkResult.success, true); + + expect(_getNames(linkResult.assets), orderedEquals(linkedAssets)); + }); + }, + ); +} + +Iterable _getNames(List assets) => + assets.whereType().map((asset) => asset.name); diff --git a/pkgs/native_assets_builder/test/helpers.dart b/pkgs/native_assets_builder/test/helpers.dart index 51d42897f..5fdc18502 100644 --- a/pkgs/native_assets_builder/test/helpers.dart +++ b/pkgs/native_assets_builder/test/helpers.dart @@ -154,13 +154,19 @@ Future copyTestProjects({ final sourceString = await sourceFile.readAsString(); final modifiedString = sourceString.replaceAll( 'path: ../../', - 'path: ${pkgNativeAssetsBuilderUri.toFilePath().replaceAll('\\', '/')}', + 'path: ${pkgNativeAssetsBuilderUri.toFilePath().unescape()}', ); await File.fromUri(targetFileUri) .writeAsString(modifiedString, flush: true); } } +extension UnescapePath on String { + /// Remove double encoding of slashes on windows, for string comparison with + /// Unix-style encoded strings. + String unescape() => replaceAll('\\', '/'); +} + /// Logger that outputs the full trace when a test fails. Logger get logger => _logger ??= () { // A new logger is lazily created for each test so that the messages diff --git a/pkgs/native_assets_builder/test_data/README.md b/pkgs/native_assets_builder/test_data/README.md index 311839a71..33f86c30e 100644 --- a/pkgs/native_assets_builder/test_data/README.md +++ b/pkgs/native_assets_builder/test_data/README.md @@ -2,5 +2,5 @@ The `dart_app` depends on multiple packages with native assets. `manifest.yaml` contains a list of all the files of the test projects. This is used to copy the test projects to temporary folders when running tests, -to prevent tests accidentally using temporary files or interferring with each +to prevent tests accidentally using temporary files or interfering with each other. diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/.gitignore b/pkgs/native_assets_builder/test_data/add_asset_link/.gitignore new file mode 100644 index 000000000..f7ad401ea --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +bin/add_asset_link/ diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/README.md b/pkgs/native_assets_builder/test_data/add_asset_link/README.md new file mode 100644 index 000000000..8b3ebd965 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/README.md @@ -0,0 +1 @@ +This sample adds a native library in the link step. \ No newline at end of file diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart new file mode 100644 index 000000000..c56edc742 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/bin/add_asset_link.dart @@ -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. + +import 'package:add_asset_link/add_asset_link.dart'; + +void main(List arguments) { + print('Hello world: ${MyMath.add(3, 4)}!'); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart new file mode 100644 index 000000000..74908a340 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart @@ -0,0 +1,31 @@ +// 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. + +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 arguments) async { + await build(arguments, (config, output) async { + final logger = Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + await CBuilder.library( + name: 'add', + assetName: 'dylib_add_build', + sources: [ + 'src/native_add.c', + ], + dartBuildFiles: ['hook/build.dart'], + linkModePreference: LinkModePreference.dynamic, + ).run( + buildConfig: config, + buildOutput: output, + logger: logger, + linkInPackage: 'add_asset_link', + ); + }); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart new file mode 100644 index 000000000..5cea3b1cd --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/link.dart @@ -0,0 +1,23 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List arguments) async { + await link(arguments, (config, output) async { + final builtDylib = config.assets.first as NativeCodeAsset; + output + ..addAsset( + NativeCodeAsset( + package: 'add_asset_link', + name: 'dylib_add_link', + linkMode: builtDylib.linkMode, + os: builtDylib.os, + architecture: builtDylib.architecture, + file: builtDylib.file, + ), + ) + ..addDependency(config.packageRoot.resolve('hook/link.dart')); + }); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart new file mode 100644 index 000000000..8c0d3965a --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/lib/add_asset_link.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/add_asset_link.dart'; diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart new file mode 100644 index 000000000..71651a7b4 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link.dart @@ -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. + +import 'add_asset_link_bindings.dart' as bindings; + +class MyMath { + static int add(int a, int b) => bindings.add(a, b); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart new file mode 100644 index 000000000..1d1d71837 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/lib/src/add_asset_link_bindings.dart @@ -0,0 +1,12 @@ +// 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. + +import 'dart:ffi' as ffi; + +@ffi.Native( + assetId: 'package:add_asset_link/dylib_add_link') +external int add( + int a, + int b, +); diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml new file mode 100644 index 000000000..be1090386 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/pubspec.yaml @@ -0,0 +1,22 @@ +name: add_asset_link +description: Add a dylib in the link step. +version: 1.0.0 + +publish_to: none + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + # native_assets_cli: ^0.5.0 + meta: ^1.12.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.c b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.c new file mode 100644 index 000000000..570e754fe --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.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 "native_add.h" + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.h b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.h new file mode 100644 index 000000000..275878fe8 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/add_asset_link/src/native_add.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 add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_builder/test_data/complex_link/.gitignore b/pkgs/native_assets_builder/test_data/complex_link/.gitignore new file mode 100644 index 000000000..a051860e1 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +bin/complex_link/ diff --git a/pkgs/native_assets_builder/test_data/complex_link/assets/data_0.json b/pkgs/native_assets_builder/test_data/complex_link/assets/data_0.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link/assets/data_1.json b/pkgs/native_assets_builder/test_data/complex_link/assets/data_1.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart b/pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart new file mode 100644 index 000000000..03fdefaba --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/bin/complex_link.dart @@ -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. + +import 'package:complex_link_helper/complex_link_helper.dart'; + +void main() { + someMethod(); +} diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart new file mode 100644 index 000000000..6c693ec5f --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart @@ -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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'complex_link'; + +void main(List args) async => build(args, (config, output) async { + output.addAssets( + List.generate( + 2, + (index) => DataAsset( + name: 'data_$index', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot.resolve('assets/data_$index.json'), + package: packageName, + ), + ), + linkInPackage: 'complex_link', + ); + }); diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart new file mode 100644 index 000000000..501438d21 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/hook/link.dart @@ -0,0 +1,15 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await link( + args, + (config, output) async => output.addAssets(treeshake(config.assets)), + ); +} + +Iterable treeshake(Iterable assets) => + assets.where((asset) => !asset.id.endsWith('data_helper_2')); diff --git a/pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml new file mode 100644 index 000000000..d5cf1197e --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link/pubspec.yaml @@ -0,0 +1,21 @@ +name: complex_link +description: Prints "Tada!", but packages some assets while tree-shaking others +version: 0.1.0 + +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cli_config: ^0.2.0 + complex_link_helper: + path: ../complex_link_helper/ + logging: ^1.1.1 + native_assets_cli: + path: ../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + path: ^1.9.0 + test: ^1.23.1 diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_0.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_0.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_1.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_1.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_2.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_2.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_3.json b/pkgs/native_assets_builder/test_data/complex_link_helper/assets/data_helper_3.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart new file mode 100644 index 000000000..95ba7e0fa --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart @@ -0,0 +1,34 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'complex_link_helper'; + +void main(List args) async => build(args, (config, output) async { + output.addAssets( + List.generate( + 2, + (index) => DataAsset( + name: 'data_helper_$index', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot.resolve('assets/data_helper_$index.json'), + package: packageName, + ), + ), + ); + output.addAssets( + List.generate( + 2, + (index) => DataAsset( + name: 'data_helper_${index + 2}', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot + .resolve('assets/data_helper_${index + 2}.json'), + package: packageName, + ), + ), + linkInPackage: 'complex_link', + ); + }); diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart b/pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart new file mode 100644 index 000000000..826bbf169 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/lib/complex_link_helper.dart @@ -0,0 +1,7 @@ +// 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. + +void someMethod() { + print('Tada!'); +} diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml b/pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml new file mode 100644 index 000000000..1521b53b1 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/pubspec.yaml @@ -0,0 +1,19 @@ +name: complex_link_helper +description: Adds two json files to the built assets, and two more for linking. +version: 0.1.0 + +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cli_config: ^0.2.0 + logging: ^1.1.1 + native_assets_cli: + path: ../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + path: ^1.9.0 + test: ^1.23.1 diff --git a/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml b/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml index 5455d7ef5..83c4aeb03 100644 --- a/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/cyclic_package_1/pubspec.yaml @@ -10,9 +10,9 @@ environment: dependencies: cyclic_package_2: path: ../cyclic_package_2 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml b/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml index ef9c9ff7e..a44e9f50c 100644 --- a/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/cyclic_package_2/pubspec.yaml @@ -10,9 +10,9 @@ environment: dependencies: cyclic_package_1: path: ../cyclic_package_1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore b/pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore new file mode 100644 index 000000000..eb803bf91 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +bin/drop_dylib_link/ diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/README.md b/pkgs/native_assets_builder/test_data/drop_dylib_link/README.md new file mode 100644 index 000000000..11147bc19 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/README.md @@ -0,0 +1 @@ +This sample builds a native library for adding and multiplying, but then only keeps the one for adding in the linking step. \ No newline at end of file diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart new file mode 100644 index 000000000..be71a0d4f --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/bin/drop_dylib_link.dart @@ -0,0 +1,15 @@ +// 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. + +import 'package:drop_dylib_link/drop_dylib_link.dart'; + +void main(List arguments) { + if (arguments.first == 'add') { + print('Hello world: ${MyMath.add(3, 4)}!'); + } else if (arguments.first == 'multiply') { + print('Hello world: ${MyMath.multiply(3, 4)}!'); + } else { + throw ArgumentError('Must pass either "add" or "multiply"'); + } +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart new file mode 100644 index 000000000..b7e430c37 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart @@ -0,0 +1,48 @@ +// 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. + +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +const packageName = 'drop_dylib_link'; + +void main(List arguments) async { + await build(arguments, (config, output) async { + final logger = Logger('') + ..level = Level.ALL + ..onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + await CBuilder.library( + name: 'add', + assetName: 'dylib_add', + sources: [ + 'src/native_add.c', + ], + dartBuildFiles: ['hook/build.dart'], + linkModePreference: LinkModePreference.dynamic, + ).run( + buildConfig: config, + buildOutput: output, + logger: logger, + linkInPackage: packageName, + ); + + await CBuilder.library( + name: 'multiply', + assetName: 'dylib_multiply', + sources: [ + 'src/native_multiply.c', + ], + dartBuildFiles: ['hook/build.dart'], + linkModePreference: LinkModePreference.dynamic, + ).run( + buildConfig: config, + buildOutput: output, + logger: logger, + linkInPackage: packageName, + ); + }); +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart new file mode 100644 index 000000000..b2f15c0d9 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/link.dart @@ -0,0 +1,18 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List arguments) async { + await link(arguments, (config, output) async { + print(''' +Received ${config.assets.length} assets: ${config.assets.map((e) => e.id)}. +'''); + output.addAssets(config.assets.where((asset) => asset.id.endsWith('add'))); + print(''' +Keeping only ${output.assets.map((e) => e.id)}. +'''); + output.addDependency(config.packageRoot.resolve('hook/link.dart')); + }); +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart new file mode 100644 index 000000000..3e0c2b6b7 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/drop_dylib_link.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/drop_dylib_link.dart'; diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart new file mode 100644 index 000000000..c71715c97 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link.dart @@ -0,0 +1,15 @@ +// 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. + +import 'package:meta/meta.dart'; + +import 'drop_dylib_link_bindings.dart' as bindings; + +class MyMath { + @ResourceIdentifier('add') + static int add(int a, int b) => bindings.add(a, b); + + @ResourceIdentifier('multiply') + static int multiply(int a, int b) => bindings.multiply(a, b); +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart new file mode 100644 index 000000000..231a1a58e --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/lib/src/drop_dylib_link_bindings.dart @@ -0,0 +1,19 @@ +// 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. + +import 'dart:ffi' as ffi; + +@ffi.Native( + assetId: 'package:drop_dylib_link/dylib_add') +external int add( + int a, + int b, +); + +@ffi.Native( + assetId: 'package:drop_dylib_link/dylib_multiply') +external int multiply( + int a, + int b, +); diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml new file mode 100644 index 000000000..527e637de --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/pubspec.yaml @@ -0,0 +1,22 @@ +name: drop_dylib_link +description: Generate two dylibs, remove one in linking. +version: 1.0.0 + +publish_to: none + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + # native_assets_cli: ^0.5.0 + meta: ^1.12.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.c b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.c new file mode 100644 index 000000000..570e754fe --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.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 "native_add.h" + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.h b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.h new file mode 100644 index 000000000..275878fe8 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_add.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 add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.c b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.c new file mode 100644 index 000000000..37ad92677 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.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 "native_multiply.h" + +MYLIB_EXPORT intptr_t multiply(intptr_t a, intptr_t b) { + return a * b; +} diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.h b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.h new file mode 100644 index 000000000..1c326f4ca --- /dev/null +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/src/native_multiply.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 intptr_t multiply(intptr_t a, intptr_t b); diff --git a/pkgs/native_assets_builder/test_data/manifest.yaml b/pkgs/native_assets_builder/test_data/manifest.yaml index 5637dae40..60b4a0d85 100644 --- a/pkgs/native_assets_builder/test_data/manifest.yaml +++ b/pkgs/native_assets_builder/test_data/manifest.yaml @@ -27,6 +27,13 @@ - package_reading_metadata/pubspec.yaml - package_with_metadata/hook/build.dart - package_with_metadata/pubspec.yaml +- simple_link/pubspec.yaml +- simple_link/hook/build.dart +- simple_link/hook/link.dart +- simple_link/assets/data_0.json +- simple_link/assets/data_1.json +- simple_link/assets/data_2.json +- simple_link/assets/data_3.json - some_dev_dep/bin/some_dev_dep.dart - some_dev_dep/pubspec.yaml - wrong_build_output_2/hook/build.dart @@ -37,3 +44,36 @@ - wrong_build_output/pubspec.yaml - wrong_namespace_asset/hook/build.dart - wrong_namespace_asset/pubspec.yaml +- complex_link/pubspec.yaml +- complex_link/bin/complex_link.dart +- complex_link/hook/build.dart +- complex_link/hook/link.dart +- complex_link/assets/data_0.json +- complex_link/assets/data_1.json +- complex_link_helper/pubspec.yaml +- complex_link_helper/lib/complex_link_helper.dart +- complex_link_helper/hook/build.dart +- complex_link_helper/assets/data_helper_0.json +- complex_link_helper/assets/data_helper_1.json +- complex_link_helper/assets/data_helper_2.json +- complex_link_helper/assets/data_helper_3.json +- drop_dylib_link/pubspec.yaml +- drop_dylib_link/lib/drop_dylib_link.dart +- drop_dylib_link/lib/src/drop_dylib_link.dart +- drop_dylib_link/lib/src/drop_dylib_link_bindings.dart +- drop_dylib_link/src/native_add.h +- drop_dylib_link/src/native_multiply.h +- drop_dylib_link/src/native_add.c +- drop_dylib_link/src/native_multiply.c +- drop_dylib_link/hook/link.dart +- drop_dylib_link/hook/build.dart +- drop_dylib_link/bin/drop_dylib_link.dart +- add_asset_link/pubspec.yaml +- add_asset_link/hook/link.dart +- add_asset_link/bin/add_asset_link.dart +- add_asset_link/lib/add_asset_link.dart +- add_asset_link/lib/src/add_asset_link.dart +- add_asset_link/lib/src/add_asset_link_bindings.dart +- add_asset_link/src/native_add.h +- add_asset_link/src/native_add.c +- add_asset_link/hook/build.dart \ No newline at end of file diff --git a/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml b/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml index 47c71fb8f..b7df5d1a1 100644 --- a/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/native_add/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml b/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml index bded3a0b1..b23647db0 100644 --- a/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/native_add_add_source/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml b/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml index 7cb8f482a..e6ee4c5b1 100644 --- a/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/native_subtract/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml b/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml index 26fe6d0d9..ce1016ec3 100644 --- a/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/package_reading_metadata/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ package_with_metadata: path: ../package_with_metadata/ yaml: ^3.1.1 diff --git a/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml b/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml index 9c277aed5..998a373d3 100644 --- a/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/package_with_metadata/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_0.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_0.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_1.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_1.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_2.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_2.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/assets/data_3.json b/pkgs/native_assets_builder/test_data/simple_link/assets/data_3.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart b/pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart new file mode 100644 index 000000000..9f5b97af6 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/bin/simple_link.dart @@ -0,0 +1,7 @@ +// 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. + +void main() { + print('Tada!'); +} diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart new file mode 100644 index 000000000..aa25818d3 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart @@ -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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +const packageName = 'simple_link'; + +void main(List args) async => build(args, (config, output) async { + output.addAssets( + List.generate( + 4, + (index) => DataAsset( + name: 'data_$index', + // TODO(mosuem): Simplify specifying files/file paths + file: config.packageRoot.resolve('assets/data_$index.json'), + package: packageName, + ), + ), + linkInPackage: 'simple_link', + ); + }); diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart new file mode 100644 index 000000000..1c019fcee --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/hook/link.dart @@ -0,0 +1,14 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await link( + args, + (config, output) async => output.addAssets(shake(config.assets)), + ); +} + +Iterable shake(Iterable assets) => assets.skip(2); diff --git a/pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml b/pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml new file mode 100644 index 000000000..7bece8b09 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/simple_link/pubspec.yaml @@ -0,0 +1,19 @@ +name: simple_link +description: Build 4 assets for linking, only keep two in link.dart. +version: 0.1.0 + +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cli_config: ^0.2.0 + logging: ^1.1.1 + native_assets_cli: + path: ../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + path: ^1.9.0 + test: ^1.23.1 diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart index 77785e79c..969f76335 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_build_output/hook/build.dart @@ -8,9 +8,7 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; void main(List args) async { final buildConfig = BuildConfigImpl.fromArguments(args); - await File.fromUri( - buildConfig.outputDirectory.resolve(BuildOutputImpl.fileNameV1_1_0)) - .writeAsString(_wrongContents); + await File.fromUri(buildConfig.outputFile).writeAsString(_wrongContents); } const _wrongContents = ''' diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml index 12698c1d1..8d24057f3 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_build_output/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart index c8412752e..dfb5b5e20 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_2/hook/build.dart @@ -8,9 +8,7 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; void main(List args) async { final buildConfig = BuildConfigImpl.fromArguments(args); - await File.fromUri( - buildConfig.outputDirectory.resolve(BuildOutputImpl.fileNameV1_1_0)) - .writeAsString(_wrongContents); + await File.fromUri(buildConfig.outputFile).writeAsString(_wrongContents); } const _wrongContents = ''' diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml index 8b7ec8702..1a5144cba 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_2/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart index bc1fbca2e..c0c95d799 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_3/hook/build.dart @@ -8,9 +8,7 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; void main(List args) async { final buildConfig = BuildConfigImpl.fromArguments(args); - await File.fromUri( - buildConfig.outputDirectory.resolve(BuildOutputImpl.fileNameV1_1_0)) - .writeAsString(_rightContents); + await File.fromUri(buildConfig.outputFile).writeAsString(_rightContents); exit(1); } diff --git a/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml index 3739c2638..65f185a8a 100644 --- a/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_build_output_3/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml b/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml index f07a745b8..cd7008b47 100644 --- a/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml +++ b/pkgs/native_assets_builder/test_data/wrong_namespace_asset/pubspec.yaml @@ -8,9 +8,9 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../native_assets_cli/ yaml: ^3.1.1 yaml_edit: ^2.1.0 diff --git a/pkgs/native_assets_cli/CHANGELOG.md b/pkgs/native_assets_cli/CHANGELOG.md index 7cb4bce9a..cfe7e3348 100644 --- a/pkgs/native_assets_cli/CHANGELOG.md +++ b/pkgs/native_assets_cli/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.0-wip + +- Add support for `hook/link.dart`. + ## 0.5.4 - Update documentation about providing `NativeCodeAsset.file` in dry runs. diff --git a/pkgs/native_assets_cli/README.md b/pkgs/native_assets_cli/README.md index 3f1eb41da..eefd10643 100644 --- a/pkgs/native_assets_cli/README.md +++ b/pkgs/native_assets_cli/README.md @@ -34,7 +34,7 @@ A typical layout of a package with native code is: to build/bundle with the Dart/Flutter SDK. This file uses the protocol specified in this package. -An example can be found in [example/](example/). +Example can be found in [example/build/](example/build/). ## Usage diff --git a/pkgs/native_assets_cli/example/README.md b/pkgs/native_assets_cli/example/README.md index 1be681f6c..b24ec948e 100644 --- a/pkgs/native_assets_cli/example/README.md +++ b/pkgs/native_assets_cli/example/README.md @@ -1,9 +1,6 @@ -The examples in this folder illustrate how native code is built and bundled -in Dart and Flutter apps. +These are examples for using the hooks defined in `package:native_assets_cli`. -* [native_add_app/](native_add_app/) has a dependency with C code. - This app should declare nothing special. Dart and Flutter should check - all dependencies for native code. -* [native_add_library/](native_add_library/) contains a library with C code. - When Dart code in this library or dependent on this library is invoked, the - C code must be built and bundled so that it can be used by the Dart code. +* [build/](build/) contains examples on how to use `hook/build.dart` to build + and bundle code assets, such as C libraries, into Dart applications. +* [link/](link/) contains examples on how to treeshake unused assets from a Dart + application using the `hook/link.dart` script. \ No newline at end of file diff --git a/pkgs/native_assets_cli/example/build/README.md b/pkgs/native_assets_cli/example/build/README.md new file mode 100644 index 000000000..1be681f6c --- /dev/null +++ b/pkgs/native_assets_cli/example/build/README.md @@ -0,0 +1,9 @@ +The examples in this folder illustrate how native code is built and bundled +in Dart and Flutter apps. + +* [native_add_app/](native_add_app/) has a dependency with C code. + This app should declare nothing special. Dart and Flutter should check + all dependencies for native code. +* [native_add_library/](native_add_library/) contains a library with C code. + When Dart code in this library or dependent on this library is invoked, the + C code must be built and bundled so that it can be used by the Dart code. diff --git a/pkgs/native_assets_cli/example/local_asset/README.md b/pkgs/native_assets_cli/example/build/local_asset/README.md similarity index 100% rename from pkgs/native_assets_cli/example/local_asset/README.md rename to pkgs/native_assets_cli/example/build/local_asset/README.md diff --git a/pkgs/native_assets_cli/example/local_asset/data/asset.txt b/pkgs/native_assets_cli/example/build/local_asset/data/asset.txt similarity index 100% rename from pkgs/native_assets_cli/example/local_asset/data/asset.txt rename to pkgs/native_assets_cli/example/build/local_asset/data/asset.txt diff --git a/pkgs/native_assets_cli/example/local_asset/hook/build.dart b/pkgs/native_assets_cli/example/build/local_asset/hook/build.dart similarity index 100% rename from pkgs/native_assets_cli/example/local_asset/hook/build.dart rename to pkgs/native_assets_cli/example/build/local_asset/hook/build.dart diff --git a/pkgs/native_assets_cli/example/local_asset/pubspec.yaml b/pkgs/native_assets_cli/example/build/local_asset/pubspec.yaml similarity index 68% rename from pkgs/native_assets_cli/example/local_asset/pubspec.yaml rename to pkgs/native_assets_cli/example/build/local_asset/pubspec.yaml index ad2f491df..39cbaced4 100644 --- a/pkgs/native_assets_cli/example/local_asset/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/local_asset/pubspec.yaml @@ -3,16 +3,16 @@ publish_to: none name: local_asset description: Uses an asset local to the package. version: 0.1.0 -repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_library +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/native_add_library environment: sdk: '>=3.3.0 <4.0.0' dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 -# native_assets_cli: -# path: ../../../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_cli/example/native_add_app/README.md b/pkgs/native_assets_cli/example/build/native_add_app/README.md similarity index 100% rename from pkgs/native_assets_cli/example/native_add_app/README.md rename to pkgs/native_assets_cli/example/build/native_add_app/README.md diff --git a/pkgs/native_assets_cli/example/native_add_app/bin/native_add_app.dart b/pkgs/native_assets_cli/example/build/native_add_app/bin/native_add_app.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_app/bin/native_add_app.dart rename to pkgs/native_assets_cli/example/build/native_add_app/bin/native_add_app.dart diff --git a/pkgs/native_assets_cli/example/native_add_app/pubspec.yaml b/pkgs/native_assets_cli/example/build/native_add_app/pubspec.yaml similarity index 87% rename from pkgs/native_assets_cli/example/native_add_app/pubspec.yaml rename to pkgs/native_assets_cli/example/build/native_add_app/pubspec.yaml index f6b2adc52..86e03409f 100644 --- a/pkgs/native_assets_cli/example/native_add_app/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/native_add_app/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none name: native_add_app description: Invokes a package with native assets. version: 0.1.0 -repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_app +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/native_add_app environment: sdk: '>=3.3.0 <4.0.0' diff --git a/pkgs/native_assets_cli/example/native_add_app/test/native_add_library_test.dart b/pkgs/native_assets_cli/example/build/native_add_app/test/native_add_library_test.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_app/test/native_add_library_test.dart rename to pkgs/native_assets_cli/example/build/native_add_app/test/native_add_library_test.dart diff --git a/pkgs/native_assets_cli/example/native_add_library/README.md b/pkgs/native_assets_cli/example/build/native_add_library/README.md similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/README.md rename to pkgs/native_assets_cli/example/build/native_add_library/README.md diff --git a/pkgs/native_assets_cli/example/native_add_library/ffigen.yaml b/pkgs/native_assets_cli/example/build/native_add_library/ffigen.yaml similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/ffigen.yaml rename to pkgs/native_assets_cli/example/build/native_add_library/ffigen.yaml diff --git a/pkgs/native_assets_cli/example/native_add_library/hook/build.dart b/pkgs/native_assets_cli/example/build/native_add_library/hook/build.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/hook/build.dart rename to pkgs/native_assets_cli/example/build/native_add_library/hook/build.dart diff --git a/pkgs/native_assets_cli/example/native_add_library/lib/native_add_library.dart b/pkgs/native_assets_cli/example/build/native_add_library/lib/native_add_library.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/lib/native_add_library.dart rename to pkgs/native_assets_cli/example/build/native_add_library/lib/native_add_library.dart diff --git a/pkgs/native_assets_cli/example/native_add_library/pubspec.yaml b/pkgs/native_assets_cli/example/build/native_add_library/pubspec.yaml similarity index 56% rename from pkgs/native_assets_cli/example/native_add_library/pubspec.yaml rename to pkgs/native_assets_cli/example/build/native_add_library/pubspec.yaml index fb790cfa1..ca8fd001f 100644 --- a/pkgs/native_assets_cli/example/native_add_library/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/native_add_library/pubspec.yaml @@ -3,19 +3,19 @@ publish_to: none name: native_add_library description: Sums two numbers with native code. version: 0.1.0 -repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/native_add_library +repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli/example/build/native_add_library environment: sdk: '>=3.3.0 <4.0.0' dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../../native_toolchain_c/ dev_dependencies: ffigen: ^8.0.2 diff --git a/pkgs/native_assets_cli/example/native_add_library/src/native_add_library.c b/pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.c similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/src/native_add_library.c rename to pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.c diff --git a/pkgs/native_assets_cli/example/native_add_library/src/native_add_library.h b/pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.h similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/src/native_add_library.h rename to pkgs/native_assets_cli/example/build/native_add_library/src/native_add_library.h diff --git a/pkgs/native_assets_cli/example/native_add_library/test/native_add_library_test.dart b/pkgs/native_assets_cli/example/build/native_add_library/test/native_add_library_test.dart similarity index 100% rename from pkgs/native_assets_cli/example/native_add_library/test/native_add_library_test.dart rename to pkgs/native_assets_cli/example/build/native_add_library/test/native_add_library_test.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/README.md b/pkgs/native_assets_cli/example/build/use_dart_api/README.md similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/README.md rename to pkgs/native_assets_cli/example/build/use_dart_api/README.md diff --git a/pkgs/native_assets_cli/example/use_dart_api/ffigen.yaml b/pkgs/native_assets_cli/example/build/use_dart_api/ffigen.yaml similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/ffigen.yaml rename to pkgs/native_assets_cli/example/build/use_dart_api/ffigen.yaml diff --git a/pkgs/native_assets_cli/example/use_dart_api/hook/build.dart b/pkgs/native_assets_cli/example/build/use_dart_api/hook/build.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/hook/build.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/hook/build.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/lib/src/use_dart_api_bindings_generated.dart b/pkgs/native_assets_cli/example/build/use_dart_api/lib/src/use_dart_api_bindings_generated.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/lib/src/use_dart_api_bindings_generated.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/lib/src/use_dart_api_bindings_generated.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/lib/use_dart_api.dart b/pkgs/native_assets_cli/example/build/use_dart_api/lib/use_dart_api.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/lib/use_dart_api.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/lib/use_dart_api.dart diff --git a/pkgs/native_assets_cli/example/use_dart_api/manifest.yaml b/pkgs/native_assets_cli/example/build/use_dart_api/manifest.yaml similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/manifest.yaml rename to pkgs/native_assets_cli/example/build/use_dart_api/manifest.yaml diff --git a/pkgs/native_assets_cli/example/use_dart_api/pubspec.yaml b/pkgs/native_assets_cli/example/build/use_dart_api/pubspec.yaml similarity index 56% rename from pkgs/native_assets_cli/example/use_dart_api/pubspec.yaml rename to pkgs/native_assets_cli/example/build/use_dart_api/pubspec.yaml index a53e696dd..bfef2670c 100644 --- a/pkgs/native_assets_cli/example/use_dart_api/pubspec.yaml +++ b/pkgs/native_assets_cli/example/build/use_dart_api/pubspec.yaml @@ -9,12 +9,12 @@ environment: dependencies: logging: ^1.1.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../../../native_assets_cli/ - native_toolchain_c: ^0.4.0 - # native_toolchain_c: - # path: ../../../native_toolchain_c/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ + # native_toolchain_c: ^0.4.0 + native_toolchain_c: + path: ../../../../native_toolchain_c/ dev_dependencies: ffigen: ^10.0.0 diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.c b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.c similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.c rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.c diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_api_dl.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_api_dl.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_embedder_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_embedder_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_embedder_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_embedder_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_native_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_native_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_native_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_native_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_tools_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_tools_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_tools_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_tools_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/dart_version.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/dart_version.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/dart_version.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/dart_version.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/internal/dart_api_dl_impl.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/internal/dart_api_dl_impl.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/internal/dart_api_dl_impl.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/internal/dart_api_dl_impl.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.c b/pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.c similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.c rename to pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.c diff --git a/pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.h b/pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.h similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/src/use_dart_api.h rename to pkgs/native_assets_cli/example/build/use_dart_api/src/use_dart_api.h diff --git a/pkgs/native_assets_cli/example/use_dart_api/test/use_dart_api_test.dart b/pkgs/native_assets_cli/example/build/use_dart_api/test/use_dart_api_test.dart similarity index 100% rename from pkgs/native_assets_cli/example/use_dart_api/test/use_dart_api_test.dart rename to pkgs/native_assets_cli/example/build/use_dart_api/test/use_dart_api_test.dart diff --git a/pkgs/native_assets_cli/example/link/README.md b/pkgs/native_assets_cli/example/link/README.md new file mode 100644 index 000000000..dfaf7d413 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/README.md @@ -0,0 +1,9 @@ +The examples in this folder illustrate how to use information from the Dart +treeshaking to "link" assets. + +* [app_with_asset_treeshaking/](app_with_asset_treeshaking/) calls some methods + from `package_with_assets`. +* [package_with_assets/](package_with_assets/) contains a library with two + assets and code which uses these assets. It declares these assets in a + `build.dart` hook, and treeshakes them based on the "resources" collected + during the kernel compilation. diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore new file mode 100644 index 000000000..3a8579040 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md new file mode 100644 index 000000000..effe43c82 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml new file mode 100644 index 000000000..dee8927aa --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart new file mode 100644 index 000000000..b315f0c22 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/bin/app_with_asset_treeshaking.dart @@ -0,0 +1,10 @@ +// 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. + +import 'package:app_with_asset_treeshaking/app_with_asset_treeshaking.dart' + as app_with_asset_treeshaking; + +void main(List arguments) { + print('Hello world: ${app_with_asset_treeshaking.callOther()}!'); +} diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart new file mode 100644 index 000000000..19ebd67d8 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/lib/app_with_asset_treeshaking.dart @@ -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. + +import 'package:package_with_assets/package_with_assets.dart'; + +String callOther() { + return someMethod(); +} diff --git a/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml new file mode 100644 index 000000000..0fc93b81d --- /dev/null +++ b/pkgs/native_assets_cli/example/link/app_with_asset_treeshaking/pubspec.yaml @@ -0,0 +1,17 @@ +publish_to: none + +name: app_with_asset_treeshaking +description: A sample command-line application. +version: 1.0.0 + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + package_with_assets: + path: ../package_with_assets/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/.gitignore b/pkgs/native_assets_cli/example/link/package_with_assets/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md b/pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md new file mode 100644 index 000000000..effe43c82 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json b/pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json new file mode 100644 index 000000000..1a7d134dd --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/assets/unused_asset.json @@ -0,0 +1,3 @@ +{ + "freddy": "mercury" +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json b/pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json new file mode 100644 index 000000000..697721f13 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/assets/used_asset.json @@ -0,0 +1,3 @@ +{ + "leroy": "jenkins" +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart new file mode 100644 index 000000000..ed19aa01a --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart @@ -0,0 +1,24 @@ +// 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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await build(args, (config, output) async { + final packageName = config.packageName; + final allAssets = [ + DataAsset( + package: packageName, + name: 'unused_asset', + file: config.packageRoot.resolve('assets/unused_asset.json'), + ), + DataAsset( + package: packageName, + name: 'used_asset', + file: config.packageRoot.resolve('assets/used_asset.json'), + ) + ]; + output.addAssets(allAssets, linkInPackage: packageName); + }); +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart new file mode 100644 index 000000000..7c81e64d1 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/link.dart @@ -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. + +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + await link(args, (config, output) async { + //TODO: Add tree shaking by reading the resources.json produced by the SDK. + final dataAssets = config.assets.whereType(); + output.addAssets(dataAssets); + }); +} diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart b/pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart new file mode 100644 index 000000000..4317fb29d --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/lib/package_with_assets.dart @@ -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. + +import 'package:meta/meta.dart'; + +//TODO: Actually use the assets, needs the AssetBundle interface for Dart. See +//also https://github.com/dart-lang/sdk/issues/54003. +@ResourceIdentifier('used_asset') +String someMethod() => 'Using used_asset'; + +@ResourceIdentifier('unused_asset') +String someOtherMethod() => 'Using unused_asset'; diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml b/pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml new file mode 100644 index 000000000..77dccdba1 --- /dev/null +++ b/pkgs/native_assets_cli/example/link/package_with_assets/pubspec.yaml @@ -0,0 +1,20 @@ +publish_to: none + +name: package_with_assets +description: A starting point for Dart libraries or applications. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.0.0 + +dependencies: + logging: ^1.1.1 + meta: ^1.12.0 + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../../../../native_assets_cli/ + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/pkgs/native_assets_cli/lib/native_assets_cli.dart b/pkgs/native_assets_cli/lib/native_assets_cli.dart index 1ddebe1a9..46a8a13d3 100644 --- a/pkgs/native_assets_cli/lib/native_assets_cli.dart +++ b/pkgs/native_assets_cli/lib/native_assets_cli.dart @@ -10,6 +10,7 @@ export 'src/api/architecture.dart' show Architecture; export 'src/api/asset.dart' show Asset, + DataAsset, DynamicLoadingBundled, DynamicLoadingSystem, LinkMode, @@ -20,7 +21,10 @@ export 'src/api/asset.dart' export 'src/api/build.dart'; export 'src/api/build_config.dart' show BuildConfig, CCompilerConfig; export 'src/api/build_mode.dart' show BuildMode; -export 'src/api/build_output.dart' show BuildOutput; +export 'src/api/build_output.dart' show BuildOutput, LinkOutput; +export 'src/api/hook_config.dart' show HookConfig; export 'src/api/ios_sdk.dart' show IOSSdk; +export 'src/api/link.dart'; +export 'src/api/link_config.dart' show LinkConfig; export 'src/api/link_mode_preference.dart' show LinkModePreference; export 'src/api/os.dart' show OS; diff --git a/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart b/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart index c96f2a2b2..80e764180 100644 --- a/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart +++ b/pkgs/native_assets_cli/lib/native_assets_cli_internal.dart @@ -30,10 +30,14 @@ export 'src/api/asset.dart' StaticLinkingImpl; export 'src/api/build_config.dart' show BuildConfigImpl, CCompilerConfigImpl; export 'src/api/build_mode.dart' show BuildModeImpl; -export 'src/api/build_output.dart' show BuildOutputImpl; +export 'src/api/build_output.dart' show HookOutputImpl; +export 'src/api/hook_config.dart' show HookConfigImpl; export 'src/api/ios_sdk.dart' show IOSSdkImpl; +export 'src/api/link_config.dart' show LinkConfigImpl; export 'src/api/link_mode_preference.dart' show LinkModePreferenceImpl; export 'src/api/os.dart' show OSImpl; export 'src/model/dependencies.dart'; +export 'src/model/hook.dart'; export 'src/model/metadata.dart'; +export 'src/model/resource_identifiers.dart'; export 'src/model/target.dart'; diff --git a/pkgs/native_assets_cli/lib/src/api/build.dart b/pkgs/native_assets_cli/lib/src/api/build.dart index 0cd4bb5f5..7b173d402 100644 --- a/pkgs/native_assets_cli/lib/src/api/build.dart +++ b/pkgs/native_assets_cli/lib/src/api/build.dart @@ -89,8 +89,8 @@ Future build( List arguments, Future Function(BuildConfig config, BuildOutput output) builder, ) async { - final config = BuildConfig(arguments); - final output = BuildOutputImpl(); + final config = BuildConfigImpl.fromArguments(arguments); + final output = HookOutputImpl(); await builder(config, output); await output.writeToFile(config: config); } diff --git a/pkgs/native_assets_cli/lib/src/api/build_config.dart b/pkgs/native_assets_cli/lib/src/api/build_config.dart index b27f6b628..3d1fa965b 100644 --- a/pkgs/native_assets_cli/lib/src/api/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/api/build_config.dart @@ -2,22 +2,21 @@ // 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:convert'; import 'dart:io'; import 'package:cli_config/cli_config.dart'; import 'package:collection/collection.dart'; -import 'package:crypto/crypto.dart'; import 'package:pub_semver/pub_semver.dart'; +import '../model/hook.dart'; import '../model/metadata.dart'; -import '../model/target.dart'; import '../utils/json.dart'; import '../utils/map.dart'; import 'architecture.dart'; import 'asset.dart'; import 'build.dart'; import 'build_mode.dart'; +import 'hook_config.dart'; import 'ios_sdk.dart'; import 'link_mode_preference.dart'; import 'os.dart'; @@ -32,53 +31,7 @@ part 'c_compiler_config.dart'; /// be automatically run, by the Flutter and Dart SDK tools. The hook will be /// run with specific commandline arguments, which [BuildConfig] can parse and /// provide more convenient access to. -abstract final class BuildConfig { - /// The directory in which all output and intermediate artifacts should be - /// placed. - Uri get outputDirectory; - - /// The name of the package the native assets are built for. - String get packageName; - - /// The root of the package the native assets are built for. - /// - /// Often a package's native assets are built because a package is a - /// dependency of another. For this it is convenient to know the packageRoot. - Uri get packageRoot; - - /// The architecture being compiled for. - /// - /// Not specified (`null`) during a [dryRun]. - Architecture? get targetArchitecture; - - /// The operating system being compiled for. - OS get targetOS; - - /// When compiling for iOS, whether to target device or simulator. - /// - /// Not available if [targetOS] is [OS.iOS]. Will throw a [StateError] if - /// accessed during a [dryRun]. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - IOSSdk get targetIOSSdk; - - /// When compiling for Android, the minimum Android SDK API version to that - /// the compiled code will be compatible with. - /// - /// Required when [targetOS] equals [OS.android]. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - /// - /// For more information about the Android API version, refer to - /// [`minSdkVersion`](https://developer.android.com/ndk/guides/sdk-versions#minsdkversion) - /// in the Android documentation. - int? get targetAndroidNdkApi; - - /// The preferred [LinkMode] method for [NativeCodeAsset]s. - LinkModePreference get linkModePreference; - +abstract final class BuildConfig implements HookConfig { /// Metadata from a direct dependency. /// /// The [packageName] of is the package name of the direct dependency. @@ -89,33 +42,6 @@ abstract final class BuildConfig { /// during a [dryRun]. Object? metadatum(String packageName, String key); - /// The configuration for invoking the C compiler. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - CCompilerConfig get cCompiler; - - /// Whether this run is a dry-run, which doesn't build anything. - /// - /// A dry-run only reports information about which assets a build would - /// create, but doesn't actually create files. - bool get dryRun; - - /// The [BuildMode] that the code should be compiled in. - /// - /// Currently [BuildMode.debug] and [BuildMode.release] are the only modes. - /// - /// Not available during a [dryRun]. Will throw a [StateError] if accessed - /// during a [dryRun]. - BuildMode get buildMode; - - /// The asset types that the invoker of this build supports. - /// - /// Currently known values: - /// * [NativeCodeAsset.type] - /// * [DataAsset.type] - Iterable get supportedAssetTypes; - /// The version of [BuildConfig]. /// /// The build config is used in the protocol between the Dart and Flutter SDKs @@ -124,7 +50,7 @@ abstract final class BuildConfig { /// We're trying to avoid breaking changes. However, in the case that we have /// to, the major version mismatch between the Dart or Flutter SDK and build /// hook (`hook/build.dart`) will lead to a nice error message. - static Version get latestVersion => BuildConfigImpl.latestVersion; + static Version get latestVersion => HookConfigImpl.latestVersion; /// Constructs a config by parsing CLI arguments and loading the config file. /// @@ -177,7 +103,7 @@ abstract final class BuildConfig { Iterable? supportedAssetTypes, }) => BuildConfigImpl( - outDir: outputDirectory, + outputDirectory: outputDirectory, packageName: packageName, packageRoot: packageRoot, buildMode: buildMode as BuildModeImpl, @@ -192,7 +118,7 @@ abstract final class BuildConfig { for (final entry in dependencyMetadata.entries) entry.key: Metadata(entry.value.cast()) } - : {}, + : null, supportedAssetTypes: supportedAssetTypes, ); @@ -204,7 +130,7 @@ abstract final class BuildConfig { /// /// For the documentation of the parameters, see the equally named fields. factory BuildConfig.dryRun({ - required Uri outDir, + required Uri outputDirectory, required String packageName, required Uri packageRoot, required OS targetOS, @@ -212,7 +138,7 @@ abstract final class BuildConfig { Iterable? supportedAssetTypes, }) => BuildConfigImpl.dryRun( - outDir: outDir, + outputDirectory: outputDirectory, packageName: packageName, packageRoot: packageRoot, targetOS: targetOS as OSImpl, diff --git a/pkgs/native_assets_cli/lib/src/api/build_output.dart b/pkgs/native_assets_cli/lib/src/api/build_output.dart index 86387e596..20aab1a8f 100644 --- a/pkgs/native_assets_cli/lib/src/api/build_output.dart +++ b/pkgs/native_assets_cli/lib/src/api/build_output.dart @@ -18,9 +18,11 @@ import '../utils/map.dart'; import 'architecture.dart'; import 'asset.dart'; import 'build_config.dart'; +import 'hook_config.dart'; import 'os.dart'; -part '../model/build_output.dart'; +part '../model/hook_output.dart'; +part 'link_output.dart'; /// The output of a build hook (`hook/build.dart`) invocation. /// @@ -42,6 +44,16 @@ abstract final class BuildOutput { /// the dry run must be provided. Iterable get assets; + /// The assets produced by this build which should be linked. + /// + /// Every key in the map is a package name. These assets in the values are not + /// bundled with the application, but are sent to the link hook of the package + /// specified in the key, which can decide if they are bundled or not. + /// + /// In dry runs, the assets for all [Architecture]s for the [OS] specified in + /// the dry run must be provided. + Map> get assetsForLinking; + /// The files used by this build. /// /// If any of the files in [dependencies] are modified after [timestamp], the @@ -77,7 +89,7 @@ abstract final class BuildOutput { Iterable? dependencies, Map? metadata, }) => - BuildOutputImpl( + HookOutputImpl( timestamp: timestamp, assets: assets?.cast().toList(), dependencies: Dependencies([...?dependencies]), @@ -85,10 +97,19 @@ abstract final class BuildOutput { ); /// Adds [Asset]s produced by this build or dry run. - void addAsset(Asset asset); + /// + /// If the [linkInPackage] argument is specified, the asset will not be + /// bundled during the build step, but sent as input to the link hook of the + /// specified package, where it can be further processed and possibly bundled. + void addAsset(Asset asset, {String? linkInPackage}); /// Adds [Asset]s produced by this build or dry run. - void addAssets(Iterable assets); + /// + /// If the [linkInPackage] argument is specified, the assets will not be + /// bundled during the build step, but sent as input to the link hook of the + /// specified package, where they can be further processed and possibly + /// bundled. + void addAssets(Iterable assets, {String? linkInPackage}); /// Adds file used by this build. /// @@ -114,5 +135,5 @@ abstract final class BuildOutput { /// /// The build output is used in the protocol between the Dart and Flutter SDKs /// and packages through build hook invocations. - static Version get latestVersion => BuildOutputImpl.latestVersion; + static Version get latestVersion => HookOutputImpl.latestVersion; } diff --git a/pkgs/native_assets_cli/lib/src/api/data_asset.dart b/pkgs/native_assets_cli/lib/src/api/data_asset.dart index 6726cf583..4c9459d43 100644 --- a/pkgs/native_assets_cli/lib/src/api/data_asset.dart +++ b/pkgs/native_assets_cli/lib/src/api/data_asset.dart @@ -15,17 +15,24 @@ part of 'asset.dart'; abstract final class DataAsset implements Asset { /// Constructs a data asset. /// - /// The [id] of this asset is a uri `package:/` from [package] - /// and [name]. + /// The unique [id] of this asset is a uri `package:/` from + /// [package] and [name]. factory DataAsset({ required String package, required String name, required Uri file, }) => DataAssetImpl( - id: 'package:$package/$name', + name: name, + package: package, file: file, ); + /// The package which contains this asset. + String get package; + + /// The name of this asset, which must be unique for the package. + String get name; + static const String type = 'data'; } diff --git a/pkgs/native_assets_cli/lib/src/api/hook_config.dart b/pkgs/native_assets_cli/lib/src/api/hook_config.dart new file mode 100644 index 000000000..4935b19e0 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/hook_config.dart @@ -0,0 +1,104 @@ +// 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. + +import 'dart:convert'; + +import 'package:cli_config/cli_config.dart'; +import 'package:collection/collection.dart'; +import 'package:crypto/crypto.dart'; +import 'package:pub_semver/pub_semver.dart'; + +import '../model/hook.dart'; +import '../model/metadata.dart'; +import '../model/target.dart'; +import '../utils/map.dart'; +import 'architecture.dart'; +import 'asset.dart'; +import 'build_config.dart'; +import 'build_mode.dart'; +import 'ios_sdk.dart'; +import 'link_config.dart'; +import 'link_mode_preference.dart'; +import 'os.dart'; + +part '../model/hook_config.dart'; + +/// The shared properties of a [LinkConfig] and a [BuildConfig]. +/// +/// This abstraction makes it easier to design APIs intended for both kinds of +/// build hooks, building and linking. +abstract class HookConfig { + /// The directory in which all output and intermediate artifacts should be + /// placed. + Uri get outputDirectory; + + /// The name of the package the assets are built for. + String get packageName; + + /// The root of the package the assets are built for. + /// + /// Often a package's assets are built because a package is a dependency of + /// another. For this it is convenient to know the packageRoot. + Uri get packageRoot; + + /// The architecture being compiled for. + /// + /// Not specified (`null`) during a [dryRun]. + Architecture? get targetArchitecture; + + /// The operating system being compiled for. + OS get targetOS; + + /// When compiling for iOS, whether to target device or simulator. + /// + /// Not available if [targetOS] is [OS.iOS]. Will throw a [StateError] if + /// accessed during a [dryRun]. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + IOSSdk? get targetIOSSdk; + + /// When compiling for Android, the minimum Android SDK API version to that + /// the compiled code will be compatible with. + /// + /// Required when [targetOS] equals [OS.android]. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + /// + /// For more information about the Android API version, refer to + /// [`minSdkVersion`](https://developer.android.com/ndk/guides/sdk-versions#minsdkversion) + /// in the Android documentation. + int? get targetAndroidNdkApi; + + /// The configuration for invoking the C compiler. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + CCompilerConfig get cCompiler; + + /// Whether this run is a dry-run, which doesn't build anything. + /// + /// A dry-run only reports information about which assets a build would + /// create, but doesn't actually create files. + bool get dryRun; + + /// The [BuildMode] that the code should be compiled in. + /// + /// Currently [BuildMode.debug] and [BuildMode.release] are the only modes. + /// + /// Not available during a [dryRun]. Will throw a [StateError] if accessed + /// during a [dryRun]. + BuildMode get buildMode; + + /// The asset types that the invoker of this hook supports. + /// + /// Currently known values: + /// * [NativeCodeAsset.type] + /// * [DataAsset.type] + Iterable get supportedAssetTypes; + + /// The preferred [LinkMode] method for [NativeCodeAsset]s. + LinkModePreference get linkModePreference; +} diff --git a/pkgs/native_assets_cli/lib/src/api/link.dart b/pkgs/native_assets_cli/lib/src/api/link.dart new file mode 100644 index 000000000..c86894a7b --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/link.dart @@ -0,0 +1,45 @@ +// 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. + +import '../../native_assets_cli_internal.dart'; +import 'build_output.dart'; +import 'link_config.dart'; + +/// Runs a native assets link. +/// +/// Can link native assets which are not already available, or expose existing +/// files. Each individual asset is assigned a unique asset ID. +/// +/// The linking script may receive assets from build scripts, which are accessed +/// through [LinkConfig.assets]. They will only be bundled with the final +/// application if included in the [LinkOutput]. +/// +/// +/// ```dart +/// import 'package:native_assets_cli/native_assets_cli.dart'; +/// +/// void main(List args) async { +/// await link(args, (config, output) async { +/// final dataAssets = config.assets +/// .whereType(); +/// output.addAssets(dataAssets); +/// }); +/// } +/// ``` +Future link( + List arguments, + Future Function(LinkConfig config, LinkOutput output) linker, +) async { + final config = LinkConfig.fromArguments(arguments) as LinkConfigImpl; + + // The built assets are dependencies of linking, as the linking should be + // rerun if they change. + final builtAssetsFiles = + config.assets.map((asset) => asset.file).whereType().toList(); + final linkOutput = HookOutputImpl( + dependencies: Dependencies(builtAssetsFiles), + ); + await linker(config, linkOutput); + await linkOutput.writeToFile(config: config); +} diff --git a/pkgs/native_assets_cli/lib/src/api/link_config.dart b/pkgs/native_assets_cli/lib/src/api/link_config.dart new file mode 100644 index 000000000..39fabc519 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/link_config.dart @@ -0,0 +1,101 @@ +// 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. +import 'dart:convert'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:cli_config/cli_config.dart'; +import 'package:collection/collection.dart'; +import 'package:pub_semver/pub_semver.dart'; + +import '../model/hook.dart'; +import '../utils/map.dart'; +import 'architecture.dart'; +import 'asset.dart'; +import 'build_config.dart'; +import 'build_mode.dart'; +import 'hook_config.dart'; +import 'ios_sdk.dart'; +import 'link_mode_preference.dart'; +import 'os.dart'; + +part '../model/link_config.dart'; + +/// The configuration for a link hook (`hook/link.dart`) invocation. +/// +/// It consists of a subset of the fields from the [BuildConfig] already passed +/// to the build hook and the [assets] from the build step. +abstract class LinkConfig implements HookConfig { + /// The list of assets to be linked. These are the assets generated by a + /// `build.dart` script destined for this packages `link.dart`. + Iterable get assets; + + /// Generate the [LinkConfig] from the input arguments to the linking script. + factory LinkConfig.fromArguments(List arguments) => + LinkConfigImpl.fromArguments(arguments); + + factory LinkConfig.build({ + required Uri outputDirectory, + required String packageName, + required Uri packageRoot, + Architecture? targetArchitecture, + required OS targetOS, + IOSSdk? targetIOSSdk, + CCompilerConfig? cCompiler, + BuildMode? buildMode, + List? supportedAssetTypes, + int? targetAndroidNdkApi, + required Iterable assets, + required LinkModePreference linkModePreference, + bool? dryRun, + Version? version, + }) => + LinkConfigImpl( + assets: assets.cast(), + outputDirectory: outputDirectory, + packageName: packageName, + packageRoot: packageRoot, + buildMode: buildMode as BuildModeImpl, + cCompiler: cCompiler as CCompilerConfigImpl?, + targetAndroidNdkApi: targetAndroidNdkApi, + targetArchitecture: targetArchitecture as ArchitectureImpl?, + targetIOSSdk: targetIOSSdk as IOSSdkImpl?, + targetOS: targetOS as OSImpl, + dryRun: dryRun, + linkModePreference: linkModePreference as LinkModePreferenceImpl, + supportedAssetTypes: supportedAssetTypes, + version: version, + ); + + factory LinkConfig.dryRun({ + required Uri outputDirectory, + required String packageName, + required Uri packageRoot, + required OS targetOS, + List? supportedAssetTypes, + required Iterable assets, + required LinkModePreference linkModePreference, + Version? version, + }) => + LinkConfigImpl.dryRun( + assets: assets.cast(), + outputDirectory: outputDirectory, + packageName: packageName, + packageRoot: packageRoot, + targetOS: targetOS as OSImpl, + supportedAssetTypes: supportedAssetTypes, + linkModePreference: linkModePreference as LinkModePreferenceImpl, + version: version, + ); + + /// The version of [BuildConfig]. + /// + /// The build config is used in the protocol between the Dart and Flutter SDKs + /// and packages through build hook invocations. + /// + /// We're trying to avoid breaking changes. However, in the case that we have + /// to, the major version mismatch between the Dart or Flutter SDK and build + /// hook (`hook/build.dart`) will lead to a nice error message. + static Version get latestVersion => HookConfigImpl.latestVersion; +} diff --git a/pkgs/native_assets_cli/lib/src/api/link_output.dart b/pkgs/native_assets_cli/lib/src/api/link_output.dart new file mode 100644 index 000000000..6e7bd996d --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/api/link_output.dart @@ -0,0 +1,61 @@ +// 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. + +part of 'build_output.dart'; + +abstract final class LinkOutput { + /// Start time for the link of this output. + /// + /// The [timestamp] is rounded down to whole seconds, because + /// [File.lastModified] is rounded to whole seconds and caching logic compares + /// these timestamps. + DateTime get timestamp; + + /// The assets produced by this link. + /// + /// In dry runs, the assets for all [Architecture]s for the [OS] specified in + /// the dry run must be provided. + Iterable get assets; + + /// The files used by this link. + /// + /// If any of the files in [dependencies] are modified after [timestamp], the + /// link will be re-run. + Iterable get dependencies; + + /// Adds file used by this link. + /// + /// If any of the files are modified after [timestamp], the link will be + /// re-run. + void addDependency(Uri dependency); + + /// Adds files used by this link. + /// + /// If any of the files are modified after [timestamp], the link will be + /// re-run. + void addDependencies(Iterable dependencies); + + /// Adds [Asset]s produced by this link or dry run. + void addAsset(Asset asset); + + /// Adds [Asset]s produced by this link or dry run. + void addAssets(Iterable assets); + + factory LinkOutput({ + List? assets, + Dependencies? dependencies, + DateTime? timestamp, + }) => + HookOutputImpl( + assets: assets, + dependencies: dependencies, + timestamp: timestamp, + ); + + /// The version of [LinkOutput]. + /// + /// The link output is used in the protocol between the Dart and Flutter SDKs + /// and packages through build hook invocations. + static Version get latestVersion => HookOutputImpl.latestVersion; +} diff --git a/pkgs/native_assets_cli/lib/src/model/asset.dart b/pkgs/native_assets_cli/lib/src/model/asset.dart index eec072e5d..0c98f3b07 100644 --- a/pkgs/native_assets_cli/lib/src/model/asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/asset.dart @@ -7,8 +7,9 @@ part of '../api/asset.dart'; abstract final class AssetImpl implements Asset { Map toJson(Version version); - static List listFromJsonList(List list) { + static List listFromJson(List? list) { final assets = []; + if (list == null) return assets; for (final jsonElement in list) { final jsonMap = as>(jsonElement); final type = jsonMap[NativeCodeAssetImpl.typeKey]; @@ -24,4 +25,10 @@ abstract final class AssetImpl implements Asset { } return assets; } + + static List> listToJson( + Iterable assets, Version version) => + [ + for (final asset in assets) asset.toJson(version), + ]; } diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart index 53a6bc170..bc052240c 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config.dart +++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart @@ -4,243 +4,78 @@ part of '../api/build_config.dart'; -final class BuildConfigImpl implements BuildConfig { +final class BuildConfigImpl extends HookConfigImpl implements BuildConfig { @override - Uri get outputDirectory => _outDir; - late final Uri _outDir; + Hook get hook => Hook.build; @override - String get packageName => _packageName; - late final String _packageName; - - @override - Uri get packageRoot => _packageRoot; - late final Uri _packageRoot; - - @override - ArchitectureImpl? get targetArchitecture => _targetArchitecture; - late final ArchitectureImpl? _targetArchitecture; - - @override - OSImpl get targetOS => _targetOS; - late final OSImpl _targetOS; - - @override - IOSSdkImpl get targetIOSSdk { - _ensureNotDryRun(); - if (_targetOS != OS.iOS) { - throw StateError( - 'This field is not available in if targetOS is not OS.iOS.', - ); + //TODO: Should be removed once migration to `hook/` is complete. + Uri get script { + final hookScript = packageRoot.resolve('hook/').resolve(hook.scriptName); + if (File.fromUri(hookScript).existsSync()) { + return hookScript; + } else { + return packageRoot.resolve(hook.scriptName); } - return _targetIOSSdk!; - } - - late final IOSSdkImpl? _targetIOSSdk; - - @override - int? get targetAndroidNdkApi { - _ensureNotDryRun(); - return _targetAndroidNdkApi; } - late final int? _targetAndroidNdkApi; - @override - LinkModePreferenceImpl get linkModePreference => _linkModePreference; - late final LinkModePreferenceImpl _linkModePreference; + String get outputName => + version > Version(1, 1, 0) ? 'build_output.json' : 'build_output.yaml'; @override Object? metadatum(String packageName, String key) { - _ensureNotDryRun(); + HookConfigImpl.ensureNotDryRun(dryRun); return _dependencyMetadata?[packageName]?.metadata[key]; } - late final Map? _dependencyMetadata; - - @override - CCompilerConfigImpl get cCompiler { - _ensureNotDryRun(); - return _cCompiler; - } - - late final CCompilerConfigImpl _cCompiler; - - @override - bool get dryRun => _dryRun ?? false; - late final bool? _dryRun; - - @override - BuildModeImpl get buildMode { - _ensureNotDryRun(); - return _buildMode; - } - - late final BuildModeImpl _buildMode; - - @override - Iterable get supportedAssetTypes => _supportedAssetTypes; - - late final List _supportedAssetTypes; + final Map? _dependencyMetadata; - Version get version => _version; - - late final Version _version; - - Config get config => _config; - late final Config _config; - - factory BuildConfigImpl({ - required Uri outDir, - required String packageName, - required Uri packageRoot, - required BuildModeImpl buildMode, - required ArchitectureImpl targetArchitecture, - required OSImpl targetOS, - IOSSdkImpl? targetIOSSdk, - int? targetAndroidNdkApi, - CCompilerConfigImpl? cCompiler, - required LinkModePreferenceImpl linkModePreference, - Map? dependencyMetadata, + static List _supportedAssetTypesBackwardsCompatibility( Iterable? supportedAssetTypes, - Version? version, - }) { - final nonValidated = BuildConfigImpl._() - .._version = version ?? latestVersion - .._outDir = outDir - .._packageName = packageName - .._packageRoot = packageRoot - .._buildMode = buildMode - .._targetArchitecture = targetArchitecture - .._targetOS = targetOS - .._targetIOSSdk = targetIOSSdk - .._targetAndroidNdkApi = targetAndroidNdkApi - .._cCompiler = cCompiler ?? CCompilerConfigImpl() - .._linkModePreference = linkModePreference - .._dependencyMetadata = dependencyMetadata - .._dryRun = false - .._supportedAssetTypes = - _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes); - final parsedConfigFile = nonValidated.toJson(); - final config = Config(fileParsed: parsedConfigFile); - return BuildConfigImpl.fromConfig(config); - } + ) => + supportedAssetTypes?.toList() ?? [NativeCodeAsset.type]; - factory BuildConfigImpl.dryRun({ - required Uri outDir, - required String packageName, - required Uri packageRoot, - required OSImpl targetOS, - required LinkModePreferenceImpl linkModePreference, + BuildConfigImpl({ + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + Version? version, + super.buildMode, + super.cCompiler, Iterable? supportedAssetTypes, - }) { - final nonValidated = BuildConfigImpl._() - .._version = latestVersion - .._outDir = outDir - .._packageName = packageName - .._packageRoot = packageRoot - .._targetOS = targetOS - .._targetArchitecture = null - .._linkModePreference = linkModePreference - .._cCompiler = CCompilerConfigImpl() - .._dryRun = true - .._supportedAssetTypes = - _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes); - final parsedConfigFile = nonValidated.toJson(); - final config = Config(fileParsed: parsedConfigFile); - return BuildConfigImpl.fromConfig(config); - } - - /// Constructs a checksum for a [BuildConfigImpl] based on the fields of a - /// buildconfig that influence the build. - /// - /// This can be used for an [outputDirectory], but should not be used for - /// dry-runs. - /// - /// In particular, it only takes the package name from [packageRoot], so that - /// the hash is equal across checkouts and ignores [outputDirectory] itself. - static String checksum({ - required String packageName, - required Uri packageRoot, - required ArchitectureImpl targetArchitecture, - required OSImpl targetOS, - required BuildModeImpl buildMode, - IOSSdkImpl? targetIOSSdk, - int? targetAndroidNdkApi, - CCompilerConfigImpl? cCompiler, - required LinkModePreferenceImpl linkModePreference, + super.targetAndroidNdkApi, + required super.targetArchitecture, + super.targetIOSSdk, + required super.targetOS, + required super.linkModePreference, Map? dependencyMetadata, - Iterable? supportedAssetTypes, - Version? version, - }) { - final input = [ - version ?? latestVersion, - packageName, - targetArchitecture.toString(), - targetOS.toString(), - targetIOSSdk.toString(), - targetAndroidNdkApi.toString(), - buildMode.toString(), - linkModePreference.toString(), - cCompiler?.archiver.toString(), - cCompiler?.compiler.toString(), - cCompiler?.envScript.toString(), - cCompiler?.envScriptArgs.toString(), - cCompiler?.linker.toString(), - if (dependencyMetadata != null) - for (final entry in dependencyMetadata.entries) ...[ - entry.key, - json.encode(entry.value.toJson()), - ], - ..._supportedAssetTypesBackwardsCompatibility(supportedAssetTypes), - ].join('###'); - final sha256String = sha256.convert(utf8.encode(input)).toString(); - // 256 bit hashes lead to 64 hex character strings. - // To avoid overflowing file paths limits, only use 32. - // Using 16 hex characters would also be unlikely to have collisions. - const nameLength = 32; - return sha256String.substring(0, nameLength); - } + super.dryRun, + }) : _dependencyMetadata = dependencyMetadata, + super( + hook: Hook.build, + version: version ?? HookConfigImpl.latestVersion, + supportedAssetTypes: + _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes), + ); - static List _supportedAssetTypesBackwardsCompatibility( + BuildConfigImpl.dryRun({ + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + required super.targetOS, + required super.linkModePreference, Iterable? supportedAssetTypes, - ) => - [ - ...?supportedAssetTypes, - if (supportedAssetTypes == null) NativeCodeAsset.type, - ]; - - BuildConfigImpl._(); - - /// The version of [BuildConfigImpl]. - /// - /// This class is used in the protocol between the Dart and Flutter SDKs - /// and packages through build hook invocations. - /// - /// If we ever were to make breaking changes, it would be useful to give - /// proper error messages rather than just fail to parse the JSON - /// representation in the protocol. - static Version latestVersion = Version(1, 2, 0); - - factory BuildConfigImpl.fromConfig(Config config) { - final result = BuildConfigImpl._().._cCompiler = CCompilerConfigImpl._(); - final configExceptions = []; - for (final f in result._readFieldsFromConfig()) { - try { - f(config); - } on FormatException catch (e, st) { - configExceptions.add(e); - configExceptions.add(st); - } - } - - if (configExceptions.isNotEmpty) { - throw FormatException('Configuration is not in the right format. ' - 'FormatExceptions: $configExceptions'); - } + }) : _dependencyMetadata = null, + super.dryRun( + hook: Hook.build, + version: HookConfigImpl.latestVersion, + supportedAssetTypes: + _supportedAssetTypesBackwardsCompatibility(supportedAssetTypes), + ); - return result; - } + factory BuildConfigImpl._fromConfig(Config config) => + _readFieldsFromConfig(config); static BuildConfigImpl fromArguments( List args, { @@ -255,306 +90,103 @@ final class BuildConfigImpl implements BuildConfig { environment: environment, workingDirectory: workingDirectory, ); - return BuildConfigImpl.fromConfig(config); + return BuildConfigImpl._fromConfig(config); } - static const outDirConfigKey = 'out_dir'; - static const packageNameConfigKey = 'package_name'; - static const packageRootConfigKey = 'package_root'; static const dependencyMetadataConfigKey = 'dependency_metadata'; - static const _versionKey = 'version'; - static const targetAndroidNdkApiConfigKey = 'target_android_ndk_api'; - static const dryRunConfigKey = 'dry_run'; - static const supportedAssetTypesKey = 'supported_asset_types'; - List _readFieldsFromConfig() { - var osSet = false; - var ccSet = false; - return [ - (config) { - final version = Version.parse(config.string('version')); - if (version.major > latestVersion.major) { - throw FormatException( - 'The config version $version is newer than this ' - 'package:native_assets_cli config version $latestVersion, ' - 'please update native_assets_cli.', - ); - } - if (version.major < latestVersion.major) { - throw FormatException( - 'The config version $version is newer than this ' - 'package:native_assets_cli config version $latestVersion, ' - 'please update the Dart or Flutter SDK.', - ); - } - _version = version; - }, - (config) => _config = config, - (config) => _dryRun = config.optionalBool(dryRunConfigKey), - (config) => _outDir = config.path(outDirConfigKey, mustExist: true), - (config) => _packageName = config.string(packageNameConfigKey), - (config) => - _packageRoot = config.path(packageRootConfigKey, mustExist: true), - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(BuildModeImpl.configKey); - } else { - _buildMode = BuildModeImpl.fromString( - config.string( - BuildModeImpl.configKey, - validValues: BuildModeImpl.values.map((e) => '$e'), - ), - ); - } - }, - (config) { - _targetOS = OSImpl.fromString( - config.string( - OSImpl.configKey, - validValues: OSImpl.values.map((e) => '$e'), - ), - ); - osSet = true; - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(ArchitectureImpl.configKey); - _targetArchitecture = null; - } else { - final validArchitectures = [ - if (!osSet) - ...ArchitectureImpl.values - else - for (final target in Target.values) - if (target.os == _targetOS) target.architecture - ]; - _targetArchitecture = ArchitectureImpl.fromString( - config.string( - ArchitectureImpl.configKey, - validValues: validArchitectures.map((e) => '$e'), - ), - ); - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(IOSSdkImpl.configKey); - } else { - _targetIOSSdk = (osSet && _targetOS == OSImpl.iOS) - ? IOSSdkImpl.fromString( - config.string( - IOSSdkImpl.configKey, - validValues: IOSSdkImpl.values.map((e) => '$e'), - ), - ) - : null; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(targetAndroidNdkApiConfigKey); - } else { - _targetAndroidNdkApi = (osSet && _targetOS == OSImpl.android) - ? config.int(targetAndroidNdkApiConfigKey) - : null; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.arConfigKeyFull); - } else { - cCompiler._archiver = config.optionalPath( - CCompilerConfigImpl.arConfigKeyFull, - mustExist: true, - ); - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._compiler = config.optionalPath( - CCompilerConfigImpl.ccConfigKeyFull, - mustExist: true, - ); - ccSet = true; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._linker = config.optionalPath( - CCompilerConfigImpl.ldConfigKeyFull, - mustExist: true, - ); - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._envScript = (ccSet && - cCompiler.compiler != null && - cCompiler.compiler!.toFilePath().endsWith('cl.exe')) - ? config.path(CCompilerConfigImpl.envScriptConfigKeyFull, - mustExist: true) - : null; - } - }, - (config) { - if (dryRun) { - _throwIfNotNullInDryRun(CCompilerConfigImpl.ccConfigKeyFull); - } else { - cCompiler._envScriptArgs = config.optionalStringList( - CCompilerConfigImpl.envScriptArgsConfigKeyFull, - splitEnvironmentPattern: ' ', - ); - } - }, - (config) { - _linkModePreference = LinkModePreferenceImpl.fromString( - config.string( - LinkModePreferenceImpl.configKey, - validValues: LinkModePreferenceImpl.values.map((e) => '$e'), - ), - ); - }, - (config) { - _dependencyMetadata = _readDependencyMetadataFromConfig(config); - }, - (config) => _supportedAssetTypes = - config.optionalStringList(supportedAssetTypesKey) ?? - [NativeCodeAsset.type], - ]; + static BuildConfigImpl _readFieldsFromConfig(Config config) { + final dryRun = HookConfigImpl.parseDryRun(config) ?? false; + final targetOS = HookConfigImpl.parseTargetOS(config); + return BuildConfigImpl( + outputDirectory: HookConfigImpl.parseOutDir(config), + packageName: HookConfigImpl.parsePackageName(config), + packageRoot: HookConfigImpl.parsePackageRoot(config), + buildMode: HookConfigImpl.parseBuildMode(config, dryRun), + targetOS: targetOS, + targetArchitecture: + HookConfigImpl.parseTargetArchitecture(config, dryRun, targetOS), + linkModePreference: HookConfigImpl.parseLinkModePreference(config), + dependencyMetadata: parseDependencyMetadata(config), + version: HookConfigImpl.parseVersion(config), + cCompiler: HookConfigImpl.parseCCompiler(config, dryRun), + supportedAssetTypes: HookConfigImpl.parseSupportedAssetTypes(config), + targetAndroidNdkApi: + HookConfigImpl.parseTargetAndroidNdkApi(config, dryRun, targetOS), + targetIOSSdk: HookConfigImpl.parseTargetIOSSdk(config, dryRun, targetOS), + dryRun: dryRun, + ); } - Map? _readDependencyMetadataFromConfig(Config config) { + static Map? parseDependencyMetadata(Config config) => + _readDependencyMetadataFromConfig(config); + + static Map? _readDependencyMetadataFromConfig( + Config config) { final fileValue = config.valueOf?>(dependencyMetadataConfigKey); if (fileValue == null) { return null; } - final result = {}; - for (final entry in fileValue.entries) { - final packageName = as(entry.key); - final defines = entry.value; - if (defines is! Map) { - throw FormatException("Unexpected value '$defines' for key " - "'$dependencyMetadataConfigKey.$packageName' in config file. " - 'Expected a Map.'); - } - final packageResult = {}; - for (final entry2 in defines.entries) { - final key = as(entry2.key); - final value = as(entry2.value); - packageResult[key] = value; - } - result[packageName] = Metadata(packageResult.sortOnKey()); - } - return result.sortOnKey(); - } - - Map toJson() { - late Map cCompilerJson; - if (!dryRun) { - cCompilerJson = _cCompiler.toJson(); - } - - return { - outDirConfigKey: _outDir.toFilePath(), - packageNameConfigKey: _packageName, - packageRootConfigKey: _packageRoot.toFilePath(), - OSImpl.configKey: _targetOS.toString(), - LinkModePreferenceImpl.configKey: _linkModePreference.toString(), - supportedAssetTypesKey: _supportedAssetTypes, - _versionKey: version.toString(), - if (dryRun) dryRunConfigKey: dryRun, - if (!dryRun) ...{ - BuildModeImpl.configKey: _buildMode.toString(), - ArchitectureImpl.configKey: _targetArchitecture.toString(), - if (_targetIOSSdk != null) - IOSSdkImpl.configKey: _targetIOSSdk.toString(), - if (_targetAndroidNdkApi != null) - targetAndroidNdkApiConfigKey: _targetAndroidNdkApi, - if (cCompilerJson.isNotEmpty) - CCompilerConfigImpl.configKey: cCompilerJson, - if (_dependencyMetadata != null && _dependencyMetadata.isNotEmpty) - dependencyMetadataConfigKey: { - for (final entry in _dependencyMetadata.entries) - entry.key: entry.value.toJson(), - }, + return fileValue + .map((key, defines) => MapEntry(as(key), defines)) + .map( + (packageName, defines) { + if (defines is! Map) { + throw FormatException("Unexpected value '$defines' for key " + "'$dependencyMetadataConfigKey.$packageName' in config file. " + 'Expected a Map.'); + } + return MapEntry( + packageName, + Metadata(defines + .map((key, value) => MapEntry(as(key), as(value))) + .sortOnKey()), + ); }, - }.sortOnKey(); + ).sortOnKey(); } - String toJsonString() => const JsonEncoder.withIndent(' ').convert(toJson()); + static BuildConfigImpl fromJson(Map buildConfigJson) => + BuildConfigImpl._fromConfig(Config(fileParsed: buildConfigJson)); + + @override + Map toJson() => { + ...hookToJson(), + if (!dryRun) ...{ + if (_dependencyMetadata != null && _dependencyMetadata.isNotEmpty) + dependencyMetadataConfigKey: _dependencyMetadata.map( + (packageName, metadata) => + MapEntry(packageName, metadata.toJson()), + ), + }, + }.sortOnKey(); @override bool operator ==(Object other) { + if (super != other) { + return false; + } if (other is! BuildConfigImpl) { return false; } - if (other.outputDirectory != outputDirectory) return false; - if (other.packageName != packageName) return false; - if (other.packageRoot != packageRoot) return false; - if (other.dryRun != dryRun) return false; - if (other.targetOS != targetOS) return false; - if (other.linkModePreference != linkModePreference) return false; - if (!const DeepCollectionEquality() - .equals(other._supportedAssetTypes, _supportedAssetTypes)) return false; - if (!dryRun) { - if (other.buildMode != buildMode) return false; - if (other.targetArchitecture != targetArchitecture) return false; - if (targetOS == OS.iOS && other.targetIOSSdk != targetIOSSdk) { - return false; - } - if (other.targetAndroidNdkApi != targetAndroidNdkApi) return false; - if (other.cCompiler != cCompiler) return false; - if (!const DeepCollectionEquality() - .equals(other._dependencyMetadata, _dependencyMetadata)) return false; + if (!dryRun && + !const DeepCollectionEquality() + .equals(other._dependencyMetadata, _dependencyMetadata)) { + return false; } return true; } @override int get hashCode => Object.hashAll([ - outputDirectory, - packageName, - packageRoot, - targetOS, + super.hashCode, linkModePreference, - dryRun, - const DeepCollectionEquality().hash(_supportedAssetTypes), if (!dryRun) ...[ - buildMode, const DeepCollectionEquality().hash(_dependencyMetadata), - targetArchitecture, - if (targetOS == OS.iOS) targetIOSSdk, - targetAndroidNdkApi, - cCompiler, ], ]); @override - String toString() => 'BuildConfig.build(${toJson()})'; - - void _ensureNotDryRun() { - if (dryRun) { - throw StateError('''This field is not available in dry runs. -In Flutter projects, native builds are generated per OS which target multiple -architectures, build modes, etc. Therefore, the list of native assets produced -can _only_ depend on OS.'''); - } - } - - void _throwIfNotNullInDryRun(String key) { - final object = config.valueOf(key); - if (object != null) { - throw const FormatException('''This field is not available in dry runs. -In Flutter projects, native builds are generated per OS which target multiple -architectures, build modes, etc. Therefore, the list of native assets produced -can _only_ depend on OS.'''); - } - } + String toString() => 'BuildConfig(${toJson()})'; } diff --git a/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md b/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md index 3f11f16d7..6d4c5b415 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md +++ b/pkgs/native_assets_cli/lib/src/model/build_config_CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.3.0 + +- Rev version to know whether the Dart/Flutter SDK can consume + `BuildOutput.assetsForLinking`. In earlier versions the key will not be read + and the list of assets to be linked will be empty. + ## 1.2.0 - Changed default encoding to JSON. diff --git a/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md b/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md index 17cbf88c8..9e58c5c8d 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md +++ b/pkgs/native_assets_cli/lib/src/model/build_output_CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.3.0 + +- Add key for assets to be linked. + Backwards compatibility older build hooks: The key can be omitted. + Backwards compatibility older SDKs: These are ignored on older SDKs. + ## 1.2.0 - Changed default encoding to JSON. diff --git a/pkgs/native_assets_cli/lib/src/model/data_asset.dart b/pkgs/native_assets_cli/lib/src/model/data_asset.dart index 3de286b8f..9a05e7c97 100644 --- a/pkgs/native_assets_cli/lib/src/model/data_asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/data_asset.dart @@ -9,17 +9,25 @@ final class DataAssetImpl implements DataAsset, AssetImpl { final Uri file; @override - final String id; + final String name; + + @override + final String package; + + @override + String get id => 'package:$package/$name'; DataAssetImpl({ required this.file, - required this.id, + required this.name, + required this.package, }); factory DataAssetImpl.fromJson(Map jsonMap) => DataAssetImpl( - id: as(jsonMap[_idKey]), - file: Uri(path: as(jsonMap[_fileKey])), + name: get(jsonMap, _nameKey), + package: get(jsonMap, _packageKey), + file: Uri(path: get(jsonMap, _fileKey)), ); @override @@ -27,24 +35,27 @@ final class DataAssetImpl implements DataAsset, AssetImpl { if (other is! DataAssetImpl) { return false; } - return other.id == id && other.file == file; + return other.package == package && other.file == file && other.name == name; } @override int get hashCode => Object.hash( - id, + package, + name, file, ); @override Map toJson(Version version) => { - _idKey: id, + _nameKey: name, + _packageKey: package, _fileKey: file.toFilePath(), typeKey: DataAsset.type, }..sortOnKey(); static const typeKey = 'type'; - static const _idKey = 'id'; + static const _nameKey = 'name'; + static const _packageKey = 'package'; static const _fileKey = 'file'; @override diff --git a/pkgs/native_assets_cli/lib/src/model/dependencies.dart b/pkgs/native_assets_cli/lib/src/model/dependencies.dart index 42dec07a4..51d29c0fb 100644 --- a/pkgs/native_assets_cli/lib/src/model/dependencies.dart +++ b/pkgs/native_assets_cli/lib/src/model/dependencies.dart @@ -22,7 +22,9 @@ class Dependencies { fileSystemPathToUri(as(dependency)), ]); - List toJson() => [ + List toJson() => toJsonList(dependencies); + + static List toJsonList(List dependencies) => [ for (final dependency in dependencies) dependency.toFilePath(), ]; diff --git a/pkgs/native_assets_cli/lib/src/model/hook.dart b/pkgs/native_assets_cli/lib/src/model/hook.dart new file mode 100644 index 000000000..28859e8b3 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/hook.dart @@ -0,0 +1,20 @@ +// 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. + +/// The two types of scripts which are hooked into the compilation process. +/// +/// The `build.dart` hook runs before, and the `link.dart` hook after +/// compilation. This enum holds static information about these hooks. +enum Hook { + link('link', 'link_config'), + build('build', 'config'); + + final String _scriptName; + final String _configName; + + String get scriptName => '$_scriptName.dart'; + String get configName => '$_configName.json'; + + const Hook(this._scriptName, this._configName); +} diff --git a/pkgs/native_assets_cli/lib/src/model/hook_config.dart b/pkgs/native_assets_cli/lib/src/model/hook_config.dart new file mode 100644 index 000000000..84493b676 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/hook_config.dart @@ -0,0 +1,477 @@ +// 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. + +part of '../api/hook_config.dart'; + +abstract class HookConfigImpl implements HookConfig { + final Hook hook; + + /// The folder in which all output and intermediate artifacts should be + /// placed. + @override + final Uri outputDirectory; + + @override + final String packageName; + + @override + final Uri packageRoot; + + final Version version; + + final BuildModeImpl? _buildMode; + + @override + BuildModeImpl get buildMode { + ensureNotDryRun(dryRun); + return _buildMode!; + } + + final CCompilerConfigImpl _cCompiler; + + @override + CCompilerConfigImpl get cCompiler { + ensureNotDryRun(dryRun); + return _cCompiler; + } + + @override + final bool dryRun; + + @override + final Iterable supportedAssetTypes; + + final int? _targetAndroidNdkApi; + + @override + final LinkModePreferenceImpl linkModePreference; + + @override + int? get targetAndroidNdkApi { + ensureNotDryRun(dryRun); + return _targetAndroidNdkApi; + } + + @override + final ArchitectureImpl? targetArchitecture; + + final IOSSdkImpl? _targetIOSSdk; + + @override + IOSSdkImpl? get targetIOSSdk { + ensureNotDryRun(dryRun); + if (targetOS != OS.iOS) { + throw StateError( + 'This field is not available in if targetOS is not OS.iOS.', + ); + } + return _targetIOSSdk; + } + + @override + final OSImpl targetOS; + + String get outputName; + + HookConfigImpl({ + required this.hook, + required this.outputDirectory, + required this.packageName, + required this.packageRoot, + required this.version, + required BuildModeImpl? buildMode, + required CCompilerConfigImpl? cCompiler, + required this.supportedAssetTypes, + required int? targetAndroidNdkApi, + required this.targetArchitecture, + required IOSSdkImpl? targetIOSSdk, + required this.linkModePreference, + required this.targetOS, + bool? dryRun, + }) : _targetAndroidNdkApi = targetAndroidNdkApi, + _targetIOSSdk = targetIOSSdk, + _buildMode = buildMode, + _cCompiler = cCompiler ?? CCompilerConfigImpl(), + dryRun = dryRun ?? false; + + HookConfigImpl.dryRun({ + required this.hook, + required this.outputDirectory, + required this.packageName, + required this.packageRoot, + required this.version, + required this.supportedAssetTypes, + required this.linkModePreference, + required this.targetOS, + }) : _cCompiler = CCompilerConfigImpl(), + dryRun = true, + targetArchitecture = null, + _buildMode = null, + _targetAndroidNdkApi = null, + _targetIOSSdk = null; + + Uri get configFile => outputDirectory.resolve('../${hook.configName}'); + + Uri get outputFile => outputDirectory.resolve(outputName); + + // This is currently overriden by [BuildConfig], do account for older versions + // still using a top-level build.dart. + Uri get script => packageRoot.resolve('hook/').resolve(hook.scriptName); + + String toJsonString() => const JsonEncoder.withIndent(' ').convert(toJson()); + + static const outDirConfigKey = 'out_dir'; + static const packageNameConfigKey = 'package_name'; + static const packageRootConfigKey = 'package_root'; + static const _versionKey = 'version'; + static const targetAndroidNdkApiConfigKey = 'target_android_ndk_api'; + static const dryRunConfigKey = 'dry_run'; + static const supportedAssetTypesKey = 'supported_asset_types'; + + Map toJson(); + + Map hookToJson() { + late Map cCompilerJson; + if (!dryRun) { + cCompilerJson = cCompiler.toJson(); + } + + return { + outDirConfigKey: outputDirectory.toFilePath(), + packageNameConfigKey: packageName, + packageRootConfigKey: packageRoot.toFilePath(), + OSImpl.configKey: targetOS.toString(), + if (supportedAssetTypes.isNotEmpty) + supportedAssetTypesKey: supportedAssetTypes, + _versionKey: version.toString(), + if (dryRun) dryRunConfigKey: dryRun, + if (!dryRun) ...{ + BuildModeImpl.configKey: buildMode.toString(), + ArchitectureImpl.configKey: targetArchitecture.toString(), + if (targetOS == OS.iOS && targetIOSSdk != null) + IOSSdkImpl.configKey: targetIOSSdk.toString(), + if (targetAndroidNdkApi != null) + targetAndroidNdkApiConfigKey: targetAndroidNdkApi!, + if (cCompilerJson.isNotEmpty) + CCompilerConfigImpl.configKey: cCompilerJson, + }, + LinkModePreferenceImpl.configKey: linkModePreference.toString(), + }.sortOnKey(); + } + + static Version parseVersion(Config config) { + final version = Version.parse(config.string('version')); + if (version.major > latestVersion.major) { + throw FormatException( + 'The config version $version is newer than this ' + 'package:native_assets_cli config version $latestVersion, ' + 'please update native_assets_cli.', + ); + } + if (version.major < latestVersion.major) { + throw FormatException( + 'The config version $version is newer than this ' + 'package:native_assets_cli config version $latestVersion, ' + 'please update the Dart or Flutter SDK.', + ); + } + return version; + } + + static bool? parseDryRun(Config config) => + config.optionalBool(dryRunConfigKey); + + static Uri parseOutDir(Config config) => + config.path(outDirConfigKey, mustExist: true); + + static String parsePackageName(Config config) => + config.string(packageNameConfigKey); + + static Uri parsePackageRoot(Config config) => + config.path(packageRootConfigKey, mustExist: true); + + static BuildModeImpl? parseBuildMode(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, BuildModeImpl.configKey); + return null; + } else { + return BuildModeImpl.fromString( + config.string( + BuildModeImpl.configKey, + validValues: BuildModeImpl.values.map((e) => '$e'), + ), + ); + } + } + + static LinkModePreferenceImpl parseLinkModePreference(Config config) => + LinkModePreferenceImpl.fromString( + config.string( + LinkModePreferenceImpl.configKey, + validValues: LinkModePreferenceImpl.values.map((e) => e.toString()), + ), + ); + + static OSImpl parseTargetOS(Config config) => OSImpl.fromString( + config.string( + OSImpl.configKey, + validValues: OSImpl.values.map((e) => '$e'), + ), + ); + static ArchitectureImpl? parseTargetArchitecture( + Config config, + bool dryRun, + OSImpl? targetOS, + ) { + if (dryRun) { + _throwIfNotNullInDryRun(config, ArchitectureImpl.configKey); + return null; + } else { + final validArchitectures = [ + if (targetOS == null) + ...ArchitectureImpl.values + else + for (final target in Target.values) + if (target.os == targetOS) target.architecture + ]; + return ArchitectureImpl.fromString( + config.string( + ArchitectureImpl.configKey, + validValues: validArchitectures.map((e) => '$e'), + ), + ); + } + } + + static IOSSdkImpl? parseTargetIOSSdk( + Config config, bool dryRun, OSImpl? targetOS) { + if (dryRun) { + _throwIfNotNullInDryRun(config, IOSSdkImpl.configKey); + return null; + } else { + return targetOS == OSImpl.iOS + ? IOSSdkImpl.fromString( + config.string( + IOSSdkImpl.configKey, + validValues: IOSSdkImpl.values.map((e) => '$e'), + ), + ) + : null; + } + } + + static int? parseTargetAndroidNdkApi( + Config config, + bool dryRun, + OSImpl? targetOS, + ) { + if (dryRun) { + _throwIfNotNullInDryRun(config, targetAndroidNdkApiConfigKey); + return null; + } else { + return (targetOS == OSImpl.android) + ? config.int(targetAndroidNdkApiConfigKey) + : null; + } + } + + static Uri? _parseArchiver(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.arConfigKeyFull); + return null; + } else { + return config.optionalPath( + CCompilerConfigImpl.arConfigKeyFull, + mustExist: true, + ); + } + } + + static Uri? _parseCompiler(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return config.optionalPath( + CCompilerConfigImpl.ccConfigKeyFull, + mustExist: true, + ); + } + } + + static Uri? _parseLinker(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return config.optionalPath( + CCompilerConfigImpl.ldConfigKeyFull, + mustExist: true, + ); + } + } + + static Uri? _parseEnvScript(Config config, bool dryRun, Uri? compiler) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return (compiler != null && compiler.toFilePath().endsWith('cl.exe')) + ? config.path(CCompilerConfigImpl.envScriptConfigKeyFull, + mustExist: true) + : null; + } + } + + static List? _parseEnvScriptArgs(Config config, bool dryRun) { + if (dryRun) { + _throwIfNotNullInDryRun(config, CCompilerConfigImpl.ccConfigKeyFull); + return null; + } else { + return config.optionalStringList( + CCompilerConfigImpl.envScriptArgsConfigKeyFull, + splitEnvironmentPattern: ' ', + ); + } + } + + static List parseSupportedAssetTypes(Config config) => + config.optionalStringList(supportedAssetTypesKey) ?? + [NativeCodeAsset.type]; + + static CCompilerConfigImpl parseCCompiler(Config config, bool dryRun) { + final parseCompiler = _parseCompiler(config, dryRun); + final cCompiler = CCompilerConfigImpl( + archiver: _parseArchiver(config, dryRun), + compiler: parseCompiler, + envScript: _parseEnvScript(config, dryRun, parseCompiler), + envScriptArgs: _parseEnvScriptArgs(config, dryRun), + linker: _parseLinker(config, dryRun), + ); + return cCompiler; + } + + static void _throwIfNotNullInDryRun(Config config, String key) { + final object = config.valueOf(key); + if (object != null) { + throw const FormatException('''This field is not available in dry runs. +In Flutter projects, native builds are generated per OS which target multiple +architectures, build modes, etc. Therefore, the list of native assets produced +can _only_ depend on OS.'''); + } + } + + static void ensureNotDryRun(bool dryRun) { + if (dryRun) { + throw StateError('''This field is not available in dry runs. +In Flutter projects, native builds are generated per OS which target multiple +architectures, build modes, etc. Therefore, the list of native assets produced +can _only_ depend on OS.'''); + } + } + + @override + bool operator ==(Object other) { + if (other is! HookConfigImpl) { + return false; + } + if (other.outputDirectory != outputDirectory) return false; + if (other.packageName != packageName) return false; + if (other.packageRoot != packageRoot) return false; + if (other.dryRun != dryRun) return false; + if (other.targetOS != targetOS) return false; + if (other.linkModePreference != linkModePreference) return false; + if (!const DeepCollectionEquality() + .equals(other.supportedAssetTypes, supportedAssetTypes)) return false; + if (!dryRun) { + if (other.buildMode != buildMode) return false; + if (other.targetArchitecture != targetArchitecture) return false; + if (targetOS == OS.iOS && other.targetIOSSdk != targetIOSSdk) { + return false; + } + if (other.targetAndroidNdkApi != targetAndroidNdkApi) return false; + if (other.cCompiler != cCompiler) return false; + } + return true; + } + + @override + int get hashCode => Object.hashAll([ + outputDirectory, + packageName, + packageRoot, + targetOS, + dryRun, + if (!dryRun) ...[ + buildMode, + targetArchitecture, + if (targetOS == OS.iOS) targetIOSSdk, + targetAndroidNdkApi, + cCompiler, + ], + ]); + + /// Constructs a checksum for a [BuildConfigImpl] based on the fields of a + /// buildconfig that influence the build. + /// + /// This can be used for an [outputDirectory], but should not be used for + /// dry-runs. + /// + /// In particular, it only takes the package name from [packageRoot], so that + /// the hash is equal across checkouts and ignores [outputDirectory] itself. + static String checksum({ + required String packageName, + required Uri packageRoot, + required ArchitectureImpl targetArchitecture, + required OSImpl targetOS, + required BuildModeImpl buildMode, + IOSSdkImpl? targetIOSSdk, + int? targetAndroidNdkApi, + CCompilerConfigImpl? cCompiler, + required LinkModePreferenceImpl linkModePreference, + Map? dependencyMetadata, + Iterable? supportedAssetTypes, + Version? version, + required Hook hook, + }) { + final input = [ + version ?? latestVersion, + packageName, + targetArchitecture.toString(), + targetOS.toString(), + targetIOSSdk.toString(), + targetAndroidNdkApi.toString(), + buildMode.toString(), + linkModePreference.toString(), + cCompiler?.archiver.toString(), + cCompiler?.compiler.toString(), + cCompiler?.envScript.toString(), + cCompiler?.envScriptArgs.toString(), + cCompiler?.linker.toString(), + if (dependencyMetadata != null) + for (final entry in dependencyMetadata.entries) ...[ + entry.key, + json.encode(entry.value.toJson()), + ], + ...supportedAssetTypes ?? [NativeCodeAsset.type], + hook.name, + ].join('###'); + final sha256String = sha256.convert(utf8.encode(input)).toString(); + // 256 bit hashes lead to 64 hex character strings. + // To avoid overflowing file paths limits, only use 32. + // Using 16 hex characters would also be unlikely to have collisions. + const nameLength = 32; + return sha256String.substring(0, nameLength); + } + + /// The version of [HookConfigImpl]. + /// + /// This class is used in the protocol between the Dart and Flutter SDKs + /// and packages through build hook invocations. + /// + /// If we ever were to make breaking changes, it would be useful to give + /// proper error messages rather than just fail to parse the JSON + /// representation in the protocol. + static Version latestVersion = Version(1, 3, 0); +} diff --git a/pkgs/native_assets_cli/lib/src/model/build_output.dart b/pkgs/native_assets_cli/lib/src/model/hook_output.dart similarity index 58% rename from pkgs/native_assets_cli/lib/src/model/build_output.dart rename to pkgs/native_assets_cli/lib/src/model/hook_output.dart index 0da94f94a..c8ddfe3c2 100644 --- a/pkgs/native_assets_cli/lib/src/model/build_output.dart +++ b/pkgs/native_assets_cli/lib/src/model/hook_output.dart @@ -4,7 +4,7 @@ part of '../api/build_output.dart'; -final class BuildOutputImpl implements BuildOutput { +final class HookOutputImpl implements BuildOutput, LinkOutput { @override final DateTime timestamp; @@ -13,6 +13,11 @@ final class BuildOutputImpl implements BuildOutput { @override Iterable get assets => _assets; + final Map> _assetsForLinking; + + @override + Map> get assetsForLinking => _assetsForLinking; + final Dependencies _dependencies; Dependencies get dependenciesModel => _dependencies; @@ -20,19 +25,21 @@ final class BuildOutputImpl implements BuildOutput { @override Iterable get dependencies => _dependencies.dependencies; - final Metadata _metadata; + final Metadata metadata; - BuildOutputImpl({ + HookOutputImpl({ DateTime? timestamp, List? assets, + Map>? assetsForLinking, Dependencies? dependencies, Metadata? metadata, }) : timestamp = (timestamp ?? DateTime.now()).roundDownToSeconds(), _assets = assets ?? [], + _assetsForLinking = assetsForLinking ?? {}, // ignore: prefer_const_constructors _dependencies = dependencies ?? Dependencies([]), // ignore: prefer_const_constructors - _metadata = metadata ?? Metadata({}); + metadata = metadata ?? Metadata({}); @override void addDependency(Uri dependency) => @@ -43,12 +50,13 @@ final class BuildOutputImpl implements BuildOutput { _dependencies.dependencies.addAll(dependencies); static const _assetsKey = 'assets'; + static const _assetsForLinkingKey = 'assetsForLinking'; static const _dependenciesKey = 'dependencies'; static const _metadataKey = 'metadata'; static const _timestampKey = 'timestamp'; static const _versionKey = 'version'; - factory BuildOutputImpl.fromJsonString(String jsonString) { + factory HookOutputImpl.fromJsonString(String jsonString) { final Object? json; if (jsonString.startsWith('{')) { json = jsonDecode(jsonString); @@ -57,11 +65,11 @@ final class BuildOutputImpl implements BuildOutput { // remove the YAML fallback. json = loadYaml(jsonString); } - return BuildOutputImpl.fromJson(as>(json)); + return HookOutputImpl.fromJson(as>(json)); } - factory BuildOutputImpl.fromJson(Map jsonMap) { - final outputVersion = Version.parse(as(jsonMap['version'])); + factory HookOutputImpl.fromJson(Map jsonMap) { + final outputVersion = Version.parse(get(jsonMap, 'version')); if (outputVersion.major > latestVersion.major) { throw FormatException( 'The output version $outputVersion is newer than the ' @@ -76,17 +84,17 @@ final class BuildOutputImpl implements BuildOutput { 'Flutter, please update native_assets_cli.', ); } - - final assets = - AssetImpl.listFromJsonList(as>(jsonMap[_assetsKey])); - - return BuildOutputImpl( - timestamp: DateTime.parse(as(jsonMap[_timestampKey])), - assets: assets, + return HookOutputImpl( + timestamp: DateTime.parse(get(jsonMap, _timestampKey)), + assets: AssetImpl.listFromJson(get?>(jsonMap, _assetsKey)), + assetsForLinking: get?>( + jsonMap, _assetsForLinkingKey) + ?.map((packageName, assets) => MapEntry( + packageName, AssetImpl.listFromJson(as>(assets)))), dependencies: - Dependencies.fromJson(as?>(jsonMap[_dependenciesKey])), + Dependencies.fromJson(get?>(jsonMap, _dependenciesKey)), metadata: - Metadata.fromJson(as?>(jsonMap[_metadataKey])), + Metadata.fromJson(get?>(jsonMap, _metadataKey)), ); } @@ -111,14 +119,24 @@ final class BuildOutputImpl implements BuildOutput { assets.add(asset); } } + final linkMinVersion = Version(1, 3, 0); + if (_assetsForLinking.isNotEmpty && version < linkMinVersion) { + throw UnsupportedError('Please update your Dart or Flutter SDK to link ' + 'assets in `link.dart` scripts. Your current version is $version, ' + 'but this feature requires $linkMinVersion'); + } return { _timestampKey: timestamp.toString(), - _assetsKey: [ - for (final asset in assets) asset.toJson(version), - ], + if (assets.isNotEmpty) _assetsKey: AssetImpl.listToJson(assets, version), + if (version >= linkMinVersion && _assetsForLinking.isNotEmpty) + _assetsForLinkingKey: + _assetsForLinking.map((packageName, assets) => MapEntry( + packageName, + AssetImpl.listToJson(assets, version), + )), if (_dependencies.dependencies.isNotEmpty) _dependenciesKey: _dependencies.toJson(), - _metadataKey: _metadata.toJson(), + if (metadata.metadata.isNotEmpty) _metadataKey: metadata.toJson(), _versionKey: version.toString(), }..sortOnKey(); } @@ -126,7 +144,7 @@ final class BuildOutputImpl implements BuildOutput { String toJsonString(Version version) => const JsonEncoder.withIndent(' ').convert(toJson(version)); - /// The version of [BuildOutputImpl]. + /// The version of [HookOutputImpl]. /// /// This class is used in the protocol between the Dart and Flutter SDKs and /// packages through build hook invocations. @@ -140,56 +158,38 @@ final class BuildOutputImpl implements BuildOutput { /// version of the Dart or Flutter SDK. When there is a need to split the /// versions of BuildConfig and BuildOutput, the BuildConfig should start /// passing the highest supported version of BuildOutput. - static Version latestVersion = BuildConfigImpl.latestVersion; - - static const fileName = 'build_output.json'; - static const fileNameV1_1_0 = 'build_output.yaml'; - - /// Reads the JSON file from [outDir]/[fileName]. - static Future readFromFile({required Uri outDir}) async { - final buildOutputUri = outDir.resolve(fileName); - final buildOutputFile = File.fromUri(buildOutputUri); - if (await buildOutputFile.exists()) { - return BuildOutputImpl.fromJsonString( - await buildOutputFile.readAsString()); - } + static Version latestVersion = HookConfigImpl.latestVersion; - final buildOutputUriV1_1_0 = outDir.resolve(fileNameV1_1_0); - final buildOutputFileV1_1_0 = File.fromUri(buildOutputUriV1_1_0); - if (await buildOutputFileV1_1_0.exists()) { - return BuildOutputImpl.fromJsonString( - await buildOutputFileV1_1_0.readAsString()); + /// Writes the JSON file from [file]. + static HookOutputImpl? readFromFile({required Uri file}) { + final buildOutputFile = File.fromUri(file); + if (buildOutputFile.existsSync()) { + return HookOutputImpl.fromJsonString(buildOutputFile.readAsStringSync()); } return null; } - /// Writes the [toJsonString] to [BuildConfig.outputDirectory]/[fileName]. - Future writeToFile({required BuildConfig config}) async { - final configVersion = (config as BuildConfigImpl).version; - final outDir = config.outputDirectory; - final Uri buildOutputUri; - if (configVersion <= Version(1, 1, 0)) { - buildOutputUri = outDir.resolve(fileNameV1_1_0); - } else { - buildOutputUri = outDir.resolve(fileName); - } + /// Writes the [toJsonString] to the output file specified in the [config]. + Future writeToFile({required HookConfigImpl config}) async { + final configVersion = config.version; final jsonString = toJsonString(configVersion); - await File.fromUri(buildOutputUri).writeAsStringCreateDirectory(jsonString); + await File.fromUri(config.outputFile) + .writeAsStringCreateDirectory(jsonString); } @override - String toString() => toJsonString(BuildConfigImpl.latestVersion); + String toString() => toJsonString(HookConfigImpl.latestVersion); @override bool operator ==(Object other) { - if (other is! BuildOutputImpl) { + if (other is! HookOutputImpl) { return false; } return other.timestamp == timestamp && const ListEquality().equals(other._assets, _assets) && other._dependencies == _dependencies && - other._metadata == _metadata; + other.metadata == metadata; } @override @@ -197,28 +197,32 @@ final class BuildOutputImpl implements BuildOutput { timestamp.hashCode, const ListEquality().hash(_assets), _dependencies, - _metadata, + metadata, ); @override void addMetadatum(String key, Object value) { - _metadata.metadata[key] = value; + metadata.metadata[key] = value; } @override void addMetadata(Map metadata) { - _metadata.metadata.addAll(metadata); + this.metadata.metadata.addAll(metadata); } - Metadata get metadataModel => _metadata; + Metadata get metadataModel => metadata; @override - void addAsset(Asset asset) { - _assets.add(asset as AssetImpl); + void addAsset(Asset asset, {String? linkInPackage}) { + _getAssetList(linkInPackage).add(asset as AssetImpl); } @override - void addAssets(Iterable assets) { - _assets.addAll(assets.cast()); + void addAssets(Iterable assets, {String? linkInPackage}) { + _getAssetList(linkInPackage).addAll(assets.cast()); } + + List _getAssetList(String? linkInPackage) => linkInPackage == null + ? _assets + : (_assetsForLinking[linkInPackage] ??= []); } diff --git a/pkgs/native_assets_cli/lib/src/model/link_config.dart b/pkgs/native_assets_cli/lib/src/model/link_config.dart new file mode 100644 index 000000000..20051fffa --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/link_config.dart @@ -0,0 +1,146 @@ +// 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. + +part of '../api/link_config.dart'; + +/// The input to the linking script. +/// +/// It consists of the fields inherited from the [HookConfig] and the [assets] +/// from the build step. +class LinkConfigImpl extends HookConfigImpl implements LinkConfig { + static const resourceIdentifierKey = 'resource_identifiers'; + + static const assetsKey = 'assets'; + + @override + final Iterable assets; + + // TODO: Placeholder for the resources.json file URL. We don't want to change + // native_assets_builder when implementing the parsing. + final Uri? _resourceIdentifierUri; + + LinkConfigImpl({ + required this.assets, + Uri? resourceIdentifierUri, + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + Version? version, + required super.buildMode, + super.cCompiler, + Iterable? supportedAssetTypes, + super.targetAndroidNdkApi, + super.targetArchitecture, + super.targetIOSSdk, + required super.targetOS, + required super.linkModePreference, + super.dryRun, + }) : _resourceIdentifierUri = resourceIdentifierUri, + super( + hook: Hook.link, + version: version ?? HookConfigImpl.latestVersion, + supportedAssetTypes: supportedAssetTypes ?? [NativeCodeAsset.type], + ); + + LinkConfigImpl.dryRun({ + required this.assets, + Uri? resourceIdentifierUri, + required super.outputDirectory, + required super.packageName, + required super.packageRoot, + Version? version, + Iterable? supportedAssetTypes, + required super.linkModePreference, + required super.targetOS, + }) : _resourceIdentifierUri = resourceIdentifierUri, + super.dryRun( + hook: Hook.link, + version: version ?? HookConfigImpl.latestVersion, + supportedAssetTypes: supportedAssetTypes ?? [NativeCodeAsset.type], + ); + + @override + Hook get hook => Hook.link; + @override + String get outputName => 'link_output.json'; + + @override + Map toJson() => { + ...hookToJson(), + if (_resourceIdentifierUri != null) + resourceIdentifierKey: _resourceIdentifierUri.toFilePath(), + assetsKey: AssetImpl.listToJson(assets, version), + }.sortOnKey(); + + static LinkConfig fromArguments(List arguments) { + final argParser = ArgParser()..addOption('config'); + + final results = argParser.parse(arguments); + final linkConfigContents = + File(results['config'] as String).readAsStringSync(); + final linkConfigJson = + jsonDecode(linkConfigContents) as Map; + + return fromJson(linkConfigJson); + } + + static LinkConfigImpl fromJson(Map linkConfigJson) { + final config = + Config.fromConfigFileContents(fileContents: jsonEncode(linkConfigJson)); + final dryRun = HookConfigImpl.parseDryRun(config) ?? false; + final targetOS = HookConfigImpl.parseTargetOS(config); + return LinkConfigImpl( + outputDirectory: HookConfigImpl.parseOutDir(config), + packageName: HookConfigImpl.parsePackageName(config), + packageRoot: HookConfigImpl.parsePackageRoot(config), + buildMode: HookConfigImpl.parseBuildMode(config, dryRun), + targetOS: targetOS, + targetArchitecture: + HookConfigImpl.parseTargetArchitecture(config, dryRun, targetOS), + linkModePreference: HookConfigImpl.parseLinkModePreference(config), + version: HookConfigImpl.parseVersion(config), + cCompiler: HookConfigImpl.parseCCompiler(config, dryRun), + supportedAssetTypes: HookConfigImpl.parseSupportedAssetTypes(config), + targetAndroidNdkApi: + HookConfigImpl.parseTargetAndroidNdkApi(config, dryRun, targetOS), + targetIOSSdk: HookConfigImpl.parseTargetIOSSdk(config, dryRun, targetOS), + assets: parseAssets(config), + resourceIdentifierUri: parseResourceIdentifier(config), + dryRun: dryRun, + ); + } + + static Uri? parseResourceIdentifier(Config config) => + config.optionalPath(resourceIdentifierKey); + + static List parseAssets(Config config) => + AssetImpl.listFromJson(config.valueOf(assetsKey)); + + @override + bool operator ==(Object other) { + if (super != other) { + return false; + } + if (other is! LinkConfigImpl) { + return false; + } + if (other._resourceIdentifierUri != _resourceIdentifierUri) { + return false; + } + if (!const DeepCollectionEquality().equals(other.assets, assets)) { + return false; + } + return true; + } + + @override + int get hashCode => Object.hashAll([ + super.hashCode, + _resourceIdentifierUri, + const DeepCollectionEquality().hash(assets), + ]); + + @override + String toString() => 'LinkConfig(${toJson()})'; +} diff --git a/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart b/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart index 3073a5855..9863303ba 100644 --- a/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart +++ b/pkgs/native_assets_cli/lib/src/model/native_code_asset.dart @@ -49,8 +49,8 @@ abstract final class LinkModeImpl implements LinkMode { /// v1.1.0 and newer. factory LinkModeImpl.fromJson(Map jsonMap) { - final type = as(jsonMap[_typeKey]); - final uriString = as(jsonMap[_uriKey]); + final type = get(jsonMap, _typeKey); + final uriString = get(jsonMap, _uriKey); final uri = uriString != null ? Uri(path: uriString) : null; return LinkModeImpl(type, uri); } @@ -244,7 +244,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { linkMode = StaticLinkingImpl(); } else { assert(linkModeJson == DynamicLoadingImpl._typeValueV1_0_0); - final pathJson = as>(jsonMap[_pathKey]); + final pathJson = get>(jsonMap, _pathKey); final type = as(pathJson[DynamicLoadingImpl._pathTypeKeyV1_0_0]); final uriString = as(pathJson[DynamicLoadingImpl._uriKey]); @@ -256,7 +256,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { linkMode = LinkModeImpl.fromJson(as>(linkModeJson)); } - final fileString = as(jsonMap[_fileKey]); + final fileString = get(jsonMap, _fileKey); final Uri? file; if (fileString != null) { file = Uri(path: fileString); @@ -270,7 +270,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { } else { file = null; } - final targetString = as(jsonMap[_targetKey]); + final targetString = get(jsonMap, _targetKey); final ArchitectureImpl? architecture; final OSImpl os; if (targetString != null) { @@ -279,8 +279,8 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { os = target.os; architecture = target.architecture; } else { - os = OSImpl.fromString(as(jsonMap[_osKey])); - final architectureString = as(jsonMap[_architectureKey]); + os = OSImpl.fromString(get(jsonMap, _osKey)); + final architectureString = get(jsonMap, _architectureKey); if (architectureString != null) { architecture = ArchitectureImpl.fromString(architectureString); } else { @@ -289,7 +289,7 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { } return NativeCodeAssetImpl( - id: as(jsonMap[_idKey]), + id: get(jsonMap, _idKey), os: os, architecture: architecture, linkMode: linkMode, @@ -363,5 +363,5 @@ final class NativeCodeAssetImpl implements NativeCodeAsset, AssetImpl { @override String toString() => - 'NativeCodeAsset(${toJson(BuildOutputImpl.latestVersion)})'; + 'NativeCodeAsset(${toJson(HookOutputImpl.latestVersion)})'; } diff --git a/pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart b/pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart new file mode 100644 index 000000000..0ed06d619 --- /dev/null +++ b/pkgs/native_assets_cli/lib/src/model/resource_identifiers.dart @@ -0,0 +1,202 @@ +// 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. + +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; + +class ResourceIdentifiers { + final List identifiers; + + ResourceIdentifiers({required this.identifiers}); + + /// Read resources from a resources.json file + factory ResourceIdentifiers.fromFile(String path) => + ResourceIdentifiers.fromFileContents(File(path).readAsStringSync()); + + /// Read resources from the contents of a resources.json file + factory ResourceIdentifiers.fromFileContents(String fileContents) { + final fileJson = (jsonDecode(fileContents) as Map)['identifiers'] as List; + return ResourceIdentifiers( + identifiers: fileJson + .map((e) => e as Map) + .map(Identifier.fromJson) + .toList()); + } + + factory ResourceIdentifiers.fromJson(Map map) => + ResourceIdentifiers( + identifiers: List.from((map['identifiers'] as List?) + ?.whereType>() + .map(Identifier.fromJson) ?? + []), + ); + + Map toJson() => { + 'identifiers': identifiers.map((x) => x.toJson()).toList(), + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is ResourceIdentifiers && + listEquals(other.identifiers, identifiers); + } + + @override + int get hashCode => identifiers.hashCode; + + @override + String toString() => 'ResourceIdentifiers(identifiers: $identifiers)'; +} + +class Identifier { + final String name; + final String id; + final Uri uri; + final bool nonConstant; + final List files; + + Identifier({ + required this.name, + required this.id, + required this.uri, + required this.nonConstant, + required this.files, + }); + + Map toJson() => { + 'name': name, + 'id': id, + 'uri': uri.toFilePath(), + 'nonConstant': nonConstant, + 'files': files.map((x) => x.toJson()).toList(), + }; + + @override + String toString() => + '''Identifier(name: $name, id: $id, uri: $uri, nonConstant: $nonConstant, files: $files)'''; + + factory Identifier.fromJson(Map map) => Identifier( + name: map['name'] as String, + id: map['id'] as String, + uri: Uri.file(map['uri'] as String), + nonConstant: map['nonConstant'] as bool, + files: List.from((map['files'] as List) + .map((e) => e as Map) + .map(ResourceFile.fromJson)), + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is Identifier && + other.name == name && + other.id == id && + other.uri == uri && + other.nonConstant == nonConstant && + listEquals(other.files, files); + } + + @override + int get hashCode => + name.hashCode ^ + id.hashCode ^ + uri.hashCode ^ + nonConstant.hashCode ^ + files.hashCode; +} + +class ResourceFile { + final int part; + final List references; + + ResourceFile({required this.part, required this.references}); + + Map toJson() => { + 'part': part, + 'references': references.map((x) => x.toJson()).toList(), + }; + + factory ResourceFile.fromJson(Map map) => ResourceFile( + part: map['part'] as int, + references: List.from((map['references'] as List) + .map((e) => e as Map) + .map(ResourceReference.fromJson)), + ); + + @override + String toString() => 'ResourceFile(part: $part, references: $references)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is ResourceFile && + other.part == part && + listEquals(other.references, references); + } + + @override + int get hashCode => 95911 ^ part.hashCode ^ references.hashCode; +} + +class ResourceReference { + final Uri uri; + final int line; + final int column; + final Map arguments; + + ResourceReference({ + required this.uri, + required this.line, + required this.column, + required this.arguments, + }); + + Map toJson() => { + '@': { + 'uri': uri.toFilePath(), + 'line': line, + 'column': column, + }, + ...arguments, + }; + + @override + String toString() => + '''ResourceReference(uri: $uri, line: $line, column: $column, arguments: $arguments)'''; + + factory ResourceReference.fromJson(Map map) { + final submap = map['@'] as Map; + return ResourceReference( + uri: Uri.file(submap['uri'] as String), + line: submap['line'] as int, + column: submap['column'] as int, + arguments: Map.from(map)..remove('@'), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final mapEquals = const DeepCollectionEquality().equals; + + return other is ResourceReference && + other.uri == uri && + other.line == line && + other.column == column && + mapEquals(other.arguments, arguments); + } + + @override + int get hashCode => + uri.hashCode ^ line.hashCode ^ column.hashCode ^ arguments.hashCode; +} diff --git a/pkgs/native_assets_cli/lib/src/utils/json.dart b/pkgs/native_assets_cli/lib/src/utils/json.dart index bd18d97b8..096c7edf9 100644 --- a/pkgs/native_assets_cli/lib/src/utils/json.dart +++ b/pkgs/native_assets_cli/lib/src/utils/json.dart @@ -7,7 +7,19 @@ T as(Object? object) { return object; } throw FormatException( - "Unexpected value '$object' in JSON. Expected a $T.", + "Unexpected value '$object' of type ${object.runtimeType} in JSON. Expected" + ' a $T.', + ); +} + +T get(Map map, String key) { + final object = map[key]; + if (object is T) { + return object; + } + throw FormatException( + "Unexpected value '$object' of type ${object.runtimeType} in JSON for key " + '$key. Expected a $T.', ); } diff --git a/pkgs/native_assets_cli/pubspec.yaml b/pkgs/native_assets_cli/pubspec.yaml index e567de627..bba4a830e 100644 --- a/pkgs/native_assets_cli/pubspec.yaml +++ b/pkgs/native_assets_cli/pubspec.yaml @@ -4,7 +4,7 @@ description: >- native assets CLI. # Note: Bump BuildConfig.version and BuildOutput.version on breaking changes! -version: 0.5.4 +version: 0.6.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli topics: @@ -16,6 +16,7 @@ environment: sdk: '>=3.3.0 <4.0.0' dependencies: + args: ^2.4.2 cli_config: ^0.2.0 collection: ^1.17.1 crypto: ^3.0.3 diff --git a/pkgs/native_assets_cli/test/api/asset_test.dart b/pkgs/native_assets_cli/test/api/asset_test.dart index 606d775a5..c01772a9d 100644 --- a/pkgs/native_assets_cli/test/api/asset_test.dart +++ b/pkgs/native_assets_cli/test/api/asset_test.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'package:native_assets_cli/native_assets_cli.dart'; -import 'package:native_assets_cli/src/api/asset.dart'; import 'package:test/test.dart'; void main() { diff --git a/pkgs/native_assets_cli/test/api/build_config_test.dart b/pkgs/native_assets_cli/test/api/build_config_test.dart index ec58b0ecd..88f5724fe 100644 --- a/pkgs/native_assets_cli/test/api/build_config_test.dart +++ b/pkgs/native_assets_cli/test/api/build_config_test.dart @@ -4,7 +4,6 @@ import 'dart:io'; -import 'package:cli_config/cli_config.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_assets_cli/src/api/build_config.dart'; import 'package:test/test.dart'; @@ -107,7 +106,7 @@ void main() async { linkModePreference: LinkModePreference.preferStatic, ); - final config = Config(fileParsed: { + final config = { 'build_mode': 'release', 'dry_run': false, 'link_mode_preference': 'prefer-static', @@ -118,15 +117,15 @@ void main() async { 'target_architecture': 'arm64', 'target_os': 'android', 'version': BuildOutput.latestVersion.toString(), - }); + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig.dryRun', () { final buildConfig2 = BuildConfig.dryRun( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetOS: OS.android, @@ -134,7 +133,7 @@ void main() async { supportedAssetTypes: [NativeCodeAsset.type], ); - final config = Config(fileParsed: { + final config = { 'dry_run': true, 'link_mode_preference': 'prefer-static', 'out_dir': outDirUri.toFilePath(), @@ -142,9 +141,9 @@ void main() async { 'package_root': packageRootUri.toFilePath(), 'target_os': 'android', 'version': BuildOutput.latestVersion.toString(), - }); + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); diff --git a/pkgs/native_assets_cli/test/api/build_test.dart b/pkgs/native_assets_cli/test/api/build_test.dart index f9c4e5874..23c662d24 100644 --- a/pkgs/native_assets_cli/test/api/build_test.dart +++ b/pkgs/native_assets_cli/test/api/build_test.dart @@ -7,7 +7,6 @@ import 'dart:io'; import 'package:file_testing/file_testing.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_assets_cli/src/api/build_config.dart'; -import 'package:native_assets_cli/src/api/build_output.dart'; import 'package:test/test.dart'; void main() async { @@ -21,6 +20,7 @@ void main() async { late Uri fakeCl; late Uri fakeVcVars; late Uri buildConfigUri; + late BuildConfig config1; setUp(() async { tempUri = (await Directory.systemTemp.createTemp()).uri; @@ -40,7 +40,7 @@ void main() async { fakeVcVars = tempUri.resolve('vcvarsall.bat'); await File.fromUri(fakeVcVars).create(); - final config1 = BuildConfig.build( + config1 = BuildConfig.build( outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, @@ -65,7 +65,8 @@ void main() async { (config, output) async { output.addDependency(packageRootUri.resolve('foo')); }); - final buildOutputUri = outDirUri.resolve(BuildOutputImpl.fileName); + final buildOutputUri = + outDirUri.resolve((config1 as BuildConfigImpl).outputName); expect(File.fromUri(buildOutputUri), exists); }); } diff --git a/pkgs/native_assets_cli/test/api/link_config_test.dart b/pkgs/native_assets_cli/test/api/link_config_test.dart new file mode 100644 index 000000000..831278f75 --- /dev/null +++ b/pkgs/native_assets_cli/test/api/link_config_test.dart @@ -0,0 +1,179 @@ +// Copyright (c) 2023, 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:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_cli/src/api/link_config.dart'; +import 'package:test/test.dart'; + +void main() async { + late Uri tempUri; + late Uri outDirUri; + late Uri outDir2Uri; + late String packageName; + late Uri packageRootUri; + late Uri fakeClang; + late Uri fakeLd; + late Uri fakeAr; + late Uri fakeCl; + late Uri fakeVcVars; + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + outDirUri = tempUri.resolve('out1/'); + await Directory.fromUri(outDirUri).create(); + outDir2Uri = tempUri.resolve('out2/'); + packageName = 'my_package'; + await Directory.fromUri(outDir2Uri).create(); + packageRootUri = tempUri.resolve('$packageName/'); + await Directory.fromUri(packageRootUri).create(); + fakeClang = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClang).create(); + fakeLd = tempUri.resolve('fake_ld'); + await File.fromUri(fakeLd).create(); + fakeAr = tempUri.resolve('fake_ar'); + await File.fromUri(fakeAr).create(); + fakeCl = tempUri.resolve('cl.exe'); + await File.fromUri(fakeCl).create(); + fakeVcVars = tempUri.resolve('vcvarsall.bat'); + await File.fromUri(fakeVcVars).create(); + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + test('LinkConfig ==', () { + final config1 = LinkConfig.build( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.iOS, + targetIOSSdk: IOSSdk.iPhoneOS, + cCompiler: CCompilerConfig( + compiler: fakeClang, + linker: fakeLd, + archiver: fakeAr, + ), + buildMode: BuildMode.release, + supportedAssetTypes: [NativeCodeAsset.type], + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + final config2 = LinkConfig.build( + outputDirectory: outDir2Uri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.android, + targetAndroidNdkApi: 30, + buildMode: BuildMode.release, + supportedAssetTypes: [NativeCodeAsset.type], + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + expect(config1, equals(config1)); + expect(config1 == config2, false); + expect(config1.outputDirectory != config2.outputDirectory, true); + expect(config1.packageRoot, config2.packageRoot); + expect(config1.targetArchitecture == config2.targetArchitecture, true); + expect(config1.targetOS != config2.targetOS, true); + expect(config1.targetIOSSdk, IOSSdk.iPhoneOS); + expect(() => config2.targetIOSSdk, throwsStateError); + expect(config1.cCompiler.compiler != config2.cCompiler.compiler, true); + expect(config1.cCompiler.linker != config2.cCompiler.linker, true); + expect(config1.cCompiler.archiver != config2.cCompiler.archiver, true); + expect(config1.cCompiler.envScript == config2.cCompiler.envScript, true); + expect(config1.cCompiler.envScriptArgs == config2.cCompiler.envScriptArgs, + true); + expect(config1.cCompiler != config2.cCompiler, true); + expect(config1.supportedAssetTypes, config2.supportedAssetTypes); + }); + + test('LinkConfig fromConfig', () { + final linkConfig2 = LinkConfig.build( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.android, + targetAndroidNdkApi: 30, + buildMode: BuildMode.release, + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + final config = { + 'build_mode': 'release', + 'dry_run': false, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_android_ndk_api': 30, + 'target_architecture': 'arm64', + 'target_os': 'android', + 'version': BuildOutput.latestVersion.toString(), + 'assets': [], + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(linkConfig2)); + }); + + test('LinkConfig.dryRun', () { + final linkConfig2 = LinkConfig.dryRun( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetOS: OS.android, + supportedAssetTypes: [NativeCodeAsset.type], + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + + final config = { + 'dry_run': true, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_os': 'android', + 'version': BuildOutput.latestVersion.toString(), + 'assets': [], + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(linkConfig2)); + }); + + test('LinkConfig fromArgs', () async { + final linkConfig = LinkConfig.build( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: Architecture.arm64, + targetOS: OS.android, + targetAndroidNdkApi: 30, + buildMode: BuildMode.release, + assets: [], + linkModePreference: LinkModePreference.preferStatic, + ); + final configFileContents = (linkConfig as LinkConfigImpl).toJsonString(); + final configUri = tempUri.resolve('config.json'); + final configFile = File.fromUri(configUri); + await configFile.writeAsString(configFileContents); + final linkConfigFromArgs = + LinkConfig.fromArguments(['--config', configUri.toFilePath()]); + expect(linkConfigFromArgs, linkConfig); + }); + + test('LinkConfig.version', () { + LinkConfig.latestVersion.toString(); + }); +} diff --git a/pkgs/native_assets_cli/test/example/local_asset_test.dart b/pkgs/native_assets_cli/test/example/local_asset_test.dart index 51e6e980c..8f07c0387 100644 --- a/pkgs/native_assets_cli/test/example/local_asset_test.dart +++ b/pkgs/native_assets_cli/test/example/local_asset_test.dart @@ -32,7 +32,7 @@ void main() async { test('local_asset build$testSuffix', () async { final testTempUri = tempUri.resolve('test1/'); await Directory.fromUri(testTempUri).create(); - final testPackageUri = packageUri.resolve('example/$name/'); + final testPackageUri = packageUri.resolve('example/build/$name/'); final dartUri = Uri.file(Platform.resolvedExecutable); final processResult = await Process.run( @@ -43,7 +43,7 @@ void main() async { '-Dpackage_name=$name', '-Dpackage_root=${testPackageUri.toFilePath()}', '-Dtarget_os=${OSImpl.current}', - '-Dversion=${BuildConfigImpl.latestVersion}', + '-Dversion=${HookConfigImpl.latestVersion}', '-Dlink_mode_preference=dynamic', '-Ddry_run=$dryRun', if (!dryRun) ...[ @@ -68,7 +68,7 @@ void main() async { expect(processResult.exitCode, 0); final buildOutputUri = tempUri.resolve('build_output.json'); - final buildOutput = BuildOutputImpl.fromJsonString( + final buildOutput = HookOutputImpl.fromJsonString( await File.fromUri(buildOutputUri).readAsString()); final assets = buildOutput.assets; final dependencies = buildOutput.dependencies; diff --git a/pkgs/native_assets_cli/test/example/native_add_library_test.dart b/pkgs/native_assets_cli/test/example/native_add_library_test.dart index c462e0360..263872d5f 100644 --- a/pkgs/native_assets_cli/test/example/native_add_library_test.dart +++ b/pkgs/native_assets_cli/test/example/native_add_library_test.dart @@ -32,7 +32,7 @@ void main() async { test('native_add build$testSuffix', () async { final testTempUri = tempUri.resolve('test1/'); await Directory.fromUri(testTempUri).create(); - final testPackageUri = packageUri.resolve('example/$name/'); + final testPackageUri = packageUri.resolve('example/build/$name/'); final dartUri = Uri.file(Platform.resolvedExecutable); final processResult = await Process.run( @@ -43,7 +43,7 @@ void main() async { '-Dpackage_name=$name', '-Dpackage_root=${testPackageUri.toFilePath()}', '-Dtarget_os=${OSImpl.current}', - '-Dversion=${BuildConfigImpl.latestVersion}', + '-Dversion=${HookConfigImpl.latestVersion}', '-Dlink_mode_preference=dynamic', '-Ddry_run=$dryRun', if (!dryRun) ...[ @@ -68,7 +68,7 @@ void main() async { expect(processResult.exitCode, 0); final buildOutputUri = tempUri.resolve('build_output.json'); - final buildOutput = BuildOutputImpl.fromJsonString( + final buildOutput = HookOutputImpl.fromJsonString( await File.fromUri(buildOutputUri).readAsString()); final assets = buildOutput.assets; final dependencies = buildOutput.dependencies; diff --git a/pkgs/native_assets_cli/test/helpers.dart b/pkgs/native_assets_cli/test/helpers.dart index ae34a37d1..830ed9d37 100644 --- a/pkgs/native_assets_cli/test/helpers.dart +++ b/pkgs/native_assets_cli/test/helpers.dart @@ -133,6 +133,10 @@ extension on Asset { } } +extension UnescapePath on String { + String unescape() => replaceAll('\\', '/'); +} + extension UriExtension on Uri { FileSystemEntity get fileSystemEntity { if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) { @@ -153,3 +157,22 @@ String yamlEncode(Object yamlEncoding) { ); return editor.toString(); } + +dynamic yamlDecode(String yaml) { + final value = loadYaml(yaml); + return yamlToDart(value); +} + +dynamic yamlToDart(dynamic value) { + if (value is YamlMap) { + final entries = >[]; + for (final key in value.keys) { + entries.add(MapEntry(key as String, yamlToDart(value[key]))); + } + return Map.fromEntries(entries); + } else if (value is YamlList) { + return List.from(value.map(yamlToDart)); + } else { + return value; + } +} diff --git a/pkgs/native_assets_cli/test/model/asset_test.dart b/pkgs/native_assets_cli/test/model/asset_test.dart index d6f5e2013..77a5e1b45 100644 --- a/pkgs/native_assets_cli/test/model/asset_test.dart +++ b/pkgs/native_assets_cli/test/model/asset_test.dart @@ -7,8 +7,6 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; -import '../helpers.dart'; - void main() { final fooUri = Uri.file('path/to/libfoo.so'); final foo3Uri = Uri(path: 'libfoo3.so'); @@ -59,11 +57,13 @@ void main() { ]; final dataAssets = [ DataAssetImpl( - id: 'package:my_package/my_data_asset', + name: 'my_data_asset', + package: 'my_package', file: dataUri, ), DataAssetImpl( - id: 'package:my_package/my_data_asset2', + name: 'my_data_asset2', + package: 'my_package', file: data2Uri, ), ]; @@ -107,64 +107,80 @@ void main() { uri: ${blaUri.toFilePath()} target: windows_x64'''; - final assetsYamlEncoding = '''- architecture: x64 - file: ${fooUri.toFilePath()} - id: package:my_package/foo - link_mode: - type: dynamic_loading_bundle - os: android - type: native_code -- architecture: x64 - id: package:my_package/foo3 - link_mode: - type: dynamic_loading_system - uri: ${foo3Uri.toFilePath()} - os: android - type: native_code -- architecture: x64 - id: package:my_package/foo4 - link_mode: - type: dynamic_loading_executable - os: android - type: native_code -- architecture: x64 - id: package:my_package/foo5 - link_mode: - type: dynamic_loading_process - os: android - type: native_code -- architecture: arm64 - file: ${barUri.toFilePath()} - id: package:my_package/bar - link_mode: - type: static - os: linux - type: native_code -- architecture: x64 - file: ${blaUri.toFilePath()} - id: package:my_package/bla - link_mode: - type: dynamic_loading_bundle - os: windows - type: native_code -- id: package:my_package/my_data_asset - file: ${dataUri.toFilePath()} - type: data -- id: package:my_package/my_data_asset2 - file: ${data2Uri.toFilePath()} - type: data'''; + final assetsJsonEncoding = [ + { + 'architecture': 'x64', + 'file': fooUri.toFilePath(), + 'id': 'package:my_package/foo', + 'link_mode': {'type': 'dynamic_loading_bundle'}, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'id': 'package:my_package/foo3', + 'link_mode': { + 'type': 'dynamic_loading_system', + 'uri': foo3Uri.toFilePath() + }, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'id': 'package:my_package/foo4', + 'link_mode': {'type': 'dynamic_loading_executable'}, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'id': 'package:my_package/foo5', + 'link_mode': {'type': 'dynamic_loading_process'}, + 'os': 'android', + 'type': 'native_code' + }, + { + 'architecture': 'arm64', + 'file': barUri.toFilePath(), + 'id': 'package:my_package/bar', + 'link_mode': {'type': 'static'}, + 'os': 'linux', + 'type': 'native_code' + }, + { + 'architecture': 'x64', + 'file': blaUri.toFilePath(), + 'id': 'package:my_package/bla', + 'link_mode': {'type': 'dynamic_loading_bundle'}, + 'os': 'windows', + 'type': 'native_code' + }, + { + 'name': 'my_data_asset', + 'package': 'my_package', + 'file': Uri.file('path/to/data.txt').toFilePath(), + 'type': 'data' + }, + { + 'name': 'my_data_asset2', + 'package': 'my_package', + 'file': Uri.file('path/to/data.json').toFilePath(), + 'type': 'data' + } + ]; test('asset yaml', () { - final yaml = yamlEncode([ - for (final item in assets) item.toJson(BuildOutputImpl.latestVersion) - ]); - expect(yaml, assetsYamlEncoding); - final assets2 = AssetImpl.listFromJsonList(loadYaml(yaml) as List); + final json = [ + for (final item in assets) item.toJson(HookOutputImpl.latestVersion) + ]; + expect(json, assetsJsonEncoding); + final assets2 = AssetImpl.listFromJson(json); expect(assets, assets2); }); test('build_output protocol v1.0.0 keeps working', () { - final assets2 = AssetImpl.listFromJsonList( + final assets2 = AssetImpl.listFromJson( loadYaml(assetsYamlEncodingV1_0_0) as List); expect(nativeCodeAssets, assets2); }); diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart index 0c906f7d0..4d82529e0 100644 --- a/pkgs/native_assets_cli/test/model/build_config_test.dart +++ b/pkgs/native_assets_cli/test/model/build_config_test.dart @@ -4,7 +4,6 @@ import 'dart:io'; -import 'package:cli_config/cli_config.dart'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:native_assets_cli/src/api/asset.dart'; import 'package:test/test.dart'; @@ -12,10 +11,10 @@ import 'package:test/test.dart'; import '../helpers.dart'; void main() async { + const packageName = 'my_package'; late Uri tempUri; late Uri outDirUri; late Uri outDir2Uri; - late String packageName; late Uri packageRootUri; late Uri fakeClang; late Uri fakeLd; @@ -28,7 +27,6 @@ void main() async { outDirUri = tempUri.resolve('out1/'); await Directory.fromUri(outDirUri).create(); outDir2Uri = tempUri.resolve('out2/'); - packageName = 'my_package'; await Directory.fromUri(outDir2Uri).create(); packageRootUri = tempUri.resolve('$packageName/'); await Directory.fromUri(packageRootUri).create(); @@ -50,7 +48,7 @@ void main() async { test('BuildConfig ==', () { final config1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -66,7 +64,7 @@ void main() async { ); final config2 = BuildConfigImpl( - outDir: outDir2Uri, + outputDirectory: outDir2Uri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -96,7 +94,7 @@ void main() async { test('BuildConfig fromConfig', () { final buildConfig2 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetArchitecture: ArchitectureImpl.arm64, @@ -106,7 +104,7 @@ void main() async { linkModePreference: LinkModePreferenceImpl.preferStatic, ); - final config = Config(fileParsed: { + final config = { 'build_mode': 'release', 'dry_run': false, 'link_mode_preference': 'prefer-static', @@ -116,39 +114,39 @@ void main() async { 'target_android_ndk_api': 30, 'target_architecture': 'arm64', 'target_os': 'android', - 'version': BuildOutputImpl.latestVersion.toString(), - }); + 'version': HookOutputImpl.latestVersion.toString(), + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig.dryRun', () { final buildConfig2 = BuildConfigImpl.dryRun( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetOS: OSImpl.android, linkModePreference: LinkModePreferenceImpl.preferStatic, ); - final config = Config(fileParsed: { + final config = { 'dry_run': true, 'link_mode_preference': 'prefer-static', 'out_dir': outDirUri.toFilePath(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), 'target_os': 'android', - 'version': BuildOutputImpl.latestVersion.toString(), - }); + 'version': HookOutputImpl.latestVersion.toString(), + }; - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(config); expect(fromConfig, equals(buildConfig2)); }); test('BuildConfig toJson fromConfig', () { final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetArchitecture: ArchitectureImpl.arm64, @@ -163,14 +161,13 @@ void main() async { ); final configFile = buildConfig1.toJson(); - final config = Config(fileParsed: configFile); - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(configFile); expect(fromConfig, equals(buildConfig1)); }); test('BuildConfig == dependency metadata', () { final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -190,7 +187,7 @@ void main() async { ); final buildConfig2 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -216,7 +213,7 @@ void main() async { test('BuildConfig toJson fromJson', () { final outDir = outDirUri; final buildConfig1 = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -240,72 +237,33 @@ void main() async { }, ); - final yamlString = yamlEncode(buildConfig1.toJson()); - final expectedYamlString = '''build_mode: release -c_compiler: - cc: ${fakeClang.toFilePath()} - ld: ${fakeLd.toFilePath()} -dependency_metadata: - bar: - key: value - foo: - a: 321 - z: - - z - - a -link_mode_preference: prefer-static -out_dir: ${outDir.toFilePath()} -package_name: $packageName -package_root: ${tempUri.toFilePath()} -supported_asset_types: - - ${NativeCodeAsset.type} -target_architecture: arm64 -target_ios_sdk: iphoneos -target_os: ios -version: ${BuildConfigImpl.latestVersion}'''; - expect(yamlString, equals(expectedYamlString)); - - final jsonString = buildConfig1.toJsonString(); - final expectedJsonString = '''{ - "build_mode": "release", - "c_compiler": { - "cc": "${fakeClang.toFilePath()}", - "ld": "${fakeLd.toFilePath()}" - }, - "dependency_metadata": { - "bar": { - "key": "value" - }, - "foo": { - "a": 321, - "z": [ - "z", - "a" - ] - } - }, - "link_mode_preference": "prefer-static", - "out_dir": "${outDir.toFilePath()}", - "package_name": "$packageName", - "package_root": "${tempUri.toFilePath()}", - "supported_asset_types": [ - "${NativeCodeAsset.type}" - ], - "target_architecture": "arm64", - "target_ios_sdk": "iphoneos", - "target_os": "ios", - "version": "${BuildConfigImpl.latestVersion}" -}'''; + final jsonObject = buildConfig1.toJson(); + final expectedJson = { + 'build_mode': 'release', + 'c_compiler': {'cc': fakeClang.toFilePath(), 'ld': fakeLd.toFilePath()}, + 'dependency_metadata': { + 'bar': {'key': 'value'}, + 'foo': { + 'a': 321, + 'z': ['z', 'a'] + } + }, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'supported_asset_types': [NativeCodeAsset.type], + 'target_architecture': 'arm64', + 'target_ios_sdk': 'iphoneos', + 'target_os': 'ios', + 'version': '${HookConfigImpl.latestVersion}' + }; expect( - jsonString.replaceAll('\\\\', '/'), - equals(expectedJsonString.replaceAll('\\', '/')), + jsonObject, + equals(expectedJson), ); - final buildConfig2 = BuildConfigImpl.fromConfig( - Config.fromConfigFileContents( - fileContents: yamlString, - ), - ); + final buildConfig2 = BuildConfigImpl.fromJson(jsonObject); expect(buildConfig2, buildConfig1); }); @@ -332,7 +290,7 @@ target_ios_sdk: iphoneos target_os: ios version: 1.0.0'''; final buildConfig1 = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -356,35 +314,32 @@ version: 1.0.0'''; }, ); - final buildConfig2 = BuildConfigImpl.fromConfig( - Config.fromConfigFileContents( - fileContents: yamlString, - ), - ); + final buildConfig2 = BuildConfigImpl.fromJson( + yamlDecode(yamlString) as Map); expect(buildConfig2, buildConfig1); }); test('BuildConfig FormatExceptions', () { expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: {})), + () => BuildConfigImpl.fromJson({}), throwsA(predicate( (e) => e is FormatException && e.message.contains( - 'No value was provided for required key: build_mode', + 'No value was provided for required key: target_os', ), )), ); expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: { - 'version': BuildConfigImpl.latestVersion.toString(), + () => BuildConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), 'target_architecture': 'arm64', 'target_os': 'android', 'target_android_ndk_api': 30, 'link_mode_preference': 'prefer-static', - })), + }), throwsA(predicate( (e) => e is FormatException && @@ -394,8 +349,8 @@ version: 1.0.0'''; )), ); expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: { - 'version': BuildConfigImpl.latestVersion.toString(), + () => BuildConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), 'out_dir': outDirUri.toFilePath(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), @@ -403,11 +358,12 @@ version: 1.0.0'''; 'target_os': 'android', 'target_android_ndk_api': 30, 'link_mode_preference': 'prefer-static', + 'build_mode': BuildModeImpl.release.name, 'dependency_metadata': { 'bar': {'key': 'value'}, 'foo': [], }, - })), + }), throwsA(predicate( (e) => e is FormatException && @@ -418,15 +374,16 @@ version: 1.0.0'''; )), ); expect( - () => BuildConfigImpl.fromConfig(Config(fileParsed: { + () => BuildConfigImpl.fromJson({ 'out_dir': outDirUri.toFilePath(), - 'version': BuildConfigImpl.latestVersion.toString(), + 'version': HookConfigImpl.latestVersion.toString(), 'package_name': packageName, 'package_root': packageRootUri.toFilePath(), 'target_architecture': 'arm64', 'target_os': 'android', 'link_mode_preference': 'prefer-static', - })), + 'build_mode': BuildModeImpl.release.name, + }), throwsA(predicate( (e) => e is FormatException && @@ -437,22 +394,9 @@ version: 1.0.0'''; ); }); - test('FormatExceptions contain full stack trace of wrapped exception', () { - try { - BuildConfigImpl.fromConfig(Config(fileParsed: { - 'out_dir': outDirUri.toFilePath(), - 'package_root': packageRootUri.toFilePath(), - 'target': [1, 2, 3, 4, 5], - 'link_mode_preference': 'prefer-static', - })); - } on FormatException catch (e) { - expect(e.toString(), stringContainsInOrder(['Config.string'])); - } - }); - test('BuildConfig toString', () { final config = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -470,7 +414,7 @@ version: 1.0.0'''; test('BuildConfig fromArgs', () async { final buildConfig = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: tempUri, targetArchitecture: ArchitectureImpl.arm64, @@ -490,37 +434,9 @@ version: 1.0.0'''; expect(buildConfig2, buildConfig); }); - test('dependency metadata via config accessor', () { - final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, - packageName: packageName, - packageRoot: tempUri, - targetArchitecture: ArchitectureImpl.arm64, - targetOS: OSImpl.android, - targetAndroidNdkApi: 30, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.preferStatic, - dependencyMetadata: { - 'bar': const Metadata({ - 'key': {'key2': 'value'}, - }), - }, - ); - // Useful for doing `path(..., exists: true)`. - expect( - buildConfig1.config.string([ - BuildConfigImpl.dependencyMetadataConfigKey, - 'bar', - 'key', - 'key2' - ].join('.')), - 'value', - ); - }); - test('envScript', () { final buildConfig1 = BuildConfigImpl( - outDir: outDirUri, + outputDirectory: outDirUri, packageName: packageName, packageRoot: packageRootUri, targetArchitecture: ArchitectureImpl.x64, @@ -535,87 +451,37 @@ version: 1.0.0'''; ); final configFile = buildConfig1.toJson(); - final config = Config(fileParsed: configFile); - final fromConfig = BuildConfigImpl.fromConfig(config); + final fromConfig = BuildConfigImpl.fromJson(configFile); expect(fromConfig, equals(buildConfig1)); }); for (final version in ['9001.0.0', '0.0.1']) { test('BuildConfig version $version', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_root': tempUri.toFilePath(), 'target_os': 'linux', - 'target_architecture': 'x64', 'version': version, - }); + 'package_name': packageName, + 'dry_run': true, + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsA(predicate( (e) => e is FormatException && e.message.contains(version) && - e.message.contains(BuildConfigImpl.latestVersion.toString()), + e.message.contains(HookConfigImpl.latestVersion.toString()), )), ); }); } - test('checksum', () async { - await inTempDir((tempUri) async { - final nativeAddUri = tempUri.resolve('native_add/'); - final fakeClangUri = tempUri.resolve('fake_clang'); - await File.fromUri(fakeClangUri).create(); - - final name1 = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: nativeAddUri, - targetArchitecture: ArchitectureImpl.x64, - targetOS: OSImpl.linux, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.dynamic, - supportedAssetTypes: [NativeCodeAsset.type], - ); - - // Using the checksum for a build folder should be stable. - expect(name1, '6723f3af2ba4cd70660494965ac55c2a'); - - // Build folder different due to metadata. - final name2 = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: nativeAddUri, - targetArchitecture: ArchitectureImpl.x64, - targetOS: OSImpl.linux, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.dynamic, - dependencyMetadata: { - 'foo': const Metadata({'key': 'value'}) - }, - ); - printOnFailure([name1, name2].toString()); - expect(name1 != name2, true); - - // Build folder different due to cc. - final name3 = BuildConfigImpl.checksum( - packageName: packageName, - packageRoot: nativeAddUri, - targetArchitecture: ArchitectureImpl.x64, - targetOS: OSImpl.linux, - buildMode: BuildModeImpl.release, - linkModePreference: LinkModePreferenceImpl.dynamic, - cCompiler: CCompilerConfigImpl( - compiler: fakeClangUri, - )); - printOnFailure([name1, name3].toString()); - expect(name1 != name3, true); - }); - }); - test('BuildConfig invalid target os architecture combination', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, @@ -623,10 +489,10 @@ version: 1.0.0'''; 'target_os': 'windows', 'target_architecture': 'arm', 'build_mode': 'debug', - 'version': BuildConfigImpl.latestVersion.toString(), - }); + 'version': HookConfigImpl.latestVersion.toString(), + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsA(predicate( (e) => e is FormatException && e.message.contains('arm'), )), @@ -635,7 +501,7 @@ version: 1.0.0'''; test('BuildConfig dry_run access invalid args', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, @@ -644,10 +510,10 @@ version: 1.0.0'''; 'target_architecture': 'arm64', 'build_mode': 'debug', 'dry_run': true, - 'version': BuildConfigImpl.latestVersion.toString(), - }); + 'version': HookConfigImpl.latestVersion.toString(), + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsA(predicate( (e) => e is FormatException && e.message.contains('In Flutter projects'), @@ -657,16 +523,16 @@ version: 1.0.0'''; test('BuildConfig dry_run access invalid args', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, 'package_root': tempUri.toFilePath(), 'target_os': 'android', 'dry_run': true, - 'version': BuildConfigImpl.latestVersion.toString(), - }); - final buildConfig = BuildConfigImpl.fromConfig(config); + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = BuildConfigImpl.fromJson(config); expect( () => buildConfig.targetAndroidNdkApi, throwsA(predicate( @@ -677,23 +543,23 @@ version: 1.0.0'''; test('BuildConfig dry_run target arch', () { final outDir = outDirUri; - final config = Config(fileParsed: { + final config = { 'link_mode_preference': 'prefer-static', 'out_dir': outDir.toFilePath(), 'package_name': packageName, 'package_root': tempUri.toFilePath(), 'target_os': 'windows', 'dry_run': true, - 'version': BuildConfigImpl.latestVersion.toString(), - }); - final buildConfig = BuildConfigImpl.fromConfig(config); + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = BuildConfigImpl.fromJson(config); expect(buildConfig.targetArchitecture, isNull); }); test('BuildConfig dry_run toString', () { final buildConfig = BuildConfigImpl.dryRun( packageName: packageName, - outDir: outDirUri, + outputDirectory: outDirUri, packageRoot: tempUri, targetOS: OSImpl.windows, linkModePreference: LinkModePreferenceImpl.dynamic, @@ -703,7 +569,7 @@ version: 1.0.0'''; }); test('invalid architecture', () { - final config = Config(fileParsed: { + final config = { 'build_mode': 'release', 'dry_run': false, 'link_mode_preference': 'prefer-static', @@ -713,10 +579,10 @@ version: 1.0.0'''; 'target_android_ndk_api': 30, 'target_architecture': 'invalid_architecture', 'target_os': 'android', - 'version': BuildOutputImpl.latestVersion.toString(), - }); + 'version': HookOutputImpl.latestVersion.toString(), + }; expect( - () => BuildConfigImpl.fromConfig(config), + () => BuildConfigImpl.fromJson(config), throwsFormatException, ); }); diff --git a/pkgs/native_assets_cli/test/model/build_output_test.dart b/pkgs/native_assets_cli/test/model/build_output_test.dart index 0472bf039..0a2aa3089 100644 --- a/pkgs/native_assets_cli/test/model/build_output_test.dart +++ b/pkgs/native_assets_cli/test/model/build_output_test.dart @@ -7,7 +7,6 @@ import 'dart:io'; import 'package:native_assets_cli/native_assets_cli_internal.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; - import '../helpers.dart'; void main() { @@ -21,44 +20,7 @@ void main() { await Directory.fromUri(tempUri).delete(recursive: true); }); - final buildOutput = BuildOutputImpl( - timestamp: DateTime.parse('2022-11-10 13:25:01.000'), - assets: [ - NativeCodeAssetImpl( - id: 'package:my_package/foo', - file: Uri(path: 'path/to/libfoo.so'), - linkMode: DynamicLoadingBundledImpl(), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - NativeCodeAssetImpl( - id: 'package:my_package/foo2', - linkMode: DynamicLoadingSystemImpl(Uri(path: 'path/to/libfoo2.so')), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - NativeCodeAssetImpl( - id: 'package:my_package/foo3', - linkMode: LookupInProcessImpl(), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - NativeCodeAssetImpl( - id: 'package:my_package/foo4', - linkMode: LookupInExecutableImpl(), - os: OSImpl.android, - architecture: ArchitectureImpl.x64, - ), - ], - dependencies: Dependencies([ - Uri.file('path/to/file.ext'), - ]), - metadata: const Metadata({ - 'key': 'value', - }), - ); - - final dryRunOutput = BuildOutputImpl( + final dryRunOutput = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), assets: [ NativeCodeAssetImpl( @@ -132,134 +94,107 @@ assets: path_type: absolute uri: path/to/libfoo.so target: android_riscv64 -metadata: {} version: 1.0.0'''; - final yamlEncoding = '''timestamp: 2022-11-10 13:25:01.000 -assets: - - architecture: x64 - file: path/to/libfoo.so - id: package:my_package/foo - link_mode: - type: dynamic_loading_bundle - os: android - type: native_code - - architecture: x64 - id: package:my_package/foo2 - link_mode: - type: dynamic_loading_system - uri: path/to/libfoo2.so - os: android - type: native_code - - architecture: x64 - id: package:my_package/foo3 - link_mode: - type: dynamic_loading_process - os: android - type: native_code - - architecture: x64 - id: package:my_package/foo4 - link_mode: - type: dynamic_loading_executable - os: android - type: native_code -dependencies: - - path/to/file.ext -metadata: - key: value -version: ${BuildOutputImpl.latestVersion}'''; - - final jsonEncoding = '''{ - "timestamp": "2022-11-10 13:25:01.000", - "assets": [ - { - "architecture": "x64", - "file": "path/to/libfoo.so", - "id": "package:my_package/foo", - "link_mode": { - "type": "dynamic_loading_bundle" + final jsonEncoding = { + 'timestamp': '2022-11-10 13:25:01.000', + 'assets': [ + { + 'architecture': 'x64', + 'file': Uri.file('path/to/libfoo.so').toFilePath(), + 'id': 'package:my_package/foo', + 'link_mode': {'type': 'dynamic_loading_bundle'}, + 'os': 'android', + 'type': 'native_code' }, - "os": "android", - "type": "native_code" - }, - { - "architecture": "x64", - "id": "package:my_package/foo2", - "link_mode": { - "type": "dynamic_loading_system", - "uri": "path/to/libfoo2.so" + { + 'architecture': 'x64', + 'id': 'package:my_package/foo2', + 'link_mode': { + 'type': 'dynamic_loading_system', + 'uri': Uri.file('path/to/libfoo2.so').toFilePath(), + }, + 'os': 'android', + 'type': 'native_code' }, - "os": "android", - "type": "native_code" - }, - { - "architecture": "x64", - "id": "package:my_package/foo3", - "link_mode": { - "type": "dynamic_loading_process" + { + 'architecture': 'x64', + 'id': 'package:my_package/foo3', + 'link_mode': {'type': 'dynamic_loading_process'}, + 'os': 'android', + 'type': 'native_code' }, - "os": "android", - "type": "native_code" + { + 'architecture': 'x64', + 'id': 'package:my_package/foo4', + 'link_mode': {'type': 'dynamic_loading_executable'}, + 'os': 'android', + 'type': 'native_code' + } + ], + 'assetsForLinking': { + 'my_package': [ + { + 'name': 'data', + 'package': 'my_package', + 'file': Uri.file('path/to/data').toFilePath(), + 'type': 'data' + } + ], + 'my_package_2': [ + { + 'name': 'data', + 'package': 'my_package', + 'file': Uri.file('path/to/data2').toFilePath(), + 'type': 'data' + } + ] }, - { - "architecture": "x64", - "id": "package:my_package/foo4", - "link_mode": { - "type": "dynamic_loading_executable" - }, - "os": "android", - "type": "native_code" - } - ], - "dependencies": [ - "path/to/file.ext" - ], - "metadata": { - "key": "value" - }, - "version": "${BuildOutputImpl.latestVersion}" -}'''; - - test('built info yaml', () { - final yaml = yamlEncode(buildOutput.toJson(BuildOutputImpl.latestVersion)) - .replaceAll('\\', '/'); - expect(yaml, yamlEncoding); + 'dependencies': [ + Uri.file('path/to/file.ext').toFilePath(), + ], + 'metadata': {'key': 'value'}, + 'version': '${HookOutputImpl.latestVersion}' + }; - final json = buildOutput - .toJsonString(BuildOutputImpl.latestVersion) - .replaceAll('\\\\', '/'); + test('built info json', () { + final buildOutput = getBuildOutput(); + final json = buildOutput.toJson(HookOutputImpl.latestVersion); expect(json, jsonEncoding); - final buildOutput2 = BuildOutputImpl.fromJsonString(yaml); + final buildOutput2 = HookOutputImpl.fromJson(json); expect(buildOutput.hashCode, buildOutput2.hashCode); expect(buildOutput, buildOutput2); }); test('built info yaml v1.0.0 parsing keeps working', () { - final buildOutput2 = BuildOutputImpl.fromJsonString(yamlEncodingV1_0_0); + final buildOutput = getBuildOutput(withLinkedAssets: false); + final buildOutput2 = HookOutputImpl.fromJsonString(yamlEncodingV1_0_0); expect(buildOutput.hashCode, buildOutput2.hashCode); expect(buildOutput, buildOutput2); }); test('built info yaml v1.0.0 serialization keeps working', () { + final buildOutput = getBuildOutput(withLinkedAssets: false); final yamlEncoding = - yamlEncode(buildOutput.toJson(Version(1, 0, 0))).replaceAll('\\', '/'); + yamlEncode(buildOutput.toJson(Version(1, 0, 0))).unescape(); expect(yamlEncoding, yamlEncodingV1_0_0); }); test('built info yaml v1.0.0 serialization keeps working dry run', () { final yamlEncoding = - yamlEncode(dryRunOutput.toJson(Version(1, 0, 0))).replaceAll('\\', '/'); + yamlEncode(dryRunOutput.toJson(Version(1, 0, 0))).unescape(); expect(yamlEncoding, yamlEncodingV1_0_0dryRun); }); - test('BuildOutput.toString', buildOutput.toString); + test('BuildOutput.toString', getBuildOutput().toString); test('BuildOutput.hashCode', () { - final buildOutput2 = BuildOutputImpl.fromJsonString(yamlEncoding); + final buildOutput = getBuildOutput(); + final buildOutput2 = HookOutputImpl.fromJson(jsonEncoding); expect(buildOutput.hashCode, buildOutput2.hashCode); - final buildOutput3 = BuildOutputImpl( + final buildOutput3 = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), ); expect(buildOutput.hashCode != buildOutput3.hashCode, true); @@ -271,7 +206,7 @@ version: ${BuildOutputImpl.latestVersion}'''; await Directory.fromUri(outDir).create(); await Directory.fromUri(packageRoot).create(); final config = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: 'dontcare', packageRoot: packageRoot, buildMode: BuildModeImpl.debug, @@ -279,8 +214,9 @@ version: ${BuildOutputImpl.latestVersion}'''; targetOS: OSImpl.macOS, linkModePreference: LinkModePreferenceImpl.dynamic, ); + final buildOutput = getBuildOutput(); await buildOutput.writeToFile(config: config); - final buildOutput2 = await BuildOutputImpl.readFromFile(outDir: outDir); + final buildOutput2 = HookOutputImpl.readFromFile(file: config.outputFile); expect(buildOutput2, buildOutput); }); @@ -290,7 +226,7 @@ version: ${BuildOutputImpl.latestVersion}'''; await Directory.fromUri(outDir).create(); await Directory.fromUri(packageRoot).create(); final config = BuildConfigImpl( - outDir: outDir, + outputDirectory: outDir, packageName: 'dontcare', packageRoot: packageRoot, buildMode: BuildModeImpl.debug, @@ -299,13 +235,14 @@ version: ${BuildOutputImpl.latestVersion}'''; linkModePreference: LinkModePreferenceImpl.dynamic, version: Version(1, 1, 0), ); + final buildOutput = getBuildOutput(withLinkedAssets: false); await buildOutput.writeToFile(config: config); - final buildOutput2 = await BuildOutputImpl.readFromFile(outDir: outDir); + final buildOutput2 = HookOutputImpl.readFromFile(file: config.outputFile); expect(buildOutput2, buildOutput); }); test('Round timestamp', () { - final buildOutput3 = BuildOutputImpl( + final buildOutput3 = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.372257'), ); expect(buildOutput3.timestamp, DateTime.parse('2022-11-10 13:25:01.000')); @@ -314,12 +251,12 @@ version: ${BuildOutputImpl.latestVersion}'''; for (final version in ['9001.0.0', '0.0.1']) { test('BuildOutput version $version', () { expect( - () => BuildOutputImpl.fromJsonString('version: $version'), + () => HookOutputImpl.fromJsonString('version: $version'), throwsA(predicate( (e) => e is FormatException && e.message.contains(version) && - e.message.contains(BuildOutputImpl.latestVersion.toString()), + e.message.contains(HookOutputImpl.latestVersion.toString()), )), ); }); @@ -327,7 +264,7 @@ version: ${BuildOutputImpl.latestVersion}'''; test('format exception', () { expect( - () => BuildOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 + () => HookOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 assets: - name: foo link_mode: dynamic @@ -343,7 +280,7 @@ version: 1.0.0'''), throwsFormatException, ); expect( - () => BuildOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 + () => HookOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 assets: - name: foo link_mode: dynamic @@ -359,7 +296,7 @@ version: 1.0.0'''), throwsFormatException, ); expect( - () => BuildOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 + () => HookOutputImpl.fromJsonString('''timestamp: 2022-11-10 13:25:01.000 assets: - name: foo link_mode: dynamic @@ -376,7 +313,7 @@ version: 1.0.0'''), }); test('BuildOutput dependencies can be modified', () { - final buildOutput = BuildOutputImpl(); + final buildOutput = HookOutputImpl(); expect( () => buildOutput.addDependencies([Uri.file('path/to/file.ext')]), returnsNormally, @@ -384,7 +321,7 @@ version: 1.0.0'''), }); test('BuildOutput setters', () { - final buildOutput = BuildOutputImpl( + final buildOutput = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), assets: [ NativeCodeAssetImpl( @@ -411,7 +348,7 @@ version: 1.0.0'''), }), ); - final buildOutput2 = BuildOutputImpl( + final buildOutput2 = HookOutputImpl( timestamp: DateTime.parse('2022-11-10 13:25:01.000'), ); buildOutput2.addAsset( @@ -448,3 +385,58 @@ version: 1.0.0'''), expect(buildOutput2.metadataModel, equals(buildOutput.metadataModel)); }); } + +HookOutputImpl getBuildOutput({bool withLinkedAssets = true}) => HookOutputImpl( + timestamp: DateTime.parse('2022-11-10 13:25:01.000'), + assets: [ + NativeCodeAssetImpl( + id: 'package:my_package/foo', + file: Uri(path: 'path/to/libfoo.so'), + linkMode: DynamicLoadingBundledImpl(), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + NativeCodeAssetImpl( + id: 'package:my_package/foo2', + linkMode: DynamicLoadingSystemImpl(Uri(path: 'path/to/libfoo2.so')), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + NativeCodeAssetImpl( + id: 'package:my_package/foo3', + linkMode: LookupInProcessImpl(), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + NativeCodeAssetImpl( + id: 'package:my_package/foo4', + linkMode: LookupInExecutableImpl(), + os: OSImpl.android, + architecture: ArchitectureImpl.x64, + ), + ], + assetsForLinking: withLinkedAssets + ? { + 'my_package': [ + DataAssetImpl( + file: Uri.file('path/to/data'), + name: 'data', + package: 'my_package', + ) + ], + 'my_package_2': [ + DataAssetImpl( + file: Uri.file('path/to/data2'), + name: 'data', + package: 'my_package', + ) + ] + } + : null, + dependencies: Dependencies([ + Uri.file('path/to/file.ext'), + ]), + metadata: const Metadata({ + 'key': 'value', + }), + ); diff --git a/pkgs/native_assets_cli/test/model/checksum_test.dart b/pkgs/native_assets_cli/test/model/checksum_test.dart new file mode 100644 index 000000000..0aa92f235 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/checksum_test.dart @@ -0,0 +1,84 @@ +import 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli_internal.dart'; +import 'package:native_assets_cli/src/api/asset.dart'; +import 'package:test/test.dart'; + +import '../helpers.dart'; + +void main() { + const packageName = 'my_package'; + test('checksum', () async { + await inTempDir((tempUri) async { + final nativeAddUri = tempUri.resolve('native_add/'); + final fakeClangUri = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClangUri).create(); + + final name1 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + supportedAssetTypes: [NativeCodeAsset.type], + hook: Hook.build, + version: HookConfigImpl.latestVersion, + ); + + // Using the checksum for a build folder should be stable. + expect(name1, 'b6170f6f00000d3766b01cea7637b607'); + + // Build folder different due to metadata. + final name2 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + dependencyMetadata: { + 'foo': const Metadata({'key': 'value'}) + }, + hook: Hook.build, + version: HookConfigImpl.latestVersion, + ); + printOnFailure([name1, name2].toString()); + expect(name1 != name2, true); + + // Build folder different due to cc. + final name3 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + cCompiler: CCompilerConfigImpl( + compiler: fakeClangUri, + ), + hook: Hook.build, + version: HookConfigImpl.latestVersion, + ); + printOnFailure([name1, name3].toString()); + expect(name1 != name3, true); + + // Build folder different due to hook. + final name4 = HookConfigImpl.checksum( + packageName: packageName, + packageRoot: nativeAddUri, + targetArchitecture: ArchitectureImpl.x64, + targetOS: OSImpl.linux, + buildMode: BuildModeImpl.release, + linkModePreference: LinkModePreferenceImpl.dynamic, + cCompiler: CCompilerConfigImpl( + compiler: fakeClangUri, + ), + hook: Hook.link, + version: HookConfigImpl.latestVersion, + ); + printOnFailure([name1, name4].toString()); + expect(name1 != name4, true); + }); + }); +} diff --git a/pkgs/native_assets_cli/test/model/link_config_test.dart b/pkgs/native_assets_cli/test/model/link_config_test.dart new file mode 100644 index 000000000..e11b0e592 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/link_config_test.dart @@ -0,0 +1,476 @@ +// Copyright (c) 2023, 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:native_assets_cli/native_assets_cli.dart'; +import 'package:native_assets_cli/native_assets_cli_internal.dart'; +import 'package:native_assets_cli/src/api/asset.dart'; +import 'package:test/test.dart'; + +void main() async { + late Uri tempUri; + late Uri outDirUri; + late Uri outDir2Uri; + const packageName = 'my_package'; + late Uri packageRootUri; + late Uri fakeClang; + late Uri fakeLd; + late Uri fakeAr; + late Uri fakeCl; + late Uri fakeVcVars; + late Uri resources; + final assets = [ + DataAsset( + package: packageName, + name: 'name', + file: Uri.file('nonexistent'), + ), + NativeCodeAsset( + package: packageName, + name: 'name2', + linkMode: DynamicLoadingBundled(), + os: OS.android, + file: Uri.file('not there'), + architecture: Architecture.riscv64, + ) + ].cast(); + + setUp(() async { + tempUri = (await Directory.systemTemp.createTemp()).uri; + outDirUri = tempUri.resolve('out1/'); + await Directory.fromUri(outDirUri).create(); + outDir2Uri = tempUri.resolve('out2/'); + await Directory.fromUri(outDir2Uri).create(); + packageRootUri = tempUri.resolve('$packageName/'); + await Directory.fromUri(packageRootUri).create(); + fakeClang = tempUri.resolve('fake_clang'); + await File.fromUri(fakeClang).create(); + fakeLd = tempUri.resolve('fake_ld'); + await File.fromUri(fakeLd).create(); + fakeAr = tempUri.resolve('fake_ar'); + await File.fromUri(fakeAr).create(); + fakeCl = tempUri.resolve('cl.exe'); + await File.fromUri(fakeCl).create(); + fakeVcVars = tempUri.resolve('vcvarsall.bat'); + await File.fromUri(fakeVcVars).create(); + resources = tempUri.resolve('resources.json'); + File.fromUri(resources).createSync(); + }); + + tearDown(() async { + await Directory.fromUri(tempUri).delete(recursive: true); + }); + + test('LinkConfig ==', () { + final config1 = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + archiver: fakeAr, + ), + buildMode: BuildModeImpl.release, + assets: assets, + resourceIdentifierUri: resources, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final config2 = LinkConfigImpl( + outputDirectory: outDir2Uri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.android, + targetAndroidNdkApi: 30, + buildMode: BuildModeImpl.release, + assets: [], + resourceIdentifierUri: null, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + expect(config1, equals(config1)); + expect(config1 == config2, false); + expect(config1.outputDirectory != config2.outputDirectory, true); + expect(config1.packageRoot, config2.packageRoot); + expect(config1.targetArchitecture == config2.targetArchitecture, true); + expect(config1.targetOS != config2.targetOS, true); + expect(config1.targetIOSSdk, IOSSdkImpl.iPhoneOS); + expect(() => config2.targetIOSSdk, throwsStateError); + expect(config1.cCompiler.compiler != config2.cCompiler.compiler, true); + expect(config1.cCompiler.linker != config2.cCompiler.linker, true); + expect(config1.cCompiler.archiver != config2.cCompiler.archiver, true); + expect(config1.cCompiler.envScript == config2.cCompiler.envScript, true); + expect(config1.cCompiler.envScriptArgs == config2.cCompiler.envScriptArgs, + true); + expect(config1.cCompiler != config2.cCompiler, true); + expect(config1.assets != config2.assets, true); + }); + + test('LinkConfig fromConfig', () { + final buildConfig2 = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.android, + targetAndroidNdkApi: 30, + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final config = { + 'build_mode': 'release', + 'dry_run': false, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_android_ndk_api': 30, + 'target_architecture': 'arm64', + 'target_os': 'android', + 'version': HookOutputImpl.latestVersion.toString(), + 'assets': AssetImpl.listToJson(assets, HookOutputImpl.latestVersion), + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(buildConfig2)); + }); + + test('LinkConfig.dryRun', () { + final buildConfig2 = LinkConfigImpl.dryRun( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetOS: OSImpl.android, + assets: [], + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final config = { + 'dry_run': true, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_os': 'android', + 'version': HookOutputImpl.latestVersion.toString(), + 'assets': [], + }; + + final fromConfig = LinkConfigImpl.fromJson(config); + expect(fromConfig, equals(buildConfig2)); + }); + + test('LinkConfig toJson fromConfig', () { + final buildConfig1 = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: packageRootUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + ), + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final configFile = buildConfig1.toJson(); + final fromConfig = LinkConfigImpl.fromJson(configFile); + expect(fromConfig, equals(buildConfig1)); + }); + + test('LinkConfig toJson fromJson', () { + final outDir = outDirUri; + final buildConfig1 = LinkConfigImpl( + outputDirectory: outDir, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + ), + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + + final jsonObject = buildConfig1.toJson(); + final expectedJson = { + 'assets': AssetImpl.listToJson(assets, HookConfigImpl.latestVersion), + 'build_mode': 'release', + 'c_compiler': {'cc': fakeClang.toFilePath(), 'ld': fakeLd.toFilePath()}, + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'supported_asset_types': [NativeCodeAsset.type], + 'target_architecture': 'arm64', + 'target_ios_sdk': 'iphoneos', + 'target_os': 'ios', + 'version': '${HookConfigImpl.latestVersion}', + 'link_mode_preference': 'prefer-static', + }; + expect(jsonObject, equals(expectedJson)); + + final buildConfig2 = LinkConfigImpl.fromJson(jsonObject); + expect(buildConfig2, buildConfig1); + }); + + test('LinkConfig FormatExceptions', () { + expect( + () => LinkConfigImpl.fromJson({}), + throwsA(predicate( + (e) => + e is FormatException && + e.message + .contains('No value was provided for required key: target_os'), + )), + ); + expect( + () => LinkConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_architecture': 'arm64', + 'target_os': 'android', + 'target_android_ndk_api': 30, + 'assets': [], + }), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains( + 'No value was provided for required key: out_dir', + ), + )), + ); + expect( + () => LinkConfigImpl.fromJson({ + 'version': HookConfigImpl.latestVersion.toString(), + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_architecture': 'arm64', + 'target_os': 'android', + 'target_android_ndk_api': 30, + 'build_mode': BuildModeImpl.release.name, + 'assets': 'astring', + 'link_mode_preference': LinkModePreferenceImpl.preferStatic.name, + }), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains( + "Unexpected value 'astring' for key '.assets' in config file. " + 'Expected a List?.', + ), + )), + ); + expect( + () => LinkConfigImpl.fromJson({ + 'out_dir': outDirUri.toFilePath(), + 'version': HookConfigImpl.latestVersion.toString(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_architecture': 'arm64', + 'target_os': 'android', + 'build_mode': BuildModeImpl.release.name, + 'link_mode_preference': LinkModePreferenceImpl.preferStatic.name, + }), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains( + 'No value was provided for required key: target_android_ndk_api', + ), + )), + ); + }); + + test('LinkConfig toString', () { + final config = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.iOS, + targetIOSSdk: IOSSdkImpl.iPhoneOS, + cCompiler: CCompilerConfigImpl( + compiler: fakeClang, + linker: fakeLd, + ), + buildMode: BuildModeImpl.release, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + expect(config.toString(), isNotEmpty); + }); + + test('LinkConfig fromArgs', () async { + final buildConfig = LinkConfigImpl( + outputDirectory: outDirUri, + packageName: packageName, + packageRoot: tempUri, + targetArchitecture: ArchitectureImpl.arm64, + targetOS: OSImpl.android, + targetAndroidNdkApi: 30, + buildMode: BuildModeImpl.release, + assets: assets, + resourceIdentifierUri: resources, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + final configFileContents = buildConfig.toJsonString(); + final configUri = tempUri.resolve('link_config.json'); + final configFile = File.fromUri(configUri); + await configFile.writeAsString(configFileContents); + final buildConfig2 = + LinkConfigImpl.fromArguments(['--config', configUri.toFilePath()]); + expect(buildConfig2, buildConfig); + }); + + for (final version in ['9001.0.0', '0.0.1']) { + test('LinkConfig version $version', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_root': tempUri.toFilePath(), + 'target_os': 'linux', + 'version': version, + 'package_name': packageName, + 'dry_run': true, + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsA(predicate( + (e) => + e is FormatException && + e.message.contains(version) && + e.message.contains(HookConfigImpl.latestVersion.toString()), + )), + ); + }); + } + + test('LinkConfig invalid target os architecture combination', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'windows', + 'target_architecture': 'arm', + 'build_mode': 'debug', + 'version': HookConfigImpl.latestVersion.toString(), + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsA(predicate( + (e) => e is FormatException && e.message.contains('arm'), + )), + ); + }); + + test('LinkConfig dry_run access invalid args', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'windows', + 'target_architecture': 'arm64', + 'build_mode': 'debug', + 'dry_run': true, + 'version': HookConfigImpl.latestVersion.toString(), + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsA(predicate( + (e) => + e is FormatException && e.message.contains('In Flutter projects'), + )), + ); + }); + + test('LinkConfig dry_run access invalid args', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'android', + 'dry_run': true, + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = LinkConfigImpl.fromJson(config); + expect( + () => buildConfig.targetAndroidNdkApi, + throwsA(predicate( + (e) => e is StateError && e.message.contains('In Flutter projects'), + )), + ); + }); + + test('LinkConfig dry_run target arch', () { + final outDir = outDirUri; + final config = { + 'link_mode_preference': 'prefer-static', + 'out_dir': outDir.toFilePath(), + 'package_name': packageName, + 'package_root': tempUri.toFilePath(), + 'target_os': 'windows', + 'dry_run': true, + 'version': HookConfigImpl.latestVersion.toString(), + }; + final buildConfig = LinkConfigImpl.fromJson(config); + expect(buildConfig.targetArchitecture, isNull); + }); + + test('LinkConfig dry_run toString', () { + final buildConfig = LinkConfigImpl.dryRun( + packageName: packageName, + outputDirectory: outDirUri, + packageRoot: tempUri, + targetOS: OSImpl.windows, + assets: assets, + linkModePreference: LinkModePreferenceImpl.preferStatic, + ); + expect(buildConfig.toJsonString(), isNotEmpty); + }); + + test('invalid architecture', () { + final config = { + 'build_mode': 'release', + 'dry_run': false, + 'link_mode_preference': 'prefer-static', + 'out_dir': outDirUri.toFilePath(), + 'package_name': packageName, + 'package_root': packageRootUri.toFilePath(), + 'target_android_ndk_api': 30, + 'target_architecture': 'invalid_architecture', + 'target_os': 'android', + 'version': HookOutputImpl.latestVersion.toString(), + }; + expect( + () => LinkConfigImpl.fromJson(config), + throwsFormatException, + ); + }); +} diff --git a/pkgs/native_assets_cli/test/model/resource_data.dart b/pkgs/native_assets_cli/test/model/resource_data.dart new file mode 100644 index 000000000..b83351e19 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/resource_data.dart @@ -0,0 +1,123 @@ +// 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. + +import 'package:native_assets_cli/src/model/resource_identifiers.dart'; + +const resourceFile = '''{ + "_comment": "Resources referenced by annotated resource identifiers", + "AppTag": "TBD", + "environment": { + "dart.tool.dart2js": false + }, + "identifiers": [] +}'''; + +final resourceIdentifiers = ResourceIdentifiers(identifiers: [ + Identifier( + name: 'methodName1', + id: 'someMetadata', + uri: Uri.file('path/to/file'), + nonConstant: true, + files: [ + ResourceFile(part: 1, references: [ + ResourceReference( + uri: Uri.file('path/to/reference'), + line: 2, + column: 4, + arguments: { + '1': 'Some positional argument', + }, + ), + ]), + ], + ), + Identifier( + name: 'methodName2', + id: 'someOtherMetadata', + uri: Uri.file('path/to/other/file'), + nonConstant: false, + files: [ + ResourceFile(part: 1, references: [ + ResourceReference( + uri: Uri.file('path/to/reference'), + line: 15, + column: 3, + arguments: { + 'namedIntParam': 1, + }, + ), + ]), + ResourceFile(part: 2, references: [ + ResourceReference( + uri: Uri.file('path/to/reference'), + line: 15, + column: 3, + arguments: { + 'namedIntParam': 2, + }, + ), + ]), + ], + ), +]); + +final resourceIdentifiersJson = { + 'identifiers': [ + { + 'name': 'methodName1', + 'id': 'someMetadata', + 'uri': Uri.file('path/to/file').toFilePath(), + 'nonConstant': true, + 'files': [ + { + 'part': 1, + 'references': [ + { + '@': { + 'uri': Uri.file('path/to/reference').toFilePath(), + 'line': 2, + 'column': 4 + }, + '1': 'Some positional argument' + } + ] + } + ] + }, + { + 'name': 'methodName2', + 'id': 'someOtherMetadata', + 'uri': Uri.file('path/to/other/file').toFilePath(), + 'nonConstant': false, + 'files': [ + { + 'part': 1, + 'references': [ + { + '@': { + 'uri': Uri.file('path/to/reference').toFilePath(), + 'line': 15, + 'column': 3 + }, + 'namedIntParam': 1 + } + ] + }, + { + 'part': 2, + 'references': [ + { + '@': { + 'uri': Uri.file('path/to/reference').toFilePath(), + 'line': 15, + 'column': 3 + }, + 'namedIntParam': 2 + } + ] + } + ] + } + ] +}; diff --git a/pkgs/native_assets_cli/test/model/resource_identifiers_test.dart b/pkgs/native_assets_cli/test/model/resource_identifiers_test.dart new file mode 100644 index 000000000..acd207e56 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/resource_identifiers_test.dart @@ -0,0 +1,31 @@ +// 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. + +import 'dart:convert'; + +import 'package:native_assets_cli/src/model/resource_identifiers.dart'; +import 'package:test/test.dart'; + +import 'resource_data.dart'; + +void main() { + test('empty resources parsing', () { + final resourceIdentifiers = + ResourceIdentifiers.fromFileContents(resourceFile); + expect(resourceIdentifiers.identifiers, isEmpty); + }); + test('Serialize to JSON', () { + const jsonEncoder = JsonEncoder.withIndent(' '); + expect( + jsonEncoder.convert(resourceIdentifiers), + jsonEncoder.convert(resourceIdentifiersJson), + ); + }); + test('Deserialize from JSON', () { + expect( + ResourceIdentifiers.fromJson(resourceIdentifiersJson), + resourceIdentifiers, + ); + }); +} diff --git a/pkgs/native_toolchain_c/README.md b/pkgs/native_toolchain_c/README.md index 01b1448a5..c9dc8b6d5 100644 --- a/pkgs/native_toolchain_c/README.md +++ b/pkgs/native_toolchain_c/README.md @@ -23,4 +23,5 @@ For bugs, please file an issue in the ## Example -An example can be found in [../native_assets_cli/example/](../native_assets_cli/example/). +An example can be found in [../native_assets_cli/example/build/]( +../native_assets_cli/example/build/). diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index 9e0683b1b..5bffb92f7 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -152,6 +152,12 @@ class CBuilder implements Builder { /// | Fuchsia | `c++` | final String? cppLinkStdLib; + /// If the code asset should be a dynamic or static library. + /// + /// This determines whether to produce a dynamic or static library. If null, + /// the value is instead retrieved from the [BuildConfig]. + final LinkModePreference? linkModePreference; + CBuilder.library({ required this.name, required this.assetName, @@ -167,6 +173,7 @@ class CBuilder implements Builder { this.std, this.language = Language.c, this.cppLinkStdLib, + this.linkModePreference, }) : _type = _CBuilderType.library; CBuilder.executable({ @@ -185,7 +192,8 @@ class CBuilder implements Builder { }) : _type = _CBuilderType.executable, assetName = null, installName = null, - pic = pie; + pic = pie, + linkModePreference = null; /// Runs the C Compiler with on this C build spec. /// @@ -195,11 +203,13 @@ class CBuilder implements Builder { required BuildConfig buildConfig, required BuildOutput buildOutput, required Logger? logger, + String? linkInPackage, }) async { final outDir = buildConfig.outputDirectory; final packageRoot = buildConfig.packageRoot; await Directory.fromUri(outDir).create(recursive: true); - final linkMode = _linkMode(buildConfig.linkModePreference); + final linkMode = + _linkMode(linkModePreference ?? buildConfig.linkModePreference); final libUri = outDir.resolve(buildConfig.targetOS.libraryFileName(name, linkMode)); final exeUri = @@ -247,17 +257,20 @@ class CBuilder implements Builder { } if (assetName != null) { - buildOutput.addAssets([ - NativeCodeAsset( - package: buildConfig.packageName, - name: assetName!, - file: libUri, - linkMode: linkMode, - os: buildConfig.targetOS, - architecture: - buildConfig.dryRun ? null : buildConfig.targetArchitecture, - ) - ]); + buildOutput.addAssets( + [ + NativeCodeAsset( + package: buildConfig.packageName, + name: assetName!, + file: libUri, + linkMode: linkMode, + os: buildConfig.targetOS, + architecture: + buildConfig.dryRun ? null : buildConfig.targetArchitecture, + ) + ], + linkInPackage: linkInPackage, + ); } if (!buildConfig.dryRun) { final includeFiles = await Stream.fromIterable(includes) 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 6f5fdbbad..3ff4560ef 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -121,30 +121,79 @@ class RunCBuilder { archiver_ = await archiver(); } - late final IOSSdk targetIosSdk; + final IOSSdk? targetIosSdk; if (buildConfig.targetOS == OS.iOS) { targetIosSdk = buildConfig.targetIOSSdk; + } else { + targetIosSdk = null; } // The Android Gradle plugin does not honor API level 19 and 20 when // invoking clang. Mimic that behavior here. // See https://github.com/dart-lang/native/issues/171. - late final int targetAndroidNdkApi; + final int? targetAndroidNdkApi; if (buildConfig.targetOS == OS.android) { final minimumApi = buildConfig.targetArchitecture == Architecture.riscv64 ? 35 : 21; targetAndroidNdkApi = max(buildConfig.targetAndroidNdkApi!, minimumApi); + } else { + targetAndroidNdkApi = null; } final architecture = buildConfig.targetArchitecture; + final sourceFiles = sources.map((e) => e.toFilePath()).toList(); + final objectFiles = []; + if (staticLibrary != null) { + for (var i = 0; i < sourceFiles.length; i++) { + final objectFile = outDir.resolve('out$i.o'); + await _compile( + compiler, + architecture, + targetAndroidNdkApi, + targetIosSdk, + [sourceFiles[i]], + objectFile, + ); + objectFiles.add(objectFile); + } + await runProcess( + executable: archiver_!, + arguments: [ + 'rc', + outDir.resolveUri(staticLibrary!).toFilePath(), + ...objectFiles.map((objectFile) => objectFile.toFilePath()), + ], + logger: logger, + captureOutput: false, + throwOnUnexpectedExitCode: true, + ); + } else { + await _compile( + compiler, + architecture, + targetAndroidNdkApi, + targetIosSdk, + sourceFiles, + dynamicLibrary != null ? outDir.resolveUri(dynamicLibrary!) : null, + ); + } + } + Future _compile( + ToolInstance compiler, + Architecture? architecture, + int? targetAndroidNdkApi, + IOSSdk? targetIosSdk, + Iterable sourceFiles, + Uri? outFile, + ) async { await runProcess( executable: compiler.uri, arguments: [ if (buildConfig.targetOS == OS.android) ...[ '--target=' '${androidNdkClangTargetFlags[architecture]!}' - '$targetAndroidNdkApi', + '${targetAndroidNdkApi!}', '--sysroot=${androidSysroot(compiler).toFilePath()}', ], if (buildConfig.targetOS == OS.macOS) @@ -153,7 +202,7 @@ class RunCBuilder { '--target=${appleClangIosTargetFlags[architecture]![targetIosSdk]!}', if (buildConfig.targetOS == OS.iOS) ...[ '-isysroot', - (await iosSdk(targetIosSdk, logger: logger)).toFilePath(), + (await iosSdk(targetIosSdk!, logger: logger)).toFilePath(), ], if (buildConfig.targetOS == OS.macOS) ...[ '-isysroot', @@ -196,7 +245,7 @@ class RunCBuilder { for (final MapEntry(key: name, :value) in defines.entries) if (value == null) '-D$name' else '-D$name=$value', for (final include in includes) '-I${include.toFilePath()}', - ...sources.map((e) => e.toFilePath()), + ...sourceFiles, if (executable != null) ...[ '-o', outDir.resolveUri(executable!).toFilePath(), @@ -204,30 +253,17 @@ class RunCBuilder { if (dynamicLibrary != null) ...[ '--shared', '-o', - outDir.resolveUri(dynamicLibrary!).toFilePath(), + outFile!.toFilePath(), ] else if (staticLibrary != null) ...[ '-c', '-o', - outDir.resolve('out.o').toFilePath(), + outFile!.toFilePath(), ], ], logger: logger, captureOutput: false, throwOnUnexpectedExitCode: true, ); - if (staticLibrary != null) { - await runProcess( - executable: archiver_!, - arguments: [ - 'rc', - outDir.resolveUri(staticLibrary!).toFilePath(), - outDir.resolve('out.o').toFilePath(), - ], - logger: logger, - captureOutput: false, - throwOnUnexpectedExitCode: true, - ); - } } Future runCl({required ToolInstance compiler}) async { diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart index cc5bfaf58..4080f5e93 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/recognizer.dart @@ -11,7 +11,7 @@ import '../tool/tool_resolver.dart'; import 'apple_clang.dart'; import 'clang.dart'; import 'gcc.dart'; -import 'msvc.dart'; +import 'msvc.dart' as msvc; class CompilerRecognizer implements ToolResolver { final Uri uri; @@ -35,7 +35,7 @@ class CompilerRecognizer implements ToolResolver { tool = clang; } } else if (filePath.endsWith('cl.exe')) { - tool = cl; + tool = msvc.cl; } if (tool != null) { @@ -46,7 +46,7 @@ class CompilerRecognizer implements ToolResolver { toolInstance, logger: logger, arguments: [ - if (tool != cl) '--version', + if (tool != msvc.cl) '--version', ], ), ]; @@ -75,7 +75,7 @@ class LinkerRecognizer implements ToolResolver { } else if (filePath.endsWith(os.executableFileName('ld'))) { tool = appleLd; } else if (filePath.endsWith('link.exe')) { - tool = link; + tool = msvc.link; } if (tool != null) { @@ -89,7 +89,7 @@ class LinkerRecognizer implements ToolResolver { ), ]; } - if (tool == link) { + if (tool == msvc.link) { return [ await CliVersionResolver.lookupVersion( toolInstance, @@ -125,7 +125,7 @@ class ArchiverRecognizer implements ToolResolver { } else if (filePath.endsWith(os.executableFileName('ar'))) { tool = appleAr; } else if (filePath.endsWith('lib.exe')) { - tool = lib; + tool = msvc.lib; } if (tool != null) { diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml index c25e84733..d6bd424a8 100644 --- a/pkgs/native_toolchain_c/pubspec.yaml +++ b/pkgs/native_toolchain_c/pubspec.yaml @@ -4,6 +4,8 @@ description: >- version: 0.4.1 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c +publish_to: none + topics: - compiler - ffi @@ -18,9 +20,9 @@ dependencies: glob: ^2.1.1 logging: ^1.1.1 meta: ^1.9.1 - native_assets_cli: ^0.5.0 - # native_assets_cli: - # path: ../native_assets_cli/ + # native_assets_cli: ^0.5.0 + native_assets_cli: + path: ../native_assets_cli/ pub_semver: ^2.1.3 dev_dependencies: diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart index 3d0e15d5c..b40e73a2a 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_test.dart @@ -117,7 +117,7 @@ void main() { final buildConfig = dryRun ? BuildConfig.dryRun( - outDir: tempUri, + outputDirectory: tempUri, packageName: name, packageRoot: tempUri, targetOS: OS.current, diff --git a/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart b/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart index 328412f72..1a2d40860 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/compiler_resolver_test.dart @@ -13,7 +13,7 @@ import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:native_toolchain_c/src/cbuilder/compiler_resolver.dart'; import 'package:native_toolchain_c/src/native_toolchain/apple_clang.dart'; import 'package:native_toolchain_c/src/native_toolchain/clang.dart'; -import 'package:native_toolchain_c/src/native_toolchain/msvc.dart'; +import 'package:native_toolchain_c/src/native_toolchain/msvc.dart' as msvc; import 'package:native_toolchain_c/src/tool/tool_error.dart'; import 'package:test/test.dart'; @@ -24,21 +24,21 @@ void main() { final tempUri = await tempDirForTest(); final ar = [ ...await appleAr.defaultResolver!.resolve(logger: logger), - ...await lib.defaultResolver!.resolve(logger: logger), + ...await msvc.lib.defaultResolver!.resolve(logger: logger), ...await llvmAr.defaultResolver!.resolve(logger: logger), ].first.uri; final cc = [ ...await appleClang.defaultResolver!.resolve(logger: logger), - ...await cl.defaultResolver!.resolve(logger: logger), + ...await msvc.cl.defaultResolver!.resolve(logger: logger), ...await clang.defaultResolver!.resolve(logger: logger), ].first.uri; final ld = [ ...await appleLd.defaultResolver!.resolve(logger: logger), - ...await link.defaultResolver!.resolve(logger: logger), + ...await msvc.link.defaultResolver!.resolve(logger: logger), ...await lld.defaultResolver!.resolve(logger: logger), ].first.uri; final envScript = [ - ...await vcvars64.defaultResolver!.resolve(logger: logger) + ...await msvc.vcvars64.defaultResolver!.resolve(logger: logger) ].firstOrNull?.uri; final buildConfig = BuildConfig.build( outputDirectory: tempUri, diff --git a/pkgs/native_toolchain_c/test/helpers.dart b/pkgs/native_toolchain_c/test/helpers.dart index 4baa41276..612c2f087 100644 --- a/pkgs/native_toolchain_c/test/helpers.dart +++ b/pkgs/native_toolchain_c/test/helpers.dart @@ -180,3 +180,7 @@ DynamicLibrary openDynamicLibraryForTest(String path) { addTearDown(library.close); return library; } + +extension UnescapePath on String { + String unescape() => replaceAll('\\', '/'); +} diff --git a/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart b/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart index 18b1a83d9..85abb1996 100644 --- a/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart +++ b/pkgs/native_toolchain_c/test/tool/tool_resolver_test.dart @@ -68,7 +68,7 @@ void main() { expect(await File.fromUri(bazExeUri).exists(), true); final barResolver = InstallLocationResolver( toolName: 'bar', - paths: [barExeUri.toFilePath().replaceAll('\\', '/')], + paths: [barExeUri.toFilePath().unescape()], ); final bazResolver = RelativeToolResolver( toolName: 'baz', @@ -94,9 +94,9 @@ void main() { final bazExeUri = tempUri.resolve(bazExeName); await File.fromUri(barExeUri).writeAsString('dummy'); final barResolver = InstallLocationResolver( - toolName: 'bar', paths: [barExeUri.toFilePath().replaceAll('\\', '/')]); + toolName: 'bar', paths: [barExeUri.toFilePath().unescape()]); final bazResolver = InstallLocationResolver( - toolName: 'baz', paths: [bazExeUri.toFilePath().replaceAll('\\', '/')]); + toolName: 'baz', paths: [bazExeUri.toFilePath().unescape()]); final barLogs = []; final bazLogs = []; await barResolver.resolve(logger: createCapturingLogger(barLogs));