Skip to content

Commit

Permalink
Merge branch 'main' into feat/android_skew
Browse files Browse the repository at this point in the history
  • Loading branch information
xxrlzzz committed Feb 17, 2024
2 parents faa408e + a9dd43c commit 74cc237
Show file tree
Hide file tree
Showing 243 changed files with 14,805 additions and 6,330 deletions.
169 changes: 168 additions & 1 deletion .circleci/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,172 @@
# Circle CI

This directory is home to the Circle CI configuration file. Circle is our continuous integration service provider. You can see the overall status of React Native's builds at https://circleci.com/gh/facebook/react-native
This directory is home to the Circle CI configuration files. Circle is our continuous integration service provider. You can see the overall status of React Native's builds at https://circleci.com/gh/facebook/react-native

You may also see an individual PR's build status by scrolling down to the Checks section in the PR.

## Purposes

We use CircleCI for mainly 3 purposes:

1. Testing changes
2. Release Nightlies
3. Release Stable Versions of React Native

When testing changes, we run all the tests on commits that lands on `main`. For commits in PR, we try to understand which kind of changes the PR is about and we try to selectively run only the relevant tests. so, for example, if a PR only touches iOS files, we are going to run only iOS tests.

A Nighly job runs every day at around 9:00 PM, GMT. They run from `main` and they publish a version of React Native using the current state of the codebase, creating a version number that follows the format: `0.<current-version+1>.0-nightly-<YYYYMMDD>-<short-commit-hash>`.
The nightly job also publish all the monorepo packages, taking care of updating the transitive dependencies of those packages.

Stable versions are released manually by the Release Crew and they run from a stable branch. Stable branches have the shape of `0.<version>-stable`.

## How It Works?

CircleCI execution is now split in two steps:
- Setup
- Testing

The setup step takes care of analyzing the changes in the PR and of deciding which jobs needs to run.

The testing flow is a set of workflows that executes the required tests.

### Setup

