From 811cc00f0cda38f587a255af1b516695bda1db9c Mon Sep 17 00:00:00 2001 From: Bartosz Polaczyk Date: Sat, 3 Jun 2023 17:59:37 -0700 Subject: [PATCH] Add E2E tests for swift driver integration --- Rakefile | 13 ++- .../cocoapods-xcremotecache/command/hooks.rb | 15 ++-- .../cocoapods-xcremotecache/gem_version.rb | 2 +- tasks/e2e.rb | 84 +++++++++++++------ 4 files changed, 78 insertions(+), 36 deletions(-) diff --git a/Rakefile b/Rakefile index 3934c77f..45a4e867 100644 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,7 @@ DERIVED_DATA_DIR = File.join('.build').freeze RELEASES_ROOT_DIR = File.join('releases').freeze EXECUTABLE_NAME = 'XCRemoteCache' -EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus', 'xclipo'] +EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'swiftc', 'xcswift-frontend', 'swift-frontend', 'xcld', 'xcldplusplus', 'xclipo'] PROJECT_NAME = 'XCRemoteCache' SWIFTLINT_ENABLED = true @@ -59,6 +59,10 @@ task :build, [:configuration, :arch, :sdks, :is_archive] do |task, args| # Path of the executable looks like: `.build/(debug|release)/XCRemoteCache` build_path_base = File.join(DERIVED_DATA_DIR, args.configuration) + # swift-frontent integration requires that the SWIFT_EXEC is `swiftc` so create + # a symbolic link between swiftc->xcswiftc and swift-frontend->xcswift-frontend + system("cd #{build_path_base} && ln -s xcswiftc swiftc") + system("cd #{build_path_base} && ln -s xcswift-frontend swift-frontend") sdk_build_paths = EXECUTABLE_NAMES.map {|e| File.join(build_path_base, e)} build_paths.push(sdk_build_paths) @@ -130,7 +134,9 @@ def create_release_zip(build_paths) # Create and move files into the release directory mkdir_p release_dir build_paths.each {|p| - cp_r p, release_dir + # -r for recursive + # -P for copying symbolic link as is + system("cp -rP #{p} #{release_dir}") } output_artifact_basename = "#{PROJECT_NAME}.zip" @@ -139,7 +145,8 @@ def create_release_zip(build_paths) # -X: no extras (uid, gid, file times, ...) # -x: exclude .DS_Store # -r: recursive - system("zip -X -x '*.DS_Store' -r #{output_artifact_basename} .") or abort "zip failure" + # -y: to store symbolic links (used for swiftc -> xcswiftc) + system("zip -X -x '*.DS_Store' -r -y #{output_artifact_basename} .") or abort "zip failure" # List contents of zip file system("unzip -l #{output_artifact_basename}") or abort "unzip failure" end diff --git a/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb b/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb index 7a270f77..3c1c3110 100644 --- a/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb +++ b/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb @@ -123,7 +123,8 @@ def self.enable_xcremotecache( exclude_build_configurations, final_target, fake_src_root, - exclude_sdks_configurations + exclude_sdks_configurations, + enable_swift_driver_integration ) srcroot_relative_xc_location = parent_dir(xc_location, repo_distance) # location of the entrite CocoaPods project, relative to SRCROOT @@ -137,14 +138,15 @@ def self.enable_xcremotecache( elsif mode == 'producer' || mode == 'producer-fast' config.build_settings.delete('CC') if config.build_settings.key?('CC') end - reset_build_setting(config.build_settings, 'SWIFT_EXEC', "$SRCROOT/#{srcroot_relative_xc_location}/xcswiftc", exclude_sdks_configurations) + swiftc_name = enable_swift_driver_integration ? 'swiftc' : 'xcswiftc' + reset_build_setting(config.build_settings, 'SWIFT_EXEC', "$SRCROOT/#{srcroot_relative_xc_location}/#{swiftc_name}", exclude_sdks_configurations) reset_build_setting(config.build_settings, 'LIBTOOL', "$SRCROOT/#{srcroot_relative_xc_location}/xclibtool", exclude_sdks_configurations) # Setting LIBTOOL to '' breaks SwiftDriver intengration so resetting it to the original value 'libtool' for all excluded configurations add_build_setting_for_sdks(config.build_settings, 'LIBTOOL', 'libtool', exclude_sdks_configurations) reset_build_setting(config.build_settings, 'LD', "$SRCROOT/#{srcroot_relative_xc_location}/xcld", exclude_sdks_configurations) reset_build_setting(config.build_settings, 'LDPLUSPLUS', "$SRCROOT/#{srcroot_relative_xc_location}/xcldplusplus", exclude_sdks_configurations) reset_build_setting(config.build_settings, 'LIPO', "$SRCROOT/#{srcroot_relative_xc_location}/xclipo", exclude_sdks_configurations) - reset_build_setting(config.build_settings, 'SWIFT_USE_INTEGRATED_DRIVER', 'NO', exclude_sdks_configurations) + reset_build_setting(config.build_settings, 'SWIFT_USE_INTEGRATED_DRIVER', 'NO', exclude_sdks_configurations) unless enable_swift_driver_integration reset_build_setting(config.build_settings, 'XCREMOTE_CACHE_FAKE_SRCROOT', fake_src_root, exclude_sdks_configurations) reset_build_setting(config.build_settings, 'XCRC_PLATFORM_PREFERRED_ARCH', "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)", exclude_sdks_configurations) @@ -498,6 +500,7 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) check_platform = @@configuration['check_platform'] fake_src_root = @@configuration['fake_src_root'] exclude_sdks_configurations = @@configuration['exclude_sdks_configurations'] || [] + enable_swift_driver_integration = @@configuration['enable_swift_driver_integration'] || false xccc_location_absolute = "#{user_proj_directory}/#{xccc_location}" xcrc_location_absolute = "#{user_proj_directory}/#{xcrc_location}" @@ -521,7 +524,7 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) next if target.name.start_with?("Pods-") next if target.name.end_with?("Tests") next if exclude_targets.include?(target.name) - enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations) + enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations, enable_swift_driver_integration) end # Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location @@ -534,7 +537,7 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) next if target.source_build_phase.files_references.empty? next if target.name.end_with?("Tests") next if exclude_targets.include?(target.name) - enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations) + enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations, enable_swift_driver_integration) end generated_project.save() end @@ -575,7 +578,7 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) # Attach XCRC to the app targets user_project.targets.each do |target| next if exclude_targets.include?(target.name) - enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations) + enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations, enable_swift_driver_integration) end # Set Target sourcemap diff --git a/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb b/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb index ddf31972..b2acb8e4 100644 --- a/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb +++ b/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb @@ -13,5 +13,5 @@ # limitations under the License. module CocoapodsXcremotecache - VERSION = "0.0.16" + VERSION = "0.0.17" end diff --git a/tasks/e2e.rb b/tasks/e2e.rb index 9aa9dbd6..3d096009 100644 --- a/tasks/e2e.rb +++ b/tasks/e2e.rb @@ -1,5 +1,6 @@ require 'json' require "ostruct" +require 'yaml' desc 'Support for E2E tests: building XCRemoteCache-enabled xcodeproj using xcodebuild' namespace :e2e do @@ -25,12 +26,21 @@ 'primary_branch' => GIT_BRANCH, 'mode' => 'consumer', 'final_target' => 'XCRemoteCacheSample', - 'artifact_maximum_age' => 0 + 'artifact_maximum_age' => 0, + + }.freeze + # A list of configurations to merge with SHARED_COCOAPODS_CONFIG to run tests with + CONFIGS = { + 'no_swift_driver' => {}, + 'swift_driver' => { + 'enable_swift_driver_integration' => true + } }.freeze DEFAULT_EXPECTATIONS = { 'misses' => 0, 'hit_rate' => 100 }.freeze + EXCLUDED_ARCHS = 'x86_64' Stats = Struct.new(:hits, :misses, :hit_rate) @@ -43,9 +53,14 @@ start_nginx configure_git - # Run scenarios for all Podfile scenarios - for podfile_path in Dir.glob('e2eTests/**/*.Podfile') - run_cocoapods_scenario(podfile_path) + for config_name, custom_config in CONFIGS + config = SHARED_COCOAPODS_CONFIG.merge(custom_config) + puts "Running E2E tests for config: #{config_name}" + + # Run scenarios for all Podfile scenarios + for podfile_path in Dir.glob('e2eTests/**/*.Podfile') + run_cocoapods_scenario(config, podfile_path) + end end # Revert all side effects clean @@ -56,13 +71,27 @@ clean_server start_nginx configure_git + CONFIGS.each do |config_name, config| + puts "Running Standalone tests for config: #{config_name}" + run_standalone_scenario(config, config_name) + end + end + + def self.run_standalone_scenario(config, config_name) # Prepare binaries for the standalone mode prepare_for_standalone(E2E_STANDALONE_SAMPLE_DIR) puts 'Building standalone producer...' ####### Producer ######### + clean_git + Dir.chdir(E2E_STANDALONE_SAMPLE_DIR) do - clean_git + system 'git checkout -f .' + # Include the config in the "shared" configuration that is commited-in to '.rcinfo' + rcinfo_path = '.rcinfo' + rcinfo = YAML.load(File.read(rcinfo_path)).merge(config) + File.open(rcinfo_path, 'w') {|f| f.write rcinfo.to_yaml } + # Run integrate the project system("pwd") system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode producer --final-producer-target StandaloneApp --configurations-exclude #{CONFIGURATIONS_EXCLUDE}") @@ -76,22 +105,25 @@ ####### Consumer ######### # new dir to emulate different srcroot - consumer_srcroot = "#{E2E_STANDALONE_SAMPLE_DIR}_consumer" + consumer_srcroot = "#{E2E_STANDALONE_SAMPLE_DIR}_consumer_#{config_name}" system("mv #{E2E_STANDALONE_SAMPLE_DIR} #{consumer_srcroot}") - at_exit { puts("reverting #{E2E_STANDALONE_SAMPLE_DIR}"); system("mv #{consumer_srcroot} #{E2E_STANDALONE_SAMPLE_DIR}") } - - prepare_for_standalone(consumer_srcroot) - Dir.chdir(consumer_srcroot) do - system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode consumer --final-producer-target StandaloneApp --consumer-eligible-configurations #{CONFIGURATION} --configurations-exclude #{CONFIGURATIONS_EXCLUDE}") - build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"}) - build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"}) - valide_hit_rate(OpenStruct.new(DEFAULT_EXPECTATIONS)) - - puts 'Building standalone consumer with local change...' - # Extra: validate local compilation of the Standalone ObjC code - system("echo '' >> StandaloneApp/StandaloneObjc.m") - build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"}) - build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"}) + begin + prepare_for_standalone(consumer_srcroot) + Dir.chdir(consumer_srcroot) do + system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode consumer --final-producer-target StandaloneApp --consumer-eligible-configurations #{CONFIGURATION} --configurations-exclude #{CONFIGURATIONS_EXCLUDE}") + build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_#{config_name}"}) + build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_#{config_name}"}) + valide_hit_rate(OpenStruct.new(DEFAULT_EXPECTATIONS)) + + puts 'Building standalone consumer with local change...' + # Extra: validate local compilation of the Standalone ObjC code + system("echo '' >> StandaloneApp/StandaloneObjc.m") + build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local_#{config_name}"}) + build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', CONFIGURATION, {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local_#{config_name}"}) + end + ensure + puts("reverting #{E2E_STANDALONE_SAMPLE_DIR}") + system("mv #{consumer_srcroot} #{E2E_STANDALONE_SAMPLE_DIR}") end # Revert all side effects @@ -153,9 +185,9 @@ def self.clean end # xcremotecache configuration to add to Podfile - def self.cocoapods_configuration_string(extra_configs = {}) + def self.cocoapods_configuration_string(config, extra_configs = {}) configuration_lines = ['xcremotecache({'] - all_properties = SHARED_COCOAPODS_CONFIG.merge(extra_configs) + all_properties = config.merge(extra_configs) config_lines = all_properties.map {|key, value| " \"#{key}\" => #{value.inspect},"} configuration_lines.push(*config_lines) configuration_lines << '})' @@ -182,7 +214,7 @@ def self.build_project(workspace, project, scheme, sdk = 'iphone', platform = 'i 'derivedDataPath' => DERIVED_DATA_PATH, }.merge(extra_args).compact xcodebuild_vars = { - 'EXCLUDED_ARCHS' => 'arm64' + 'EXCLUDED_ARCHS' => EXCLUDED_ARCHS } args = ['set -o pipefail;', 'xcodebuild'] args.push(*xcodebuild_args.map {|k,v| "-#{k} '#{v}'"}) @@ -227,12 +259,12 @@ def self.valide_hit_rate(expectations) puts("Hit rate: #{status.hit_rate}% (#{status.hits}/#{all_targets})") end - def self.run_cocoapods_scenario(template_path) + def self.run_cocoapods_scenario(config, template_path) # Optional file, which adds extra cocoapods configs to a template template_config_path = "#{template_path}.config" extra_config = File.exist?(template_config_path) ? JSON.load(File.read(template_config_path)) : {} - producer_configuration = cocoapods_configuration_string({'mode' => 'producer'}.merge(extra_config)) - consumer_configuration = cocoapods_configuration_string(extra_config) + producer_configuration = cocoapods_configuration_string(config, {'mode' => 'producer'}.merge(extra_config)) + consumer_configuration = cocoapods_configuration_string(config, extra_config) expectations = build_expectations(template_path) puts("****** Scenario: #{template_path}")