From 8fe2b591c7e073d629e95cd7b67aa1dfa96ece38 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 27 Jun 2022 01:19:36 -0700 Subject: [PATCH] Move LocalPodspecPatch to dedicated file (#34025) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/34025 This diff moves the monkeypatch LocalPodspecPatch to a dedicated ruby file. It also adds test for that ## Changelog [iOS][Changed] - Move LocalPodspecPatch to dedicated file Reviewed By: cortinico Differential Revision: D37069361 fbshipit-source-id: 28fddb197484f45aa20ccac516c874e79448e999 --- .../__tests__/local_podspec_patch-test.rb | 171 ++++++++++++++++++ .../cocoapods/__tests__/test_utils/DirMock.rb | 33 ++++ .../__tests__/test_utils/FileMock.rb | 17 ++ .../test_utils/LocalPodspecPatchMock.rb | 14 ++ .../cocoapods/__tests__/test_utils/PodMock.rb | 29 +++ scripts/cocoapods/local_podspec_patch.rb | 51 ++++++ scripts/react_native_pods.rb | 46 +---- 7 files changed, 317 insertions(+), 44 deletions(-) create mode 100644 scripts/cocoapods/__tests__/local_podspec_patch-test.rb create mode 100644 scripts/cocoapods/__tests__/test_utils/LocalPodspecPatchMock.rb create mode 100644 scripts/cocoapods/local_podspec_patch.rb diff --git a/scripts/cocoapods/__tests__/local_podspec_patch-test.rb b/scripts/cocoapods/__tests__/local_podspec_patch-test.rb new file mode 100644 index 00000000000000..b0e8692bbd972d --- /dev/null +++ b/scripts/cocoapods/__tests__/local_podspec_patch-test.rb @@ -0,0 +1,171 @@ +# 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. + +require "test/unit" +require "json" +require_relative "../local_podspec_patch.rb" +require_relative "./test_utils/FileMock.rb" +require_relative "./test_utils/DirMock.rb" +require_relative "./test_utils/PodMock.rb" +require_relative "./test_utils/LocalPodspecPatchMock.rb" + +class LocalPodspecPatchTests < Test::Unit::TestCase + def setup + File.enable_testing_mode! + Dir.enable_testing_mode! + end + + def teardown + File.reset() + Dir.reset() + end + + # =================== # + # Test - Pods To Update # + # =================== # + + def test_podsToUpdate_whenNoFilesExists_returnLocalPodspecs + # Arrange + react_native_path = "../node_modules/react-native" + globs = ["a/path/to/boost.podspec", "a/path/to/DoubleConversion.podspec"] + mocked_pwd = "a/path/to" + Dir.mocked_existing_globs(globs, "#{react_native_path}/third-party-podspecs/*") + Dir.set_pwd(mocked_pwd) + + # Act + local_podspec = LocalPodspecPatch.pods_to_update(:react_native_path => react_native_path) + + # Assert + assert_equal(local_podspec, []) + assert_equal(Dir.glob_invocation, ["#{react_native_path}/third-party-podspecs/*"]) + assert_equal(File.exist_invocation_params, [ + File.join(mocked_pwd, "Pods/Local Podspecs", "boost.podspec.json"), + File.join(mocked_pwd, "Pods/Local Podspecs", "DoubleConversion.podspec.json"), + ]) + end + + def test_podsToUpdate_whenFilesExistsWithSameVersions_returnsEmpty + # Arrange + react_native_path = "../node_modules/react-native" + globs = ["a/path/to/boost.podspec", "a/path/to/DoubleConversion.podspec"] + mocked_pwd = "a/path/to" + prepare_PodsToUpdate_test_withMatchingVersions(react_native_path, globs, mocked_pwd) + + # Act + local_podspec = LocalPodspecPatch.pods_to_update(:react_native_path => react_native_path) + + # Assert + assert_equal(local_podspec, []) + assert_equal(Dir.glob_invocation, ["#{react_native_path}/third-party-podspecs/*"]) + assert_equal(File.exist_invocation_params, [ + File.join(mocked_pwd, "Pods/Local Podspecs", "boost.podspec.json"), + File.join(mocked_pwd, "Pods/Local Podspecs", "DoubleConversion.podspec.json"), + ]) + end + + def test_podsToUpdate_whenFilesExistsWithDifferentVersions_returnsThem + # Arrange + react_native_path = "../node_modules/react-native" + globs = ["a/path/to/boost.podspec", "a/path/to/DoubleConversion.podspec"] + mocked_pwd = "a/path/to" + prepare_PodsToUpdate_test_withDifferentVersions(react_native_path, globs, mocked_pwd) + + # Act + local_podspec = LocalPodspecPatch.pods_to_update(:react_native_path => react_native_path) + + # Assert + assert_equal(local_podspec, [ + "boost", + "DoubleConversion" + ]) + assert_equal(Dir.glob_invocation, ["#{react_native_path}/third-party-podspecs/*"]) + assert_equal(File.exist_invocation_params, [ + File.join(mocked_pwd, "Pods/Local Podspecs", "boost.podspec.json"), + File.join(mocked_pwd, "Pods/Local Podspecs", "DoubleConversion.podspec.json"), + ]) + end + + # ======================================== # + # Test - Patch Detect Changes With Podfile # + # ======================================== # + def test_patchDetectChangesWithPodfile_whenAlreadyChanged_returnSameChangeSet() + local_pods = [ + "boost", + "DoubleConversion" + ] + LocalPodspecPatch.mock_local_podspecs(local_pods) + changes = { + :unchanged => ["some_pod"], + :changed => ["boost", "DoubleConversion", "another_pod"] + } + + Pod::Lockfile.prepend(LocalPodspecPatch) + + new_changes = Pod::Lockfile.new().patch_detect_changes_with_podfile(changes) + + assert_equal(new_changes, { + :unchanged => ["some_pod"], + :changed => ["boost", "DoubleConversion", "another_pod"] + }) + end + + def test_patchDetectChangesWithPodfile_whenLocalPodsUnchanged_movesLocalPodsToChangeSet() + pods = [ + "boost", + "DoubleConversion" + ] + LocalPodspecPatch.mock_local_podspecs(pods) + changes = { + :unchanged => ["first_pod", "boost", "DoubleConversion"], + :changed => ["another_pod"] + } + + Pod::Lockfile.prepend(LocalPodspecPatch) + + new_changes = Pod::Lockfile.new().patch_detect_changes_with_podfile(changes) + + assert_equal(new_changes, { + :unchanged => ["first_pod"], + :changed => ["another_pod", "boost", "DoubleConversion"] + }) + end + + # ========= # + # Utilities # + # ========= # + def prepare_PodsToUpdate_test_withMatchingVersions(react_native_path, globs, mocked_pwd) + File.mocked_existing_files([ + "a/path/to/Pods/Local Podspecs/boost.podspec.json", + "a/path/to/Pods/Local Podspecs/DoubleConversion.podspec.json" + ]) + File.files_to_read({ + "a/path/to/Pods/Local Podspecs/boost.podspec.json" => "{ \"version\": \"0.0.1\"}", + "a/path/to/Pods/Local Podspecs/DoubleConversion.podspec.json" => "{ \"version\": \"1.0.1\"}", + }) + Dir.mocked_existing_globs(globs, "#{react_native_path}/third-party-podspecs/*") + Dir.set_pwd(mocked_pwd) + Pod::Specification.specs_from_file({ + "../node_modules/react-native/third-party-podspecs/boost.podspec" => Pod::PodSpecMock.new(:version => "0.0.1"), + "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" => Pod::PodSpecMock.new(:version => "1.0.1"), + }) + end + + def prepare_PodsToUpdate_test_withDifferentVersions(react_native_path, globs, mocked_pwd) + File.mocked_existing_files([ + "a/path/to/Pods/Local Podspecs/boost.podspec.json", + "a/path/to/Pods/Local Podspecs/DoubleConversion.podspec.json" + ]) + File.files_to_read({ + "a/path/to/Pods/Local Podspecs/boost.podspec.json" => "{ \"version\": \"0.0.1\"}", + "a/path/to/Pods/Local Podspecs/DoubleConversion.podspec.json" => "{ \"version\": \"1.0.1\"}", + }) + Dir.mocked_existing_globs(globs, "#{react_native_path}/third-party-podspecs/*") + Dir.set_pwd(mocked_pwd) + Pod::Specification.specs_from_file({ + "../node_modules/react-native/third-party-podspecs/boost.podspec" => Pod::PodSpecMock.new(:version => "0.1.1"), + "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" => Pod::PodSpecMock.new(:version => "1.1.1"), + }) + end +end diff --git a/scripts/cocoapods/__tests__/test_utils/DirMock.rb b/scripts/cocoapods/__tests__/test_utils/DirMock.rb index 329ab5c57ebfb1..9c7a91328a76ed 100644 --- a/scripts/cocoapods/__tests__/test_utils/DirMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/DirMock.rb @@ -9,6 +9,11 @@ class Dir @@exist_invocation_params = [] @@mocked_existing_dirs = [] + @@glob_invocation = [] + @@mocked_existing_globs = {} + + @@pwd = nil + # Monkey patched exists? method. # It is used also by the test runner, so it can't start monkey patched # To use this, invoke the `is_testing` method before starting your test. @@ -33,6 +38,31 @@ def self.mocked_existing_dirs(dirs) @@mocked_existing_dirs = dirs end + # Set what the `glob` function should return + def self.mocked_existing_globs(globs, path) + @@mocked_existing_globs[path] = globs + end + + def self.glob_invocation + return @@glob_invocation + end + + def self.glob(path) + @@glob_invocation.push(path) + return @@mocked_existing_globs[path] + end + + def self.set_pwd(pwd) + @@pwd = pwd + end + + def self.pwd + if @@pwd != nil + return @@pwd + end + return pwd + end + # Turn on the mocking features of the File mock def self.enable_testing_mode!() @@is_testing = true @@ -40,8 +70,11 @@ def self.enable_testing_mode!() # Resets all the settings for the File mock def self.reset() + @@pwd = nil @@mocked_existing_dirs = [] @@is_testing = false @@exist_invocation_params = [] + @@glob_invocation = [] + @@mocked_existing_globs = {} end end diff --git a/scripts/cocoapods/__tests__/test_utils/FileMock.rb b/scripts/cocoapods/__tests__/test_utils/FileMock.rb index 7099047cb2d350..dc1ebee2395d50 100644 --- a/scripts/cocoapods/__tests__/test_utils/FileMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/FileMock.rb @@ -16,6 +16,8 @@ class File @@open_invocation_count = 0 @@open_files = [] + + @@files_to_read = {} attr_reader :collected_write attr_reader :fsync_invocation_count @@ -91,6 +93,10 @@ def self.open_files return @@open_files end + def self.file_invocation_params + return @@file_invocation_params + end + def write(text) @collected_write.push(text.to_s) end @@ -99,6 +105,15 @@ def fsync() @fsync_invocation_count += 1 end + + def self.files_to_read(files) + @@files_to_read = files + end + + def self.read(filepath) + return @@files_to_read[filepath] + end + # Resets all the settings for the File mock def self.reset() @@delete_invocation_count = 0 @@ -108,7 +123,9 @@ def self.reset() @@open_invocation_count = 0 @@mocked_existing_files = [] @@is_testing = false + @@file_invocation_params = [] @@exist_invocation_params = [] + @@files_to_read = {} end diff --git a/scripts/cocoapods/__tests__/test_utils/LocalPodspecPatchMock.rb b/scripts/cocoapods/__tests__/test_utils/LocalPodspecPatchMock.rb new file mode 100644 index 00000000000000..7548ae444b5b83 --- /dev/null +++ b/scripts/cocoapods/__tests__/test_utils/LocalPodspecPatchMock.rb @@ -0,0 +1,14 @@ +# 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. + +module LocalPodspecPatch + def self.mock_local_podspecs(pods) + @@local_podspecs = pods + end + + def reset() + @@local_podspecs = [] + end +end diff --git a/scripts/cocoapods/__tests__/test_utils/PodMock.rb b/scripts/cocoapods/__tests__/test_utils/PodMock.rb index 0171c2dd4dd032..fcbd46a65e305d 100644 --- a/scripts/cocoapods/__tests__/test_utils/PodMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/PodMock.rb @@ -83,4 +83,33 @@ def self.reset() @@executed_commands = [] end end + + class Specification + @@specs_from_file = {} + + def self.specs_from_file(specs) + @@specs_from_file = specs + end + + def self.from_file(path) + return @@specs_from_file[path] + end + + def reset() + @@specs_from_file = {} + end + end + + class PodSpecMock + attr_reader :version + + def initialize(version: "0.0.1") + @version = version + end + end + + class Lockfile + def initialize() + end + end end diff --git a/scripts/cocoapods/local_podspec_patch.rb b/scripts/cocoapods/local_podspec_patch.rb new file mode 100644 index 00000000000000..4d5481518dd4aa --- /dev/null +++ b/scripts/cocoapods/local_podspec_patch.rb @@ -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. + +# Monkeypatch of `Pod::Lockfile` to ensure automatic update of dependencies integrated with a local podspec when their version changed. +# This is necessary because local podspec dependencies must be otherwise manually updated. +module LocalPodspecPatch + # Returns local podspecs whose versions differ from the one in the `react-native` package. + def self.pods_to_update(react_native_path: "../node_modules/react-native") + @@local_podspecs = Dir.glob("#{react_native_path}/third-party-podspecs/*").map { |file| File.basename(file, ".podspec") } + @@local_podspecs = @@local_podspecs.select do |podspec_name| + + # Read local podspec to determine the cached version + local_podspec_path = File.join( + Dir.pwd, "Pods/Local Podspecs/#{podspec_name}.podspec.json" + ) + + # Local podspec cannot be outdated if it does not exist, yet + next unless File.exist?(local_podspec_path) + + local_podspec = File.read(local_podspec_path) + local_podspec_json = JSON.parse(local_podspec) + local_version = local_podspec_json["version"] + + # Read the version from a podspec from the `react-native` package + podspec_path = "#{react_native_path}/third-party-podspecs/#{podspec_name}.podspec" + current_podspec = Pod::Specification.from_file(podspec_path) + current_version = current_podspec.version.to_s + current_version != local_version + end + @@local_podspecs + end + + # Patched `detect_changes_with_podfile` method + def detect_changes_with_podfile(podfile) + Pod::UI.puts "Invoke detect_changes_with_podfile patched method".red + changes = super(podfile) + return patch_detect_changes_with_podfile(changes) + end + + def patch_detect_changes_with_podfile(changes) + @@local_podspecs.each do |local_podspec| + next unless changes[:unchanged].include?(local_podspec) + + changes[:unchanged].delete(local_podspec) + changes[:changed] << local_podspec + end + changes + end +end diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index 9c5d0c00ae9f25..6f9a54b96247fc 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -12,6 +12,7 @@ require_relative './cocoapods/codegen.rb' require_relative './cocoapods/utils.rb' require_relative './cocoapods/new_architecture.rb' +require_relative './cocoapods/local_podspec_patch.rb' $CODEGEN_OUTPUT_DIR = 'build/generated/ios' $CODEGEN_COMPONENT_DIR = 'react/renderer/components' @@ -121,7 +122,7 @@ def use_react_native! (options={}) use_flipper_pods(flipper_configuration.versions, :configurations => flipper_configuration.configurations) end - pods_to_update = LocalPodspecPatch.pods_to_update(options) + pods_to_update = LocalPodspecPatch.pods_to_update(:react_native_path => prefix) if !pods_to_update.empty? if Pod::Lockfile.public_instance_methods.include?(:detect_changes_with_podfile) Pod::Lockfile.prepend(LocalPodspecPatch) @@ -497,46 +498,3 @@ def __apply_Xcode_12_5_M1_post_install_workaround(installer) time_header = "#{Pod::Config.instance.installation_root.to_s}/Pods/RCT-Folly/folly/portability/Time.h" `sed -i -e $'s/ && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)//' #{time_header}` end - -# Monkeypatch of `Pod::Lockfile` to ensure automatic update of dependencies integrated with a local podspec when their version changed. -# This is necessary because local podspec dependencies must be otherwise manually updated. -module LocalPodspecPatch - # Returns local podspecs whose versions differ from the one in the `react-native` package. - def self.pods_to_update(react_native_options) - prefix = react_native_options[:path] ||= "../node_modules/react-native" - @@local_podspecs = Dir.glob("#{prefix}/third-party-podspecs/*").map { |file| File.basename(file, ".podspec") } - @@local_podspecs = @@local_podspecs.select do |podspec_name| - # Read local podspec to determine the cached version - local_podspec_path = File.join( - Dir.pwd, "Pods/Local Podspecs/#{podspec_name}.podspec.json" - ) - - # Local podspec cannot be outdated if it does not exist, yet - next unless File.file?(local_podspec_path) - - local_podspec = File.read(local_podspec_path) - local_podspec_json = JSON.parse(local_podspec) - local_version = local_podspec_json["version"] - - # Read the version from a podspec from the `react-native` package - podspec_path = "#{prefix}/third-party-podspecs/#{podspec_name}.podspec" - current_podspec = Pod::Specification.from_file(podspec_path) - - current_version = current_podspec.version.to_s - current_version != local_version - end - @@local_podspecs - end - - # Patched `detect_changes_with_podfile` method - def detect_changes_with_podfile(podfile) - changes = super(podfile) - @@local_podspecs.each do |local_podspec| - next unless changes[:unchanged].include?(local_podspec) - - changes[:unchanged].delete(local_podspec) - changes[:changed] << local_podspec - end - changes - end -end