The code of the setup workflow lives in the root [`config.yml`](https://github.com/facebook/react-native/blob/main/.circleci/config.yml) file.
It uses the `Continuation orb` from CircleCI to start a CI flow that depends on the changes present in the PR.

If the changes are not coming from a PR (either a simple commit or if the CI is running on main) **we always run all the tests** as a cautionary measure.

The setup job has also to expose all the pipeline parameters that we would need to pass to the actual workflow. Those parameters are **automatically forwarded** to the workflows that are started as a result of the setup.

The setup job uses a JS script to carry on its logic. The [`pipeline_selection.js`](https://github.com/facebook/react-native/blob/main/scripts/circleci/pipeline_selection.js) script can be invoked with two commands:
- `filter-jobs`
- `create-configs`

The **`filter-jobs`** command takes care of creating a JSON representation of the tests we need to run based on the changes in the PR.

The **`create-configs`** command consumes the JSON representation to create a CircleCI configuration that can then executes all the required tests.

#### Creating a Configuration

To create a configuration, the `pipeline-selection` scripts collates together various pieces of `YML` files that lives in the [`Configurations` folder](https://github.com/facebook/react-native/tree/main/.circleci/configurations).

The order in which these files are appended is **important** and it always contains the following.:

1. `top_level.yml`: this file contains some high level directives for CircleCI, like the version, the list of orbs, the cache-keys, and the pipeline parameters that can be used by the workflows.
2. `executors.yml`: this file contains the list of the executors used in our jobs and their configurations.
3. `commands.yml`: this file contains all the commands that can be used by jobs to executes. Commands are reusable functions that are shared by multiple jobs.
4. `jobs.yml`: this file contains the jobs that are used by workflows to carry on some specific tasks. They are composed of sequential commands.
5. `workflows.yml`: this file contains the shared workflows that needs to (or can) be always executed, no matter which kind of changes are pushed to CI. An example of these workflows is `analysis` (which is always executed) or `nightly` (which can be executed if a specific pipeline parameter is passed to the CI).

Then, the `pipeline_selection create-configs` attach some specific test workflows, depending on the changes that are present in the PR. These change-dependent workflows live in the [`test_workflows`](https://github.com/facebook/react-native/tree/main/.circleci/configurations/test_workflows) folder.
These workflows are:
* `testAll.yml` => runs all the possible tests. This workflow is executed on main and on PRs which change set touches both iOS and Android
* `testAndroid.yml` => runs all the build steps and Android tests. This is used on changes that happens on the Android codebase and infra (`ReactAndroid` folder)
* `testIOS.yml` => runs all the build steps and iOS tests. This is used on changes that happens on the iOS codebase and infra (`React` folder)
* `testE2E.yml` => runs the E2E tests. As of today, E2E tests can be triggered if the commit message contains the `#run-e2e-tests` tag.
* `testJS.yml` => For all the changes that do not touch native/platform code, we only run JS tests.

Notice that if there are changes on files that do not represents code (for example `.md` files like this one or the `Changelog`) we don't run any CI.

## Test workflows

The test workflows for native code are composed of 2 parts:
- building React Native
- testing

Building React Native requires us to build several parts of it:
1. We need to build the Hermes JS engine
2. We need to build Android to create prebuilds
3. We need to package everything in an npm package that will mimic a React native release
4. We need to create a local maven repository

### Building Hermes Engine

#### Android
The `build_android` workflows takes care of building the Android version of Hermes and to put it properly in a local maven repository.
See the [Build Android](#build_android) section below.

#### iOS
Hermes is a very complicated item to build for iOS.
It is composed of the Hermes compiler (HermesC) and of the actual engine.

Hermes is shipped as a universal XCFramework. This means that we need to build all the architecture slices and then put them together in the XCFramework archive.
We also need to build 2 configurations: Debug and Release.

In order to be efficient and to save costs, we parallelize the process as much as possible:

1. We prepare the environment for building Hermes.
2. We build HermesC which is required by all the slices.
3. We start 8 jobs to build all the required slices in parallel:
1. `iphone` slice, Debug mode
1. `iphonesimulator` slice, Debug mode
1. `macos` slice, Debug mode
1. `catalyst` slice, Debug mode
1. `iphone` slice, Release mode
1. `iphonesimulator` slice, Release mode
1. `macos` slice, Release mode
1. `catalyst` slice, Release mode
4. We then have 2 jobs to create the Debug and Release tarballs in parallel.
1. The Debug job receives the 4 Debug slices
1. The Release job receives the 4 Release slices

The `Debug` and `Release` tarball are then uploaded as artifacts. Notice that these we use these artifacts to **test the release** of React Native.

While building Hermes, we take also care of building the dSYMs. A dSYM (Debug Symbols) is an archive that contains the Debug Symbols that users can load to de-symbolicate the Hermes Stack traces. These symbols are published when we create a React Native release.

A lot of these build steps are automated by some shell scripts that lives in the [`react-native/packages/react-native/sdks/hermes-engine/utils` folder](https://github.com/facebook/react-native/tree/main/packages/react-native/sdks/hermes-engine/utils).

### Build Android

The android build is all managed by Gradle, so building android should be as easy as calling a [`gradle` command](https://github.com/facebook/react-native/blob/main/.circleci/configurations/jobs.yml#L268-L274).

The relevant part here is that the build android generates a `maven-local` repository that is passed to the [`build_npm_package`](https://github.com/facebook/react-native/blob/main/.circleci/configurations/jobs.yml#L1182) and that we use to test the releases.

### Build NPM package

This job is the responsible to create an NPM package that is suitable to be released or tested in CI.
If we are in a release flow (for example the Nightly workflow), it also proceed with the publication.

The job can be invoked with different parameters:
- `dry-run` => it does not publish anything, but prepare the artifacts to be used for testing
- `nightly` => it creates the artifacts and publish a nightly version of React Native.
- `release` => it creates the artifacts and publish a stable version of React Native.

The build NPM package takes all the artifacts produced in the previous steps (iOS' Hermes, iOS' Hermes dSYMs, Android's `maven-local`) and creates an npm package packing all the code.

If in a release mode, it also proceed publishing the NPM package to NPM, and the artifacts to Maven central, which we use to distribute all the artifacts.

This job also uploads the `maven-local` repository and a zipped version of the npm package to CircleCI's artifacts. We use these artifacts to **test the release** of React Native.

## Testing React Native
React Native tests runs in two different scenarios:
- RNTester
- A New App

### RNTester
RNTester is our internal testing app. It is a fully working React Native app that lives in the [`react-native/packages/rn-tester` folder](https://github.com/facebook/react-native/tree/main/packages/rn-tester) of the repository.
RNTester is an app which contains code that exercise most part of the React Native frameworks.
It also has the feature of building React Native **from source**. For that reason, it does not have to wait for the NPM package to be ready, but RNTester's tests can start as soon as the `build_android` step and the step that builds Hermes for iOS are done.

Notice the Tests on RNTester for iOS consumes the Hermes engine that is built in the previous steps.

For Android, these tests creates an APK that is uploaded as an artifact in CircleCI. We use these artifacts to **test the releases** of React Native..

### A New App
The React Native repo contains a template app in the [`react-native/packages/react-native/template` folder]() that is used to spin up a new application that is preconfigured with React Native.

We have several tests that we run starting from the template, testing various configurations:
- Debug/Release
- JSC/Hermes (two different JS engine we support)
- New/Old Architecture (two different Architectures for React Native)

We want to test all the React Native changes against the template, but we can't publish a React native version on each change that is merged. Therefore, to run tests on the template we use a NPM registry proxy called [Verdaccio](https://verdaccio.org/).

When running a Template test our CI follows roughly these steps:
1. Prepare the executor
2. Start a Verdaccio server
3. Publish on Verdaccio all the monorepo [packages](https://github.com/facebook/react-native/tree/main/packages) on which React Native depends on.
4. Publish on Verdaccio the react-native NPM package that has been created in the NPM step
5. Spin up a new React native apps from the template, downloading react-native from Verdaccio.

In this way, we are sure that we can test all the changes that happen in React Native on a new React Native app.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
value: << pipeline.git.branch >>
steps:
- run:
name: "[Main or Stable] Create input fo config to test everything"
name: "[Main or Stable] Create input for config to test everything"
command: |
mkdir -p /tmp/circleci/
echo '{ "run_all": true }' > /tmp/circleci/pipeline_config.json
Expand Down
21 changes: 0 additions & 21 deletions .circleci/configurations/commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,6 @@ commands:
echo "rbenv found; Skipping installation"
fi
xcode=$(xcodebuild -version | cut -d' ' -f2 | head -n 1)
# For some reason, the Xcode 14.1 machnine is already properly configured.
if [[ $xcode != '14.1' ]]; then
brew reinstall libyaml
gem install psych -- --with-libyaml-dir=$(brew --prefix libyaml)
export RUBY_CONFIGURE_OPTS=--with-libyaml-dir=$(brew --prefix libyaml)
fi
# Install the right version of ruby
if [[ -z "$(rbenv versions | grep << parameters.ruby_version >>)" ]]; then
# ensure that `ruby-build` can see all the available versions of Ruby
Expand Down Expand Up @@ -84,18 +75,6 @@ commands:
type: string

steps:
- run:
name: Move to Node 18 if Needed
command: |
node_version=$(node -v)
major_version=$(echo $node_version | cut -c 2- | cut -d '.' -f 1)
if [[ $major_version < 18 ]]; then
nvm install 18.18.0
nvm alias default 18.18.0
else
echo 'We have the right node version!'
fi
- restore_cache:
keys:
- << parameters.yarn_base_cache_key >>-{{ arch }}-{{ checksum "yarn.lock" }}
Expand Down
2 changes: 1 addition & 1 deletion .circleci/configurations/executors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ executors:
reactnativeios-lts:
<<: *defaults
macos:
xcode: '14.1.0'
xcode: '14.3.1'
resource_class: macos.x86.medium.gen2
environment:
- BUILD_FROM_SOURCE: true
18 changes: 7 additions & 11 deletions .circleci/configurations/test_workflows/testAll.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,17 @@
flavor: "Debug"
executor: reactnativeios-lts
- test_ios_template:
architecture: "OldArch"
requires:
- build_npm_package
matrix:
parameters:
architecture: ["NewArch", "OldArch"]
flavor: ["Debug", "Release"]
jsengine: ["Hermes", "JSC"]
use_frameworks: ["StaticLibraries", "DynamicFrameworks"]
exclude:
# This config is tested with Ruby 3.2.0. Let's not double test it.
- architecture: "NewArch"
flavor: "Debug"
- flavor: "Debug"
jsengine: "Hermes"
use_frameworks: "StaticLibraries"
- test_ios_rntester:
Expand All @@ -84,33 +83,30 @@
architecture: "NewArch"
executor: reactnativeios-lts
- test_ios_rntester:
architecture: "NewArch"
requires:
- build_hermes_macos
matrix:
parameters:
architecture: ["NewArch", "OldArch"]
jsengine: ["Hermes", "JSC"]
use_frameworks: ["StaticLibraries", "DynamicFrameworks"]
exclude:
# Tested by test_ios-Hermes
- architecture: "OldArch"
jsengine: "Hermes"
- jsengine: "Hermes"
use_frameworks: "StaticLibraries"
# Tested by test_ios-JSC
- architecture: "OldArch"
jsengine: "JSC"
- jsengine: "JSC"
use_frameworks: "StaticLibraries"
# Tested with Ruby 3.2.0, do not test this twice.
- architecture: "NewArch"
jsengine: "Hermes"
- jsengine: "Hermes"
use_frameworks: "StaticLibraries"
- test_ios_rntester:
run_unit_tests: true
architecture: "OldArch"
use_frameworks: "StaticLibraries"
ruby_version: "2.6.10"
requires:
- build_hermes_macos
matrix:
parameters:
architecture: ["OldArch", "NewArch"]
jsengine: ["Hermes", "JSC"]
18 changes: 7 additions & 11 deletions .circleci/configurations/test_workflows/testIOS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,17 @@
flavor: "Debug"
executor: reactnativeios-lts
- test_ios_template:
architecture: "OldArch"
requires:
- build_npm_package
matrix:
parameters:
architecture: ["NewArch", "OldArch"]
flavor: ["Debug", "Release"]
jsengine: ["Hermes", "JSC"]
use_frameworks: ["StaticLibraries", "DynamicFrameworks"]
exclude:
# Tested with Ruby 3.2.0, let's not double test this
- architecture: "NewArch"
flavor: "Debug"
- flavor: "Debug"
jsengine: "Hermes"
use_frameworks: "StaticLibraries"
- test_ios_rntester:
Expand All @@ -75,33 +74,30 @@
architecture: "NewArch"
executor: reactnativeios-lts
- test_ios_rntester:
architecture: "NewArch"
requires:
- build_hermes_macos
matrix:
parameters:
architecture: ["NewArch", "OldArch"]
jsengine: ["Hermes", "JSC"]
use_frameworks: ["StaticLibraries", "DynamicFrameworks"]
exclude:
# Tested by test_ios-Hermes
- architecture: "OldArch"
jsengine: "Hermes"
- jsengine: "Hermes"
use_frameworks: "StaticLibraries"
# Tested by test_ios-JSC
- architecture: "OldArch"
jsengine: "JSC"
- jsengine: "JSC"
use_frameworks: "StaticLibraries"
# Tested with Ruby 3.2.0, let's not double test this
- architecture: "NewArch"
jsengine: "Hermes"
- jsengine: "Hermes"
use_frameworks: "StaticLibraries"
- test_ios_rntester:
run_unit_tests: true
architecture: "OldArch"
use_frameworks: "StaticLibraries"
ruby_version: "2.6.10"
requires:
- build_hermes_macos
matrix:
parameters:
architecture: ["NewArch", "OldArch"]
jsengine: ["Hermes", "JSC"]
Loading

0 comments on commit 74cc237

Please sign in to comment.