diff --git a/.gitignore b/.gitignore index 3ff72b28b1..238501bf44 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,9 @@ /node_modules /libwebrtc /args.txt + + +# Stream specifics +fastlane/vendor +fastlane/*.xml +out_ios_libs diff --git a/README.md b/README.md index f35e1cf6da..9a93dca1ed 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,20 @@ This repository is a fork of the WebRTC project. The original README can be found [here](README_webrtc.md). +### Fork Specifics + +#### `.gitignore` + +Due to the fork specifics, the repo's `.gitignore` has been updated to match the fork's requirements. + +#### Building Tools + +The fork contains a `fastlane` pipeline to produce builds for iOS. To access the pipeline you need to switch into `src/fastlane` and execute `bundle exec fastlane lanes` to see the available lanes. + +##### Building for iOS + +- Build the WebRTC library for iOS `bundle exec fastlane ios build` + ### License - [WebRTC](https://webrtc.org) software is licensed under the [BSD license](https://github.com/GetStream/webrtc/blob/main/LICENSE). - Includes patches from [shiguredo-webrtc-build](https://github.com/shiguredo-webrtc-build), licensed under the [Apache 2.0](https://github.com/shiguredo-webrtc-build/webrtc-build/blob/master/LICENSE). diff --git a/fastlane/.env b/fastlane/.env new file mode 100644 index 0000000000..6230f91510 --- /dev/null +++ b/fastlane/.env @@ -0,0 +1,7 @@ + +FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=180 +FASTLANE_XCODEBUILD_SETTINGS_RETRIES=10 +FASTLANE_SKIP_ACTION_SUMMARY=true +FASTLANE_HIDE_PLUGINS_TABLE=true +FASTLANE_SKIP_UPDATE_CHECK=true +FASTLANE_HIDE_CHANGELOG=true diff --git a/fastlane/.rubocop.yml b/fastlane/.rubocop.yml new file mode 100644 index 0000000000..8336cfa33a --- /dev/null +++ b/fastlane/.rubocop.yml @@ -0,0 +1,170 @@ +--- +require: +- rubocop/require_tools +- rubocop-performance +AllCops: + TargetRubyVersion: 2.4 + NewCops: enable + Include: + - "**/*.rb" + - "**/*file" +Style/MultipleComparison: + Enabled: false +Style/PercentLiteralDelimiters: + Enabled: false +Style/ClassCheck: + EnforcedStyle: kind_of? +Style/FrozenStringLiteralComment: + Enabled: false +Style/SafeNavigation: + Enabled: false +Performance/RegexpMatch: + Enabled: false +Performance/StringReplacement: + Enabled: false +Style/NumericPredicate: + Enabled: false +Metrics/BlockLength: + Enabled: false +Metrics/ModuleLength: + Enabled: false +Naming/VariableNumber: + Enabled: false +Style/MissingRespondToMissing: + Enabled: false +Style/MultilineBlockChain: + Enabled: false +Style/NumericLiteralPrefix: + Enabled: false +Style/TernaryParentheses: + Enabled: false +Style/EmptyMethod: + Enabled: false +Lint/UselessAssignment: + Exclude: + - "**/spec/**/*" +Require/MissingRequireStatement: + Enabled: false +Layout/FirstHashElementIndentation: + Enabled: false +Layout/HashAlignment: + Enabled: false +Style/HashLikeCase: + Enabled: false +Layout/DotPosition: + Enabled: false +Style/DoubleNegation: + Enabled: false +Style/SymbolArray: + Enabled: false +Layout/HeredocIndentation: + Enabled: false +Style/MixinGrouping: + Exclude: + - "**/spec/**/*" +Lint/SuppressedException: + Enabled: false +Lint/UnusedBlockArgument: + Enabled: false +Lint/AmbiguousBlockAssociation: + Enabled: false +Style/GlobalVars: + Enabled: false +Style/ClassAndModuleChildren: + Enabled: false +Style/SpecialGlobalVars: + Enabled: false +Metrics/AbcSize: + Enabled: false +Metrics/MethodLength: + Enabled: false +Metrics/CyclomaticComplexity: + Enabled: false +Style/WordArray: + MinSize: 19 +Style/SignalException: + Enabled: false +Style/RedundantReturn: + Enabled: false +Style/IfUnlessModifier: + Enabled: false +Style/AndOr: + Enabled: true + EnforcedStyle: conditionals +Metrics/ClassLength: + Max: 320 +Layout/LineLength: + Max: 370 +Metrics/ParameterLists: + Max: 17 +Metrics/PerceivedComplexity: + Max: 20 +Style/GuardClause: + Enabled: false +Style/StringLiterals: + Enabled: false +Style/ConditionalAssignment: + Enabled: false +Style/RedundantSelf: + Enabled: false +Lint/UnusedMethodArgument: + Enabled: false +Lint/ParenthesesAsGroupedExpression: + Exclude: + - "**/spec/**/*" +Naming/PredicateName: + Enabled: false +Style/PerlBackrefs: + Enabled: false +Naming/FileName: + Exclude: + - "**/Dangerfile" + - "**/Brewfile" + - "**/Gemfile" + - "**/Podfile" + - "**/Rakefile" + - "**/Fastfile" + - "**/Scanfile" + - "**/Matchfile" + - "**/Appfile" + - "**/Allurefile" + - "**/Sonarfile" + - "**/Deliverfile" + - "**/Snapfile" + - "**/Pluginfile" + - "**/*.gemspec" +Style/Documentation: + Enabled: false +Style/MutableConstant: + Enabled: false +Style/ZeroLengthPredicate: + Enabled: false +Style/IfInsideElse: + Enabled: false +Style/CollectionMethods: + Enabled: false +Style/MethodCallWithArgsParentheses: + Enabled: true + IgnoredMethods: + - require + - require_relative + - fastlane_require + - gem + - program + - command + - raise + - attr_accessor + - attr_reader + - desc + - lane + - private_lane + - platform + - to + - not_to + - describe + - it + - be + - context + - before + - after + - and diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000000..78eda3a824 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,18 @@ +skip_docs +opt_out_usage + +require 'json' +require 'net/http' +require 'fileutils' + +import './lanes/utilities.rb' +import './lanes/gclient.rb' +import './lanes/ios.rb' + +before_all do + if is_ci + setup_ci + setup_git_config + end + sh('bundle exec rubocop') +end diff --git a/fastlane/Gemfile b/fastlane/Gemfile new file mode 100644 index 0000000000..5371f20543 --- /dev/null +++ b/fastlane/Gemfile @@ -0,0 +1,17 @@ +source 'https://rubygems.org' + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +gem 'cocoapods' +gem 'fastlane' +gem 'json' +gem 'plist' +gem 'rubocop', '1.38', group: :rubocop_dependencies + +group :rubocop_dependencies do + gem 'rubocop-performance' + gem 'rubocop-require_tools' +end + +plugins_path = File.join(File.dirname(__FILE__), 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/fastlane/Gemfile.lock b/fastlane/Gemfile.lock new file mode 100644 index 0000000000..29164fce8e --- /dev/null +++ b/fastlane/Gemfile.lock @@ -0,0 +1,340 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.2.2) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + artifactory (3.0.17) + ast (2.4.3) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1168.0) + aws-sdk-core (3.233.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.113.0) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.199.1) + aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + claide (1.1.0) + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.4) + declarative (0.0.20) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + drb (2.2.3) + emoji_regex (3.2.3) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + excon (0.112.0) + faraday (1.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.228.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-stream_actions (0.3.90) + xctest_list (= 1.2.1) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + ffi (1.17.2-arm64-darwin) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.5.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) + concurrent-ruby (~> 1.0) + jmespath (1.6.2) + json (2.15.0) + jwt (2.10.2) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.25.5) + molinillo (0.8.0) + multi_json (1.17.0) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + nap (1.1.0) + naturally (2.3.0) + netrc (0.11.0) + nkf (0.2.0) + optparse (0.6.0) + os (1.1.4) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + plist (3.7.2) + prism (1.5.1) + public_suffix (4.0.7) + racc (1.8.1) + rainbow (3.1.1) + rake (13.3.0) + regexp_parser (2.11.3) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.4.4) + rouge (3.28.0) + rubocop (1.38.0) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.1.2.1) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.23.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.47.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-performance (1.19.1) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-require_tools (0.1.2) + rubocop (>= 0.49.1) + ruby-macho (2.5.1) + ruby-progressbar (1.13.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + securerandom (0.4.1) + security (0.1.5) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + xctest_list (1.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + fastlane + fastlane-plugin-stream_actions (= 0.3.90) + json + plist + rubocop (= 1.38) + rubocop-performance + rubocop-require_tools + +BUNDLED WITH + 2.3.3 diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 0000000000..f26ef457be --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1,5 @@ +git_url("git@github.com:GetStream/ios-certificates.git") + +storage_mode("git") + +team_id("EHV7XZLAHA") diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000000..f545710b61 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-stream_actions', '0.3.90' diff --git a/fastlane/lanes/gclient.rb b/fastlane/lanes/gclient.rb new file mode 100644 index 0000000000..751b621e4b --- /dev/null +++ b/fastlane/lanes/gclient.rb @@ -0,0 +1,64 @@ +require 'fileutils' + +private_lane :configure_gclient do |options| + log_debug( + message: 'Configuring gclient...', + verbose: options[:verbose] + ) + + base_root = File.expand_path('../../../', __dir__) + root = if options[:output] + File.expand_path(options[:output], base_root) + else + File.join(base_root, '.output') + end + FileUtils.mkdir_p(root) + + Dir.chdir(root) do + # Set gclient root + execute_command(command: 'gclient root', verbose: options[:verbose]) + + # Configure gclient with the spec + target_oses = Array(options[:target_os]).compact.map(&:to_s) + target_os_entries = target_oses.map { |os| "\"#{os}\"" }.join(', ') + gclient_spec = <<~SPEC + solutions = [ + { + "name": "src", + "url": "git@github.com:GetStream/webrtc.git", + "deps_file": "DEPS", + "managed": False, + "custom_deps": {}, + }, + ] + target_os = [#{target_os_entries}] + SPEC + + # Write spec to temporary file and configure + execute_command( + command: "gclient config --spec '#{gclient_spec.gsub("'", "'\"'\"'")}'", + verbose: options[:verbose] + ) + + UI.success('gclient configured successfully') + + sync_dependencies( + number_of_jobs: options[:number_of_jobs], + verbose: options[:verbose] + ) + end +end + +private_lane :sync_dependencies do |options| + jobs = options[:number_of_jobs] || 8 + log_debug( + message: 'Syncing dependencies...', + verbose: options[:verbose] + ) + + command = "gclient sync -j#{jobs}" + command += ' -v' if options[:verbose] + + execute_command(command: command, verbose: options[:verbose]) + UI.success('Dependencies synced successfully') +end diff --git a/fastlane/lanes/ios.rb b/fastlane/lanes/ios.rb new file mode 100644 index 0000000000..3c8d079794 --- /dev/null +++ b/fastlane/lanes/ios.rb @@ -0,0 +1,365 @@ +require 'fileutils' +require 'shellwords' +fastlane_require 'fastlane-plugin-stream_actions' + +platform :ios do + desc 'Sync dependencies and build the WebRTC iOS libraries' + + lane :build do |options| + options[:root] = File.expand_path('../../..', __dir__) + options[:build_root] = File.join(options[:root], '.output') + options[:products_root] = File.join(options[:root], '.products') + options[:sdk_name] = 'WebRTC' + options[:product_source] = File.join(options[:root], "src/out_ios_libs/#{options[:sdk_name]}.xcframework") + options[:build_tool] = File.join(options[:root], 'src/tools_webrtc/ios/build_ios_libs.py') + options[:rename_to_sdk_name] = "Stream#{options[:sdk_name]}" + options[:product] = File.join(options[:products_root], "#{options[:rename_to_sdk_name]}.xcframework") + options[:match_file] = File.join(options[:root], 'src/fastlane/Matchfile') + catalyst_option = options.key?(:maccatalyst_support) ? options[:maccatalyst_support] : true + options[:maccatalyst_support] = !%w[false 0 off no].include?(catalyst_option.to_s.downcase) + + log_info(message: "Root: #{options[:root]}") + log_info(message: "Build root: #{options[:build_root]}") + log_info(message: "Products root: #{options[:products_root]}") + log_info(message: "Product source: #{options[:product_source]}") + log_info(message: "Build tool: #{options[:build_tool]}") + log_info(message: "Matchfile: #{options[:match_file]}") + log_info(message: "Product: #{options[:product]}") + log_info(message: "Mac Catalyst support: #{options[:maccatalyst_support]}") + + clean_up_products(options) + verify_environment(options) + configure_google_client(options) + build_product(options) + move_product(options) + rename_product(options) + prepare_signing(options) + sign_product(options) + verify_signatures(options) + zip_product(options) + end + + lane :clean_up_products do |options| + lane_options = extract_prefixed_options(options, 'clean_up_products') + next unless lane_options[:skip] != true + + log_debug(message: 'Cleaning up products', verbose: options[:verbose]) + + products_root = options[:products_root] + assert(message: 'Missing required option :products_root') if products_root.to_s.strip.empty? + + return unless Dir.exist?(products_root) + + log_info(message: "Cleaning products directory at #{products_root}") + Dir.children(products_root).each do |entry| + FileUtils.rm_rf(File.join(products_root, entry)) + end + end + + lane :verify_environment do |options| + lane_options = extract_prefixed_options(options, 'verify_environment') + next unless lane_options[:skip] != true + + verify_build_environment(verbose: options[:verbose]) + + ensure_required_tool(tool: 'xcodebuild', verbose: options[:verbose]) + + # Check if we're on macOS + log_error(message: 'iOS builds require macOS') unless RUBY_PLATFORM.include?('darwin') + + # Check if Xcode is installed + log_error(message: 'Xcode command line tools not found') unless system('xcode-select -p > /dev/null 2>&1') + end + + lane :configure_google_client do |options| + lane_options = extract_prefixed_options(options, 'configure_google_client') + next if lane_options[:skip] == true + + target_os = ['ios'] + target_os << 'mac' if options[:maccatalyst_support] + log_info(message: "Configuring gclient target_os: #{target_os.join(', ')}") + + configure_gclient( + target_os: target_os, + verbose: options[:verbose], + number_of_jobs: options[:number_of_jobs], + output: options[:build_root] + ) + end + + lane :build_product do |options| + lane_options = extract_prefixed_options(options, 'build_product') + next if lane_options[:skip] == true + + deployment_target = options[:deployment_target] || '13.0' + maccatalyst_support = options[:maccatalyst_support] + + args_list = resolve_build_product_args( + args: extract_prefixed_options(lane_options, 'arg'), + verbose: options[:verbose] + ) + + script_path = options[:build_tool] + Dir.chdir(options[:build_root]) do + command_parts = ["\"#{script_path}\""] + command_parts << "--deployment-target #{deployment_target}" + archs = ['device:arm64', 'simulator:arm64', 'simulator:x64'] + archs += ['catalyst:arm64', 'catalyst:x64'] if maccatalyst_support + command_parts << '--arch' + command_parts.concat(archs) + command_parts << '--extra-gn-args' + command_parts.concat(args_list) + + execute_command( + command: command_parts.join(' '), + verbose: options[:verbose] + ) + end + end + + lane :move_product do |options| + lane_options = extract_prefixed_options(options, 'move_product') + next if lane_options[:skip] == true + + product_source = options[:product_source] + assert(message: 'Missing required option :product_source') if product_source.to_s.strip.empty? + assert(message: "Product not found at #{product_source}") unless File.exist?(product_source) + + product_destination = options[:products_root] + assert(message: 'Missing required option :products_root') if product_destination.to_s.strip.empty? + + FileUtils.mkdir_p(product_destination) + + destination_path = File.join(product_destination, File.basename(product_source)) + log_info(message: "Moving product from #{product_source} to #{destination_path}") + FileUtils.mv(product_source, destination_path) + + File.expand_path(destination_path) + end + + lane :rename_product do |options| + lane_options = extract_prefixed_options(options, 'rename_product') + next if lane_options[:skip] == true + + product_source = options[:product_source] + assert(message: 'Missing required option :product_source') if product_source.to_s.strip.empty? + products_root = options[:products_root] + assert(message: 'Missing required option :products_root') if products_root.to_s.strip.empty? + product_destination = File.join(products_root, File.basename(product_source)) + assert(message: 'Missing required option :product_destination') if product_destination.to_s.strip.empty? + assert(message: "Product not found at #{product_destination}") unless File.exist?(product_destination) + sdk_name = options[:sdk_name] + assert(message: 'Missing required option :sdk_name') if sdk_name.to_s.strip.empty? + modified_sdk_name = options[:rename_to_sdk_name] + assert(message: 'Missing required option :rename_to_sdk_name') if modified_sdk_name.to_s.strip.empty? + + old_framework_path = product_destination + new_framework_path = File.join(products_root, "#{modified_sdk_name}.xcframework") + + # Rename the framework itself + sh("cp -R #{old_framework_path} #{new_framework_path}") + + # Rename all files with the old framework name with the new one + ["#{sdk_name}.framework", "#{sdk_name}.h", sdk_name].each do |file_name| + Dir.glob("#{new_framework_path}/**/*").each do |old_file_path| + next unless File.basename(old_file_path) == file_name + + new_file_path = old_file_path.reverse.sub(sdk_name.reverse, modified_sdk_name.reverse).reverse + File.rename(old_file_path, new_file_path) + end + end + + # Replace all occurrences of the old framework name with the new one in the plist and modulemap files + Dir.glob(["#{new_framework_path}/**/Info.plist", "#{new_framework_path}/**/module.modulemap"]).each do |file| + sh("plutil -convert xml1 #{file}") if file.include?('Info.plist') + old_text = File.read(file) + new_text = old_text.gsub(/#{sdk_name}/, modified_sdk_name) + File.open(file, 'w') { |f| f.puts(new_text) } if old_text != new_text + end + + # Replace all imports of the old framework with the new one + Dir.glob("#{new_framework_path}/**/*.h").each do |file| + old_text = File.read(file) + new_text = old_text.gsub(/import <#{sdk_name}/, "import <#{modified_sdk_name}") + File.open(file, 'w') { |f| f.puts(new_text) } if old_text != new_text + end + + # Rename the rpath for all the frameworks and update symlinks if required + framework_paths = new_framework_path.include?('.xcframework') ? Dir.glob("#{new_framework_path}/**/*.framework") : [new_framework_path] + framework_paths.each do |path| + Dir.chdir(path) do + if File.symlink?(modified_sdk_name) + old_symlink = File.readlink(modified_sdk_name) + new_symlink = old_symlink.reverse.sub(sdk_name.reverse, modified_sdk_name.reverse).reverse + + File.delete(modified_sdk_name) + File.symlink(new_symlink, modified_sdk_name) + end + + sh("install_name_tool -id @rpath/#{modified_sdk_name}.framework/#{modified_sdk_name} #{modified_sdk_name}") + end + end + new_framework_path + end + + lane :prepare_signing do |options| + lane_options = extract_prefixed_options(options, 'prepare_signing') + next unless lane_options[:skip] == true + + custom_match( + api_key: appstore_api_key, + app_identifier: ['io.getstream.iOS.VideoDemoApp'], # dummy app to get the certificates + readonly: true + ) + end + + lane :sign_product do |options| + lane_options = extract_prefixed_options(options, 'sign_product') + next if lane_options[:skip] == true + + matchfile = options[:match_file] + assert(message: 'Missing required option :match_file') if matchfile.to_s.strip.empty? + + team_id = File.read(matchfile).match(/team_id\("(.*)"\)/)[1] + frameworks = Dir.glob("#{options[:product]}/**/*.framework") + signing_identity = "'Apple Distribution: Stream.io Inc (#{team_id})'" + frameworks.each do |framework| + execute_command( + command: "/usr/bin/codesign --force --timestamp --deep -v --sign #{signing_identity} \"#{framework}\"", + verbose: options[:verbose] + ) + end + end + + lane :verify_signatures do |options| + lane_options = extract_prefixed_options(options, 'verify_signatures') + next if lane_options[:skip] == true + + product_path = options[:product] + assert(message: 'Missing required option :product') if product_path.to_s.strip.empty? + assert(message: "Product not found at #{product_path}") unless File.exist?(product_path) + + frameworks = Dir.glob("#{product_path}/**/*.framework") + assert(message: 'No frameworks found to validate signatures') if frameworks.empty? + + log_info(message: "Validating code signatures for #{frameworks.count} frameworks") + + frameworks.each do |framework| + execute_command( + command: "/usr/bin/codesign --verify --deep --strict --verbose=2 \"#{framework}\"", + verbose: options[:verbose] + ) + end + end + + lane :zip_product do |options| + lane_options = extract_prefixed_options(options, 'zip_product') + next if lane_options[:skip] == true + + file_path = options[:product] + zip_path = File.join(options[:products_root], "#{options[:rename_to_sdk_name]}.zip") + execute_command( + command: "ditto -c -k --sequesterRsrc --keepParent #{file_path} #{zip_path}", + verbose: options[:verbose] + ) + zip_path + end + + desc 'Compute a checksum for the packaged xcframework zip' + lane :compute_zip_checksum do |options| + lane_options = extract_prefixed_options(options, 'compute_zip_checksum') + next if lane_options[:skip] == true + + ensure_required_tool(tool: 'swift', verbose: options[:verbose]) + + zip_path = lane_options[:zip_path] || options[:zip_path] + if zip_path.to_s.strip.empty? + products_root = options[:products_root] + rename_to_sdk_name = options[:rename_to_sdk_name] + unless products_root.to_s.strip.empty? || rename_to_sdk_name.to_s.strip.empty? + zip_path = File.join(products_root, + "#{rename_to_sdk_name}.zip") + end + end + + assert(message: 'Missing required option :zip_path') if zip_path.to_s.strip.empty? + assert(message: "Zip file not found at #{zip_path}") unless File.exist?(zip_path) + + command = "swift package compute-checksum #{Shellwords.escape(zip_path)}" + checksum = execute_command( + command: command, + verbose: options[:verbose] + ).to_s.strip + + log_info(message: "Checksum for #{zip_path}: #{checksum}") + checksum + end + + private_lane :resolve_build_product_args do |options| + args = options[:args] || {} + + provided_args = [] + + arg_options = args.each_with_object({}) do |(key, value), memo| + key_str = key.to_s + + next if value.nil? + + memo[key_str] = value + end + + arg_options.each do |key, value| + value_str = value.to_s + provided_args << "#{key}=#{value_str}" + end + + default_args = { + 'is_debug' => 'false', + 'use_goma' => 'false', + 'use_rtti' => 'false', + 'rtc_libvpx_build_vp9' => 'true' + } + + args_map = default_args.dup + additional_args = {} + + provided_args.each do |arg| + next if arg.to_s.strip.empty? + + key, value = arg.split('=', 2) + next if key.nil? || key.strip.empty? + + key = key.strip + value = value.nil? ? '' : value.strip + value = 'true' if value.empty? + + if args_map.key?(key) + args_map[key] = value + else + additional_args[key] = value + end + end + + args_list = default_args.keys.map do |key| + value = args_map[key] + value.nil? || value.empty? ? key : "#{key}=#{value}" + end + + additional_args.each do |key, value| + args_list << (value.nil? || value.empty? ? key : "#{key}=#{value}") + end + + log_info(message: "Resolved GN args: #{args_list.join(', ')}") + + args_list + end + + private_lane :appstore_api_key do + @appstore_api_key ||= app_store_connect_api_key( + key_id: 'MT3PRT8TB7', + issuer_id: '69a6de96-0738-47e3-e053-5b8c7c11a4d1', + key_content: ENV.fetch('APPSTORE_API_KEY', nil), + in_house: false + ) + end +end diff --git a/fastlane/lanes/utilities.rb b/fastlane/lanes/utilities.rb new file mode 100644 index 0000000000..18654a64b3 --- /dev/null +++ b/fastlane/lanes/utilities.rb @@ -0,0 +1,64 @@ +private_lane :verify_build_environment do |options| + log_debug( + message: 'Verifying build environment...', + verbose: options[:verbose] + ) + + # Check if required tools are available + ensure_required_tool(tool: 'gclient', verbose: options[:verbose]) + ensure_required_tool(tool: 'python3', verbose: options[:verbose]) + + UI.success('Build environment verified successfully') +end + +private_lane :log_debug do |options| + UI.message(options[:message]) if options[:verbose] +end + +private_lane :log_info do |options| + UI.message(options[:message]) +end + +private_lane :log_success do |options| + UI.success(options[:message]) +end + +private_lane :log_error do |options| + UI.error(options[:message]) +end + +private_lane :assert do |options| + UI.abort_with_message!(options[:message]) +end + +private_lane :ensure_required_tool do |options| + tool = options[:tool] + UI.user_error!("Required tool '#{tool}' not found in PATH") unless system("which #{tool} > /dev/null 2>&1") +end + +private_lane :execute_command do |options| + sh( + options[:command], + print_command: true, + print_command_output: options[:verbose] + ) +end + +def extract_prefixed_options(options, prefix) + return {} if options.nil? + + prefix = prefix.to_s + return {} if prefix.empty? + + prefix = prefix.end_with?('_') ? prefix : "#{prefix}_" + + options.each_with_object({}) do |(key, value), extracted| + next if value.nil? + + key_str = key.to_s + next unless key_str.start_with?(prefix) + + stripped_key = key_str.sub(prefix, '') + extracted[stripped_key.to_sym] = value + end +end diff --git a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm index aad4b2abed..8d64119630 100644 --- a/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm +++ b/sdk/objc/api/peerconnection/RTCAudioDeviceModule.mm @@ -299,7 +299,7 @@ - (NSInteger)initRecording { - (NSInteger)initAndStartRecording { return _workerThread->BlockingCall([self] { webrtc::AudioEngineDevice *engine_device = - dynamic_cast(_native.get()); + static_cast(_native.get()); if (engine_device != nullptr) { return engine_device->InitAndStartRecording(); } else { @@ -326,7 +326,7 @@ - (BOOL)isRecording { } - (BOOL)isEngineRunning { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return false; return _workerThread->BlockingCall([module] { return module->IsEngineRunning(); }); @@ -344,7 +344,7 @@ - (NSInteger)setMicrophoneMuted:(BOOL)muted { } - (RTC_OBJC_TYPE(RTCAudioEngineState))engineState { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return RTC_OBJC_TYPE(RTCAudioEngineState)(); return _workerThread->BlockingCall([module] { @@ -363,7 +363,7 @@ - (NSInteger)setMicrophoneMuted:(BOOL)muted { } - (void)setEngineState:(RTC_OBJC_TYPE(RTCAudioEngineState))state { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return; _workerThread->BlockingCall([module, state] { @@ -382,7 +382,7 @@ - (void)setEngineState:(RTC_OBJC_TYPE(RTCAudioEngineState))state { #pragma mark - Unique to AudioEngineDevice - (BOOL)isRecordingAlwaysPreparedMode { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return NO; return _workerThread->BlockingCall([module] { @@ -392,7 +392,7 @@ - (BOOL)isRecordingAlwaysPreparedMode { } - (NSInteger)setRecordingAlwaysPreparedMode:(BOOL)enabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return -1; return _workerThread->BlockingCall( @@ -400,7 +400,7 @@ - (NSInteger)setRecordingAlwaysPreparedMode:(BOOL)enabled { } - (BOOL)isManualRenderingMode { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return NO; return _workerThread->BlockingCall([module] { @@ -410,7 +410,7 @@ - (BOOL)isManualRenderingMode { } - (NSInteger)setManualRenderingMode:(BOOL)enabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return -1; return _workerThread->BlockingCall( @@ -418,7 +418,7 @@ - (NSInteger)setManualRenderingMode:(BOOL)enabled { } - (BOOL)isAdvancedDuckingEnabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return NO; return _workerThread->BlockingCall([module] { @@ -428,7 +428,7 @@ - (BOOL)isAdvancedDuckingEnabled { } - (void)setAdvancedDuckingEnabled:(BOOL)enabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return; _workerThread->BlockingCall( @@ -436,7 +436,7 @@ - (void)setAdvancedDuckingEnabled:(BOOL)enabled { } - (NSInteger)duckingLevel { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return 0; return _workerThread->BlockingCall([module] { @@ -446,14 +446,14 @@ - (NSInteger)duckingLevel { } - (void)setDuckingLevel:(NSInteger)value { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return; _workerThread->BlockingCall([module, value] { return module->SetDuckingLevel(value) == 0; }); } - (RTC_OBJC_TYPE(RTCAudioEngineMuteMode))muteMode { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return RTC_OBJC_TYPE(RTCAudioEngineMuteModeUnknown); return _workerThread->BlockingCall([module] { @@ -464,7 +464,7 @@ - (void)setDuckingLevel:(NSInteger)value { } - (NSInteger)setMuteMode:(RTC_OBJC_TYPE(RTCAudioEngineMuteMode))mode { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return -1; return _workerThread->BlockingCall( @@ -472,7 +472,7 @@ - (NSInteger)setMuteMode:(RTC_OBJC_TYPE(RTCAudioEngineMuteMode))mode { } - (BOOL)isVoiceProcessingEnabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return NO; return _workerThread->BlockingCall([module] { @@ -482,7 +482,7 @@ - (BOOL)isVoiceProcessingEnabled { } - (NSInteger)setVoiceProcessingEnabled:(BOOL)enabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return -1; return _workerThread->BlockingCall( @@ -490,7 +490,7 @@ - (NSInteger)setVoiceProcessingEnabled:(BOOL)enabled { } - (BOOL)isVoiceProcessingBypassed { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return NO; return _workerThread->BlockingCall([module] { @@ -500,7 +500,7 @@ - (BOOL)isVoiceProcessingBypassed { } - (void)setVoiceProcessingBypassed:(BOOL)enabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return; _workerThread->BlockingCall( @@ -508,7 +508,7 @@ - (void)setVoiceProcessingBypassed:(BOOL)enabled { } - (BOOL)isVoiceProcessingAGCEnabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return NO; return _workerThread->BlockingCall([module] { @@ -518,7 +518,7 @@ - (BOOL)isVoiceProcessingAGCEnabled { } - (void)setVoiceProcessingAGCEnabled:(BOOL)enabled { - webrtc::AudioEngineDevice *module = dynamic_cast(_native.get()); + webrtc::AudioEngineDevice *module = static_cast(_native.get()); if (module == nullptr) return; _workerThread->BlockingCall(