Skip to content

Create prebuilt React Native artifacts #59738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

mateuuszzzzz
Copy link
Contributor

@mateuuszzzzz mateuuszzzzz commented Apr 7, 2025

Explanation of Change

TO DO:

  • Move entire workflow from POC here.
  • Modify it to support building artifacts for:
    • Standalone NewDot
    • HybridApp (we need separate flow for HybridApp as it has own react-native patches)
  • Optimise the checkout steps to fetch only the minimum required content

Fixed Issues

$ #57120
PROPOSAL:

Tests

  • Verify that no errors appear in the JS console

Offline tests

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
MacOS: Desktop

Copy link
Contributor

github-actions bot commented Apr 7, 2025

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

Copy link
Contributor

github-actions bot commented Apr 8, 2025

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

Copy link
Contributor

github-actions bot commented Apr 8, 2025

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

Copy link
Contributor

github-actions bot commented Apr 8, 2025

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

Copy link
Contributor

github-actions bot commented Apr 8, 2025

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

@mateuuszzzzz
Copy link
Contributor Author

mateuuszzzzz commented Apr 10, 2025

@roryabraham PR in the current form should be ready, feel free to review it. I tested it on my forked App repository today and it worked fine. It created 2 artifacts for standalone NewDot and HybridApp here: https://github.com/mateuuszzzzz?tab=packages&repo_name=App

I have a few things to mention/discuss:

  • I noticed issue with 409 status code during publication. It seems like this is known problem https://github.com/orgs/community/discussions/149386. It doesn't break publication process, but causes the job to fail. It started to happen today so maybe my account is broken after many test publications. (You can see this error here)

  • I was thinking about re-adding workflow_dispatch so we could trigger workflow manually in order to test it.

  • In order to publish a package we use slightly modified version of this publish.gradle file. Let me know if there are things we'd like to add there. Especially, we could consider adding own signing{Key,Pwd}

  • I’m currently using ubuntu-latest for all jobs, but I think it’s worth switching to ubuntu-latest-xl for the jobs that generate artifacts.

  • In a separate PR, I'd like to add the changes necessary to enable the use of custom build artifacts during regular builds.

  • Publication process uses github token and github actor. I had to enable package write permissions in order to use it on my fork. We might want to consider switching to bot credentials instead.

COMMIT_HASH: ${{ github.event.after }}
PATCHES_HASH: ${{ env.PATCHES_HASH }}

buildAndPublishRNArtifactsForHybridApp:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is almost the same as buildAndPublishRNArtifactsForStandaloneNewDot. Maybe I’ll manage to create a composite action.

fi

IFS=' ' read -ra PATCH_DIRS <<< "$PATCHES_PATHS"
find "${PATCH_DIRS[@]}" -type f -name "react-native+${VERSION}*.patch" -exec sha256sum {} + | sort | sha256sum | awk '{print $1}'
Copy link
Contributor

Choose a reason for hiding this comment

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

do we have to add VERSION here? 🤔 Couldn't we go with react-native+*.patch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm good point, I assumed that it's just safer to take react-native+${VERSION}*.patch into consideration.

Copy link
Contributor

Choose a reason for hiding this comment

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

what about related packages such as @react-native/gradle-plugin or @react-native/virtualized-lists?

I see patches such as @react-native+gradle-plugin+{version}.patch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this is something we should definitely add. I wasn’t sure about it at first, but it could be critical. @react-native includes a codegen subpackage that generates C++ code, and I bet other packages like gradle-plugin could also affect the final form of the prebuilt artifacts. I’ll take care of it today.

Comment on lines +16 to +20
VERSIONS_FROM_MAVEN_REPOSITORY=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \
"$MAVEN_METADATA_URL" | \
grep -o "<version>${VERSION}-[0-9]\+</version>" | \
grep -o "${VERSION}-[0-9]" | \
sort -t'-' -k2 -n)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd appreciate comments on what's going on here 🙏

paths:
- package.json
- patches/react-native+*.patch
- Mobile-Expensify
Copy link
Contributor

