From b3fe351c5e87391e3818c668bd0aed18226192d5 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 18 Jun 2020 12:42:50 +0200 Subject: [PATCH] fix: Allow users to enable Flipper (#121) --- .rubocop.yml | 24 +++++++- Gemfile.lock | 19 +++--- android/app/build.gradle | 32 +++++++++- .../com/sample/react/ReactNativeFlipper.kt | 58 +++++++++++++++++++ .../sample/react/TestAppReactNativeHost.kt | 17 +++++- ios/ReactTestApp.xcodeproj/project.pbxproj | 4 +- ios/ReactTestApp/ReactInstance.swift | 13 +++++ .../ReactTestApp-Bridging-Header.h | 9 +++ ios/test_app.rb | 55 ++++++++++++++---- ios/use_react_native-0.60.rb | 2 +- ios/use_react_native-0.61.rb | 2 +- ios/use_react_native-0.62.rb | 36 +++++++++++- test/test_test_app.rb | 23 ++++---- 13 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 android/app/src/flipper/java/com/sample/react/ReactNativeFlipper.kt diff --git a/.rubocop.yml b/.rubocop.yml index 56c78a0d7..565600e44 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,17 +1,33 @@ AllCops: Exclude: - "**/node_modules/**/*" - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.6 Layout/LineLength: Max: 100 +Lint/UnusedMethodArgument: + AllowUnusedKeywordArguments: true + Metrics/AbcSize: Enabled: false Metrics/MethodLength: Enabled: false +Metrics/CyclomaticComplexity: + IgnoredMethods: [ + make_project!, + use_react_native!, + use_test_app_internal! + ] + +Metrics/PerceivedComplexity: + IgnoredMethods: [ + use_react_native!, + use_test_app_internal! + ] + Naming/FileName: Exclude: - !ruby/regexp /-\d+\.\d+\.rb$/ @@ -34,3 +50,9 @@ Style/HashTransformKeys: Style/HashTransformValues: Enabled: true + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: comma diff --git a/Gemfile.lock b/Gemfile.lock index 4c95d3b68..ef5ef8263 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,27 @@ GEM remote: https://rubygems.org/ specs: - ast (2.4.0) - jaro_winkler (1.5.4) - minitest (5.14.0) + ast (2.4.1) + minitest (5.14.1) parallel (1.19.1) - parser (2.7.0.5) + parser (2.7.1.3) ast (~> 2.4.0) rainbow (3.0.0) + regexp_parser (1.7.1) rexml (3.2.4) - rubocop (0.80.1) - jaro_winkler (~> 1.5.1) + rubocop (0.85.1) parallel (~> 1.10) parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.7) rexml + rubocop-ast (>= 0.0.3) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (0.0.3) + parser (>= 2.7.0.1) ruby-progressbar (1.10.1) - unicode-display_width (1.6.1) + unicode-display_width (1.7.0) PLATFORMS ruby diff --git a/android/app/build.gradle b/android/app/build.gradle index adedb94a3..f3925b365 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlinVersion = "1.3.70" + ext.kotlinVersion = "1.3.72" def buildscriptDir = buildscript.sourceFile.getParent() apply from: "$buildscriptDir/../test-app-util.gradle" @@ -34,12 +34,22 @@ def testAppDir = file("$projectDir/../../") apply from: file("${testAppDir}/test-app.gradle") applyTestAppModule(project, "com.sample") -project.ext.react = [enableHermes: true] +project.ext.react = [ + enableFlipper: hasProperty('FLIPPER_VERSION'), + enableHermes: true, +] android { compileSdkVersion 29 buildToolsVersion "29.0.3" + // TODO: Remove this block when minSdkVersion >= 24. See + // https://stackoverflow.com/q/53402639 for details. + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + defaultConfig { applicationId "com.sample" minSdkVersion 21 @@ -55,6 +65,12 @@ android { pickFirst "lib/x86_64/libc++_shared.so" pickFirst "lib/x86/libc++_shared.so" } + + sourceSets { + if (project.ext.react.enableFlipper) { + debug.java.srcDirs += 'src/flipper/java' + } + } } def hermesEnginePath = findNodeModulesPath(projectDir, "hermes-engine") ?: findNodeModulesPath(projectDir, "hermesvm") @@ -86,4 +102,16 @@ dependencies { testImplementation "junit:junit:4.13" androidTestImplementation "androidx.test.ext:junit:1.1.1" androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0" + + if (project.ext.react.enableFlipper) { + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { + exclude group:'com.facebook.fbjni' + } + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + } } diff --git a/android/app/src/flipper/java/com/sample/react/ReactNativeFlipper.kt b/android/app/src/flipper/java/com/sample/react/ReactNativeFlipper.kt new file mode 100644 index 000000000..7636fe929 --- /dev/null +++ b/android/app/src/flipper/java/com/sample/react/ReactNativeFlipper.kt @@ -0,0 +1,58 @@ +package com.sample.react + +import android.content.Context +import com.facebook.flipper.android.AndroidFlipperClient +import com.facebook.flipper.android.utils.FlipperUtils +import com.facebook.flipper.core.FlipperClient +import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin +import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin +import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin +import com.facebook.flipper.plugins.inspector.DescriptorMapping +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin +import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor +import com.facebook.flipper.plugins.network.NetworkFlipperPlugin +import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin +import com.facebook.react.ReactInstanceManager +import com.facebook.react.ReactInstanceManager.ReactInstanceEventListener +import com.facebook.react.bridge.ReactContext +import com.facebook.react.modules.network.NetworkingModule + +@Suppress("unused") +object ReactNativeFlipper { + @JvmStatic + fun initialize(context: Context?, reactInstanceManager: ReactInstanceManager) { + if (!FlipperUtils.shouldEnableFlipper(context)) { + return + } + + val client: FlipperClient = AndroidFlipperClient.getInstance(context) + client.addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())) + client.addPlugin(DatabasesFlipperPlugin(context)) + client.addPlugin(SharedPreferencesFlipperPlugin(context)) + client.addPlugin(CrashReporterPlugin.getInstance()) + + val networkFlipperPlugin = NetworkFlipperPlugin() + NetworkingModule.setCustomClientBuilder { builder -> + builder.addNetworkInterceptor(FlipperOkhttpInterceptor(networkFlipperPlugin)) + } + client.addPlugin(networkFlipperPlugin) + + client.start() + + // FrescoFlipperPlugin needs to ensure that ImagePipelineFactory is + // initialized and must therefore be run after all native modules have + // been initialized. + val reactContext = reactInstanceManager.currentReactContext + if (reactContext == null) { + reactInstanceManager.addReactInstanceEventListener( + object : ReactInstanceEventListener { + override fun onReactContextInitialized(reactContext: ReactContext) { + reactInstanceManager.removeReactInstanceEventListener(this) + reactContext.runOnNativeModulesQueueThread { client.addPlugin(FrescoFlipperPlugin()) } + } + }) + } else { + client.addPlugin(FrescoFlipperPlugin()) + } + } +} diff --git a/android/app/src/main/java/com/sample/react/TestAppReactNativeHost.kt b/android/app/src/main/java/com/sample/react/TestAppReactNativeHost.kt index b944bbbe4..d52dec7cc 100644 --- a/android/app/src/main/java/com/sample/react/TestAppReactNativeHost.kt +++ b/android/app/src/main/java/com/sample/react/TestAppReactNativeHost.kt @@ -3,6 +3,7 @@ package com.sample.react import android.app.Activity import android.app.Application import android.content.Context +import android.util.Log import com.facebook.react.PackageList import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactNativeHost @@ -83,6 +84,20 @@ class TestAppReactNativeHost @Inject constructor( SoLoader.init(application, false) reactInstanceManager.createReactContextInBackground() + + if (BuildConfig.DEBUG) { + try { + Class.forName("com.sample.react.ReactNativeFlipper") + .getMethod("initialize", Context::class.java, ReactInstanceManager::class.java) + .invoke(null, application, reactInstanceManager) + } catch (e: ClassNotFoundException) { + Log.i( + "ReactTestApp", + "To use Flipper, define `FLIPPER_VERSION` in your `gradle.properties`. " + + "If you're using React Native 0.62, you should use `FLIPPER_VERSION=0.33.1`." + ) + } + } } override fun createReactInstanceManager(): ReactInstanceManager { @@ -116,7 +131,7 @@ class TestAppReactNativeHost @Inject constructor( val latch = CountDownLatch(1) var packagerIsRunning = false DevServerHelper( - DevInternalSettings.withoutNativeDeltaClient(context) {}, + DevInternalSettings(context) {}, context.packageName, BundleStatusProvider { BundleStatus() } ).isPackagerRunning { diff --git a/ios/ReactTestApp.xcodeproj/project.pbxproj b/ios/ReactTestApp.xcodeproj/project.pbxproj index 69293b44c..d6f6b1941 100644 --- a/ios/ReactTestApp.xcodeproj/project.pbxproj +++ b/ios/ReactTestApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -277,7 +277,7 @@ }; }; buildConfigurationList = 19ECD0CD232ED425003D8557 /* Build configuration list for PBXProject "ReactTestApp" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 11.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( diff --git a/ios/ReactTestApp/ReactInstance.swift b/ios/ReactTestApp/ReactInstance.swift index c41855016..b7e349e7a 100644 --- a/ios/ReactTestApp/ReactInstance.swift +++ b/ios/ReactTestApp/ReactInstance.swift @@ -71,6 +71,19 @@ final class ReactInstance: NSObject, RCTBridgeDelegate, RCTTurboModuleLookupDele object: nil ) #endif + + #if USE_FLIPPER + if let flipper = FlipperClient.shared() { + flipper.add(FlipperKitLayoutPlugin( + rootNode: application, + with: SKDescriptorMapper() + )) + flipper.add(FKUserDefaultsPlugin(suiteName: nil)) + flipper.add(FlipperKitReactPlugin()) + flipper.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter())) + flipper.start() + } + #endif } init(forTestingPurposesOnly: Bool) { diff --git a/ios/ReactTestApp/ReactTestApp-Bridging-Header.h b/ios/ReactTestApp/ReactTestApp-Bridging-Header.h index 682436db3..7cae344da 100644 --- a/ios/ReactTestApp/ReactTestApp-Bridging-Header.h +++ b/ios/ReactTestApp/ReactTestApp-Bridging-Header.h @@ -17,5 +17,14 @@ #import #import +#if USE_FLIPPER +#import +#import +#import +#import +#import +#import +#endif + #import "React+Compatibility.h" #import "UIViewController+ReactTestApp.h" diff --git a/ios/test_app.rb b/ios/test_app.rb index 076581831..2079634e8 100644 --- a/ios/test_app.rb +++ b/ios/test_app.rb @@ -8,6 +8,8 @@ require('json') require('pathname') +@use_flipper = false + def assert(condition, message) raise message unless condition end @@ -81,9 +83,9 @@ def resources_pod(project_root, target_platform) 'source' => { 'git' => 'https://github.com/microsoft/react-native-test-app.git' }, 'platforms' => { 'ios' => '12.0', - 'osx' => '10.14' + 'osx' => '10.14', }, - 'resources' => resources + 'resources' => resources, } app_dir = File.dirname(app_manifest) @@ -93,7 +95,10 @@ def resources_pod(project_root, target_platform) Pathname.new(app_dir).relative_path_from(project_root).to_s end -# rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity +def use_flipper!(versions = {}) + @use_flipper = versions +end + def use_react_native!(project_root, target_platform) react_native = Pathname.new(resolve_module('react-native')) version = package_version(react_native.to_s) @@ -108,11 +113,11 @@ def use_react_native!(project_root, target_platform) raise "Unsupported React Native version: #{version[0]}" end - include_react_native!(react_native.relative_path_from(project_root).to_s, - target_platform, - project_root) + include_react_native!(react_native: react_native.relative_path_from(project_root).to_s, + target_platform: target_platform, + project_root: project_root, + use_flipper: @use_flipper) end -# rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity def make_project!(xcodeproj, project_root, target_platform) src_xcodeproj = File.join(__dir__, '..', target_platform.to_s, xcodeproj) @@ -146,7 +151,22 @@ def make_project!(xcodeproj, project_root, target_platform) next if target.name != 'ReactTestApp' target.build_configurations.each do |config| - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', version_macro] + use_flipper = config.name == 'Debug' && @use_flipper + + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + version_macro, + 'FB_SONARKIT_ENABLED=' + (use_flipper ? '1' : '0'), + 'USE_FLIPPER=' + (use_flipper ? '1' : '0'), + ] + + next unless use_flipper + + config.build_settings['OTHER_SWIFT_FLAGS'] ||= [ + '$(inherited)', + '-DFB_SONARKIT_ENABLED', + '-DUSE_FLIPPER', + ] end end app_project.save @@ -154,7 +174,6 @@ def make_project!(xcodeproj, project_root, target_platform) dst_xcodeproj end -# rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity def use_test_app_internal!(target_platform) assert(%i[ios macos].include?(target_platform), "Unsupported platform: #{target_platform}") @@ -188,15 +207,29 @@ def use_test_app_internal!(target_platform) end end - post_install do + post_install do |installer| puts '' puts 'NOTE' puts " `#{xcodeproj}` was sourced from `react-native-test-app`" puts ' All modifications will be overwritten next time you run `pod install`' puts '' + + installer.pods_project.targets.each do |target| + case target.name + when 'SwiftLint' + # Let SwiftLint inherit the deployment target from the Pods project + target.build_configurations.each do |config| + config.build_settings.delete('IPHONEOS_DEPLOYMENT_TARGET') + config.build_settings.delete('MACOSX_DEPLOYMENT_TARGET') + end + when 'YogaKit' # Flipper + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = '4.1' + end + end + end end end -# rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity class ReactTestAppTargets def initialize(podfile) diff --git a/ios/use_react_native-0.60.rb b/ios/use_react_native-0.60.rb index 7a052332f..375344fd6 100644 --- a/ios/use_react_native-0.60.rb +++ b/ios/use_react_native-0.60.rb @@ -6,7 +6,7 @@ # # rubocop:disable Layout/LineLength -def include_react_native!(react_native, target_platform, project_root) +def include_react_native!(react_native:, target_platform:, project_root:, use_flipper:) react_native = "#{react_native}-macos" if target_platform == :macos pod 'React', :path => react_native diff --git a/ios/use_react_native-0.61.rb b/ios/use_react_native-0.61.rb index cd809a6f4..5aac3e573 100644 --- a/ios/use_react_native-0.61.rb +++ b/ios/use_react_native-0.61.rb @@ -6,7 +6,7 @@ # # rubocop:disable Layout/LineLength -def include_react_native!(react_native, target_platform, project_root) +def include_react_native!(react_native:, target_platform:, project_root:, use_flipper:) react_native = "#{react_native}-macos" if target_platform == :macos pod 'FBLazyVector', :path => "#{react_native}/Libraries/FBLazyVector" diff --git a/ios/use_react_native-0.62.rb b/ios/use_react_native-0.62.rb index 9731ca01d..971ae48ba 100644 --- a/ios/use_react_native-0.62.rb +++ b/ios/use_react_native-0.62.rb @@ -6,7 +6,41 @@ # # rubocop:disable Layout/LineLength -def include_react_native!(react_native, _target_platform, _project_root) +def add_flipper_pods!(versions = {}) + versions['Flipper'] ||= '~> 0.33.1' + versions['DoubleConversion'] ||= '1.1.7' + versions['Flipper-Folly'] ||= '~> 2.1' + versions['Flipper-Glog'] ||= '0.3.6' + versions['Flipper-PeerTalk'] ||= '~> 0.0.4' + versions['Flipper-RSocket'] ||= '~> 1.0' + + pod 'FlipperKit', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FlipperKitLayoutPlugin', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/SKIOSNetworkPlugin', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FlipperKitUserDefaultsPlugin', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FlipperKitReactPlugin', versions['Flipper'], :configuration => 'Debug' + + # List all transitive dependencies for FlipperKit pods + # to avoid them being linked in Release builds + pod 'Flipper', versions['Flipper'], :configuration => 'Debug' + pod 'Flipper-DoubleConversion', versions['DoubleConversion'], :configuration => 'Debug' + pod 'Flipper-Folly', versions['Flipper-Folly'], :configuration => 'Debug' + pod 'Flipper-Glog', versions['Flipper-Glog'], :configuration => 'Debug' + pod 'Flipper-PeerTalk', versions['Flipper-PeerTalk'], :configuration => 'Debug' + pod 'Flipper-RSocket', versions['Flipper-RSocket'], :configuration => 'Debug' + pod 'FlipperKit/Core', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/CppBridge', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FBCxxFollyDynamicConvert', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FBDefines', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FKPortForwarding', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FlipperKitHighlightOverlay', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FlipperKitLayoutTextSearchable', versions['Flipper'], :configuration => 'Debug' + pod 'FlipperKit/FlipperKitNetworkPlugin', versions['Flipper'], :configuration => 'Debug' +end + +def include_react_native!(react_native:, target_platform:, project_root:, use_flipper:) + add_flipper_pods!(use_flipper) if use_flipper + pod 'FBLazyVector', :path => "#{react_native}/Libraries/FBLazyVector" pod 'FBReactNativeSpec', :path => "#{react_native}/Libraries/FBReactNativeSpec" pod 'RCTRequired', :path => "#{react_native}/Libraries/RCTRequired" diff --git a/test/test_test_app.rb b/test/test_test_app.rb index 9e0312312..cc010bb21 100644 --- a/test/test_test_app.rb +++ b/test/test_test_app.rb @@ -47,21 +47,20 @@ class TestTestApp < Minitest::Test fixture_path('with_resources'), fixture_path('with_resources', target.to_s), fixture_path('with_platform_resources'), - fixture_path('with_platform_resources', target.to_s) + fixture_path('with_platform_resources', target.to_s), ].each do |project_root| - begin - podspec_path = resources_pod(project_root, target) - manifest_path = app_manifest_path(project_root, podspec_path) - manifest = JSON.parse(File.read(manifest_path)) + podspec_path = resources_pod(project_root, target) + manifest_path = app_manifest_path(project_root, podspec_path) + manifest = JSON.parse(File.read(manifest_path)) - if project_root.to_s.include?('with_platform_resources') - assert_equal(platform_resources, manifest['resources'].sort) - else - assert_equal(resources, manifest['resources'].sort) - end - ensure - File.delete(manifest_path) + if project_root.to_s.include?('with_platform_resources') + assert_equal(platform_resources, manifest['resources'].sort) + else + assert_equal(resources, manifest['resources'].sort) end + + ensure + File.delete(manifest_path) end end end