diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml new file mode 100644 index 00000000000..f31030e2c3b --- /dev/null +++ b/.github/actions/setup-node/action.yml @@ -0,0 +1,14 @@ +name: Setup node.js +description: 'Set up your GitHub Actions workflow with a specific version of node.js' +inputs: + node-version: + description: 'The node.js version to use' + required: false + default: '22.14.0' +runs: + using: "composite" + steps: + - name: Setup node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} diff --git a/.github/actions/setup-xcode/action.yml b/.github/actions/setup-xcode/action.yml new file mode 100644 index 00000000000..c0ae6ceccea --- /dev/null +++ b/.github/actions/setup-xcode/action.yml @@ -0,0 +1,14 @@ +name: Setup xcode +description: 'Set up your GitHub Actions workflow with a specific version of xcode' +inputs: + xcode-version: + description: 'The xcode version to use' + required: false + default: '16.2.0' +runs: + using: "composite" + steps: + - name: Setup xcode + uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: ${{ inputs.xcode-version }} diff --git a/.github/actions/yarn-install/action.yml b/.github/actions/yarn-install/action.yml new file mode 100644 index 00000000000..71678ef2192 --- /dev/null +++ b/.github/actions/yarn-install/action.yml @@ -0,0 +1,23 @@ +name: yarn-install +runs: + using: composite + steps: + - name: Setup node.js + uses: ./.github/actions/setup-node + - name: Install dependencies + shell: bash + run: | + cd utils/scripts/hermes + MAX_ATTEMPTS=2 + ATTEMPT=0 + WAIT_TIME=20 + while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + yarn install --non-interactive --frozen-lockfile && break + echo "yarn install failed. Retrying in $WAIT_TIME seconds..." + sleep $WAIT_TIME + ATTEMPT=$((ATTEMPT + 1)) + done + if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo "All attempts to invoke yarn install failed - Aborting the workflow" + exit 1 + fi diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 00000000000..c94740bd273 --- /dev/null +++ b/.github/workflows/build-android.yml @@ -0,0 +1,52 @@ +name: build-android + +on: + workflow_call: + inputs: + release-type: + required: true + description: The type of release we are building. It could be commitly, release or dry-run + type: string + +jobs: + build-android: + runs-on: 8-core-ubuntu + env: + HERMES_WS_DIR: /home/runner/work/hermes/hermes + container: + image: reactnativecommunity/react-native-android:latest + env: + TERM: "dumb" + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup node.js + uses: ./.github/actions/setup-node + - name: Install node dependencies + uses: ./.github/actions/yarn-install + - name: Set React Native Version + shell: bash + run: node ./utils/scripts/hermes/set-artifacts-version.js --build-type ${{ inputs.release-type }} + - name: Build android + shell: bash + run: | + cd android + + if [[ "${{ inputs.release-type }}" == "commitly" ]]; then + export ORG_GRADLE_PROJECT_isSnapshot="true" + TASKS="publishAndroidOnlyToMavenTempLocal publishAndroidOnlyToSonatype :build" + else + TASKS="publishAndroidOnlyToMavenTempLocal :build" + fi + + ./gradlew $TASKS -PenableWarningsAsErrors=true + - name: Upload Maven Artifacts + uses: actions/upload-artifact@v4.3.4 + with: + name: maven-local + path: /tmp/maven-local diff --git a/.github/workflows/build-apple-slices-hermes.yml b/.github/workflows/build-apple-slices-hermes.yml new file mode 100644 index 00000000000..47032e30467 --- /dev/null +++ b/.github/workflows/build-apple-slices-hermes.yml @@ -0,0 +1,86 @@ +name: build-apple-slices-hermes + +on: workflow_call + +jobs: + build_apple_slices_hermes: + runs-on: macos-14 + env: + IOS_DEPLOYMENT_TARGET: "15.1" + XROS_DEPLOYMENT_TARGET: "1.0" + MAC_DEPLOYMENT_TARGET: "10.15" + strategy: + fail-fast: false + matrix: + flavor: [Debug, Release] + slice: + [ + macosx, + iphoneos, + iphonesimulator, + appletvos, + appletvsimulator, + catalyst, + xros, + xrsimulator, + ] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup xcode + uses: ./.github/actions/setup-xcode + - name: Restore HermesC Artifact + uses: actions/download-artifact@v4 + with: + name: hermesc-apple + path: ./build_host_hermesc + - name: Build the Hermes ${{ matrix.slice }} frameworks + shell: bash + run: | + SLICE=${{ matrix.slice }} + FLAVOR=${{ matrix.flavor }} + FINAL_PATH=build_"$SLICE"_"$FLAVOR" + echo "Final path for this slice is: $FINAL_PATH" + + # HermesC is used to build hermes, so it has to be executable + chmod +x ./build_host_hermesc/bin/hermesc + + if [[ "$SLICE" == "macosx" ]]; then + echo "[HERMES] Building Hermes for MacOS" + + chmod +x ./utils/build-mac-framework-rn.sh + BUILD_TYPE="${{ matrix.flavor }}" ./utils/build-mac-framework-rn.sh + else + echo "[HERMES] Building Hermes for iOS: $SLICE" + + chmod +x ./utils/build-ios-framework-rn.sh + BUILD_TYPE="${{ matrix.flavor }}" ./utils/build-ios-framework-rn.sh "$SLICE" + fi + + echo "Moving from build_$SLICE to $FINAL_PATH" + mv build_"$SLICE" "$FINAL_PATH" + + # check whether everything is there + if [[ -d "$FINAL_PATH/lib/hermesvm.framework" ]]; then + echo "Successfully built hermesvm.framework for $SLICE in $FLAVOR" + else + echo "Failed to built hermesvm.framework for $SLICE in $FLAVOR" + exit 1 + fi + + if [[ -d "$FINAL_PATH/lib/hermesvm.framework.dSYM" ]]; then + echo "Successfully built hermesvm.framework.dSYM for $SLICE in $FLAVOR" + else + echo "Failed to built hermesvm.framework.dSYM for $SLICE in $FLAVOR" + echo "Please try again" + exit 1 + fi + - name: Compress slices to preserve Symlinks + shell: bash + run: | + tar -czv -f build_${{ matrix.slice }}_${{ matrix.flavor }}.tar.gz build_${{ matrix.slice }}_${{ matrix.flavor }} + - name: Upload Artifact for Slice (${{ matrix.slice }}, ${{ matrix.flavor }}) + uses: actions/upload-artifact@v4.3.4 + with: + name: slice-${{ matrix.slice }}-${{ matrix.flavor }} + path: ./build_${{ matrix.slice }}_${{ matrix.flavor }}.tar.gz diff --git a/.github/workflows/build-hermes-macos.yml b/.github/workflows/build-hermes-macos.yml new file mode 100644 index 00000000000..6ebea73d1f4 --- /dev/null +++ b/.github/workflows/build-hermes-macos.yml @@ -0,0 +1,144 @@ +name: build-hermes-macos + +on: workflow_call + +jobs: + build-hermes-macos: + runs-on: macos-14 + continue-on-error: true + env: + HERMES_TARBALL_ARTIFACTS_DIR: /tmp/hermes/hermes-runtime-darwin + strategy: + fail-fast: false + matrix: + flavor: [Debug, Release] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup xcode + uses: ./.github/actions/setup-xcode + - name: Setup node.js + uses: ./.github/actions/setup-node + - name: Yarn Install Dependencies + uses: ./.github/actions/yarn-install + - name: Download slice artifacts + uses: actions/download-artifact@v4 + with: + pattern: slice-*-${{ matrix.flavor }} + path: . + merge-multiple: true + - name: Unzip slices + shell: bash + run: | + ls -l . + tar -xzv -f build_catalyst_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_iphoneos_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_iphonesimulator_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_appletvos_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_appletvsimulator_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_macosx_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_xros_${{ matrix.flavor }}.tar.gz + tar -xzv -f build_xrsimulator_${{ matrix.flavor }}.tar.gz + - name: Move back build folders + shell: bash + run: | + ls -l build_* + mv build_catalyst_${{ matrix.flavor }} build_catalyst + mv build_iphoneos_${{ matrix.flavor }} build_iphoneos + mv build_iphonesimulator_${{ matrix.flavor }} build_iphonesimulator + mv build_appletvos_${{ matrix.flavor }} build_appletvos + mv build_appletvsimulator_${{ matrix.flavor }} build_appletvsimulator + mv build_macosx_${{ matrix.flavor }} build_macosx + mv build_xros_${{ matrix.flavor }} build_xros + mv build_xrsimulator_${{ matrix.flavor }} build_xrsimulator + - name: Prepare destroot folder + shell: bash + run: | + chmod +x ./utils/build-apple-framework-rn.sh + source ./utils/build-apple-framework-rn.sh + prepare_dest_root_for_ci + - name: Create fat framework for iOS + shell: bash + run: | + echo "[HERMES] Creating the universal framework" + chmod +x ./utils/build-ios-framework-rn.sh + ./utils/build-ios-framework-rn.sh build_framework + + chmod +x ./destroot/bin/hermesc + - name: Package the Hermes Apple frameworks + shell: bash + run: | + BUILD_TYPE="${{ matrix.flavor }}" + echo "Packaging Hermes Apple frameworks for $BUILD_TYPE build type" + + TARBALL_OUTPUT_DIR=$(mktemp -d /tmp/hermes-tarball-output-XXXXXXXX) + + echo "Packaging Hermes Apple frameworks for $BUILD_TYPE build type" + + TARBALL_OUTPUT_PATH=$(node ./utils/scripts/hermes/create-tarball.js \ + --inputDir . \ + --buildType "$BUILD_TYPE" \ + --outputDir $TARBALL_OUTPUT_DIR) + + echo "Hermes tarball saved to $TARBALL_OUTPUT_PATH" + + mkdir -p $HERMES_TARBALL_ARTIFACTS_DIR + cp $TARBALL_OUTPUT_PATH $HERMES_TARBALL_ARTIFACTS_DIR/. + + mkdir -p /tmp/hermes/osx-bin/${{ matrix.flavor }} + cp ./build_macosx/bin/* /tmp/hermes/osx-bin/${{ matrix.flavor }} + ls -lR /tmp/hermes/osx-bin/ + - name: Create dSYM archive + shell: bash + run: | + FLAVOR=${{ matrix.flavor }} + WORKING_DIR="/tmp/hermes_tmp/dSYM/$FLAVOR" + + mkdir -p "$WORKING_DIR/macosx" + mkdir -p "$WORKING_DIR/catalyst" + mkdir -p "$WORKING_DIR/iphoneos" + mkdir -p "$WORKING_DIR/iphonesimulator" + mkdir -p "$WORKING_DIR/appletvos" + mkdir -p "$WORKING_DIR/appletvsimulator" + mkdir -p "$WORKING_DIR/xros" + mkdir -p "$WORKING_DIR/xrsimulator" + + DSYM_FILE_PATH=lib/hermesvm.framework.dSYM + cp -r build_macosx/$DSYM_FILE_PATH "$WORKING_DIR/macosx/" + cp -r build_catalyst/$DSYM_FILE_PATH "$WORKING_DIR/catalyst/" + cp -r build_iphoneos/$DSYM_FILE_PATH "$WORKING_DIR/iphoneos/" + cp -r build_iphonesimulator/$DSYM_FILE_PATH "$WORKING_DIR/iphonesimulator/" + cp -r build_appletvos/$DSYM_FILE_PATH "$WORKING_DIR/appletvos/" + cp -r build_appletvsimulator/$DSYM_FILE_PATH "$WORKING_DIR/appletvsimulator/" + cp -r build_xros/$DSYM_FILE_PATH "$WORKING_DIR/xros/" + cp -r build_xrsimulator/$DSYM_FILE_PATH "$WORKING_DIR/xrsimulator/" + + DEST_DIR="/tmp/hermes/dSYM/$FLAVOR" + tar -C "$WORKING_DIR" -czvf "hermesvm.framework.dSYM" . + + mkdir -p "$DEST_DIR" + mv "hermesvm.framework.dSYM" "$DEST_DIR" + - name: Upload hermes dSYM artifacts + uses: actions/upload-artifact@v4.3.4 + with: + name: hermes-dSYM-${{ matrix.flavor }} + path: /tmp/hermes/dSYM/${{ matrix.flavor }} + - name: Upload hermes Runtime artifacts + uses: actions/upload-artifact@v4.3.4 + with: + name: hermes-darwin-bin-${{ matrix.flavor }} + path: /tmp/hermes/hermes-runtime-darwin/hermes-ios-${{ matrix.flavor }}.tar.gz + - name: Upload hermes osx artifacts + uses: actions/upload-artifact@v4.3.4 + with: + name: hermes-osx-bin-${{ matrix.flavor }} + path: /tmp/hermes/osx-bin/${{ matrix.flavor }} + - name: Upload Hermes Artifacts + uses: actions/cache/save@v4 + if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, '-stable') }} # To avoid that the cache explode. + with: + key: v4-hermes-artifacts-${{ matrix.flavor }}-${{ matrix.hermes-version }}-${{ matrix.react-native-version }}-${{ hashFiles('./packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh') }} + path: | + /tmp/hermes/osx-bin/${{ matrix.flavor }} + /tmp/hermes/dSYM/${{ matrix.flavor }} + /tmp/hermes/hermes-runtime-darwin/hermes-ios-${{ matrix.flavor }}.tar.gz diff --git a/.github/workflows/build-hermesc-apple.yml b/.github/workflows/build-hermesc-apple.yml new file mode 100644 index 00000000000..c3d659605ec --- /dev/null +++ b/.github/workflows/build-hermesc-apple.yml @@ -0,0 +1,22 @@ +name: build-hermesc-apple + +on: workflow_call + +jobs: + build-hermesc-apple: + runs-on: macos-14 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup xcode + uses: ./.github/actions/setup-xcode + - name: Build HermesC Apple + shell: bash + run: | + source ./utils/build-apple-framework-rn.sh + build_host_hermesc_if_needed + - name: Upload HermesC Artifact + uses: actions/upload-artifact@v4.3.4 + with: + name: hermesc-apple + path: ./build_host_hermesc diff --git a/.github/workflows/build-hermesc-linux.yml b/.github/workflows/build-hermesc-linux.yml new file mode 100644 index 00000000000..67c32edfcda --- /dev/null +++ b/.github/workflows/build-hermesc-linux.yml @@ -0,0 +1,43 @@ +name: build-hermesc-linux + +on: workflow_call + +jobs: + build-hermesc-linux: + runs-on: ubuntu-latest + env: + HERMES_TARBALL_ARTIFACTS_DIR: /tmp/hermes/hermes-runtime-darwin + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + shell: bash + run: | + sudo apt update + sudo apt install -y git openssh-client build-essential \ + libreadline-dev libicu-dev jq zip python3 + + sudo mkdir -p /usr/local/cmake + sudo wget -qO- https://github.com/Kitware/CMake/releases/download/v3.31.6/cmake-3.31.6-linux-x86_64.tar.gz \ + | sudo tar --strip-components=1 -xz -C /usr/local/cmake + sudo cp /usr/local/cmake/bin/* /usr/local/bin/ + export PATH=/usr/local/bin:$PATH + cmake --version + + - name: Set up workspace + shell: bash + run: | + mkdir -p /tmp/hermes/linux64-bin + - name: Build HermesC for Linux + shell: bash + run: | + cmake -S . -B build -DHERMES_STATIC_LINK=ON -DCMAKE_BUILD_TYPE=Release -DHERMES_ENABLE_TEST_SUITE=OFF \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True -DCMAKE_CXX_FLAGS=-s -DCMAKE_C_FLAGS=-s \ + -DCMAKE_EXE_LINKER_FLAGS="-Wl,--whole-archive -lpthread -Wl,--no-whole-archive" + cmake --build build --target hermesc -j 4 + cp build/bin/hermesc /tmp/hermes/linux64-bin/. + - name: Upload linux artifacts + uses: actions/upload-artifact@v4.3.4 + with: + name: hermes-linux-bin + path: /tmp/hermes/linux64-bin diff --git a/.github/workflows/build-hermesc-windows.yml b/.github/workflows/build-hermesc-windows.yml new file mode 100644 index 00000000000..aa44da3af8d --- /dev/null +++ b/.github/workflows/build-hermesc-windows.yml @@ -0,0 +1,70 @@ +name: build-hermesc-windows + +on: workflow_call + +jobs: + build-hermesc-windows: + runs-on: windows-2025 + env: + HERMES_WS_DIR: 'C:\tmp\hermes' + HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin' + HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin' + ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip" + MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin' + CMAKE_DIR: 'C:\Program Files\CMake\bin' + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up workspace + shell: powershell + run: | + mkdir -p C:\tmp\hermes\osx-bin + - name: setup-msbuild + uses: microsoft/setup-msbuild@v1.3.2 + - name: Set up workspace + shell: powershell + run: | + New-Item -ItemType Directory -ErrorAction SilentlyContinue $Env:HERMES_WS_DIR\icu + New-Item -ItemType Directory -ErrorAction SilentlyContinue $Env:HERMES_WS_DIR\deps + New-Item -ItemType Directory -ErrorAction SilentlyContinue $Env:HERMES_WS_DIR\win64-bin + - name: Downgrade CMake + shell: powershell + run: choco install cmake --version 3.31.6 --force + - name: Build HermesC for Windows + shell: powershell + run: | + cd $Env:HERMES_WS_DIR\icu + # If Invoke-WebRequest shows a progress bar, it will fail with + # Win32 internal error "Access is denied" 0x5 occurred [...] + $progressPreference = 'silentlyContinue' + Invoke-WebRequest -Uri "$Env:ICU_URL" -OutFile "icu.zip" + Expand-Archive -Path "icu.zip" -DestinationPath "." + + cd $Env:HERMES_WS_DIR + Copy-Item -Path "icu\bin64\icu*.dll" -Destination "deps" + # Include MSVC++ 2015 redistributables + Copy-Item -Path "c:\windows\system32\msvcp140.dll" -Destination "deps" + Copy-Item -Path "c:\windows\system32\vcruntime140.dll" -Destination "deps" + Copy-Item -Path "c:\windows\system32\vcruntime140_1.dll" -Destination "deps" + + $Env:PATH += ";$Env:CMAKE_DIR;$Env:MSBUILD_DIR" + $Env:ICU_ROOT = "$Env:HERMES_WS_DIR\icu" + + cd $Env:GITHUB_WORKSPACE + cmake -S . -B build_release -G 'Visual Studio 17 2022' -Ax64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True -DHERMES_ENABLE_WIN10_ICU_FALLBACK=OFF + if (-not $?) { throw "Failed to configure Hermes" } + echo "Running windows build..." + cd build_release + cmake --build . --target hermesc --config Release + if (-not $?) { throw "Failed to build Hermes" } + + echo "Copying hermesc.exe to win64-bin" + cd $Env:GITHUB_WORKSPACE + Copy-Item -Path "build_release\bin\Release\hermesc.exe" -Destination "$Env:HERMES_WS_DIR\win64-bin" + # Include Windows runtime dependencies + Copy-Item -Path "$Env:HERMES_WS_DIR\deps\*" -Destination "$Env:HERMES_WS_DIR\win64-bin" + - name: Upload windows artifacts + uses: actions/upload-artifact@v4.3.4 + with: + name: hermes-win64-bin + path: C:\tmp\hermes\win64-bin\ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000000..4158d311518 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,90 @@ +name: publish + +on: + workflow_call: + inputs: + release-type: + required: true + description: The type of release we are building. It could be release or dry-run + type: string + +jobs: + publish: + runs-on: 8-core-ubuntu + container: + image: reactnativecommunity/react-native-android:latest + env: + TERM: "dumb" + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + env: + HERMES_WS_DIR: /tmp/hermes + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup git safe folders + shell: bash + run: git config --global --add safe.directory '*' + - name: Create /tmp/hermes/osx-bin directory + shell: bash + run: mkdir -p /tmp/hermes/osx-bin + - name: Download osx-bin release artifacts + uses: actions/download-artifact@v4 + with: + name: hermes-osx-bin-Release + path: /tmp/hermes/osx-bin/Release + - name: Download osx-bin debug artifacts + uses: actions/download-artifact@v4 + with: + name: hermes-osx-bin-Debug + path: /tmp/hermes/osx-bin/Debug + - name: Download darwin-bin release artifacts + uses: actions/download-artifact@v4 + with: + name: hermes-darwin-bin-Release + path: /tmp/hermes/hermes-runtime-darwin + - name: Download darwin-bin debug artifacts + uses: actions/download-artifact@v4 + with: + name: hermes-darwin-bin-Debug + path: /tmp/hermes/hermes-runtime-darwin + - name: Download hermes dSYM debug artifacts + uses: actions/download-artifact@v4 + with: + name: hermes-dSYM-Debug + path: /tmp/hermes/dSYM/Debug + - name: Download hermes dSYM release vartifacts + uses: actions/download-artifact@v4 + with: + name: hermes-dSYM-Release + path: /tmp/hermes/dSYM/Release + - name: Download linux-bin artifacts + uses: actions/download-artifact@v4 + with: + name: hermes-linux-bin + path: /tmp/hermes/linux64-bin + - name: Show /tmp/hermes directory + shell: bash + run: ls -lR /tmp/hermes + - name: Copy Hermes binaries + shell: bash + run: | + mkdir -p ./android/ios-artifacts/artifacts/ + cp $HERMES_WS_DIR/hermes-runtime-darwin/hermes-ios-Debug.tar.gz ./android/ios-artifacts/artifacts/hermes-ios-debug.tar.gz + cp $HERMES_WS_DIR/hermes-runtime-darwin/hermes-ios-Release.tar.gz ./android/ios-artifacts/artifacts/hermes-ios-release.tar.gz + cp $HERMES_WS_DIR/dSYM/Debug/hermesvm.framework.dSYM ./android/ios-artifacts/artifacts/hermes-framework-dSYM-debug.tar.gz + cp $HERMES_WS_DIR/dSYM/Release/hermesvm.framework.dSYM ./android/ios-artifacts/artifacts/hermes-framework-dSYM-release.tar.gz + - name: Print Artifacts Directory + shell: bash + run: ls -lR ./android/ios-artifacts/artifacts/ + - name: Setup node.js + uses: ./.github/actions/setup-node + - name: Install dependencies + uses: ./.github/actions/yarn-install + - name: Publish artifacts + shell: bash + run: | + node ./utils/scripts/hermes/publish-artifacts.js -t ${{ inputs.release-type }} diff --git a/.github/workflows/rn-build-hermes.yml b/.github/workflows/rn-build-hermes.yml new file mode 100644 index 00000000000..ceb9fd28f23 --- /dev/null +++ b/.github/workflows/rn-build-hermes.yml @@ -0,0 +1,64 @@ +name: RN Build Static Hermes + +on: + workflow_dispatch: + inputs: + release-type: + description: 'Release type (release, commitly, dry-run)' + required: false + default: 'dry-run' + pull_request: + push: + branches: + - static_h + +jobs: + set_release_type: + runs-on: ubuntu-latest + if: github.repository == 'facebook/hermes' + outputs: + RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }} + env: + EVENT_NAME: ${{ github.event_name }} + REF: ${{ github.ref }} + steps: + - id: set_release_type + run: | + if [[ $EVENT_NAME == "push" && $REF == refs/heads/static_h ]]; then + echo "Setting release type to commitly" + echo "RELEASE_TYPE=commitly" >> $GITHUB_OUTPUT + elif [[ $EVENT_NAME == "workflow_dispatch" ]]; then + echo "Setting release type to ${{ github.event.inputs.release-type }}" + echo "RELEASE_TYPE=${{ github.event.inputs.release-type }}" >> $GITHUB_OUTPUT + else + echo "Setting release type to dry-run" + echo "RELEASE_TYPE=dry-run" >> $GITHUB_OUTPUT + fi + build_hermesc_apple: + uses: ./.github/workflows/build-hermesc-apple.yml + build_apple_slices_hermes: + uses: ./.github/workflows/build-apple-slices-hermes.yml + needs: build_hermesc_apple + build_hermes_macos: + uses: ./.github/workflows/build-hermes-macos.yml + needs: build_apple_slices_hermes + build_hermesc_linux: + uses: ./.github/workflows/build-hermesc-linux.yml + build_hermesc_windows: + uses: ./.github/workflows/build-hermesc-windows.yml + build_android: + uses: ./.github/workflows/build-android.yml + needs: set_release_type + secrets: inherit + with: + release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} + publish: + uses: ./.github/workflows/publish.yml + needs: + [ + set_release_type, + build_hermes_macos, + build_hermesc_linux, + ] + with: + release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} diff --git a/API/hermes/CMakeLists.txt b/API/hermes/CMakeLists.txt index aaa3d18def3..1324f71702a 100644 --- a/API/hermes/CMakeLists.txt +++ b/API/hermes/CMakeLists.txt @@ -95,4 +95,4 @@ set(HERMES_ENABLE_EH OFF) add_hermes_library(synthTraceParser SynthTraceParser.cpp LINK_OBJLIBS hermesSupport hermesParser synthTrace) # compileJS uses neither exceptions nor RTTI -add_hermes_library(compileJS CompileJS.cpp LINK_OBJLIBS hermesPublic) +add_hermes_library(compileJS CompileJS.cpp LINK_OBJLIBS hermesPublic hermesHBCBackend) diff --git a/CMakeLists.txt b/CMakeLists.txt index 548e3957cc2..22a851dfbae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ endif() # - npm/package.json # - hermes-engine.podspec project(Hermes - VERSION 0.12.0 + VERSION 2.0.0 LANGUAGES C CXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") diff --git a/android/.gitignore b/android/.gitignore index 56d4f2caa75..dc806e3b47b 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,2 +1,4 @@ .gradle .externalNativeBuild +.cxx +staging diff --git a/android/build-logic/build.gradle.kts b/android/build-logic/build.gradle.kts new file mode 100644 index 00000000000..5c23d8ae225 --- /dev/null +++ b/android/build-logic/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +plugins { `kotlin-dsl` } + +repositories { + mavenCentral() + gradlePluginPortal() +} + +group = "com.facebook.hermes-v2" + +kotlin { + @Suppress("MagicNumber") jvmToolchain(17) + compilerOptions { + allWarningsAsErrors = providers.gradleProperty("warningsAsErrors").orNull.toBoolean() + } +} + +gradlePlugin { + plugins { + register("hermesUtils") { + id = "com.facebook.hermes.plugins.internal.hermesUtils" + implementationClass = "HermesUtilsPlugin" + } + } +} diff --git a/android/build-logic/settings.gradle.kts b/android/build-logic/settings.gradle.kts new file mode 100644 index 00000000000..3a643afda30 --- /dev/null +++ b/android/build-logic/settings.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +dependencyResolutionManagement { + repositories { + mavenCentral() + google() + gradlePluginPortal() + } + versionCatalogs.create("libs") { from(files("../gradle/libs.versions.toml")) } +} + +rootProject.name = "build-logic" diff --git a/android/build-logic/src/main/kotlin/com/facebook/hermes/plugins/internal/TestUtilsPlugin.kt b/android/build-logic/src/main/kotlin/com/facebook/hermes/plugins/internal/TestUtilsPlugin.kt new file mode 100644 index 00000000000..d242f4c9a0e --- /dev/null +++ b/android/build-logic/src/main/kotlin/com/facebook/hermes/plugins/internal/TestUtilsPlugin.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import java.io.File +import org.gradle.api.InvalidUserDataException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension + +class HermesUtilsPlugin : Plugin { + override fun apply(project: Project) { + val hermesUtils = HermesUtils(project) + + project.file(hermesUtils.outputDir).mkdirs() + project.file("${hermesUtils.outputDir}/aar").mkdirs() + + project.extensions.extraProperties["hermesUtils"] = hermesUtils + } +} + +class HermesUtils(private val project: Project) { + private val libs = + project.extensions.getByType(VersionCatalogsExtension::class.java).named("libs") + + val hermesWs: String + get() { + val value = System.getenv("HERMES_WS_DIR") + if (value == null || value == "") { + throw InvalidUserDataException("HERMES_WS_DIR is not set") + } + return value + } + + val hermesC: String + get() { + val candidateHermesCPaths = + listOf( + "$hermesWs/build/ImportHostCompilers.cmake", + "$hermesWs/build_release/ImportHostCompilers.cmake", + ) + + val hermesCPath = candidateHermesCPaths.lastOrNull { File(it).exists() } + if (hermesCPath == null) { + project.logger.warn("Could not find hermesC path. Using default path.") + return "$hermesWs/build/ImportHostCompilers.cmake" + } + + return hermesCPath + } + + // For Facebook internal use: + val fbsource: String + get() = System.getenv("FBSOURCE_DIR") ?: "${System.getenv("HOME")}/fbsource" + + val outputDir: String + get() = "${hermesWs}/build_android/outputs" + + val compileSdk: Int + get() = libs.findVersion("compileSdk").get().getRequiredVersion().toInt() + + val minSdk: Int + get() = libs.findVersion("minSdk").get().getRequiredVersion().toInt() + + val facebookBuild: String + get() = System.getenv("FACEBOOK") ?: "0" + + val cmakeVersion: String + get() = libs.findVersion("cmake").get().getDisplayName() + + val ndkVersion: String + get() = libs.findVersion("ndkVersion").get().getDisplayName() + + val abis: List + get() = + (project.findProperty("abis") as? String)?.split(",") + ?: listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86") +} diff --git a/android/build-logic/src/main/kotlin/com/facebook/hermes/tasks/internal/CustomExecTask.kt b/android/build-logic/src/main/kotlin/com/facebook/hermes/tasks/internal/CustomExecTask.kt new file mode 100644 index 00000000000..2a02725393d --- /dev/null +++ b/android/build-logic/src/main/kotlin/com/facebook/hermes/tasks/internal/CustomExecTask.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.hermes.tasks.internal + +import java.io.File +import java.io.FileOutputStream +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputFile + +/** + * A Task that will just expose an Exec-like task and that offers properties to configure the + * standard output and error. + */ +abstract class CustomExecTask : Exec() { + + @get:OutputFile @get:Optional abstract val standardOutputFile: RegularFileProperty + + @get:OutputFile @get:Optional abstract val errorOutputFile: RegularFileProperty + + @get:Input @get:Optional abstract val onlyIfProvidedPathDoesNotExists: Property + + override fun exec() { + if (onlyIfProvidedPathDoesNotExists.isPresent && + File(onlyIfProvidedPathDoesNotExists.get()).exists()) { + return + } + if (standardOutputFile.isPresent) { + standardOutput = FileOutputStream(standardOutputFile.get().asFile) + } + if (errorOutputFile.isPresent) { + errorOutput = FileOutputStream(errorOutputFile.get().asFile) + } + super.exec() + } +} diff --git a/android/build-logic/src/main/kotlin/publish.gradle.kts b/android/build-logic/src/main/kotlin/publish.gradle.kts new file mode 100644 index 00000000000..92c9ef0dbc2 --- /dev/null +++ b/android/build-logic/src/main/kotlin/publish.gradle.kts @@ -0,0 +1,82 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +plugins { + id("maven-publish") + id("signing") +} + +val isSnapshot: Boolean = findProperty("isSnapshot")?.toString()?.toBoolean() ?: false +val signingKey: String? = findProperty("SIGNING_KEY")?.toString() +val signingPwd: String? = findProperty("SIGNING_PWD")?.toString() + +val mavenTempLocalUrl = "file:///tmp/maven-local" + +publishing { + publications { + create("release") { + afterEvaluate { + if (project.plugins.hasPlugin("com.android.library")) { + from(components["default"]) + } + + version = + if (isSnapshot) { + "${project.version}-SNAPSHOT" + } else { + project.version.toString() + } + + groupId = project.group.toString() + } + + pom { + name.set("hermes") + description.set("A JavaScript engine optimized for fast start-up of React Native apps") + url.set("https://github.com/facebook/hermes/tree/static_h") + + developers { + developer { + id.set("facebook") + name.set("Facebook") + } + } + + licenses { + license { + name.set("MIT License") + url.set("https://github.com/facebook/hermes/blob/HEAD/LICENSE") + distribution.set("repo") + } + } + + scm { + url.set("https://github.com/facebook/hermes.git") + connection.set("scm:git:https://github.com/facebook/hermes.git") + developerConnection.set("scm:git:git@github.com:facebook/hermes.git") + } + } + } + } + + repositories { + maven { + name = "mavenTempLocal" + url = uri(mavenTempLocalUrl) + } + } +} + +if (signingKey != null && signingPwd != null) { + logger.info("PGP Key found - Signing enabled") + signing { + useInMemoryPgpKeys(signingKey, signingPwd) + sign(publishing.publications["release"]) + } +} else { + logger.info("Signing disabled as the PGP key was not found") +} diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 109a4b79d6b..00000000000 --- a/android/build.gradle +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// This must be consistent with the release_version in: -// - CMakeLists.txt -// - npm/package.json -// - hermes-engine.podspec -def release_version = "0.12.0" - -buildscript { - ext { - hermes_ws = System.getenv("HERMES_WS_DIR") - if (hermes_ws == null || hermes_ws == "") { - throw new InvalidUserDataException("HERMES_WS_DIR is not set") - } - outputDir = file("${hermes_ws}/build_android/outputs") - outputDir.mkdirs() - file("${outputDir}/aar").mkdirs() - - facebookBuild = System.getenv("FACEBOOK") ?: "0" - hermesHostBuild = System.getenv("HERMES_WS_DIR") - - // Look for a hermes host build, either debug or release - // This is used as part of the Hermes build process - hermesC = "" - for (f in [ - "${hermesHostBuild}/build/ImportHermesc.cmake", - "${hermesHostBuild}/build_release/ImportHermesc.cmake"]) { - if(file(f).exists()) hermesC = f; - } - assert hermesC != "" : "Hermes host build not found" - - // For Facebook internal use: - fbsource = System.getenv("FBSOURCE_DIR") ?: - System.getenv("HOME") + "/fbsource" - - minSdkVersion = 16 - compileSdkVersion = 31 - abis = project.hasProperty('abis') ? - project.getProperty("abis").split(",") : - ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"] - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath("de.undercouch:gradle-download-task:4.0.2") - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - - -buildDir = "${hermes_ws}/build_android" - -task copyApiHeaders(type: Copy) { - from "$projectDir/../API/hermes" - include '**/*.h' - into "$outputDir/include/hermes" -} - -task copyJsiHeaders(type: Copy) { - from "$projectDir/../API/jsi/jsi" - include '**/*.h' - into "$outputDir/include/jsi" -} - -task copyPublicHeaders(type: Copy) { - from "$projectDir/../public/hermes/Public" - include '**/*.h' - into "$outputDir/include/hermes/Public" -} - -// TODO: With the Intl API implementation, we have java class files inside the hermes library on Android, -// Which has non-platform dependencies such as fbjni, facebook annotation library etc. -// It is no longer appropriate or correct to publish hermes as a flat archive, instead we should publish as a maven library with proper POM file describing the dependencies. -task githubRelease(dependsOn: ['hermes:assembleIntl', 'cppruntime:build', 'copyApiHeaders', 'copyJsiHeaders', 'copyPublicHeaders'], type: Tar) { - archiveBaseName = "hermes-runtime-android" - archiveVersion = "v" + release_version - archiveExtension = "tar.gz" - compression = Compression.GZIP - destinationDirectory = file("${hermes_ws}/build_android/distributions") - from "$outputDir" -} - -task githubReleaseNoIntl(dependsOn: ['hermes:assembleNointl', 'cppruntime:build', 'copyApiHeaders', 'copyJsiHeaders', 'copyPublicHeaders'], type: Tar) { - archiveBaseName = "hermes-runtime-android-nointl" - archiveVersion = "v" + release_version - archiveExtension = "tar.gz" - compression = Compression.GZIP - destinationDirectory = file("${hermes_ws}/build_android/distributions") - from "$outputDir" -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 00000000000..7f8beaf21a6 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,325 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import com.facebook.hermes.tasks.internal.CustomExecTask +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id("maven-publish") + id("signing") + alias(libs.plugins.nexus.publish) + alias(libs.plugins.android.library) + alias(libs.plugins.download) + id("publish") +} + +group = "com.facebook.hermes-v2" + +version = project.findProperty("VERSION_NAME")?.toString()!! + +val cmakeVersion = System.getenv("CMAKE_VERSION") ?: libs.versions.cmake.get() +val cmakePath = "${getSDKPath()}/cmake/$cmakeVersion" +val cmakeBinaryPath = "${cmakePath}/bin/cmake" +val sonatypeUsername = findProperty("SONATYPE_USERNAME")?.toString() +val sonatypePassword = findProperty("SONATYPE_PASSWORD")?.toString() + +nexusPublishing { + repositories { + sonatype { + username.set(sonatypeUsername) + password.set(sonatypePassword) + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) + } + } +} + +fun getSDKPath(): String { + val androidSdkRoot = System.getenv("ANDROID_SDK_ROOT") + val androidHome = System.getenv("ANDROID_HOME") + return when { + !androidSdkRoot.isNullOrBlank() -> androidSdkRoot + !androidHome.isNullOrBlank() -> androidHome + else -> throw IllegalStateException("Neither ANDROID_SDK_ROOT nor ANDROID_HOME is set.") + } +} + +fun getSDKManagerPath(): String { + val metaSdkManagerPath = File("${getSDKPath()}/cmdline-tools/latest/bin/sdkmanager") + val ossSdkManagerPath = File("${getSDKPath()}/tools/bin/sdkmanager") + val windowsMetaSdkManagerPath = File("${getSDKPath()}/cmdline-tools/latest/bin/sdkmanager.bat") + val windowsOssSdkManagerPath = File("${getSDKPath()}/tools/bin/sdkmanager.bat") + val linuxSdkManagerPath = File("${getSDKPath()}/cmdline-tools/tools/bin/sdkmanager") + return when { + metaSdkManagerPath.exists() -> metaSdkManagerPath.absolutePath + windowsMetaSdkManagerPath.exists() -> windowsMetaSdkManagerPath.absolutePath + ossSdkManagerPath.exists() -> ossSdkManagerPath.absolutePath + windowsOssSdkManagerPath.exists() -> windowsOssSdkManagerPath.absolutePath + linuxSdkManagerPath.exists() -> linuxSdkManagerPath.absolutePath + else -> throw GradleException("Could not find sdkmanager executable.") + } +} + +val hermesDir = project.projectDir.parentFile +val hermesBuildDir = File("$hermesDir/build") + +project.layout.buildDirectory.set(hermesBuildDir) + +val buildDir = project.layout.buildDirectory.get().asFile +val hermesCOutputBinary = File("$buildDir/hermes/bin/hermesc") + +// This filetree represents the file of the Hermes build that we want as input/output +// of the buildHermesC task. Gradle will compute the hash of files in the file tree +// and won't rebuilt hermesc unless those files are changing. +val hermesBuildOutputFileTree = + fileTree(hermesBuildDir.toString()) + .include("**/*.cmake", "**/*.marks", "**/compiler_depends.ts", "**/Makefile", "**/link.txt") + +val ndkBuildJobs = Runtime.getRuntime().availableProcessors().toString() +val prefabHeadersDir = File("$buildDir/prefab-headers") + +// We inject the JSI directory used inside the Hermes build with the -DJSI_DIR config. +val jsiDir = File("$hermesDir/API/jsi") + +// NOTE: ideally, we would like CMake to be installed automatically by the `externalNativeBuild` +// below. To do that, we would need the various `ConfigureCMake*` tasks to run *before* +// `configureBuildForHermes` and `buildHermesC` so that CMake is available for their run. But the +// `ConfigureCMake*` tasks depend upon the `ImportHermesc.cmake` file which is actually generated by +// the two tasks mentioned before, so we install CMake manually to break the circular dependency. +val installCMake by + tasks.registering(CustomExecTask::class) { + onlyIfProvidedPathDoesNotExists.set(cmakePath) + commandLine( + windowsAwareCommandLine(getSDKManagerPath(), "--install", "cmake;${cmakeVersion}") + ) + } + +val configureBuildForHermes by + tasks.registering(CustomExecTask::class) { + dependsOn(installCMake) + dependsOn("prepareLintJarForPublish") + dependsOn("generatePomFileForReleasePublication") + workingDir(hermesDir) + inputs.dir(hermesDir) + outputs.files(hermesBuildOutputFileTree) + var cmakeCommandLine = + windowsAwareCommandLine( + cmakeBinaryPath, + // Suppress all warnings as this is the Hermes build and we can't fix them. + "--log-level=ERROR", + "-Wno-dev", + "-S", + ".", + "-B", + hermesBuildDir.toString(), + "-DJSI_DIR=" + jsiDir.absolutePath, + "-DCMAKE_BUILD_TYPE=Debug", + ) + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + cmakeCommandLine = cmakeCommandLine + "-GNMake Makefiles" + } + commandLine(cmakeCommandLine) + standardOutputFile.set(project.file("$buildDir/configure-hermesc.log")) + } + +val buildHermesC by + tasks.registering(CustomExecTask::class) { + dependsOn(configureBuildForHermes) + workingDir(hermesDir) + inputs.files(hermesBuildOutputFileTree) + outputs.file(hermesCOutputBinary) + commandLine( + cmakeBinaryPath, + "--build", + hermesBuildDir.toString(), + "--target", + "hermesc", + "-j", + ndkBuildJobs, + ) + standardOutputFile.set(project.file("$buildDir/build-hermesc.log")) + errorOutputFile.set(project.file("$buildDir/build-hermesc.error.log")) + doFirst { println("Building hermesc with command: ${commandLine.joinToString(" ")}") } + } + +val prepareHeadersForPrefab by + tasks.registering(Copy::class) { + dependsOn(buildHermesC) + from("$hermesDir/API") + from("$hermesDir/public") + include("**/*.h") + exclude("jsi/**") + into(prefabHeadersDir) + } + +val buildHermesLib by + tasks.registering(CustomExecTask::class) { + dependsOn(buildHermesC) + workingDir(hermesDir) + inputs.files(hermesBuildOutputFileTree) + commandLine( + cmakeBinaryPath, + "--build", + hermesBuildDir.toString(), + "--target", + "libhermes", + "-j", + ndkBuildJobs, + ) + standardOutputFile.set(project.file("$buildDir/build-hermes-lib.log")) + errorOutputFile.set(project.file("$buildDir/build-hermes-lib.error.log")) + } + +fun windowsAwareCommandLine(vararg commands: String): List { + val result = + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + mutableListOf("cmd", "/c") + } else { + mutableListOf() + } + result.addAll(commands) + return result +} + +repositories { + mavenCentral() + google() +} + +android { + compileSdk = libs.versions.compileSdk.get().toInt() + buildToolsVersion = libs.versions.buildTools.get() + namespace = "com.facebook.hermes" + + // Used to override the NDK path/version on internal CI or by allowing + // users to customize the NDK path/version from their root project (e.g. for Apple Silicon + // support) + if (rootProject.hasProperty("ndkPath") && rootProject.properties["ndkPath"] != null) { + ndkPath = rootProject.properties["ndkPath"].toString() + } + if (rootProject.hasProperty("ndkVersion") && rootProject.properties["ndkVersion"] != null) { + ndkVersion = rootProject.properties["ndkVersion"].toString() + } else { + ndkVersion = libs.versions.ndkVersion.get() + } + + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + + externalNativeBuild { + cmake { + arguments( + "--log-level=ERROR", + "-Wno-dev", + "-DHERMES_IS_ANDROID=True", + "-DANDROID_STL=c++_shared", + "-DANDROID_PIE=True", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", + "-DIMPORT_HOST_COMPILERS=${File(hermesBuildDir, "ImportHostCompilers.cmake").toString()}", + "-DJSI_DIR=${jsiDir}", + "-DHERMES_BUILD_SHARED_JSI=True", + // "-DHERMES_RELEASE_VERSION=for RN ${version}", + "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True", + // We intentionally build Hermes with Intl support only. This is to simplify + // the build setup and to avoid overcomplicating the build-type matrix. + "-DHERMES_ENABLE_INTL=True", + ) + + targets("hermesvm") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } + } + + externalNativeBuild { + cmake { + version = cmakeVersion + path = File("$hermesDir/CMakeLists.txt") + } + } + + buildTypes { + debug { + externalNativeBuild { + cmake { + // JS developers aren't VM developers. + // Therefore we're passing as build type Release, to provide a faster build. + // This has the (unlucky) side effect of letting AGP call the build + // tasks `configureCMakeRelease` while is actually building the debug flavor. + arguments("-DCMAKE_BUILD_TYPE=Release", "-DHERMES_ENABLE_DEBUGGER=1") + } + } + } + release { externalNativeBuild { cmake { arguments("-DCMAKE_BUILD_TYPE=MinSizeRel") } } } + + buildTypes { + create("debugOptimized") { + initWith(getByName("debug")) + externalNativeBuild { cmake { arguments("-DCMAKE_BUILD_TYPE=Release") } } + } + } + } + + sourceSets.getByName("main") { + manifest.srcFile("$hermesDir/android/hermes/src/main/AndroidManifest.xml") + java.srcDirs("$hermesDir/lib/Platform/Intl/java", "$hermesDir/lib/Platform/Unicode/java") + } + + buildFeatures { + prefab = true + prefabPublishing = true + } + + dependencies { + implementation(libs.fbjni) + implementation(libs.yoga.proguard.annotations) + implementation(libs.androidx.annotation) + } + + packaging { + jniLibs.excludes.add("**/libc++_shared.so") + jniLibs.excludes.add("**/libjsi.so") + jniLibs.excludes.add("**/libfbjni.so") + } + + publishing { + multipleVariants { + withSourcesJar() + allVariants() + } + } + + prefab { create("hermesvm") { headers = prefabHeadersDir.absolutePath } } +} + +afterEvaluate { + tasks.getByName("preBuild").dependsOn(buildHermesC) + tasks.getByName("preBuild").dependsOn(prepareHeadersForPrefab) +} + +tasks.withType().configureEach { + options.compilerArgs.add("-Xlint:deprecation,unchecked") + options.compilerArgs.add("-Werror") +} + +tasks.register("publishAndroidOnlyToMavenTempLocal") { + dependsOn(":publishAllPublicationsToMavenTempLocalRepository", ":build") +} + +tasks.register("publishAndroidOnlyToSonatype") { dependsOn(":publishToSonatype") } + +// We need to override the artifact ID as this project is called `hermes-engine` but +// the maven coordinates are on `hermes-android`. +publishing { + publications { getByName("release", MavenPublication::class) { artifactId = "hermes-android" } } +} diff --git a/android/cppruntime/build.gradle b/android/cppruntime/build.gradle index 887039685d1..a4ccb85d53b 100644 --- a/android/cppruntime/build.gradle +++ b/android/cppruntime/build.gradle @@ -5,16 +5,26 @@ * LICENSE file in the root directory of this source tree. */ -apply plugin: 'com.android.library' +plugins { + id("com.android.library") + id("com.facebook.hermes.plugins.internal.hermesUtils") +} + +repositories { + google() + mavenCentral() +} -buildDir = "${rootProject.ext.hermes_ws}/build_android/cppruntime" +buildDir = "${hermesUtils.hermesWs}/build_android/cppruntime" buildDir.mkdirs() android { - compileSdkVersion = rootProject.ext.compileSdkVersion + namespace = "com.facebook.hermes.cppruntime" + compileSdk = hermesUtils.compileSdk + ndkVersion = hermesUtils.ndkVersion defaultConfig { - minSdkVersion = rootProject.ext.minSdkVersion + minSdk = hermesUtils.minSdk externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared" @@ -23,15 +33,15 @@ android { } ndk { - abiFilters (*rootProject.ext.abis) + abiFilters (*hermesUtils.abis) } } externalNativeBuild { cmake { - version "3.22.1" - path "src/main/cpp/CMakeLists.txt" - buildStagingDirectory = "${rootProject.ext.hermes_ws}/staging/cppruntime" + version = hermesUtils.cmakeVersion + path = "src/main/cpp/CMakeLists.txt" + buildStagingDirectory = "${hermesUtils.hermesWs}/staging/cppruntime" buildStagingDirectory.mkdirs() } } @@ -48,14 +58,14 @@ android { def aarDir = "$buildDir/outputs/aar" tasks.named("assembleDebug").configure { doLast { - file("$aarDir/cppruntime-debug.aar").renameTo("${rootProject.ext.outputDir}/hermes-cppruntime-debug.aar") - file("$aarDir/cppruntime.aar").renameTo("${rootProject.ext.outputDir}/hermes-cppruntime-debug.aar") + file("$aarDir/cppruntime-debug.aar").renameTo("${hermesUtils.outputDir}/hermes-cppruntime-debug.aar") + file("$aarDir/cppruntime.aar").renameTo("${hermesUtils.outputDir}/hermes-cppruntime-debug.aar") } } tasks.named("assembleRelease").configure { doLast { - file("$aarDir/cppruntime-release.aar").renameTo("${rootProject.ext.outputDir}/hermes-cppruntime-release.aar") - file("$aarDir/cppruntime.aar").renameTo("${rootProject.ext.outputDir}/hermes-cppruntime-release.aar") + file("$aarDir/cppruntime-release.aar").renameTo("${hermesUtils.outputDir}/hermes-cppruntime-release.aar") + file("$aarDir/cppruntime.aar").renameTo("${hermesUtils.outputDir}/hermes-cppruntime-release.aar") } } } diff --git a/android/gradle.properties b/android/gradle.properties index 938b16188c3..e74ab8743f4 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,5 @@ +VERSION_NAME=0.0.0 + android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml new file mode 100644 index 00000000000..6be613a702a --- /dev/null +++ b/android/gradle/libs.versions.toml @@ -0,0 +1,30 @@ +[versions] +# Android versions +minSdk = "24" +targetSdk = "36" +compileSdk = "36" +buildTools = "36.0.0" +ndkVersion = "27.1.12297006" +# CMake version +cmake = "3.31.6" +# Dependencies versions +agp = "8.11.0" +androidx-annotation = "1.6.0" +download = "5.4.0" +fbjni = "0.7.0" +kotlin = "2.1.20" +ktfmt = "0.22.0" +nexus-publish = "2.0.0" +yoga-proguard-annotations = "1.19.0" + +[libraries] +androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } +fbjni = { module = "com.facebook.fbjni:fbjni", version.ref = "fbjni" } +yoga-proguard-annotations = { module = "com.facebook.yoga:proguard-annotations", version.ref = "yoga-proguard-annotations" } + +[plugins] +android-library = { id = "com.android.library", version.ref = "agp" } +download = { id = "de.undercouch.download", version.ref = "download" } +ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" } +nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index b1159fc54f3..3ae1e2f124c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/hermes/build.gradle b/android/hermes/build.gradle deleted file mode 100644 index 62441adce74..00000000000 --- a/android/hermes/build.gradle +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -apply plugin: 'com.android.library' - -buildDir = "${rootProject.ext.hermes_ws}/build_android/hermes" -buildDir.mkdirs() - -android { - compileSdkVersion = rootProject.ext.compileSdkVersion - - defaultConfig { - minSdkVersion rootProject.ext.minSdkVersion - namespace "com.facebook.hermes" - - externalNativeBuild { - cmake { - arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" - arguments "-DHERMES_IS_ANDROID=True" - arguments "-DHERMES_FACEBOOK_BUILD=${rootProject.ext.facebookBuild}" - arguments "-DANDROID_STL=c++_shared" - arguments "-DANDROID_PIE=True" - arguments "-DIMPORT_HERMESC=${rootProject.ext.hermesC}" - arguments "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True" - arguments "-DHERMES_BUILD_SHARED_JSI=True" - targets "libhermes" - } - } - ndk { - abiFilters (*rootProject.ext.abis) - } - } - - externalNativeBuild { - cmake { - version "3.22.1" - path "../../CMakeLists.txt" - buildStagingDirectory = "${rootProject.ext.hermes_ws}/staging/hermes" - buildStagingDirectory.mkdirs() - } - } - - buildTypes { - debug { - externalNativeBuild { - cmake { - // JS developers aren't VM developers. Give them a faster build. - arguments "-DCMAKE_BUILD_TYPE=Release" - } - } - } - release { - externalNativeBuild { - cmake { - arguments "-DHERMES_ENABLE_DEBUGGER=False" - arguments "-DCMAKE_BUILD_TYPE=MinSizeRel" - } - } - } - } - - flavorDimensions "featureset" - productFlavors { - intl { - isDefault true - dimension "featureset" - externalNativeBuild { - cmake { - arguments "-DHERMES_ENABLE_INTL=True" - } - } - } - nointl { - dimension "featureset" - externalNativeBuild { - cmake { - arguments "-DHERMES_ENABLE_INTL=False" - } - } - } - } - - sourceSets { - intl { - java { - srcDirs = [ - "../../lib/Platform/Intl/java", - ] - } - } - } - - // Allow using prefab so that we can import libfbjni.so. - buildFeatures { - prefab true - } - - dependencies { - implementation 'com.facebook.fbjni:fbjni:0.3.0' - intlImplementation 'com.facebook.soloader:soloader:0.10.4' - intlImplementation 'com.facebook.yoga:proguard-annotations:1.19.0' - intlImplementation "androidx.annotation:annotation:1.3.0" - } - - packagingOptions { - exclude "**/libc++_shared.so" - exclude "**/libjsi.so" - exclude "**/libfbjni.so" - } - - afterEvaluate { - // Gradle 4/5.0 outputs android-debug.aar and android-release.aar - // Gradle 5.1 outputs android.aar for both - // Unify the two by renaming the files afterwards. - // Neither appear to care whether the original filename actually exists. - def aarDir = "$buildDir/outputs/aar" - tasks.named("assembleIntlDebug").configure { - doLast { - file("$aarDir/hermes-intl-debug.aar").renameTo("${rootProject.ext.outputDir}/hermes-debug.aar") - file("$aarDir/hermes-intl.aar").renameTo("${rootProject.ext.outputDir}/hermes-debug.aar") - } - } - tasks.named("assembleIntlRelease").configure { - doLast { - file("$aarDir/hermes-intl-release.aar").renameTo("${rootProject.ext.outputDir}/hermes-release.aar") - file("$aarDir/hermes-intl.aar").renameTo("${rootProject.ext.outputDir}/hermes-release.aar") - } - } - tasks.named("assembleNointlDebug").configure { - doLast { - file("$aarDir/hermes-nointl-debug.aar").renameTo("${rootProject.ext.outputDir}/hermes-debug.aar") - file("$aarDir/hermes-nointl.aar").renameTo("${rootProject.ext.outputDir}/hermes-debug.aar") - } - } - tasks.named("assembleNointlRelease").configure { - doLast { - file("$aarDir/hermes-nointl-release.aar").renameTo("${rootProject.ext.outputDir}/hermes-release.aar") - file("$aarDir/hermes-nointl.aar").renameTo("${rootProject.ext.outputDir}/hermes-release.aar") - } - } - tasks.named("mergeIntlDebugNativeLibs").configure { - doLast { task -> - copy { - from(task) - into("${rootProject.ext.outputDir}/unstripped-debug") - } - } - } - tasks.named("mergeIntlReleaseNativeLibs").configure { - doLast { task -> - copy { - from(task) - into("${rootProject.ext.outputDir}/unstripped-release") - } - } - } - tasks.named("mergeNointlDebugNativeLibs").configure { - doLast { task -> - copy { - from(task) - into("${rootProject.ext.outputDir}/unstripped-debug") - } - } - } - tasks.named("mergeNointlReleaseNativeLibs").configure { - doLast { task -> - copy { - from(task) - into("${rootProject.ext.outputDir}/unstripped-release") - } - } - } - } -} diff --git a/android/intltest/build.gradle b/android/intltest/build.gradle index 7ea4bd805b2..15844abffb8 100644 --- a/android/intltest/build.gradle +++ b/android/intltest/build.gradle @@ -9,22 +9,23 @@ // ./gradlew intl:connectedAndroidTest plugins { - id('com.android.library') + id("com.android.library") id("de.undercouch.download") + id("com.facebook.hermes.plugins.internal.hermesUtils") } import de.undercouch.gradle.tasks.download.Download -buildDir = "${rootProject.ext.hermes_ws}/build_android/intltest" +buildDir = "${hermesUtils.hermesWs}/build_android/intltest" buildDir.mkdirs() def testDestination = "java/com/facebook/hermes/test/assets" task prepareTests() { doLast { - def test262Dir = file(rootProject.ext.fbsource).exists() ? - "${rootProject.ext.fbsource}/xplat/third-party/javascript-test-suites/test262/" : - "${rootProject.ext.hermes_ws}/test262" + def test262Dir = file(hermesUtils.fbsource).exists() ? + "${hermesUtils.fbsource}/xplat/third-party/javascript-test-suites/test262/" : + "${hermesUtils.hermesWs}/test262" assert file(test262Dir).exists() : "Test262 directory not found" copy{ from(test262Dir) { @@ -56,37 +57,39 @@ task prepareTests() { // TODO: Figure out how to deduplicate this file and intl/build.gradle android { - compileSdkVersion 31 + namespace = "com.facebook.hermes.intltest" + compileSdk = hermesUtils.compileSdk + ndkVersion = hermesUtils.ndkVersion defaultConfig { - minSdkVersion 16 + minSdk = hermesUtils.minSdk ndk { - abiFilters (*rootProject.ext.abis) + abiFilters (*hermesUtils.abis) } testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' externalNativeBuild { cmake { arguments "-DHERMES_IS_ANDROID=True" - arguments "-DHERMES_FACEBOOK_BUILD=${rootProject.ext.facebookBuild}" + arguments "-DHERMES_FACEBOOK_BUILD=${hermesUtils.facebookBuild}" arguments "-DANDROID_STL=c++_shared" arguments "-DANDROID_PIE=True" - arguments "-DIMPORT_HERMESC=${rootProject.ext.hermesC}" + arguments "-DIMPORT_HOST_COMPILERS=${hermesUtils.hermesC}" arguments "-DHERMES_SLOW_DEBUG=False" arguments "-DHERMES_ENABLE_INTL=True" - targets "jsijni", "jsijniepi", "libhermes" + targets "jsijni", "jsijniepi", "hermesvm" } } ndk { - abiFilters (*rootProject.ext.abis) + abiFilters (*hermesUtils.abis) } } externalNativeBuild { cmake { - version "3.22.1" + version hermesUtils.cmakeVersion path "../../CMakeLists.txt" - buildStagingDirectory = "${rootProject.ext.hermes_ws}/staging/intl" + buildStagingDirectory = "${hermesUtils.hermesWs}/staging/intl" buildStagingDirectory.mkdirs() } } @@ -137,3 +140,8 @@ android { } } } + +repositories { + google() + mavenCentral() +} diff --git a/android/ios-artifacts/build.gradle.kts b/android/ios-artifacts/build.gradle.kts new file mode 100644 index 00000000000..9e3771ea207 --- /dev/null +++ b/android/ios-artifacts/build.gradle.kts @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +plugins { + id("maven-publish") + id("publish") +} + +group = "com.facebook.hermes-v2" + +version = project.findProperty("VERSION_NAME")?.toString()!! + +configurations.maybeCreate("iosArtifacts") + +// Those artifacts should be placed inside the `artifacts/hermes-ios-*.tar.gz` location. +val hermesiOSDebugArtifactFile: RegularFile = + layout.projectDirectory.file("artifacts/hermes-ios-debug.tar.gz") +val hermesiOSDebugArtifact: PublishArtifact = + artifacts.add("iosArtifacts", hermesiOSDebugArtifactFile) { + type = "tgz" + extension = "tar.gz" + classifier = "hermes-ios-debug" + } +val hermesiOSReleaseArtifactFile: RegularFile = + layout.projectDirectory.file("artifacts/hermes-ios-release.tar.gz") +val hermesiOSReleaseArtifact: PublishArtifact = + artifacts.add("iosArtifacts", hermesiOSReleaseArtifactFile) { + type = "tgz" + extension = "tar.gz" + classifier = "hermes-ios-release" + } + +// Those artifacts should be placed inside the `artifacts/hermes-*.framework.dSYM` location +val hermesDSYMDebugArtifactFile: RegularFile = + layout.projectDirectory.file("artifacts/hermes-framework-dSYM-debug.tar.gz") +val hermesDSYMDebugArtifact: PublishArtifact = + artifacts.add("iosArtifacts", hermesDSYMDebugArtifactFile) { + type = "tgz" + extension = "tar.gz" + classifier = "hermes-framework-dSYM-debug" + } +val hermesDSYMReleaseArtifactFile: RegularFile = + layout.projectDirectory.file("artifacts/hermes-framework-dSYM-release.tar.gz") +val hermesDSYMReleaseArtifact: PublishArtifact = + artifacts.add("iosArtifacts", hermesDSYMReleaseArtifactFile) { + type = "tgz" + extension = "tar.gz" + classifier = "hermes-framework-dSYM-release" + } + +publishing { + publications { + getByName("release", MavenPublication::class) { + artifactId = "hermes-ios" + artifact(hermesiOSDebugArtifact) + artifact(hermesiOSReleaseArtifact) + artifact(hermesDSYMDebugArtifact) + artifact(hermesDSYMReleaseArtifact) + } + } +} diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index e8fbd66b713..00000000000 --- a/android/settings.gradle +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -rootProject.name = 'Hermes' - -include ':hermes' -include ':cppruntime' -include ':intltest' diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 00000000000..73a5f1fcbd2 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +pluginManagement { + includeBuild("build-logic") + + repositories { + mavenCentral() + google() + gradlePluginPortal() + } +} + +rootProject.name = "hermes-engine" + +include(":ios-artifacts", ":cppruntime", ":intltest") diff --git a/lib/Platform/Logging.cpp b/lib/Platform/Logging.cpp index 893756d2d33..c6f9f80861b 100644 --- a/lib/Platform/Logging.cpp +++ b/lib/Platform/Logging.cpp @@ -11,6 +11,7 @@ #include #elif defined(__APPLE__) #include +#include #else #include #endif diff --git a/utils/build-apple-framework-rn.sh b/utils/build-apple-framework-rn.sh new file mode 100755 index 00000000000..9b1fd75ebe4 --- /dev/null +++ b/utils/build-apple-framework-rn.sh @@ -0,0 +1,216 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# Defines functions for building various Hermes frameworks. +# See build-ios-framework.sh and build-mac-framework.sh for usage examples. + +CURR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" + +IMPORT_HERMESC_PATH=${HERMES_OVERRIDE_HERMESC_PATH:-$PWD/build_host_hermesc/ImportHostCompilers.cmake} +BUILD_TYPE=${BUILD_TYPE:-Debug} + +HERMES_PATH="$CURR_SCRIPT_DIR/.." + +NUM_CORES=$(sysctl -n hw.ncpu) + +PLATFORMS=("macosx" "iphoneos" "iphonesimulator" "catalyst" "xros" "xrsimulator" "appletvos" "appletvsimulator") + +if [[ -z "$JSI_PATH" ]]; then + JSI_PATH="$HERMES_PATH/API/jsi" +fi + +function use_env_var { + if [[ -n "$1" ]]; then + echo "$1" + else + echo "error: Missing $2 environment variable" + exit 1 + fi +} + +function get_ios_deployment_target { + use_env_var "${IOS_DEPLOYMENT_TARGET}" "IOS_DEPLOYMENT_TARGET" +} + +function get_visionos_deployment_target { + use_env_var "${XROS_DEPLOYMENT_TARGET}" "XROS_DEPLOYMENT_TARGET" +} + +function get_mac_deployment_target { + use_env_var "${MAC_DEPLOYMENT_TARGET}" "MAC_DEPLOYMENT_TARGET" +} + +# Build host hermes compiler for internal bytecode +function build_host_hermesc { + echo "Building hermesc" + pushd "$HERMES_PATH" > /dev/null || exit 1 + cmake -S . -B build_host_hermesc -DJSI_DIR="$JSI_PATH" -DCMAKE_BUILD_TYPE=Release + cmake --build ./build_host_hermesc --target hermesc -j "${NUM_CORES}" + popd > /dev/null || exit 1 +} + +# Utility function to configure an Apple framework +function configure_apple_framework { + local enable_debugger cmake_build_type xcode_15_flags xcode_major_version + + if [[ $BUILD_TYPE == "Debug" ]]; then + enable_debugger="true" + else + enable_debugger="false" + fi + if [[ $BUILD_TYPE == "Debug" ]]; then + # JS developers aren't VM developers. + # Therefore we're passing as build type Release, to provide a faster build. + cmake_build_type="Release" + else + cmake_build_type="MinSizeRel" + fi + + xcode_15_flags="" + xcode_major_version=$(xcodebuild -version | grep -oE '[0-9]*' | head -n 1) + if [[ $xcode_major_version -ge 15 ]]; then + xcode_15_flags="LINKER:-ld_classic" + fi + + boost_context_flag="" + if [[ $1 == "catalyst" ]]; then + boost_context_flag="-DHERMES_ALLOW_BOOST_CONTEXT=0" + fi + + pushd "$HERMES_PATH" > /dev/null || exit 1 + cmake -S . -B "build_$1" \ + -DHERMES_EXTRA_LINKER_FLAGS="$xcode_15_flags" \ + -DHERMES_APPLE_TARGET_PLATFORM:STRING="$1" \ + -DCMAKE_OSX_ARCHITECTURES:STRING="$2" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING="$3" \ + -DHERMES_ENABLE_DEBUGGER:BOOLEAN="$enable_debugger" \ + -DHERMES_ENABLE_INTL:BOOLEAN=true \ + -DHERMES_ENABLE_LIBFUZZER:BOOLEAN=false \ + -DHERMES_ENABLE_FUZZILLI:BOOLEAN=false \ + -DHERMES_ENABLE_TEST_SUITE:BOOLEAN=false \ + -DHERMES_ENABLE_BITCODE:BOOLEAN=false \ + -DHERMES_BUILD_APPLE_FRAMEWORK:BOOLEAN=true \ + -DHERMES_BUILD_SHARED_JSI:BOOLEAN=false \ + -DCMAKE_CXX_FLAGS:STRING="-gdwarf" \ + -DCMAKE_C_FLAGS:STRING="-gdwarf" \ + -DIMPORT_HOST_COMPILERS:PATH="$IMPORT_HERMESC_PATH" \ + -DJSI_DIR="$JSI_PATH" \ + -DCMAKE_BUILD_TYPE="$cmake_build_type" \ + $boost_context_flag + popd > /dev/null || exit 1 +} + +function generate_dSYM { + TARGET_PLATFORM="$1" + + DSYM_PATH="$PWD/build_$TARGET_PLATFORM/lib/hermesvm.framework.dSYM" + xcrun dsymutil "$PWD/build_$TARGET_PLATFORM/lib/hermesvm.framework/hermesvm" --out "$DSYM_PATH" + mkdir -p "$PWD/destroot/Library/Frameworks/$TARGET_PLATFORM" + cp -R "$DSYM_PATH" "$PWD/destroot/Library/Frameworks/$TARGET_PLATFORM" +} + +function build_host_hermesc_if_needed { + if [[ ! -f "$IMPORT_HERMESC_PATH" ]]; then + build_host_hermesc + else + echo "[HermesC] Skipping! Found an existent hermesc already at: $IMPORT_HERMESC_PATH" + fi +} + +# Utility function to build an Apple framework +function build_apple_framework { + # Only build host HermesC if no file found at $IMPORT_HERMESC_PATH + build_host_hermesc_if_needed + + # Confirm ImportHermesc.cmake is now available. + [ ! -f "$IMPORT_HERMESC_PATH" ] && + echo "Host hermesc is required to build apple frameworks!" + + # $1: platform, $2: architectures, $3: deployment target + echo "Building $BUILD_TYPE framework for $1 with architectures: $2" + configure_apple_framework "$1" "$2" "$3" + + pushd "$HERMES_PATH" > /dev/null || exit 1 + mkdir -p "destroot/Library/Frameworks/$1" + cmake --build "./build_$1" --target hermesvm -j "${NUM_CORES}" + cp -R "./build_$1"/lib/hermesvm.framework* "destroot/Library/Frameworks/$1" + generate_dSYM "$1" + + # In a MacOS build, also produce the hermes and hermesc CLI tools. + if [[ $1 == macosx ]]; then + cmake --build "./build_$1" --target hermesc hermesvm -j "${NUM_CORES}" + mkdir -p destroot/bin + cp "./build_$1/bin"/* "destroot/bin" + fi + + # Copy over Hermes and JSI API headers. + mkdir -p destroot/include/hermes/Public + cp public/hermes/Public/*.h destroot/include/hermes/Public + + mkdir -p destroot/include/hermes + cp API/hermes/*.h destroot/include/hermes + + mkdir -p destroot/include/hermes/cdp + cp API/hermes/cdp/*.h destroot/include/hermes/cdp + + mkdir -p destroot/include/jsi + cp "$JSI_PATH"/jsi/*.h destroot/include/jsi + popd > /dev/null || exit 1 +} + +function prepare_dest_root_for_ci { + mkdir -p "destroot/bin" + for platform in "${PLATFORMS[@]}"; do + mkdir -p "destroot/Library/Frameworks/$platform" + cp -R "./build_$platform/lib/hermesvm.framework"* "destroot/Library/Frameworks/$platform" + done + + cp "./build_macosx/bin/"* "destroot/bin" + + # Copy over Hermes and JSI API headers. + mkdir -p destroot/include/hermes/Public + cp public/hermes/Public/*.h destroot/include/hermes/Public + + mkdir -p destroot/include/hermes + cp API/hermes/*.h destroot/include/hermes + + mkdir -p destroot/include/hermes/cdp + cp API/hermes/cdp/*.h destroot/include/hermes/cdp + + mkdir -p destroot/include/jsi + cp "$JSI_PATH"/jsi/*.h destroot/include/jsi +} + +# Accepts an array of frameworks and will place all of +# the architectures into an universal folder and then remove +# the merged frameworks from destroot +function create_universal_framework { + pushd "$HERMES_PATH/destroot/Library/Frameworks" > /dev/null || exit 1 + + local platforms=("$@") + local args="" + + echo "Creating universal framework for platforms: ${platforms[*]}" + + for i in "${!platforms[@]}"; do + local platform="${platforms[$i]}" + local hermes_framework_path="${platform}/hermesvm.framework" + args+="-framework $hermes_framework_path " + done + + mkdir -p universal + # shellcheck disable=SC2086 + if xcodebuild -create-xcframework $args -output "universal/hermesvm.xcframework" + then + # # Remove the thin iOS hermesvm.frameworks that are now part of the universal + # XCFramework + for platform in "${platforms[@]}"; do + rm -r "$platform" + done + fi + + popd > /dev/null || exit 1 +} diff --git a/utils/build-ios-framework-rn.sh b/utils/build-ios-framework-rn.sh new file mode 100755 index 00000000000..d6faccbfd3c --- /dev/null +++ b/utils/build-ios-framework-rn.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +if [ "$CI" ]; then + set -x +fi +set -e + +# Given a specific target, retrieve the right architecture for it +# $1 the target you want to build. Allowed values: iphoneos, iphonesimulator, catalyst, xros, xrsimulator +function get_architecture { + if [[ $1 == "iphoneos" || $1 == "xros" ]]; then + echo "arm64" + elif [[ $1 == "iphonesimulator" || $1 == "xrsimulator" ]]; then + echo "x86_64;arm64" + elif [[ $1 == "appletvos" ]]; then + echo "arm64" + elif [[ $1 == "appletvsimulator" ]]; then + echo "x86_64;arm64" + elif [[ $1 == "catalyst" ]]; then + echo "x86_64;arm64" + else + echo "Error: unknown architecture passed $1" + exit 1 + fi +} + +function get_deployment_target { + if [[ $1 == "xros" || $1 == "xrsimulator" ]]; then + echo "$(get_visionos_deployment_target)" + else # tvOS and iOS use the same deployment target + echo "$(get_ios_deployment_target)" + fi +} + +# build a single framework +# $1 is the target to build +function build_framework { + if [ ! -d destroot/Library/Frameworks/universal/hermesvm.xcframework ]; then + deployment_target=$(get_deployment_target "$1") + + architecture=$(get_architecture "$1") + + build_apple_framework "$1" "$architecture" "$deployment_target" + else + echo "Skipping; Clean \"destroot\" to rebuild". + fi +} + +# group the frameworks together to create a universal framework +function build_universal_framework { + if [ ! -d destroot/Library/Frameworks/universal/hermesvm.xcframework ]; then + create_universal_framework "iphoneos" "iphonesimulator" "catalyst" "xros" "xrsimulator" "appletvos" "appletvsimulator" + else + echo "Skipping; Clean \"destroot\" to rebuild". + fi +} + +# single function that builds sequentially iphoneos, iphonesimulator and catalyst +# this is used to preserve backward compatibility +function create_framework { + if [ ! -d destroot/Library/Frameworks/universal/hermesvm.xcframework ]; then + build_framework "iphoneos" + build_framework "iphonesimulator" + build_framework "appletvos" + build_framework "appletvsimulator" + build_framework "catalyst" + build_framework "xros" + build_framework "xrsimulator" + build_universal_framework + else + echo "Skipping; Clean \"destroot\" to rebuild". + fi +} + + +CURR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +# shellcheck source=xplat/hermes/utils/build-apple-framework-rn.sh +source "${CURR_SCRIPT_DIR}/build-apple-framework-rn.sh" + +if [[ -z $1 ]]; then + create_framework +elif [[ $1 == "build_framework" ]]; then + build_universal_framework +else + build_framework "$1" +fi diff --git a/utils/build-mac-framework-rn.sh b/utils/build-mac-framework-rn.sh new file mode 100755 index 00000000000..05d46640958 --- /dev/null +++ b/utils/build-mac-framework-rn.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +if [ "$CI" ]; then + set -x +fi +set -e + +CURR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +# shellcheck source=xplat/hermes/utils/build-apple-framework-rn.sh +source "${CURR_SCRIPT_DIR}/build-apple-framework-rn.sh" + +if [ ! -d destroot/Library/Frameworks/macosx/hermesvm.framework ]; then + mac_deployment_target=$(get_mac_deployment_target) + + build_apple_framework "macosx" "x86_64;arm64" "$mac_deployment_target" +else + echo "Skipping; Clean \"destroot\" to rebuild". +fi diff --git a/utils/scripts/hermes/.gitignore b/utils/scripts/hermes/.gitignore new file mode 100644 index 00000000000..3c3629e647f --- /dev/null +++ b/utils/scripts/hermes/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/utils/scripts/hermes/consts.js b/utils/scripts/hermes/consts.js new file mode 100644 index 00000000000..09cca983df8 --- /dev/null +++ b/utils/scripts/hermes/consts.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +const path = require('path'); + +/** + * The absolute path to the repo root. + */ +const REPO_ROOT /*: string */ = path.resolve(__dirname, '../../..'); + +/** + * The absolute path to the android directory. + */ +const ANDROID_DIR /*: string */ = path.join(REPO_ROOT, 'android'); + +module.exports = { + REPO_ROOT, + ANDROID_DIR, +}; diff --git a/utils/scripts/hermes/create-tarball.js b/utils/scripts/hermes/create-tarball.js new file mode 100644 index 00000000000..ba5e40e3126 --- /dev/null +++ b/utils/scripts/hermes/create-tarball.js @@ -0,0 +1,75 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +const { createHermesPrebuiltArtifactsTarball } = require("./hermes-utils"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +/** + * This script creates a Hermes prebuilt artifacts tarball. + * Must be invoked after Hermes has been built. + */ +const yargs = require("yargs"); + +let argv = yargs + .option("i", { + alias: "inputDir", + describe: "Path to directory where Hermes build artifacts were generated.", + }) + .option("b", { + alias: "buildType", + type: "string", + describe: "Specifies whether Hermes was built for Debug or Release.", + default: "Debug", + }) + .option("o", { + alias: "outputDir", + describe: "Location where the tarball will be saved to.", + }) + .option("exclude-debug-symbols", { + describe: "Whether dSYMs should be excluded from the tarball.", + type: "boolean", + default: true, + }).argv; + +async function main() { + // $FlowFixMe[prop-missing] + const hermesDir = argv.inputDir; + // $FlowFixMe[prop-missing] + const buildType = argv.buildType; + // $FlowFixMe[prop-missing] + const excludeDebugSymbols = argv.excludeDebugSymbols; + // $FlowFixMe[prop-missing] + let tarballOutputDir = argv.outputDir; + + if (!tarballOutputDir) { + try { + tarballOutputDir = fs.mkdtempSync( + path.join(os.tmpdir(), "hermes-engine-tarball-") + ); + } catch (error) { + throw new Error( + `[Hermes] Failed to create temporary output directory: ${error}` + ); + } + } + + const tarballOutputPath = createHermesPrebuiltArtifactsTarball( + hermesDir, + buildType, + tarballOutputDir, + excludeDebugSymbols + ); + console.log(tarballOutputPath); + return tarballOutputPath; +} + +void main().then(() => { + process.exit(0); +}); diff --git a/utils/scripts/hermes/hermes-utils.js b/utils/scripts/hermes/hermes-utils.js new file mode 100644 index 00000000000..9072dce2d08 --- /dev/null +++ b/utils/scripts/hermes/hermes-utils.js @@ -0,0 +1,131 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; + +const { spawnSync } = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +/** + * Delegate execution to the supplied command. + * + * @param command Path to the command. + * @param args Array of arguments pass to the command. + * @param options child process options. + */ +function delegateSync( + command /*: string */, + args /*: (Array | child_process$spawnSyncOpts) */, + options /*: ?child_process$spawnSyncOpts */ +) { + return spawnSync(command, args, { stdio: "inherit", ...options }); +} + +function getHermesPrebuiltArtifactsTarballName( + buildType /*:: ?: string */ +) /*: string */ { + if (buildType == null) { + throw Error("Did not specify build type."); + } + return `hermes-ios-${buildType.toLowerCase()}.tar.gz`; +} + +/** + * Creates a tarball with the contents of the supplied directory. + */ +function createTarballFromDirectory( + directory /*: string */, + filename /*: string */ +) { + const args = ["-C", directory, "-czvf", filename, "."]; + delegateSync("tar", args); +} + +function createHermesPrebuiltArtifactsTarball( + hermesDir /*: string */, + buildType /*: string */, + tarballOutputDir /*: string */, + excludeDebugSymbols /*: boolean */ +) /*: string */ { + validateHermesFrameworksExist(path.join(hermesDir, "destroot")); + + if (!fs.existsSync(tarballOutputDir)) { + fs.mkdirSync(tarballOutputDir, { recursive: true }); + } + + let tarballTempDir; + try { + tarballTempDir = fs.mkdtempSync( + path.join(os.tmpdir(), "hermes-engine-destroot-") + ); + + let args = ["-a"]; + if (excludeDebugSymbols) { + args.push("--exclude=dSYMs/"); + args.push("--exclude=*.dSYM/"); + } + args.push("./destroot"); + args.push(tarballTempDir); + delegateSync("rsync", args, { + cwd: hermesDir, + }); + if (fs.existsSync(path.join(hermesDir, "LICENSE"))) { + delegateSync("cp", ["LICENSE", tarballTempDir], { cwd: hermesDir }); + } + } catch (error) { + throw new Error(`Failed to copy destroot to tempdir: ${error}`); + } + + const tarballFilename = path.join( + tarballOutputDir, + getHermesPrebuiltArtifactsTarballName(buildType) + ); + + try { + createTarballFromDirectory(tarballTempDir, tarballFilename); + } catch (error) { + throw new Error(`[Hermes] Failed to create tarball: ${error}`); + } + + if (!fs.existsSync(tarballFilename)) { + throw new Error( + `Tarball creation failed, could not locate tarball at ${tarballFilename}` + ); + } + + return tarballFilename; +} + +function validateHermesFrameworksExist(destrootDir /*: string */) { + if ( + !fs.existsSync( + path.join(destrootDir, "Library/Frameworks/macosx/hermesvm.framework") + ) + ) { + throw new Error( + "Error: Hermes macOS Framework not found. Are you sure Hermes has been built?" + ); + } + if ( + !fs.existsSync( + path.join( + destrootDir, + "Library/Frameworks/universal/hermesvm.xcframework" + ) + ) + ) { + throw new Error( + "Error: Hermes iOS XCFramework not found. Are you sure Hermes has been built?" + ); + } +} + +module.exports = { + createHermesPrebuiltArtifactsTarball, +}; diff --git a/utils/scripts/hermes/package.json b/utils/scripts/hermes/package.json new file mode 100644 index 00000000000..04d71688253 --- /dev/null +++ b/utils/scripts/hermes/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "yargs": "^17.6.2", + "shelljs": "^0.8.5" + } +} diff --git a/utils/scripts/hermes/publish-artifacts.js b/utils/scripts/hermes/publish-artifacts.js new file mode 100644 index 00000000000..1a76d3026d2 --- /dev/null +++ b/utils/scripts/hermes/publish-artifacts.js @@ -0,0 +1,124 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +/*:: +import type {BuildType} from './version-utils'; +*/ + +const { + getVersion, + updateGradlePropertiesFile, + validateBuildType, +} = require('./version-utils'); +const {echo, exec, exit, popd, pushd} = require('shelljs'); +const yargs = require('yargs'); + +async function main() { + const argv = yargs + .option('t', { + alias: 'builtType', + describe: 'The type of build you want to perform.', + choices: ['dry-run', 'commitly', 'release'], + default: 'dry-run', + }) + .strict().argv; + + // $FlowFixMe[prop-missing] + const buildType = argv.builtType; + + if (!validateBuildType(buildType)) { + throw new Error(`Unsupported build type: ${buildType}`); + } + + await publishArtifacts(buildType); +} + +async function publishArtifacts(buildType /*: BuildType */) { + if (buildType === 'dry-run') { + console.log('Skipping publishing artifacts because --dry-run is set.'); + return; + } + + const version = getVersion(buildType); + + updateGradlePropertiesFile(version); + pushd('android'); + await publishAndroidArtifactsToMaven(version, buildType); + await publishExternalArtifactsToMaven(version, buildType); + popd(); +} + +function publishAndroidArtifactsToMaven( + releaseVersion /*: string */, + buildType /*: BuildType */, +) { + // We want to gate ourselves against accidentally publishing a 1.x or a 1000.x on + // maven central which will break the semver for our artifacts. + if (buildType === 'release' && releaseVersion.startsWith('2.')) { + // -------- For stable releases, we also need to close and release the staging repository. + if ( + exec('./gradlew publishAndroidToSonatype closeSonatypeStagingRepository') + .code + ) { + echo( + 'Failed to close and release the staging repository on Sonatype (Maven Central) for Android artifacts', + ); + exit(1); + } + } else { + echo( + 'Nothing to do as this is not a stable release - Commitlies Android artifacts are published by build_android', + ); + } + + echo('Finished publishing Android artifacts to Maven Central'); +} + +function publishExternalArtifactsToMaven( + releaseVersion /*: string */, + buildType /*: BuildType */, +) { + // We want to gate ourselves against accidentally publishing a 1.x or a 1000.x on + // maven central which will break the semver for our artifacts. + if (buildType === 'release' && releaseVersion.startsWith('2.')) { + // -------- For stable releases, we do the publishing and close the staging repository. + // This can't be done earlier in build_android because this artifact are partially built by the iOS jobs. + if ( + exec( + './gradlew :ios-artifacts:publishToSonatype closeSonatypeStagingRepository', + ).code + ) { + echo( + 'Failed to close and release the staging repository on Sonatype (Maven Central) for external artifacts', + ); + exit(1); + } + } else { + const isSnapshot = buildType === 'commitly'; + // -------- For commitly releases, we only need to publish the snapshot to Sonatype snapshot repo. + if ( + exec( + './gradlew :ios-artifacts:publishToSonatype -PisSnapshot=' + + isSnapshot.toString(), + ).code + ) { + echo('Failed to publish external artifacts to Sonatype (Maven Central)'); + exit(1); + } + } + + echo('Finished publishing external artifacts to Maven Central'); +} + +if (require.main === module) { + void main(); +} diff --git a/utils/scripts/hermes/scm-utils.js b/utils/scripts/hermes/scm-utils.js new file mode 100644 index 00000000000..c26faeb93f2 --- /dev/null +++ b/utils/scripts/hermes/scm-utils.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const {echo, exec} = require('shelljs'); + +function getCurrentCommit() /*: string */ { + return isGitRepo() + ? exec('git rev-parse HEAD', { + silent: true, + }).stdout.trim() + : 'TEMP'; +} + +function isGitRepo() /*: boolean */ { + try { + return ( + exec('git rev-parse --is-inside-work-tree', { + silent: true, + }).stdout.trim() === 'true' + ); + } catch (error) { + echo( + `It wasn't possible to check if we are in a git repository. Details: ${error}`, + ); + } + return false; +} + +module.exports = { + getCurrentCommit, + isGitRepo, +}; diff --git a/utils/scripts/hermes/set-artifacts-version.js b/utils/scripts/hermes/set-artifacts-version.js new file mode 100644 index 00000000000..f68599283f6 --- /dev/null +++ b/utils/scripts/hermes/set-artifacts-version.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +/*:: +import type {BuildType, Version} from './version-utils'; +*/ + +const {ANDROID_DIR} = require('./consts'); +const { + getVersion, + validateBuildType, + updateGradlePropertiesFile, +} = require('./version-utils'); +const {promises: fs} = require('fs'); +const path = require('path'); +const {parseArgs} = require('util'); + +const config = { + options: { + 'build-type': { + type: 'string', + short: 'b', + }, + help: {type: 'boolean'}, + }, +}; + +async function main() { + const { + values: {'build-type': buildType, help}, + /* $FlowFixMe[incompatible-call] Natural Inference rollout. See + * https://fburl.com/workplace/6291gfvu */ + } = parseArgs(config); + + if (help) { + console.log(` + Usage: node ./utils/scripts/hermes/set-artifacts-version.js [OPTIONS] + + Updates version in gradle.proparties file. + + Options: + --build-type One of ['dry-run', 'commitly', 'release']. + `); + return; + } + + if (!validateBuildType(buildType)) { + throw new Error(`Unsupported build type: ${buildType}`); + } + + const version = await getVersion(buildType); + await updateGradlePropertiesFile(version); +} + +if (require.main === module) { + void main(); +} diff --git a/utils/scripts/hermes/version-utils.js b/utils/scripts/hermes/version-utils.js new file mode 100644 index 00000000000..d51c9ad6013 --- /dev/null +++ b/utils/scripts/hermes/version-utils.js @@ -0,0 +1,98 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +/*:: +export type BuildType = 'dry-run' | 'release'; +export type Version = { + version: string, + major: string, + minor: string, + patch: string, + prerelease: ?string, +} +*/ + +const {REPO_ROOT} = require('./consts'); +const {ANDROID_DIR} = require('./consts'); +const {getCurrentCommit} = require('./scm-utils'); +const {promises: fs} = require('fs'); +const path = require('path'); + +const CMAKE_FILE_PATH = `${REPO_ROOT}/CMakeLists.txt`; +const GRADLE_FILE_PATH = path.join(ANDROID_DIR, 'gradle.properties'); +const VERSION_REGEX = + /project\(Hermes\s+VERSION\s+(\d+\.\d+\.\d+)\s+LANGUAGES\s+C\s+CXX\)/; + +function validateBuildType( + buildType /*: string */, + // $FlowFixMe[incompatible-type-guard] +) /*: buildType is BuildType */ { + const validBuildTypes = new Set(['release', 'commitly', 'dry-run']); + + // $FlowFixMe[incompatible-return] + // $FlowFixMe[incompatible-type-guard] + return validBuildTypes.has(buildType); +} + +// commitly version: 0.x.y-commitly-- +async function getVersion(buildType /*: BuildType */) /*: Promise */ { + const currentCommit = getCurrentCommit(); + const shortCommit = currentCommit.slice(0, 9); + + if (buildType === 'dry-run') { + return `2.0.0-${shortCommit}`; + } + + const mainVersion = await getMainVersion(); + if (buildType === 'commitly') { + const date = new Date(); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + const dateIdentifier = + date.toISOString().slice(0, -14).replace(/[-]/g, '') + + `${hours}${minutes}`; + return `${mainVersion}-commitly-${dateIdentifier}-${shortCommit}`; + } + + // release + return mainVersion; +} + +async function getMainVersion() /*: Promise */ { + const cmakeContent = await fs.readFile(CMAKE_FILE_PATH, 'utf8'); + const versionMatch = extractMatchIfValid(cmakeContent); + const [, version] = versionMatch; + return version; +} + +function extractMatchIfValid(cmakeContent /*: string */) { + const match = cmakeContent.match(VERSION_REGEX); + if (!match) { + throw new Error('Could not find version in CMakeLists.txt'); + } + return match; +} + +async function updateGradlePropertiesFile( + version /*: string */, +) /*: Promise */ { + const contents = await fs.readFile(GRADLE_FILE_PATH, 'utf-8'); + + return fs.writeFile( + GRADLE_FILE_PATH, + contents.replace(/^VERSION_NAME=.*/, `VERSION_NAME=${version}`), + ); +} + +module.exports = { + validateBuildType, + getVersion, + updateGradlePropertiesFile, +}; diff --git a/utils/scripts/hermes/yarn.lock b/utils/scripts/hermes/yarn.lock new file mode 100644 index 00000000000..057d2fbdc76 --- /dev/null +++ b/utils/scripts/hermes/yarn.lock @@ -0,0 +1,240 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob@^7.0.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve@^1.1.6: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.6.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1"