diff --git a/.github/workflows/hook.yaml b/.github/workflows/hook.yaml new file mode 100644 index 000000000..3be0faf0b --- /dev/null +++ b/.github/workflows/hook.yaml @@ -0,0 +1,61 @@ +# CI for the native_* packages. +# +# Combined into a single workflow so that deps are configured and installed once. + +name: hook +permissions: read-all + +on: + pull_request: + # No `branches:` to enable stacked PRs on GitHub. + paths: + - ".github/workflows/hook.yaml" + - "pkgs/hook/**" + - "pkgs/code_assets/**" + - "pkgs/data_assets/**" + push: + branches: [main] + paths: + - ".github/workflows/native.yaml" + - "pkgs/hook/**" + - "pkgs/code_assets/**" + - "pkgs/data_assets/**" + schedule: + - cron: "0 0 * * 0" # weekly + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu, windows] + sdk: [dev] + package: [hook, code_assets, data_assets] + + runs-on: ${{ matrix.os }}-latest + + defaults: + run: + working-directory: pkgs/${{ matrix.package }} + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + with: + sdk: ${{ matrix.sdk }} + + - run: dart pub get + + - run: dart analyze --fatal-infos + + - run: dart format --output=none --set-exit-if-changed . + + - run: dart test + + - run: | + dart tool/normalize.dart + git diff --exit-code + working-directory: pkgs/${{ matrix.package }}/ + if: ${{ matrix.package == 'hook' && matrix.sdk == 'dev' }} + diff --git a/pkgs/code_assets/analysis_options.yaml b/pkgs/code_assets/analysis_options.yaml new file mode 100644 index 000000000..349b9d631 --- /dev/null +++ b/pkgs/code_assets/analysis_options.yaml @@ -0,0 +1,17 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + errors: + todo: ignore + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + - dangling_library_doc_comments + - prefer_const_declarations + - prefer_expression_function_bodies + - prefer_final_in_for_each + - prefer_final_locals diff --git a/pkgs/code_assets/doc/schema/README.md b/pkgs/code_assets/doc/schema/README.md new file mode 100644 index 000000000..fb915d3d3 --- /dev/null +++ b/pkgs/code_assets/doc/schema/README.md @@ -0,0 +1,17 @@ +These schemas document the protocol for build and link hooks with code assets. + +* If you are a hook author, you should likely be using a hook-helper-package + Dart API (such as `package:native_toolchain_c`) instead of directly consuming + and producing JSON. +* If you are a hook-helper-package author, you should likely be using a + hook-helper-package's Dart API (such as `package:native_assets_cli`) which + abstracts away from syntactic changes. If you do want to directly interact + with the JSON, you can find the relevant schemas in [hook/](hook/). +* If you are an SDK author, you should likely be using the SDK helper package + (`package:native_assets_builder`) which abstracts away from syntactic changes. + If you do want to directly interact with the JSON, you can find the relevant + schemas in [sdk/](sdk/). + +These schemas are an extension of the base protocol: + +* [`package:hook`/doc/schema](../../../hook/doc/schema/) diff --git a/pkgs/code_assets/doc/schema/hook/build_input.schema.json b/pkgs/code_assets/doc/schema/hook/build_input.schema.json new file mode 100644 index 000000000..05352dbd7 --- /dev/null +++ b/pkgs/code_assets/doc/schema/hook/build_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:hook BuildInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildInput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/hook/build_output.schema.json b/pkgs/code_assets/doc/schema/hook/build_output.schema.json new file mode 100644 index 000000000..975a89f97 --- /dev/null +++ b/pkgs/code_assets/doc/schema/hook/build_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:hook BuildOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildOutput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/hook/link_input.schema.json b/pkgs/code_assets/doc/schema/hook/link_input.schema.json new file mode 100644 index 000000000..c98fa6044 --- /dev/null +++ b/pkgs/code_assets/doc/schema/hook/link_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:hook LinkInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkInput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/hook/link_output.schema.json b/pkgs/code_assets/doc/schema/hook/link_output.schema.json new file mode 100644 index 000000000..b414a2c41 --- /dev/null +++ b/pkgs/code_assets/doc/schema/hook/link_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:hook LinkOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkOutput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/hook/shared_definitions.schema.json b/pkgs/code_assets/doc/schema/hook/shared_definitions.schema.json new file mode 100644 index 000000000..028dfe84f --- /dev/null +++ b/pkgs/code_assets/doc/schema/hook/shared_definitions.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:hook shared definitions", + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json" + }, + { + "$ref": "../shared/shared_definitions.schema.json#" + } + ], + "definitions": { + "BuildInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/BuildInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/BuildOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildOutput" + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookInput" + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookOutput" + } + ] + }, + "LinkInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/LinkInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/LinkOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/code_assets/doc/schema/sdk/build_input.schema.json b/pkgs/code_assets/doc/schema/sdk/build_input.schema.json new file mode 100644 index 000000000..96c8e3651 --- /dev/null +++ b/pkgs/code_assets/doc/schema/sdk/build_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:sdk BuildInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildInput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/sdk/build_output.schema.json b/pkgs/code_assets/doc/schema/sdk/build_output.schema.json new file mode 100644 index 000000000..979c3aa0e --- /dev/null +++ b/pkgs/code_assets/doc/schema/sdk/build_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:sdk BuildOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildOutput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/sdk/link_input.schema.json b/pkgs/code_assets/doc/schema/sdk/link_input.schema.json new file mode 100644 index 000000000..4fce6f767 --- /dev/null +++ b/pkgs/code_assets/doc/schema/sdk/link_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:sdk LinkInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkInput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/sdk/link_output.schema.json b/pkgs/code_assets/doc/schema/sdk/link_output.schema.json new file mode 100644 index 000000000..28ca693df --- /dev/null +++ b/pkgs/code_assets/doc/schema/sdk/link_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:sdk LinkOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkOutput" + } + ] +} diff --git a/pkgs/code_assets/doc/schema/sdk/shared_definitions.schema.json b/pkgs/code_assets/doc/schema/sdk/shared_definitions.schema.json new file mode 100644 index 000000000..97d6a13ed --- /dev/null +++ b/pkgs/code_assets/doc/schema/sdk/shared_definitions.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:sdk shared definitions", + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json" + }, + { + "$ref": "../shared/shared_definitions.schema.json#" + } + ], + "definitions": { + "BuildInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/BuildInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/BuildOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildOutput" + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookInput" + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookOutput" + } + ] + }, + "LinkInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/LinkInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/LinkOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json b/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json new file mode 100644 index 000000000..d9d1ae6e5 --- /dev/null +++ b/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json @@ -0,0 +1,497 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:code_assets party:shared shared definitions", + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json" + } + ], + "definitions": { + "AndroidCodeConfig": { + "type": "object", + "properties": { + "target_ndk_api": { + "type": "integer" + } + }, + "required": [] + }, + "Architecture": { + "type": "string", + "anyOf": [ + { + "const": "arm" + }, + { + "const": "arm64" + }, + { + "const": "ia32" + }, + { + "const": "riscv32" + }, + { + "const": "riscv64" + }, + { + "const": "x64" + }, + { + "type": "string" + } + ] + }, + "Asset": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/Asset" + }, + { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "const": "native_code" + }, + { + "type": "string" + } + ] + } + }, + "if": { + "properties": { + "type": { + "const": "native_code" + } + } + }, + "then": { + "properties": { + "architecture": { + "allOf": [ + { + "$ref": "#/definitions/Architecture" + } + ] + }, + "file": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + }, + "id": { + "type": "string" + }, + "link_mode": { + "$ref": "#/definitions/LinkMode" + }, + "os": { + "$ref": "#/definitions/OS" + } + }, + "required": [ + "id", + "link_mode", + "os" + ], + "if": { + "properties": { + "link_mode": { + "properties": { + "type": { + "anyOf": [ + { + "const": "dynamic_loading_bundle" + }, + { + "const": "static" + } + ] + } + } + } + } + }, + "then": { + "required": [ + "file" + ] + } + } + } + ] + }, + "BuildConfig": { + "allOf": [ + { + "$ref": "#/definitions/Config" + }, + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/BuildConfig" + } + ] + }, + "BuildInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/BuildInput" + }, + { + "properties": { + "config": { + "$ref": "#/definitions/BuildConfig" + } + } + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/BuildOutput" + }, + { + "properties": { + "assetsForLinking": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + } + } + ] + }, + "CCompilerConfig": { + "type": "object", + "properties": { + "ar": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + }, + "cc": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + }, + "env_script": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + }, + "env_script_arguments": { + "type": "array", + "items": { + "type": "string" + } + }, + "ld": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + }, + "windows": { + "type": "object", + "properties": { + "developer_command_prompt": { + "type": "object", + "properties": { + "arguments": { + "type": "array", + "items": { + "type": "string" + } + }, + "script": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + } + }, + "required": [ + "arguments", + "script" + ] + } + } + } + }, + "required": [ + "ar", + "cc", + "ld" + ] + }, + "CodeConfig": { + "type": "object", + "properties": { + "android": { + "$ref": "#/definitions/AndroidCodeConfig" + }, + "c_compiler": { + "$ref": "#/definitions/CCompilerConfig" + }, + "ios": { + "$ref": "#/definitions/IOSCodeConfig" + }, + "link_mode_preference": { + "$ref": "#/definitions/LinkModePreference" + }, + "macos": { + "$ref": "#/definitions/MacOSCodeConfig" + }, + "target_architecture": { + "$ref": "#/definitions/Architecture" + }, + "target_os": { + "$ref": "#/definitions/OS" + } + }, + "required": [ + "link_mode_preference", + "target_architecture", + "target_os" + ], + "allOf": [ + { + "if": { + "properties": { + "target_os": { + "const": "macos" + } + } + }, + "then": { + "required": [ + "macos" + ] + } + }, + { + "if": { + "properties": { + "target_os": { + "const": "ios" + } + } + }, + "then": { + "required": [ + "ios" + ] + } + }, + { + "if": { + "properties": { + "target_os": { + "const": "android" + } + } + }, + "then": { + "required": [ + "android" + ] + } + }, + { + "if": { + "properties": { + "target_os": { + "const": "windows" + } + } + }, + "then": { + "properties": { + "c_compiler": { + "required": [ + "windows" + ] + } + } + } + } + ] + }, + "Config": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/Config" + }, + { + "properties": { + "code": { + "$ref": "#/definitions/CodeConfig" + } + } + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + }, + "required": [ + "config" + ] + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + } + ] + }, + "IOSCodeConfig": { + "type": "object", + "properties": { + "target_sdk": { + "type": "string" + }, + "target_version": { + "type": "integer" + } + }, + "required": [] + }, + "LinkInput": { + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkMode": { + "type": "object", + "properties": { + "type": { + "type": "string", + "anyOf": [ + { + "const": "dynamic_loading_bundle" + }, + { + "const": "dynamic_loading_executable" + }, + { + "const": "dynamic_loading_process" + }, + { + "const": "dynamic_loading_system" + }, + { + "const": "static" + }, + { + "type": "string" + } + ] + } + }, + "required": [ + "type" + ], + "if": { + "properties": { + "type": { + "const": "dynamic_loading_system" + } + } + }, + "then": { + "properties": { + "uri": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/relativePath" + } + }, + "required": [ + "uri" + ] + } + }, + "LinkModePreference": { + "type": "string", + "anyOf": [ + { + "const": "dynamic" + }, + { + "const": "prefer-dynamic" + }, + { + "const": "prefer-static" + }, + { + "const": "static" + }, + { + "type": "string" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + }, + "MacOSCodeConfig": { + "type": "object", + "properties": { + "target_version": { + "type": "integer" + } + }, + "required": [] + }, + "OS": { + "type": "string", + "anyOf": [ + { + "const": "android" + }, + { + "const": "ios" + }, + { + "const": "linux" + }, + { + "const": "macos" + }, + { + "const": "windows" + }, + { + "type": "string" + } + ] + } + } +} diff --git a/pkgs/code_assets/pubspec.yaml b/pkgs/code_assets/pubspec.yaml new file mode 100644 index 000000000..bd1b70346 --- /dev/null +++ b/pkgs/code_assets/pubspec.yaml @@ -0,0 +1,17 @@ +name: code_assets +version: 0.1.0-wip +repository: https://github.com/dart-lang/native/tree/main/pkgs/code_assets + +publish_to: none + +environment: + sdk: '>=3.7.0 <4.0.0' + +dependencies: + hook: + path: ../hook/ + +dev_dependencies: + dart_flutter_team_lints: ^2.1.1 + json_schema: ^5.2.0 + test: ^1.25.15 diff --git a/pkgs/code_assets/test/data/build_input_android.json b/pkgs/code_assets/test/data/build_input_android.json new file mode 100644 index 000000000..2d6da07cd --- /dev/null +++ b/pkgs/code_assets/test/data/build_input_android.json @@ -0,0 +1,28 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "native_code" + ], + "code": { + "android": { + "target_ndk_api": 21 + }, + "c_compiler": { + "ar": "/Users/dacoharkes/Library/Android/sdk/ndk/28.0.12674087/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar", + "cc": "/Users/dacoharkes/Library/Android/sdk/ndk/28.0.12674087/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang", + "ld": "/Users/dacoharkes/Library/Android/sdk/ndk/28.0.12674087/toolchains/llvm/prebuilt/darwin-x86_64/bin/ld.lld" + }, + "link_mode_preference": "dynamic", + "target_architecture": "arm", + "target_os": "android" + }, + "linking_enabled": true + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/c540d75d10834674921701cd6c3c7c15/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/Hook.build/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/c540d75d10834674921701cd6c3c7c15/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_input_ios.json b/pkgs/code_assets/test/data/build_input_ios.json new file mode 100644 index 000000000..3cb7e2972 --- /dev/null +++ b/pkgs/code_assets/test/data/build_input_ios.json @@ -0,0 +1,29 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "native_code" + ], + "code": { + "c_compiler": { + "ar": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar", + "cc": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang", + "ld": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld" + }, + "ios": { + "target_sdk": "iphonesimulator", + "target_version": 12 + }, + "link_mode_preference": "dynamic", + "target_architecture": "arm64", + "target_os": "ios" + }, + "linking_enabled": false + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/51d66b45a7c440edc44bf124509a5dda/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/build/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/51d66b45a7c440edc44bf124509a5dda/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_input_linux.json b/pkgs/code_assets/test/data/build_input_linux.json new file mode 100644 index 000000000..94f55c9ca --- /dev/null +++ b/pkgs/code_assets/test/data/build_input_linux.json @@ -0,0 +1,25 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "native_code" + ], + "code": { + "c_compiler": { + "ar": "/usr/lib/llvm-16/bin/llvm-ar", + "cc": "/usr/lib/llvm-16/bin/clang", + "ld": "/usr/lib/llvm-16/bin/ld.lld" + }, + "link_mode_preference": "dynamic", + "target_architecture": "x64", + "target_os": "linux" + }, + "linking_enabled": false + }, + "out_dir": "/usr/local/google/home/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/79cc7fbaec53b1465c3e388e6848234b/out/", + "out_dir_shared": "/usr/local/google/home/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/Hook.build/", + "out_file": "/usr/local/google/home/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/79cc7fbaec53b1465c3e388e6848234b/output.json", + "package_name": "my_package", + "package_root": "/usr/local/google/home/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_input_macos.json b/pkgs/code_assets/test/data/build_input_macos.json new file mode 100644 index 000000000..2edc403dd --- /dev/null +++ b/pkgs/code_assets/test/data/build_input_macos.json @@ -0,0 +1,37 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "native_code" + ], + "code": { + "c_compiler": { + "ar": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar", + "cc": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang", + "ld": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld" + }, + "link_mode_preference": "dynamic", + "macos": { + "target_version": 13 + }, + "target_architecture": "arm64", + "target_os": "macos" + }, + "linking_enabled": false + }, + "dependency_metadata": { + "some_package": { + "baz": 1, + "foo": "bar", + "qux": { + "bar": "baz" + } + } + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/build/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_input_windows.json b/pkgs/code_assets/test/data/build_input_windows.json new file mode 100644 index 000000000..e35f53acc --- /dev/null +++ b/pkgs/code_assets/test/data/build_input_windows.json @@ -0,0 +1,33 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "native_code" + ], + "code": { + "c_compiler": { + "ar": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.35.32215\\bin\\Hostx64\\x64\\lib.exe", + "cc": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.35.32215\\bin\\Hostx64\\x64\\cl.exe", + "env_script": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat", + "env_script_arguments": [], + "ld": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.35.32215\\bin\\Hostx64\\x64\\link.exe", + "windows": { + "developer_command_prompt": { + "arguments": [], + "script": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" + } + } + }, + "link_mode_preference": "dynamic", + "target_architecture": "x64", + "target_os": "windows" + }, + "linking_enabled": false + }, + "out_dir": "C:\\src\\dacoharkes\\playground\\my_package\\example\\.dart_tool\\native_assets_builder\\my_package\\190c04c47dc2f9eb26d2411b1968b2cf\\out\\", + "out_dir_shared": "C:\\src\\dacoharkes\\playground\\my_package\\example\\.dart_tool\\native_assets_builder\\shared\\my_package\\Hook.build\\", + "out_file": "C:\\src\\dacoharkes\\playground\\my_package\\example\\.dart_tool\\native_assets_builder\\my_package\\190c04c47dc2f9eb26d2411b1968b2cf\\output.json", + "package_name": "my_package", + "package_root": "C:\\src\\dacoharkes\\playground\\my_package\\", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_output_android.json b/pkgs/code_assets/test/data/build_output_android.json new file mode 100644 index 000000000..d2e10883d --- /dev/null +++ b/pkgs/code_assets/test/data/build_output_android.json @@ -0,0 +1,20 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "architecture": "arm", + "file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/c540d75d10834674921701cd6c3c7c15/out/libmy_package.so", + "id": "package:my_package/my_package_bindings_generated.dart", + "link_mode": { + "type": "dynamic_loading_bundle" + }, + "os": "android", + "type": "native_code" + } + ], + "dependencies": [ + "/Users/dacoharkes/src/dacoharkes/playground/my_package/src/my_package.c" + ], + "timestamp": "2025-02-17 18:23:19.000", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_output_linux.json b/pkgs/code_assets/test/data/build_output_linux.json new file mode 100644 index 000000000..874c092ff --- /dev/null +++ b/pkgs/code_assets/test/data/build_output_linux.json @@ -0,0 +1,20 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "architecture": "x64", + "file": "/usr/local/google/home/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/79cc7fbaec53b1465c3e388e6848234b/out/libmy_package.so", + "id": "package:my_package/my_package_bindings_generated.dart", + "link_mode": { + "type": "dynamic_loading_bundle" + }, + "os": "linux", + "type": "native_code" + } + ], + "dependencies": [ + "/usr/local/google/home/dacoharkes/src/dacoharkes/playground/my_package/src/my_package.c" + ], + "timestamp": "2025-02-17 17:36:39.000", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_output_macos.json b/pkgs/code_assets/test/data/build_output_macos.json new file mode 100644 index 000000000..32d0103f5 --- /dev/null +++ b/pkgs/code_assets/test/data/build_output_macos.json @@ -0,0 +1,72 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "architecture": "arm64", + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/ZnD2I3/native_add/.dart_tool/native_assets_builder/native_add/14857adfaff1b1829b79c612288f54d7/out/libnative_add.dylib", + "id": "package:native_add/src/native_add_bindings_generated.dart", + "link_mode": { + "type": "dynamic_loading_bundle" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/iv6i0d/native_add/.dart_tool/native_assets_builder/native_add/c6b312c90c95d2d98ffb6760a738fb36/out/libnative_add.a", + "id": "package:native_add/src/native_add_bindings_generated.dart", + "link_mode": { + "type": "static" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "id": "package:system_library/memory_executable.dart", + "link_mode": { + "type": "dynamic_loading_executable" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "id": "package:system_library/memory_process.dart", + "link_mode": { + "type": "dynamic_loading_process" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "id": "package:system_library/memory_system.dart", + "link_mode": { + "type": "dynamic_loading_system", + "uri": "libc.dylib" + }, + "os": "macos", + "type": "native_code" + } + ], + "assetsForLinking": { + "package_with_linker": [ + { + "architecture": "arm64", + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/iv6i0d/native_add/.dart_tool/native_assets_builder/native_add/c6b312c90c95d2d98ffb6760a738fb36/out/libnative_add.a", + "id": "package:native_add/src/native_add_bindings_generated.dart", + "link_mode": { + "type": "static" + }, + "os": "macos", + "type": "native_code" + } + ] + }, + "dependencies": [ + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/ZnD2I3/native_add/src/native_add.c" + ], + "timestamp": "2025-02-10 16:47:01.000", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/build_output_windows.json b/pkgs/code_assets/test/data/build_output_windows.json new file mode 100644 index 000000000..e194435b3 --- /dev/null +++ b/pkgs/code_assets/test/data/build_output_windows.json @@ -0,0 +1,20 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "architecture": "x64", + "file": "C:\\src\\dacoharkes\\playground\\my_package\\example\\.dart_tool\\native_assets_builder\\my_package\\190c04c47dc2f9eb26d2411b1968b2cf\\out\\my_package.dll", + "id": "package:my_package/my_package_bindings_generated.dart", + "link_mode": { + "type": "dynamic_loading_bundle" + }, + "os": "windows", + "type": "native_code" + } + ], + "dependencies": [ + "C:\\src\\dacoharkes\\playground\\my_package\\src\\my_package.c" + ], + "timestamp": "2025-02-17 09:57:36.000", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/link_input_macos.json b/pkgs/code_assets/test/data/link_input_macos.json new file mode 100644 index 000000000..5f33a2af4 --- /dev/null +++ b/pkgs/code_assets/test/data/link_input_macos.json @@ -0,0 +1,39 @@ +{ + "$schema": "../../doc/schema/sdk/link_input.schema.json", + "assets": [ + { + "architecture": "arm64", + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/iv6i0d/native_add/.dart_tool/native_assets_builder/native_add/c6b312c90c95d2d98ffb6760a738fb36/out/libnative_add.a", + "id": "package:native_add/src/native_add_bindings_generated.dart", + "link_mode": { + "type": "static" + }, + "os": "macos", + "type": "native_code" + } + ], + "config": { + "build_asset_types": [ + "native_code" + ], + "code": { + "c_compiler": { + "ar": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar", + "cc": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang", + "ld": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld" + }, + "link_mode_preference": "dynamic", + "macos": { + "target_version": 13 + }, + "target_architecture": "arm64", + "target_os": "macos" + } + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/link/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/data/link_output_macos.json b/pkgs/code_assets/test/data/link_output_macos.json new file mode 100644 index 000000000..2d813cc3a --- /dev/null +++ b/pkgs/code_assets/test/data/link_output_macos.json @@ -0,0 +1,58 @@ +{ + "$schema": "../../doc/schema/hook/link_output.schema.json", + "assets": [ + { + "architecture": "arm64", + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/ZnD2I3/native_add/.dart_tool/native_assets_builder/native_add/14857adfaff1b1829b79c612288f54d7/out/libnative_add.dylib", + "id": "package:native_add/src/native_add_bindings_generated.dart", + "link_mode": { + "type": "dynamic_loading_bundle" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/iv6i0d/native_add/.dart_tool/native_assets_builder/native_add/c6b312c90c95d2d98ffb6760a738fb36/out/libnative_add.a", + "id": "package:native_add/src/native_add_bindings_generated.dart", + "link_mode": { + "type": "static" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "id": "package:system_library/memory_executable.dart", + "link_mode": { + "type": "dynamic_loading_executable" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "id": "package:system_library/memory_process.dart", + "link_mode": { + "type": "dynamic_loading_process" + }, + "os": "macos", + "type": "native_code" + }, + { + "architecture": "arm64", + "id": "package:system_library/memory_system.dart", + "link_mode": { + "type": "dynamic_loading_system", + "uri": "libc.dylib" + }, + "os": "macos", + "type": "native_code" + } + ], + "dependencies": [ + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/ZnD2I3/native_add/src/native_add.c" + ], + "timestamp": "2025-02-10 16:47:01.000", + "version": "1.9.0" +} diff --git a/pkgs/code_assets/test/schema/helpers.dart b/pkgs/code_assets/test/schema/helpers.dart new file mode 100644 index 000000000..8cf5b7cfe --- /dev/null +++ b/pkgs/code_assets/test/schema/helpers.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +export '../../../hook/test/schema/helpers.dart'; diff --git a/pkgs/code_assets/test/schema/schema_test.dart b/pkgs/code_assets/test/schema/schema_test.dart new file mode 100644 index 000000000..3627dadf0 --- /dev/null +++ b/pkgs/code_assets/test/schema/schema_test.dart @@ -0,0 +1,230 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: avoid_dynamic_calls + +import 'dart:convert'; + +import 'package:json_schema/json_schema.dart'; + +import 'helpers.dart'; + +void main() { + final schemasUri = packageUri.resolve('doc/schema/'); + final hookSchemasUri = packageUri.resolve('../hook/doc/schema/'); + final allSchemas = loadSchemas([schemasUri, hookSchemasUri]); + + final testDataUri = packageUri.resolve('test/data/'); + final allTestData = loadTestsData(testDataUri); + + testAllTestData(allSchemas, allTestData); + + const dataSuffix = '_macos'; + testFieldsHook( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + dataSuffix: dataSuffix, + ); + + testFields( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + dataSuffix: dataSuffix, + fields: _codeFields(allTestData), + ); + + testFields( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + dataSuffix: '_windows', + fields: _codeFieldsWindows, + ); + + testFields( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + dataSuffix: '_ios', + fields: _codeFieldsIOS, + ); + + testFields( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + dataSuffix: '_android', + fields: _codeFieldsAndroid, + ); +} + +Uri packageUri = findPackageRoot('code_assets'); + +FieldsFunction _codeFields(AllTestData allTestData) { + final dataUri = packageUri.resolve('test/data/build_output_macos.json'); + final assets = + ((jsonDecode(allTestData[dataUri]!) as Map)['assets'] + as List) + .cast>(); + late int dynamicLoadingBundledIndex, dynamicLoadingSystemIndex, staticIndex; + for (var i = 0; i < assets.length; i++) { + final asset = assets[i]; + switch (asset['link_mode']['type']) { + case 'dynamic_loading_bundle': + dynamicLoadingBundledIndex = i; + case 'dynamic_loading_system': + dynamicLoadingSystemIndex = i; + case 'static': + staticIndex = i; + } + } + + List<(List, void Function(ValidationResults result))> codeFields({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, + }) { + const requiredCodeAssetFields = [ + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + // ['architecture'], + ['os'], + ['id'], + ['link_mode'], + ['link_mode', 'type'], + ]; + + return <(List, void Function(ValidationResults result))>[ + if (inputOrOutput == InputOrOutput.input) ...[ + (['config', 'code', 'c_compiler'], expectOptionalFieldMissing), + (['config', 'code', 'c_compiler', 'ar'], expectRequiredFieldMissing), + (['config', 'code', 'c_compiler', 'cc'], expectRequiredFieldMissing), + (['config', 'code', 'c_compiler', 'ld'], expectRequiredFieldMissing), + (['config', 'code', 'macos'], expectRequiredFieldMissing), + ( + ['config', 'code', 'macos', 'target_version'], + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + expectOptionalFieldMissing, + ), + if (hook == Hook.link) ...[ + for (final field in requiredCodeAssetFields) + (['assets', 0, ...field], expectRequiredFieldMissing), + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + (['assets', 0, 'architecture'], expectOptionalFieldMissing), + ], + ], + if (inputOrOutput == InputOrOutput.output) ...[ + for (final field in requiredCodeAssetFields) + (['assets', 0, ...field], expectRequiredFieldMissing), + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + (['assets', 0, 'architecture'], expectOptionalFieldMissing), + if (hook == Hook.build) ...[ + for (final field in requiredCodeAssetFields) + ( + ['assetsForLinking', 'package_with_linker', 0, ...field], + expectRequiredFieldMissing, + ), + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + ( + ['assetsForLinking', 'package_with_linker', 0, 'architecture'], + expectOptionalFieldMissing, + ), + ], + (['assets', staticIndex, 'file'], expectRequiredFieldMissing), + ( + ['assets', dynamicLoadingBundledIndex, 'file'], + expectRequiredFieldMissing, + ), + ( + ['assets', dynamicLoadingSystemIndex, 'link_mode', 'uri'], + expectRequiredFieldMissing, + ), + ], + ]; + } + + return codeFields; +} + +List<(List, void Function(ValidationResults result))> +_codeFieldsWindows({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, +}) => <(List, void Function(ValidationResults result))>[ + if (inputOrOutput == InputOrOutput.input && hook == Hook.build) ...[ + ( + ['config', 'code', 'c_compiler', 'env_script'], + expectOptionalFieldMissing, + ), + ( + ['config', 'code', 'c_compiler', 'env_script_arguments'], + expectOptionalFieldMissing, + ), + (['config', 'code', 'c_compiler', 'windows'], expectRequiredFieldMissing), + ( + ['config', 'code', 'c_compiler', 'windows', 'developer_command_prompt'], + expectOptionalFieldMissing, + ), + ( + [ + 'config', + 'code', + 'c_compiler', + 'windows', + 'developer_command_prompt', + 'script', + ], + expectRequiredFieldMissing, + ), + ( + [ + 'config', + 'code', + 'c_compiler', + 'windows', + 'developer_command_prompt', + 'arguments', + ], + expectRequiredFieldMissing, + ), + ], +]; + +List<(List, void Function(ValidationResults result))> _codeFieldsIOS({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, +}) => <(List, void Function(ValidationResults result))>[ + if (inputOrOutput == InputOrOutput.input && hook == Hook.build) ...[ + (['config', 'code', 'ios'], expectRequiredFieldMissing), + ( + ['config', 'code', 'ios', 'target_sdk'], + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + expectOptionalFieldMissing, + ), + ( + ['config', 'code', 'ios', 'target_version'], + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + expectOptionalFieldMissing, + ), + ], +]; + +List<(List, void Function(ValidationResults result))> +_codeFieldsAndroid({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, +}) => <(List, void Function(ValidationResults result))>[ + if (inputOrOutput == InputOrOutput.input && hook == Hook.build) ...[ + (['config', 'code', 'android'], expectRequiredFieldMissing), + ( + ['config', 'code', 'android', 'target_ndk_api'], + // TODO(https://github.com/dart-lang/native/issues/2039): Make required. + expectOptionalFieldMissing, + ), + ], +]; diff --git a/pkgs/data_assets/analysis_options.yaml b/pkgs/data_assets/analysis_options.yaml new file mode 100644 index 000000000..349b9d631 --- /dev/null +++ b/pkgs/data_assets/analysis_options.yaml @@ -0,0 +1,17 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + errors: + todo: ignore + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + - dangling_library_doc_comments + - prefer_const_declarations + - prefer_expression_function_bodies + - prefer_final_in_for_each + - prefer_final_locals diff --git a/pkgs/data_assets/doc/schema/README.md b/pkgs/data_assets/doc/schema/README.md new file mode 100644 index 000000000..b828da46a --- /dev/null +++ b/pkgs/data_assets/doc/schema/README.md @@ -0,0 +1,17 @@ +These schemas document the protocol for build and link hooks with data assets. + +* If you are a hook author, you should likely be using a hook-helper-package's + Dart API (such as `package:native_assets_cli`) which abstracts away from + syntactic changes. +* If you are a hook-helper-package author, you should likely be using a + hook-helper-package's Dart API (such as `package:native_assets_cli`) which + abstracts away from syntactic changes. If you do want to directly interact + with the JSON, you can find the relevant schemas in [hook/](hook/). +* If you are an SDK author, you should likely be using the SDK helper package + (`package:native_assets_builder`) which abstracts away from syntactic changes. + If you do want to directly interact with the JSON, you can find the relevant + schemas in [sdk/](sdk/). + +These schemas are an extension of the base protocol: + +* [`package:hook`/doc/schema](../../../hook/doc/schema/) diff --git a/pkgs/data_assets/doc/schema/hook/build_input.schema.json b/pkgs/data_assets/doc/schema/hook/build_input.schema.json new file mode 100644 index 000000000..77e5b9e91 --- /dev/null +++ b/pkgs/data_assets/doc/schema/hook/build_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:hook BuildInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildInput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/hook/build_output.schema.json b/pkgs/data_assets/doc/schema/hook/build_output.schema.json new file mode 100644 index 000000000..c8183332e --- /dev/null +++ b/pkgs/data_assets/doc/schema/hook/build_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:hook BuildOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildOutput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/hook/link_input.schema.json b/pkgs/data_assets/doc/schema/hook/link_input.schema.json new file mode 100644 index 000000000..a63277b38 --- /dev/null +++ b/pkgs/data_assets/doc/schema/hook/link_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:hook LinkInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkInput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/hook/link_output.schema.json b/pkgs/data_assets/doc/schema/hook/link_output.schema.json new file mode 100644 index 000000000..e5eb980be --- /dev/null +++ b/pkgs/data_assets/doc/schema/hook/link_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:hook LinkOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkOutput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/hook/shared_definitions.schema.json b/pkgs/data_assets/doc/schema/hook/shared_definitions.schema.json new file mode 100644 index 000000000..06e436287 --- /dev/null +++ b/pkgs/data_assets/doc/schema/hook/shared_definitions.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:hook shared definitions", + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json" + }, + { + "$ref": "../shared/shared_definitions.schema.json#" + } + ], + "definitions": { + "BuildInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/BuildInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/BuildOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildOutput" + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookInput" + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookOutput" + } + ] + }, + "LinkInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/LinkInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/hook/shared_definitions.schema.json#/definitions/LinkOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/data_assets/doc/schema/sdk/build_input.schema.json b/pkgs/data_assets/doc/schema/sdk/build_input.schema.json new file mode 100644 index 000000000..d30a48769 --- /dev/null +++ b/pkgs/data_assets/doc/schema/sdk/build_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:sdk BuildInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildInput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/sdk/build_output.schema.json b/pkgs/data_assets/doc/schema/sdk/build_output.schema.json new file mode 100644 index 000000000..b8aa366b2 --- /dev/null +++ b/pkgs/data_assets/doc/schema/sdk/build_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:sdk BuildOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildOutput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/sdk/link_input.schema.json b/pkgs/data_assets/doc/schema/sdk/link_input.schema.json new file mode 100644 index 000000000..986efa20e --- /dev/null +++ b/pkgs/data_assets/doc/schema/sdk/link_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:sdk LinkInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkInput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/sdk/link_output.schema.json b/pkgs/data_assets/doc/schema/sdk/link_output.schema.json new file mode 100644 index 000000000..4188c8907 --- /dev/null +++ b/pkgs/data_assets/doc/schema/sdk/link_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:sdk LinkOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkOutput" + } + ] +} diff --git a/pkgs/data_assets/doc/schema/sdk/shared_definitions.schema.json b/pkgs/data_assets/doc/schema/sdk/shared_definitions.schema.json new file mode 100644 index 000000000..615bd2374 --- /dev/null +++ b/pkgs/data_assets/doc/schema/sdk/shared_definitions.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:sdk shared definitions", + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json" + }, + { + "$ref": "../shared/shared_definitions.schema.json#" + } + ], + "definitions": { + "BuildInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/BuildInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/BuildOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildOutput" + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookInput" + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookOutput" + } + ] + }, + "LinkInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/LinkInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../../../../hook/doc/schema/sdk/shared_definitions.schema.json#/definitions/LinkOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/data_assets/doc/schema/shared/shared_definitions.schema.json b/pkgs/data_assets/doc/schema/shared/shared_definitions.schema.json new file mode 100644 index 000000000..49c76ae9f --- /dev/null +++ b/pkgs/data_assets/doc/schema/shared/shared_definitions.schema.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:data_assets party:shared shared definitions", + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json" + } + ], + "definitions": { + "Asset": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/Asset" + }, + { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "const": "data" + }, + { + "type": "string" + } + ] + } + }, + "if": { + "properties": { + "type": { + "const": "data" + } + } + }, + "then": { + "properties": { + "file": { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/absolutePath" + }, + "name": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "file", + "name", + "package" + ] + } + } + ] + }, + "BuildInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/BuildOutput" + }, + { + "properties": { + "assetsForLinking": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + } + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/HookInput" + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + } + ] + }, + "LinkInput": { + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "../../../../hook/doc/schema/shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/data_assets/pubspec.yaml b/pkgs/data_assets/pubspec.yaml new file mode 100644 index 000000000..d1fae6c83 --- /dev/null +++ b/pkgs/data_assets/pubspec.yaml @@ -0,0 +1,17 @@ +name: data_assets +version: 0.1.0-wip +repository: https://github.com/dart-lang/native/tree/main/pkgs/data_assets + +publish_to: none + +environment: + sdk: '>=3.7.0 <4.0.0' + +dependencies: + hook: + path: ../hook/ + +dev_dependencies: + dart_flutter_team_lints: ^2.1.1 + json_schema: ^5.2.0 + test: ^1.25.15 diff --git a/pkgs/data_assets/test/data/build_input.json b/pkgs/data_assets/test/data/build_input.json new file mode 100644 index 000000000..bcbf60354 --- /dev/null +++ b/pkgs/data_assets/test/data/build_input.json @@ -0,0 +1,24 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "data" + ], + "linking_enabled": false + }, + "dependency_metadata": { + "some_package": { + "baz": 1, + "foo": "bar", + "qux": { + "bar": "baz" + } + } + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/build/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/data_assets/test/data/build_output.json b/pkgs/data_assets/test/data/build_output.json new file mode 100644 index 000000000..a6d4d870a --- /dev/null +++ b/pkgs/data_assets/test/data/build_output.json @@ -0,0 +1,39 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_0.json", + "name": "assets/data_0.json", + "package": "simple_link", + "type": "data" + }, + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_1.json", + "name": "assets/data_1.json", + "package": "simple_link", + "type": "data" + } + ], + "assetsForLinking": { + "package_with_linker": [ + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_0.json", + "name": "assets/data_0.json", + "package": "simple_link", + "type": "data" + }, + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_1.json", + "name": "assets/data_1.json", + "package": "simple_link", + "type": "data" + } + ] + }, + "dependencies": [ + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_2.json", + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_3.json" + ], + "timestamp": "2025-02-11 11:20:20.000", + "version": "1.9.0" +} diff --git a/pkgs/data_assets/test/data/link_input.json b/pkgs/data_assets/test/data/link_input.json new file mode 100644 index 000000000..5f2c573f6 --- /dev/null +++ b/pkgs/data_assets/test/data/link_input.json @@ -0,0 +1,29 @@ +{ + "$schema": "../../doc/schema/sdk/link_input.schema.json", + "assets": [ + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_0.json", + "name": "assets/data_0.json", + "package": "simple_link", + "type": "data" + }, + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_1.json", + "name": "assets/data_1.json", + "package": "simple_link", + "type": "data" + } + ], + "config": { + "build_asset_types": [ + "data" + ], + "linking_enabled": false + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/link/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/data_assets/test/data/link_output.json b/pkgs/data_assets/test/data/link_output.json new file mode 100644 index 000000000..a76683fc5 --- /dev/null +++ b/pkgs/data_assets/test/data/link_output.json @@ -0,0 +1,23 @@ +{ + "$schema": "../../doc/schema/hook/link_output.schema.json", + "assets": [ + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_0.json", + "name": "assets/data_0.json", + "package": "simple_link", + "type": "data" + }, + { + "file": "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_1.json", + "name": "assets/data_1.json", + "package": "simple_link", + "type": "data" + } + ], + "dependencies": [ + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_2.json", + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_3.json" + ], + "timestamp": "2025-02-11 11:20:20.000", + "version": "1.9.0" +} diff --git a/pkgs/data_assets/test/schema/helpers.dart b/pkgs/data_assets/test/schema/helpers.dart new file mode 100644 index 000000000..8cf5b7cfe --- /dev/null +++ b/pkgs/data_assets/test/schema/helpers.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +export '../../../hook/test/schema/helpers.dart'; diff --git a/pkgs/data_assets/test/schema/schema_test.dart b/pkgs/data_assets/test/schema/schema_test.dart new file mode 100644 index 000000000..3552497e3 --- /dev/null +++ b/pkgs/data_assets/test/schema/schema_test.dart @@ -0,0 +1,59 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:json_schema/json_schema.dart'; + +import 'helpers.dart'; + +void main() { + final schemasUri = packageUri.resolve('doc/schema/'); + final hookSchemasUri = packageUri.resolve('../hook/doc/schema/'); + final allSchemas = loadSchemas([schemasUri, hookSchemasUri]); + + final testDataUri = packageUri.resolve('test/data/'); + final allTestData = loadTestsData(testDataUri); + + testAllTestData(allSchemas, allTestData); + + testFieldsHook( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + ); + + testFields( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + fields: _dataFields, + ); +} + +Uri packageUri = findPackageRoot('data_assets'); + +const _dataAssetFields = ['package', 'name', 'file']; + +List<(List, void Function(ValidationResults result))> _dataFields({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, +}) => <(List, void Function(ValidationResults result))>[ + if (inputOrOutput == InputOrOutput.input) ...[ + if (hook == Hook.link) ...[ + for (final field in _dataAssetFields) + (['assets', 0, field], expectRequiredFieldMissing), + ], + ], + if (inputOrOutput == InputOrOutput.output) ...[ + for (final field in _dataAssetFields) + (['assets', 0, field], expectRequiredFieldMissing), + if (hook == Hook.build) ...[ + for (final field in _dataAssetFields) + ( + ['assetsForLinking', 'package_with_linker', 0, field], + expectRequiredFieldMissing, + ), + ], + ], +]; diff --git a/pkgs/hook/analysis_options.yaml b/pkgs/hook/analysis_options.yaml new file mode 100644 index 000000000..349b9d631 --- /dev/null +++ b/pkgs/hook/analysis_options.yaml @@ -0,0 +1,17 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + errors: + todo: ignore + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + - dangling_library_doc_comments + - prefer_const_declarations + - prefer_expression_function_bodies + - prefer_final_in_for_each + - prefer_final_locals diff --git a/pkgs/hook/doc/schema/README.md b/pkgs/hook/doc/schema/README.md new file mode 100644 index 000000000..559b53c94 --- /dev/null +++ b/pkgs/hook/doc/schema/README.md @@ -0,0 +1,19 @@ +These schemas document the protocol for build and link hooks. + +* If you are a hook author, you should likely be using a hook-helper-package + Dart API (such as `package:native_toolchain_c`) instead of directly consuming + and producing JSON. +* If you are a hook-helper-package author, you should likely be using a + hook-helper-package's Dart API (such as `package:native_assets_cli`) which + abstracts away from syntactic changes. If you do want to directly interact + with the JSON, you can find the relevant schemas in [hook/](hook/). +* If you are an SDK author, you should likely be using the SDK helper package + (`package:native_assets_builder`) which abstracts away from syntactic changes. + If you do want to directly interact with the JSON, you can find the relevant + schemas in [sdk/](sdk/). + +The base hook protocol without any extensions does not provide much use. See the +documentation for known extensions: + +* [`package:code_assets`/doc/schema](../../../code_assets/doc/schema/) +* [`package:data_assets`/doc/schema](../../../data_assets/doc/schema/) diff --git a/pkgs/hook/doc/schema/hook/build_input.schema.json b/pkgs/hook/doc/schema/hook/build_input.schema.json new file mode 100644 index 000000000..96d228014 --- /dev/null +++ b/pkgs/hook/doc/schema/hook/build_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:hook BuildInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildInput" + } + ] +} diff --git a/pkgs/hook/doc/schema/hook/build_output.schema.json b/pkgs/hook/doc/schema/hook/build_output.schema.json new file mode 100644 index 000000000..167ada4c0 --- /dev/null +++ b/pkgs/hook/doc/schema/hook/build_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:hook BuildOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildOutput" + } + ] +} diff --git a/pkgs/hook/doc/schema/hook/link_input.schema.json b/pkgs/hook/doc/schema/hook/link_input.schema.json new file mode 100644 index 000000000..d11dfc219 --- /dev/null +++ b/pkgs/hook/doc/schema/hook/link_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:hook LinkInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkInput" + } + ] +} diff --git a/pkgs/hook/doc/schema/hook/link_output.schema.json b/pkgs/hook/doc/schema/hook/link_output.schema.json new file mode 100644 index 000000000..d4471d766 --- /dev/null +++ b/pkgs/hook/doc/schema/hook/link_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:hook LinkOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkOutput" + } + ] +} diff --git a/pkgs/hook/doc/schema/hook/shared_definitions.schema.json b/pkgs/hook/doc/schema/hook/shared_definitions.schema.json new file mode 100644 index 000000000..59a63bac1 --- /dev/null +++ b/pkgs/hook/doc/schema/hook/shared_definitions.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:hook shared definitions", + "allOf": [ + { + "$ref": "../shared/shared_definitions.schema.json#" + } + ], + "definitions": { + "BuildInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "unevaluatedProperties": false, + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildOutput" + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "properties": { + "out_file": { + "$comment": "'out_file' is not provided by older SDKs. Then, it must be $out_dir/output.json." + }, + "version": { + "deprecated": true, + "$comment": "Future SDKs will no longer provide 'version'." + } + } + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookOutput" + }, + { + "required": [ + "version" + ] + } + ] + }, + "LinkInput": { + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkInput" + } + ] + }, + "LinkOutput": { + "unevaluatedProperties": false, + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/hook/doc/schema/sdk/build_input.schema.json b/pkgs/hook/doc/schema/sdk/build_input.schema.json new file mode 100644 index 000000000..38bfbca0a --- /dev/null +++ b/pkgs/hook/doc/schema/sdk/build_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:sdk BuildInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildInput" + } + ] +} diff --git a/pkgs/hook/doc/schema/sdk/build_output.schema.json b/pkgs/hook/doc/schema/sdk/build_output.schema.json new file mode 100644 index 000000000..9078212db --- /dev/null +++ b/pkgs/hook/doc/schema/sdk/build_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:sdk BuildOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/BuildOutput" + } + ] +} diff --git a/pkgs/hook/doc/schema/sdk/link_input.schema.json b/pkgs/hook/doc/schema/sdk/link_input.schema.json new file mode 100644 index 000000000..c282c5fa7 --- /dev/null +++ b/pkgs/hook/doc/schema/sdk/link_input.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:sdk LinkInput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkInput" + } + ] +} diff --git a/pkgs/hook/doc/schema/sdk/link_output.schema.json b/pkgs/hook/doc/schema/sdk/link_output.schema.json new file mode 100644 index 000000000..bdf2a0dd6 --- /dev/null +++ b/pkgs/hook/doc/schema/sdk/link_output.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:sdk LinkOutput", + "allOf": [ + { + "$ref": "shared_definitions.schema.json#/definitions/LinkOutput" + } + ] +} diff --git a/pkgs/hook/doc/schema/sdk/shared_definitions.schema.json b/pkgs/hook/doc/schema/sdk/shared_definitions.schema.json new file mode 100644 index 000000000..3ff2a2b1e --- /dev/null +++ b/pkgs/hook/doc/schema/sdk/shared_definitions.schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:sdk shared definitions", + "allOf": [ + { + "$ref": "../shared/shared_definitions.schema.json#" + } + ], + "definitions": { + "BuildInput": { + "unevaluatedProperties": false, + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildInput" + } + ] + }, + "BuildOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/BuildOutput" + } + ] + }, + "HookInput": { + "allOf": [ + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookInput" + }, + { + "properties": { + "out_file": { + "$comment": "'out_file' is not read by older hooks. If the file doesn't exist, then it must be $out_dir/output.json." + }, + "version": { + "$comment": "Older SDKs will read 'version', so it must be provided." + } + }, + "required": [ + "out_file", + "version" + ] + } + ] + }, + "HookOutput": { + "allOf": [ + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/HookOutput" + } + ] + }, + "LinkInput": { + "unevaluatedProperties": false, + "allOf": [ + { + "$ref": "#/definitions/HookInput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkInput" + }, + { + "required": [ + "out_file", + "version" + ] + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + }, + { + "$ref": "../shared/shared_definitions.schema.json#/definitions/LinkOutput" + } + ] + } + } +} diff --git a/pkgs/hook/doc/schema/shared/shared_definitions.schema.json b/pkgs/hook/doc/schema/shared/shared_definitions.schema.json new file mode 100644 index 000000000..f410267cb --- /dev/null +++ b/pkgs/hook/doc/schema/shared/shared_definitions.schema.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "package:hook party:shared shared definitions", + "definitions": { + "Asset": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "BuildConfig": { + "allOf": [ + { + "$ref": "#/definitions/Config" + }, + { + "properties": { + "linking_enabled": { + "type": "boolean" + } + }, + "required": [ + "linking_enabled" + ] + } + ] + }, + "BuildInput": { + "properties": { + "config": { + "$ref": "#/definitions/BuildConfig" + }, + "dependency_metadata": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": true + } + } + }, + "allOf": [ + { + "$ref": "#/definitions/HookInput" + } + ] + }, + "BuildOutput": { + "type": "object", + "properties": { + "assetsForLinking": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "metadata": { + "type": "object", + "additionalProperties": true + } + }, + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + } + ] + }, + "Config": { + "type": "object", + "properties": { + "build_asset_types": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "build_asset_types" + ] + }, + "HookInput": { + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "config": { + "$ref": "#/definitions/Config" + }, + "out_dir": { + "$ref": "#/definitions/absolutePath" + }, + "out_dir_shared": { + "$ref": "#/definitions/absolutePath" + }, + "out_file": { + "$ref": "#/definitions/absolutePath" + }, + "package_name": { + "type": "string" + }, + "package_root": { + "$ref": "#/definitions/absolutePath" + }, + "version": { + "type": "string" + } + }, + "required": [ + "config", + "out_dir", + "out_dir_shared", + "package_name", + "package_root", + "version" + ] + }, + "HookOutput": { + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/absolutePath" + } + }, + "timestamp": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "timestamp", + "version" + ] + }, + "LinkInput": { + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "resource_identifiers": { + "$ref": "#/definitions/absolutePath" + } + }, + "allOf": [ + { + "$ref": "#/definitions/HookInput" + } + ] + }, + "LinkOutput": { + "allOf": [ + { + "$ref": "#/definitions/HookOutput" + } + ] + }, + "absolutePath": { + "type": "string", + "pattern": "^(\\/|[A-Za-z]:)" + }, + "relativePath": { + "type": "string", + "pattern": "^([A-Za-z])" + } + } +} diff --git a/pkgs/hook/pubspec.yaml b/pkgs/hook/pubspec.yaml new file mode 100644 index 000000000..f24cbbd23 --- /dev/null +++ b/pkgs/hook/pubspec.yaml @@ -0,0 +1,14 @@ +name: hook +version: 0.1.0-wip +repository: https://github.com/dart-lang/native/tree/main/pkgs/hook + +publish_to: none + +environment: + sdk: '>=3.7.0 <4.0.0' + +dev_dependencies: + dart_flutter_team_lints: ^2.1.1 + json_schema: ^5.2.0 + path: ^1.9.1 + test: ^1.25.15 diff --git a/pkgs/hook/test/data/build_input.json b/pkgs/hook/test/data/build_input.json new file mode 100644 index 000000000..5f4161b15 --- /dev/null +++ b/pkgs/hook/test/data/build_input.json @@ -0,0 +1,24 @@ +{ + "$schema": "../../doc/schema/sdk/build_input.schema.json", + "config": { + "build_asset_types": [ + "my_asset_type" + ], + "linking_enabled": false + }, + "dependency_metadata": { + "some_package": { + "baz": 1, + "foo": "bar", + "qux": { + "bar": "baz" + } + } + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/build/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/hook/test/data/build_output.json b/pkgs/hook/test/data/build_output.json new file mode 100644 index 000000000..d189e75b9 --- /dev/null +++ b/pkgs/hook/test/data/build_output.json @@ -0,0 +1,38 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "some_key": "some_value", + "type": "some_asset_type" + }, + { + "some_other_key": "some_value", + "type": "some_other_asset_type" + } + ], + "assetsForLinking": { + "package_with_linker": [ + { + "some_key": "some_value", + "type": "some_asset_type" + }, + { + "some_other_key": "some_value", + "type": "some_other_asset_type" + } + ] + }, + "dependencies": [ + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_2.json", + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_3.json" + ], + "metadata": { + "baz": 1, + "foo": "bar", + "qux": { + "bar": "baz" + } + }, + "timestamp": "2025-02-11 11:20:20.000", + "version": "1.9.0" +} diff --git a/pkgs/hook/test/data/build_output_windows.json b/pkgs/hook/test/data/build_output_windows.json new file mode 100644 index 000000000..e194435b3 --- /dev/null +++ b/pkgs/hook/test/data/build_output_windows.json @@ -0,0 +1,20 @@ +{ + "$schema": "../../doc/schema/hook/build_output.schema.json", + "assets": [ + { + "architecture": "x64", + "file": "C:\\src\\dacoharkes\\playground\\my_package\\example\\.dart_tool\\native_assets_builder\\my_package\\190c04c47dc2f9eb26d2411b1968b2cf\\out\\my_package.dll", + "id": "package:my_package/my_package_bindings_generated.dart", + "link_mode": { + "type": "dynamic_loading_bundle" + }, + "os": "windows", + "type": "native_code" + } + ], + "dependencies": [ + "C:\\src\\dacoharkes\\playground\\my_package\\src\\my_package.c" + ], + "timestamp": "2025-02-17 09:57:36.000", + "version": "1.9.0" +} diff --git a/pkgs/hook/test/data/link_input.json b/pkgs/hook/test/data/link_input.json new file mode 100644 index 000000000..aa8a685be --- /dev/null +++ b/pkgs/hook/test/data/link_input.json @@ -0,0 +1,25 @@ +{ + "$schema": "../../doc/schema/sdk/link_input.schema.json", + "assets": [ + { + "some_key": "some_value", + "type": "some_asset_type" + }, + { + "some_other_key": "some_value", + "type": "some_other_asset_type" + } + ], + "config": { + "build_asset_types": [ + "some_asset_type", + "some_other_asset_type" + ] + }, + "out_dir": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/out/", + "out_dir_shared": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/shared/my_package/link/", + "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", + "package_name": "my_package", + "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "version": "1.9.0" +} diff --git a/pkgs/hook/test/data/link_output.json b/pkgs/hook/test/data/link_output.json new file mode 100644 index 000000000..42d0ac716 --- /dev/null +++ b/pkgs/hook/test/data/link_output.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../doc/schema/hook/link_output.schema.json", + "assets": [ + { + "some_key": "some_value", + "type": "some_asset_type" + }, + { + "some_other_key": "some_value", + "type": "some_other_asset_type" + } + ], + "dependencies": [ + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_2.json", + "/private/var/folders/2y/mngq9h194yzglt4kzttzfq6800klzg/T/0s5bKi/simple_link/assets/data_3.json" + ], + "timestamp": "2025-02-11 11:20:20.000", + "version": "1.9.0" +} diff --git a/pkgs/hook/test/schema/helpers.dart b/pkgs/hook/test/schema/helpers.dart new file mode 100644 index 000000000..6aea7e8e2 --- /dev/null +++ b/pkgs/hook/test/schema/helpers.dart @@ -0,0 +1,365 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: avoid_dynamic_calls + +import 'dart:convert'; +import 'dart:io'; + +import 'package:json_schema/json_schema.dart'; +import 'package:test/test.dart'; + +/// Test files are run in a variety of ways, find this package root in all. +/// +/// Test files can be run from source from any working directory. The Dart SDK +/// `tools/test.py` runs them from the root of the SDK for example. +/// +/// Test files can be run from dill from the root of package. `package:test` +/// does this. +/// +/// https://github.com/dart-lang/test/issues/110 +Uri findPackageRoot(String packageName) { + final script = Platform.script; + final fileName = script.name; + if (fileName.endsWith('.dart')) { + // We're likely running from source. + var directory = script.resolve('.'); + while (true) { + final dirName = directory.name; + if (dirName == packageName) { + return directory; + } + final parent = directory.resolve('..'); + if (parent == directory) break; + directory = parent; + } + } else if (fileName.endsWith('.dill')) { + final cwd = Directory.current.uri; + final dirName = cwd.name; + if (dirName == packageName) { + return cwd; + } + } + throw StateError( + "Could not find package root for package '$packageName'. " + 'Tried finding the package root via Platform.script ' + "'${Platform.script.toFilePath()}' and Directory.current " + "'${Directory.current.uri.toFilePath()}'.", + ); +} + +extension on Uri { + String get name => pathSegments.where((e) => e != '').last; +} + +typedef AllSchemas = Map; + +/// The schemas are reused in tests, so load and parse them all. +AllSchemas loadSchemas(List directories) { + final allSchemaJsons = >{}; + for (final dirUri in directories) { + Directory.fromUri(dirUri).listSync(recursive: true).forEach((file) { + if (file is File && file.path.endsWith('.schema.json')) { + final json = + jsonDecode(file.readAsStringSync()) as Map; + allSchemaJsons[file.uri] = json; + } + }); + } + final allSchemas = {}; + for (final entry in allSchemaJsons.entries) { + final schema = JsonSchema.create( + entry.value, + refProvider: RefProvider.sync((String originalRef) { + if (originalRef.startsWith( + 'https://json-schema.org/draft/2020-12/schema#', + )) { + throw UnsupportedError('This is not supported in json_schema.'); + } + // Unmangle refs + var ref = originalRef; + // https://github.com/Workiva/json_schema/issues/202 + if (ref.startsWith('/')) { + ref = ref.substring(1); + } + if (ref.startsWith('hook/doc/schema/')) { + ref = '../../../../$ref'; + } else if (ref.startsWith('shared/')) { + ref = '../$ref'; + } else if (ref.startsWith('../../../hook/doc/schema/')) { + ref = '../$ref'; + } + final x = entry.key.resolve(ref); + return allSchemaJsons[x]!; + }), + ); + allSchemas[entry.key] = schema; + } + return allSchemas; +} + +typedef AllTestData = Map; + +/// The data is modified in tests, so load but don't json decode them all. +AllTestData loadTestsData(Uri directory) { + final allTestData = {}; + for (final file in Directory.fromUri(directory).listSync()) { + file as File; + allTestData[file.uri] = file.readAsStringSync(); + } + return allTestData; +} + +/// Test all [allTestData] against the schemas referred to. +void testAllTestData(AllSchemas allSchemas, AllTestData allTestData) { + for (final dataUri in allTestData.keys) { + final data = jsonDecode(allTestData[dataUri]!); + final schemaRef = data[r'$schema'] as String; + + final schemaRefs = [ + schemaRef, + // The schema should have the most restrictive one, try the other one as + // well. + if (schemaRef.contains('sdk')) schemaRef.replaceAll('sdk', 'hook'), + if (schemaRef.contains('hook')) schemaRef.replaceAll('hook', 'sdk'), + ]; + + for (final schemaRef in schemaRefs) { + final schemaUri = dataUri.resolve(schemaRef); + test('Validate $dataUri against $schemaUri', () { + printOnFailure(dataUri.toString()); + printOnFailure(schemaUri.toString()); + final schema = allSchemas[schemaUri]!; + final result = schema.validate(data); + for (final e in result.errors) { + printOnFailure(e.toString()); + } + expect(result.isValid, isTrue); + }); + } + } +} + +/// Test removing a field or modifying it. +/// +/// Changing a field to a wrong type is always expected to fail. +/// +/// Removing a field can be valid, the expectations must be passed in +/// [missingExpectations]. +void testField({ + required Uri schemaUri, + required JsonSchema schema, + required String data, + required List field, + required void Function(ValidationResults result) missingExpectations, +}) { + final fieldPath = field.join('.'); + test('$schemaUri $fieldPath missing', () { + final dataDecoded = jsonDecode(data); + final dataToModify = _traverseJson( + dataDecoded, + field.sublist(0, field.length - 1), + ); + if (dataToModify is List) { + final index = field.last as int; + dataToModify.removeAt(index); + } else { + dataToModify.remove(field.last); + } + + final result = schema.validate(dataDecoded); + printOnFailure(result.toString()); + missingExpectations(result); + }); + + test('$schemaUri $fieldPath wrong type', () { + final dataDecoded = jsonDecode(data); + final dataToModify = _traverseJson( + dataDecoded, + field.sublist(0, field.length - 1), + ); + final originalValue = dataToModify[field.last]; + final wrongTypeValue = originalValue is int ? '123' : 123; + dataToModify[field.last] = wrongTypeValue; + + final result = schema.validate(dataDecoded); + expect(result.isValid, isFalse); + }); +} + +void expectRequiredFieldMissing(ValidationResults result) { + expect(result.isValid, isFalse); +} + +void expectOptionalFieldMissing(ValidationResults result) { + expect(result.isValid, isTrue); +} + +typedef FieldsReturn = + List<(List, void Function(ValidationResults result))>; +typedef FieldsFunction = + FieldsReturn Function({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, + }); + +enum InputOrOutput { input, output } + +enum Hook { build, link } + +enum Party { sdk, hook } + +void testFields({ + required AllSchemas allSchemas, + required AllTestData allTestData, + required Uri packageUri, + String dataSuffix = '', + required FieldsFunction fields, +}) { + for (final hook in Hook.values) { + for (final party in Party.values) { + for (final inputOrOutput in InputOrOutput.values) { + final fields_ = fields( + hook: hook, + inputOrOutput: inputOrOutput, + party: party, + ); + if (fields_.isEmpty) { + continue; + } + + final schemaName = '${hook.name}_${inputOrOutput.name}'; + final schemaUri = packageUri.resolve( + 'doc/schema/${party.name}/$schemaName.schema.json', + ); + final schema = allSchemas[schemaUri]!; + final dataName = '${hook.name}_${inputOrOutput.name}$dataSuffix'; + final dataUri = packageUri.resolve('test/data/$dataName.json'); + final data = allTestData[dataUri]!; + + for (final (field, missingExpectations) in fields_) { + testField( + field: field, + schemaUri: schemaUri, + schema: schema, + data: data, + missingExpectations: missingExpectations, + ); + } + } + } + } +} + +/// Test all base hook expectations against the hook schemas. +/// +/// This has been put in a reusable location, to be able to run it on protocol +/// extensions. +void testFieldsHook({ + required AllSchemas allSchemas, + required AllTestData allTestData, + required Uri packageUri, + String dataSuffix = '', +}) { + testFields( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + dataSuffix: dataSuffix, + fields: _hookFields, + ); +} + +FieldsReturn _hookFields({ + required InputOrOutput inputOrOutput, + required Hook hook, + required Party party, +}) { + void versionMissingExpectation(ValidationResults result) { + if ((party == Party.sdk && inputOrOutput == InputOrOutput.input) || + (party == Party.hook && inputOrOutput == InputOrOutput.output)) { + // The writer must output this field. SDK must support older hooks reading + // it. + expect(result.isValid, isFalse); + } else { + // Newer hooks must support future SDKs not outputting a this field. + // TODO: Stop requiring version in the reader. + // expect(result.isValid, isTrue); + expect(result.isValid, isFalse); + } + } + + void outFileMissingExpectation(ValidationResults result) { + if (party == Party.sdk) { + // It's a new field, newer hooks will try to use it. SDKs must write it. + expect(result.isValid, isFalse); + } else { + // Older SDKs don't output the field. So, the reader must be okay not + // reading it. + expect(result.isValid, isTrue); + } + } + + return <(List, void Function(ValidationResults result))>[ + ([r'$schema'], expectOptionalFieldMissing), + (['version'], versionMissingExpectation), + if (inputOrOutput == InputOrOutput.input) ...[ + (['out_dir_shared'], expectRequiredFieldMissing), + (['out_dir'], expectRequiredFieldMissing), + (['package_name'], expectRequiredFieldMissing), + (['package_root'], expectRequiredFieldMissing), + (['config', 'build_asset_types'], expectRequiredFieldMissing), + if (hook == Hook.build) ...[ + (['config', 'linking_enabled'], expectRequiredFieldMissing), + (['dependency_metadata'], expectOptionalFieldMissing), + (['dependency_metadata', 'some_package'], expectOptionalFieldMissing), + ], + if (hook == Hook.link) ...[ + (['assets'], expectOptionalFieldMissing), + (['assets', 0], expectOptionalFieldMissing), + (['assets', 0, 'type'], expectRequiredFieldMissing), + ], + (['out_file'], outFileMissingExpectation), + ], + if (inputOrOutput == InputOrOutput.output) ...[ + (['timestamp'], expectRequiredFieldMissing), + (['dependencies'], expectOptionalFieldMissing), + (['dependencies', 0], expectOptionalFieldMissing), + (['assets'], expectOptionalFieldMissing), + (['assets', 0], expectOptionalFieldMissing), + (['assets', 0, 'type'], expectRequiredFieldMissing), + if (hook == Hook.build) ...[ + (['metadata'], expectOptionalFieldMissing), + (['assetsForLinking'], expectOptionalFieldMissing), + ( + ['assetsForLinking', 'package_with_linker', 0], + expectOptionalFieldMissing, + ), + (['assetsForLinking'], expectOptionalFieldMissing), + ( + ['assetsForLinking', 'package_with_linker', 0, 'type'], + expectRequiredFieldMissing, + ), + ], + ], + ]; +} + +dynamic _traverseJson(dynamic json, List path) { + while (path.isNotEmpty) { + final key = path.removeAt(0); + switch (key) { + case final int i: + json = (json as List)[i] as Object; + break; + case final String s: + json = (json as Map)[s] as Object; + break; + default: + throw UnsupportedError(key.toString()); + } + } + return json; +} diff --git a/pkgs/hook/test/schema/schema_test.dart b/pkgs/hook/test/schema/schema_test.dart new file mode 100644 index 000000000..4edb0cc6b --- /dev/null +++ b/pkgs/hook/test/schema/schema_test.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'helpers.dart'; + +void main() { + final schemasUri = packageUri.resolve('doc/schema/'); + final hookSchemasUri = packageUri.resolve('../hook/doc/schema/'); + final allSchemas = loadSchemas([schemasUri, hookSchemasUri]); + + final testDataUri = packageUri.resolve('test/data/'); + final allTestData = loadTestsData(testDataUri); + + testAllTestData(allSchemas, allTestData); + + testFieldsHook( + allSchemas: allSchemas, + allTestData: allTestData, + packageUri: packageUri, + ); +} + +Uri packageUri = findPackageRoot('hook'); diff --git a/pkgs/hook/tool/normalize.dart b/pkgs/hook/tool/normalize.dart new file mode 100644 index 000000000..16a5c2a6d --- /dev/null +++ b/pkgs/hook/tool/normalize.dart @@ -0,0 +1,202 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; +import 'dart:io'; +import 'package:path/path.dart' as p; + +import '../test/schema/helpers.dart' show findPackageRoot; + +void main() { + final packageUri = findPackageRoot('hook'); + + final directories = [ + Directory.fromUri(packageUri), + Directory.fromUri(packageUri.resolve('../code_assets/')), + Directory.fromUri(packageUri.resolve('../data_assets/')), + ]; + for (final directory in directories) { + processDirectory(directory); + } +} + +void processDirectory(Directory directory) { + final entities = directory.listSync(recursive: true); + for (final entity in entities) { + if (entity is File && + p.extension(entity.path) == '.json' && + !entity.path.contains('.dart_tool/')) { + processFile(entity); + } + } +} + +void processFile(File file) { + try { + final contents = file.readAsStringSync(); + final dynamic decoded = json.decode(contents); + + final sorted = sortJson(decoded, file.path); + + const encoder = JsonEncoder.withIndent(' '); + final sortedJson = encoder.convert(sorted); + + file.writeAsStringSync('$sortedJson\n'); + print('Normalized: ${file.path}'); + } catch (e) { + print('Error processing ${file.path}: $e'); + } +} + +const List _orderedKeysInSchemas = [ + // Schema Identification: Defines the JSON Schema version and identifier. + // Should be at the top. + '\$schema', + '\$id', + + // Schema Metadata: Human-readable information about the schema. + 'title', + 'description', + + // Core Types: The basic data types and related keywords. + 'type', + 'enum', + 'const', + + // Object Schemas: Keywords for defining and validating JSON objects. + 'properties', + 'required', + 'additionalProperties', + 'patternProperties', + 'unevaluatedProperties', + + // Array Schemas: Keywords for defining and validating JSON arrays. + 'items', + 'prefixItems', + 'contains', + 'minContains', + 'maxContains', + + // Combining Schemas: Keywords for combining and manipulating schemas. + 'allOf', + 'anyOf', + 'oneOf', + 'not', + + // Conditional Application: Keywords for applying schemas conditionally. + 'if', + 'then', + 'else', + 'dependentSchemas', + 'dependentRequired', + + // Reusable Definitions: Keywords for defining and referencing reusable schema + // components. + '\$defs', + 'definitions', + + // Semantic Validation: Keywords for validating data based on its semantic + // type. + 'format', + 'examples', + 'default', + + // Metadata Annotations: Keywords for adding metadata annotations to schemas. + 'readOnly', + 'writeOnly', + 'deprecated', + + // Numeric Validation: Keywords for validating numeric data. + 'multipleOf', + 'maximum', + 'exclusiveMaximum', + 'minimum', + 'exclusiveMinimum', + + // String Validation: Keywords for validating string data. + 'maxLength', + 'minLength', + 'pattern', + + // Array Validation: Keywords for validating array data. + 'maxItems', + 'minItems', + 'uniqueItems', + + // Object Validation: Keywords for validating object data. + 'maxProperties', + 'minProperties', + + // Informational: Keyword for adding comments to the schema. + '\$comment', +]; + +dynamic sortJson(dynamic data, String filePath) { + if (data is Map) { + final sortedMap = {}; + final keys = data.keys.toList(); + + final isSchema = filePath.endsWith('schema.json'); + if (isSchema) { + keys.sort((a, b) { + final aIndex = _orderedKeysInSchemas.indexOf(a); + final bIndex = _orderedKeysInSchemas.indexOf(b); + if (aIndex == -1 && bIndex == -1) { + // Both keys are not in _orderedKeys, sort alphabetically. + return a.compareTo(b); + } else if (aIndex == -1) { + // Only b is in _orderedKeys, sort b first + return 1; + } else if (bIndex == -1) { + // Only a is in _orderedKeys, sort a first + return -1; + } else { + return aIndex.compareTo(bIndex); + } + }); + } else { + // Sort keys alphabetically for non-schemas. + keys.sort(); + } + + for (final key in keys) { + sortedMap[key] = sortJson(data[key], filePath); + } + return sortedMap; + } + if (data is List) { + return data.map((item) => sortJson(item, filePath)).toList()..sort((a, b) { + if (a is Map && b is Map) { + final aKeys = a.keys.toList(); + final bKeys = b.keys.toList(); + for (var i = 0; i < aKeys.length && i < bKeys.length; i++) { + final comparison = aKeys[i].toString().compareTo(bKeys[i].toString()); + if (comparison != 0) { + return comparison; + } + final aValue = a[aKeys[i]]; + final bValue = b[bKeys[i]]; + if (aValue is String && bValue is String) { + final valueComparison = aValue.compareTo(bValue); + if (valueComparison != 0) { + return valueComparison; + } + } + if (aValue == bValue) { + continue; + } + throw UnimplementedError( + 'Not implemented to compare $aValue and $bValue.', + ); + } + return 0; + } + if (a is String && b is String) { + return a.compareTo(b); + } + throw UnimplementedError('Not implemented to compare $a and $b.'); + }); + } + return data; +}