Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/prebuilt-hermes-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"react-native-node-api": minor
---

Add a `prebuilt-hermes` command, which resolves an archive of the pinned Hermes
commit prebuilt for Apple platforms and prints its path. The archive holds the
`destroot` layout React Native's `hermes-engine.podspec` expects from a tarball
pointed at by `HERMES_ENGINE_TARBALL_PATH`, so an app that sets that variable
vendors the prebuilt frameworks rather than compiling Hermes as part of its own
build.

It is resolved from a local cache, then from a release asset published for the
pinned commit, and only built locally if neither has it. Its name covers
everything that changes its contents — the pinned commit, the React Native
version whose `ReactCommon/jsi` it is compiled against, the build type and the
platforms — so a stale archive can never be mistaken for a matching one.

Nothing consumes this yet: `pod install` still builds Hermes from source.
78 changes: 78 additions & 0 deletions .github/workflows/hermes-prebuilt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Hermes prebuilt

# Builds the pinned Hermes for Apple platforms and publishes it as a release
# asset, so `pod install` downloads it instead of compiling Hermes as part of
# every app build. The asset is keyed by everything that changes its contents,
# so a bumped pin publishes alongside the previous one rather than replacing it.

env:
GIT_CONFIG_COUNT: 1
GIT_CONFIG_KEY_0: "url.https://github.com/.insteadOf"
GIT_CONFIG_VALUE_0: "git@github.com:"

on:
workflow_dispatch:
push:
branches:
- main
- next
paths:
- "packages/host/src/node/cli/hermes.ts"
- "packages/host/src/node/cli/hermes-prebuilt.ts"
- ".github/workflows/hermes-prebuilt.yml"

# Two runs publishing the same tag would race on creating the release.
concurrency:
group: ${{ github.workflow }}

jobs:
publish:
name: Publish prebuilt Hermes
runs-on: macos-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: lts/krypton
- uses: pnpm/action-setup@v6
with:
cache: true
- run: pnpm install
- run: pnpm run build
# Resolved from the test app so the archive is built against the React
# Native version this repository actually pins.
- name: Resolve prebuilt Hermes name
id: hermes
working-directory: apps/test-app
run: |
echo "archive=$(pnpm exec react-native-node-api prebuilt-hermes --print name)" >> "$GITHUB_OUTPUT"
echo "tag=$(pnpm exec react-native-node-api prebuilt-hermes --print tag)" >> "$GITHUB_OUTPUT"
- name: Cache prebuilt Hermes
uses: actions/cache@v6
with:
path: ~/Library/Caches/react-native-node-api/hermes-prebuilt
key: ${{ steps.hermes.outputs.archive }}
# --no-download so a re-run rebuilds rather than round-tripping the asset
# it is about to publish.
- name: Build prebuilt Hermes
id: build
working-directory: apps/test-app
run: echo "path=$(pnpm exec react-native-node-api prebuilt-hermes --no-download)" >> "$GITHUB_OUTPUT"
# --latest=false keeps these out of the "latest release" slot, which
# belongs to the package releases changesets publishes.
- name: Publish as a release asset
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.hermes.outputs.tag }}
ARCHIVE_PATH: ${{ steps.build.outputs.path }}
run: |
if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "Prebuilt Hermes ($TAG)" \
--notes "Hermes, built for Apple platforms from the commit pinned in \`packages/host/src/node/cli/hermes.ts\`. Downloaded by \`react-native-node-api prebuilt-hermes\` and injected into the app's \`pod install\` through \`HERMES_ENGINE_TARBALL_PATH\`." \
--latest=false
fi
gh release upload "$TAG" "$ARCHIVE_PATH" --repo "$GITHUB_REPOSITORY" --clobber
24 changes: 23 additions & 1 deletion docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,29 @@ npx react-native-node-api <command> [options]
Run `npx react-native-node-api help` or `npx react-native-node-api help <command>` to see this same information from the CLI itself.

> [!NOTE]
> This document is hand-written from the [Commander](https://github.com/tj/commander.js) program definition in [`packages/host/src/node/cli/program.ts`](../packages/host/src/node/cli/program.ts) (with the `vendor-hermes` command defined in [`hermes.ts`](../packages/host/src/node/cli/hermes.ts)). It needs to be kept in sync by hand whenever a command or its options change.
> This document is hand-written from the [Commander](https://github.com/tj/commander.js) program definition in [`packages/host/src/node/cli/program.ts`](../packages/host/src/node/cli/program.ts) (with the `vendor-hermes` command defined in [`hermes.ts`](../packages/host/src/node/cli/hermes.ts) and `prebuilt-hermes` in [`hermes-prebuilt.ts`](../packages/host/src/node/cli/hermes-prebuilt.ts)). It needs to be kept in sync by hand whenever a command or its options change.

## `prebuilt-hermes [from]`

Resolves an archive of the pinned Hermes, prebuilt for Apple platforms, and prints its path. The archive holds the `destroot` layout React Native's `hermes-engine.podspec` expects from a tarball pointed at by `HERMES_ENGINE_TARBALL_PATH`, so an app that sets that variable vendors the prebuilt frameworks instead of compiling Hermes as part of its own build.

The archive is looked for in this order, and cached under `~/Library/Caches/react-native-node-api/hermes-prebuilt` (overridable with `REACT_NATIVE_NODE_API_CACHE_PATH`):

1. The cache, unless `--force` is passed.
2. The [release asset](https://github.com/callstackincubator/react-native-node-api/releases) published for the pinned commit by the `Hermes prebuilt` workflow, unless `--no-download` is passed.
3. A local build from the vendored source, unless `--no-build` is passed. This requires macOS and Xcode, and takes a while — but only once per pinned commit.

Its name covers everything that changes its contents: the pinned Hermes commit, the React Native version whose `ReactCommon/jsi` it is compiled against, the build type and the platforms. That makes it usable as a CI cache key.

- `[from]` — Path to a file inside the app package. Defaults to the current working directory.
- `--react-native-package <package-name>` — The React Native package to resolve Hermes for. Defaults to `react-native`.
- `--build-type <type>` — One of `debug` or `release`. `debug` enables Hermes' debugger. Defaults to `debug`.
- `--platform <name>` — Apple platform to build for, repeatable. Defaults to `iphoneos` and `iphonesimulator`.
- `--silent` — Don't print anything except the final path. Defaults to `false`.
- `--force` — Re-resolve the archive even if it is already cached. Defaults to `false`.
- `--no-download` — Don't download a published archive.
- `--no-build` — Don't build the archive locally when none is published.
- `--print <property>` — Print `name`, `tag` or `url` of the archive instead of resolving it.

## `vendor-hermes [from]`

Expand Down
Loading
Loading