Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions .github/workflows/buildAdHoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ jobs:
mobile-expensify-ref: ${{ inputs.MOBILE_EXPENSIFY_REF }}
pull-request-number: ${{ inputs.APP_PR_NUMBER }}
force-native-build: ${{ inputs.FORCE_NATIVE_BUILD }}
download-dsyms: true
secrets: inherit

buildWeb:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/buildIOS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ on:
description: Force a full native build, bypassing Rock remote cache
type: string
default: 'false'
download-dsyms:
description: Download the prebuilt react-native dSYMs so Sentry can symbolicate its native frames. Off by default - it adds two large downloads per build.
type: boolean
default: false

outputs:
IOS_VERSION:
Expand All @@ -51,6 +55,7 @@ jobs:
runs-on: blacksmith-12vcpu-macos-latest
env:
PULL_REQUEST_NUMBER: ${{ inputs.pull-request-number }}
RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS: ${{ inputs.download-dsyms && '1' || '0' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the dSYM mode in Rock's native fingerprint

When the same commit is first built by verifyHybridApp.yml, this input defaults to false, while deployment and AdHoc builds later set it to true. Rock downloads an existing native build when its fingerprint matches (contributingGuides/SETUP_IOS.md:5-9), but rock.config.mjs:24-34 does not include RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS in the fingerprint environment. Consequently, the dSYM-enabled run can reuse the archive produced without dSYMs and skip the pod install/archive work that consumes them, leaving React Native frames unsymbolicated; add this variable to fingerprint.env or otherwise force distinct native fingerprints for the two modes.

Useful? React with 👍 / 👎.

@mateuuszzzzz mateuuszzzzz Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment can be a bit misleading, but the point is:

The output of the build is an xcarchive. This archive may or may not include dSYMs. I don't think the lack of this distinction breaks the build (in fact, it shouldn't, since debug symbols don't affect the binary layout), but it does clearly draw a line between archives with and without dSYMs.

I.e., our final output depends on the dSYMs flag, so it makes sense to include it in the rock fingerprint.

outputs:
IOS_VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }}
ROCK_ARTIFACT_URL: ${{ steps.set-artifact-url.outputs.ARTIFACT_URL }}
Expand Down Expand Up @@ -112,6 +117,21 @@ jobs:
- name: Install New Expensify Gems
run: bundle install

- name: Resolve react-native artifact version
Comment thread
mateuuszzzzz marked this conversation as resolved.
id: resolve-rn-artifacts
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
VERSION=$(bun scripts/artifacts-utils/resolve-artifacts.ts --platform=ios --hybrid=true --new-dot-root="$GITHUB_WORKSPACE" | jq -r '.version // "source"')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
VERSION=$(bun scripts/artifacts-utils/resolve-artifacts.ts --platform=ios --hybrid=true --new-dot-root="$GITHUB_WORKSPACE" | jq -r '.version // "source"')
VERSION=$(./node_modules/.bin/bun scripts/artifacts-utils/resolve-artifacts.ts --platform=ios --hybrid=true --new-dot-root="$GITHUB_WORKSPACE" | jq -r '.version // "source"')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or alternatively: update setupNode composite action to add that to the path so plain bun works. npx bun would also work, but it spins up a node runtime just to resolve a known binary path for the bun binary, which then must start up. So it adds ~60ms vs executing bun directly. nbd in this context

echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"

- name: Cache react-native artifacts
Comment thread
mateuuszzzzz marked this conversation as resolved.
# v5.0.1
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb
with:
path: ~/Library/Caches/Expensify/react-native-artifacts
key: ${{ runner.os }}-rn-artifacts-react-hybrid-${{ steps.resolve-rn-artifacts.outputs.VERSION }}-dsyms-${{ inputs.download-dsyms }}

- name: Cache Pod dependencies

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the Pods cache key doesn't account for download-dsyms but the dSYM state lives inside the cached path: process_dsyms merges the symbols into the framework tarball under Pods/ReactNativeCore-artifacts (artifacts_dir in rncore.rb).

verifyHybridApp.yml calls this workflow with the download-dsyms: 'false' and runs on pull_request_target, so its caches are main-scoped and restorable by staging/production deploy runs. This creates a situation where a deploy that restores a Pods cache and then skips the CocoaPods installation will archive without the react-native dSYMs. As a result, Sentry silently loses the symbols, depending on who saved the cache last.

We could append -${{ inputs.download-dsyms }} to the cache key to prevent this from happening

@mateuuszzzzz mateuuszzzzz Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding pods installation step: This step is a bit outdated at the moment. Currently it does not have Github token in context so it cannot even install pods properly. Pods are also installed by Rock (so we have duplication) and this step does it correctly, but at the same time it does not have advanced caching logic. This is something I'd like to address in separate PR that will improve current pods installation and disable this step in rock.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created the follow-up issue: #99331

