Create open Shorebird workspace meta-repo
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.dart text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.lock text eol=lf
|
||||||
|
|
||||||
|
.gitattributes text eol=lf
|
||||||
|
.gitignore text eol=lf
|
||||||
|
.gitmodules text eol=lf
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Platform/generated checkout metadata.
|
||||||
|
.gclient
|
||||||
|
.gclient_entries
|
||||||
|
.gclient_previous_*
|
||||||
|
.cipd/
|
||||||
|
buildtools/
|
||||||
|
sdk/
|
||||||
|
|
||||||
|
# Root scratch output.
|
||||||
|
build_log*.txt
|
||||||
|
build_patched_sdk.*
|
||||||
|
compare_tool.dart
|
||||||
|
test_bytecode.dart
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Local agent state.
|
||||||
|
.agents/
|
||||||
|
|
||||||
|
# Dart/Flutter build output in root-owned test fixtures.
|
||||||
|
**/.dart_tool/
|
||||||
|
**/build/
|
||||||
|
|
||||||
|
# Local editor/OS files.
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
[submodule "dart-sdk-new"]
|
||||||
|
path = dart-sdk-new
|
||||||
|
url = https://git.tonycloud.org/dart-lang/sdk.git
|
||||||
|
branch = tonycloud/dev
|
||||||
|
[submodule "depot_tools"]
|
||||||
|
path = depot_tools
|
||||||
|
url = https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||||
|
branch = main
|
||||||
|
[submodule "flutter"]
|
||||||
|
path = flutter
|
||||||
|
url = https://git.tonycloud.org/flutter/flutter.git
|
||||||
|
branch = shorebird/dev
|
||||||
|
[submodule "shorebird"]
|
||||||
|
path = shorebird
|
||||||
|
url = https://git.tonycloud.org/flutter/shorebird.git
|
||||||
|
branch = main
|
||||||
|
[submodule "shorebird-engine"]
|
||||||
|
path = shorebird-engine
|
||||||
|
url = https://github.com/shorebirdtech/engine.git
|
||||||
|
branch = shorebird/dev
|
||||||
|
[submodule "shorebird-server"]
|
||||||
|
path = shorebird-server
|
||||||
|
url = https://git.tonycloud.org/flutter/shorebird-server.git
|
||||||
|
branch = main
|
||||||
|
[submodule "updater"]
|
||||||
|
path = updater
|
||||||
|
url = https://git.tonycloud.org/flutter/shorebird-updater.git
|
||||||
|
branch = main
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Open Shorebird Workspace
|
||||||
|
|
||||||
|
This repository is a meta-workspace for the open Shorebird replacement work. It
|
||||||
|
pins the source repositories used by the SDK, Flutter/engine, CLI, updater, and
|
||||||
|
self-hosted server work as git submodules, while keeping local test fixtures and
|
||||||
|
platform scripts in this root repository.
|
||||||
|
|
||||||
|
## Submodules
|
||||||
|
|
||||||
|
| Path | Role | Remote | Branch |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `dart-sdk-new` | Dart SDK fork with AOT patch runtime work | `https://git.tonycloud.org/dart-lang/sdk.git` | `tonycloud/dev` |
|
||||||
|
| `flutter` | Flutter fork used by app and engine integration tests | `https://git.tonycloud.org/flutter/flutter.git` | `shorebird/dev` |
|
||||||
|
| `shorebird` | CLI, protocol/client packages, open patch tools | `https://git.tonycloud.org/flutter/shorebird.git` | `main` |
|
||||||
|
| `shorebird-engine` | Shorebird engine reference checkout | `https://github.com/shorebirdtech/engine.git` | `shorebird/dev` |
|
||||||
|
| `shorebird-server` | Self-hosted CodePush/auth/management server | `https://git.tonycloud.org/flutter/shorebird-server.git` | `main` |
|
||||||
|
| `updater` | Runtime updater and patch package tooling | `https://git.tonycloud.org/flutter/shorebird-updater.git` | `main` |
|
||||||
|
| `depot_tools` | Chromium/Dart checkout tooling | `https://chromium.googlesource.com/chromium/tools/depot_tools.git` | `main` |
|
||||||
|
|
||||||
|
The Dart SDK checkout has additional gclient-managed dependencies under
|
||||||
|
`dart-sdk-new/third_party`. They are intentionally not top-level submodules.
|
||||||
|
|
||||||
|
## First Checkout
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
For Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/bootstrap_linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
For macOS and iOS-preparation checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/bootstrap_macos.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
By default the bootstrap scripts run `gclient sync`, which can download a large
|
||||||
|
toolchain/dependency set. Set `SKIP_GCLIENT_SYNC=1` when the checkout is already
|
||||||
|
synced or when you only want source-level tests.
|
||||||
|
|
||||||
|
## Local Test Fixture
|
||||||
|
|
||||||
|
`testapps/license_flavor_patch_test` is kept in the root repository because it
|
||||||
|
tests the open AOT patch and license/flavor behavior across the nested repos.
|
||||||
|
Generated build output under that app is ignored by this meta-repository.
|
||||||
|
|
||||||
|
## Documents
|
||||||
|
|
||||||
|
- `docs/REPOSITORIES.md` records the remote URL inventory and cleanup policy.
|
||||||
|
- `docs/PLATFORM_TESTING.md` documents the Linux, macOS, and later iOS test
|
||||||
|
flow.
|
||||||
Submodule
+1
Submodule dart-sdk-new added at 08139af589
Submodule
+1
Submodule depot_tools added at 90f5d4d14d
@@ -0,0 +1,81 @@
|
|||||||
|
# Linux And macOS Platform Testing
|
||||||
|
|
||||||
|
The first complete runtime proof was built on Windows. The next platform pass
|
||||||
|
should verify the same source layout on Linux and macOS, then use macOS to
|
||||||
|
prepare iOS testing without introducing JIT or writable-executable-memory
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Linux:
|
||||||
|
|
||||||
|
- Git
|
||||||
|
- Bash
|
||||||
|
- Dart or Flutter on `PATH`
|
||||||
|
- Go 1.23+
|
||||||
|
- Python and standard build dependencies required by Dart/Flutter
|
||||||
|
|
||||||
|
macOS:
|
||||||
|
|
||||||
|
- Git
|
||||||
|
- Bash
|
||||||
|
- Dart or Flutter on `PATH`
|
||||||
|
- Go 1.23+
|
||||||
|
- Xcode command line tools
|
||||||
|
- Full Xcode for iOS simulator/device follow-up
|
||||||
|
|
||||||
|
Both platforms need network access for a fresh `git submodule update` and
|
||||||
|
`gclient sync`.
|
||||||
|
|
||||||
|
## Bootstrap
|
||||||
|
|
||||||
|
Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/bootstrap_linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/bootstrap_macos.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful environment variables:
|
||||||
|
|
||||||
|
| Variable | Effect |
|
||||||
|
| --- | --- |
|
||||||
|
| `SKIP_GCLIENT_SYNC=1` | Do not run `gclient sync`. Useful when dependencies are already present. |
|
||||||
|
| `SKIP_TESTS=1` | Prepare checkout only; skip Dart/Go tests. |
|
||||||
|
| `DART_BIN=/path/to/dart` | Override the Dart executable. |
|
||||||
|
| `FLUTTER_BIN=/path/to/flutter` | Override the Flutter executable. |
|
||||||
|
| `GO_BIN=/path/to/go` | Override the Go executable. |
|
||||||
|
| `RUN_IOS_SMOKE=1` | On macOS, run a no-codesign iOS build smoke check when Flutter/Xcode are available. |
|
||||||
|
|
||||||
|
## Expected Checks
|
||||||
|
|
||||||
|
The bootstrap scripts run:
|
||||||
|
|
||||||
|
1. `git submodule update --init --recursive`
|
||||||
|
2. platform-specific `.gclient` generation
|
||||||
|
3. optional `gclient sync`
|
||||||
|
4. public updater sync into the Flutter engine third-party location
|
||||||
|
5. Shorebird CLI focused tests for self-hosted `base_url` behavior
|
||||||
|
6. open AOT patch tool tests
|
||||||
|
7. self-hosted server Go tests
|
||||||
|
8. license/flavor AOT verification when `dart-sdk-new/out/ReleaseX64AotPatch/args.gn`
|
||||||
|
already exists
|
||||||
|
|
||||||
|
## iOS Preparation Notes
|
||||||
|
|
||||||
|
The macOS script writes:
|
||||||
|
|
||||||
|
```python
|
||||||
|
target_os = ["mac", "ios"]
|
||||||
|
```
|
||||||
|
|
||||||
|
That prepares gclient for iOS dependencies. The actual iOS runtime test should
|
||||||
|
still be run later on macOS after the Flutter engine/Dart SDK patch build is
|
||||||
|
available. The AOT patching design remains iOS-compatible only if patch loading
|
||||||
|
uses mapped snapshot data/instructions and does not rely on JIT, the KBC
|
||||||
|
interpreter, or writable executable memory.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Repository Inventory
|
||||||
|
|
||||||
|
Last verified: 2026-06-24.
|
||||||
|
|
||||||
|
## Active Top-Level Repositories
|
||||||
|
|
||||||
|
| Path | Remote URL | Branch | Pinned commit |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `dart-sdk-new` | `https://git.tonycloud.org/dart-lang/sdk.git` | `tonycloud/dev` | `08139af589d0d32fbdec64b127c075ea3427cde8` |
|
||||||
|
| `depot_tools` | `https://chromium.googlesource.com/chromium/tools/depot_tools.git` | `main` | `90f5d4d14de66a5aae4a55b554ad4565111169af` |
|
||||||
|
| `flutter` | `https://git.tonycloud.org/flutter/flutter.git` | `shorebird/dev` | `9754ce03d76fb06c23a72ddbd8b7137bfa85446c` |
|
||||||
|
| `shorebird` | `https://git.tonycloud.org/flutter/shorebird.git` | `main` | `c824005c2dc65f56b0fa078984fadb649876fce7` |
|
||||||
|
| `shorebird-engine` | `https://github.com/shorebirdtech/engine.git` | `shorebird/dev` | `e4575c7403bd3453d04bfa03e01218e5a0bca0c6` |
|
||||||
|
| `shorebird-server` | `https://git.tonycloud.org/flutter/shorebird-server.git` | `main` | `774954fce70181846995ebcad842e9c54e57f1a2` |
|
||||||
|
| `updater` | `https://git.tonycloud.org/flutter/shorebird-updater.git` | `main` | `3ac748ff28eb4ebb53630c66230b9b1be5df73c2` |
|
||||||
|
|
||||||
|
## Generated Or Obsolete Root Folders
|
||||||
|
|
||||||
|
The following root folders are not part of the meta-workspace and can be
|
||||||
|
regenerated or replaced by tracked source:
|
||||||
|
|
||||||
|
| Path | Reason |
|
||||||
|
| --- | --- |
|
||||||
|
| `.cipd` | Local CIPD cache created by gclient/depot_tools. |
|
||||||
|
| `buildtools` | Ad hoc root tool download/output; Dart build tools live under `dart-sdk-new/buildtools`. |
|
||||||
|
| `dart-sdk` | Empty stale SDK checkout. |
|
||||||
|
| `hello_shorebird_test` | Old throwaway Flutter app. The active fixture is `testapps/license_flavor_patch_test`. |
|
||||||
|
| `patches` | Earlier one-off patch files superseded by source changes and `packages/open_aot_patch_tools`. |
|
||||||
|
| `sdk` | Generated Windows toolchain cache. |
|
||||||
|
| `testapp2` | Old throwaway Flutter app. |
|
||||||
|
| `testapp3` | Old throwaway Flutter app. |
|
||||||
|
|
||||||
|
## Submodule Policy
|
||||||
|
|
||||||
|
Only top-level project repositories are submodules. Third-party repositories and
|
||||||
|
CIPD packages fetched by `gclient sync` remain owned by their upstream DEPS files
|
||||||
|
and should not be duplicated in `.gitmodules`.
|
||||||
|
|
||||||
|
Root-owned content should be limited to:
|
||||||
|
|
||||||
|
- workspace documentation
|
||||||
|
- bootstrap and test scripts
|
||||||
|
- small test fixtures that intentionally combine multiple submodules
|
||||||
|
- meta-repository config such as `.gitignore` and `.gitmodules`
|
||||||
|
|
||||||
|
## Nested Git Checkouts Observed
|
||||||
|
|
||||||
|
These repositories were found under top-level checkouts. They are managed by the
|
||||||
|
owning repository's DEPS/gclient workflow, not by the root `.gitmodules` file.
|
||||||
|
|
||||||
|
| Path | Remote URL |
|
||||||
|
| --- | --- |
|
||||||
|
| `dart-sdk-new/buildtools/clang_format/script` | `https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git` |
|
||||||
|
| `dart-sdk-new/tests/co19/src` | `https://dart.googlesource.com/co19` |
|
||||||
|
| `dart-sdk-new/third_party/binaryen/src` | `https://chromium.googlesource.com/external/github.com/WebAssembly/binaryen.git` |
|
||||||
|
| `dart-sdk-new/third_party/boringssl/src` | `https://boringssl.googlesource.com/boringssl.git` |
|
||||||
|
| `dart-sdk-new/third_party/cpu_features/src` | `https://chromium.googlesource.com/external/github.com/google/cpu_features.git` |
|
||||||
|
| `dart-sdk-new/third_party/crashpad/crashpad` | `https://chromium.googlesource.com/crashpad/crashpad.git` |
|
||||||
|
| `dart-sdk-new/third_party/cygwin` | `https://chromium.googlesource.com/chromium/deps/cygwin.git` |
|
||||||
|
| `dart-sdk-new/third_party/emsdk` | `https://dart.googlesource.com/external/github.com/emscripten-core/emsdk.git` |
|
||||||
|
| `dart-sdk-new/third_party/googletest` | `https://fuchsia.googlesource.com/third_party/googletest` |
|
||||||
|
| `dart-sdk-new/third_party/icu` | `https://chromium.googlesource.com/chromium/deps/icu.git` |
|
||||||
|
| `dart-sdk-new/third_party/jinja2` | `https://chromium.googlesource.com/chromium/src/third_party/jinja2.git` |
|
||||||
|
| `dart-sdk-new/third_party/libc` | `https://llvm.googlesource.com/llvm-project/libc` |
|
||||||
|
| `dart-sdk-new/third_party/libcxx` | `https://llvm.googlesource.com/llvm-project/libcxx` |
|
||||||
|
| `dart-sdk-new/third_party/libcxxabi` | `https://llvm.googlesource.com/llvm-project/libcxxabi` |
|
||||||
|
| `dart-sdk-new/third_party/markupsafe` | `https://chromium.googlesource.com/chromium/src/third_party/markupsafe.git` |
|
||||||
|
| `dart-sdk-new/third_party/mini_chromium/mini_chromium` | `https://chromium.googlesource.com/chromium/mini_chromium` |
|
||||||
|
| `dart-sdk-new/third_party/perfetto/src` | `https://chromium.googlesource.com/external/github.com/google/perfetto` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/core` | `https://dart.googlesource.com/core.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/dart_style` | `https://dart.googlesource.com/dart_style.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/dartdoc` | `https://dart.googlesource.com/dartdoc.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/ecosystem` | `https://dart.googlesource.com/ecosystem.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/http` | `https://dart.googlesource.com/http.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/i18n` | `https://dart.googlesource.com/i18n.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/leak_tracker` | `https://dart.googlesource.com/leak_tracker.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/native` | `https://dart.googlesource.com/native.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/protobuf` | `https://dart.googlesource.com/protobuf.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/pub` | `https://dart.googlesource.com/pub.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/shelf` | `https://dart.googlesource.com/shelf.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/sync_http` | `https://dart.googlesource.com/sync_http.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/tar` | `https://dart.googlesource.com/external/github.com/simolus3/tar.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/test` | `https://dart.googlesource.com/test.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/tools` | `https://dart.googlesource.com/tools.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/vector_math` | `https://dart.googlesource.com/external/github.com/google/vector_math.dart.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/web` | `https://dart.googlesource.com/web.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/webdev` | `https://dart.googlesource.com/webdev.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/webdriver` | `https://dart.googlesource.com/external/github.com/google/webdriver.dart.git` |
|
||||||
|
| `dart-sdk-new/third_party/pkg/webkit_inspection_protocol` | `https://dart.googlesource.com/external/github.com/google/webkit_inspection_protocol.dart.git` |
|
||||||
|
| `dart-sdk-new/third_party/ply` | `https://chromium.googlesource.com/chromium/src/third_party/ply.git` |
|
||||||
|
| `dart-sdk-new/third_party/WebCore` | `https://dart.googlesource.com/webcore.git` |
|
||||||
|
| `dart-sdk-new/third_party/zlib` | `https://chromium.googlesource.com/chromium/src/third_party/zlib.git` |
|
||||||
Submodule
+1
Submodule flutter added at 9754ce03d7
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$ROOT/scripts/platform_test_common.sh" linux
|
||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$ROOT/scripts/platform_test_common.sh" macos
|
||||||
Executable
+69
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PLATFORM="${1:?platform is required}"
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
DART_BIN="${DART_BIN:-dart}"
|
||||||
|
FLUTTER_BIN="${FLUTTER_BIN:-flutter}"
|
||||||
|
GO_BIN="${GO_BIN:-go}"
|
||||||
|
|
||||||
|
run() {
|
||||||
|
echo
|
||||||
|
echo "==> $*"
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
|
echo "missing required command: $1" >&2
|
||||||
|
exit 127
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command git
|
||||||
|
require_command "$DART_BIN"
|
||||||
|
require_command "$GO_BIN"
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "macos" ]]; then
|
||||||
|
if command -v xcodebuild >/dev/null 2>&1; then
|
||||||
|
xcodebuild -version
|
||||||
|
else
|
||||||
|
echo "warning: xcodebuild not found; iOS follow-up checks will be unavailable." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
run git -C "$ROOT" submodule update --init --recursive
|
||||||
|
run "$ROOT/scripts/write_gclient.sh" "$PLATFORM"
|
||||||
|
run "$ROOT/scripts/sync_open_sources.sh"
|
||||||
|
|
||||||
|
export PATH="$ROOT/depot_tools:$PATH"
|
||||||
|
if [[ "${SKIP_GCLIENT_SYNC:-0}" != "1" ]]; then
|
||||||
|
require_command gclient
|
||||||
|
run gclient sync --no-history
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "==> skipping gclient sync because SKIP_GCLIENT_SYNC=1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${SKIP_TESTS:-0}" == "1" ]]; then
|
||||||
|
echo
|
||||||
|
echo "==> skipping tests because SKIP_TESTS=1"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
run bash -lc "cd '$ROOT/shorebird/packages/shorebird_cli' && '$DART_BIN' pub get && '$DART_BIN' test test/src/user_config_test.dart test/src/shorebird_env_test.dart test/src/shorebird_cli_command_runner_test.dart test/src/commands/init_command_test.dart"
|
||||||
|
run bash -lc "cd '$ROOT/shorebird/packages/open_aot_patch_tools' && '$DART_BIN' pub get && '$DART_BIN' test"
|
||||||
|
run bash -lc "cd '$ROOT/shorebird-server' && '$GO_BIN' test ./..."
|
||||||
|
|
||||||
|
AOT_ARGS="$ROOT/dart-sdk-new/out/ReleaseX64AotPatch/args.gn"
|
||||||
|
if [[ -f "$AOT_ARGS" ]]; then
|
||||||
|
run bash -lc "cd '$ROOT/testapps/license_flavor_patch_test' && '$FLUTTER_BIN' pub get && '$DART_BIN' run tool/verify_aot_patch.dart"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "==> skipping license/flavor AOT verification; missing $AOT_ARGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "macos" && "${RUN_IOS_SMOKE:-0}" == "1" ]]; then
|
||||||
|
require_command "$FLUTTER_BIN"
|
||||||
|
run bash -lc "cd '$ROOT/testapps/license_flavor_patch_test' && '$FLUTTER_BIN' build ios --debug --no-codesign"
|
||||||
|
fi
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
UPDATER_SRC="${UPDATER_SRC:-$ROOT/updater}"
|
||||||
|
UPDATER_URL="${UPDATER_URL:-https://github.com/shorebirdtech/updater.git}"
|
||||||
|
TARGET="$ROOT/flutter/engine/src/flutter/third_party/updater"
|
||||||
|
|
||||||
|
echo "[open-source-sync] updater source: $UPDATER_SRC"
|
||||||
|
echo "[open-source-sync] target: $TARGET"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$TARGET")"
|
||||||
|
|
||||||
|
if [[ -L "$TARGET" ]]; then
|
||||||
|
echo "[open-source-sync] updater target is already a symlink."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$TARGET/.git" ]]; then
|
||||||
|
echo "[open-source-sync] updating existing updater checkout."
|
||||||
|
git -C "$TARGET" fetch --tags origin
|
||||||
|
git -C "$TARGET" checkout "${UPDATER_REVISION:-main}"
|
||||||
|
if [[ "${UPDATER_REVISION:-main}" == "main" ]]; then
|
||||||
|
git -C "$TARGET" pull --ff-only
|
||||||
|
fi
|
||||||
|
elif [[ -e "$TARGET" ]]; then
|
||||||
|
echo "target exists but is not a symlink or git checkout: $TARGET" >&2
|
||||||
|
exit 1
|
||||||
|
elif [[ -d "$UPDATER_SRC/.git" ]]; then
|
||||||
|
rel_target="$(python3 - "$TARGET" "$UPDATER_SRC" <<'PY'
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
print(os.path.relpath(sys.argv[2], os.path.dirname(sys.argv[1])))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
ln -s "$rel_target" "$TARGET"
|
||||||
|
echo "[open-source-sync] linked updater submodule into Flutter engine."
|
||||||
|
else
|
||||||
|
echo "[open-source-sync] cloning public updater checkout."
|
||||||
|
git clone "$UPDATER_URL" "$TARGET"
|
||||||
|
git -C "$TARGET" checkout "${UPDATER_REVISION:-main}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$TARGET/library/include/updater_engine.h" ]]; then
|
||||||
|
echo "updater checkout is missing library/include/updater_engine.h" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[open-source-sync] public Shorebird updater is available."
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PLATFORM="${1:-}"
|
||||||
|
|
||||||
|
case "$PLATFORM" in
|
||||||
|
linux)
|
||||||
|
TARGET_OS='["linux"]'
|
||||||
|
;;
|
||||||
|
macos)
|
||||||
|
TARGET_OS='["mac", "ios"]'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 <linux|macos>" >&2
|
||||||
|
exit 64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat > "$ROOT/.gclient" <<EOF
|
||||||
|
solutions = [
|
||||||
|
{
|
||||||
|
"name": "dart-sdk-new",
|
||||||
|
"url": "https://git.tonycloud.org/dart-lang/sdk.git",
|
||||||
|
"deps_file": "DEPS",
|
||||||
|
"managed": False,
|
||||||
|
"custom_deps": {},
|
||||||
|
"custom_vars": {
|
||||||
|
"dart_root": "dart-sdk-new",
|
||||||
|
"download_android_deps": False,
|
||||||
|
"checkout_javascript_engines": False,
|
||||||
|
"checkout_benchmarks_internal": False,
|
||||||
|
"checkout_flute": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
target_os = $TARGET_OS
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Wrote $ROOT/.gclient for $PLATFORM with target_os = $TARGET_OS"
|
||||||
Submodule
+1
Submodule shorebird added at c824005c2d
Submodule
+1
Submodule shorebird-engine added at e4575c7403
Submodule
+1
Submodule shorebird-server added at 774954fce7
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:license_flavor_patch_test/license.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final license = currentLicenseType();
|
||||||
|
print('license:${license.name}');
|
||||||
|
print(proFeatureEnabled ? 'pro-feature:enabled' : 'pro-feature:off');
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
enum LicenseType { free, pro, enterprise }
|
||||||
|
|
||||||
|
LicenseType currentLicenseType() {
|
||||||
|
const license = String.fromEnvironment('LICENSE_TYPE', defaultValue: 'free');
|
||||||
|
return switch (license) {
|
||||||
|
'pro' => LicenseType.pro,
|
||||||
|
'enterprise' => LicenseType.enterprise,
|
||||||
|
_ => LicenseType.free,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get proFeatureEnabled =>
|
||||||
|
currentLicenseType() == LicenseType.pro ||
|
||||||
|
currentLicenseType() == LicenseType.enterprise;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'license.dart';
|
||||||
|
|
||||||
|
void main() => runApp(const LicenseFlavorPatchApp());
|
||||||
|
|
||||||
|
class LicenseFlavorPatchApp extends StatelessWidget {
|
||||||
|
const LicenseFlavorPatchApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final license = currentLicenseType();
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('License Patch Test')),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'license:${license.name}',
|
||||||
|
key: const ValueKey('license-status'),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
proFeatureEnabled ? 'pro-feature:enabled' : 'pro-feature:off',
|
||||||
|
key: const ValueKey('feature-status'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.19"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.17.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.10"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.2.0"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.9.0 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
name: license_flavor_patch_test
|
||||||
|
description: Test app for encrypted flavor/license AOT patch verification.
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.9.0
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- shorebird.yaml
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
app_id: license-flavor-patch-test
|
||||||
|
flavors:
|
||||||
|
free: license-flavor-free
|
||||||
|
pro: license-flavor-pro
|
||||||
|
enterprise: license-flavor-enterprise
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:license_flavor_patch_test/license.dart';
|
||||||
|
import 'package:license_flavor_patch_test/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final isProFlavor = currentLicenseType() == LicenseType.pro;
|
||||||
|
|
||||||
|
test('pro flavor enables pro feature gate', () {
|
||||||
|
expect(currentLicenseType(), LicenseType.pro);
|
||||||
|
expect(proFeatureEnabled, isTrue);
|
||||||
|
}, skip: !isProFlavor);
|
||||||
|
|
||||||
|
testWidgets('renders pro license status labels', (tester) async {
|
||||||
|
await tester.pumpWidget(const LicenseFlavorPatchApp());
|
||||||
|
|
||||||
|
expect(find.text('license:pro'), findsOneWidget);
|
||||||
|
expect(find.text('pro-feature:enabled'), findsOneWidget);
|
||||||
|
}, skip: !isProFlavor);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:license_flavor_patch_test/license.dart';
|
||||||
|
import 'package:license_flavor_patch_test/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('defaults to free license flavor', () {
|
||||||
|
expect(currentLicenseType(), LicenseType.free);
|
||||||
|
expect(proFeatureEnabled, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders free license status labels', (tester) async {
|
||||||
|
await tester.pumpWidget(const LicenseFlavorPatchApp());
|
||||||
|
|
||||||
|
expect(find.text('license:free'), findsOneWidget);
|
||||||
|
expect(find.text('pro-feature:off'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
final appDir = File.fromUri(Platform.script).parent.parent;
|
||||||
|
final workspaceRoot = appDir.parent.parent;
|
||||||
|
final dartRoot = Directory('${workspaceRoot.path}/dart-sdk-new');
|
||||||
|
final patchBuildDir = Directory('${dartRoot.path}/out/ReleaseX64AotPatch');
|
||||||
|
final toolWorkspace = Directory('${workspaceRoot.path}/shorebird');
|
||||||
|
final workDir = Directory('${appDir.path}/build/open_aot_patch_verify');
|
||||||
|
|
||||||
|
final argsFile = File('${patchBuildDir.path}/args.gn');
|
||||||
|
if (!argsFile.existsSync()) {
|
||||||
|
throw StateError(
|
||||||
|
'Missing ${argsFile.path}. Build the patch runtime first with '
|
||||||
|
'dart_enable_aot_patching=true and dart_dynamic_modules=false.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final args = argsFile.readAsStringSync();
|
||||||
|
if (!args.contains('dart_enable_aot_patching = true') ||
|
||||||
|
!args.contains('dart_dynamic_modules = false')) {
|
||||||
|
throw StateError(
|
||||||
|
'${argsFile.path} must enable AOT patching and disable dynamic modules.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workDir.existsSync()) {
|
||||||
|
workDir.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
workDir.createSync(recursive: true);
|
||||||
|
|
||||||
|
final packageConfig = File('${workDir.path}/package_config.json');
|
||||||
|
packageConfig.writeAsStringSync(
|
||||||
|
jsonEncode({
|
||||||
|
'configVersion': 2,
|
||||||
|
'packages': [
|
||||||
|
{
|
||||||
|
'name': 'license_flavor_patch_test',
|
||||||
|
'rootUri': appDir.uri.toString(),
|
||||||
|
'packageUri': 'lib/',
|
||||||
|
'languageVersion': '3.9',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
final baseMap = File('${workDir.path}/base.obfuscation.json');
|
||||||
|
final patchMap = File('${workDir.path}/patch.obfuscation.json');
|
||||||
|
final baseVmcode = File('${workDir.path}/base_free.vmcode');
|
||||||
|
final patchVmcode = File('${workDir.path}/patch_pro.vmcode');
|
||||||
|
final reconstructedVmcode = File('${workDir.path}/reconstructed_pro.vmcode');
|
||||||
|
|
||||||
|
await _compileAot(
|
||||||
|
workspaceRoot: workspaceRoot,
|
||||||
|
dartRoot: dartRoot,
|
||||||
|
patchBuildDir: patchBuildDir,
|
||||||
|
packageConfig: packageConfig,
|
||||||
|
licenseType: 'free',
|
||||||
|
outputVmcode: baseVmcode,
|
||||||
|
saveObfuscationMap: baseMap,
|
||||||
|
);
|
||||||
|
await _compileAot(
|
||||||
|
workspaceRoot: workspaceRoot,
|
||||||
|
dartRoot: dartRoot,
|
||||||
|
patchBuildDir: patchBuildDir,
|
||||||
|
packageConfig: packageConfig,
|
||||||
|
licenseType: 'pro',
|
||||||
|
outputVmcode: patchVmcode,
|
||||||
|
loadObfuscationMap: baseMap,
|
||||||
|
saveObfuscationMap: patchMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
_expectStatus(
|
||||||
|
await _runAot(patchBuildDir, baseVmcode),
|
||||||
|
license: 'free',
|
||||||
|
proFeature: false,
|
||||||
|
);
|
||||||
|
_expectStatus(
|
||||||
|
await _runAot(patchBuildDir, patchVmcode),
|
||||||
|
license: 'pro',
|
||||||
|
proFeature: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final artifact = File('${workDir.path}/patch.json');
|
||||||
|
final encrypted = File('${workDir.path}/patch.encrypted.json');
|
||||||
|
const keyHex =
|
||||||
|
'000102030405060708090a0b0c0d0e0f'
|
||||||
|
'101112131415161718191a1b1c1d1e1f';
|
||||||
|
const wrongKeyHex =
|
||||||
|
'1f1e1d1c1b1a19181716151413121110'
|
||||||
|
'0f0e0d0c0b0a09080706050403020100';
|
||||||
|
const nonceHex = '000102030405060708090a0b';
|
||||||
|
const offlineExpiresAt = '2030-01-01T00:00:00Z';
|
||||||
|
const beforeExpiry = '2029-01-01T00:00:00Z';
|
||||||
|
const afterExpiry = '2031-01-01T00:00:00Z';
|
||||||
|
|
||||||
|
await _runTool(toolWorkspace, [
|
||||||
|
'link',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
'--patch=${patchVmcode.path}',
|
||||||
|
'--output=${artifact.path}',
|
||||||
|
'--app-id=license-flavor-patch-test',
|
||||||
|
'--app-build-id=host-aot-smoke',
|
||||||
|
'--base-flavor-id=free',
|
||||||
|
'--base-license-type=free',
|
||||||
|
'--flavor-id=pro',
|
||||||
|
'--license-type=pro',
|
||||||
|
'--sdk-hash=${_sha256File(File('${patchBuildDir.path}/gen_snapshot.exe'))}',
|
||||||
|
'--target-os=windows',
|
||||||
|
'--target-arch=x64',
|
||||||
|
'--obfuscation-map-hash=${_sha256File(baseMap)}',
|
||||||
|
'--offline-expires-at=$offlineExpiresAt',
|
||||||
|
'--full-snapshot=true',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final artifactJson = (jsonDecode(artifact.readAsStringSync()) as Map)
|
||||||
|
.cast<String, Object?>();
|
||||||
|
if (artifactJson['payload_kind'] != 'full-snapshot') {
|
||||||
|
throw StateError('Expected a directly loadable full-snapshot payload.');
|
||||||
|
}
|
||||||
|
final metadata = (artifactJson['metadata'] as Map).cast<String, Object?>();
|
||||||
|
if (metadata['offline_expires_at'] != '2030-01-01T00:00:00.000Z') {
|
||||||
|
throw StateError('Expected normalized offline_expires_at metadata.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await _runTool(toolWorkspace, [
|
||||||
|
'encrypt',
|
||||||
|
'--input=${artifact.path}',
|
||||||
|
'--output=${encrypted.path}',
|
||||||
|
'--key-id=test-key',
|
||||||
|
'--key-hex=$keyHex',
|
||||||
|
'--nonce-hex=$nonceHex',
|
||||||
|
]);
|
||||||
|
|
||||||
|
await _runTool(toolWorkspace, [
|
||||||
|
'verify',
|
||||||
|
'--input=${encrypted.path}',
|
||||||
|
'--key-hex=$keyHex',
|
||||||
|
'--artifact-sha256=${_sha256File(encrypted)}',
|
||||||
|
'--base-flavor-id=free',
|
||||||
|
'--base-license-type=free',
|
||||||
|
'--flavor-id=pro',
|
||||||
|
'--license-type=pro',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
'--now=$beforeExpiry',
|
||||||
|
]);
|
||||||
|
await _runToolExpectFailure(toolWorkspace, [
|
||||||
|
'verify',
|
||||||
|
'--input=${encrypted.path}',
|
||||||
|
'--key-hex=$keyHex',
|
||||||
|
'--base-flavor-id=free',
|
||||||
|
'--base-license-type=free',
|
||||||
|
'--flavor-id=pro',
|
||||||
|
'--license-type=pro',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
'--now=$afterExpiry',
|
||||||
|
]);
|
||||||
|
await _runToolExpectFailure(toolWorkspace, [
|
||||||
|
'verify',
|
||||||
|
'--input=${encrypted.path}',
|
||||||
|
'--key-hex=$wrongKeyHex',
|
||||||
|
'--base-flavor-id=free',
|
||||||
|
'--base-license-type=free',
|
||||||
|
'--flavor-id=pro',
|
||||||
|
'--license-type=pro',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
]);
|
||||||
|
await _runToolExpectFailure(toolWorkspace, [
|
||||||
|
'verify',
|
||||||
|
'--input=${encrypted.path}',
|
||||||
|
'--key-hex=$keyHex',
|
||||||
|
'--base-flavor-id=free',
|
||||||
|
'--base-license-type=free',
|
||||||
|
'--flavor-id=enterprise',
|
||||||
|
'--license-type=pro',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
]);
|
||||||
|
|
||||||
|
await _runTool(toolWorkspace, [
|
||||||
|
'dump-blobs',
|
||||||
|
'--input=${encrypted.path}',
|
||||||
|
'--key-hex=$keyHex',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
'--output=${reconstructedVmcode.path}',
|
||||||
|
'--now=$beforeExpiry',
|
||||||
|
]);
|
||||||
|
if (_sha256File(reconstructedVmcode) != _sha256File(patchVmcode)) {
|
||||||
|
throw StateError('Reconstructed patch vmcode does not match pro snapshot.');
|
||||||
|
}
|
||||||
|
|
||||||
|
_expectStatus(
|
||||||
|
await _runAot(patchBuildDir, reconstructedVmcode),
|
||||||
|
license: 'pro',
|
||||||
|
proFeature: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _runToolExpectFailure(toolWorkspace, [
|
||||||
|
'dump-blobs',
|
||||||
|
'--input=${encrypted.path}',
|
||||||
|
'--key-hex=$keyHex',
|
||||||
|
'--base=${baseVmcode.path}',
|
||||||
|
'--output=${reconstructedVmcode.path}',
|
||||||
|
'--now=$afterExpiry',
|
||||||
|
]);
|
||||||
|
|
||||||
|
stdout.writeln(
|
||||||
|
'AOT patch applied to license_flavor_patch_test successfully.',
|
||||||
|
);
|
||||||
|
stdout.writeln('base: license:free / pro-feature:off');
|
||||||
|
stdout.writeln('patch: license:pro / pro-feature:enabled');
|
||||||
|
stdout.writeln('artifact: ${encrypted.path}');
|
||||||
|
stdout.writeln('vmcode: ${reconstructedVmcode.path}');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _compileAot({
|
||||||
|
required Directory workspaceRoot,
|
||||||
|
required Directory dartRoot,
|
||||||
|
required Directory patchBuildDir,
|
||||||
|
required File packageConfig,
|
||||||
|
required String licenseType,
|
||||||
|
required File outputVmcode,
|
||||||
|
required File saveObfuscationMap,
|
||||||
|
File? loadObfuscationMap,
|
||||||
|
}) async {
|
||||||
|
final dart = '${dartRoot.path}/tools/sdks/dart-sdk/bin/dart.exe';
|
||||||
|
final genKernel = '${dartRoot.path}/pkg/vm/bin/gen_kernel.dart';
|
||||||
|
final source =
|
||||||
|
'${workspaceRoot.path}/testapps/license_flavor_patch_test/bin/license_status.dart';
|
||||||
|
final dill = File('${outputVmcode.parent.path}/${licenseType}_app.dill');
|
||||||
|
await _run(dart, [
|
||||||
|
genKernel,
|
||||||
|
'--packages',
|
||||||
|
packageConfig.path,
|
||||||
|
'--platform',
|
||||||
|
'${patchBuildDir.path}/vm_platform_product.dill',
|
||||||
|
'--aot',
|
||||||
|
'--target-os',
|
||||||
|
'windows',
|
||||||
|
'-Ddart.vm.product=true',
|
||||||
|
'-DLICENSE_TYPE=$licenseType',
|
||||||
|
'-o',
|
||||||
|
dill.path,
|
||||||
|
'--invocation-modes=compile',
|
||||||
|
'--verbosity=all',
|
||||||
|
source,
|
||||||
|
]);
|
||||||
|
|
||||||
|
final genSnapshot = '${patchBuildDir.path}/gen_snapshot.exe';
|
||||||
|
await _run(genSnapshot, [
|
||||||
|
'--snapshot-kind=app-aot-elf',
|
||||||
|
'--elf=${outputVmcode.path}',
|
||||||
|
'--strip',
|
||||||
|
'--obfuscate',
|
||||||
|
if (loadObfuscationMap != null)
|
||||||
|
'--load-obfuscation-map=${loadObfuscationMap.path}',
|
||||||
|
'--save-obfuscation-map=${saveObfuscationMap.path}',
|
||||||
|
dill.path,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _runAot(Directory patchBuildDir, File vmcode) async {
|
||||||
|
final result = await _run('${patchBuildDir.path}/dartaotruntime.exe', [
|
||||||
|
vmcode.path,
|
||||||
|
]);
|
||||||
|
return result.stdout as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProcessResult> _runTool(
|
||||||
|
Directory toolWorkspace,
|
||||||
|
List<String> args,
|
||||||
|
) async {
|
||||||
|
return _run(
|
||||||
|
'${toolWorkspace.parent.path}/dart-sdk-new/tools/sdks/dart-sdk/bin/dart.exe',
|
||||||
|
['packages/open_aot_patch_tools/bin/open_aot_patch_tools.dart', ...args],
|
||||||
|
workingDirectory: toolWorkspace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProcessResult> _runToolExpectFailure(
|
||||||
|
Directory toolWorkspace,
|
||||||
|
List<String> args,
|
||||||
|
) async {
|
||||||
|
final result = await Process.run(
|
||||||
|
'${toolWorkspace.parent.path}/dart-sdk-new/tools/sdks/dart-sdk/bin/dart.exe',
|
||||||
|
['packages/open_aot_patch_tools/bin/open_aot_patch_tools.dart', ...args],
|
||||||
|
workingDirectory: toolWorkspace.path,
|
||||||
|
);
|
||||||
|
if (result.exitCode == 0) {
|
||||||
|
throw StateError(
|
||||||
|
'Expected open_aot_patch_tools ${args.join(' ')} to fail.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProcessResult> _run(
|
||||||
|
String executable,
|
||||||
|
List<String> args, {
|
||||||
|
Directory? workingDirectory,
|
||||||
|
}) async {
|
||||||
|
final result = await Process.run(
|
||||||
|
executable,
|
||||||
|
args,
|
||||||
|
workingDirectory: workingDirectory?.path,
|
||||||
|
);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
throw ProcessException(
|
||||||
|
executable,
|
||||||
|
args,
|
||||||
|
[
|
||||||
|
'exit code ${result.exitCode}',
|
||||||
|
'stdout:',
|
||||||
|
result.stdout,
|
||||||
|
'stderr:',
|
||||||
|
result.stderr,
|
||||||
|
].join('\n'),
|
||||||
|
result.exitCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _expectStatus(
|
||||||
|
String output, {
|
||||||
|
required String license,
|
||||||
|
required bool proFeature,
|
||||||
|
}) {
|
||||||
|
final lines = const LineSplitter()
|
||||||
|
.convert(output.trim())
|
||||||
|
.where((line) => line.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
final expectedFeature = proFeature ? 'enabled' : 'off';
|
||||||
|
if (!lines.contains('license:$license') ||
|
||||||
|
!lines.contains('pro-feature:$expectedFeature')) {
|
||||||
|
throw StateError(
|
||||||
|
'Unexpected app status. Expected license:$license and '
|
||||||
|
'pro-feature:$expectedFeature, got:\n$output',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _sha256File(File file) {
|
||||||
|
final result = Process.runSync('certutil', [
|
||||||
|
'-hashfile',
|
||||||
|
file.path,
|
||||||
|
'SHA256',
|
||||||
|
]);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
throw ProcessException(
|
||||||
|
'certutil',
|
||||||
|
['-hashfile', file.path, 'SHA256'],
|
||||||
|
result.stderr.toString(),
|
||||||
|
result.exitCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final match = RegExp(
|
||||||
|
r'\b[0-9a-fA-F]{64}\b',
|
||||||
|
).firstMatch('${result.stdout}\n${result.stderr}');
|
||||||
|
if (match == null) {
|
||||||
|
throw StateError('Could not parse SHA256 hash for ${file.path}.');
|
||||||
|
}
|
||||||
|
return match.group(0)!.toLowerCase();
|
||||||
|
}
|
||||||
Submodule
+1
Submodule updater added at 3ac748ff28
Reference in New Issue
Block a user