Choose a reason for hiding this comment

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

It's going to be triggered very often :/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, but there's not much we can do about it.

We could potentially move HybridApp artifacts job to Mobile-Expenisfy repository and there add path patches/react-native+*.patch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's wait for the others to discuss 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's ok. package.json changes just as frequently as Mobile-Expensify (with every version bump)

echo "Detected changes in HybridApp patches:"
git -C Mobile-Expensify diff --name-only $PREVIOUS_SUBMODULE_COMMIT..$CURRENT_SUBMODULE_COMMIT -- "patches/react-native+*.patch"
env:
PATCHES_PATHS: "Mobile-Expensify/patches"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
PATCHES_PATHS: "Mobile-Expensify/patches"
PATCHES_PATH: "Mobile-Expensify/patches"

? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can pass many paths separated by space. It is used to compute patches in HybridApp scenario where we need to consider both patches directories.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking that maybe we could move all patches to App repository and slightly refactor them to work on both standalone and HybridApp.

This would allow us to build only one react-android package, but it's a significant amount of work, so it could be a separate issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rather keep HybridApp-related patches in Mobile-Expensify as we do now

buildAndPublishRNArtifactsForStandaloneNewDot:
runs-on: ubuntu-latest
if: ${{ always() && needs.verifyNewDotPatchesAndReactNativeVersion.result == 'success'}}
needs: verifyNewDotPatchesAndReactNativeVersion
Copy link
Contributor

Choose a reason for hiding this comment

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

let's move it above if 🙏


