From 649cd4f4c449713d937817c2c3800d9fdb9472ee Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 23 Aug 2023 16:49:33 -0700 Subject: [PATCH] Merge latest changes from RN 0.71.13 + a few cherry-picks (#1911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [LOCAL] update podlock post release to fix CI * Fix measurement of uncontrolled TextInput after edit Summary: D42721684 (https://github.com/facebook/react-native/commit/be69c8b5a77ae60cced1b2af64e48b90d9955be5) left a pretty bad bug when using Fabric for Android. I missed that in Fabric specifically, on edit we will cache the Spannable backing the EditText for use in future measurement. Because we've stripped the sizing spans, Spannable measurement has incorrect font size, and the TextInput size will change (collapsing) after the first edit. This effectively breaks any uncontrolled TextInput which does not have explicit dimensions set. Changelog: [Android][Fixed] - Fix measurement of uncontrolled TextInput after edit Reviewed By: sammy-SC Differential Revision: D43158407 fbshipit-source-id: 51602eab06c9a50e2b60ef0ed87bdb4df025e51e * Minimize EditText Spans 1/9: Fix precedence (#36543) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36543 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. We cache the backing EditText span on text change to later measure. To measure outside of a TextInput we need to restore any spans we removed. Spans may overlap, so base attributes should be behind everything else. The logic here for dealing with precedence is incorrect, and we should instead accomplish this by twiddling with the `SPAN_PRIORITY` bits. Changelog: [Android][Fixed] - Minimize Spans 1/N: Fix precedence Reviewed By: javache Differential Revision: D44240779 fbshipit-source-id: f731b353587888faad946b8cf1e868095cdeced3 * Minimize EditText Spans 2/9: Make stripAttributeEquivalentSpans generic (#36546) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36546 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This change generalizes `stripAttributeEquivalentSpans()` to allow plugging in different spans. Changelog: [Internal] Reviewed By: rshest Differential Revision: D44240781 fbshipit-source-id: 89005266020f216368e9ad9ce382699bd8db85a8 # Conflicts: # ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java * Minimize EditText Spans 3/9: ReactBackgroundColorSpan (#36547) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36547 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This adds `ReactBackgroundColorSpan` to the list of spans eligible to be stripped. Changelog: [Android][Fixed] - Minimize Spans 3/N: ReactBackgroundColorSpan Reviewed By: javache Differential Revision: D44240782 fbshipit-source-id: 2ded1a1687a41cf6d5f83e89ffadd2d932089969 * Minimize EditText Spans 4/9: ReactForegroundColorSpan (#36545) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36545 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This adds ReactForegroundColorSpan to the list of spans eligible to be stripped. Changelog: [Android][Fixed] - Minimize Spans 4/N: ReactForegroundColorSpan Reviewed By: javache Differential Revision: D44240780 fbshipit-source-id: d86939cc2d7ed9116a4167026c7d48928fc51757 * Minimize EditText Spans 5/9: Strikethrough and Underline (#36544) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36544 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This change makes us apply strikethrough and underline as paint flags to the underlying EditText, instead of just the spans. We then opt ReactUnderlineSpan and ReactStrikethroughSpan into being strippable. This does actually create visual behavior changes, where child text will inherit any underline or strikethrough of the root EditText (including if the child specifies `textDecorationLine: "none"`. The new behavior is consistent with both iOS and web though, so it seems like more of a bugfix than a regression. Changelog: [Android][Fixed] - Minimize Spans 5/N: Strikethrough and Underline Reviewed By: rshest Differential Revision: D44240778 fbshipit-source-id: d564dfc0121057a5e3b09bb71b8f5662e28be17e * Minimize EditText Spans 6/9: letterSpacing (#36548) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36548 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This change lets us set `letterSpacing` on the EditText instead of using our custom span. Changelog: [Android][Fixed] - Minimize EditText Spans 6/N: letterSpacing Reviewed By: rshest Differential Revision: D44240777 fbshipit-source-id: 9bd10c3261257037d8cacf37971011aaa94d1a77 * Minimize EditText Spans 7/9: Avoid temp list (#36576) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36576 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This change addresses some minor CR feedback and removes the temporary list of spans in favor of applying them directly. Changelog: [Internal] Reviewed By: javache Differential Revision: D44295190 fbshipit-source-id: bd784e2c514301d45d0bacd8ee6de5c512fc565c * Minimize EditText Spans 8/9: CustomStyleSpan (#36577) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36577 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. This change allows us to strip CustomStyleSpan. We already set all but `fontVariant` on the underlying EditText, so we just need to route that through as well. Note that because this span is non-parcelable, it is seemingly not subject to the buggy behavior on Samsung devices of infinitely cloning the spans, but non-parcelable spans have different issues on the devices (they disappear), so moving `fontVariant` to the top-level makes sense here. Changelog: [Android][Fixed] - Minimize EditText Spans 8/N: CustomStyleSpan Reviewed By: javache Differential Revision: D44297384 fbshipit-source-id: ed4c000e961dd456a2a8f4397e27c23a87defb6e * Mimimize EditText Spans 9/9: Remove addSpansForMeasurement() (#36575) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36575 This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior. D23670779 addedd a previous mechanism to add spans for measurement caching, like we needed to do as part of this change. It is called in more specific cases (e.g. when there is a text hint but no text), but it edits the live EditText spannable instead of the cache copy, and does not handle nested text at all. We are already adding spans back to the input after this, behind everything else, and can replace it with the code we have been adding. Changelog: [Android][Fixed] - Mimimize EditText Spans 9/9: Remove `addSpansForMeasurement()` Reviewed By: javache Differential Revision: D44298159 fbshipit-source-id: 1af44a39de7550b7e66e45db9ebc3523ae9ff002 # Conflicts: # ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java * Use RN Build Utils for Building Hermes Artifacts Summary: We moved Hermes some build utils from [Hermes repo](https://github.com/facebook/hermes/tree/main/utils) to [React Navtie repo](https://github.com/facebook/react-native/tree/main/sdks/hermes-engine/utils) a while ago to have more control over them. However some paths on the CI were not updated. We continued to use old build scripts for Hermes prebuilds. Some unfortunate side effects are: - `HERMES_ENABLE_DEBUGGER` is [hardcoded to true](https://github.com/facebook/hermes/blob/main/utils/build-apple-framework.sh#L65). That makes Hermes much slower in Release configuration. - BUILD_TYPE is [set to Release](https://github.com/facebook/hermes/blob/main/utils/build-apple-framework.sh#L10) instead of `MinSizeRel` which inreases Hermes binary size. This diff copies these build utils from RN to Hermes source directory before we perform Hermes build. Changelog [Internal] Reviewed By: cipolleschi Differential Revision: D44066721 fbshipit-source-id: f45ad6a31fb01c10199f69cc7bbcbbc83b793d34 * Fix TextView alignment being reset on state updates Summary: Changelog: [Android][Fixed] Resolved bug with Text components in new arch losing text alignment state. Reviewed By: mdvacca Differential Revision: D34108943 fbshipit-source-id: 3992e9406345be919b5e3595fc1f9e61cf67a699 # Conflicts: # ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java # ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java * [0.71.7] Bump version numbers * Do not send extra onChangeText even wnen instantianting multiline TextView (#36930) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36930 This diff fixes https://github.com/facebook/react-native/issues/36494 Now this code matches its [Fabric counterpart](https://github.com/facebook/react-native/commit/7b4889937ceb0eccdbb62a610b58525c29928be7). There is also one extra check that `_lastStringStateWasUpdatedWith != nil`. With that in place we don't send extra `onChangeText` event when `attributedText` is assigned for the first time on TextView construction. Changelog: [IOS][Fixed] - Do not send extra onChangeText even wnen instantianting multiline TextView Reviewed By: sammy-SC Differential Revision: D45049135 fbshipit-source-id: 62fa281308b9d2e0a807d024f08d8a214bd99b5e * Read Maven group from GROUP property (#37204) Summary: The [React Native TV repo](https://github.com/react-native-tvos/react-native-tvos) shares most of the same Android code as the core repo. Beginning in 0.71, it needs to also publish Android Maven artifacts for the `react-android` and `hermes-android` libraries. In order to avoid conflicts, it needs to publish the artifacts to a different group name. However, `react-native-gradle-plugin` uses a hardcoded group name (`com.facebook.react`). Solution: read the group name from the existing `GROUP` property in `ReactAndroid/gradle.properties`. ## Changelog: [Android] [Fixed] - read GROUP name in gradle-plugin dependency code Pull Request resolved: https://github.com/facebook/react-native/pull/37204 Test Plan: - Android unit tests have been added for the new code and new method in `DependencyUtils.kt`. - Existing tests should pass - The new code defaults to the correct group (`com.facebook.react`) so no functional change is expected in the core repo. Reviewed By: luluwu2032 Differential Revision: D45576700 Pulled By: cortinico fbshipit-source-id: 6297ab515b4bdbb17024989c7d3035b0a2ded0ae # Conflicts: # packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt * bumped packages versions #publish-packages-to-npm * [LOCAL] bump RNGP to 0.71.18 * [LOCAL] update podlock * [0.71.8] Bump version numbers * fix(types): cross platform autoComplete for TextInput (#36931) Summary: Since v0.71 the autoComplete prop on TextInput is available on iOS ([release notes](https://reactnative.dev/blog/2023/01/12/version-071#component-specific-behavior)). However, this change is not reflected in the types. Original types PR here - https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65144 by chwallen ## Changelog: [GENERAL] [FIXED] - Fix autoComplete type for TextInput Pull Request resolved: https://github.com/facebook/react-native/pull/36931 Test Plan: Setting the autoComplete prop on TextInput to `nickname`, `organization`, `organization-title`, or `url` should not result in typescript errors. Reviewed By: NickGerleman Differential Revision: D45052350 Pulled By: javache fbshipit-source-id: 40993833b4ed14f91e3bf3521a264ea93517a0c9 * Allow string `transform` style in TypeScript (#37569) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37569 Fixes https://github.com/facebook/react-native/issues/37543 Missed as part of D39423409 (or maybe we didn't have TS types inline yet?) Changelog: [General][Fixed] - Allow string `transform` style in TypeScript Reviewed By: cortinico Differential Revision: D46161450 fbshipit-source-id: 24ee9e19365b7209ec0a2c8fb5a5d7ac78203f4d * fix: [gradle-plugin] 3rd party lib dependency substitution (#37445) Summary: For 3rd party libraries to work with a React Native fork (such as the TV repo) that uses a different Maven group for `react-android` and `hermes-android` artifacts, an additional dependency substitution is required. ## Changelog: [Android][fixed] RNGP dependency substitutions for fork with different Maven group Pull Request resolved: https://github.com/facebook/react-native/pull/37445 Test Plan: - Manual tested with an existing project - Unit tests pass Reviewed By: rshest, dmytrorykun Differential Revision: D45948901 Pulled By: cortinico fbshipit-source-id: 4151a1d3616172a92c68812c3a0034c98b330d67 * fix: fix virtualizedList scrollToEnd for 0 items (#36067) Summary: Fixes https://github.com/facebook/react-native/issues/36066 ## Changelog [GENERAL] [FIXED] - VirtualizedList scrollToEnd with no data Pull Request resolved: https://github.com/facebook/react-native/pull/36067 Test Plan: Run `yarn test VirtualizedList-test` Reviewed By: jacdebug Differential Revision: D43041763 Pulled By: javache fbshipit-source-id: d4d5e871284708a89bf9911d82e9aa97d7625aca * [LOCAL] bump hermes version post release * Make CircleCI caches for hermesc be version dependent (#37452) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37452 Fixes #37428 We do have cache poisoning for hermesc on Windows and Linux due to reusing the same cache key among different React Native version. This fixes it by specifying a cache key which is version dependent + it invalidates the caches by defining a new key. Changelog: [Internal] [Fixed] - Make CircleCI caches for hermesc be version dependent Reviewed By: cortinico Differential Revision: D45909178 fbshipit-source-id: 830c87ae45739c7053342a68dac2ee7581945c1d * [0.71.9] Bump version numbers * bumped packages versions #publish-packages-to-npm * bump RNGP to 0.71.19 * [0.71.10] Bump version numbers * [LOCAL] update podlock to fix CI * Revert "fix: border width top/bottom not matching the border radius (#34362)" This reverts commit cd6a91343ee24af83c7437b3f2449b41e97760e9. * Fix invalid context cast in ReactRootView (#36121) Summary: Hi 👋 I'm one of the Expo contributors. When upgrading the Stripe dependency in Expo for SDK48 release on Android, we noticed the following error: ```java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity``` With Kudo we narrowed it down to an underlying issue in the following cast in `ReactRootView` done during checking for keyboard events: ```((Activity) getContext())``` The `getContext()` is actually a `ContextThemeWrapper` for `ExperienceActivity`, so we should not cast it to an `Activity` directly - instead, we unwrap it using `getBaseContext()`. Implementing the following fix into the Expo fork fixed the crash for us: https://github.com/expo/react-native/commit/0e2c9cada120a3709e8285ab4bcdaa1c5c446732 ## Changelog [ANDROID] [FIXED] - Fixed crash occurring in certain native views when handling keyboard events. Pull Request resolved: https://github.com/facebook/react-native/pull/36121 Test Plan: Tested manually by comparing two builds before and after this change. The main branch build crashes when the [Stripe 0.23.1](https://github.com/stripe/stripe-react-native) Card Element from the example app is mounted on screen. Applying the change fixed the issue. Happy to make a more isolated reproduction if necessary. --- Full stack trace: ``` AndroidRuntime D Shutting down VM E FATAL EXCEPTION: main E Process: host.exp.exponent, PID: 8849 E java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity E at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.checkForKeyboardEvents(ReactRootView.java:937) E at com.facebook.react.ReactRootView$CustomGlobalLayoutListener.onGlobalLayout(ReactRootView.java:913) E at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:1061) E at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3352) E at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2286) E at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8948) E at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1231) E at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239) E at android.view.Choreographer.doCallbacks(Choreographer.java:899) E at android.view.Choreographer.doFrame(Choreographer.java:832) E at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214) E at android.os.Handler.handleCallback(Handler.java:942) E at android.os.Handler.dispatchMessage(Handler.java:99) E at android.os.Looper.loopOnce(Looper.java:201) E at android.os.Looper.loop(Looper.java:288) E at android.app.ActivityThread.main(ActivityThread.java:7898) E at java.lang.reflect.Method.invoke(Native Method) E at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) E at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) ``` Reviewed By: cortinico Differential Revision: D43186530 Pulled By: javache fbshipit-source-id: 2143495f6b0c71f342eba6d5abb2bfa4a529fbdd * Prevent crash in runAnimationStep on OnePlus and Oppo devices (#37487) Summary: We've been encountering a crash in `runAnimationStep` with "Calculated frame index should never be lower than 0" https://github.com/facebook/react-native/issues/35766 with OnePlus/Oppo devices as well, but don't have one on hand to test. This just works around the issue: if the time is before the start time of an animation, we shouldn't do anything anyways, so we just log a message instead of throwing while in production. We still throw in debug mode though for easier debugging. ### Hypothesis of the root cause Based on stacktrace in https://github.com/facebook/react-native/issues/35766 (which is the same one we see) Normally, this should happen 1. Choreographer.java constructs a FrameDisplayEventReceiver 2. FrameDisplayEventReceiver.onVSync gets called, which sets the `mTimestampNanos` 3. FrameDisplayEventReceiver.run gets called, which then eventually calls our `doFrame` callback with `mTimestampNanos`. This then causes `FrameBasedAnimationDriver.runAnimationStep` to be called with the same timestamp I suspect what's happening on OnePlus devices is that the `onVSync` call either doesn't happen or happens rarely enough that the `mTimestampNanos` when `run` is called is sometime in the past ### Fix 1. Add logging so we get the parameters to debug more if we end up getting this error 2. In production, just ignore past times instead of throwing an Error ## Changelog: Pick one each for the category and type tags: [ANDROID] [FIXED] - Prevent crash on OnePlus/Oppo devices in runAnimationStep Pull Request resolved: https://github.com/facebook/react-native/pull/37487 Test Plan: Ran our app using patched version and verified no issues showed up when using it Reviewed By: cipolleschi Differential Revision: D46102968 Pulled By: cortinico fbshipit-source-id: bcb36a0c2aed0afdb8e7e68b141a3db4eb02695a * Use `Content-Location` header in bundle response as JS source URL (#37501) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37501 This is the iOS side of the fix for https://github.com/facebook/react-native/issues/36794. That issue aside for the moment, the high-level idea here is to conceptually separate the bundle *request URL*, which represents a request for the *latest* bundle, from the *source URL* passed to JS engines, which should represent the code actually being executed. In future, we'd like to use this to refer to a point-in-time snapshot of the bundle, so that stack traces more often refer to the code that was actually run, even if it's since been updated on disk (actually implementing this isn't planned at the moment, but it helps describe the distinction). Short term, this separation gives us a way to address the issue with JSC on iOS 16.4 by allowing Metro to provide the client with a [JSC-safe URL](https://github.com/react-native-community/discussions-and-proposals/pull/646) to pass to the JS engine, even where the request URL isn't JSC-safe. We'll deliver that URL to the client on HTTP bundle requests via the [`Content-Location`](https://www.rfc-editor.org/rfc/rfc9110#name-content-location) header, which is a published standard for communicating a location for the content provided in a successful response (typically used to provide a direct URL to an asset after content negotiation, but I think it fits here too). For the long-term goal we should follow up with the same functionality on Android and out-of-tree platforms, but it's non-essential for anything other than iOS 16.4 at the moment. For the issue fix to work end-to-end we'll also need to update Metro, but the two pieces are decoupled and non-breaking so it doesn't matter which lands first. Changelog: [iOS][Changed] Prefer `Content-Location` header in bundle response as JS source URL Reviewed By: huntie Differential Revision: D45950661 fbshipit-source-id: 170fcd63a098f81bdcba55ebde0cf3569dceb88d * [LOCAL] Make 0.70 compatible with Xcode 15 (thanks to @AlexanderEggers for the commit in main) * [LOCAL] Bump CLI to 10.2.4 and Metro to 0.73.10 * Add web compat unit tests and fixes (#35316) Summary: Adds compat with W3C logical CSS properties. See https://github.com/facebook/react-native/issues/34425 This is a replacement for reverted https://github.com/facebook/react-native/issues/34590, which can no longer be imported internally. [General][Added] - Added CSS logical properties. Pull Request resolved: https://github.com/facebook/react-native/pull/35316 Test Plan: Unit test snapshots. Reviewed By: NickGerleman Differential Revision: D41230978 Pulled By: necolas fbshipit-source-id: 40e93d0d697f0cb28390480ce2b4bcbce18da70a * resolve some merge conflicts * fix tests * [0.71.11] Bump version numbers * [LOCAL] update podlock for CI * fix(ios): fix `pod install --project-directory=ios` failing (#37993) * Prevent LogBox from crashing on long messages (#38005) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38005 Fixes https://github.com/facebook/react-native/issues/32093 by guarding the expensive `BABEL_CODE_FRAME_ERROR_FORMAT` regex with a cheaper initial scan. (Longer term, we should reduce our reliance on string parsing and propagate more structured errors.) Changelog: [General][Fixed] Prevent LogBox from crashing on very long messages Reviewed By: GijsWeterings Differential Revision: D46892454 fbshipit-source-id: 3afadcdd75969c2589bbb06f47d1c4c1c2690abd # Conflicts: # Libraries/LogBox/Data/parseLogBoxLog.js # packages/react-native/package.json * [0.71.12] Bump version numbers * [LOCAL] Bump podfile.lock * [LOCAL] Download artifacts from CI to speed up testing (#37971) (#38612) Co-authored-by: Riccardo Cipolleschi resolved: https://github.com/facebook/react-native/pull/37971 * fix: mount devtools overlay only if react devtools are connected (#38784) * Allow RCTBundleURLProvider to request an inline source map (#37878) (#38995) Summary: See: http://blog.nparashuram.com/2019/10/debugging-react-native-ios-apps-with.html When using direct debugging with JavaScriptCore, Safari Web Inspector doesn't pick up the source map over the network. Instead, as far as I can tell, it expects you to pass the source URL at the time you load your bundle: https://developer.apple.com/documentation/javascriptcore/jscontext/1451384-evaluatescript?language=objc . This leads to a very sub-par developer experience debugging the JSbundle directly. It will however, pick up an inline source map. Therefore, let's add a way to have React Native tell metro to request an inline source map. I did this by modifying `RCTBundleURLProvider` to have a new query parameter for `inlineSourceMap`, and set to true by default for JSC. [IOS] [ADDED] - Added support to inline the source map via RCTBundleURLProvider Pull Request resolved: https://github.com/facebook/react-native/pull/37878 Test Plan: I can put a breakpoint in RNTester, via Safari Web Inspector, in human readable code :D Screenshot 2023-06-14 at 4 09 03 AM Reviewed By: motiz88 Differential Revision: D46855418 Pulled By: huntie fbshipit-source-id: 2134cdbcd0a3e81052d26ed75f83601ae4ddecfe * chore(releases): improve bump oss script to allow less human errors (#38666) (#38890) Summary: One of the limitations of the existing flow for the release crew is that they need to manually remember to publish all the other packages in the monorepo ahead of a new patch release - this PR modifies the logic for the bump-oss-version script (and makes it available via yarn) so that it will not run if: * there are git changes lying around * if some of the packages need a new release it required a bit of refactoring to extract some portions of the logic from the bump-all-package-versions script, but I think the end result is pretty decent. ## Changelog: [INTERNAL] [CHANGED] - improve bump oss script to allow less human errors Pull Request resolved: https://github.com/facebook/react-native/pull/38666 Test Plan: * checkout this branch * comment L54 of bump-oss-version.js (to remove the check on the branch name) * run `yarn bump-all-updated-packages`, verify that it works and that it detects that some packages have unreleased code * run `yarn bump-oss-version -t asd -v asd` (the "fake" parameters are needed to pass the yargs check), verify that it will throw an error because it finds a package that has unreleased code Reviewed By: mdvacca Differential Revision: D48156963 Pulled By: cortinico fbshipit-source-id: 2473ad5a84578c5236c905fd9aa9a88113fe8d22 # Conflicts: # scripts/publish-npm.js re-add the file nit # Conflicts: # package.json * Add scripts and pipeline to poll for maven (#38980) (#39039) * Fix onChangeText not firing when clearing the value of TextInput with multiline=true on iOS (#37958) Summary: This fix fixes the TextInput issue in https://github.com/facebook/react-native/issues/37784 with onChangeText not working on iOS only. ## Changelog: [IOS] [FIXED] - Fix onChangeText not firing when clearing the value of TextInput with multiline=true on iOS Pull Request resolved: https://github.com/facebook/react-native/pull/37958 Test Plan: 1. Run the repro code given in the issue (https://github.com/facebook/react-native/issues/37784). 2. Verified that onChangeText works after pressing the submit button. 3. Run the repro code from the issue (https://github.com/facebook/react-native/issues/36494) that caused this issue. 4. Verified that issue (https://github.com/facebook/react-native/issues/36494) is not reoccurring. Reviewed By: rshest Differential Revision: D47185775 Pulled By: dmytrorykun fbshipit-source-id: 1a1a6534d6bf8b5bb8cf1090734dd894bab43f82 * For targeting SDK 34 - Added RECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED flag support in DevSupportManagerBase (#38256) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38256 Add RECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED flag support to DevSupportManagerBase for Android 14 change. See https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported for details. Without this fix, app crashes during launch because of : ```SecurityException: {package name here}: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts``` Changelog: [Targeting SDK 34] Added RECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED flag support in DevSupportManagerBase Reviewed By: javache Differential Revision: D47313501 fbshipit-source-id: 12e8299559d08b4ff87b4bdabb0a29d27763c698 * [LOCAL] skip un-semver packages in 71 branch * Revert "[LOCAL] skip un-semver packages in 71 branch" This reverts commit a90485a9576b2f4c7f28153e2eb096d09f6d7d37. * [LOCAL] augment forEachPackage to accept optional excludes and add for the bump and trigger release script * [0.71.13] Bump version numbers * Re-enable direct debugging with JSC on iOS 16.4+ (#37914) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37914 Restores https://github.com/facebook/react-native/pull/37874 (reverted earlier today), with fix for `JSCRuntime.cpp` build on Android. Changelog: None Reviewed By: cortinico Differential Revision: D46762984 fbshipit-source-id: 6d56f81b9d0c928887860993b2b729ed96c0734c * Guard `JSGlobalContextSetInspectable` behind a compile time check for Xcode 14.3+ (#39037) Summary: An earlier [change](https://github.com/facebook/react-native/commit/8b1bf058c4bcbf4e5ca45b0056217266a1ed870c) I made (that huntie resubmitted) only works on Xcode 14.3+ (See more info [here](https://github.com/react-native-community/discussions-and-proposals/discussions/687)). This change adds the appropriate compiler checks so that the change is compatible with Xcode 14.2 and earlier, and therefore cherry-pickable to 0.71 and 0.72. The check works by checking if iOS 16.4+ is defined, which is the closest proxy I could find for "Is this Xcode 14.3". ## Changelog: [IOS] [CHANGED] - Guard `JSGlobalContextSetInspectable` behind a compile time check for Xcode 14.3+ Pull Request resolved: https://github.com/facebook/react-native/pull/39037 Test Plan: I can't actually test on Xcode 14.2 (it won't launch on my MacBook 😢), but I made a similar [PR](https://github.com/microsoft/react-native-macos/pull/1848) in React Native macOS, whose CI checks run against Xcode 14.2 and I'm getting passing checks there. Reviewed By: huntie Differential Revision: D48414196 Pulled By: NickGerleman fbshipit-source-id: ba10a6505dd11d982cc56c02bf9f7dcdc104bbec * Fix some issues --------- Co-authored-by: Lorenzo Sciandra Co-authored-by: Nick Gerleman Co-authored-by: Lorenzo Sciandra Co-authored-by: Dmitry Rykun Co-authored-by: Pieter De Baets Co-authored-by: Distiller Co-authored-by: Douglas Lowder Co-authored-by: Distiller Co-authored-by: Kyle Roach Co-authored-by: Julien Brayere Co-authored-by: Riccardo Cipolleschi Co-authored-by: Distiller Co-authored-by: Distiller Co-authored-by: aleqsio Co-authored-by: Harry Yu Co-authored-by: Rob Hogan Co-authored-by: Nicolas Gallagher Co-authored-by: Alexander Eggers Co-authored-by: Distiller Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Co-authored-by: Moti Zilberman Co-authored-by: Distiller Co-authored-by: Riccardo Cipolleschi Co-authored-by: Riccardo Cipolleschi Co-authored-by: Ruslan Lesiutin Co-authored-by: Koichi Nagaoka Co-authored-by: Kun Wang Co-authored-by: Distiller Co-authored-by: Alex Hunt --- .circleci/config.yml | 25 +- Libraries/ReactNative/AppContainer.js | 34 +- React/Base/RCTBundleURLProvider.h | 41 ++- React/Base/RCTBundleURLProvider.mm | 98 ++++-- .../devsupport/DevSupportManagerBase.java | 20 +- ReactCommon/jsc/JSCRuntime.cpp | 10 + package.json | 4 +- .../RCTBundleURLProviderTests.m | 8 +- scripts/circle-ci-artifacts-utils.js | 198 +++++++++++ scripts/circleci/poll-maven.js | 72 ++++ .../bump-all-updated-packages/bump-utils.js | 49 +++ .../bump-all-updated-packages/index.js | 49 ++- scripts/monorepo/for-each-package.js | 12 +- scripts/npm-utils.js | 41 +++ scripts/publish-npm.js | 2 +- scripts/release-utils.js | 5 - scripts/test-e2e-local-clean.js | 4 +- scripts/test-e2e-local.js | 322 ++++++++++-------- scripts/testing-utils.js | 189 +++++++++- ...ion.js => trigger-react-native-release.js} | 86 ++++- scripts/update-template-package.js | 51 +++ yarn.lock | 2 +- 22 files changed, 1090 insertions(+), 232 deletions(-) create mode 100644 scripts/circle-ci-artifacts-utils.js create mode 100644 scripts/circleci/poll-maven.js create mode 100644 scripts/monorepo/bump-all-updated-packages/bump-utils.js create mode 100644 scripts/npm-utils.js rename scripts/{bump-oss-version.js => trigger-react-native-release.js} (63%) mode change 100755 => 100644 create mode 100644 scripts/update-template-package.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 931a29901819c9..8ec8f5050344df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -812,6 +812,10 @@ jobs: - report_bundle_size: platform: android + - store_artifacts: + path: ~/react-native/packages/rn-tester/android/app/build/outputs/apk/ + destination: rntester-apk + # Optionally, run disabled tests - when: condition: << parameters.run_disabled_tests >> @@ -1060,7 +1064,10 @@ jobs: - run: name: Display Environment info - command: npx envinfo@latest + command: | + npm install -g envinfo + envinfo -v + envinfo - restore_cache: keys: @@ -1562,6 +1569,19 @@ jobs: -d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${CIRCLE_TAG:1}\" }}" # END: Stable releases + poll_maven: + docker: + - image: cimg/node:current + resource_class: small + steps: + - checkout_code_with_cache + - run_yarn + - run: + name: Poll Maven for Artifacts + command: | + node scripts/circleci/poll-maven.js + + # ------------------------- # JOBS: Nightly # ------------------------- @@ -1873,6 +1893,9 @@ workflows: - build_hermesc_linux - build_hermes_macos - build_hermesc_windows + - poll_maven: + requires: + - build_and_publish_npm_package package_and_publish_release_dryrun: jobs: diff --git a/Libraries/ReactNative/AppContainer.js b/Libraries/ReactNative/AppContainer.js index cf3f1d0274e8e0..347bda2a2b4109 100644 --- a/Libraries/ReactNative/AppContainer.js +++ b/Libraries/ReactNative/AppContainer.js @@ -17,6 +17,8 @@ import {type EventSubscription} from '../vendor/emitter/EventEmitter'; import {RootTagContext, createRootTag} from './RootTag'; import * as React from 'react'; +const reactDevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; + type Props = $ReadOnly<{| children?: React.Node, fabric?: boolean, @@ -45,9 +47,17 @@ class AppContainer extends React.Component { }; _mainRef: ?React.ElementRef; _subscription: ?EventSubscription = null; + _reactDevToolsAgentListener: ?() => void = null; static getDerivedStateFromError: any = undefined; + mountReactDevToolsOverlays(): void { + const DevtoolsOverlay = require('../Inspector/DevtoolsOverlay').default; + const devtoolsOverlay = ; + + this.setState({devtoolsOverlay}); + } + componentDidMount(): void { if (__DEV__) { if (!this.props.internal_excludeInspector) { @@ -69,13 +79,21 @@ class AppContainer extends React.Component { this.setState({inspector}); }, ); - if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ != null) { - const DevtoolsOverlay = - require('../Inspector/DevtoolsOverlay').default; - const devtoolsOverlay = ( - + + if (reactDevToolsHook != null) { + if (reactDevToolsHook.reactDevtoolsAgent) { + // In case if this is not the first AppContainer rendered and React DevTools are already attached + this.mountReactDevToolsOverlays(); + return; + } + + this._reactDevToolsAgentListener = () => + this.mountReactDevToolsOverlays(); + + reactDevToolsHook.on( + 'react-devtools', + this._reactDevToolsAgentListener, ); - this.setState({devtoolsOverlay}); } } } @@ -85,6 +103,10 @@ class AppContainer extends React.Component { if (this._subscription != null) { this._subscription.remove(); } + + if (reactDevToolsHook != null && this._reactDevToolsAgentListener != null) { + reactDevToolsHook.off('react-devtools', this._reactDevToolsAgentListener); + } } render(): React.Node { diff --git a/React/Base/RCTBundleURLProvider.h b/React/Base/RCTBundleURLProvider.h index e2e07da0313990..1626b51cb5f1be 100644 --- a/React/Base/RCTBundleURLProvider.h +++ b/React/Base/RCTBundleURLProvider.h @@ -22,7 +22,11 @@ RCT_EXTERN const NSUInteger kRCTBundleURLProviderDefaultPort; RCT_EXTERN void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed); #endif -extern NSString *const kRCTPlatformName; // [macOS] +#if !TARGET_OS_OSX // [macOS] +static NSString *const kRCTPlatformName = @"ios"; +#else // [macOS +static NSString *const kRCTPlatformName = @"macos"; +#endif // macOS] // [macOS] @interface RCTBundleURLProvider : NSObject @@ -103,6 +107,7 @@ extern NSString *const kRCTPlatformName; // [macOS] @property (nonatomic, assign) BOOL enableMinification; @property (nonatomic, assign) BOOL enableDev; +@property (nonatomic, assign) BOOL inlineSourceMap; /** * The scheme/protocol used of the packager, the default is the http protocol @@ -127,13 +132,32 @@ extern NSString *const kRCTPlatformName; // [macOS] + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot packagerHost:(NSString *)packagerHost enableDev:(BOOL)enableDev - enableMinification:(BOOL)enableMinification; + enableMinification:(BOOL)enableMinification + __deprecated_msg( + "Use `jsBundleURLForBundleRoot:packagerHost:enableDev:enableMinification:inlineSourceMap:` instead"); + ++ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerHost:(NSString *)packagerHost + packagerScheme:(NSString *)scheme + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification + modulesOnly:(BOOL)modulesOnly + runModule:(BOOL)runModule + __deprecated_msg( + "Use jsBundleURLForBundleRoot:packagerHost:enableDev:enableMinification:inlineSourceMap:modulesOnly:runModule:` instead"); + ++ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerHost:(NSString *)packagerHost + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification + inlineSourceMap:(BOOL)inlineSourceMap; + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot packagerHost:(NSString *)packagerHost packagerScheme:(NSString *)scheme enableDev:(BOOL)enableDev enableMinification:(BOOL)enableMinification + inlineSourceMap:(BOOL)inlineSourceMap modulesOnly:(BOOL)modulesOnly runModule:(BOOL)runModule; /** @@ -144,6 +168,17 @@ extern NSString *const kRCTPlatformName; // [macOS] + (NSURL *)resourceURLForResourcePath:(NSString *)path packagerHost:(NSString *)packagerHost scheme:(NSString *)scheme - query:(NSString *)query; + query:(NSString *)query + __deprecated_msg("Use version with queryItems parameter instead"); + +/** + * Given a hostname for the packager and a resource path (including "/"), return the URL to the resource. + * In general, please use the instance method to decide if the packager is running and fallback to the pre-packaged + * resource if it is not: -resourceURLForResourceRoot:resourceName:resourceExtension:offlineBundle: + */ ++ (NSURL *)resourceURLForResourcePath:(NSString *)path + packagerHost:(NSString *)packagerHost + scheme:(NSString *)scheme + queryItems:(NSArray *)queryItems; @end diff --git a/React/Base/RCTBundleURLProvider.mm b/React/Base/RCTBundleURLProvider.mm index ec296d6d1d11d4..b671f7944f491f 100644 --- a/React/Base/RCTBundleURLProvider.mm +++ b/React/Base/RCTBundleURLProvider.mm @@ -15,12 +15,6 @@ const NSUInteger kRCTBundleURLProviderDefaultPort = RCT_METRO_PORT; -#if !TARGET_OS_OSX // [macOS] -NSString *const kRCTPlatformName = @"ios"; -#else // [macOS -NSString *const kRCTPlatformName = @"macos"; -#endif // macOS] - #if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY static BOOL kRCTAllowPackagerAccess = YES; void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed) @@ -28,10 +22,12 @@ void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed) kRCTAllowPackagerAccess = allowed; } #endif + static NSString *const kRCTPackagerSchemeKey = @"RCT_packager_scheme"; static NSString *const kRCTJsLocationKey = @"RCT_jsLocation"; static NSString *const kRCTEnableDevKey = @"RCT_enableDev"; static NSString *const kRCTEnableMinificationKey = @"RCT_enableMinification"; +static NSString *const kRCTInlineSourceMapKey = @"RCT_inlineSourceMap"; @implementation RCTBundleURLProvider @@ -189,6 +185,7 @@ - (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackURLProvider:( packagerScheme:[self packagerScheme] enableDev:[self enableDev] enableMinification:[self enableMinification] + inlineSourceMap:[self inlineSourceMap] modulesOnly:NO runModule:YES]; } @@ -201,6 +198,7 @@ - (NSURL *)jsBundleURLForSplitBundleRoot:(NSString *)bundleRoot packagerScheme:[self packagerScheme] enableDev:[self enableDev] enableMinification:[self enableMinification] + inlineSourceMap:[self inlineSourceMap] modulesOnly:YES runModule:NO]; } @@ -240,13 +238,29 @@ - (NSURL *)resourceURLForResourceRoot:(NSString *)root return [[self class] resourceURLForResourcePath:path packagerHost:packagerServerHostPort scheme:packagerServerScheme - query:nil]; + queryItems:nil]; } + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot packagerHost:(NSString *)packagerHost enableDev:(BOOL)enableDev enableMinification:(BOOL)enableMinification +{ + return [self jsBundleURLForBundleRoot:bundleRoot + packagerHost:packagerHost + packagerScheme:nil + enableDev:enableDev + enableMinification:enableMinification + inlineSourceMap:NO + modulesOnly:NO + runModule:YES]; +} + ++ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerHost:(NSString *)packagerHost + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification + inlineSourceMap:(BOOL)inlineSourceMap { return [self jsBundleURLForBundleRoot:bundleRoot @@ -254,6 +268,7 @@ + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot packagerScheme:nil enableDev:enableDev enableMinification:enableMinification + inlineSourceMap:inlineSourceMap modulesOnly:NO runModule:YES]; } @@ -265,28 +280,45 @@ + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot enableMinification:(BOOL)enableMinification modulesOnly:(BOOL)modulesOnly runModule:(BOOL)runModule +{ + return [self jsBundleURLForBundleRoot:bundleRoot + packagerHost:packagerHost + packagerScheme:nil + enableDev:enableDev + enableMinification:enableMinification + inlineSourceMap:NO + modulesOnly:modulesOnly + runModule:runModule]; +} + ++ (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerHost:(NSString *)packagerHost + packagerScheme:(NSString *)scheme + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification + inlineSourceMap:(BOOL)inlineSourceMap + modulesOnly:(BOOL)modulesOnly + runModule:(BOOL)runModule { NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot]; + BOOL lazy = enableDev; + NSArray *queryItems = @[ + [[NSURLQueryItem alloc] initWithName:@"platform" value:kRCTPlatformName], + [[NSURLQueryItem alloc] initWithName:@"dev" value:enableDev ? @"true" : @"false"], + [[NSURLQueryItem alloc] initWithName:@"minify" value:enableMinification ? @"true" : @"false"], + [[NSURLQueryItem alloc] initWithName:@"inlineSourceMap" value:inlineSourceMap ? @"true" : @"false"], + [[NSURLQueryItem alloc] initWithName:@"modulesOnly" value:modulesOnly ? @"true" : @"false"], + [[NSURLQueryItem alloc] initWithName:@"runModule" value:runModule ? @"true" : @"false"], #ifdef HERMES_BYTECODE_VERSION - NSString *runtimeBytecodeVersion = [NSString stringWithFormat:@"&runtimeBytecodeVersion=%u", HERMES_BYTECODE_VERSION]; -#else - NSString *runtimeBytecodeVersion = @""; + [[NSURLQueryItem alloc] initWithName:@"runtimeBytecodeVersion" value:HERMES_BYTECODE_VERSION], #endif - - // When we support only iOS 8 and above, use queryItems for a better API. - NSString *query = [NSString stringWithFormat:@"platform=%@&dev=%@&minify=%@&modulesOnly=%@&runModule=%@%@", - kRCTPlatformName, // [macOS] - enableDev ? @"true" : @"false", - enableMinification ? @"true" : @"false", - modulesOnly ? @"true" : @"false", - runModule ? @"true" : @"false", - runtimeBytecodeVersion]; + ]; NSString *bundleID = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey]; if (bundleID) { - query = [NSString stringWithFormat:@"%@&app=%@", query, bundleID]; + queryItems = [queryItems arrayByAddingObject:[[NSURLQueryItem alloc] initWithName:@"app" value:bundleID]]; } - return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost scheme:scheme query:query]; + return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost scheme:scheme queryItems:queryItems]; } + (NSURL *)resourceURLForResourcePath:(NSString *)path @@ -303,6 +335,20 @@ + (NSURL *)resourceURLForResourcePath:(NSString *)path return components.URL; } ++ (NSURL *)resourceURLForResourcePath:(NSString *)path + packagerHost:(NSString *)packagerHost + scheme:(NSString *)scheme + queryItems:(NSArray *)queryItems +{ + NSURLComponents *components = [NSURLComponents componentsWithURL:serverRootWithHostPort(packagerHost, scheme) + resolvingAgainstBaseURL:NO]; + components.path = path; + if (queryItems != nil) { + components.queryItems = queryItems; + } + return components.URL; +} + - (void)updateValue:(id)object forKey:(NSString *)key { [[NSUserDefaults standardUserDefaults] setObject:object forKey:key]; @@ -320,6 +366,11 @@ - (BOOL)enableMinification return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTEnableMinificationKey]; } +- (BOOL)inlineSourceMap +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kRCTInlineSourceMapKey]; +} + - (NSString *)jsLocation { return [[NSUserDefaults standardUserDefaults] stringForKey:kRCTJsLocationKey]; @@ -349,6 +400,11 @@ - (void)setEnableMinification:(BOOL)enableMinification [self updateValue:@(enableMinification) forKey:kRCTEnableMinificationKey]; } +- (void)setInlineSourceMap:(BOOL)inlineSourceMap +{ + [self updateValue:@(inlineSourceMap) forKey:kRCTInlineSourceMapKey]; +} + - (void)setPackagerScheme:(NSString *)packagerScheme { [self updateValue:packagerScheme forKey:kRCTPackagerSchemeKey]; diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index 0141e70c3eb69b..8fece5579f943c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -20,6 +20,7 @@ import android.graphics.Color; import android.graphics.Typeface; import android.hardware.SensorManager; +import android.os.Build; import android.util.Pair; import android.view.Gravity; import android.view.View; @@ -1098,7 +1099,7 @@ private void reload() { if (!mIsReceiverRegistered) { IntentFilter filter = new IntentFilter(); filter.addAction(getReloadAppAction(mApplicationContext)); - mApplicationContext.registerReceiver(mReloadAppBroadcastReceiver, filter); + compatRegisterReceiver(mApplicationContext, mReloadAppBroadcastReceiver, filter, true); mIsReceiverRegistered = true; } @@ -1214,4 +1215,21 @@ public void setPackagerLocationCustomizer( return mSurfaceDelegateFactory.createSurfaceDelegate(moduleName); } + + /** + * Starting with Android 14, apps and services that target Android 14 and use context-registered + * receivers are required to specify a flag to indicate whether or not the receiver should be + * exported to all other apps on the device: either RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED + * + *

https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported + */ + private void compatRegisterReceiver( + Context context, BroadcastReceiver receiver, IntentFilter filter, boolean exported) { + if (Build.VERSION.SDK_INT >= 34 && context.getApplicationInfo().targetSdkVersion >= 34) { + context.registerReceiver( + receiver, filter, exported ? Context.RECEIVER_EXPORTED : Context.RECEIVER_NOT_EXPORTED); + } else { + context.registerReceiver(receiver, filter); + } + } } diff --git a/ReactCommon/jsc/JSCRuntime.cpp b/ReactCommon/jsc/JSCRuntime.cpp index bbc606561ee353..38ebedaf78d23f 100644 --- a/ReactCommon/jsc/JSCRuntime.cpp +++ b/ReactCommon/jsc/JSCRuntime.cpp @@ -300,6 +300,9 @@ class JSCRuntime : public jsi::Runtime { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0 #define _JSC_NO_ARRAY_BUFFERS #endif +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400 +#define _JSC_HAS_INSPECTABLE +#endif #endif #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_11 @@ -396,6 +399,13 @@ JSCRuntime::JSCRuntime(JSGlobalContextRef ctx) stringCounter_(0) #endif { +#ifndef NDEBUG +#ifdef _JSC_HAS_INSPECTABLE + if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) { + JSGlobalContextSetInspectable(ctx_, true); + } +#endif +#endif } JSCRuntime::~JSCRuntime() { diff --git a/package.json b/package.json index 7c000cf0b524e5..d7f76a79fa666f 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,8 @@ "test-typescript": "dtslint types", "test-typescript-offline": "dtslint --localTs node_modules/typescript/lib types", "bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages", - "align-package-versions": "node ./scripts/monorepo/align-package-versions.js" + "align-package-versions": "node ./scripts/monorepo/align-package-versions.js", + "trigger-react-native-release": "node ./scripts/trigger-react-native-release.js" }, "peerDependencies": { "react": "18.2.0" @@ -116,7 +117,6 @@ "@react-native-community/cli": "10.2.4", "@react-native-community/cli-platform-android": "10.2.0", "@react-native-community/cli-platform-ios": "10.2.4", - "@react-native-community/cli-tools": "10.1.1", "@react-native/assets": "1.0.0", "@react-native/normalize-color": "2.1.0", "@react-native/polyfills": "2.0.0", diff --git a/packages/rn-tester/RNTesterUnitTests/RCTBundleURLProviderTests.m b/packages/rn-tester/RNTesterUnitTests/RCTBundleURLProviderTests.m index 25a16cc177eab5..427e3f3682bce7 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTBundleURLProviderTests.m +++ b/packages/rn-tester/RNTesterUnitTests/RCTBundleURLProviderTests.m @@ -27,7 +27,7 @@ URLWithString: [NSString stringWithFormat: - @"http://localhost:8081/%@.bundle?platform=%@&dev=true&minify=false&modulesOnly=false&runModule=true&runtimeBytecodeVersion=%u&app=com.apple.dt.xctest.tool", + @"http://localhost:8081/%@.bundle?platform=%@&dev=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&runtimeBytecodeVersion=%u&app=com.apple.dt.xctest.tool", testFile, kRCTPlatformName, // [macOS] HERMES_BYTECODE_VERSION]]; @@ -36,7 +36,7 @@ URLWithString: [NSString stringWithFormat: - @"http://localhost:8081/%@.bundle?platform=%@&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.apple.dt.xctest.tool", + @"http://localhost:8081/%@.bundle?platform=%@&dev=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.apple.dt.xctest.tool", testFile, kRCTPlatformName]]; // [macOS] #endif @@ -49,7 +49,7 @@ URLWithString: [NSString stringWithFormat: - @"http://192.168.1.1:8081/%@.bundle?platform=%@&dev=true&minify=false&modulesOnly=false&runModule=true&runtimeBytecodeVersion=%u&app=com.apple.dt.xctest.tool", + @"http://192.168.1.1:8081/%@.bundle?platform=%@&dev=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&runtimeBytecodeVersion=%u&app=com.apple.dt.xctest.tool", testFile, kRCTPlatformName, // [macOS] HERMES_BYTECODE_VERSION]]; @@ -58,7 +58,7 @@ URLWithString: [NSString stringWithFormat: - @"http://192.168.1.1:8081/%@.bundle?platform=%@&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.apple.dt.xctest.tool", + @"http://192.168.1.1:8081/%@.bundle?platform=%@&dev=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.apple.dt.xctest.tool", testFile, kRCTPlatformName]]; // [macOS] #endif diff --git a/scripts/circle-ci-artifacts-utils.js b/scripts/circle-ci-artifacts-utils.js new file mode 100644 index 00000000000000..3a9d9d2a0c83f1 --- /dev/null +++ b/scripts/circle-ci-artifacts-utils.js @@ -0,0 +1,198 @@ +#!/usr/bin/env node +/** + * 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. + * + * @format + */ + +'use strict'; + +const {exec} = require('shelljs'); + +const util = require('util'); +const asyncRequest = require('request'); +const request = util.promisify(asyncRequest); + +let circleCIHeaders; +let jobs; +let baseTemporaryPath; + +async function initialize(circleCIToken, baseTempPath, branchName) { + console.info('Getting CircleCI information'); + circleCIHeaders = {'Circle-Token': circleCIToken}; + baseTemporaryPath = baseTempPath; + exec(`mkdir -p ${baseTemporaryPath}`); + const pipeline = await _getLastCircleCIPipelineID(branchName); + const packageAndReleaseWorkflow = await _getPackageAndReleaseWorkflow( + pipeline.id, + ); + const testsWorkflow = await _getTestsWorkflow(pipeline.id); + const jobsPromises = [ + _getCircleCIJobs(packageAndReleaseWorkflow.id), + _getCircleCIJobs(testsWorkflow.id), + ]; + + const jobsResults = await Promise.all(jobsPromises); + + jobs = jobsResults.flatMap(j => j); +} + +function baseTmpPath() { + return baseTemporaryPath; +} + +async function _getLastCircleCIPipelineID(branchName) { + const options = { + method: 'GET', + url: 'https://circleci.com/api/v2/project/gh/facebook/react-native/pipeline', + qs: { + branch: branchName, + }, + headers: circleCIHeaders, + }; + + const response = await request(options); + if (response.error) { + throw new Error(error); + } + + const items = JSON.parse(response.body).items; + + if (!items || items.length === 0) { + throw new Error( + 'No pipelines found on this branch. Make sure that the CI has run at least once, successfully', + ); + } + + const lastPipeline = items[0]; + return {id: lastPipeline.id, number: lastPipeline.number}; +} + +async function _getSpecificWorkflow(pipelineId, workflowName) { + const options = { + method: 'GET', + url: `https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`, + headers: circleCIHeaders, + }; + const response = await request(options); + if (response.error) { + throw new Error(error); + } + + const body = JSON.parse(response.body); + let workflow = body.items.find(w => w.name === workflowName); + _throwIfWorkflowNotFound(workflow, workflowName); + return workflow; +} + +function _throwIfWorkflowNotFound(workflow, name) { + if (!workflow) { + throw new Error( + `Can't find a workflow named ${name}. Please check whether that workflow has started.`, + ); + } +} + +async function _getPackageAndReleaseWorkflow(pipelineId) { + return _getSpecificWorkflow(pipelineId, 'package_and_publish_release_dryrun'); +} + +async function _getTestsWorkflow(pipelineId) { + return _getSpecificWorkflow(pipelineId, 'tests'); +} + +async function _getCircleCIJobs(workflowId) { + const options = { + method: 'GET', + url: `https://circleci.com/api/v2/workflow/${workflowId}/job`, + headers: circleCIHeaders, + }; + const response = await request(options); + if (response.error) { + throw new Error(error); + } + + const body = JSON.parse(response.body); + return body.items; +} + +async function _getJobsArtifacts(jobNumber) { + const options = { + method: 'GET', + url: `https://circleci.com/api/v2/project/gh/facebook/react-native/${jobNumber}/artifacts`, + headers: circleCIHeaders, + }; + const response = await request(options); + if (response.error) { + throw new Error(error); + } + + const body = JSON.parse(response.body); + return body.items; +} + +async function _findUrlForJob(jobName, artifactPath) { + const job = jobs.find(j => j.name === jobName); + _throwIfJobIsNull(job); + _throwIfJobIsUnsuccessful(job); + + const artifacts = await _getJobsArtifacts(job.job_number); + return artifacts.find(artifact => artifact.path.indexOf(artifactPath) > -1) + .url; +} + +function _throwIfJobIsNull(job) { + if (!job) { + throw new Error( + `Can't find a job with name ${job.name}. Please verify that it has been executed and that all its dependencies completed successfully.`, + ); + } +} + +function _throwIfJobIsUnsuccessful(job) { + if (job.status !== 'success') { + throw new Error( + `The job ${job.name} status is ${job.status}. We need a 'success' status to proceed with the testing.`, + ); + } +} + +async function artifactURLHermesDebug() { + return _findUrlForJob('build_hermes_macos-Debug', 'hermes-ios-debug.tar.gz'); +} + +async function artifactURLForMavenLocal() { + return _findUrlForJob('build_and_publish_npm_package-2', 'maven-local.zip'); +} + +async function artifactURLForHermesRNTesterAPK(emulatorArch) { + return _findUrlForJob( + 'test_android', + `rntester-apk/hermes/debug/app-hermes-${emulatorArch}-debug.apk`, + ); +} + +async function artifactURLForJSCRNTesterAPK(emulatorArch) { + return _findUrlForJob( + 'test_android', + `rntester-apk/jsc/debug/app-jsc-${emulatorArch}-debug.apk`, + ); +} + +function downloadArtifact(artifactURL, destination) { + exec(`rm -rf ${destination}`); + exec(`curl ${artifactURL} -Lo ${destination}`); +} + +module.exports = { + initialize, + downloadArtifact, + artifactURLForJSCRNTesterAPK, + artifactURLForHermesRNTesterAPK, + artifactURLForMavenLocal, + artifactURLHermesDebug, + baseTmpPath, +}; diff --git a/scripts/circleci/poll-maven.js b/scripts/circleci/poll-maven.js new file mode 100644 index 00000000000000..298909691e4c93 --- /dev/null +++ b/scripts/circleci/poll-maven.js @@ -0,0 +1,72 @@ +/** + * 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. + * + * @format + */ + +const fetch = require('node-fetch'); +const fs = require('fs'); + +const baseMavenRepo = 'https://repo1.maven.org/maven2/com/facebook/react'; +const artifacts = ['react-native-artifacts', 'react-android', 'hermes-android']; +const humanNames = { + 'react-native-artifacts': 'Hermes for iOS', + 'react-android': 'React Native for Android', + 'hermes-android': 'Hermes for Android', +}; +const ping_minutes = 5; +const max_hours = 5; +const ping_interval = ping_minutes * 60 * 1000; // 5 minutes +const max_wait = max_hours * 60 * 60 * 1000; // 5 hours + +const startTime = Date.now(); + +async function pingMaven(artifact, rnVersion) { + const url = `${baseMavenRepo}/${artifact}/${rnVersion}`; + const response = await fetch(url, {method: 'HEAD'}); + if (response.status === 200) { + console.log(`Found artifact for ${humanNames[artifact]}\n`); + return; + } else if (response.status !== 404) { + throw new Error( + `Unexpected response code ${response.status} for ${humanNames[artifact]}`, + ); + } + + const elapsedTime = Date.now() - startTime; + if (elapsedTime > max_wait) { + throw new Error(`${max_hours} hours has passed. Exiting.`); + } + // Wait a bit + console.log( + `${humanNames[artifact]} not available yet. Waiting ${ping_minutes} minutes.\n`, + ); + await new Promise(resolve => setTimeout(resolve, ping_interval)); + await pingMaven(url); +} + +async function main() { + const package = JSON.parse( + fs.readFileSync('packages/react-native/package.json', 'utf8'), + ); + const rnVersion = package.version; + + if (rnVersion === '1000.0.0') { + console.log( + 'We are not on a release branch when a release has been initiated. Exiting.', + ); + return; + } + + console.log(`Checking artifacts for React Native version ${rnVersion}\n`); + + for (const artifact of artifacts) { + console.log(`Start pinging for ${humanNames[artifact]}`); + await pingMaven(artifact, rnVersion); + } +} + +main(); diff --git a/scripts/monorepo/bump-all-updated-packages/bump-utils.js b/scripts/monorepo/bump-all-updated-packages/bump-utils.js new file mode 100644 index 00000000000000..07c57e5313ab27 --- /dev/null +++ b/scripts/monorepo/bump-all-updated-packages/bump-utils.js @@ -0,0 +1,49 @@ +/** + * 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. + * + * @format + */ + +const chalk = require('chalk'); +const {echo, exec} = require('shelljs'); + +const detectPackageUnreleasedChanges = ( + packageRelativePathFromRoot, + packageName, + ROOT_LOCATION, +) => { + const hashOfLastCommitInsidePackage = exec( + `git log -n 1 --format=format:%H -- ${packageRelativePathFromRoot}`, + {cwd: ROOT_LOCATION, silent: true}, + ).stdout.trim(); + + const hashOfLastCommitThatChangedVersion = exec( + `git log -G\\"version\\": --format=format:%H -n 1 -- ${packageRelativePathFromRoot}/package.json`, + {cwd: ROOT_LOCATION, silent: true}, + ).stdout.trim(); + + if (hashOfLastCommitInsidePackage === hashOfLastCommitThatChangedVersion) { + echo( + `\uD83D\uDD0E No changes for package ${chalk.green( + packageName, + )} since last version bump`, + ); + return false; + } else { + echo(`\uD83D\uDCA1 Found changes for ${chalk.yellow(packageName)}:`); + exec( + `git log --pretty=oneline ${hashOfLastCommitThatChangedVersion}..${hashOfLastCommitInsidePackage} ${packageRelativePathFromRoot}`, + { + cwd: ROOT_LOCATION, + }, + ); + echo(); + + return true; + } +}; + +module.exports = detectPackageUnreleasedChanges; diff --git a/scripts/monorepo/bump-all-updated-packages/index.js b/scripts/monorepo/bump-all-updated-packages/index.js index 09291c0d723423..2501c86461c4d8 100644 --- a/scripts/monorepo/bump-all-updated-packages/index.js +++ b/scripts/monorepo/bump-all-updated-packages/index.js @@ -24,6 +24,7 @@ const { const forEachPackage = require('../for-each-package'); const checkForGitChanges = require('../check-for-git-changes'); const bumpPackageVersion = require('./bump-package-version'); +const detectPackageUnreleasedChanges = require('./bump-utils'); const ROOT_LOCATION = path.join(__dirname, '..', '..', '..'); @@ -61,35 +62,16 @@ const buildExecutor = return; } - const hashOfLastCommitInsidePackage = exec( - `git log -n 1 --format=format:%H -- ${packageRelativePathFromRoot}`, - {cwd: ROOT_LOCATION, silent: true}, - ).stdout.trim(); - - const hashOfLastCommitThatChangedVersion = exec( - `git log -G\\"version\\": --format=format:%H -n 1 -- ${packageRelativePathFromRoot}/package.json`, - {cwd: ROOT_LOCATION, silent: true}, - ).stdout.trim(); - - if (hashOfLastCommitInsidePackage === hashOfLastCommitThatChangedVersion) { - echo( - `\uD83D\uDD0E No changes for package ${chalk.green( - packageName, - )} since last version bump`, - ); - + if ( + !detectPackageUnreleasedChanges( + packageRelativePathFromRoot, + packageName, + ROOT_LOCATION, + ) + ) { return; } - echo(`\uD83D\uDCA1 Found changes for ${chalk.yellow(packageName)}:`); - exec( - `git log --pretty=oneline ${hashOfLastCommitThatChangedVersion}..${hashOfLastCommitInsidePackage} ${packageRelativePathFromRoot}`, - { - cwd: ROOT_LOCATION, - }, - ); - echo(); - await inquirer .prompt([ { @@ -133,9 +115,18 @@ const buildExecutor = const buildAllExecutors = () => { const executors = []; - forEachPackage((...params) => { - executors.push(buildExecutor(...params)); - }); + forEachPackage( + (...params) => { + executors.push(buildExecutor(...params)); + }, + [ + 'assets', + 'eslint-config-react-native-community', + 'eslint-plugin-react-native-community', + 'normalize-color', + 'polyfills', + ], + ); return executors; }; diff --git a/scripts/monorepo/for-each-package.js b/scripts/monorepo/for-each-package.js index 10991b5936da1d..2118139c1ac705 100644 --- a/scripts/monorepo/for-each-package.js +++ b/scripts/monorepo/for-each-package.js @@ -34,14 +34,16 @@ const getDirectories = source => */ /** - * Iterate through every package inside /packages (ignoring react-native) and call provided callback for each of them + * Iterate through every package inside /packages (ignoring react-native and any additional excluded packages) and call provided callback for each of them * - * @param {forEachPackageCallback} callback The callback which will be called for each package + * @param {forEachPackageCallback} callback - The callback which will be called for each package + * @param {string[]} [additionalExcludes] - Additional packages to exclude */ -const forEachPackage = callback => { - // We filter react-native package on purpose, so that no CI's script will be executed for this package in future +const forEachPackage = (callback, additionalExcludes = []) => { + const packagesToExclude = [...PACKAGES_BLOCK_LIST, ...additionalExcludes]; + const packagesDirectories = getDirectories(PACKAGES_LOCATION).filter( - directoryName => !PACKAGES_BLOCK_LIST.includes(directoryName), + directoryName => !packagesToExclude.includes(directoryName), ); packagesDirectories.forEach(packageDirectory => { diff --git a/scripts/npm-utils.js b/scripts/npm-utils.js new file mode 100644 index 00000000000000..effeb4ce33c0b1 --- /dev/null +++ b/scripts/npm-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. + * + * @format + */ + +'use strict'; + +/** + * `package` is an object form of package.json + * `dependencies` is a map of dependency to version string + * + * This replaces both dependencies and devDependencies in package.json + */ +function applyPackageVersions(originalPackageJson, packageVersions) { + const packageJson = {...originalPackageJson}; + + for (const name of Object.keys(packageVersions)) { + if ( + packageJson.dependencies != null && + packageJson.dependencies[name] != null + ) { + packageJson.dependencies[name] = packageVersions[name]; + } + + if ( + packageJson.devDependencies != null && + packageJson.devDependencies[name] != null + ) { + packageJson.devDependencies[name] = packageVersions[name]; + } + } + return packageJson; +} + +module.exports = { + applyPackageVersions, +}; diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index a3211953091d85..102893144443ea 100755 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -112,7 +112,7 @@ const rawVersion = : // For nightly we continue to use 0.0.0 for clarity for npm nightlyBuild ? '0.0.0' - : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in set-rn-version) + : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in trigger-react-native-release) getPkgJsonVersion(); // [macOS] We can't use the CircleCI build tag, so we use the version argument instead. let version, diff --git a/scripts/release-utils.js b/scripts/release-utils.js index 9cb69c4c5ea1d9..cc5764749c3ab9 100644 --- a/scripts/release-utils.js +++ b/scripts/release-utils.js @@ -109,11 +109,6 @@ function generateiOSArtifacts( ) { pushd(`${hermesCoreSourceFolder}`); - //Need to generate hermesc - exec( - `${hermesCoreSourceFolder}/utils/build-hermesc-xcode.sh ${hermesCoreSourceFolder}/build_host_hermesc`, - ); - //Generating iOS Artifacts exec( `JSI_PATH=${jsiFolder} BUILD_TYPE=${buildType} ${hermesCoreSourceFolder}/utils/build-mac-framework.sh`, diff --git a/scripts/test-e2e-local-clean.js b/scripts/test-e2e-local-clean.js index 37ca37032732b5..6dd85f5bba7c8b 100644 --- a/scripts/test-e2e-local-clean.js +++ b/scripts/test-e2e-local-clean.js @@ -44,6 +44,7 @@ if (isPackagerRunning() === 'running') { console.info('\n** Cleaning Gradle build artifacts **\n'); exec('./gradlew cleanAll'); exec('rm -rf /tmp/maven-local'); +exec('rm -rf /tmp/react-native-tmp'); // iOS console.info('\n** Nuking the derived data folder **\n'); @@ -56,9 +57,6 @@ exec('rm -rf ~/Library/Caches/CocoaPods/Pods/External/hermes-engine'); console.info('\n** Removing the RNTester Pods **\n'); exec('rm -rf packages/rn-tester/Pods'); -// I'm not sure we want to also remove the lock file -// exec('rm -rf packages/rn-tester/Podfile.lock'); - // RNTestProject console.info('\n** Removing the RNTestProject folder **\n'); exec('rm -rf /tmp/RNTestProject'); diff --git a/scripts/test-e2e-local.js b/scripts/test-e2e-local.js index afae78ea83e6be..775e7e79ecd2b6 100644 --- a/scripts/test-e2e-local.js +++ b/scripts/test-e2e-local.js @@ -16,29 +16,21 @@ * and to make it more accessible for other devs to play around with. */ -const {exec, exit, pushd, popd, pwd, cd, cp} = require('shelljs'); +const {exec, pushd, popd, pwd, cd} = require('shelljs'); +const updateTemplatePackage = require('./update-template-package'); const yargs = require('yargs'); -const fs = require('fs'); const path = require('path'); +const fs = require('fs'); const os = require('os'); const { - launchAndroidEmulator, - isPackagerRunning, + checkPackagerRunning, + maybeLaunchAndroidEmulator, launchPackagerInSeparateWindow, + setupCircleCIArtifacts, + prepareArtifacts, } = require('./testing-utils'); -const { - generateAndroidArtifacts, - saveFilesToRestore, - generateiOSArtifacts, -} = require('./release-utils'); - -const { - downloadHermesSourceTarball, - expandHermesSourceTarball, -} = require('./hermes/hermes-utils'); - const argv = yargs .option('t', { alias: 'target', @@ -54,111 +46,152 @@ const argv = yargs alias: 'hermes', type: 'boolean', default: true, + }) + .option('c', { + alias: 'circleciToken', + type: 'string', }).argv; -/* - * see the test-local-e2e.js script for clean up process +// === RNTester === // + +/** + * Start the test for RNTester on iOS. + * + * Parameters: + * - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them. + * - @onReleaseBranch whether we are on a release branch or not */ +async function testRNTesterIOS(circleCIArtifacts, onReleaseBranch) { + console.info( + `We're going to test the ${ + argv.hermes ? 'Hermes' : 'JSC' + } version of RNTester iOS with the new Architecture enabled`, + ); -// command order: we ask the user to select if they want to test RN tester -// or RNTestProject + // remember that for this to be successful + // you should have run bundle install once + // in your local setup + if (argv.hermes && circleCIArtifacts != null) { + const hermesURL = await circleCIArtifacts.artifactURLHermesDebug(); + const hermesPath = path.join( + circleCIArtifacts.baseTmpPath(), + 'hermes-ios-debug.tar.gz', + ); + // download hermes source code from manifold + circleCIArtifacts.downloadArtifact(hermesURL, hermesPath); + console.info(`Downloaded Hermes in ${hermesPath}`); + exec( + `HERMES_ENGINE_TARBALL_PATH=${hermesPath} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`, + ); + } else { + exec( + `USE_HERMES=${ + argv.hermes ? 1 : 0 + } REACT_NATIVE_CI=${onReleaseBranch} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`, + ); + } -// if they select RN tester, we ask if iOS or Android, and then we run the tests -// if they select RNTestProject, we run the RNTestProject test + // if everything succeeded so far, we can launch Metro and the app + // start the Metro server in a separate window + launchPackagerInSeparateWindow(pwd()); -// let's check if Metro is already running, if it is let's kill it and start fresh -if (isPackagerRunning() === 'running') { - exec("lsof -i :8081 | grep LISTEN | /usr/bin/awk '{print $2}' | xargs kill"); + // launch the app on iOS simulator + exec('npx react-native run-ios --scheme RNTester --simulator "iPhone 14"'); } -const onReleaseBranch = exec('git rev-parse --abbrev-ref HEAD', { - silent: true, -}) - .stdout.trim() - .endsWith('-stable'); +/** + * Start the test for RNTester on Android. + * + * Parameters: + * - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them. + */ +async function testRNTesterAndroid(circleCIArtifacts) { + maybeLaunchAndroidEmulator(); -if (argv.target === 'RNTester') { - // FIXME: make sure that the commands retains colors - // (--ansi) doesn't always work - // see also https://github.com/shelljs/shelljs/issues/86 + console.info( + `We're going to test the ${ + argv.hermes ? 'Hermes' : 'JSC' + } version of RNTester Android with the new Architecture enabled`, + ); - if (argv.platform === 'iOS') { - console.info( - `We're going to test the ${ - argv.hermes ? 'Hermes' : 'JSC' - } version of RNTester iOS with the new Architecture enabled`, - ); + // Start the Metro server so it will be ready if the app can be built and installed successfully. + launchPackagerInSeparateWindow(pwd()); - // remember that for this to be successful - // you should have run bundle install once - // in your local setup - also: if I'm on release branch, I pick the - // hermes ref from the hermes ref file (see hermes-engine.podspec) - exec( - `cd packages/rn-tester && USE_HERMES=${ - argv.hermes ? 1 : 0 - } REACT_NATIVE_CI=${onReleaseBranch} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`, - ); + // Wait for the Android Emulator to be properly loaded and bootstrapped + exec( + "adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'", + ); - // if everything succeeded so far, we can launch Metro and the app - // start the Metro server in a separate window - launchPackagerInSeparateWindow(); + if (circleCIArtifacts != null) { + const downloadPath = path.join( + circleCIArtifacts.baseTmpPath(), + 'rntester.apk', + ); - // launch the app on iOS simulator - pushd('packages/rn-tester'); - exec('npx react-native run-ios --scheme RNTester --simulator "iPhone 14"'); - popd(); - } else { - // we do the android path here + const emulatorArch = exec('adb shell getprop ro.product.cpu.abi').trim(); + const rntesterAPKURL = argv.hermes + ? await circleCIArtifacts.artifactURLForHermesRNTesterAPK(emulatorArch) + : await circleCIArtifacts.artifactURLForJSCRNTesterAPK(emulatorArch); - launchAndroidEmulator(); + console.info('Start Downloading APK'); + circleCIArtifacts.downloadArtifact(rntesterAPKURL, downloadPath); - console.info( - `We're going to test the ${ - argv.hermes ? 'Hermes' : 'JSC' - } version of RNTester Android with the new Architecture enabled`, - ); + exec(`adb install ${downloadPath}`); + } else { exec( - `./gradlew :packages:rn-tester:android:app:${ + `../../gradlew :packages:rn-tester:android:app:${ argv.hermes ? 'installHermesDebug' : 'installJscDebug' } --quiet`, ); + } - // launch the app on Android simulator - // TODO: we should find a way to make it work like for iOS, via npx react-native run-android - // currently, that fails with an error. + // launch the app + // TODO: we should find a way to make it work like for iOS, via npx react-native run-android + // currently, that fails with an error. + exec( + 'adb shell am start -n com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity', + ); - // if everything succeeded so far, we can launch Metro and the app - // start the Metro server in a separate window - launchPackagerInSeparateWindow(); + // just to make sure that the Android up won't have troubles finding the Metro server + exec('adb reverse tcp:8081 tcp:8081'); +} - // launch the app - exec( - 'adb shell am start -n com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity', - ); +/** + * Function that start testing on RNTester. + * + * Parameters: + * - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them. + * - @onReleaseBranch whether we are on a release branch or not + */ +async function testRNTester(circleCIArtifacts, onReleaseBranch) { + // FIXME: make sure that the commands retains colors + // (--ansi) doesn't always work + // see also https://github.com/shelljs/shelljs/issues/86 + pushd('packages/rn-tester'); - // just to make sure that the Android up won't have troubles finding the Metro server - exec('adb reverse tcp:8081 tcp:8081'); + if (argv.platform === 'iOS') { + await testRNTesterIOS(circleCIArtifacts, onReleaseBranch); + } else { + await testRNTesterAndroid(circleCIArtifacts); } -} else { + popd(); +} + +// === RNTestProject === // + +async function testRNTestProject(circleCIArtifacts) { console.info("We're going to test a fresh new RN project"); // create the local npm package to feed the CLI // base setup required (specular to publish-npm.js) - const tmpPublishingFolder = fs.mkdtempSync( - path.join(os.tmpdir(), 'rn-publish-'), - ); - console.info(`The temp publishing folder is ${tmpPublishingFolder}`); - - saveFilesToRestore(tmpPublishingFolder); - - // we need to add the unique timestamp to avoid npm/yarn to use some local caches const baseVersion = require('../package.json').version; // in local testing, 1000.0.0 mean we are on main, every other case means we are // working on a release version const buildType = baseVersion !== '1000.0.0' ? 'release' : 'dry-run'; + // we need to add the unique timestamp to avoid npm/yarn to use some local caches const dateIdentifier = new Date() .toISOString() .slice(0, -8) @@ -167,59 +200,44 @@ if (argv.target === 'RNTester') { const releaseVersion = `${baseVersion}-${dateIdentifier}`; - // this is needed to generate the Android artifacts correctly - const exitCode = exec( - `node scripts/set-rn-version.js --to-version ${releaseVersion} --build-type ${buildType}`, - ).code; - - if (exitCode !== 0) { - console.error( - `Failed to set the RN version. Version ${releaseVersion} is not valid for ${buildType}`, - ); - process.exit(exitCode); - } - - // Generate native files for Android - generateAndroidArtifacts(releaseVersion, tmpPublishingFolder); - - // Setting up generating native iOS (will be done later) + // Prepare some variables for later use const repoRoot = pwd(); - const jsiFolder = `${repoRoot}/ReactCommon/jsi`; - const hermesCoreSourceFolder = `${repoRoot}/sdks/hermes`; - - if (!fs.existsSync(hermesCoreSourceFolder)) { - console.info('The Hermes source folder is missing. Downloading...'); - downloadHermesSourceTarball(); - expandHermesSourceTarball(); - } - - // need to move the scripts inside the local hermes cloned folder - // cp sdks/hermes-engine/utils/*.sh /utils/. - cp( - `${repoRoot}/sdks/hermes-engine/utils/*.sh`, - `${repoRoot}/sdks/hermes/utils/.`, + const reactNativePackagePath = `${repoRoot}`; + const localNodeTGZPath = `${reactNativePackagePath}/react-native-${releaseVersion}.tgz`; + + const mavenLocalPath = + circleCIArtifacts != null + ? path.join(circleCIArtifacts.baseTmpPath(), 'maven-local.zip') + : '/private/tmp/maven-local'; + const hermesPath = await prepareArtifacts( + circleCIArtifacts, + mavenLocalPath, + localNodeTGZPath, + releaseVersion, + buildType, + reactNativePackagePath, ); - // for this scenario, we only need to create the debug build - // (env variable PRODUCTION defines that podspec side) - const buildTypeiOSArtifacts = 'Debug'; - - // the android ones get set into /private/tmp/maven-local - const localMavenPath = '/private/tmp/maven-local'; - - // Generate native files for iOS - const tarballOutputPath = generateiOSArtifacts( - jsiFolder, - hermesCoreSourceFolder, - buildTypeiOSArtifacts, - localMavenPath, - ); - - const localNodeTGZPath = `${repoRoot}/react-native-${releaseVersion}.tgz`; - exec(`node scripts/set-rn-template-version.js "file:${localNodeTGZPath}"`); + updateTemplatePackage({ + 'react-native': `file:${localNodeTGZPath}`, + }); // create locally the node module - exec('npm pack'); + exec('npm pack --pack-destination ', {cwd: reactNativePackagePath}); + + // node pack does not creates a version of React Native with the right name on main. + // Let's add some defensive programming checks: + if (!fs.existsSync(localNodeTGZPath)) { + const tarfile = fs + .readdirSync(reactNativePackagePath) + .find(name => name.startsWith('react-native-') && name.endsWith('.tgz')); + if (!tarfile) { + throw new Error("Couldn't find a zipped version of react-native"); + } + exec( + `cp ${path.join(reactNativePackagePath, tarfile)} ${localNodeTGZPath}`, + ); + } pushd('/tmp/'); // need to avoid the pod install step - we'll do it later @@ -232,14 +250,14 @@ if (argv.target === 'RNTester') { // need to do this here so that Android will be properly setup either way exec( - 'echo "REACT_NATIVE_MAVEN_LOCAL_REPO=/private/tmp/maven-local" >> android/gradle.properties', + `echo "REACT_NATIVE_MAVEN_LOCAL_REPO=${mavenLocalPath}" >> android/gradle.properties`, ); // doing the pod install here so that it's easier to play around RNTestProject cd('ios'); exec('bundle install'); exec( - `HERMES_ENGINE_TARBALL_PATH=${tarballOutputPath} USE_HERMES=${ + `HERMES_ENGINE_TARBALL_PATH=${hermesPath} USE_HERMES=${ argv.hermes ? 1 : 0 } bundle exec pod install --ansi`, ); @@ -255,7 +273,37 @@ if (argv.target === 'RNTester') { popd(); // just cleaning up the temp folder, the rest is done by the test clean script - exec(`rm -rf ${tmpPublishingFolder}`); + exec(`rm -rf /tmp/react-native-tmp`); +} + +async function main() { + /* + * see the test-local-e2e.js script for clean up process + */ + + // command order: we ask the user to select if they want to test RN tester + // or RNTestProject + + // if they select RN tester, we ask if iOS or Android, and then we run the tests + // if they select RNTestProject, we run the RNTestProject test + + checkPackagerRunning(); + + const branchName = exec('git rev-parse --abbrev-ref HEAD', { + silent: true, + }).stdout.trim(); + const onReleaseBranch = branchName.endsWith('-stable'); + + let circleCIArtifacts = await setupCircleCIArtifacts( + argv.circleciToken, + branchName, + ); + + if (argv.target === 'RNTester') { + await testRNTester(circleCIArtifacts, onReleaseBranch); + } else { + await testRNTestProject(circleCIArtifacts); + } } -exit(0); +main(); diff --git a/scripts/testing-utils.js b/scripts/testing-utils.js index a687a37cd562ca..636bf9cfa1351b 100644 --- a/scripts/testing-utils.js +++ b/scripts/testing-utils.js @@ -9,9 +9,23 @@ 'use strict'; -const {exec} = require('shelljs'); +const {exec, cp} = require('shelljs'); +const fs = require('fs'); const os = require('os'); const {spawn} = require('node:child_process'); +const path = require('path'); + +const circleCIArtifactsUtils = require('./circle-ci-artifacts-utils.js'); + +const { + generateAndroidArtifacts, + generateiOSArtifacts, +} = require('./release-utils'); + +const { + downloadHermesSourceTarball, + expandHermesSourceTarball, +} = require('./hermes/hermes-utils.js'); /* * Android related utils - leverages android tooling @@ -35,12 +49,12 @@ const launchEmulator = emulatorName => { // from docs: "When using the detached option to start a long-running process, the process will not stay running in the background after the parent exits unless it is provided with a stdio configuration that is not connected to the parent. If the parent's stdio is inherited, the child will remain attached to the controlling terminal." // here: https://nodejs.org/api/child_process.html#optionsdetached - const cp = spawn(emulatorCommand, [`@${emulatorName}`], { + const child_process = spawn(emulatorCommand, [`@${emulatorName}`], { detached: true, stdio: 'ignore', }); - cp.unref(); + child_process.unref(); }; function tryLaunchEmulator() { @@ -60,7 +74,20 @@ function tryLaunchEmulator() { }; } -function launchAndroidEmulator() { +function hasConnectedDevice() { + const physicalDevices = exec('adb devices | grep -v emulator', {silent: true}) + .stdout.trim() + .split('\n') + .slice(1); + return physicalDevices.length > 0; +} + +function maybeLaunchAndroidEmulator() { + if (hasConnectedDevice()) { + console.info('Already have a device connected. Skip launching emulator.'); + return; + } + const result = tryLaunchEmulator(); if (result.success) { console.info('Successfully launched emulator.'); @@ -97,15 +124,157 @@ function isPackagerRunning( } // this is a very limited implementation of how this should work -// literally, this is macos only -// a more robust implementation can be found here: -// https://github.com/react-native-community/cli/blob/7c003f2b1d9d80ec5c167614ba533a004272c685/packages/cli-platform-android/src/commands/runAndroid/index.ts#L195 -function launchPackagerInSeparateWindow() { - exec("open -a 'Terminal' ./scripts/packager.sh"); +function launchPackagerInSeparateWindow(folderPath) { + const command = `tell application "Terminal" to do script "cd ${folderPath} && yarn start"`; + exec(`osascript -e '${command}' >/dev/null </utils/. + cp( + `${reactNativePackagePath}/sdks/hermes-engine/utils/*.sh`, + `${reactNativePackagePath}/sdks/hermes/utils/.`, + ); + + // for this scenario, we only need to create the debug build + // (env variable PRODUCTION defines that podspec side) + const buildTypeiOSArtifacts = 'Debug'; + + // the android ones get set into /private/tmp/maven-local + const localMavenPath = '/private/tmp/maven-local'; + + // Generate native files for iOS + const hermesPath = generateiOSArtifacts( + jsiFolder, + hermesCoreSourceFolder, + buildTypeiOSArtifacts, + localMavenPath, + ); + + return hermesPath; +} + +/** + * It prepares the artifacts required to run a new project created from the template + * + * Parameters: + * - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them. + * - @mavenLocalPath path to the local maven repo that is needed by Android. + * - @localNodeTGZPath path where we want to store the react-native tgz. + * - @releaseVersion the version that is about to be released. + * - @buildType the type of build we want to execute if we build locally. + * - @reactNativePackagePath the path to the react native package within the repo. + * + * Returns: + * - @hermesPath the path to hermes for iOS + */ +async function prepareArtifacts( + circleCIArtifacts, + mavenLocalPath, + localNodeTGZPath, + releaseVersion, + buildType, + reactNativePackagePath, +) { + return circleCIArtifacts != null + ? await downloadArtifactsFromCircleCI( + circleCIArtifacts, + mavenLocalPath, + localNodeTGZPath, + ) + : buildArtifactsLocally(releaseVersion, buildType, reactNativePackagePath); } module.exports = { - launchAndroidEmulator, + checkPackagerRunning, + maybeLaunchAndroidEmulator, isPackagerRunning, launchPackagerInSeparateWindow, + setupCircleCIArtifacts, + prepareArtifacts, }; diff --git a/scripts/bump-oss-version.js b/scripts/trigger-react-native-release.js old mode 100755 new mode 100644 similarity index 63% rename from scripts/bump-oss-version.js rename to scripts/trigger-react-native-release.js index bf8c3d8e91394a..f1f79489686714 --- a/scripts/bump-oss-version.js +++ b/scripts/trigger-react-native-release.js @@ -14,14 +14,21 @@ * This script walks a releaser through bumping the version for a release * It will commit the appropriate tags to trigger the CircleCI jobs. */ -const {exit} = require('shelljs'); +const {exit, echo} = require('shelljs'); +const chalk = require('chalk'); const yargs = require('yargs'); const inquirer = require('inquirer'); const request = require('request'); +const path = require('path'); const {getBranchName, exitIfNotOnGit} = require('./scm-utils'); const {parseVersion, isReleaseBranch} = require('./version-utils'); const {failIfTagExists} = require('./release-utils'); +const checkForGitChanges = require('./monorepo/check-for-git-changes'); +const forEachPackage = require('./monorepo/for-each-package'); +const detectPackageUnreleasedChanges = require('./monorepo/bump-all-updated-packages/bump-utils.js'); + +const ROOT_LOCATION = path.join(__dirname, '..'); let argv = yargs .option('r', { @@ -42,7 +49,7 @@ let argv = yargs .check(() => { const branch = exitIfNotOnGit( () => getBranchName(), - "Not in git. You can't invoke bump-oss-versions.js from outside a git repo.", + "Not in git. You can't invoke trigger-react-native-release from outside a git repo.", ); exitIfNotOnReleaseBranch(branch); return true; @@ -57,6 +64,61 @@ function exitIfNotOnReleaseBranch(branch) { } } +const buildExecutor = + (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => + async () => { + const {name: packageName} = packageManifest; + if (packageManifest.private) { + return; + } + + if ( + detectPackageUnreleasedChanges( + packageRelativePathFromRoot, + packageName, + ROOT_LOCATION, + ) + ) { + // if I enter here, I want to throw an error upward + throw new Error( + `Package ${packageName} has unreleased changes. Please release it first.`, + ); + } + }; + +const buildAllExecutors = () => { + const executors = []; + + forEachPackage( + (...params) => { + executors.push(buildExecutor(...params)); + }, + [ + 'assets', + 'eslint-config-react-native-community', + 'eslint-plugin-react-native-community', + 'normalize-color', + 'polyfills', + ], + ); + + return executors; +}; + +async function exitIfUnreleasedPackages() { + // use the other script to verify that there's no packages in the monorepo + // that have changes that haven't been released + + const executors = buildAllExecutors(); + for (const executor of executors) { + await executor().catch(error => { + echo(chalk.red(error)); + // need to throw upward + throw error; + }); + } +} + function triggerReleaseWorkflow(options) { return new Promise((resolve, reject) => { request(options, function (error, response, body) { @@ -72,8 +134,26 @@ function triggerReleaseWorkflow(options) { async function main() { const branch = exitIfNotOnGit( () => getBranchName(), - "Not in git. You can't invoke bump-oss-versions.js from outside a git repo.", + "Not in git. You can't invoke trigger-react-native-release from outside a git repo.", ); + + // check for uncommitted changes + if (checkForGitChanges()) { + echo( + chalk.red( + 'Found uncommitted changes. Please commit or stash them before running this script', + ), + ); + exit(1); + } + + // now check for unreleased packages + try { + await exitIfUnreleasedPackages(); + } catch (error) { + exit(1); + } + const token = argv.token; const releaseVersion = argv.toVersion; failIfTagExists(releaseVersion, 'release'); diff --git a/scripts/update-template-package.js b/scripts/update-template-package.js new file mode 100644 index 00000000000000..77f0b74ecc5397 --- /dev/null +++ b/scripts/update-template-package.js @@ -0,0 +1,51 @@ +/** + * 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. + * + * @format + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const {applyPackageVersions} = require('./npm-utils'); + +/** + * Updates the react-native template package.json with + * dependencies in `dependencyMap`. + * + * `dependencyMap` is a dict of package name to its version + * ex. {"react-native": "0.23.0", "other-dep": "nightly"} + */ +function updateTemplatePackage(dependencyMap) { + const jsonPath = path.join(__dirname, '../template/package.json'); + const templatePackageJson = require(jsonPath); + + const updatedPackageJson = applyPackageVersions( + templatePackageJson, + dependencyMap, + ); + + fs.writeFileSync( + jsonPath, + JSON.stringify(updatedPackageJson, null, 2) + '\n', + 'utf-8', + ); +} + +if (require.main === module) { + const dependencyMapStr = process.argv[2]; + if (!dependencyMapStr) { + console.error( + 'Please provide a json string of package name and their version. Ex. \'{"packageName":"0.23.0"}\'', + ); + process.exit(1); + } + + updateTemplatePackage(JSON.parse(dependencyMapStr)); +} + +module.exports = updateTemplatePackage; diff --git a/yarn.lock b/yarn.lock index 5b8b9e86237352..bf6ca93d7d020a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1684,7 +1684,7 @@ serve-static "^1.13.1" ws "^7.5.1" -"@react-native-community/cli-tools@10.1.1", "@react-native-community/cli-tools@^10.1.1": +"@react-native-community/cli-tools@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-10.1.1.tgz#fa66e509c0d3faa31f7bb87ed7d42ad63f368ddd" integrity sha512-+FlwOnZBV+ailEzXjcD8afY2ogFEBeHOw/8+XXzMgPaquU2Zly9B+8W089tnnohO3yfiQiZqkQlElP423MY74g==