From ca8174e15f77cbeecb7ff7a5a583abb668817777 Mon Sep 17 00:00:00 2001 From: Franco Meloni Date: Thu, 19 May 2022 14:57:04 -0700 Subject: [PATCH] Create script to automatically set CLANG_CXX_LANGUAGE_STANDARD on the client project (#33863) Summary: Currently this [section](https://reactnative.dev/docs/next/new-architecture-app-intro#ios-enable-c17-language-feature-support) of the Playbook tells us to set CLANG_CXX_LANGUAGE_STANDARD = "c++17" in the main app target for the new architecture to work. Would be nice to be able to automate that instead ## Changelog [iOS] [Added] - Cocoapods function to add the `CLANG_CXX_LANGUAGE_STANDARD` to all the targets if needed Pull Request resolved: https://github.com/facebook/react-native/pull/33863 Test Plan: I've created some unit tests for the newly added function. I've executed pod install and the ruby tests locally. Reviewed By: cipolleschi Differential Revision: D36484366 Pulled By: f-meloni fbshipit-source-id: 553b092e747bef11d82195619ae1058985fdc325 --- React-Core.podspec | 2 +- package.json | 1 + packages/rn-tester/Podfile | 2 + .../RNTesterPods.xcodeproj/project.pbxproj | 6 +- .../__tests__/new_architecture-test.rb | 133 ++++++++++++++++++ .../__tests__/test_utils/InstallerMock.rb | 36 ++++- scripts/cocoapods/new_architecture.rb | 30 ++++ 7 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 scripts/cocoapods/__tests__/new_architecture-test.rb create mode 100644 scripts/cocoapods/new_architecture.rb diff --git a/React-Core.podspec b/React-Core.podspec index bfc24c77a1fab7..0e90d6788aa2d3 100644 --- a/React-Core.podspec +++ b/React-Core.podspec @@ -47,7 +47,7 @@ Pod::Spec.new do |s| s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags s.header_dir = "React" s.framework = "JavaScriptCore" - s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/RCT-Folly\" \"${PODS_ROOT}/Headers/Public/React-hermes\" \"${PODS_ROOT}/Headers/Public/hermes-engine\" \"${PODS_ROOT}/Headers/Public/FlipperKit\" \"$(PODS_ROOT)/Headers/Public/ReactCommon\" \"$(PODS_ROOT)/Headers/Public/React-RCTFabric\"", "DEFINES_MODULE" => "YES", "GCC_PREPROCESSOR_DEFINITIONS" => "RCT_METRO_PORT=${RCT_METRO_PORT}" } + s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/RCT-Folly\" \"${PODS_ROOT}/Headers/Public/React-hermes\" \"${PODS_ROOT}/Headers/Public/hermes-engine\" \"${PODS_ROOT}/Headers/Public/FlipperKit\" \"$(PODS_ROOT)/Headers/Public/ReactCommon\" \"$(PODS_ROOT)/Headers/Public/React-RCTFabric\"", "DEFINES_MODULE" => "YES", "GCC_PREPROCESSOR_DEFINITIONS" => "RCT_METRO_PORT=${RCT_METRO_PORT}", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" } s.user_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\""} s.default_subspec = "Default" diff --git a/package.json b/package.json index 0f8c55c64f9931..38194cec574fbc 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "scripts/cocoapods/codegen.rb", "scripts/cocoapods/fabric.rb", "scripts/cocoapods/flipper.rb", + "scripts/cocoapods/new_architecture.rb", "scripts/react-native-xcode.sh", "sdks/hermes-engine", "sdks/hermesc", diff --git a/packages/rn-tester/Podfile b/packages/rn-tester/Podfile index ac63ccd992fb7e..91811ab85812e5 100644 --- a/packages/rn-tester/Podfile +++ b/packages/rn-tester/Podfile @@ -1,4 +1,5 @@ require_relative '../../scripts/react_native_pods' +require_relative '../../scripts/cocoapods/new_architecture' source 'https://cdn.cocoapods.org/' platform :ios, '12.4' @@ -65,4 +66,5 @@ end post_install do |installer| react_native_post_install(installer, @prefix_path) __apply_Xcode_12_5_M1_post_install_workaround(installer) + set_clang_cxx_language_standard_if_needed(installer) end diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj index 4a0f2a6ece4f97..c282bbf979607f 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -843,7 +843,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -913,6 +913,7 @@ "-ObjC", "-lc++", ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../.."; SDKROOT = iphoneos; WARNING_CFLAGS = ( "-Wextra", @@ -927,7 +928,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -989,6 +990,7 @@ "-ObjC", "-lc++", ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../.."; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; WARNING_CFLAGS = ( diff --git a/scripts/cocoapods/__tests__/new_architecture-test.rb b/scripts/cocoapods/__tests__/new_architecture-test.rb new file mode 100644 index 00000000000000..1188b3add31eff --- /dev/null +++ b/scripts/cocoapods/__tests__/new_architecture-test.rb @@ -0,0 +1,133 @@ +# 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_relative "../new_architecture.rb" +require_relative "./test_utils/InstallerMock.rb" +require_relative "./test_utils/PodMock.rb" + +class NewArchitectureTests < Test::Unit::TestCase + def setup + File.enable_testing_mode! + end + + def teardown + Pod::UI.reset() + end + + + def test_setClangCxxLanguageStandardIfNeeded_whenReactCoreIsPresent + installer = prepare_mocked_installer_with_react_core + set_clang_cxx_language_standard_if_needed(installer) + + assert_equal(installer.aggregate_targets[0].user_project.build_configurations[0].build_settings["CLANG_CXX_LANGUAGE_STANDARD"], "c++17") + assert_equal(installer.aggregate_targets[1].user_project.build_configurations[0].build_settings["CLANG_CXX_LANGUAGE_STANDARD"], "c++17") + assert_equal(installer.pods_project.targets[1].received_resolved_build_setting_parameters, [ReceivedCommonResolvedBuildSettings.new("CLANG_CXX_LANGUAGE_STANDARD", true)]) + assert_equal(Pod::UI.collected_messages, ["Setting CLANG_CXX_LANGUAGE_STANDARD to c++17 on /test/path.xcproj", "Setting CLANG_CXX_LANGUAGE_STANDARD to c++17 on /test/path2.xcproj"]) + end + + def test_setClangCxxLanguageStandardIfNeeded_whenReactCoreIsNotPresent + installer = prepare_mocked_installer_without_react_core + set_clang_cxx_language_standard_if_needed(installer) + + assert_equal(installer.aggregate_targets[0].user_project.build_configurations[0].build_settings["CLANG_CXX_LANGUAGE_STANDARD"], nil) + assert_equal(installer.aggregate_targets[1].user_project.build_configurations[0].build_settings["CLANG_CXX_LANGUAGE_STANDARD"], nil) + assert_equal(installer.pods_project.targets[0].received_resolved_build_setting_parameters, []) + assert_equal(Pod::UI.collected_messages, []) + end + + def test_setClangCxxLanguageStandardIfNeeded_whenThereAreDifferentValuesForLanguageStandard_takesTheFirstValue + installer = prepare_mocked_installer_with_react_core_and_different_language_standards + set_clang_cxx_language_standard_if_needed(installer) + + assert_equal(installer.aggregate_targets[0].user_project.build_configurations[0].build_settings["CLANG_CXX_LANGUAGE_STANDARD"], "c++17") + assert_equal(installer.aggregate_targets[1].user_project.build_configurations[0].build_settings["CLANG_CXX_LANGUAGE_STANDARD"], "c++17") + assert_equal(installer.pods_project.targets[1].received_resolved_build_setting_parameters, [ReceivedCommonResolvedBuildSettings.new("CLANG_CXX_LANGUAGE_STANDARD", true)]) + assert_equal(Pod::UI.collected_messages, ["Setting CLANG_CXX_LANGUAGE_STANDARD to c++17 on /test/path.xcproj", "Setting CLANG_CXX_LANGUAGE_STANDARD to c++17 on /test/path2.xcproj"]) + end +end + +def prepare_mocked_installer_with_react_core + return InstallerMock.new( + PodsProjectMock.new([ + TargetMock.new( + "YogaKit", + [ + BuildConfigurationMock.new("Debug"), + BuildConfigurationMock.new("Release"), + ] + ), + TargetMock.new( + "React-Core", + [ + BuildConfigurationMock.new("Debug", { "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" }), + BuildConfigurationMock.new("Release", { "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" }), + ] + ) + ] + ), + [ + AggregatedProjectMock.new( + UserProjectMock.new("/test/path.xcproj", [BuildConfigurationMock.new("Debug")]) + ), + AggregatedProjectMock.new( + UserProjectMock.new("/test/path2.xcproj", [BuildConfigurationMock.new("Debug")]) + ), + ] + ) +end + +def prepare_mocked_installer_with_react_core_and_different_language_standards + return InstallerMock.new( + PodsProjectMock.new([ + TargetMock.new( + "YogaKit", + [ + BuildConfigurationMock.new("Debug"), + BuildConfigurationMock.new("Release"), + ] + ), + TargetMock.new( + "React-Core", + [ + BuildConfigurationMock.new("Debug", { "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" }), + BuildConfigurationMock.new("Release", { "CLANG_CXX_LANGUAGE_STANDARD" => "new" }), + ] + ) + ] + ), + [ + AggregatedProjectMock.new( + UserProjectMock.new("/test/path.xcproj", [BuildConfigurationMock.new("Debug")]) + ), + AggregatedProjectMock.new( + UserProjectMock.new("/test/path2.xcproj", [BuildConfigurationMock.new("Debug")]) + ), + ] + ) +end + +def prepare_mocked_installer_without_react_core + return InstallerMock.new( + PodsProjectMock.new([ + TargetMock.new( + "YogaKit", + [ + BuildConfigurationMock.new("Debug"), + BuildConfigurationMock.new("Release"), + ] + ) + ] + ), + [ + AggregatedProjectMock.new( + UserProjectMock.new("/test/path.xcproj", [BuildConfigurationMock.new("Debug")]) + ), + AggregatedProjectMock.new( + UserProjectMock.new("/test/path2.xcproj", [BuildConfigurationMock.new("Debug")]) + ), + ] + ) +end diff --git a/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb b/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb index 856169b679fa95..2366981f688421 100644 --- a/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb @@ -38,9 +38,11 @@ class InstallerMock attr_reader :pods_project + attr_reader :aggregate_targets - def initialize(pods_project = PodsProjectMock.new) + def initialize(pods_project = PodsProjectMock.new, aggregate_targets = [AggregatedProjectMock.new]) @pods_project = pods_project + @aggregate_targets = aggregate_targets end def target_with_name(name) @@ -58,13 +60,45 @@ def initialize(targets = []) end end +class AggregatedProjectMock + attr_reader :user_project + + def initialize(user_project = UserProjectMock.new) + @user_project = user_project + end +end + +class UserProjectMock + attr_reader :path + attr_reader :build_configurations + + def initialize(path = "/test/path.xcproj", build_configurations = []) + @path = path + @build_configurations = build_configurations + end + + def save() + end +end + +ReceivedCommonResolvedBuildSettings = Struct.new(:key, :resolve_against_xcconfig) + class TargetMock attr_reader :name attr_reader :build_configurations + attr_reader :received_resolved_build_setting_parameters + def initialize(name, build_configurations = []) @name = name @build_configurations = build_configurations + @received_resolved_build_setting_parameters = [] + end + + def resolved_build_setting(key, resolve_against_xcconfig: false) + received_resolved_build_setting_parameters.append(ReceivedCommonResolvedBuildSettings.new(key, resolve_against_xcconfig)) + + return {name: build_configurations[0].build_settings[key]} end end diff --git a/scripts/cocoapods/new_architecture.rb b/scripts/cocoapods/new_architecture.rb new file mode 100644 index 00000000000000..82ac79c678abcf --- /dev/null +++ b/scripts/cocoapods/new_architecture.rb @@ -0,0 +1,30 @@ +# 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. + +def set_clang_cxx_language_standard_if_needed(installer) + language_standard = nil + + installer.pods_project.targets.each do |target| + if target.name == 'React-Core' + language_standard = target.resolved_build_setting("CLANG_CXX_LANGUAGE_STANDARD", resolve_against_xcconfig: true).values[0] + end + end + + unless language_standard.nil? + projects = installer.aggregate_targets + .map{ |t| t.user_project } + .uniq{ |p| p.path } + + projects.each do |project| + Pod::UI.puts("Setting CLANG_CXX_LANGUAGE_STANDARD to #{ language_standard } on #{ project.path }") + + project.build_configurations.each do |config| + config.build_settings["CLANG_CXX_LANGUAGE_STANDARD"] = language_standard + end + + project.save() + end + end +end