buildAndPublishRNArtifactsForStandaloneNewDot:
runs-on: ubuntu-latest
if: ${{ always() && needs.verifyNewDotPatchesAndReactNativeVersion.result == 'success'}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need always here? And what does it do?

Copy link
Contributor Author

@mateuuszzzzz mateuuszzzzz Apr 14, 2025

Choose a reason for hiding this comment

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

Hmm, maybe it's not needed here 🤔 always ensures that the job runs even if any of the required jobs have failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On the other hand, buildAndPublishRNArtifactsForHybridApp needs to use always() because we need to ensure it runs as long as at least one required job hasn't failed.

echo "Version: ${{ env.NEW_PATCHED_VERSION }}"
echo "Commit hash: ${{ env.COMMIT_HASH }}"
echo "Patches hash: ${{ env.PATCHES_HASH }}"
export ORG_GRADLE_PROJECT_reactNativeArchitectures="armeabi-v7a,arm64-v8a,x86,x86_64"
Copy link
Contributor

Choose a reason for hiding this comment

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

Where do those versions come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From previous step

Copy link
Contributor

Choose a reason for hiding this comment

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

oh sorry, I meant the architectures 😅

Copy link
Contributor

@roryabraham roryabraham left a comment

Choose a reason for hiding this comment

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

Full diff of my requested changes:

full diff
diff --git a/.github/scripts/computePatchesHash.sh b/.github/scripts/computePatchesHash.sh
deleted file mode 100755
index 196f49bcf21..00000000000
--- a/.github/scripts/computePatchesHash.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-if [ -z "$VERSION" ]; then
-    echo "VERSION env variable is not set"
-    exit 1
-fi
-
-if [ -z "$PATCHES_PATHS" ]; then
-    echo "PATCHES_PATHS env variable is not set"
-    exit 1
-fi
-
-IFS=' ' read -ra PATCH_DIRS <<< "$PATCHES_PATHS"
-find "${PATCH_DIRS[@]}" -type f -name "react-native+${VERSION}*.patch" -exec sha256sum {} + | sort | sha256sum | awk '{print $1}'
diff --git a/.github/scripts/getNewPatchedRNVersion.sh b/.github/scripts/getNewPatchedRNVersion.sh
index 8b84b8cd5e8..736249e5769 100755
--- a/.github/scripts/getNewPatchedRNVersion.sh
+++ b/.github/scripts/getNewPatchedRNVersion.sh
@@ -1,9 +1,6 @@
 #!/bin/bash
 
-if [ -z "$VERSION" ]; then
-    echo "VERSION env variable is not set"
-    exit 1
-fi
+VERSION="$(jq -r '.dependencies["react-native"]' package.json)"
 
 if [[ "$IS_HYBRID_BUILD" == "true" ]]; then
     PACKAGE="react-hybrid"
diff --git a/.github/workflows/buildAndPublishReactNativeArtifacts.yml b/.github/workflows/buildAndPublishReactNativeArtifacts.yml
index b39aeaca0d6..5ea26af58d0 100644
--- a/.github/workflows/buildAndPublishReactNativeArtifacts.yml
+++ b/.github/workflows/buildAndPublishReactNativeArtifacts.yml
@@ -10,204 +10,112 @@ on:
       - Mobile-Expensify
 
 jobs:
-  verifyNewDotPatchesAndReactNativeVersion:
-    runs-on: ubuntu-latest
+  publish:
+    runs-on: ${{ github.repository_owner == 'Expensify' && 'ubuntu-latest-xl' || 'ubuntu-latest' }}
+    strategy:
+      matrix:
+        is_hybrid: [true, false]
+    env:
+      PATCHES_PATHS: ${{ matrix.is_hybrid && 'Mobile-Expensify/patches' || 'patches' }}
     steps:
+      # v4
       - name: Checkout
-        # v4
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-
-      - name: Check if NewDot patches or react-native version have changed
-        run: |
-          VERSION=$(jq -r '.dependencies["react-native"]' package.json)
-          HASH=$(VERSION=$VERSION ./.github/scripts/computePatchesHash.sh)
-
-          git fetch origin ${{ github.event.before }}
-          git checkout ${{ github.event.before }}
-
-          OLD_VERSION=$(jq -r '.dependencies["react-native"]' package.json)
-          # If version has changed and we want to find differences in patches, we need to consider pattern that involves previous react-native version
-          OLD_HASH=$(VERSION=$OLD_VERSION ./.github/scripts/computePatchesHash.sh)
-
-          if [ "$HASH" == "$OLD_HASH" ] && [ "$VERSION" == "$OLD_VERSION" ]; then
-            echo "No relevant changes"
-            exit 1
-          fi
-
-          if [ "$VERSION" != "$OLD_VERSION" ]; then
-            echo "Detected react-native version bump ($OLD_VERSION -> $VERSION)"
-            exit 0
-          fi
-
-          if [ "$HASH" != "$OLD_HASH" ]; then
-            echo "Detected changes in NewDot patches:"
-            git diff --name-only ${{ github.event.before }}..${{ github.event.after }} -- "patches/react-native+*.patch"
-          fi
-        env:
-          PATCHES_PATHS: "patches"
-
-  verifyHybridAppPatches:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        # v4
         uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
         with:
-            submodules: true
-            fetch-depth: 0
-      - name: Get react-native version
-        run: |
-          echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> $GITHUB_ENV
+          submodules: ${{ matrix.is_hybrid }}
+          ref: ${{ github.event.before }}
 
-      - name: Check if HybridApp patches have changed
-        run: |
-          CURRENT_SUBMODULE_COMMIT=$(git rev-parse :Mobile-Expensify)
-          CURRENT_PATCHES_HASH=$(./.github/scripts/computePatchesHash.sh)
- 
-          git fetch origin ${{ github.event.before }}
-          git checkout ${{ github.event.before }}
+      - name: Get previous commit hash
+        id: getOldHash
+        run: echo "HASH=$(git rev-parse ${{ matrix.is_hybrid == 'true' && ':Mobile-Expensify' || 'HEAD' }})" >> "$GITHUB_OUTPUT"
 
-          git submodule update
-          
-          PREVIOUS_SUBMODULE_COMMIT=$(git rev-parse :Mobile-Expensify)
-
-          if [ "$CURRENT_SUBMODULE_COMMIT" == "$PREVIOUS_SUBMODULE_COMMIT" ]; then
-            echo "Submodule commit is the same as the previous one, skipping patch check"
-            exit 1
-          fi
-
-          PREVIOUS_PATCHES_HASH=$(./.github/scripts/computePatchesHash.sh)
-
-          if [ "$CURRENT_PATCHES_HASH" == "$PREVIOUS_PATCHES_HASH" ]; then
-            echo "No changes in HybridApp patches"
-            exit 1
-          fi
-
-          echo "Detected changes in HybridApp patches:"
-          git -C Mobile-Expensify diff --name-only $PREVIOUS_SUBMODULE_COMMIT..$CURRENT_SUBMODULE_COMMIT -- "patches/react-native+*.patch"
-        env:
-          PATCHES_PATHS: "Mobile-Expensify/patches"
-          VERSION: ${{ env.VERSION }}
+      - name: Get previous react-native version
+        id: getOldVersion
+        run: echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> "$GITHUB_OUTPUT"
 
-  buildAndPublishRNArtifactsForStandaloneNewDot:
-    runs-on: ubuntu-latest
-    if: ${{ always() && needs.verifyNewDotPatchesAndReactNativeVersion.result == 'success'}}
-    needs: verifyNewDotPatchesAndReactNativeVersion
-    steps:
-      - name: Checkout
-        # v4
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-
-      - name: Get react-native version
+      - name: Checkout new ref
         run: |
-          echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> $GITHUB_ENV
-
-      - name: Setup Node
-        uses: ./.github/actions/composite/setupNode
-
-      - name: Setup Java
-        # v4
-        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
-        with:
-          distribution: oracle
-          java-version: 17
+          git fetch origin ${{ github.event.after }} --depth=1
+          git checkout ${{ github.event.after }}
+          git submodule update
 
-      - name: Setup Gradle
-        # v4
-        uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244
+      - name: Get new commit hash
+        id: getNewHash
+        run: echo "HASH=$(git rev-parse ${{ matrix.is_hybrid == 'true' && ':Mobile-Expensify' || 'HEAD' }})" >> "$GITHUB_OUTPUT"
 
-      - name: Save patches hash
-        run: |
-          echo "PATCHES_HASH=$(./.github/scripts/computePatchesHash.sh)" >> $GITHUB_ENV
-        env:
-          VERSION: ${{ env.VERSION }}
-          PATCHES_PATHS: "patches"
+      - name: Get new react-native version
+        id: getNewVersion
+        run: echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> "$GITHUB_OUTPUT"
 
-      - name: Determine new patched RN version
+      - name: Check if version changed
+        id: didVersionChange
         run: |
-          echo "NEW_PATCHED_VERSION=$(./.github/scripts/getNewPatchedRNVersion.sh)" >> $GITHUB_ENV
-        env:
-          VERSION: ${{ env.VERSION }}
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          IS_HYBRID_BUILD: "false"
+          readonly DID_VERSION_CHANGE=${{ steps.getOldVersion.outputs.VERSION != steps.getNewVersion.outputs.VERSION && 'true' || 'false' }}
+          echo "DID_VERSION_CHANGE=$DID_VERSION_CHANGE" >> "$GITHUB_OUTPUT"
+          if [[ "$DID_VERSION_CHANGE" == 'true' ]]; then
+            echo "::notice::Detected react-native version bump (${{ steps.getOldVersion.outputs.VERSION }} -> ${{ steps.getNewVersion.outputs.VERSION }})"
+          fi
 
-      - name: Build and publish RN artifacts
+      - name: Check if patches changed
+        id: didPatchesChange
         run: |
-          cd android
-          echo "Starting artifacts build for standalone NewDot"
-          echo "Version: ${{ env.NEW_PATCHED_VERSION }}"
-          echo "Commit hash: ${{ env.COMMIT_HASH }}"
-          echo "Patches hash: ${{ env.PATCHES_HASH }}"
-          export ORG_GRADLE_PROJECT_reactNativeArchitectures="armeabi-v7a,arm64-v8a,x86,x86_64"
-          ./gradlew buildReactNativeArtifacts -x lint -x test -x check
-          ./gradlew publishReactNativeArtifacts
-        env:
-          GH_PUBLISH_ACTOR: ${{ github.actor }}
-          GH_PUBLISH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          IS_HYBRID_BUILD: "false"
-          PATCHED_VERSION: ${{ env.NEW_PATCHED_VERSION }}
-          COMMIT_HASH: ${{ github.event.after }}
-          PATCHES_HASH: ${{ env.PATCHES_HASH }}
-
-  buildAndPublishRNArtifactsForHybridApp:
-    runs-on: ubuntu-latest
-    if: ${{ always() && (needs.verifyNewDotPatchesAndReactNativeVersion.result == 'success' || needs.verifyHybridAppPatches.result == 'success')}}
-    needs: [verifyNewDotPatchesAndReactNativeVersion, verifyHybridAppPatches]
-    steps:
-      - name: Checkout
-        # v4
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-        with:
-          submodules: true
+          if git ${{ matrix.is_hybrid == 'true' && '-C Mobile-Expensify' }} diff --exit-code --name-only ${{ steps.getOldHash.outputs.HASH }}..${{ steps.getNewHash.outputs.HASH }} -- patches/react-native+*.patch; then
+            echo "::notice::Detected changes in NewDot patches"
+            echo "DID_PATCHES_CHANGE=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "DID_PATCHES_CHANGE=false" >> "$GITHUB_OUTPUT"
+          fi
 
-      - name: Get react-native version
+      - name: Check if we should build and publish the package
+        id: shouldPublish
         run: |
-          echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> $GITHUB_ENV
+          if [[ '${{ steps.didVersionChange.outputs.DID_VERSION_CHANGE }}' == 'true' || '${{ steps.didPatchesChange.outputs.DID_PATCHES_CHANGE }}' == 'true ]]; then
+            echo "SHOULD_PUBLISH=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "::notice::No relevant changes, skipping publishing a new React Native build"
+            echo "SHOULD_PUBLISH=false" >> "$GITHUB_OUTPUT"
+          fi
 
       - name: Setup Node
+        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
         uses: ./.github/actions/composite/setupNode
         with:
-          IS_HYBRID_BUILD: "true"
+          IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}
 
+      # v4
       - name: Setup Java
-        # v4
+        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
         uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
         with:
           distribution: oracle
           java-version: 17
 
+      # v4
       - name: Setup Gradle
-        # v4
+        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
         uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244
 
-      - name: Save patches hash
-        run: |
-          echo "PATCHES_HASH=$(./.github/scripts/computePatchesHash.sh)" >> $GITHUB_ENV
-        env:
-          VERSION: ${{ env.VERSION }}
-          PATCHES_PATHS: "patches Mobile-Expensify/patches"
-
       - name: Determine new patched RN version
-        run: |
-          echo "NEW_PATCHED_VERSION=$(./.github/scripts/getNewPatchedRNVersion.sh)" >> $GITHUB_ENV
+        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
+        id: getNewPatchedVersion
+        run: echo "NEW_PATCHED_VERSION=$(./.github/scripts/getNewPatchedRNVersion.sh)" >> $GITHUB_OUTPUT
         env:
-          VERSION: ${{ env.VERSION }}
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          IS_HYBRID_BUILD: "true"
+          GITHUB_TOKEN: ${{ github.token }}
+          IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}
+
       - name: Build and publish RN artifacts
+        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
+        working-directory: ${{ matrix.is_hybrid == 'true' && 'Mobile-Expensify/Android' || 'android' }}
         run: |
-          cd Mobile-Expensify/Android
-          echo "Starting artifacts build for HybridApp"
-          echo "Version: ${{ env.NEW_PATCHED_VERSION }}"
+          echo "Starting artifacts build for ${{ matrix.is_hybrid == 'true' && 'HybridApp' || 'NewDot Standalone' }}"
+          echo "Version: ${{ env.PATCHED_VERSION }}"
           echo "Commit hash: ${{ env.COMMIT_HASH }}"
-          echo "Patches hash: ${{ env.PATCHES_HASH }}"
           export ORG_GRADLE_PROJECT_reactNativeArchitectures="armeabi-v7a,arm64-v8a,x86,x86_64"
           ./gradlew buildReactNativeArtifacts -x lint -x test -x check
           ./gradlew publishReactNativeArtifacts
         env:
           GH_PUBLISH_ACTOR: ${{ github.actor }}
-          GH_PUBLISH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          IS_HYBRID_BUILD: "true"
-          PATCHED_VERSION: ${{ env.NEW_PATCHED_VERSION }}
+          GH_PUBLISH_TOKEN: ${{ github.token }}
+          IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}
+          PATCHED_VERSION: ${{ steps.getNewPatchedVersion.outputs.NEW_PATCHED_VERSION }}
           COMMIT_HASH: ${{ github.event.after }}