It captures the three points from this thread — the missing GitHub token context, the duplication with Rock, and the lack of advanced caching — with the proposal to improve the pods installation and disable the equivalent step in Rock.


view run

# v5.0.1
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ jobs:
with:
ref: ${{ needs.prep.outputs.DEPLOY_SHA }}
variant: Release
download-dsyms: true
secrets: inherit

iosUploadTestflight:
Expand Down
4 changes: 2 additions & 2 deletions scripts/artifacts-utils/android/ExpensiUtils.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ ext.ExpensiUtils = new Object() {
}

// Returns [buildFromSource, version, githubUsername, githubToken]; any failure -> build from source.
Map resolveArtifacts(String packageName, String newDotRootDir, boolean isHybrid) {
Map resolveArtifacts(String newDotRootDir, boolean isHybrid) {
def cmd = [
"bun", "${newDotRootDir}/scripts/artifacts-utils/resolve-artifacts.ts",
"--platform=android", "--package=${packageName}", "--hybrid=${isHybrid}", "--new-dot-root=${newDotRootDir}"
"--platform=android", "--hybrid=${isHybrid}", "--new-dot-root=${newDotRootDir}"
]
try {
def result = runCommand(cmd, 120, new File(newDotRootDir))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ settings.extensions.configure(PatchedArtifactsConfig) { config ->
config.packageName = getProperty('patchedArtifacts.packageName')

def isHybrid = config.packageName == 'react-hybrid'
def resolution = ExpensiUtils.resolveArtifacts(config.packageName, getNewDotRootDir().toString(), isHybrid)
def resolution = ExpensiUtils.resolveArtifacts(getNewDotRootDir().toString(), isHybrid)
config.version = resolution.version
config.buildFromSource = resolution.buildFromSource

Expand Down
7 changes: 3 additions & 4 deletions scripts/artifacts-utils/ios/patched_ios_artifacts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ def self.log(message, level = :info)

def self.setup
is_hybrid = ENV['IS_HYBRID_APP'] == 'true'
package_name = is_hybrid ? 'react-hybrid' : 'react-standalone'

# Manual escape hatch: force a full from-source build (e.g. to unblock a prebuild issue).
build_from_source = ENV['BUILD_RN_FROM_SOURCE'] == '1'
# The escape hatch short-circuits before anything touches the network: no resolver, no prefetch.
resolution = build_from_source ? {'buildFromSource' => true, 'version' => nil} : prefetch(resolve(package_name, is_hybrid))
resolution = build_from_source ? {'buildFromSource' => true, 'version' => nil} : prefetch(resolve(is_hybrid))

# A single decision drives both prebuilt flags, so we never land in a mixed
# prebuilt-deps / source-core state (which desyncs the CocoaPods sandbox).
Expand Down Expand Up @@ -181,10 +180,10 @@ def self.download_and_verify(url, destination, github_token)
destination
end

def self.resolve(package_name, is_hybrid)
def self.resolve(is_hybrid)
cmd = [
'bun', File.join(NEW_DOT_ROOT, 'scripts/artifacts-utils/resolve-artifacts.ts'),
'--platform=ios', "--package=#{package_name}", "--hybrid=#{is_hybrid}", "--new-dot-root=#{NEW_DOT_ROOT}"
'--platform=ios', "--hybrid=#{is_hybrid}", "--new-dot-root=#{NEW_DOT_ROOT}"
]
# stdout is pure JSON; the resolver logs to stderr.
output = IO.popen(cmd, chdir: NEW_DOT_ROOT, &:read)
Expand Down
7 changes: 4 additions & 3 deletions scripts/artifacts-utils/resolve-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import resolveArtifacts from './lib/artifactsResolver';
*
* Usage:
* bun scripts/artifacts-utils/resolve-artifacts.ts \
* --platform=ios --package=react-hybrid --hybrid=true --new-dot-root=.
* --platform=ios --hybrid=true --new-dot-root=.
*
* Prints the result as JSON to stdout (logs go to stderr) and always exits 0.
*/
const args = parseCommandLineArguments();
const platform = args.platform;
const packageName = args.package ?? '';
const isHybrid = args.hybrid === 'true';
const packageName = isHybrid ? 'react-hybrid' : 'react-standalone';

if (platform !== 'ios' && platform !== 'android') {
process.stderr.write(`[PatchedArtifacts] Invalid or missing --platform "${platform ?? ''}" (expected "ios" or "android"); building from source.\n`);
process.stdout.write(JSON.stringify({buildFromSource: true, version: null, packageName, artifactId: ''}));
process.exit(0);
}

const options = {packageName, newDotRoot: args['new-dot-root'] ?? '.', isHybrid: args.hybrid === 'true'};
const options = {packageName, newDotRoot: args['new-dot-root'] ?? '.', isHybrid};
const resolution = platform === 'ios' ? resolveArtifacts({...options, platform: 'ios'}) : resolveArtifacts({...options, platform: 'android'});

resolution
Expand Down
Loading