From 0cc9bfe2442873a2df48ae7788ccec147fbefded Mon Sep 17 00:00:00 2001 From: Joel Winarske Date: Mon, 15 Jun 2026 20:29:39 -0700 Subject: [PATCH] feat: auto-resolve PackageKit native bridge; use hosted packagekit_dart - Depend on the published `packagekit_dart ^0.3.2` instead of a path dependency. - native_lib.dart: locate `libpackagekit_nc.so` from the package's own build or, for hosted installs, the `package:hooks` build-hook output under `.dart_tool/`, so the Linux backend works without `PK_NC_LIB`. - docs: full per-command README reference (every option, default, value set); add `example/`; refresh CHANGELOG. - chore: whitelist `Kitware`, `armv`, `armhf` for cspell. Signed-off-by: Joel Winarske --- .github/cspell.json | 3 + CHANGELOG.md | 7 + README.md | 435 +++++++++++++++++++++++++++--------- example/README.md | 69 ++++++ lib/src/pkg/native_lib.dart | 100 +++++++-- pubspec.yaml | 8 +- 6 files changed, 494 insertions(+), 128 deletions(-) create mode 100644 example/README.md diff --git a/.github/cspell.json b/.github/cspell.json index 1f7547b..1e4bc4d 100644 --- a/.github/cspell.json +++ b/.github/cspell.json @@ -21,6 +21,8 @@ "aarch", "aotruntime", "archs", + "armhf", + "armv", "Brewfile", "cmake", "cpp", @@ -35,6 +37,7 @@ "glibc", "homescreen", "icudtl", + "Kitware", "libapp", "libc", "libflutter", diff --git a/CHANGELOG.md b/CHANGELOG.md index 15bd7c7..00b65c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ Port of `meta-flutter/workspace-automation` to a Dart CLI. matrix. - feat: `emb setup` — one-shot provision; emits `setup_env.sh`. - feat: `emb env` — write `setup_env.sh`. +- feat: depend on the published `packagekit_dart` `^0.3.2` (hosted, not a path + dependency). +- feat: auto-resolve the PackageKit native bridge (`libpackagekit_nc.so`) — from + the package's own build or, for hosted installs, the `package:hooks` build-hook + output under `.dart_tool/` — so the Linux backend works without `PK_NC_LIB`. +- docs: full per-command reference (every option, default, and value set) in the + README; add `example/`. # 0.0.1 diff --git a/README.md b/README.md index 4d29898..ff57bbe 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ deployable app bundles for embedders such as port of [meta-flutter/workspace-automation](https://github.com/meta-flutter/workspace-automation) (`flutter_workspace.py` + `create_aot.py`). -It does the whole flow in a handful of commands: +The whole flow is a handful of commands: ``` deps → repos → Flutter SDK → engine → AOT → ivi-homescreen bundle @@ -21,32 +21,44 @@ deps → repos → Flutter SDK → engine → AOT → ivi-homescreen bundle [meta-flutter/flutter-engine](https://github.com/meta-flutter/flutter-engine) releases (auto, keyed by the SDK's engine commit). - **Cross-compile AOT** for `arm64` / `riscv64` from an `x86_64` host using the - engine's simulator `gen_snapshot` (no qemu — the artifact is self-contained). -- **Self-describing packages**: a package declares its build in its manifest and - `emb build ` does the rest. + engine's simulator `gen_snapshot` — no qemu (the artifact is self-contained). +- **Self-describing packages**: a package declares its build in a manifest and + `emb build ` does the rest. --- ## Requirements -- Dart SDK ≥ 3.10.1 (the `emb` tool itself). -- Linux for host **dependency install** (PackageKit — dnf/apt/zypper). The - macOS/Windows package backends are stubbed in this build. -- `git`, `tar`, `curl` on PATH. +- **Dart SDK ≥ 3.10.1** (to run/build the `emb` tool itself). +- **Linux** for host **dependency install** (PackageKit — dnf/apt/zypper). The + macOS (Homebrew) and Windows (WinGet) backends are stubbed in this build; all + other commands are cross-platform. +- `git`, `tar`, `curl` on `PATH`. - Cross-compiling to a device arch is supported **from an x86_64 host**. +--- + ## Install ```sh -# From this directory: +# From the package root: dart pub get -dart pub global activate --source=path . +dart pub global activate --source=path . # puts `emb` on PATH -# Then (ensure the pub-global bin dir is on PATH): emb --help ``` -You can also run it without activating: `dart run bin/emb.dart `. +Make sure the pub-global bin dir (`$PUB_CACHE/bin`, e.g. `~/.pub-cache/bin`) is +on `PATH`. You can also run without activating: + +```sh +dart run bin/emb.dart # from the package root +``` + +> The Linux backend loads `packagekit_dart`'s native bridge +> (`libpackagekit_nc.so`). `emb` locates it automatically — from the package's +> own build, or from the `package:hooks` build-hook output under +> `.dart_tool/`. Override with `PK_NC_LIB=/path/to/libpackagekit_nc.so`. --- @@ -69,114 +81,324 @@ emb bundle --app-path ./app/my_app --arch arm64 --build # ivi-homescreen --b=/bundle/my_app-release-arm64 ``` -`emb setup` runs everything; you can also run the phases individually -(`emb deps`, `emb sync`, `emb flutter`, `emb engine`). The workspace root is -`$FLUTTER_WORKSPACE`, else the current directory, else `--workspace `. +`emb setup` runs every phase; you can also run them individually +(`emb deps`, `emb sync`, `emb flutter`, `emb engine`). --- -## Building bundles +## Workspace & path resolution -A bundle is the directory layout `ivi-homescreen` consumes: +Every command that touches the workspace resolves its root **in this order**: + +1. `--workspace ` (explicit flag), else +2. the `$FLUTTER_WORKSPACE` environment variable, else +3. the current working directory. + +``` +/ # the resolved root + app/ # cloned source repositories + flutter/ # Flutter SDK + bundle/ # built bundles (default output) + setup_env.sh # generated environment script + .config/flutter_workspace/ + flutter-engine//... # downloaded + extracted engine SDKs + flutter-engine/bundle--/ # staged engine halves (icudtl + .so) +``` + +--- + +## Command reference + +``` +emb [arguments] +``` + +### Global options + +| Option | Description | +|---|---| +| `-h`, `--help` | Print usage. Works at the top level and per command (`emb --help`). | +| `-v`, `--version` | Print the CLI version. | +| `--[no-]verbose` | Noisy logging, including every shell command executed. | + +`--workspace` (`-w`), shown on most commands, follows the resolution order +above. `--mode`/`--arch` defaults and value sets differ **per command** — see +each entry. + +--- + +### `emb doctor` + +Report host detection (os / arch / distro) and package-manager backend +availability. No options. Exit code is non-zero if the backend is unavailable. + +```sh +emb doctor +``` + +--- + +### `emb setup` + +One-shot provision: **deps → repos → Flutter SDK → engine**, then writes +`setup_env.sh`. Each phase is individually skippable. + +| Option | Default | Description | +|---|---|---| +| `-c`, `--config ` | `configs` | Legacy JSON config directory. | +| `-p`, `--packages ` | — | Directory to discover self-describing `emb` manifests. | +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `--flutter-version ` | `globals.json` `flutter_version` | Flutter version/tag/branch. | +| `--arch ` | host arch | Engine arch to prefetch. | +| `-m`, `--mode ` | `release` | Engine runtime modes to prefetch (repeatable): `release`, `profile`, `debug`. | +| `-y`, `--yes` | off | Skip the deps confirmation prompt (CI). | +| `--skip-deps` | off | Skip host dependency install. | +| `--skip-sync` | off | Skip repository sync. | +| `--skip-flutter` | off | Skip Flutter SDK install. | +| `--skip-engine` | off | Skip engine artifact fetch. | + +```sh +emb setup --config ../configs --yes +emb setup --config ../configs --skip-deps --skip-sync # SDK + engine only +``` + +--- + +### `emb deps` + +Coalesce host dependencies across all selected manifests, filter to what's +**missing** on this host, and install the union in **one** transaction. + +| Option | Default | Description | +|---|---|---| +| `-c`, `--config ` | `configs` | Legacy JSON config directory. **Repeatable.** | +| `-p`, `--packages ` | — | Directory to discover self-describing `emb` manifests. **Repeatable.** | +| `--dry-run` | off | Resolve and print the install plan without changing the system. | +| `-y`, `--yes` | off | Skip the confirmation prompt (CI). | + +```sh +emb deps --config ../configs --dry-run # plan only +emb deps --config ../configs --yes # install in one transaction +``` + +--- + +### `emb sync` + +Clone/update source repositories into `/app` (bounded concurrency). + +| Option | Default | Description | +|---|---|---| +| `-c`, `--config ` | `configs` | Legacy JSON config directory. **Repeatable.** | +| `-p`, `--packages ` | — | Directory to discover self-describing `emb` manifests. | +| `--repos ` | — | A JSON file containing a bare array of repo entries. **Repeatable.** | +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `-j`, `--concurrency ` | `4` | Maximum concurrent git operations. | + +```sh +emb sync --config ../configs -j 8 +``` + +--- + +### `emb flutter` + +Install the Flutter SDK into `/flutter`. + +| Option | Default | Description | +|---|---|---| +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `--flutter-version ` | `globals.json` `flutter_version` | Version/tag/branch to check out. | +| `-c`, `--config ` | `configs` | Directory to read `globals.json` from. | +| `--configure` | off | Run `flutter config` (desktop + custom devices) and `flutter doctor` after install. | + +```sh +emb flutter --flutter-version 3.44.2 --configure +``` + +--- + +### `emb engine` + +Fetch prebuilt Flutter engine artifacts (auto fetch-else-build). Modes you don't +fetch here are auto-fetched on demand by `emb bundle`/`emb build`. + +| Option | Default | Description | +|---|---|---| +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `--commit ` | `/flutter/bin/internal/engine.version` | Engine commit to fetch. | +| `--arch ` | host arch | Engine arch token (see [Architectures](#architectures--cross-compiling)). | +| `-m`, `--mode ` | `release` | Runtime modes to fetch (repeatable): `release`, `profile`, `debug`. | +| `--clean` | off | Re-stage bundles even when already present. | +| `--check` | off | Only check prebuilt availability; do not download. | + +```sh +emb engine --arch arm64 --mode release --mode profile +emb engine --arch riscv64 --check # is a prebuilt available? +``` + +--- + +### `emb aot` + +Build the AOT image (`libapp.so`) for a Flutter app — the AOT primitive used by +`emb bundle --build`. **Debug is not AOT**, so only `release`/`profile` apply. + +| Option | Default | Description | +|---|---|---| +| `-a`, `--app-path ` | **mandatory** | Path to the Flutter application to build. | +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `-m`, `--mode ` | `release` | Runtime modes to build (repeatable): `release`, `profile`. | +| `--arch ` | host arch | Target arch for `gen_snapshot` (e.g. `arm64` for a Pi). | +| `--gen-snapshot ` | auto-resolved | Explicit `gen_snapshot` path (overrides resolution). | +| `--glibc-sysroot ` | artifact's bundled `clang_x64/lib64` | Directory with `ld-linux` + libc to run `gen_snapshot` under. | + +```sh +emb aot --app-path ./app/my_app --arch arm64 --mode release --mode profile +``` + +--- + +### `emb bundle` + +Assemble an ivi-homescreen bundle from app + engine artifacts. With `--build` it +runs `emb aot` first; the engine SDK for the `(mode, arch)` is fetched +implicitly if not already cached. + +| Option | Default | Description | +|---|---|---| +| `-a`, `--app-path ` | **mandatory** | Path to the Flutter application. | +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `-m`, `--mode ` | `release` | Single mode: `debug` (JIT, no AOT), `profile`, or `release` (AOT). | +| `--arch ` | host arch | Target arch (e.g. `arm64`). | +| `-o`, `--output ` (alias `--out`) | `/bundle/--` | Output bundle directory — any path. | +| `--build` | off | Run `emb aot` first to (re)build `flutter_assets` + `libapp.so`. | + +```sh +emb bundle --app-path ./app/my_app --arch arm64 --build # release +emb bundle --app-path ./app/my_app --arch arm64 --build --mode debug # JIT +emb bundle --app-path ./app/my_app --arch riscv64 --build --mode profile +emb bundle --app-path ./app/my_app --build # host +emb bundle --app-path ./app/my_app --arch arm64 --output /tmp/out # custom path +``` + +--- + +### `emb build` + +Build a **self-describing** package — a directory with an `emb.yaml` (or an +`emb:` key in `pubspec.yaml`) — into bundles, using the manifest's `build:` +matrix. The package directory is a **positional argument** (mandatory; omitting +it prints a usage error). + +``` +emb build [options] +``` + +| Option | Default | Description | +|---|---|---| +| `` | **mandatory (positional)** | Directory containing `emb.yaml` or a `pubspec.yaml` with an `emb:` key. | +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `--arch ` | manifest's `archs` | Override target arch(es). **Repeatable.** | +| `-m`, `--mode ` | manifest's `modes` | Override mode(s): `debug`, `profile`, `release`. **Repeatable.** | +| `--no-build` | off | Assemble from existing artifacts; skip compiling. | + +```sh +emb build ./app/my_app # full manifest matrix +emb build ./app/my_app --arch arm64 --mode release # override the matrix +emb build ./app/my_app --no-build # assemble only +``` + +#### Manifest (`emb.yaml`) + +```yaml +id: my_app +type: app +build: + app_path: . # Flutter app dir, relative to this manifest (default ".") + archs: [arm64, x86_64] # target architectures (default: host) + modes: [release, debug] # default: [release] + output: bundles # optional output dir, relative to the workspace +deps: # optional host packages, by OS / distro + linux: + fedora: [pkg-config, freetype-devel] + ubuntu: [pkg-config, libfreetype-dev] + macos: [pkg-config, freetype] + windows: [Kitware.CMake] +``` + +--- + +### `emb env` + +Write `setup_env.sh` (`PATH` for Flutter/Dart, `FLUTTER_WORKSPACE`, `PUB_CACHE`, +`XDG_CONFIG_HOME`, the engine version, …). + +| Option | Default | Description | +|---|---|---| +| `-w`, `--workspace ` | resolution order | Workspace root. | +| `-o`, `--output ` | `/setup_env.sh` | Output file path. | +| `--print` | off | Print to stdout instead of writing a file. | + +```sh +emb env # write /setup_env.sh +emb env --print # preview on stdout +``` + +--- + +### `emb update` + +Update the CLI itself (via `pub`). No options. + +```sh +emb update +``` + +--- + +## Modes + +| `--mode` | How it builds | Bundle contents | Valid in | +|-----------|---------------------------------------|-----------------|----------| +| `debug` | JIT — `flutter build bundle --debug` | `kernel_blob.bin`, **no** `libapp.so` | `bundle`, `build`, `engine`, `setup` | +| `profile` | AOT (`gen_snapshot`) | `libapp.so` (+ profile engine) | all | +| `release` | AOT (`gen_snapshot`) | `libapp.so` (+ release engine) | all | + +`emb aot` only accepts `release`/`profile` (debug isn't AOT). Where a command +takes **multiple** modes (`aot`, `build`, `engine`, `setup`), repeat the flag: +`--mode release --mode profile`. `emb bundle` takes a **single** mode. + +A bundle is the directory ivi-homescreen consumes: ``` / data/flutter_assets/ # app code + assets data/icudtl.dat # from the engine lib/libflutter_engine.so # from the engine (per mode) - lib/libapp.so # AOT image (profile/release only) -``` - -### Modes - -| `--mode` | how it builds | bundle contents | -|-----------|---------------------------------------|------------------------------| -| `debug` | JIT — `flutter build bundle --debug` | `kernel_blob.bin`, **no** `libapp.so` | -| `profile` | AOT (`gen_snapshot`) | `libapp.so` (+ profile engine) | -| `release` | AOT (`gen_snapshot`) | `libapp.so` (+ release engine) | - -Defaults: **`--mode release`**, **`--arch` = host arch**. The engine SDK for the -selected `(mode, arch)` is **fetched implicitly** if it isn't already cached, so -you don't have to run `emb engine` first. - -```sh -emb bundle --app-path ./app/my_app --arch arm64 --build # release -emb bundle --app-path ./app/my_app --arch arm64 --build --mode debug # JIT -emb bundle --app-path ./app/my_app --arch riscv64 --build --mode profile -emb bundle --app-path ./app/my_app --build # host (x86_64) -emb bundle --app-path ./app/my_app --arch arm64 --out /tmp/bundle-out # custom path -``` - -### Manifest-driven (`emb build`) - -Give a package an `emb` manifest (an `emb.yaml`, or an `emb:` key in -`pubspec.yaml`) so it builds itself with no flags: - -```yaml -# emb.yaml -id: my_app -type: app -build: - app_path: . # Flutter app dir, relative to this manifest - archs: [arm64, x86_64] # target architectures (default: host) - modes: [release, debug]# default: [release] - output: bundles # optional output dir, relative to the workspace -deps: # optional host packages, by OS / distro - linux: - fedora: [pkg-config, freetype-devel] - ubuntu: [pkg-config, libfreetype-dev] -``` - -```sh -emb build ./app/my_app # builds the full arch × mode matrix -emb build ./app/my_app --arch arm64 --mode release # override the matrix -emb build ./app/my_app --no-build # assemble from existing artifacts only + lib/libapp.so # AOT image (profile/release only) ``` --- -## Cross-compiling +## Architectures & cross-compiling -Cross-compile is **x86_64 host → target arch** (`arm64`, `riscv64`, …). The -meta-flutter engine SDK ships a host-x86_64 **simulator** `gen_snapshot` -(`linux_simarm64`) that emits target code and carries its own loader/libc in -`clang_x64/lib64`, so it runs on any host glibc — **no qemu required**. `emb` -selects and runs it automatically; `--gen-snapshot` / `--glibc-sysroot` override -if you need to. Building **on** an arm64 host targets arm64 natively. +`--arch` accepts machine names or Flutter tokens; they normalize to one of four +**engine arch tokens** used in paths and bundle names: ---- - -## Commands - -| Command | What it does | +| You pass | Normalized token | |---|---| -| `emb doctor` | Host detection (os/arch/distro) + package-backend availability. | -| `emb setup` | One-shot provision: deps → repos → Flutter SDK → engine, writes `setup_env.sh`. Phases are skippable (`--skip-deps`, …). | -| `emb deps` | Coalesce host deps across manifests, filter to what's missing, install in one transaction. `--dry-run`, `--yes`. | -| `emb sync` | Clone/update source repos into `/app` (concurrent). | -| `emb flutter` | Clone/checkout the Flutter SDK into `/flutter`. | -| `emb engine` | Fetch prebuilt engine artifacts for `--arch`/`--mode`. `--check` to test availability. | -| `emb aot` | Build `libapp.so` (profile/release) for an app — the AOT primitive. | -| `emb bundle` | Build (with `--build`) + assemble an ivi-homescreen bundle. | -| `emb build` | Build a self-describing package (manifest `build:`) into bundles. | -| `emb env` | Write `setup_env.sh` (`--print` to stdout). | +| `x64`, `x86_64`, `amd64` | `x86_64` | +| `arm64`, `aarch64` | `arm64` | +| `arm`, `armv7`, `armv7hf`, `armhf` | `armv7hf` | +| `riscv64` | `riscv64` | -Run `emb help ` for the full flag list. - ---- - -## Workspace layout - -``` -/ # $FLUTTER_WORKSPACE - app/ # cloned source repos - flutter/ # Flutter SDK - bundle/ # built bundles (default output) - setup_env.sh # generated env - .config/flutter_workspace/ - flutter-engine//... # downloaded + extracted engine SDKs - flutter-engine/bundle--/ # staged engine halves -``` +Cross-compile is **x86_64 host → target arch**. The meta-flutter engine SDK +ships a host-x86_64 **simulator** `gen_snapshot` that emits target code and +carries its own loader/libc in `clang_x64/lib64`, so it runs on any host glibc — +**no qemu required**. `emb` selects and runs it automatically; `--gen-snapshot` +/ `--glibc-sysroot` override if needed. Building **on** an arm64 host targets +arm64 natively. --- @@ -184,13 +406,14 @@ Run `emb help ` for the full flag list. ```sh dart pub get -dart analyze # very_good_analysis +dart analyze dart test ``` -> Note: the Linux PackageKit backend loads `packagekit_dart`'s native bridge. -> `emb` locates it automatically; set `PK_NC_LIB=/path/to/libpackagekit_nc.so` -> to override. +The macOS/Windows package backends live in `lib/src/pkg/_platform/` and are +excluded from the default (Linux) build — see that folder's `README.md`. Dart +has no OS-conditional dependencies, so they're wired in only on per-OS builds +that add `brew_dart` / `winget_dart`. [license_badge]: https://img.shields.io/badge/license-Apache%202.0-blue.svg [license_link]: https://opensource.org/licenses/Apache-2.0 diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..1f13150 --- /dev/null +++ b/example/README.md @@ -0,0 +1,69 @@ +# emb examples + +`emb` is a CLI. Install it, then run the commands below. See the +[top-level README](../README.md) for the full command/flag reference. + +```sh +# from the package root +dart pub get +dart pub global activate --source=path . # puts `emb` on PATH +# or run without activating: dart run bin/emb.dart +``` + +## 1. Inspect the host + +```sh +emb doctor +``` + +``` +Host + os: linux + arch: x86_64 (flutter: x64, engine: x86_64) + host type: fedora + version: 43 + +Package backend + selected: packagekit +✓ packagekit is available +``` + +## 2. Provision a workspace + +Installs host dependencies (one transaction), clones repos, installs the +Flutter SDK, and fetches the prebuilt engine — then writes `setup_env.sh`. + +```sh +emb setup --config ../configs --yes +. ./setup_env.sh # Flutter/Dart on PATH, FLUTTER_WORKSPACE, … +``` + +Run phases individually if you prefer: `emb deps`, `emb sync`, `emb flutter`, +`emb engine`. + +## 3. Build an ivi-homescreen bundle + +```sh +# Release AOT bundle for a Raspberry Pi (arm64), cross-compiled from x86_64: +emb bundle --app-path ./app/my_app --arch arm64 --build + +# Debug (JIT) bundle for the host: +emb bundle --app-path ./app/my_app --mode debug --build +``` + +The bundle directory (`data/flutter_assets`, `data/icudtl.dat`, +`lib/libflutter_engine.so`, and `lib/libapp.so` for profile/release) is what +ivi-homescreen runs: + +```sh +ivi-homescreen --b=/bundle/my_app-release-arm64 +``` + +## 4. Self-describing packages + +A package that ships an `emb.yaml` (or an `emb:` key in `pubspec.yaml`) builds +itself with no flags: + +```sh +emb build ./app/my_app # builds the manifest's arch × mode matrix +``` diff --git a/lib/src/pkg/native_lib.dart b/lib/src/pkg/native_lib.dart index 8ecdc6b..adcd96f 100644 --- a/lib/src/pkg/native_lib.dart +++ b/lib/src/pkg/native_lib.dart @@ -4,37 +4,105 @@ import 'dart:isolate'; import 'package:packagekit_dart/packagekit_dart.dart'; import 'package:path/path.dart' as p; -/// Best-effort: point `packagekit_dart`'s loader at the package's own prebuilt +/// Best-effort: point `packagekit_dart`'s loader at a prebuilt /// `libpackagekit_nc.so` so the Linux backend works without the user having to /// export `PK_NC_LIB`. /// /// The package's loader is synchronous and cannot resolve its own location, so -/// we do it here (async) via [Isolate.resolvePackageUri] and hand the path to -/// [setPackagekitLibraryPath] before the first `PkClient` use. No-ops on -/// non-Linux hosts, when `PK_NC_LIB` is already set, or when the package can't -/// be resolved (e.g. fully AOT-compiled) — in which case the loader's own +/// we do it here (async) and hand the path to [setPackagekitLibraryPath] before +/// the first `PkClient` use. Two layouts are handled: +/// +/// 1. **Path / vendored dependency** — the `.so` sits in the package itself +/// (`/build/` or `/native/_build/`), found via +/// [Isolate.resolvePackageUri]. +/// 2. **Hosted (pub.dev) dependency** — the package dir in the pub cache is +/// read-only, so the `package:hooks` build hook compiles the `.so` into the +/// *consuming* project's `.dart_tool/hooks_runner/...` output instead. We +/// locate it there. +/// +/// No-ops on non-Linux hosts, when `PK_NC_LIB` is already set, or when nothing +/// is found (e.g. a fully AOT-compiled binary) — in which case the loader's own /// env/next-to-exe/system fallbacks still apply. Future configurePackageKitLibrary() async { if (!Platform.isLinux) return; if ((Platform.environment['PK_NC_LIB'] ?? '').isNotEmpty) return; try { + // (1) The package's own prebuilt locations (path / vendored builds). final uri = await Isolate.resolvePackageUri( Uri.parse('package:packagekit_dart/packagekit_dart.dart'), ); - if (uri == null || !uri.isScheme('file')) return; - // /lib/packagekit_dart.dart → - final pkgRoot = p.dirname(p.dirname(uri.toFilePath())); - final candidates = [ - p.join(pkgRoot, 'build', 'libpackagekit_nc.so'), - p.join(pkgRoot, 'native', '_build', 'libpackagekit_nc.so'), - ]; - for (final c in candidates) { - if (File(c).existsSync()) { - setPackagekitLibraryPath(c); - return; + if (uri != null && uri.isScheme('file')) { + // /lib/packagekit_dart.dart → + final pkgRoot = p.dirname(p.dirname(uri.toFilePath())); + for (final c in [ + p.join(pkgRoot, 'build', 'libpackagekit_nc.so'), + p.join(pkgRoot, 'native', '_build', 'libpackagekit_nc.so'), + ]) { + if (File(c).existsSync()) { + setPackagekitLibraryPath(c); + return; + } } } + + // (2) The build hook's output under the consuming project's .dart_tool. + final hookBuilt = _findHookBuiltLibrary(); + if (hookBuilt != null) { + setPackagekitLibraryPath(hookBuilt); + return; + } } on Object { // Best-effort only; fall through to the loader's other strategies. } } + +/// Locate the `libpackagekit_nc.so` produced by the `package:hooks` build hook, +/// which lands under `.dart_tool/hooks_runner/shared/packagekit_dart/build/ +/// //libpackagekit_nc.so` in the consuming project. +/// +/// Returns the most-recently-built match, or `null` if none is found. +String? _findHookBuiltLibrary() { + final dartTool = _locateDartTool(); + if (dartTool == null) return null; + final buildRoot = Directory( + p.join(dartTool, 'hooks_runner', 'shared', 'packagekit_dart', 'build'), + ); + if (!buildRoot.existsSync()) return null; + + File? best; + // Layout is shallow: build///libpackagekit_nc.so. + for (final hashDir in buildRoot.listSync().whereType()) { + for (final cmakeDir in hashDir.listSync().whereType()) { + final so = File(p.join(cmakeDir.path, 'libpackagekit_nc.so')); + if (!so.existsSync()) continue; + if (best == null || + so.statSync().modified.isAfter(best.statSync().modified)) { + best = so; + } + } + } + return best?.path; +} + +/// Best-effort discovery of the consuming project's `.dart_tool` directory: +/// prefer the running package config, else walk up from the current directory. +String? _locateDartTool() { + final pc = Platform.packageConfig; + if (pc != null && pc.isNotEmpty) { + final path = pc.startsWith('file:') ? Uri.parse(pc).toFilePath() : pc; + // .../.dart_tool/package_config.json → .../.dart_tool + final dir = p.dirname(path); + if (p.basename(dir) == '.dart_tool' && Directory(dir).existsSync()) { + return dir; + } + } + var dir = Directory.current; + for (var i = 0; i < 8; i++) { + final dartTool = p.join(dir.path, '.dart_tool'); + if (Directory(dartTool).existsSync()) return dartTool; + final parent = dir.parent; + if (parent.path == dir.path) break; + dir = parent; + } + return null; +} diff --git a/pubspec.yaml b/pubspec.yaml index f146312..c610274 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,10 +4,7 @@ description: >- ivi-homescreen bundles (cross-compiled AOT for arm64/riscv64). version: 0.1.0 repository: https://github.com/toyota-connected/emb_cli -# Not published to pub.dev yet: depends on sibling packages via path. -# To publish, see README/CHANGELOG — the path deps must become hosted. -publish_to: none - +issue_tracker: https://github.com/toyota-connected/emb_cli/issues environment: sdk: ">=3.10.1 <4.0.0" @@ -21,8 +18,7 @@ dependencies: # backends live in lib/src/pkg/_platform/ and are wired in per-OS builds — # Dart has no OS-conditional dependencies, so depending on them here would # force them to compile on every platform. See lib/src/pkg/_platform/README. - packagekit_dart: - path: ../app/packagekit_dart + packagekit_dart: ^0.3.2 path: ^1.9.0 pub_updater: ">=0.4.0 <0.6.0" yaml: ^3.1.0