-          PATCHES_HASH: ${{ env.PATCHES_HASH }}
diff --git a/patches/react-native+0.77.1+024+publish-gradle.patch b/patches/react-native+0.77.1+024+publish-gradle.patch
index 415b83af56e..e7e7877be6d 100644
--- a/patches/react-native+0.77.1+024+publish-gradle.patch
+++ b/patches/react-native+0.77.1+024+publish-gradle.patch
@@ -4,7 +4,7 @@ index 32287a7..68c9284 100644
 +++ b/node_modules/react-native/ReactAndroid/publish.gradle
 @@ -13,7 +13,11 @@ def signingKey = findProperty("SIGNING_KEY")
  def signingPwd = findProperty("SIGNING_PWD")
- 
+
  def reactAndroidProjectDir = project(':packages:react-native:ReactAndroid').projectDir
 -def mavenTempLocalUrl = "file:///tmp/maven-local"
 +def mavenURL = "https://maven.pkg.github.com/Expensify/App"
@@ -12,13 +12,13 @@ index 32287a7..68c9284 100644
 +def patchedVersion = System.getenv("PATCHED_VERSION")
 +def isHybridBuild = System.getenv("IS_HYBRID_BUILD")?.toBoolean()
 +def publishingGroupId = isHybridBuild ? "com.expensify.react-standalone" : "com.expensify.react-hybrid"
- 
+
  publishing {
      publications {
 @@ -26,12 +30,14 @@ publishing {
                  }
              }
- 
+
 +            groupId = publishingGroupId
 +
              // We populate the publishing version using the project version,
@@ -30,14 +30,13 @@ index 32287a7..68c9284 100644
 -                version = this.version
 +                version = patchedVersion
              }
- 
+
              pom {
 @@ -39,10 +45,15 @@ publishing {
                  description = "A framework for building native apps with React"
                  url = "https://github.com/facebook/react-native"
- 
+
 +                properties = [
-+                    "patchesHash": System.getenv("PATCHES_HASH"),
 +                    "commitHash": System.getenv("COMMIT_HASH"),
 +                ]
 +
@@ -49,9 +48,9 @@ index 32287a7..68c9284 100644
 +                        name = "Expensify"
                      }
                  }
- 
+
 @@ -65,8 +76,11 @@ publishing {
- 
+
      repositories {
          maven {
 -            name = "mavenTempLocal"
Just the workflow, because the diff is hard to read
name: Build and publish React Native artifacts

on:
  push:
    branches:
      - main
    paths:
      - package.json
      - patches/react-native+*.patch
      - Mobile-Expensify

jobs:
  publish:
    runs-on: ${{ github.repository_owner == 'Expensify' && 'ubuntu-latest-xl' || 'ubuntu-latest' }}
    strategy:
      matrix:
        is_hybrid: [true, false]
    env:
      PATCHES_PATHS: ${{ matrix.is_hybrid && 'Mobile-Expensify/patches' || 'patches' }}
    steps:
      # v4
      - name: Checkout
        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
        with:
          submodules: ${{ matrix.is_hybrid }}
          ref: ${{ github.event.before }}

      - name: Get previous commit hash
        id: getOldHash
        run: echo "HASH=$(git rev-parse ${{ matrix.is_hybrid == 'true' && ':Mobile-Expensify' || 'HEAD' }})" >> "$GITHUB_OUTPUT"

      - name: Get previous react-native version
        id: getOldVersion
        run: echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> "$GITHUB_OUTPUT"

      - name: Checkout new ref
        run: |
          git fetch origin ${{ github.event.after }} --depth=1
          git checkout ${{ github.event.after }}
          git submodule update

      - name: Get new commit hash
        id: getNewHash
        run: echo "HASH=$(git rev-parse ${{ matrix.is_hybrid == 'true' && ':Mobile-Expensify' || 'HEAD' }})" >> "$GITHUB_OUTPUT"

      - name: Get new react-native version
        id: getNewVersion
        run: echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> "$GITHUB_OUTPUT"

      - name: Check if version changed
        id: didVersionChange
        run: |
          readonly DID_VERSION_CHANGE=${{ steps.getOldVersion.outputs.VERSION != steps.getNewVersion.outputs.VERSION && 'true' || 'false' }}
          echo "DID_VERSION_CHANGE=$DID_VERSION_CHANGE" >> "$GITHUB_OUTPUT"
          if [[ "$DID_VERSION_CHANGE" == 'true' ]]; then
            echo "::notice::Detected react-native version bump (${{ steps.getOldVersion.outputs.VERSION }} -> ${{ steps.getNewVersion.outputs.VERSION }})"
          fi

      - name: Check if patches changed
        id: didPatchesChange
        run: |
          if git ${{ matrix.is_hybrid == 'true' && '-C Mobile-Expensify' }} diff --exit-code --name-only ${{ steps.getOldHash.outputs.HASH }}..${{ steps.getNewHash.outputs.HASH }} -- patches/react-native+*.patch; then
            echo "::notice::Detected changes in NewDot patches"
            echo "DID_PATCHES_CHANGE=true" >> "$GITHUB_OUTPUT"
          else
            echo "DID_PATCHES_CHANGE=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if we should build and publish the package
        id: shouldPublish
        run: |
          if [[ '${{ steps.didVersionChange.outputs.DID_VERSION_CHANGE }}' == 'true' || '${{ steps.didPatchesChange.outputs.DID_PATCHES_CHANGE }}' == 'true ]]; then
            echo "SHOULD_PUBLISH=true" >> "$GITHUB_OUTPUT"
          else
            echo "::notice::No relevant changes, skipping publishing a new React Native build"
            echo "SHOULD_PUBLISH=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Setup Node
        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
        uses: ./.github/actions/composite/setupNode
        with:
          IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}

      # v4
      - name: Setup Java
        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
        uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
        with:
          distribution: oracle
          java-version: 17

      # v4
      - name: Setup Gradle
        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
        uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244

      - name: Determine new patched RN version
        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
        id: getNewPatchedVersion
        run: echo "NEW_PATCHED_VERSION=$(./.github/scripts/getNewPatchedRNVersion.sh)" >> $GITHUB_OUTPUT
        env:
          GITHUB_TOKEN: ${{ github.token }}
          IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}

      - name: Build and publish RN artifacts
        if: ${{ steps.shouldPublish.outputs.SHOULD_PUBLISH == 'true' }}
        working-directory: ${{ matrix.is_hybrid == 'true' && 'Mobile-Expensify/Android' || 'android' }}
        run: |
          echo "Starting artifacts build for ${{ matrix.is_hybrid == 'true' && 'HybridApp' || 'NewDot Standalone' }}"
          echo "Version: ${{ env.PATCHED_VERSION }}"
          echo "Commit hash: ${{ env.COMMIT_HASH }}"
          export ORG_GRADLE_PROJECT_reactNativeArchitectures="armeabi-v7a,arm64-v8a,x86,x86_64"
          ./gradlew buildReactNativeArtifacts -x lint -x test -x check
          ./gradlew publishReactNativeArtifacts
        env:
          GH_PUBLISH_ACTOR: ${{ github.actor }}
          GH_PUBLISH_TOKEN: ${{ github.token }}
          IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}
          PATCHED_VERSION: ${{ steps.getNewPatchedVersion.outputs.NEW_PATCHED_VERSION }}
          COMMIT_HASH: ${{ github.event.after }}

@@ -0,0 +1,14 @@
#!/bin/bash

if [ -z "$VERSION" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

Per our bash style guide (which is currently not living in a public repo), prefer [[

Suggested change
if [ -z "$VERSION" ]; then
if [[ -z "$VERSION" ]]; then

exit 1
fi

if [ -z "$PATCHES_PATHS" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if [ -z "$PATCHES_PATHS" ]; then
if [[ -z "$PATCHES_PATHS" ]]; then

#!/bin/bash

if [ -z "$VERSION" ]; then
echo "VERSION env variable is not set"
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB but you could source shellUtils.sh and then use info, warning, error for colored output

Copy link
Contributor

Choose a reason for hiding this comment

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

If you do this, the preferred way to get the script dir is:

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
readonly SCRIPT_DIR

fi

IFS=' ' read -ra PATCH_DIRS <<< "$PATCHES_PATHS"
find "${PATCH_DIRS[@]}" -type f -name "react-native+${VERSION}*.patch" -exec sha256sum {} + | sort | sha256sum | awk '{print $1}'
Copy link
Contributor

Choose a reason for hiding this comment

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

what about related packages such as @react-native/gradle-plugin or @react-native/virtualized-lists?

I see patches such as @react-native+gradle-plugin+{version}.patch

@@ -0,0 +1,30 @@
#!/bin/bash

if [ -z "$VERSION" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if [ -z "$VERSION" ]; then
if [[ -z "$VERSION" ]]; then

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, can we just simplify this by doing this inside the script rather than having to set an environment variable?

VERSION="$(jq -r '.dependencies["react-native"]' package.json)"
readonly VERSION;

env:
PATCHES_PATHS: "patches"

verifyHybridAppPatches:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can DRY this workflow up a lot using a matrix strategy.


if [ "$HASH" != "$OLD_HASH" ]; then
echo "Detected changes in NewDot patches:"
git diff --name-only ${{ github.event.before }}..${{ github.event.after }} -- "patches/react-native+*.patch"
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this, I've just realized that we don't actually need computePatchesHash at all ... we can just use git diff?

Copy link
Contributor

Choose a reason for hiding this comment

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

NAB but I'd call this workflow publishReactNativeAndroidArtifacts

paths:
- package.json
- patches/react-native+*.patch
- Mobile-Expensify
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's ok. package.json changes just as frequently as Mobile-Expensify (with every version bump)

VERSION: ${{ env.VERSION }}

buildAndPublishRNArtifactsForStandaloneNewDot:
runs-on: ubuntu-latest
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use large runners for the build, but only on Expensify org. Otherwise GitHub will start billing anyone for large runner minutes if they push code to main in their fork:

Suggested change
runs-on: ubuntu-latest
runs-on: ${{ github.repository_owner == 'Expensify' && 'ubuntu-latest-xl' || 'ubuntu-latest' }}

@roryabraham
Copy link
Contributor

In a separate PR, I'd like to add the changes necessary to enable the use of custom build artifacts during regular builds

I'm not sure what you mean by this.

Publication process uses github token and github actor. I had to enable package write permissions in order to use it on my fork. We might want to consider switching to bot credentials instead.

Looks like github.token has package:read/write permission by default: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

So we should be good with the standard token rather than another bot token

@mateuuszzzzz
Copy link
Contributor Author

mateuuszzzzz commented Apr 17, 2025

In a separate PR, I'd like to add the changes necessary to enable the use of custom build artifacts during regular builds

I'm not sure what you mean by this.

Sorry, I meant that this PR only added mechanism that builds react-native artifacts. Currently there's no automated way to use those custom artifacts during HybridApp / Standalone NewDot builds.

I thought it would be nice to implement this second part in another PR as it gives us better control and minimizes the risk of breaking local or CI builds if something goes wrong.

@roryabraham
Copy link
Contributor

Cool, that makes sense to me - create the artifacts in one PR and consume them in another

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants